openscad-parser 2.3.3__tar.gz → 2.4.0__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.3/README.rst → openscad_parser-2.4.0/PKG-INFO +146 -0
- openscad_parser-2.3.3/PKG-INFO → openscad_parser-2.4.0/README.rst +103 -40
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/pyproject.toml +8 -2
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/ast/__init__.py +3 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/ast/nodes.py +4 -4
- openscad_parser-2.4.0/src/openscad_parser/ast/pretty_print.py +169 -0
- openscad_parser-2.4.0/src/openscad_parser/cli.py +84 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/__init__.py +0 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/ast/builder.py +0 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/ast/scope.py +0 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/ast/serialization.py +0 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/ast/source_map.py +0 -0
- {openscad_parser-2.3.3 → openscad_parser-2.4.0}/src/openscad_parser/grammar.py +0 -0
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openscad_parser
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: A PEG parser to read OpenSCAD language source code, with optional AST tree generation.
|
|
5
|
+
Keywords: openscad,openscad parser,parser
|
|
6
|
+
Author: Revar Desmera
|
|
7
|
+
Author-email: Revar Desmera <revarbat@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Manufacturing
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
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
|
|
20
|
+
Classifier: Topic :: Artistic Software
|
|
21
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Dist: arpeggio>=2.0.3
|
|
26
|
+
Requires-Dist: pytest>=7.0.0 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=7.1.0 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: pyyaml>=6.0 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: genbadge[coverage]>=1.1.0 ; extra == 'dev'
|
|
30
|
+
Requires-Dist: pyyaml>=6.0 ; extra == 'yaml'
|
|
31
|
+
Maintainer: Revar Desmera
|
|
32
|
+
Maintainer-email: Revar Desmera <revarbat@gmail.com>
|
|
33
|
+
Requires-Python: >=3.11
|
|
34
|
+
Project-URL: Bug Tracker, https://github.com/belfryscad/openscad_parser/issues
|
|
35
|
+
Project-URL: Documentation, https://github.com/belfryscad/openscad_parser/WRITING_DOCS.md
|
|
36
|
+
Project-URL: Homepage, https://github.com/belfryscad/openscad_parser
|
|
37
|
+
Project-URL: Releases, https://github.com/belfryscad/openscad_parser/releases
|
|
38
|
+
Project-URL: Repository, https://github.com/belfryscad/openscad_parser
|
|
39
|
+
Project-URL: Usage, https://github.com/belfryscad/openscad_parser/README.rst
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Provides-Extra: yaml
|
|
42
|
+
Description-Content-Type: text/x-rst
|
|
43
|
+
|
|
1
44
|
OpenSCAD Parser
|
|
2
45
|
===============
|
|
3
46
|
|
|
@@ -22,6 +65,8 @@ Features
|
|
|
22
65
|
- AST tree can contain comment nodes (single-line and multi-line)
|
|
23
66
|
- AST tree uses dataclasses and can be pickled/unpickled for caching/serialization
|
|
24
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
|
|
25
70
|
|
|
26
71
|
Installation
|
|
27
72
|
------------
|
|
@@ -512,6 +557,13 @@ Main Functions
|
|
|
512
557
|
- ``scope.lookup_module(name)`` — search this scope and its parents
|
|
513
558
|
- ``scope.parent`` — the enclosing scope (``None`` for root)
|
|
514
559
|
|
|
560
|
+
``to_openscad(nodes: list[ASTNode], indent_width: int = 4)``
|
|
561
|
+
Convert a list of AST nodes to formatted OpenSCAD source code.
|
|
562
|
+
|
|
563
|
+
:param nodes: Top-level AST nodes as returned by the ``getAST*`` functions.
|
|
564
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
565
|
+
:returns: Formatted OpenSCAD source as a string.
|
|
566
|
+
|
|
515
567
|
Serialization Functions
|
|
516
568
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
517
569
|
|
|
@@ -719,6 +771,100 @@ They are also available from ``openscad_parser.ast.serialization``::
|
|
|
719
771
|
ast_from_yaml,
|
|
720
772
|
)
|
|
721
773
|
|
|
774
|
+
Pretty-Printing
|
|
775
|
+
---------------
|
|
776
|
+
|
|
777
|
+
The ``to_openscad()`` function converts an AST back to formatted OpenSCAD source code::
|
|
778
|
+
|
|
779
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
780
|
+
|
|
781
|
+
code = "module box(w,h){cube([w,h,1]);}"
|
|
782
|
+
ast = getASTfromString(code)
|
|
783
|
+
|
|
784
|
+
formatted = to_openscad(ast)
|
|
785
|
+
# module box(w, h) {
|
|
786
|
+
# cube([w, h, 1.0]);
|
|
787
|
+
# }
|
|
788
|
+
print(formatted)
|
|
789
|
+
|
|
790
|
+
The pretty-printer normalises whitespace and indentation while preserving the logical
|
|
791
|
+
structure of the code. It supports all AST node types including modules, functions,
|
|
792
|
+
control structures, modifiers, list comprehensions, and comments.
|
|
793
|
+
|
|
794
|
+
``to_openscad(nodes, indent_width=4)``
|
|
795
|
+
Convert a list of AST nodes to formatted OpenSCAD source.
|
|
796
|
+
|
|
797
|
+
:param nodes: Top-level AST nodes (as returned by ``getAST*`` functions).
|
|
798
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
799
|
+
:returns: Formatted OpenSCAD source code as a string.
|
|
800
|
+
|
|
801
|
+
- Blank lines are inserted before and after module/function declarations.
|
|
802
|
+
- Single-child module instantiations are formatted inline; multiple children use a block.
|
|
803
|
+
- Comments are preserved when the AST was parsed with ``include_comments=True``.
|
|
804
|
+
|
|
805
|
+
Controlling indentation::
|
|
806
|
+
|
|
807
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
808
|
+
|
|
809
|
+
ast = getASTfromString("module m() { cube(1); }")
|
|
810
|
+
print(to_openscad(ast, indent_width=2))
|
|
811
|
+
# module m() {
|
|
812
|
+
# cube(1.0);
|
|
813
|
+
# }
|
|
814
|
+
|
|
815
|
+
Command-Line Interface
|
|
816
|
+
-----------------------
|
|
817
|
+
|
|
818
|
+
The ``openscad-parser`` CLI is installed alongside the package::
|
|
819
|
+
|
|
820
|
+
pip install openscad-parser
|
|
821
|
+
|
|
822
|
+
Usage::
|
|
823
|
+
|
|
824
|
+
openscad-parser [OPTIONS] [FILE]
|
|
825
|
+
|
|
826
|
+
Read from a file or ``-`` for stdin. Default output is JSON.
|
|
827
|
+
|
|
828
|
+
**Options:**
|
|
829
|
+
|
|
830
|
+
``--json``
|
|
831
|
+
Output AST as JSON (default).
|
|
832
|
+
|
|
833
|
+
``--yaml``
|
|
834
|
+
Output AST as YAML (requires ``pip install openscad_parser[yaml]``).
|
|
835
|
+
|
|
836
|
+
``--format``
|
|
837
|
+
Output reformatted OpenSCAD source code.
|
|
838
|
+
|
|
839
|
+
``--indent N``
|
|
840
|
+
Indentation width in spaces (default: 4). Applies to ``--format`` and ``--json``.
|
|
841
|
+
|
|
842
|
+
``--include-comments``
|
|
843
|
+
Include comment nodes in the output.
|
|
844
|
+
|
|
845
|
+
``--no-includes``
|
|
846
|
+
Do not expand ``include <...>`` statements; keep ``IncludeStatement`` nodes instead.
|
|
847
|
+
|
|
848
|
+
**Examples:**
|
|
849
|
+
|
|
850
|
+
Dump AST as JSON::
|
|
851
|
+
|
|
852
|
+
openscad-parser model.scad
|
|
853
|
+
openscad-parser - < model.scad # stdin
|
|
854
|
+
|
|
855
|
+
Reformat OpenSCAD source::
|
|
856
|
+
|
|
857
|
+
openscad-parser --format model.scad
|
|
858
|
+
openscad-parser --format --indent 2 model.scad
|
|
859
|
+
|
|
860
|
+
Output YAML::
|
|
861
|
+
|
|
862
|
+
openscad-parser --yaml model.scad
|
|
863
|
+
|
|
864
|
+
Include comments in the AST::
|
|
865
|
+
|
|
866
|
+
openscad-parser --include-comments --json model.scad
|
|
867
|
+
|
|
722
868
|
Error Handling
|
|
723
869
|
--------------
|
|
724
870
|
|
|
@@ -1,43 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: openscad_parser
|
|
3
|
-
Version: 2.3.3
|
|
4
|
-
Summary: A PEG parser to read OpenSCAD language source code, with optional AST tree generation.
|
|
5
|
-
Keywords: openscad,openscad parser,parser
|
|
6
|
-
Author: Revar Desmera
|
|
7
|
-
Author-email: Revar Desmera <revarbat@gmail.com>
|
|
8
|
-
License-Expression: MIT
|
|
9
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Intended Audience :: Manufacturing
|
|
13
|
-
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
-
Classifier: Operating System :: POSIX
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Topic :: Artistic Software
|
|
18
|
-
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
19
|
-
Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
|
|
20
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
-
Requires-Dist: arpeggio>=2.0.3
|
|
23
|
-
Requires-Dist: pytest>=7.0.0 ; extra == 'dev'
|
|
24
|
-
Requires-Dist: pytest-cov>=7.1.0 ; extra == 'dev'
|
|
25
|
-
Requires-Dist: pyyaml>=6.0 ; extra == 'dev'
|
|
26
|
-
Requires-Dist: coverage-badge>=1.1.0 ; extra == 'dev'
|
|
27
|
-
Requires-Dist: pyyaml>=6.0 ; extra == 'yaml'
|
|
28
|
-
Maintainer: Revar Desmera
|
|
29
|
-
Maintainer-email: Revar Desmera <revarbat@gmail.com>
|
|
30
|
-
Requires-Python: >=3.11
|
|
31
|
-
Project-URL: Bug Tracker, https://github.com/belfryscad/openscad_parser/issues
|
|
32
|
-
Project-URL: Documentation, https://github.com/belfryscad/openscad_parser/WRITING_DOCS.md
|
|
33
|
-
Project-URL: Homepage, https://github.com/belfryscad/openscad_parser
|
|
34
|
-
Project-URL: Releases, https://github.com/belfryscad/openscad_parser/releases
|
|
35
|
-
Project-URL: Repository, https://github.com/belfryscad/openscad_parser
|
|
36
|
-
Project-URL: Usage, https://github.com/belfryscad/openscad_parser/README.rst
|
|
37
|
-
Provides-Extra: dev
|
|
38
|
-
Provides-Extra: yaml
|
|
39
|
-
Description-Content-Type: text/x-rst
|
|
40
|
-
|
|
41
1
|
OpenSCAD Parser
|
|
42
2
|
===============
|
|
43
3
|
|
|
@@ -62,6 +22,8 @@ Features
|
|
|
62
22
|
- AST tree can contain comment nodes (single-line and multi-line)
|
|
63
23
|
- AST tree uses dataclasses and can be pickled/unpickled for caching/serialization
|
|
64
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
|
|
65
27
|
|
|
66
28
|
Installation
|
|
67
29
|
------------
|
|
@@ -552,6 +514,13 @@ Main Functions
|
|
|
552
514
|
- ``scope.lookup_module(name)`` — search this scope and its parents
|
|
553
515
|
- ``scope.parent`` — the enclosing scope (``None`` for root)
|
|
554
516
|
|
|
517
|
+
``to_openscad(nodes: list[ASTNode], indent_width: int = 4)``
|
|
518
|
+
Convert a list of AST nodes to formatted OpenSCAD source code.
|
|
519
|
+
|
|
520
|
+
:param nodes: Top-level AST nodes as returned by the ``getAST*`` functions.
|
|
521
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
522
|
+
:returns: Formatted OpenSCAD source as a string.
|
|
523
|
+
|
|
555
524
|
Serialization Functions
|
|
556
525
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
557
526
|
|
|
@@ -759,6 +728,100 @@ They are also available from ``openscad_parser.ast.serialization``::
|
|
|
759
728
|
ast_from_yaml,
|
|
760
729
|
)
|
|
761
730
|
|
|
731
|
+
Pretty-Printing
|
|
732
|
+
---------------
|
|
733
|
+
|
|
734
|
+
The ``to_openscad()`` function converts an AST back to formatted OpenSCAD source code::
|
|
735
|
+
|
|
736
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
737
|
+
|
|
738
|
+
code = "module box(w,h){cube([w,h,1]);}"
|
|
739
|
+
ast = getASTfromString(code)
|
|
740
|
+
|
|
741
|
+
formatted = to_openscad(ast)
|
|
742
|
+
# module box(w, h) {
|
|
743
|
+
# cube([w, h, 1.0]);
|
|
744
|
+
# }
|
|
745
|
+
print(formatted)
|
|
746
|
+
|
|
747
|
+
The pretty-printer normalises whitespace and indentation while preserving the logical
|
|
748
|
+
structure of the code. It supports all AST node types including modules, functions,
|
|
749
|
+
control structures, modifiers, list comprehensions, and comments.
|
|
750
|
+
|
|
751
|
+
``to_openscad(nodes, indent_width=4)``
|
|
752
|
+
Convert a list of AST nodes to formatted OpenSCAD source.
|
|
753
|
+
|
|
754
|
+
:param nodes: Top-level AST nodes (as returned by ``getAST*`` functions).
|
|
755
|
+
:param indent_width: Spaces per indentation level (default: 4).
|
|
756
|
+
:returns: Formatted OpenSCAD source code as a string.
|
|
757
|
+
|
|
758
|
+
- Blank lines are inserted before and after module/function declarations.
|
|
759
|
+
- Single-child module instantiations are formatted inline; multiple children use a block.
|
|
760
|
+
- Comments are preserved when the AST was parsed with ``include_comments=True``.
|
|
761
|
+
|
|
762
|
+
Controlling indentation::
|
|
763
|
+
|
|
764
|
+
from openscad_parser.ast import getASTfromString, to_openscad
|
|
765
|
+
|
|
766
|
+
ast = getASTfromString("module m() { cube(1); }")
|
|
767
|
+
print(to_openscad(ast, indent_width=2))
|
|
768
|
+
# module m() {
|
|
769
|
+
# cube(1.0);
|
|
770
|
+
# }
|
|
771
|
+
|
|
772
|
+
Command-Line Interface
|
|
773
|
+
-----------------------
|
|
774
|
+
|
|
775
|
+
The ``openscad-parser`` CLI is installed alongside the package::
|
|
776
|
+
|
|
777
|
+
pip install openscad-parser
|
|
778
|
+
|
|
779
|
+
Usage::
|
|
780
|
+
|
|
781
|
+
openscad-parser [OPTIONS] [FILE]
|
|
782
|
+
|
|
783
|
+
Read from a file or ``-`` for stdin. Default output is JSON.
|
|
784
|
+
|
|
785
|
+
**Options:**
|
|
786
|
+
|
|
787
|
+
``--json``
|
|
788
|
+
Output AST as JSON (default).
|
|
789
|
+
|
|
790
|
+
``--yaml``
|
|
791
|
+
Output AST as YAML (requires ``pip install openscad_parser[yaml]``).
|
|
792
|
+
|
|
793
|
+
``--format``
|
|
794
|
+
Output reformatted OpenSCAD source code.
|
|
795
|
+
|
|
796
|
+
``--indent N``
|
|
797
|
+
Indentation width in spaces (default: 4). Applies to ``--format`` and ``--json``.
|
|
798
|
+
|
|
799
|
+
``--include-comments``
|
|
800
|
+
Include comment nodes in the output.
|
|
801
|
+
|
|
802
|
+
``--no-includes``
|
|
803
|
+
Do not expand ``include <...>`` statements; keep ``IncludeStatement`` nodes instead.
|
|
804
|
+
|
|
805
|
+
**Examples:**
|
|
806
|
+
|
|
807
|
+
Dump AST as JSON::
|
|
808
|
+
|
|
809
|
+
openscad-parser model.scad
|
|
810
|
+
openscad-parser - < model.scad # stdin
|
|
811
|
+
|
|
812
|
+
Reformat OpenSCAD source::
|
|
813
|
+
|
|
814
|
+
openscad-parser --format model.scad
|
|
815
|
+
openscad-parser --format --indent 2 model.scad
|
|
816
|
+
|
|
817
|
+
Output YAML::
|
|
818
|
+
|
|
819
|
+
openscad-parser --yaml model.scad
|
|
820
|
+
|
|
821
|
+
Include comments in the AST::
|
|
822
|
+
|
|
823
|
+
openscad-parser --include-comments --json model.scad
|
|
824
|
+
|
|
762
825
|
Error Handling
|
|
763
826
|
--------------
|
|
764
827
|
|
|
@@ -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.0"
|
|
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,12 +38,15 @@ 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",
|
|
41
47
|
"pytest-cov>=7.1.0",
|
|
42
48
|
"PyYAML>=6.0",
|
|
43
|
-
"coverage
|
|
49
|
+
"genbadge[coverage]>=1.1.0",
|
|
44
50
|
]
|
|
45
51
|
yaml = [
|
|
46
52
|
"PyYAML>=6.0",
|
|
@@ -87,6 +87,9 @@ from .builder import ASTBuilderVisitor, Position
|
|
|
87
87
|
# Import scope classes
|
|
88
88
|
from .scope import Scope, build_scopes
|
|
89
89
|
|
|
90
|
+
# Import pretty-printer
|
|
91
|
+
from .pretty_print import to_openscad
|
|
92
|
+
|
|
90
93
|
# Import serialization functions
|
|
91
94
|
from .serialization import (
|
|
92
95
|
ast_to_dict,
|
|
@@ -333,7 +333,7 @@ class RangeLiteral(Primary):
|
|
|
333
333
|
step: Expression
|
|
334
334
|
|
|
335
335
|
def __str__(self):
|
|
336
|
-
return f"{self.start}:{self.end}:{self.step}"
|
|
336
|
+
return f"[{self.start}:{self.end}:{self.step}]"
|
|
337
337
|
|
|
338
338
|
def build_scope(self, parent_scope: "Scope") -> None:
|
|
339
339
|
self.scope = parent_scope
|
|
@@ -1208,7 +1208,7 @@ class ListCompLet(VectorElement):
|
|
|
1208
1208
|
body: Expression
|
|
1209
1209
|
|
|
1210
1210
|
def __str__(self):
|
|
1211
|
-
return f"let ({self.assignments}) {self.body}"
|
|
1211
|
+
return f"let ({', '.join(str(a) for a in self.assignments)}) {self.body}"
|
|
1212
1212
|
|
|
1213
1213
|
def build_scope(self, parent_scope: "Scope") -> None:
|
|
1214
1214
|
self.scope = parent_scope
|
|
@@ -1267,7 +1267,7 @@ class ListCompFor(VectorElement):
|
|
|
1267
1267
|
body: VectorElement
|
|
1268
1268
|
|
|
1269
1269
|
def __str__(self):
|
|
1270
|
-
return f"for ({self.assignments}) {self.body}"
|
|
1270
|
+
return f"for ({', '.join(str(a) for a in self.assignments)}) {self.body}"
|
|
1271
1271
|
|
|
1272
1272
|
def build_scope(self, parent_scope: "Scope") -> None:
|
|
1273
1273
|
self.scope = parent_scope
|
|
@@ -1308,7 +1308,7 @@ class ListCompCFor(VectorElement):
|
|
|
1308
1308
|
body: VectorElement
|
|
1309
1309
|
|
|
1310
1310
|
def __str__(self):
|
|
1311
|
-
return f"for ({self.initial}; {self.condition}; {self.increment}) {self.body}"
|
|
1311
|
+
return f"for ({', '.join(str(a) for a in self.initial)}; {self.condition}; {', '.join(str(a) for a in self.increment)}) {self.body}"
|
|
1312
1312
|
|
|
1313
1313
|
def build_scope(self, parent_scope: "Scope") -> None:
|
|
1314
1314
|
self.scope = parent_scope
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Pretty-printer: convert an OpenSCAD AST back to formatted source code."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from .nodes import (
|
|
4
|
+
ASTNode, Assignment, FunctionDeclaration, ModuleDeclaration,
|
|
5
|
+
UseStatement, IncludeStatement,
|
|
6
|
+
ModuleInstantiation,
|
|
7
|
+
ModularCall, ModularFor, ModularCFor,
|
|
8
|
+
ModularIntersectionFor, ModularIntersectionCFor,
|
|
9
|
+
ModularLet, ModularEcho, ModularAssert,
|
|
10
|
+
ModularIf, ModularIfElse,
|
|
11
|
+
ModularModifierShowOnly, ModularModifierHighlight,
|
|
12
|
+
ModularModifierBackground, ModularModifierDisable,
|
|
13
|
+
CommentLine, CommentSpan,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def to_openscad(nodes: list[ASTNode], indent_width: int = 4) -> str:
|
|
18
|
+
"""Convert a list of AST nodes to formatted OpenSCAD source code.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
nodes: The AST nodes to format (top-level statements).
|
|
22
|
+
indent_width: Number of spaces per indentation level (default: 4).
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Formatted OpenSCAD source code as a string.
|
|
26
|
+
"""
|
|
27
|
+
parts = []
|
|
28
|
+
prev_complex = False
|
|
29
|
+
for node in nodes:
|
|
30
|
+
is_complex = isinstance(node, (ModuleDeclaration, FunctionDeclaration))
|
|
31
|
+
if parts and (is_complex or prev_complex):
|
|
32
|
+
parts.append("")
|
|
33
|
+
parts.append(_fmt_node(node, 0, indent_width))
|
|
34
|
+
prev_complex = is_complex
|
|
35
|
+
return "\n".join(parts)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# --- helpers ---
|
|
39
|
+
|
|
40
|
+
def _as_list(val) -> list:
|
|
41
|
+
if isinstance(val, list):
|
|
42
|
+
return val
|
|
43
|
+
if val is None:
|
|
44
|
+
return []
|
|
45
|
+
return [val]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _join_str(items) -> str:
|
|
49
|
+
return ", ".join(str(i) for i in items)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _fmt_node(node: ASTNode, indent: int, w: int) -> str:
|
|
53
|
+
"""Format any top-level or block-body node."""
|
|
54
|
+
pad = " " * indent
|
|
55
|
+
|
|
56
|
+
if isinstance(node, CommentLine):
|
|
57
|
+
return f"{pad}//{node.text}"
|
|
58
|
+
if isinstance(node, CommentSpan):
|
|
59
|
+
return f"{pad}/*{node.text}*/"
|
|
60
|
+
if isinstance(node, UseStatement):
|
|
61
|
+
return f"{pad}use <{node.filepath.val}>"
|
|
62
|
+
if isinstance(node, IncludeStatement):
|
|
63
|
+
return f"{pad}include <{node.filepath.val}>"
|
|
64
|
+
if isinstance(node, Assignment):
|
|
65
|
+
return f"{pad}{node.name} = {node.expr};"
|
|
66
|
+
if isinstance(node, FunctionDeclaration):
|
|
67
|
+
params = _join_str(node.parameters)
|
|
68
|
+
return f"{pad}function {node.name}({params}) = {node.expr};"
|
|
69
|
+
if isinstance(node, ModuleDeclaration):
|
|
70
|
+
params = _join_str(node.parameters)
|
|
71
|
+
block = _fmt_block(node.children, indent, w)
|
|
72
|
+
return f"{pad}module {node.name}({params}) {block}"
|
|
73
|
+
if isinstance(node, ModuleInstantiation):
|
|
74
|
+
return _fmt_inst(node, indent, w)
|
|
75
|
+
return f"{pad}{node}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _fmt_block(nodes: list, indent: int, w: int) -> str:
|
|
79
|
+
"""Format a list of nodes as a braced block."""
|
|
80
|
+
pad = " " * indent
|
|
81
|
+
if not nodes:
|
|
82
|
+
return "{}"
|
|
83
|
+
inner = "\n".join(_fmt_node(n, indent + w, w) for n in nodes)
|
|
84
|
+
return "{\n" + inner + "\n" + pad + "}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _fmt_child(body, indent: int, w: int) -> str:
|
|
88
|
+
"""Format the child body of a module instantiation.
|
|
89
|
+
|
|
90
|
+
Returns the tail string appended after the header:
|
|
91
|
+
- ``";"`` when there are no children
|
|
92
|
+
- ``"\\n child;"`` for a single inline child
|
|
93
|
+
- ``" {\\n ...\\n}"`` for a block of multiple children
|
|
94
|
+
"""
|
|
95
|
+
nodes = _as_list(body)
|
|
96
|
+
pad = " " * indent
|
|
97
|
+
|
|
98
|
+
if not nodes:
|
|
99
|
+
return ";"
|
|
100
|
+
if len(nodes) == 1:
|
|
101
|
+
return "\n" + _fmt_inst(nodes[0], indent + w, w)
|
|
102
|
+
inner = "\n".join(_fmt_inst(n, indent + w, w) for n in nodes)
|
|
103
|
+
return " {\n" + inner + "\n" + pad + "}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _fmt_inst(node: ModuleInstantiation, indent: int, w: int, prefix: str = "") -> str:
|
|
107
|
+
"""Format a ModuleInstantiation node.
|
|
108
|
+
|
|
109
|
+
``prefix`` accumulates modifier characters (``!``, ``#``, ``%``, ``*``)
|
|
110
|
+
so nested modifiers produce e.g. ``!#cube(10);``.
|
|
111
|
+
"""
|
|
112
|
+
pad = " " * indent
|
|
113
|
+
|
|
114
|
+
# Modifiers: push prefix down to the wrapped node
|
|
115
|
+
if isinstance(node, ModularModifierShowOnly):
|
|
116
|
+
return _fmt_inst(node.child, indent, w, "!" + prefix)
|
|
117
|
+
if isinstance(node, ModularModifierHighlight):
|
|
118
|
+
return _fmt_inst(node.child, indent, w, "#" + prefix)
|
|
119
|
+
if isinstance(node, ModularModifierBackground):
|
|
120
|
+
return _fmt_inst(node.child, indent, w, "%" + prefix)
|
|
121
|
+
if isinstance(node, ModularModifierDisable):
|
|
122
|
+
return _fmt_inst(node.child, indent, w, "*" + prefix)
|
|
123
|
+
|
|
124
|
+
if isinstance(node, ModularCall):
|
|
125
|
+
args = _join_str(node.arguments)
|
|
126
|
+
return f"{pad}{prefix}{node.name}({args})" + _fmt_child(node.children, indent, w)
|
|
127
|
+
|
|
128
|
+
if isinstance(node, ModularFor):
|
|
129
|
+
assigns = _join_str(_as_list(node.assignments))
|
|
130
|
+
return f"{pad}{prefix}for ({assigns})" + _fmt_child(node.body, indent, w)
|
|
131
|
+
|
|
132
|
+
if isinstance(node, ModularCFor):
|
|
133
|
+
init = _join_str(_as_list(node.initial))
|
|
134
|
+
inc = _join_str(_as_list(node.increment))
|
|
135
|
+
return f"{pad}{prefix}for ({init}; {node.condition}; {inc})" + _fmt_child(node.body, indent, w)
|
|
136
|
+
|
|
137
|
+
if isinstance(node, ModularIntersectionFor):
|
|
138
|
+
assigns = _join_str(_as_list(node.assignments))
|
|
139
|
+
return f"{pad}{prefix}intersection_for ({assigns})" + _fmt_child(node.body, indent, w)
|
|
140
|
+
|
|
141
|
+
if isinstance(node, ModularIntersectionCFor):
|
|
142
|
+
init = _join_str(_as_list(node.initial))
|
|
143
|
+
inc = _join_str(_as_list(node.increment))
|
|
144
|
+
return f"{pad}{prefix}intersection_for ({init}; {node.condition}; {inc})" + _fmt_child(node.body, indent, w)
|
|
145
|
+
|
|
146
|
+
if isinstance(node, ModularLet):
|
|
147
|
+
assigns = _join_str(_as_list(node.assignments))
|
|
148
|
+
return f"{pad}{prefix}let ({assigns})" + _fmt_child(node.children, indent, w)
|
|
149
|
+
|
|
150
|
+
if isinstance(node, ModularEcho):
|
|
151
|
+
args = _join_str(node.arguments)
|
|
152
|
+
return f"{pad}{prefix}echo({args})" + _fmt_child(node.children, indent, w)
|
|
153
|
+
|
|
154
|
+
if isinstance(node, ModularAssert):
|
|
155
|
+
args = _join_str(node.arguments)
|
|
156
|
+
return f"{pad}{prefix}assert({args})" + _fmt_child(node.children, indent, w)
|
|
157
|
+
|
|
158
|
+
if isinstance(node, ModularIf):
|
|
159
|
+
header = f"{pad}{prefix}if ({node.condition})"
|
|
160
|
+
return header + _fmt_child(node.true_branch, indent, w)
|
|
161
|
+
|
|
162
|
+
if isinstance(node, ModularIfElse):
|
|
163
|
+
header = f"{pad}{prefix}if ({node.condition})"
|
|
164
|
+
true_tail = _fmt_child(node.true_branch, indent, w)
|
|
165
|
+
false_tail = _fmt_child(node.false_branch, indent, w)
|
|
166
|
+
connector = " else" if true_tail.startswith(" {") else f"\n{pad}else"
|
|
167
|
+
return header + true_tail + connector + false_tail
|
|
168
|
+
|
|
169
|
+
return f"{pad}{prefix}{node};"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Command-line interface for openscad_parser."""
|
|
2
|
+
import sys
|
|
3
|
+
import argparse
|
|
4
|
+
from openscad_parser.ast import getASTfromString, getASTfromFile, ast_to_json
|
|
5
|
+
from openscad_parser.ast.pretty_print import to_openscad
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
ap = argparse.ArgumentParser(
|
|
10
|
+
prog="openscad-parser",
|
|
11
|
+
description=(
|
|
12
|
+
"Parse an OpenSCAD file and dump its AST as JSON (default), "
|
|
13
|
+
"YAML, or reformatted OpenSCAD source."
|
|
14
|
+
),
|
|
15
|
+
)
|
|
16
|
+
ap.add_argument(
|
|
17
|
+
"file",
|
|
18
|
+
nargs="?",
|
|
19
|
+
metavar="FILE",
|
|
20
|
+
help="OpenSCAD source file to parse. Omit or use '-' to read from stdin.",
|
|
21
|
+
)
|
|
22
|
+
output = ap.add_mutually_exclusive_group()
|
|
23
|
+
output.add_argument(
|
|
24
|
+
"--json", action="store_true", default=True,
|
|
25
|
+
help="Output AST as JSON (default).",
|
|
26
|
+
)
|
|
27
|
+
output.add_argument(
|
|
28
|
+
"--yaml", action="store_true",
|
|
29
|
+
help="Output AST as YAML (requires PyYAML).",
|
|
30
|
+
)
|
|
31
|
+
output.add_argument(
|
|
32
|
+
"--format", action="store_true",
|
|
33
|
+
help="Output reformatted OpenSCAD source code.",
|
|
34
|
+
)
|
|
35
|
+
ap.add_argument(
|
|
36
|
+
"--include-comments", action="store_true",
|
|
37
|
+
help="Include comment nodes in the AST.",
|
|
38
|
+
)
|
|
39
|
+
ap.add_argument(
|
|
40
|
+
"--no-includes", action="store_true",
|
|
41
|
+
help="Do not expand include <...> statements (keeps IncludeStatement nodes).",
|
|
42
|
+
)
|
|
43
|
+
ap.add_argument(
|
|
44
|
+
"--indent", type=int, default=4, metavar="N",
|
|
45
|
+
help="Indentation width in spaces (default: 4). Applies to --format and --json.",
|
|
46
|
+
)
|
|
47
|
+
args = ap.parse_args()
|
|
48
|
+
|
|
49
|
+
# --yaml and --format clear the --json default
|
|
50
|
+
if args.yaml or args.format:
|
|
51
|
+
args.json = False
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
if args.file is None or args.file == "-":
|
|
55
|
+
code = sys.stdin.read()
|
|
56
|
+
ast = getASTfromString(code, include_comments=args.include_comments)
|
|
57
|
+
else:
|
|
58
|
+
ast = getASTfromFile(
|
|
59
|
+
args.file,
|
|
60
|
+
include_comments=args.include_comments,
|
|
61
|
+
process_includes=not args.no_includes,
|
|
62
|
+
)
|
|
63
|
+
except FileNotFoundError as e:
|
|
64
|
+
print(f"openscad-parser: {e}", file=sys.stderr)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
if ast is None:
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
if args.format:
|
|
71
|
+
print(to_openscad(ast, indent_width=args.indent))
|
|
72
|
+
elif args.yaml:
|
|
73
|
+
try:
|
|
74
|
+
from openscad_parser.ast import ast_to_yaml
|
|
75
|
+
except ImportError:
|
|
76
|
+
print("openscad-parser: --yaml requires PyYAML (pip install openscad_parser[yaml])", file=sys.stderr)
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
print(ast_to_yaml(ast))
|
|
79
|
+
else:
|
|
80
|
+
print(ast_to_json(ast, indent=args.indent))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|