openscad-parser 2.4.6__tar.gz → 2.4.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/PKG-INFO +1 -1
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/pyproject.toml +1 -1
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/builder.py +2 -2
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/nodes.py +8 -2
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/pretty_print.py +96 -8
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/README.rst +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/__init__.py +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/__init__.py +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/scope.py +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/serialization.py +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/ast/source_map.py +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/cli.py +0 -0
- {openscad_parser-2.4.6 → openscad_parser-2.4.8}/src/openscad_parser/grammar.py +0 -0
|
@@ -624,7 +624,7 @@ class ASTBuilderVisitor(PTNodeVisitor):
|
|
|
624
624
|
arguments = children[0] if len(children) > 0 else []
|
|
625
625
|
body = children[1] if len(children) > 1 else None
|
|
626
626
|
if body is None:
|
|
627
|
-
|
|
627
|
+
body = UndefinedLiteral(position=self._get_node_position(node))
|
|
628
628
|
return AssertOp(arguments=arguments, body=body, position=self._get_node_position(node))
|
|
629
629
|
|
|
630
630
|
def visit_echo_expr(self, node, children):
|
|
@@ -637,7 +637,7 @@ class ASTBuilderVisitor(PTNodeVisitor):
|
|
|
637
637
|
arguments = children[0] if len(children) > 0 else []
|
|
638
638
|
body = children[1] if len(children) > 1 else None
|
|
639
639
|
if body is None:
|
|
640
|
-
|
|
640
|
+
body = UndefinedLiteral(position=self._get_node_position(node))
|
|
641
641
|
return EchoOp(arguments=arguments, body=body, position=self._get_node_position(node))
|
|
642
642
|
|
|
643
643
|
def visit_ternary_expr(self, node, children):
|
|
@@ -430,7 +430,10 @@ class EchoOp(Expression):
|
|
|
430
430
|
body: Expression
|
|
431
431
|
|
|
432
432
|
def __str__(self):
|
|
433
|
-
|
|
433
|
+
args = ', '.join(str(arg) for arg in self.arguments)
|
|
434
|
+
if isinstance(self.body, UndefinedLiteral):
|
|
435
|
+
return f"echo({args})"
|
|
436
|
+
return f"echo({args}) {self.body}"
|
|
434
437
|
|
|
435
438
|
def build_scope(self, parent_scope: "Scope") -> None:
|
|
436
439
|
self.scope = parent_scope
|
|
@@ -458,7 +461,10 @@ class AssertOp(Expression):
|
|
|
458
461
|
body: Expression
|
|
459
462
|
|
|
460
463
|
def __str__(self):
|
|
461
|
-
|
|
464
|
+
args = ', '.join(str(arg) for arg in self.arguments)
|
|
465
|
+
if isinstance(self.body, UndefinedLiteral):
|
|
466
|
+
return f"assert({args})"
|
|
467
|
+
return f"assert({args}) {self.body}"
|
|
462
468
|
|
|
463
469
|
def build_scope(self, parent_scope: "Scope") -> None:
|
|
464
470
|
self.scope = parent_scope
|
|
@@ -15,11 +15,15 @@ from .nodes import (
|
|
|
15
15
|
EchoOp,
|
|
16
16
|
AssertOp,
|
|
17
17
|
LetOp,
|
|
18
|
+
UndefinedLiteral,
|
|
18
19
|
PrimaryCall,
|
|
19
20
|
ListComprehension,
|
|
20
21
|
ListCompFor,
|
|
21
22
|
ListCompCFor,
|
|
22
23
|
ListCompLet,
|
|
24
|
+
ListCompIf,
|
|
25
|
+
ListCompIfElse,
|
|
26
|
+
ListCompEach,
|
|
23
27
|
PositionalArgument,
|
|
24
28
|
NamedArgument,
|
|
25
29
|
)
|
|
@@ -43,7 +47,27 @@ def to_openscad(nodes: list[ASTNode], indent_width: int = 4) -> str:
|
|
|
43
47
|
parts.append("")
|
|
44
48
|
parts.append(_fmt_node(node, 0, indent_width))
|
|
45
49
|
prev_complex = is_complex
|
|
46
|
-
return "\n".join(parts)
|
|
50
|
+
return _coalesce_paren_bracket("\n".join(parts))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _coalesce_paren_bracket(text: str) -> str:
|
|
54
|
+
"""Join consecutive lines where one is a bare ')' and the next starts with '['."""
|
|
55
|
+
lines = text.split("\n")
|
|
56
|
+
result = []
|
|
57
|
+
i = 0
|
|
58
|
+
while i < len(lines):
|
|
59
|
+
if (
|
|
60
|
+
i + 1 < len(lines)
|
|
61
|
+
and lines[i].strip() == ")"
|
|
62
|
+
and lines[i + 1].lstrip().startswith("[")
|
|
63
|
+
):
|
|
64
|
+
indent = len(lines[i]) - len(lines[i].lstrip())
|
|
65
|
+
result.append(" " * indent + ") " + lines[i + 1].lstrip())
|
|
66
|
+
i += 2
|
|
67
|
+
else:
|
|
68
|
+
result.append(lines[i])
|
|
69
|
+
i += 1
|
|
70
|
+
return "\n".join(result)
|
|
47
71
|
|
|
48
72
|
|
|
49
73
|
# Line length beyond which call arguments are formatted one-per-line.
|
|
@@ -105,14 +129,29 @@ def _fmt_list_elem(elem, indent: int, w: int) -> str:
|
|
|
105
129
|
return f"let({assigns})\n{pad}{body}"
|
|
106
130
|
if isinstance(elem, LetOp):
|
|
107
131
|
formatted = [_fmt_assign(a, indent + w, w) for a in elem.assignments]
|
|
108
|
-
body =
|
|
132
|
+
body = _fmt_expr(elem.body, indent, w)
|
|
109
133
|
if len(formatted) > 1 or any("\n" in fa for fa in formatted):
|
|
110
134
|
assign_lines = (",\n" + inner_pad).join(formatted)
|
|
111
135
|
return f"let(\n{inner_pad}{assign_lines}\n{pad})\n{pad}{body}"
|
|
112
136
|
assigns = ", ".join(formatted)
|
|
113
|
-
|
|
137
|
+
inline = f"let({assigns}) {body}"
|
|
138
|
+
if "\n" in body or len(inline) + indent > _MULTILINE_CHAR_LIMIT:
|
|
139
|
+
return f"let({assigns})\n{pad}{body}"
|
|
140
|
+
return inline
|
|
114
141
|
if isinstance(elem, ListComprehension):
|
|
115
142
|
return _fmt_expr(elem, indent, w)
|
|
143
|
+
if isinstance(elem, ListCompIf):
|
|
144
|
+
cond = str(elem.condition)
|
|
145
|
+
body = _fmt_list_elem(elem.true_expr, indent + w, w)
|
|
146
|
+
return f"if ({cond})\n{inner_pad}{body}"
|
|
147
|
+
if isinstance(elem, ListCompIfElse):
|
|
148
|
+
cond = str(elem.condition)
|
|
149
|
+
true_body = _fmt_list_elem(elem.true_expr, indent + w, w)
|
|
150
|
+
false_body = _fmt_list_elem(elem.false_expr, indent + w, w)
|
|
151
|
+
return f"if ({cond})\n{inner_pad}{true_body}\n{pad}else\n{inner_pad}{false_body}"
|
|
152
|
+
if isinstance(elem, ListCompEach):
|
|
153
|
+
body = _fmt_list_elem(elem.body, indent, w)
|
|
154
|
+
return f"each {body}"
|
|
116
155
|
return str(elem)
|
|
117
156
|
|
|
118
157
|
|
|
@@ -138,10 +177,43 @@ def _fmt_argument(arg, indent: int, w: int) -> str:
|
|
|
138
177
|
return str(arg)
|
|
139
178
|
|
|
140
179
|
|
|
180
|
+
def _fmt_ternary_chain(expr: TernaryOp, indent: int, w: int) -> str:
|
|
181
|
+
"""Format a right-chain of ternaries with flat ? / : alignment.
|
|
182
|
+
|
|
183
|
+
All ': ' connectors stay at the same indent column as the conditions;
|
|
184
|
+
each true branch is on its own line at indent+w. The '?' moves to
|
|
185
|
+
the end of the condition line rather than the beginning of the true-branch.
|
|
186
|
+
|
|
187
|
+
cond1?
|
|
188
|
+
true1
|
|
189
|
+
: cond2?
|
|
190
|
+
true2
|
|
191
|
+
: final_else
|
|
192
|
+
"""
|
|
193
|
+
pad = " " * indent
|
|
194
|
+
inner_pad = " " * (indent + w)
|
|
195
|
+
parts = []
|
|
196
|
+
node: TernaryOp = expr
|
|
197
|
+
while isinstance(node, TernaryOp):
|
|
198
|
+
parts.append((node.condition, node.true_expr))
|
|
199
|
+
node = node.false_expr
|
|
200
|
+
final = node
|
|
201
|
+
lines = []
|
|
202
|
+
for i, (cond, true_expr) in enumerate(parts):
|
|
203
|
+
true_str = _fmt_expr(true_expr, indent + w, w)
|
|
204
|
+
prefix = "" if i == 0 else f"{pad}: "
|
|
205
|
+
lines.append(f"{prefix}{cond} ?\n{inner_pad}{true_str}")
|
|
206
|
+
lines.append(f"{pad}: {_fmt_expr(final, indent + w, w)}")
|
|
207
|
+
return "\n".join(lines)
|
|
208
|
+
|
|
209
|
+
|
|
141
210
|
def _fmt_expr(expr, indent: int, w: int) -> str:
|
|
142
211
|
"""Format an expression with indent-aware layout for ternary, assert, and echo."""
|
|
143
212
|
pad = " " * indent
|
|
144
213
|
if isinstance(expr, TernaryOp):
|
|
214
|
+
# Right-chain of ternaries → flat cascade format
|
|
215
|
+
if isinstance(expr.false_expr, TernaryOp):
|
|
216
|
+
return _fmt_ternary_chain(expr, indent, w)
|
|
145
217
|
pad2 = " " * (indent + 2)
|
|
146
218
|
def _fmt_branch(branch):
|
|
147
219
|
# Nested ternary: 2 spaces per nesting level
|
|
@@ -157,9 +229,13 @@ def _fmt_expr(expr, indent: int, w: int) -> str:
|
|
|
157
229
|
)
|
|
158
230
|
if isinstance(expr, AssertOp):
|
|
159
231
|
args = ", ".join(str(a) for a in expr.arguments)
|
|
232
|
+
if isinstance(expr.body, UndefinedLiteral):
|
|
233
|
+
return f"assert({args})"
|
|
160
234
|
return f"assert({args})\n{pad}{_fmt_expr(expr.body, indent, w)}"
|
|
161
235
|
if isinstance(expr, EchoOp):
|
|
162
236
|
args = ", ".join(str(a) for a in expr.arguments)
|
|
237
|
+
if isinstance(expr.body, UndefinedLiteral):
|
|
238
|
+
return f"echo({args})"
|
|
163
239
|
return f"echo({args})\n{pad}{_fmt_expr(expr.body, indent, w)}"
|
|
164
240
|
if isinstance(expr, LetOp):
|
|
165
241
|
inner_pad = " " * (indent + w)
|
|
@@ -171,7 +247,7 @@ def _fmt_expr(expr, indent: int, w: int) -> str:
|
|
|
171
247
|
f"{pad}{_fmt_expr(expr.body, indent, w)}"
|
|
172
248
|
)
|
|
173
249
|
assigns = ", ".join(formatted)
|
|
174
|
-
return f"let({assigns})\n{
|
|
250
|
+
return f"let({assigns})\n{inner_pad}{_fmt_expr(expr.body, indent + w, w)}"
|
|
175
251
|
if isinstance(expr, PrimaryCall):
|
|
176
252
|
inline = str(expr)
|
|
177
253
|
if len(inline) + indent > _MULTILINE_CHAR_LIMIT:
|
|
@@ -301,12 +377,24 @@ def _fmt_inst(node: ModuleInstantiation, indent: int, w: int, prefix: str = "")
|
|
|
301
377
|
return f"{pad}{prefix}let ({assigns})" + _fmt_child(node.children, indent, w)
|
|
302
378
|
|
|
303
379
|
if isinstance(node, ModularEcho):
|
|
304
|
-
|
|
305
|
-
|
|
380
|
+
head = f"{pad}{prefix}echo"
|
|
381
|
+
inline = f"{head}({_join_str(node.arguments)})"
|
|
382
|
+
if len(inline) > _MULTILINE_CHAR_LIMIT:
|
|
383
|
+
call = _fmt_multiline_args(head, node.arguments, indent, w,
|
|
384
|
+
fmt_fn=lambda a: _fmt_argument(a, indent + w, w))
|
|
385
|
+
else:
|
|
386
|
+
call = inline
|
|
387
|
+
return call + _fmt_child(node.children, indent, w)
|
|
306
388
|
|
|
307
389
|
if isinstance(node, ModularAssert):
|
|
308
|
-
|
|
309
|
-
|
|
390
|
+
head = f"{pad}{prefix}assert"
|
|
391
|
+
inline = f"{head}({_join_str(node.arguments)})"
|
|
392
|
+
if len(inline) > _MULTILINE_CHAR_LIMIT:
|
|
393
|
+
call = _fmt_multiline_args(head, node.arguments, indent, w,
|
|
394
|
+
fmt_fn=lambda a: _fmt_argument(a, indent + w, w))
|
|
395
|
+
else:
|
|
396
|
+
call = inline
|
|
397
|
+
return call + _fmt_child(node.children, indent, w)
|
|
310
398
|
|
|
311
399
|
if isinstance(node, ModularIf):
|
|
312
400
|
header = f"{pad}{prefix}if ({node.condition})"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|