Skip to content

Commit 5932662

Browse files
add for expressions rules
1 parent d8ac92d commit 5932662

File tree

4 files changed

+320
-5
lines changed

4 files changed

+320
-5
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
from typing import Any, Tuple, Optional, List
2+
3+
from lark.tree import Meta
4+
5+
from hcl2.rule_transformer.rules.abstract import LarkRule, LarkElement
6+
from hcl2.rule_transformer.rules.expressions import ExpressionRule
7+
from hcl2.rule_transformer.rules.literal_rules import IdentifierRule
8+
from hcl2.rule_transformer.rules.tokens import (
9+
LSQB,
10+
RSQB,
11+
LBRACE,
12+
RBRACE,
13+
FOR,
14+
IN,
15+
IF,
16+
COMMA,
17+
COLON,
18+
ELLIPSIS,
19+
FOR_OBJECT_ARROW,
20+
)
21+
from hcl2.rule_transformer.rules.whitespace import (
22+
NewLineOrCommentRule,
23+
InlineCommentMixIn,
24+
)
25+
from hcl2.rule_transformer.utils import (
26+
SerializationOptions,
27+
SerializationContext,
28+
to_dollar_string,
29+
)
30+
31+
32+
class ForIntroRule(InlineCommentMixIn):
33+
"""Rule for the intro part of for expressions: 'for key, value in collection :'"""
34+
35+
_children: Tuple[
36+
FOR,
37+
Optional[NewLineOrCommentRule],
38+
IdentifierRule,
39+
Optional[COMMA],
40+
Optional[IdentifierRule],
41+
Optional[NewLineOrCommentRule],
42+
IN,
43+
Optional[NewLineOrCommentRule],
44+
ExpressionRule,
45+
Optional[NewLineOrCommentRule],
46+
COLON,
47+
Optional[NewLineOrCommentRule],
48+
]
49+
50+
@staticmethod
51+
def lark_name() -> str:
52+
return "for_intro"
53+
54+
def __init__(self, children, meta: Optional[Meta] = None):
55+
# Insert null comments at positions where they might be missing
56+
self._possibly_insert_null_second_identifier(children)
57+
self._possibly_insert_null_comments(children, [1, 5, 7, 9, 11])
58+
super().__init__(children, meta)
59+
60+
def _possibly_insert_null_second_identifier(self, children: List[LarkRule]):
61+
second_identifier_present = (
62+
len([child for child in children if isinstance(child, IdentifierRule)]) == 2
63+
)
64+
if not second_identifier_present:
65+
children.insert(3, None)
66+
children.insert(4, None)
67+
68+
@property
69+
def first_iterator(self) -> IdentifierRule:
70+
"""Returns the first iterator"""
71+
return self._children[2]
72+
73+
@property
74+
def second_iterator(self) -> Optional[IdentifierRule]:
75+
"""Returns the second iterator or None if not present"""
76+
return self._children[4]
77+
78+
@property
79+
def iterable(self) -> ExpressionRule:
80+
"""Returns the collection expression being iterated over"""
81+
return self._children[8]
82+
83+
def serialize(
84+
self, options=SerializationOptions(), context=SerializationContext()
85+
) -> str:
86+
result = "for "
87+
88+
result += f"{self.first_iterator.serialize(options, context)}"
89+
if self.second_iterator:
90+
result += f", {self.second_iterator.serialize(options, context)}"
91+
92+
result += f" in {self.iterable.serialize(options, context)} : "
93+
94+
return result
95+
96+
97+
class ForCondRule(InlineCommentMixIn):
98+
"""Rule for the optional condition in for expressions: 'if condition'"""
99+
100+
_children: Tuple[
101+
IF,
102+
Optional[NewLineOrCommentRule],
103+
ExpressionRule, # condition expression
104+
]
105+
106+
@staticmethod
107+
def lark_name() -> str:
108+
return "for_cond"
109+
110+
def __init__(self, children, meta: Optional[Meta] = None):
111+
self._possibly_insert_null_comments(children, [1])
112+
super().__init__(children, meta)
113+
114+
@property
115+
def condition_expr(self) -> ExpressionRule:
116+
"""Returns the condition expression"""
117+
return self._children[2]
118+
119+
def serialize(
120+
self, options=SerializationOptions(), context=SerializationContext()
121+
) -> str:
122+
return f"if {self.condition_expr.serialize(options, context)}"
123+
124+
125+
class ForTupleExprRule(ExpressionRule):
126+
"""Rule for tuple/array for expressions: [for item in items : expression]"""
127+
128+
_children: Tuple[
129+
LSQB,
130+
Optional[NewLineOrCommentRule],
131+
ForIntroRule,
132+
Optional[NewLineOrCommentRule],
133+
ExpressionRule,
134+
Optional[NewLineOrCommentRule],
135+
Optional[ForCondRule],
136+
Optional[NewLineOrCommentRule],
137+
RSQB,
138+
]
139+
140+
@staticmethod
141+
def lark_name() -> str:
142+
return "for_tuple_expr"
143+
144+
def __init__(self, children, meta: Optional[Meta] = None):
145+
self._possibly_insert_null_comments(children, [1, 3, 5, 7])
146+
self._possibly_insert_null_condition(children)
147+
super().__init__(children, meta)
148+
149+
def _possibly_insert_null_condition(self, children: List[LarkElement]):
150+
if not len([child for child in children if isinstance(child, ForCondRule)]):
151+
children.insert(6, None)
152+
153+
@property
154+
def for_intro(self) -> ForIntroRule:
155+
"""Returns the for intro rule"""
156+
return self._children[2]
157+
158+
@property
159+
def value_expr(self) -> ExpressionRule:
160+
"""Returns the value expression"""
161+
return self._children[4]
162+
163+
@property
164+
def condition(self) -> Optional[ForCondRule]:
165+
"""Returns the optional condition rule"""
166+
return self._children[6]
167+
168+
def serialize(
169+
self, options=SerializationOptions(), context=SerializationContext()
170+
) -> Any:
171+
172+
result = "["
173+
174+
with context.modify(inside_dollar_string=True):
175+
result += self.for_intro.serialize(options, context)
176+
result += self.value_expr.serialize(options, context)
177+
178+
if self.condition is not None:
179+
result += f" {self.condition.serialize(options, context)}"
180+
181+
result += "]"
182+
if not context.inside_dollar_string:
183+
result = to_dollar_string(result)
184+
return result
185+
186+
187+
class ForObjectExprRule(ExpressionRule):
188+
"""Rule for object for expressions: {for key, value in items : key => value}"""
189+
190+
_children: Tuple[
191+
LBRACE,
192+
Optional[NewLineOrCommentRule],
193+
ForIntroRule,
194+
Optional[NewLineOrCommentRule],
195+
ExpressionRule,
196+
FOR_OBJECT_ARROW,
197+
Optional[NewLineOrCommentRule],
198+
ExpressionRule,
199+
Optional[NewLineOrCommentRule],
200+
Optional[ELLIPSIS],
201+
Optional[NewLineOrCommentRule],
202+
Optional[ForCondRule],
203+
Optional[NewLineOrCommentRule],
204+
RBRACE,
205+
]
206+
207+
@staticmethod
208+
def lark_name() -> str:
209+
return "for_object_expr"
210+
211+
def __init__(self, children, meta: Optional[Meta] = None):
212+
self._possibly_insert_null_comments(children, [1, 3, 6, 8, 10, 12])
213+
self._possibly_insert_null_optionals(children)
214+
super().__init__(children, meta)
215+
216+
def _possibly_insert_null_optionals(self, children: List[LarkElement]):
217+
has_ellipsis = False
218+
has_condition = False
219+
220+
for child in children:
221+
# if not has_ellipsis and isinstance(child, ELLIPSIS):
222+
if (
223+
has_ellipsis is False
224+
and child is not None
225+
and child.lark_name() == ELLIPSIS.lark_name()
226+
):
227+
has_ellipsis = True
228+
if not has_condition and isinstance(child, ForCondRule):
229+
has_condition = True
230+
231+
if not has_ellipsis:
232+
children.insert(9, None)
233+
234+
if not has_condition:
235+
children.insert(11, None)
236+
237+
@property
238+
def for_intro(self) -> ForIntroRule:
239+
"""Returns the for intro rule"""
240+
return self._children[2]
241+
242+
@property
243+
def key_expr(self) -> ExpressionRule:
244+
"""Returns the key expression"""
245+
return self._children[4]
246+
247+
@property
248+
def value_expr(self) -> ExpressionRule:
249+
"""Returns the value expression"""
250+
return self._children[7]
251+
252+
@property
253+
def ellipsis(self) -> Optional[ELLIPSIS]:
254+
"""Returns the optional ellipsis token"""
255+
return self._children[9]
256+
257+
@property
258+
def condition(self) -> Optional[ForCondRule]:
259+
"""Returns the optional condition rule"""
260+
return self._children[11]
261+
262+
def serialize(
263+
self, options=SerializationOptions(), context=SerializationContext()
264+
) -> Any:
265+
result = "{"
266+
with context.modify(inside_dollar_string=True):
267+
result += self.for_intro.serialize(options, context)
268+
result += f"{self.key_expr.serialize(options, context)} => "
269+
270+
result += self.value_expr.serialize(
271+
SerializationOptions(wrap_objects=True), context
272+
)
273+
274+
if self.ellipsis is not None:
275+
result += self.ellipsis.serialize(options, context)
276+
277+
if self.condition is not None:
278+
result += f" {self.condition.serialize(options, context)}"
279+
280+
result += "}"
281+
if not context.inside_dollar_string:
282+
result = to_dollar_string(result)
283+
return result

hcl2/rule_transformer/rules/functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def arguments(self) -> List[ExpressionRule]:
4040
return [child for child in self._children if isinstance(child, ExpressionRule)]
4141

4242
def serialize(self, options = SerializationOptions(), context = SerializationContext()) -> Any:
43-
result = ", ".join([argument.serialize(options, context) for argument in self.arguments])
43+
result = ", ".join([str(argument.serialize(options, context)) for argument in self.arguments])
4444
if self.has_ellipsis:
4545
result += " ..."
4646
return result

hcl2/rule_transformer/rules/tokens.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ def serialize_conversion(self) -> Callable[[Any], str]:
9191
DBLQUOTE = StaticStringToken[("DBLQUOTE", '"')]
9292
ATTR_SPLAT = StaticStringToken[("ATTR_SPLAT", ".*")]
9393
FULL_SPLAT = StaticStringToken[("FULL_SPLAT", "[*]")]
94+
FOR = StaticStringToken[("FOR", "for")]
95+
IN = StaticStringToken[("IN", "in")]
96+
IF = StaticStringToken[("IF", "if")]
97+
FOR_OBJECT_ARROW = StaticStringToken[("FOR_OBJECT_ARROW", "=>")]
9498

9599

96100
class IntLiteral(LarkToken):

hcl2/rule_transformer/transformer.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
ExprTermRule,
2424
ConditionalRule,
2525
)
26+
from hcl2.rule_transformer.rules.for_expressions import (
27+
ForTupleExprRule,
28+
ForObjectExprRule,
29+
ForIntroRule,
30+
ForCondRule,
31+
)
2632
from hcl2.rule_transformer.rules.functions import ArgumentsRule, FunctionCallRule
2733
from hcl2.rule_transformer.rules.indexing import (
2834
IndexExprTermRule,
@@ -40,12 +46,13 @@
4046
IntLitRule,
4147
IdentifierRule,
4248
BinaryOperatorRule,
49+
KeywordRule,
4350
)
4451
from hcl2.rule_transformer.rules.strings import (
4552
InterpolationRule,
4653
StringRule,
47-
StringPartRule,
48-
HeredocTemplateRule,
54+
StringPartRule,
55+
HeredocTemplateRule,
4956
HeredocTrimTemplateRule,
5057
)
5158
from hcl2.rule_transformer.rules.tokens import (
@@ -72,6 +79,7 @@ def __init__(self, discard_new_line_or_comments: bool = False):
7279
self.discard_new_line_or_comments = discard_new_line_or_comments
7380

7481
def __default_token__(self, token: Token) -> StringToken:
82+
# TODO make this return StaticStringToken where applicable
7583
return StringToken[token.type](token.value)
7684

7785
def FLOAT_LITERAL(self, token: Token) -> FloatLiteral:
@@ -109,6 +117,10 @@ def new_line_or_comment(self, meta: Meta, args) -> NewLineOrCommentRule:
109117
def identifier(self, meta: Meta, args) -> IdentifierRule:
110118
return IdentifierRule(args, meta)
111119

120+
@v_args(meta=True)
121+
def keyword(self, meta: Meta, args) -> KeywordRule:
122+
return KeywordRule(args, meta)
123+
112124
@v_args(meta=True)
113125
def int_lit(self, meta: Meta, args) -> IntLitRule:
114126
return IntLitRule(args, meta)
@@ -132,11 +144,11 @@ def interpolation(self, meta: Meta, args) -> InterpolationRule:
132144
@v_args(meta=True)
133145
def heredoc_template(self, meta: Meta, args) -> HeredocTemplateRule:
134146
return HeredocTemplateRule(args, meta)
135-
147+
136148
@v_args(meta=True)
137149
def heredoc_template_trim(self, meta: Meta, args) -> HeredocTrimTemplateRule:
138150
return HeredocTrimTemplateRule(args, meta)
139-
151+
140152
@v_args(meta=True)
141153
def expr_term(self, meta: Meta, args) -> ExprTermRule:
142154
return ExprTermRule(args, meta)
@@ -236,3 +248,19 @@ def full_splat(self, meta: Meta, args) -> FullSplatRule:
236248
@v_args(meta=True)
237249
def full_splat_expr_term(self, meta: Meta, args) -> FullSplatExprTermRule:
238250
return FullSplatExprTermRule(args, meta)
251+
252+
@v_args(meta=True)
253+
def for_tuple_expr(self, meta: Meta, args) -> ForTupleExprRule:
254+
return ForTupleExprRule(args, meta)
255+
256+
@v_args(meta=True)
257+
def for_object_expr(self, meta: Meta, args) -> ForObjectExprRule:
258+
return ForObjectExprRule(args, meta)
259+
260+
@v_args(meta=True)
261+
def for_intro(self, meta: Meta, args) -> ForIntroRule:
262+
return ForIntroRule(args, meta)
263+
264+
@v_args(meta=True)
265+
def for_cond(self, meta: Meta, args) -> ForCondRule:
266+
return ForCondRule(args, meta)

0 commit comments

Comments
 (0)