Skip to content

Commit e90ec16

Browse files
fix operator precedence
1 parent 5ccfa65 commit e90ec16

3 files changed

Lines changed: 92 additions & 14 deletions

File tree

hcl2/rule_transformer/hcl2.lark

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ FLOAT_LITERAL: (NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+) "." DECIMAL+ (EX
2424
| (NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+) (EXP_MARK)
2525

2626
// Operators
27-
BINARY_OP : DOUBLE_EQ | NEQ | LT | GT | LEQ | GEQ | MINUS | ASTERISK | SLASH | PERCENT | DOUBLE_AMP | DOUBLE_PIPE | PLUS
2827
DOUBLE_EQ : "=="
2928
NEQ : "!="
3029
LT : "<"
@@ -99,16 +98,61 @@ string_part: STRING_CHARS
9998
| interpolation
10099

101100
// Expressions
102-
?expression : expr_term | operation | conditional
101+
?expression : or_expr QMARK new_line_or_comment? expression new_line_or_comment? COLON new_line_or_comment? expression -> conditional
102+
| or_expr
103103
interpolation: INTERP_START expression RBRACE
104-
conditional : expression QMARK new_line_or_comment? expression new_line_or_comment? COLON new_line_or_comment? expression
105104

106-
// Operations
107-
?operation : unary_op | binary_op
105+
// Operator precedence ladder (lowest to highest)
106+
// Each level uses left recursion for left-associativity.
107+
// Rule aliases (-> binary_op, -> binary_term, -> binary_operator) maintain
108+
// transformer compatibility with BinaryOpRule / BinaryTermRule / BinaryOperatorRule.
109+
110+
// Logical OR
111+
?or_expr : or_expr or_binary_term new_line_or_comment? -> binary_op
112+
| and_expr
113+
or_binary_term : or_binary_operator new_line_or_comment? and_expr -> binary_term
114+
!or_binary_operator : DOUBLE_PIPE -> binary_operator
115+
116+
// Logical AND
117+
?and_expr : and_expr and_binary_term new_line_or_comment? -> binary_op
118+
| eq_expr
119+
and_binary_term : and_binary_operator new_line_or_comment? eq_expr -> binary_term
120+
!and_binary_operator : DOUBLE_AMP -> binary_operator
121+
122+
// Equality
123+
?eq_expr : eq_expr eq_binary_term new_line_or_comment? -> binary_op
124+
| rel_expr
125+
eq_binary_term : eq_binary_operator new_line_or_comment? rel_expr -> binary_term
126+
!eq_binary_operator : DOUBLE_EQ -> binary_operator
127+
| NEQ -> binary_operator
128+
129+
// Relational
130+
?rel_expr : rel_expr rel_binary_term new_line_or_comment? -> binary_op
131+
| add_expr
132+
rel_binary_term : rel_binary_operator new_line_or_comment? add_expr -> binary_term
133+
!rel_binary_operator : LT -> binary_operator
134+
| GT -> binary_operator
135+
| LEQ -> binary_operator
136+
| GEQ -> binary_operator
137+
138+
// Additive
139+
?add_expr : add_expr add_binary_term new_line_or_comment? -> binary_op
140+
| mul_expr
141+
add_binary_term : add_binary_operator new_line_or_comment? mul_expr -> binary_term
142+
!add_binary_operator : PLUS -> binary_operator
143+
| MINUS -> binary_operator
144+
145+
// Multiplicative
146+
?mul_expr : mul_expr mul_binary_term new_line_or_comment? -> binary_op
147+
| unary_expr
148+
mul_binary_term : mul_binary_operator new_line_or_comment? unary_expr -> binary_term
149+
!mul_binary_operator : ASTERISK -> binary_operator
150+
| SLASH -> binary_operator
151+
| PERCENT -> binary_operator
152+
153+
// Unary (highest precedence for operations)
154+
?unary_expr : unary_op | expr_term
108155
!unary_op : (MINUS | NOT) expr_term
109-
binary_op : expression binary_term new_line_or_comment?
110-
binary_term : binary_operator new_line_or_comment? expression
111-
!binary_operator : BINARY_OP
112156

113157
// Expression terms
114158
expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR

hcl2/rule_transformer/reconstructor.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
from hcl2.rule_transformer.rules.for_expressions import ForIntroRule
77
from hcl2.rule_transformer.rules.literal_rules import IdentifierRule
88
from hcl2.rule_transformer.rules.strings import StringRule
9-
from hcl2.rule_transformer.rules.expressions import ExprTermRule, ConditionalRule
9+
from hcl2.rule_transformer.rules.expressions import (
10+
ExprTermRule,
11+
ConditionalRule,
12+
UnaryOpRule,
13+
)
1014

1115

1216
class HCLReconstructor:
@@ -24,6 +28,7 @@ def _reset_state(self):
2428
self._in_parentheses = False
2529
self._in_object = False
2630
self._in_tuple = False
31+
self._in_unary_op = False
2732

2833
def _should_add_space_before(
2934
self, current_node: Union[Tree, Token], parent_rule_name: str = None
@@ -106,7 +111,25 @@ def _should_add_space_before(
106111
return True
107112

108113
# space around binary operators
109-
if tokens.BINARY_OP.lark_name() in [token_type, self._last_token_name]:
114+
binary_op_types = {
115+
"DOUBLE_EQ",
116+
"NEQ",
117+
"LT",
118+
"GT",
119+
"LEQ",
120+
"GEQ",
121+
"MINUS",
122+
"ASTERISK",
123+
"SLASH",
124+
"PERCENT",
125+
"DOUBLE_AMP",
126+
"DOUBLE_PIPE",
127+
"PLUS",
128+
}
129+
if not self._in_unary_op and (
130+
token_type in binary_op_types
131+
or self._last_token_name in binary_op_types
132+
):
110133
return True
111134

112135
elif isinstance(current_node, Tree):
@@ -130,7 +153,13 @@ def _reconstruct_tree(self, tree: Tree, parent_rule_name: str = None) -> List[st
130153
result = []
131154
rule_name = tree.data
132155

133-
if rule_name == ExprTermRule.lark_name():
156+
if rule_name == UnaryOpRule.lark_name():
157+
self._in_unary_op = True
158+
for child in tree.children:
159+
result.extend(self._reconstruct_node(child, rule_name))
160+
self._in_unary_op = False
161+
162+
elif rule_name == ExprTermRule.lark_name():
134163
# Check if parenthesized
135164
if (
136165
len(tree.children) >= 3

hcl2/rule_transformer/rules/expressions.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ def expr_term(self):
214214
def serialize(
215215
self, options=SerializationOptions(), context=SerializationContext()
216216
) -> Any:
217-
return to_dollar_string(
218-
f"{self.operator}{self.expr_term.serialize(options, context)}"
219-
)
217+
218+
with context.modify(inside_dollar_string=True):
219+
result = f"{self.operator}{self.expr_term.serialize(options, context)}"
220+
221+
if not context.inside_dollar_string:
222+
result = to_dollar_string(result)
223+
224+
return result

0 commit comments

Comments
 (0)