Skip to content

Commit b90981a

Browse files
Preserve literals of e-notation floats in parsing and reconstruction (#226)
1 parent ffb3ed8 commit b90981a

File tree

6 files changed

+117
-22
lines changed

6 files changed

+117
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1515

1616
- Issue parsing ellipsis in a separate line within `for` expression ([#221](https://github.com/amplify-education/python-hcl2/pull/221))
1717
- Issue parsing inline expression as an object key; **see Limitations in README.md** ([#222](https://github.com/amplify-education/python-hcl2/pull/222))
18+
- Preserve literals of e-notation floats in parsing and reconstruction. Thanks, @eranor ([#226](https://github.com/amplify-education/python-hcl2/pull/226))
1819

1920
## \[7.1.0\] - 2025-04-10
2021

hcl2/reconstructor.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def _should_add_space(self, rule, current_terminal):
260260
Terminal("STRING_LIT"),
261261
Terminal("DECIMAL"),
262262
Terminal("NAME"),
263+
Terminal("NEGATIVE_DECIMAL"),
263264
]:
264265
return True
265266

@@ -412,10 +413,7 @@ def _newline(self, level: int, count: int = 1) -> Tree:
412413
def _is_block(self, value: Any) -> bool:
413414
if isinstance(value, dict):
414415
block_body = value
415-
if (
416-
START_LINE_KEY in block_body.keys()
417-
or END_LINE_KEY in block_body.keys()
418-
):
416+
if START_LINE_KEY in block_body.keys() or END_LINE_KEY in block_body.keys():
419417
return True
420418

421419
try:
@@ -520,7 +518,7 @@ def _transform_dict_to_body(self, hcl_dict: dict, level: int) -> Tree:
520518

521519
return Tree(Token("RULE", "body"), children)
522520

523-
# pylint: disable=too-many-branches, too-many-return-statements
521+
# pylint: disable=too-many-branches, too-many-return-statements too-many-statements
524522
def _transform_value_to_expr_term(self, value, level) -> Union[Token, Tree]:
525523
"""Transforms a value from a dictionary into an "expr_term" (a value in HCL2)
526524
@@ -611,6 +609,37 @@ def _transform_value_to_expr_term(self, value, level) -> Union[Token, Tree]:
611609
],
612610
)
613611

612+
if isinstance(value, float):
613+
value = str(value)
614+
literal = []
615+
616+
if value[0] == "-":
617+
# pop two first chars - minus and a digit
618+
literal.append(Token("NEGATIVE_DECIMAL", value[:2]))
619+
value = value[2:]
620+
621+
while value != "":
622+
char = value[0]
623+
624+
if char == ".":
625+
# current char marks beginning of decimal part: pop all remaining chars and end the loop
626+
literal.append(Token("DOT", char))
627+
literal.extend(Token("DECIMAL", char) for char in value[1:])
628+
break
629+
630+
if char == "e":
631+
# current char marks beginning of e-notation: pop all remaining chars and end the loop
632+
literal.append(Token("EXP_MARK", value))
633+
break
634+
635+
literal.append(Token("DECIMAL", char))
636+
value = value[1:]
637+
638+
return Tree(
639+
Token("RULE", "expr_term"),
640+
[Tree(Token("RULE", "float_lit"), literal)],
641+
)
642+
614643
# store strings as single literals
615644
if isinstance(value, str):
616645
# potentially unpack a complex syntax structure

hcl2/transformer.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ def __init__(self, with_meta: bool = False):
4444
super().__init__()
4545

4646
def float_lit(self, args: List) -> float:
47-
return float("".join([self.to_tf_inline(arg) for arg in args]))
47+
value = "".join([self.to_tf_inline(arg) for arg in args])
48+
if "e" in value:
49+
return self.to_string_dollar(value)
50+
return float(value)
4851

4952
def int_lit(self, args: List) -> int:
5053
return int("".join([self.to_tf_inline(arg) for arg in args]))
@@ -177,7 +180,9 @@ def conditional(self, args: List) -> str:
177180
return f"{args[0]} ? {args[1]} : {args[2]}"
178181

179182
def binary_op(self, args: List) -> str:
180-
return " ".join([self.to_tf_inline(arg) for arg in args])
183+
return " ".join(
184+
[self.unwrap_string_dollar(self.to_tf_inline(arg)) for arg in args]
185+
)
181186

182187
def unary_op(self, args: List) -> str:
183188
args = self.process_nulls(args)
@@ -304,21 +309,31 @@ def strip_new_line_tokens(self, args: List) -> List:
304309
"""
305310
return [arg for arg in args if arg != "\n" and arg is not Discard]
306311

312+
def is_string_dollar(self, value: str) -> bool:
313+
if not isinstance(value, str):
314+
return False
315+
return value.startswith("${") and value.endswith("}")
316+
307317
def to_string_dollar(self, value: Any) -> Any:
308318
"""Wrap a string in ${ and }"""
309-
if isinstance(value, str):
319+
if not isinstance(value, str):
320+
return value
310321
# if it's already wrapped, pass it unmodified
311-
if value.startswith("${") and value.endswith("}"):
312-
return value
322+
if self.is_string_dollar(value):
323+
return value
313324

314-
if value.startswith('"') and value.endswith('"'):
315-
value = str(value)[1:-1]
316-
return self.process_escape_sequences(value)
325+
if value.startswith('"') and value.endswith('"'):
326+
value = str(value)[1:-1]
327+
return self.process_escape_sequences(value)
328+
329+
if self.is_type_keyword(value):
330+
return value
317331

318-
if self.is_type_keyword(value):
319-
return value
332+
return f"${{{value}}}"
320333

321-
return f"${{{value}}}"
334+
def unwrap_string_dollar(self, value: str):
335+
if self.is_string_dollar(value):
336+
return value[2:-1]
322337
return value
323338

324339
def strip_quotes(self, value: Any) -> Any:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"locals": [
3+
{
4+
"simple_float": 123.456,
5+
"small_float": 0.123,
6+
"large_float": 9876543.21,
7+
"negative_float": -42.5,
8+
"negative_small": -0.001,
9+
"scientific_positive": "${1.23e5}",
10+
"scientific_negative": "${9.87e-3}",
11+
"scientific_large": "${6.022e+23}",
12+
"integer_as_float": 100.0,
13+
"float_calculation": "${105e+2 * 3.0 / 2.1}",
14+
"float_comparison": "${5e1 > 2.3 ? 1.0 : 0.0}",
15+
"float_list": [
16+
1.1,
17+
2.2,
18+
3.3,
19+
-4.4,
20+
"${5.5e2}"
21+
],
22+
"float_object": {
23+
"pi": 3.14159,
24+
"euler": 2.71828,
25+
"sqrt2": 1.41421,
26+
"scientific": "${-123e+2}"
27+
}
28+
}
29+
]
30+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
locals {
2+
simple_float = 123.456
3+
small_float = 0.123
4+
large_float = 9876543.21
5+
negative_float = -42.5
6+
negative_small = -0.001
7+
scientific_positive = 1.23e5
8+
scientific_negative = 9.87e-3
9+
scientific_large = 6.022e+23
10+
integer_as_float = 100.0
11+
float_calculation = 105e+2 * 3.0 / 2.1
12+
float_comparison = 5e1 > 2.3 ? 1.0 : 0.0
13+
float_list = [1.1, 2.2, 3.3, -4.4, 5.5e2]
14+
float_object = {
15+
pi = 3.14159
16+
euler = 2.71828
17+
sqrt2 = 1.41421
18+
scientific = -123e+2
19+
}
20+
}

test/unit/test_hcl2_syntax.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ def test_index(self):
156156

157157
def test_e_notation(self):
158158
literals = {
159-
"var = 3e4": {"var": 30000.0},
160-
"var = 3.5e5": {"var": 350000.0},
161-
"var = -3e6": {"var": -3e6},
162-
"var = -2.3e4": {"var": -2.3e4},
163-
"var = -5e-2": {"var": -5e-2},
164-
"var = -6.1e-3": {"var": -6.1e-3},
159+
"var = 3e4": {"var": "${3e4}"},
160+
"var = 3.5e5": {"var": "${3.5e5}"},
161+
"var = -3e6": {"var": "${-3e6}"},
162+
"var = -2.3e4": {"var": "${-2.3e4}"},
163+
"var = -5e-2": {"var": "${-5e-2}"},
164+
"var = -6.1e-3": {"var": "${-6.1e-3}"},
165165
}
166166
for actual, expected in literals.items():
167167
result = self.load_to_dict(actual)

0 commit comments

Comments
 (0)