Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/openscad_parser/ast/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ class RangeLiteral(Primary):
step: Expression

def __str__(self):
# A synthesised default step (2-arg source range) is tagged with the
# range node's own position; an explicit third argument has a distinct
# source position.
if self.step.position == self.position:
return f"[{self.start} : {self.end}]"
return f"[{self.start} : {self.end} : {self.step}]"

def build_scope(self, parent_scope: "Scope") -> None:
Expand Down
11 changes: 9 additions & 2 deletions tests/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,15 @@ def test_primary_and_range_str():
member = PrimaryMember(left=_ident("obj"), member=_ident("x"), position=_pos())
assert str(member) == "obj.x"

range_lit = RangeLiteral(start=_num(0.0), end=_num(5.0), step=_num(1.0), position=_pos())
assert str(range_lit) == "[0 : 5 : 1]"
# 2-arg: step shares range position (synthesised default) → omit step
range_2arg = RangeLiteral(start=_num(0.0), end=_num(5.0), step=_num(1.0), position=_pos())
assert str(range_2arg) == "[0:5]"

# 3-arg: step has a distinct position (explicit in source) → include step
step_pos = Position(origin="<test>", line=1, column=5)
step_explicit = NumberLiteral(val=2.0, position=step_pos)
range_3arg = RangeLiteral(start=_num(0.0), end=_num(5.0), step=step_explicit, position=_pos())
assert str(range_3arg) == "[0: 5 : 2]"


def test_list_comprehension_str():
Expand Down
4 changes: 2 additions & 2 deletions tests/test_pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def test_assert_no_child(self):
class TestListCompForFormatting:
def test_short_for_expands(self):
out = _fmt("x = [for (i = [0:3]) i];")
assert out == "x = [\n for (i = [0 : 3 : 1])\n i\n ];"
assert out == "x = [\n for (i = [0 : 3])\n i\n ];"

def test_long_for_body_on_new_line(self):
out = _fmt("x = [for (long_variable_name = [start_value:step_value:end_value]) long_variable_name * scaling_factor_x];")
Expand All @@ -408,7 +408,7 @@ def test_long_for_body_on_new_line(self):
def test_long_for_assignments_multiline(self):
out = _fmt("x = [for (very_long_variable_name_alpha = [start_value:end_value], very_long_variable_name_beta = [0:10]) very_long_variable_name_alpha];")
assert "for (\n" in out
assert " very_long_variable_name_alpha = [start_value : end_value : 1]," in out
assert " very_long_variable_name_alpha = [start_value : end_value]," in out
assert ")\n" in out

def test_nested_for(self):
Expand Down
33 changes: 33 additions & 0 deletions tests/test_vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,39 @@ def test_range_expressions(self, parser):
code = "x = [0:2*5:10];"
parse_success(parser, code)

def test_range_two_arg_round_trip(self):
"""Two-argument range [start:end] must not gain a spurious :1 after round-trip."""
import tempfile
import os
from openscad_parser.ast import getASTfromFile
from openscad_parser.ast.pretty_print import to_openscad
code = "x = [for(i = [0:5]) i];"
with tempfile.NamedTemporaryFile(mode='w', suffix='.scad', delete=False) as f:
f.write(code)
fname = f.name
output = to_openscad(getASTfromFile(fname, process_includes=False))
os.unlink(fname)
assert ":1]" not in output, f"Spurious step in output: {output!r}"
assert "[0:5]" in output

def test_range_three_arg_round_trip(self):
"""Three-argument range [start:step:end] must preserve all three arguments."""
import tempfile
import os
from openscad_parser.ast import getASTfromFile
from openscad_parser.ast.pretty_print import to_openscad
cases = [
("x = [for(i = [10:-1:0]) i];", "[10:-1:0]"),
("x = [for(i = [0:1:4]) i];", "[0:1:4]"),
]
for src, expected in cases:
with tempfile.NamedTemporaryFile(mode='w', suffix='.scad', delete=False) as f:
f.write(src)
fname = f.name
output = to_openscad(getASTfromFile(fname, process_includes=False))
os.unlink(fname)
assert expected in output, f"{src!r} → missing {expected!r} in {output!r}"


class TestListComprehensionFor:
"""Test list comprehension with for."""
Expand Down