openscad-parser 2.3.4__tar.gz → 2.4.1__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.3.4 → openscad_parser-2.4.1}/PKG-INFO +183 -55
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/README.rst +179 -54
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/pyproject.toml +7 -1
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/ast/__init__.py +3 -2
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/ast/builder.py +60 -41
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/ast/nodes.py +23 -129
- openscad_parser-2.4.1/src/openscad_parser/ast/pretty_print.py +159 -0
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/ast/serialization.py +0 -4
- openscad_parser-2.4.1/src/openscad_parser/cli.py +84 -0
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/grammar.py +7 -29
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/__init__.py +0 -0
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/ast/scope.py +0 -0
- {openscad_parser-2.3.4 → openscad_parser-2.4.1}/src/openscad_parser/ast/source_map.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openscad_parser
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: A PEG parser to read OpenSCAD language source code, with optional AST tree generation.
|
|
5
5
|
Keywords: openscad,openscad parser,parser
|
|
6
6
|
Author: Revar Desmera
|
|
@@ -14,6 +14,9 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
|
14
14
|
Classifier: Operating System :: Microsoft :: Windows
|
|
15
15
|
Classifier: Operating System :: POSIX
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
20
|
Classifier: Topic :: Artistic Software
|
|
18
21
|
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
19
22
|
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
|
|
@@ -62,6 +65,8 @@ Features
|
|
|
62
65
|
- AST tree can contain comment nodes (single-line and multi-line)
|
|
63
66
|
- AST tree uses dataclasses and can be pickled/unpickled for caching/serialization
|
|
64
67
|
- JSON and YAML serialization/deserialization of AST trees
|
|
68
|
+
- Pretty-printer that converts an AST back to formatted OpenSCAD source (``to_openscad()``)
|
|
69
|
+
- Command-line interface (``openscad-parser``) for JSON/YAML/formatted output
|
|
65
70
|
|
|
66
71
|
Installation
|
|
67
72
|
------------
|
|
@@ -355,107 +360,129 @@ Parsing Library Files
|
|
|
355
360
|
AST Node Types
|
|
356
361
|
--------------
|
|
357
362
|
|
|
358
|
-
The AST includes comprehensive node types for all OpenSCAD language constructs
|
|
363
|
+
The AST includes comprehensive node types for all OpenSCAD language constructs.
|
|
364
|
+
All nodes inherit from ``ASTNode`` and carry ``position: Position`` and ``scope: Scope | None`` attributes.
|
|
359
365
|
|
|
360
366
|
Base Classes
|
|
361
367
|
~~~~~~~~~~~~
|
|
362
368
|
|
|
363
|
-
- ``ASTNode``: Base class for all AST nodes
|
|
369
|
+
- ``ASTNode(position: Position, scope: Scope | None)``: Base class for all AST nodes
|
|
364
370
|
- ``Expression``: Base class for all expression nodes
|
|
365
|
-
- ``Primary``: Base class for atomic value types
|
|
371
|
+
- ``Primary``: Base class for atomic value types (extends ``Expression``)
|
|
366
372
|
- ``ModuleInstantiation``: Base class for module-related statements
|
|
373
|
+
- ``VectorElement``: Base class for list comprehension elements
|
|
367
374
|
|
|
368
375
|
Literals
|
|
369
376
|
~~~~~~~~
|
|
370
377
|
|
|
371
|
-
- ``Identifier``: Variable, function, or module names
|
|
372
|
-
- ``StringLiteral``: String values
|
|
373
|
-
- ``NumberLiteral``: Numeric values
|
|
374
|
-
- ``BooleanLiteral``: true/false values
|
|
375
|
-
- ``UndefinedLiteral``: undef value
|
|
376
|
-
- ``RangeLiteral``: Range expressions [start:end
|
|
378
|
+
- ``Identifier(name: str)``: Variable, function, or module names
|
|
379
|
+
- ``StringLiteral(val: str)``: String values
|
|
380
|
+
- ``NumberLiteral(val: float)``: Numeric values
|
|
381
|
+
- ``BooleanLiteral(val: bool)``: true/false values
|
|
382
|
+
- ``UndefinedLiteral``: The ``undef`` value (no additional fields)
|
|
383
|
+
- ``RangeLiteral(start: Expression, end: Expression, step: Expression)``: Range expressions ``[start:step:end]``
|
|
377
384
|
|
|
378
385
|
Operators
|
|
379
386
|
~~~~~~~~~
|
|
380
387
|
|
|
388
|
+
All operators inherit from ``Expression`` and represent their respective operations with typed fields for operands. The AST preserves operator precedence and associativity as defined in OpenSCAD.
|
|
389
|
+
|
|
381
390
|
Arithmetic:
|
|
382
|
-
|
|
383
|
-
- ``
|
|
391
|
+
|
|
392
|
+
- ``AdditionOp(left: Expression, right: Expression)``: represents ``left + right``
|
|
393
|
+
- ``SubtractionOp(left: Expression, right: Expression)``: represents ``left - right``
|
|
394
|
+
- ``MultiplicationOp(left: Expression, right: Expression)``: represents ``left * right``
|
|
395
|
+
- ``DivisionOp(left: Expression, right: Expression)``: represents ``left / right``
|
|
396
|
+
- ``ModuloOp(left: Expression, right: Expression)``: represents ``left % right``
|
|
397
|
+
- ``ExponentOp(left: Expression, right: Expression)``: represents ``left ^ right``
|
|
398
|
+
- ``UnaryMinusOp(expr: Expression)``: represents ``-expr``
|
|
384
399
|
|
|
385
400
|
Logical:
|
|
386
|
-
|
|
401
|
+
|
|
402
|
+
- ``LogicalAndOp(left: Expression, right: Expression)``: represents ``left && right``
|
|
403
|
+
- ``LogicalOrOp(left: Expression, right: Expression)``: represents ``left || right``
|
|
404
|
+
- ``LogicalNotOp(expr: Expression)``: represents ``!expr``
|
|
387
405
|
|
|
388
406
|
Comparison:
|
|
389
|
-
|
|
390
|
-
- ``
|
|
391
|
-
- ``
|
|
407
|
+
|
|
408
|
+
- ``EqualityOp(left: Expression, right: Expression)``: represents ``left == right``
|
|
409
|
+
- ``InequalityOp(left: Expression, right: Expression)``: represents ``left != right``
|
|
410
|
+
- ``GreaterThanOp(left: Expression, right: Expression)``: represents ``left > right``
|
|
411
|
+
- ``GreaterThanOrEqualOp(left: Expression, right: Expression)``: represents ``left >= right``
|
|
412
|
+
- ``LessThanOp(left: Expression, right: Expression)``: represents ``left < right``
|
|
413
|
+
- ``LessThanOrEqualOp(left: Expression, right: Expression)``: represents ``left <= right``
|
|
392
414
|
|
|
393
415
|
Bitwise:
|
|
394
|
-
|
|
395
|
-
- ``
|
|
416
|
+
|
|
417
|
+
- ``BitwiseAndOp(left: Expression, right: Expression)``: represents ``left & right``
|
|
418
|
+
- ``BitwiseOrOp(left: Expression, right: Expression)``: represents ``left | right``
|
|
419
|
+
- ``BitwiseShiftLeftOp(left: Expression, right: Expression)``: represents ``left << right``
|
|
420
|
+
- ``BitwiseShiftRightOp(left: Expression, right: Expression)``: represents ``left >> right``
|
|
421
|
+
- ``BitwiseNotOp(expr: Expression)``: represents ``~expr``
|
|
396
422
|
|
|
397
423
|
Other:
|
|
398
|
-
|
|
424
|
+
|
|
425
|
+
- ``TernaryOp(condition: Expression, true_expr: Expression, false_expr: Expression)``: Represents ``condition ? true_expr : false_expr``
|
|
399
426
|
|
|
400
427
|
Expressions
|
|
401
428
|
~~~~~~~~~~~
|
|
402
429
|
|
|
403
|
-
- ``LetOp``: let(assignments) body
|
|
404
|
-
- ``EchoOp``: echo(arguments) body
|
|
405
|
-
- ``AssertOp``: assert(arguments) body
|
|
406
|
-
- ``FunctionLiteral``: function(parameters) body
|
|
407
|
-
- ``PrimaryCall``:
|
|
408
|
-
- ``PrimaryIndex``:
|
|
409
|
-
- ``PrimaryMember
|
|
430
|
+
- ``LetOp(assignments: list[Assignment], body: Expression)``: let clause ``let(assignments) body``
|
|
431
|
+
- ``EchoOp(arguments: list[Argument], body: Expression)``: echo clause ``echo(arguments) body``
|
|
432
|
+
- ``AssertOp(arguments: list[Argument], body: Expression)``: assert clause ``assert(arguments) body``
|
|
433
|
+
- ``FunctionLiteral(parameters: list[ParameterDeclaration], body: Expression)``: Anonymous function expression ``function(parameters) body``
|
|
434
|
+
- ``PrimaryCall(left: Expression, arguments: list[Argument])``: Function calls ``left(arguments)``
|
|
435
|
+
- ``PrimaryIndex(left: Expression, index: Expression)``: Array indexing ``left[index]``
|
|
436
|
+
- ``PrimaryMember(left: Expression, member: Identifier)``: Member access ``left.member``
|
|
410
437
|
|
|
411
438
|
List Comprehensions
|
|
412
439
|
~~~~~~~~~~~~~~~~~~~
|
|
413
440
|
|
|
414
|
-
- ``ListComprehension``: Vector/list literals
|
|
415
|
-
- ``ListCompFor``: for loops in list comprehensions
|
|
416
|
-
- ``ListCompCFor``: C-style for loops
|
|
417
|
-
- ``ListCompIf
|
|
418
|
-
- ``
|
|
419
|
-
- ``
|
|
441
|
+
- ``ListComprehension(elements: list[VectorElement])``: Vector/list literals ``[elements]``
|
|
442
|
+
- ``ListCompFor(assignments: list[Assignment], body: VectorElement)``: for loops in list comprehensions ``for(assignments) body``
|
|
443
|
+
- ``ListCompCFor(inits: list[Assignment], condition: Expression, incrs: list[Assignment], body: VectorElement)``: C-style for loops in list comprehensions ``for(inits; condition; incrs) body``
|
|
444
|
+
- ``ListCompIf(condition: Expression, true_expr: VectorElement)``: Conditional inclusion without else ``if(condition) true_expr``
|
|
445
|
+
- ``ListCompIfElse(condition: Expression, true_expr: VectorElement, false_expr: VectorElement)``: Conditional inclusion with else ``if(condition) true_expr else false_expr``
|
|
446
|
+
- ``ListCompLet(assignments: list[Assignment], body: VectorElement)``: let expressions in list comprehensions ``let(assignments) body``
|
|
447
|
+
- ``ListCompEach(body: VectorElement)``: each expressions (flattens nested lists) ``each body``
|
|
420
448
|
|
|
421
449
|
Module Instantiations
|
|
422
450
|
~~~~~~~~~~~~~~~~~~~~~
|
|
423
451
|
|
|
424
|
-
- ``ModularCall``: Module calls
|
|
425
|
-
- ``ModularFor``: for loops
|
|
426
|
-
- ``
|
|
427
|
-
- ``
|
|
428
|
-
- ``
|
|
429
|
-
- ``
|
|
430
|
-
- ``
|
|
431
|
-
- ``
|
|
432
|
-
- ``
|
|
433
|
-
- ``
|
|
434
|
-
- ``
|
|
435
|
-
- ``
|
|
436
|
-
- ``ModularModifierDisable``: ``*`` modifier
|
|
452
|
+
- ``ModularCall(name: Identifier, arguments: list[Argument], children: list[ModuleInstantiation])``: Module calls ``name(arguments) { children }``
|
|
453
|
+
- ``ModularFor(assignments: list[Assignment], body: ModuleInstantiation)``: for loops in module bodies ``for(assignments) body``
|
|
454
|
+
- ``ModularIntersectionFor(assignments: list[Assignment], body: ModuleInstantiation)``: intersection_for loops ``intersection_for(assignments) body``
|
|
455
|
+
- ``ModularLet(assignments: list[Assignment], children: list[ModuleInstantiation])``: let statements in module bodies ``let(assignments) { children }``
|
|
456
|
+
- ``ModularEcho(arguments: list[Argument], children: list[ModuleInstantiation])``: echo statements in module bodies ``echo(arguments) { children }``
|
|
457
|
+
- ``ModularAssert(arguments: list[Argument], children: list[ModuleInstantiation])``: assert statements in module bodies ``assert(arguments) { children }``
|
|
458
|
+
- ``ModularIf(condition: Expression, true_branch: ModuleInstantiation)``: if statements in module bodies, with no else ``if(condition) true_branch``
|
|
459
|
+
- ``ModularIfElse(condition: Expression, true_branch: ModuleInstantiation, false_branch: ModuleInstantiation)``: if/else statements in module bodies ``if(condition) true_branch else false_branch``
|
|
460
|
+
- ``ModularModifierShowOnly(child: ModuleInstantiation)``: Show-Only modifier ``!child``
|
|
461
|
+
- ``ModularModifierHighlight(child: ModuleInstantiation)``: Highlight modifier ``#child``
|
|
462
|
+
- ``ModularModifierBackground(child: ModuleInstantiation)``: Background modifier ``%child``
|
|
463
|
+
- ``ModularModifierDisable(child: ModuleInstantiation)``: Disabler modifier ``*child``
|
|
437
464
|
|
|
438
465
|
Declarations
|
|
439
466
|
~~~~~~~~~~~~
|
|
440
467
|
|
|
441
|
-
- ``ModuleDeclaration``: module
|
|
442
|
-
- ``FunctionDeclaration``: function
|
|
443
|
-
- ``ParameterDeclaration``:
|
|
444
|
-
- ``Assignment``:
|
|
468
|
+
- ``ModuleDeclaration(name: Identifier, parameters: list[ParameterDeclaration], children: list[ModuleInstantiation | Assignment | FunctionDeclaration | ModuleDeclaration])``: Module definitions ``module name(parameters) { children }``
|
|
469
|
+
- ``FunctionDeclaration(name: Identifier, parameters: list[ParameterDeclaration], expr: Expression)``: Function definitions ``function name(parameters) = expr;``
|
|
470
|
+
- ``ParameterDeclaration(name: Identifier, default: Expression | None)``: Function/module parameter with optional default value ``name=default`` or ``name``
|
|
471
|
+
- ``Assignment(name: Identifier, expr: Expression)``: Variable assignments ``name = expr;``
|
|
445
472
|
|
|
446
473
|
Statements
|
|
447
474
|
~~~~~~~~~~
|
|
448
475
|
|
|
449
|
-
- ``UseStatement``: use <filepath
|
|
450
|
-
- ``IncludeStatement``: include <filepath
|
|
451
|
-
- ``PositionalArgument``: Function call positional arguments
|
|
452
|
-
- ``NamedArgument``: Function call named arguments
|
|
476
|
+
- ``UseStatement(filepath: StringLiteral)``: Represents ``use <filepath>``
|
|
477
|
+
- ``IncludeStatement(filepath: StringLiteral)``: Represents ``include <filepath>``
|
|
478
|
+
- ``PositionalArgument(expr: Expression)``: Function call positional arguments ``expr``
|
|
479
|
+
- ``NamedArgument(name: Identifier, expr: Expression)``: Function call named arguments ``name=expr``
|
|
453
480
|
|
|
454
481
|
Comments
|
|
455
482
|
~~~~~~~~
|
|
456
483
|
|
|
457
|
-
- ``CommentLine``: Single-line comments
|
|
458
|
-
- ``CommentSpan``: Multi-line comments ``/* */``
|
|
484
|
+
- ``CommentLine(text: str)``: Single-line comments ``// str``
|
|
485
|
+
- ``CommentSpan(text: str)``: Multi-line comments ``/* str */``
|
|
459
486
|
|
|
460
487
|
All AST node classes are fully documented with docstrings that include:
|
|
461
488
|
- Description of what the node represents
|
|
@@ -552,6 +579,13 @@ Main Functions
|
|
|
552
579
|
- ``scope.lookup_module(name)`` — search this scope and its parents
|
|
553
580
|
- ``scope.parent`` — the enclosing scope (``None`` for root)
|
|
554
581
|
|
|
582
|
+
``to_openscad(nodes: list[ASTNode], indent_width: int = 4)``
|
|
583
|
+
Convert a list of AST nodes to formatted OpenSCAD source code.
|
|
584
|
+
|
|
585
|
+
:param nodes: Top-level AST nodes as returned by the ``getAST*`` functions.
|
|
586
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
587
|
+
:returns: Formatted OpenSCAD source as a string.
|
|
588
|
+
|
|
555
589
|
Serialization Functions
|
|
556
590
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
557
591
|
|
|
@@ -759,6 +793,100 @@ They are also available from ``openscad_parser.ast.serialization``::
|
|
|
759
793
|
ast_from_yaml,
|
|
760
794
|
)
|
|
761
795
|
|
|
796
|
+
Pretty-Printing
|
|
797
|
+
---------------
|
|
798
|
+
|
|
799
|
+
The ``to_openscad()`` function converts an AST back to formatted OpenSCAD source code::
|
|
800
|
+
|
|
801
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
802
|
+
|
|
803
|
+
code = "module box(w,h){cube([w,h,1]);}"
|
|
804
|
+
ast = getASTfromString(code)
|
|
805
|
+
|
|
806
|
+
formatted = to_openscad(ast)
|
|
807
|
+
# module box(w, h) {
|
|
808
|
+
# cube([w, h, 1.0]);
|
|
809
|
+
# }
|
|
810
|
+
print(formatted)
|
|
811
|
+
|
|
812
|
+
The pretty-printer normalises whitespace and indentation while preserving the logical
|
|
813
|
+
structure of the code. It supports all AST node types including modules, functions,
|
|
814
|
+
control structures, modifiers, list comprehensions, and comments.
|
|
815
|
+
|
|
816
|
+
``to_openscad(nodes, indent_width=4)``
|
|
817
|
+
Convert a list of AST nodes to formatted OpenSCAD source.
|
|
818
|
+
|
|
819
|
+
:param nodes: Top-level AST nodes (as returned by ``getAST*`` functions).
|
|
820
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
821
|
+
:returns: Formatted OpenSCAD source code as a string.
|
|
822
|
+
|
|
823
|
+
- Blank lines are inserted before and after module/function declarations.
|
|
824
|
+
- Single-child module instantiations are formatted inline; multiple children use a block.
|
|
825
|
+
- Comments are preserved when the AST was parsed with ``include_comments=True``.
|
|
826
|
+
|
|
827
|
+
Controlling indentation::
|
|
828
|
+
|
|
829
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
830
|
+
|
|
831
|
+
ast = getASTfromString("module m() { cube(1); }")
|
|
832
|
+
print(to_openscad(ast, indent_width=2))
|
|
833
|
+
# module m() {
|
|
834
|
+
# cube(1.0);
|
|
835
|
+
# }
|
|
836
|
+
|
|
837
|
+
Command-Line Interface
|
|
838
|
+
-----------------------
|
|
839
|
+
|
|
840
|
+
The ``openscad-parser`` CLI is installed alongside the package::
|
|
841
|
+
|
|
842
|
+
pip install openscad-parser
|
|
843
|
+
|
|
844
|
+
Usage::
|
|
845
|
+
|
|
846
|
+
openscad-parser [OPTIONS] [FILE]
|
|
847
|
+
|
|
848
|
+
Read from a file or ``-`` for stdin. Default output is JSON.
|
|
849
|
+
|
|
850
|
+
**Options:**
|
|
851
|
+
|
|
852
|
+
``--json``
|
|
853
|
+
Output AST as JSON (default).
|
|
854
|
+
|
|
855
|
+
``--yaml``
|
|
856
|
+
Output AST as YAML (requires ``pip install openscad_parser[yaml]``).
|
|
857
|
+
|
|
858
|
+
``--format``
|
|
859
|
+
Output reformatted OpenSCAD source code.
|
|
860
|
+
|
|
861
|
+
``--indent N``
|
|
862
|
+
Indentation width in spaces (default: 4). Applies to ``--format`` and ``--json``.
|
|
863
|
+
|
|
864
|
+
``--include-comments``
|
|
865
|
+
Include comment nodes in the output.
|
|
866
|
+
|
|
867
|
+
``--no-includes``
|
|
868
|
+
Do not expand ``include <...>`` statements; keep ``IncludeStatement`` nodes instead.
|
|
869
|
+
|
|
870
|
+
**Examples:**
|
|
871
|
+
|
|
872
|
+
Dump AST as JSON::
|
|
873
|
+
|
|
874
|
+
openscad-parser model.scad
|
|
875
|
+
openscad-parser - < model.scad # stdin
|
|
876
|
+
|
|
877
|
+
Reformat OpenSCAD source::
|
|
878
|
+
|
|
879
|
+
openscad-parser --format model.scad
|
|
880
|
+
openscad-parser --format --indent 2 model.scad
|
|
881
|
+
|
|
882
|
+
Output YAML::
|
|
883
|
+
|
|
884
|
+
openscad-parser --yaml model.scad
|
|
885
|
+
|
|
886
|
+
Include comments in the AST::
|
|
887
|
+
|
|
888
|
+
openscad-parser --include-comments --json model.scad
|
|
889
|
+
|
|
762
890
|
Error Handling
|
|
763
891
|
--------------
|
|
764
892
|
|
|
@@ -22,6 +22,8 @@ Features
|
|
|
22
22
|
- AST tree can contain comment nodes (single-line and multi-line)
|
|
23
23
|
- AST tree uses dataclasses and can be pickled/unpickled for caching/serialization
|
|
24
24
|
- JSON and YAML serialization/deserialization of AST trees
|
|
25
|
+
- Pretty-printer that converts an AST back to formatted OpenSCAD source (``to_openscad()``)
|
|
26
|
+
- Command-line interface (``openscad-parser``) for JSON/YAML/formatted output
|
|
25
27
|
|
|
26
28
|
Installation
|
|
27
29
|
------------
|
|
@@ -315,107 +317,129 @@ Parsing Library Files
|
|
|
315
317
|
AST Node Types
|
|
316
318
|
--------------
|
|
317
319
|
|
|
318
|
-
The AST includes comprehensive node types for all OpenSCAD language constructs
|
|
320
|
+
The AST includes comprehensive node types for all OpenSCAD language constructs.
|
|
321
|
+
All nodes inherit from ``ASTNode`` and carry ``position: Position`` and ``scope: Scope | None`` attributes.
|
|
319
322
|
|
|
320
323
|
Base Classes
|
|
321
324
|
~~~~~~~~~~~~
|
|
322
325
|
|
|
323
|
-
- ``ASTNode``: Base class for all AST nodes
|
|
326
|
+
- ``ASTNode(position: Position, scope: Scope | None)``: Base class for all AST nodes
|
|
324
327
|
- ``Expression``: Base class for all expression nodes
|
|
325
|
-
- ``Primary``: Base class for atomic value types
|
|
328
|
+
- ``Primary``: Base class for atomic value types (extends ``Expression``)
|
|
326
329
|
- ``ModuleInstantiation``: Base class for module-related statements
|
|
330
|
+
- ``VectorElement``: Base class for list comprehension elements
|
|
327
331
|
|
|
328
332
|
Literals
|
|
329
333
|
~~~~~~~~
|
|
330
334
|
|
|
331
|
-
- ``Identifier``: Variable, function, or module names
|
|
332
|
-
- ``StringLiteral``: String values
|
|
333
|
-
- ``NumberLiteral``: Numeric values
|
|
334
|
-
- ``BooleanLiteral``: true/false values
|
|
335
|
-
- ``UndefinedLiteral``: undef value
|
|
336
|
-
- ``RangeLiteral``: Range expressions [start:end
|
|
335
|
+
- ``Identifier(name: str)``: Variable, function, or module names
|
|
336
|
+
- ``StringLiteral(val: str)``: String values
|
|
337
|
+
- ``NumberLiteral(val: float)``: Numeric values
|
|
338
|
+
- ``BooleanLiteral(val: bool)``: true/false values
|
|
339
|
+
- ``UndefinedLiteral``: The ``undef`` value (no additional fields)
|
|
340
|
+
- ``RangeLiteral(start: Expression, end: Expression, step: Expression)``: Range expressions ``[start:step:end]``
|
|
337
341
|
|
|
338
342
|
Operators
|
|
339
343
|
~~~~~~~~~
|
|
340
344
|
|
|
345
|
+
All operators inherit from ``Expression`` and represent their respective operations with typed fields for operands. The AST preserves operator precedence and associativity as defined in OpenSCAD.
|
|
346
|
+
|
|
341
347
|
Arithmetic:
|
|
342
|
-
|
|
343
|
-
- ``
|
|
348
|
+
|
|
349
|
+
- ``AdditionOp(left: Expression, right: Expression)``: represents ``left + right``
|
|
350
|
+
- ``SubtractionOp(left: Expression, right: Expression)``: represents ``left - right``
|
|
351
|
+
- ``MultiplicationOp(left: Expression, right: Expression)``: represents ``left * right``
|
|
352
|
+
- ``DivisionOp(left: Expression, right: Expression)``: represents ``left / right``
|
|
353
|
+
- ``ModuloOp(left: Expression, right: Expression)``: represents ``left % right``
|
|
354
|
+
- ``ExponentOp(left: Expression, right: Expression)``: represents ``left ^ right``
|
|
355
|
+
- ``UnaryMinusOp(expr: Expression)``: represents ``-expr``
|
|
344
356
|
|
|
345
357
|
Logical:
|
|
346
|
-
|
|
358
|
+
|
|
359
|
+
- ``LogicalAndOp(left: Expression, right: Expression)``: represents ``left && right``
|
|
360
|
+
- ``LogicalOrOp(left: Expression, right: Expression)``: represents ``left || right``
|
|
361
|
+
- ``LogicalNotOp(expr: Expression)``: represents ``!expr``
|
|
347
362
|
|
|
348
363
|
Comparison:
|
|
349
|
-
|
|
350
|
-
- ``
|
|
351
|
-
- ``
|
|
364
|
+
|
|
365
|
+
- ``EqualityOp(left: Expression, right: Expression)``: represents ``left == right``
|
|
366
|
+
- ``InequalityOp(left: Expression, right: Expression)``: represents ``left != right``
|
|
367
|
+
- ``GreaterThanOp(left: Expression, right: Expression)``: represents ``left > right``
|
|
368
|
+
- ``GreaterThanOrEqualOp(left: Expression, right: Expression)``: represents ``left >= right``
|
|
369
|
+
- ``LessThanOp(left: Expression, right: Expression)``: represents ``left < right``
|
|
370
|
+
- ``LessThanOrEqualOp(left: Expression, right: Expression)``: represents ``left <= right``
|
|
352
371
|
|
|
353
372
|
Bitwise:
|
|
354
|
-
|
|
355
|
-
- ``
|
|
373
|
+
|
|
374
|
+
- ``BitwiseAndOp(left: Expression, right: Expression)``: represents ``left & right``
|
|
375
|
+
- ``BitwiseOrOp(left: Expression, right: Expression)``: represents ``left | right``
|
|
376
|
+
- ``BitwiseShiftLeftOp(left: Expression, right: Expression)``: represents ``left << right``
|
|
377
|
+
- ``BitwiseShiftRightOp(left: Expression, right: Expression)``: represents ``left >> right``
|
|
378
|
+
- ``BitwiseNotOp(expr: Expression)``: represents ``~expr``
|
|
356
379
|
|
|
357
380
|
Other:
|
|
358
|
-
|
|
381
|
+
|
|
382
|
+
- ``TernaryOp(condition: Expression, true_expr: Expression, false_expr: Expression)``: Represents ``condition ? true_expr : false_expr``
|
|
359
383
|
|
|
360
384
|
Expressions
|
|
361
385
|
~~~~~~~~~~~
|
|
362
386
|
|
|
363
|
-
- ``LetOp``: let(assignments) body
|
|
364
|
-
- ``EchoOp``: echo(arguments) body
|
|
365
|
-
- ``AssertOp``: assert(arguments) body
|
|
366
|
-
- ``FunctionLiteral``: function(parameters) body
|
|
367
|
-
- ``PrimaryCall``:
|
|
368
|
-
- ``PrimaryIndex``:
|
|
369
|
-
- ``PrimaryMember
|
|
387
|
+
- ``LetOp(assignments: list[Assignment], body: Expression)``: let clause ``let(assignments) body``
|
|
388
|
+
- ``EchoOp(arguments: list[Argument], body: Expression)``: echo clause ``echo(arguments) body``
|
|
389
|
+
- ``AssertOp(arguments: list[Argument], body: Expression)``: assert clause ``assert(arguments) body``
|
|
390
|
+
- ``FunctionLiteral(parameters: list[ParameterDeclaration], body: Expression)``: Anonymous function expression ``function(parameters) body``
|
|
391
|
+
- ``PrimaryCall(left: Expression, arguments: list[Argument])``: Function calls ``left(arguments)``
|
|
392
|
+
- ``PrimaryIndex(left: Expression, index: Expression)``: Array indexing ``left[index]``
|
|
393
|
+
- ``PrimaryMember(left: Expression, member: Identifier)``: Member access ``left.member``
|
|
370
394
|
|
|
371
395
|
List Comprehensions
|
|
372
396
|
~~~~~~~~~~~~~~~~~~~
|
|
373
397
|
|
|
374
|
-
- ``ListComprehension``: Vector/list literals
|
|
375
|
-
- ``ListCompFor``: for loops in list comprehensions
|
|
376
|
-
- ``ListCompCFor``: C-style for loops
|
|
377
|
-
- ``ListCompIf
|
|
378
|
-
- ``
|
|
379
|
-
- ``
|
|
398
|
+
- ``ListComprehension(elements: list[VectorElement])``: Vector/list literals ``[elements]``
|
|
399
|
+
- ``ListCompFor(assignments: list[Assignment], body: VectorElement)``: for loops in list comprehensions ``for(assignments) body``
|
|
400
|
+
- ``ListCompCFor(inits: list[Assignment], condition: Expression, incrs: list[Assignment], body: VectorElement)``: C-style for loops in list comprehensions ``for(inits; condition; incrs) body``
|
|
401
|
+
- ``ListCompIf(condition: Expression, true_expr: VectorElement)``: Conditional inclusion without else ``if(condition) true_expr``
|
|
402
|
+
- ``ListCompIfElse(condition: Expression, true_expr: VectorElement, false_expr: VectorElement)``: Conditional inclusion with else ``if(condition) true_expr else false_expr``
|
|
403
|
+
- ``ListCompLet(assignments: list[Assignment], body: VectorElement)``: let expressions in list comprehensions ``let(assignments) body``
|
|
404
|
+
- ``ListCompEach(body: VectorElement)``: each expressions (flattens nested lists) ``each body``
|
|
380
405
|
|
|
381
406
|
Module Instantiations
|
|
382
407
|
~~~~~~~~~~~~~~~~~~~~~
|
|
383
408
|
|
|
384
|
-
- ``ModularCall``: Module calls
|
|
385
|
-
- ``ModularFor``: for loops
|
|
386
|
-
- ``
|
|
387
|
-
- ``
|
|
388
|
-
- ``
|
|
389
|
-
- ``
|
|
390
|
-
- ``
|
|
391
|
-
- ``
|
|
392
|
-
- ``
|
|
393
|
-
- ``
|
|
394
|
-
- ``
|
|
395
|
-
- ``
|
|
396
|
-
- ``ModularModifierDisable``: ``*`` modifier
|
|
409
|
+
- ``ModularCall(name: Identifier, arguments: list[Argument], children: list[ModuleInstantiation])``: Module calls ``name(arguments) { children }``
|
|
410
|
+
- ``ModularFor(assignments: list[Assignment], body: ModuleInstantiation)``: for loops in module bodies ``for(assignments) body``
|
|
411
|
+
- ``ModularIntersectionFor(assignments: list[Assignment], body: ModuleInstantiation)``: intersection_for loops ``intersection_for(assignments) body``
|
|
412
|
+
- ``ModularLet(assignments: list[Assignment], children: list[ModuleInstantiation])``: let statements in module bodies ``let(assignments) { children }``
|
|
413
|
+
- ``ModularEcho(arguments: list[Argument], children: list[ModuleInstantiation])``: echo statements in module bodies ``echo(arguments) { children }``
|
|
414
|
+
- ``ModularAssert(arguments: list[Argument], children: list[ModuleInstantiation])``: assert statements in module bodies ``assert(arguments) { children }``
|
|
415
|
+
- ``ModularIf(condition: Expression, true_branch: ModuleInstantiation)``: if statements in module bodies, with no else ``if(condition) true_branch``
|
|
416
|
+
- ``ModularIfElse(condition: Expression, true_branch: ModuleInstantiation, false_branch: ModuleInstantiation)``: if/else statements in module bodies ``if(condition) true_branch else false_branch``
|
|
417
|
+
- ``ModularModifierShowOnly(child: ModuleInstantiation)``: Show-Only modifier ``!child``
|
|
418
|
+
- ``ModularModifierHighlight(child: ModuleInstantiation)``: Highlight modifier ``#child``
|
|
419
|
+
- ``ModularModifierBackground(child: ModuleInstantiation)``: Background modifier ``%child``
|
|
420
|
+
- ``ModularModifierDisable(child: ModuleInstantiation)``: Disabler modifier ``*child``
|
|
397
421
|
|
|
398
422
|
Declarations
|
|
399
423
|
~~~~~~~~~~~~
|
|
400
424
|
|
|
401
|
-
- ``ModuleDeclaration``: module
|
|
402
|
-
- ``FunctionDeclaration``: function
|
|
403
|
-
- ``ParameterDeclaration``:
|
|
404
|
-
- ``Assignment``:
|
|
425
|
+
- ``ModuleDeclaration(name: Identifier, parameters: list[ParameterDeclaration], children: list[ModuleInstantiation | Assignment | FunctionDeclaration | ModuleDeclaration])``: Module definitions ``module name(parameters) { children }``
|
|
426
|
+
- ``FunctionDeclaration(name: Identifier, parameters: list[ParameterDeclaration], expr: Expression)``: Function definitions ``function name(parameters) = expr;``
|
|
427
|
+
- ``ParameterDeclaration(name: Identifier, default: Expression | None)``: Function/module parameter with optional default value ``name=default`` or ``name``
|
|
428
|
+
- ``Assignment(name: Identifier, expr: Expression)``: Variable assignments ``name = expr;``
|
|
405
429
|
|
|
406
430
|
Statements
|
|
407
431
|
~~~~~~~~~~
|
|
408
432
|
|
|
409
|
-
- ``UseStatement``: use <filepath
|
|
410
|
-
- ``IncludeStatement``: include <filepath
|
|
411
|
-
- ``PositionalArgument``: Function call positional arguments
|
|
412
|
-
- ``NamedArgument``: Function call named arguments
|
|
433
|
+
- ``UseStatement(filepath: StringLiteral)``: Represents ``use <filepath>``
|
|
434
|
+
- ``IncludeStatement(filepath: StringLiteral)``: Represents ``include <filepath>``
|
|
435
|
+
- ``PositionalArgument(expr: Expression)``: Function call positional arguments ``expr``
|
|
436
|
+
- ``NamedArgument(name: Identifier, expr: Expression)``: Function call named arguments ``name=expr``
|
|
413
437
|
|
|
414
438
|
Comments
|
|
415
439
|
~~~~~~~~
|
|
416
440
|
|
|
417
|
-
- ``CommentLine``: Single-line comments
|
|
418
|
-
- ``CommentSpan``: Multi-line comments ``/* */``
|
|
441
|
+
- ``CommentLine(text: str)``: Single-line comments ``// str``
|
|
442
|
+
- ``CommentSpan(text: str)``: Multi-line comments ``/* str */``
|
|
419
443
|
|
|
420
444
|
All AST node classes are fully documented with docstrings that include:
|
|
421
445
|
- Description of what the node represents
|
|
@@ -512,6 +536,13 @@ Main Functions
|
|
|
512
536
|
- ``scope.lookup_module(name)`` — search this scope and its parents
|
|
513
537
|
- ``scope.parent`` — the enclosing scope (``None`` for root)
|
|
514
538
|
|
|
539
|
+
``to_openscad(nodes: list[ASTNode], indent_width: int = 4)``
|
|
540
|
+
Convert a list of AST nodes to formatted OpenSCAD source code.
|
|
541
|
+
|
|
542
|
+
:param nodes: Top-level AST nodes as returned by the ``getAST*`` functions.
|
|
543
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
544
|
+
:returns: Formatted OpenSCAD source as a string.
|
|
545
|
+
|
|
515
546
|
Serialization Functions
|
|
516
547
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
517
548
|
|
|
@@ -719,6 +750,100 @@ They are also available from ``openscad_parser.ast.serialization``::
|
|
|
719
750
|
ast_from_yaml,
|
|
720
751
|
)
|
|
721
752
|
|
|
753
|
+
Pretty-Printing
|
|
754
|
+
---------------
|
|
755
|
+
|
|
756
|
+
The ``to_openscad()`` function converts an AST back to formatted OpenSCAD source code::
|
|
757
|
+
|
|
758
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
759
|
+
|
|
760
|
+
code = "module box(w,h){cube([w,h,1]);}"
|
|
761
|
+
ast = getASTfromString(code)
|
|
762
|
+
|
|
763
|
+
formatted = to_openscad(ast)
|
|
764
|
+
# module box(w, h) {
|
|
765
|
+
# cube([w, h, 1.0]);
|
|
766
|
+
# }
|
|
767
|
+
print(formatted)
|
|
768
|
+
|
|
769
|
+
The pretty-printer normalises whitespace and indentation while preserving the logical
|
|
770
|
+
structure of the code. It supports all AST node types including modules, functions,
|
|
771
|
+
control structures, modifiers, list comprehensions, and comments.
|
|
772
|
+
|
|
773
|
+
``to_openscad(nodes, indent_width=4)``
|
|
774
|
+
Convert a list of AST nodes to formatted OpenSCAD source.
|
|
775
|
+
|
|
776
|
+
:param nodes: Top-level AST nodes (as returned by ``getAST*`` functions).
|
|
777
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
778
|
+
:returns: Formatted OpenSCAD source code as a string.
|
|
779
|
+
|
|
780
|
+
- Blank lines are inserted before and after module/function declarations.
|
|
781
|
+
- Single-child module instantiations are formatted inline; multiple children use a block.
|
|
782
|
+
- Comments are preserved when the AST was parsed with ``include_comments=True``.
|
|
783
|
+
|
|
784
|
+
Controlling indentation::
|
|
785
|
+
|
|
786
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
787
|
+
|
|
788
|
+
ast = getASTfromString("module m() { cube(1); }")
|
|
789
|
+
print(to_openscad(ast, indent_width=2))
|
|
790
|
+
# module m() {
|
|
791
|
+
# cube(1.0);
|
|
792
|
+
# }
|
|
793
|
+
|
|
794
|
+
Command-Line Interface
|
|
795
|
+
-----------------------
|
|
796
|
+
|
|
797
|
+
The ``openscad-parser`` CLI is installed alongside the package::
|
|
798
|
+
|
|
799
|
+
pip install openscad-parser
|
|
800
|
+
|
|
801
|
+
Usage::
|
|
802
|
+
|
|
803
|
+
openscad-parser [OPTIONS] [FILE]
|
|
804
|
+
|
|
805
|
+
Read from a file or ``-`` for stdin. Default output is JSON.
|
|
806
|
+
|
|
807
|
+
**Options:**
|
|
808
|
+
|
|
809
|
+
``--json``
|
|
810
|
+
Output AST as JSON (default).
|
|
811
|
+
|
|
812
|
+
``--yaml``
|
|
813
|
+
Output AST as YAML (requires ``pip install openscad_parser[yaml]``).
|
|
814
|
+
|
|
815
|
+
``--format``
|
|
816
|
+
Output reformatted OpenSCAD source code.
|
|
817
|
+
|
|
818
|
+
``--indent N``
|
|
819
|
+
Indentation width in spaces (default: 4). Applies to ``--format`` and ``--json``.
|
|
820
|
+
|
|
821
|
+
``--include-comments``
|
|
822
|
+
Include comment nodes in the output.
|
|
823
|
+
|
|
824
|
+
``--no-includes``
|
|
825
|
+
Do not expand ``include <...>`` statements; keep ``IncludeStatement`` nodes instead.
|
|
826
|
+
|
|
827
|
+
**Examples:**
|
|
828
|
+
|
|
829
|
+
Dump AST as JSON::
|
|
830
|
+
|
|
831
|
+
openscad-parser model.scad
|
|
832
|
+
openscad-parser - < model.scad # stdin
|
|
833
|
+
|
|
834
|
+
Reformat OpenSCAD source::
|
|
835
|
+
|
|
836
|
+
openscad-parser --format model.scad
|
|
837
|
+
openscad-parser --format --indent 2 model.scad
|
|
838
|
+
|
|
839
|
+
Output YAML::
|
|
840
|
+
|
|
841
|
+
openscad-parser --yaml model.scad
|
|
842
|
+
|
|
843
|
+
Include comments in the AST::
|
|
844
|
+
|
|
845
|
+
openscad-parser --include-comments --json model.scad
|
|
846
|
+
|
|
722
847
|
Error Handling
|
|
723
848
|
--------------
|
|
724
849
|
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openscad_parser"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.1"
|
|
8
8
|
description = "A PEG parser to read OpenSCAD language source code, with optional AST tree generation."
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
authors = [
|
|
@@ -24,6 +24,9 @@ classifiers = [
|
|
|
24
24
|
"Operating System :: Microsoft :: Windows",
|
|
25
25
|
"Operating System :: POSIX",
|
|
26
26
|
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
27
30
|
"Topic :: Artistic Software",
|
|
28
31
|
"Topic :: Multimedia :: Graphics :: 3D Modeling",
|
|
29
32
|
"Topic :: Multimedia :: Graphics :: 3D Rendering",
|
|
@@ -35,6 +38,9 @@ dependencies = [
|
|
|
35
38
|
"arpeggio>=2.0.3",
|
|
36
39
|
]
|
|
37
40
|
|
|
41
|
+
[project.scripts]
|
|
42
|
+
openscad-parser = "openscad_parser.cli:main"
|
|
43
|
+
|
|
38
44
|
[project.optional-dependencies]
|
|
39
45
|
dev = [
|
|
40
46
|
"pytest>=7.0.0",
|