openscad-cpp-evaluator 0.15.0__tar.gz → 0.17.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.
Files changed (196) hide show
  1. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/bindings/CMakeLists.txt +1 -0
  3. openscad_cpp_evaluator-0.17.0/bindings/ast_to_py.cpp +326 -0
  4. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/bindings/module.cpp +14 -0
  5. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/csg_node.hpp +16 -0
  6. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/eval_error.hpp +12 -0
  7. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/evaluator.hpp +18 -0
  8. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/pyproject.toml +1 -1
  9. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/python/openscad_cpp_evaluator/__init__.py +36 -1
  10. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/bytecode_vm.cpp +2 -0
  11. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/csg_generate.cpp +7 -0
  12. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/csg_resolve.cpp +3 -0
  13. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/eval_error.cpp +44 -0
  14. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/expr_eval.cpp +19 -1
  15. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_bytecode_compiler.cpp +11 -3
  16. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_csg_tree.cpp +70 -0
  17. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_python_bindings.py +116 -0
  18. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/.github/workflows/ci.yml +0 -0
  19. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/.github/workflows/wheels.yml +0 -0
  20. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/.gitignore +0 -0
  21. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/.gitmodules +0 -0
  22. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/CLAUDE.md +0 -0
  23. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/LICENSE +0 -0
  25. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/README.md +0 -0
  26. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/README.md +0 -0
  36. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  37. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  38. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  40. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  41. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  42. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  43. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  44. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  45. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  47. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  48. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  49. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  50. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  51. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  53. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  54. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  55. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  56. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  57. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  58. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  59. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  60. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  61. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  62. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  63. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  64. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  68. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  69. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  70. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  71. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  72. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  73. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  74. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  75. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  76. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  77. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  78. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  79. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  80. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  81. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  82. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  83. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  84. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  85. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  86. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  87. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  88. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  89. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  90. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  91. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  92. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  93. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  94. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  95. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  96. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  97. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  98. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  99. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  100. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  101. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  103. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  104. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  105. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  106. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  107. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  108. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  109. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  110. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  111. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  113. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  114. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  115. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  116. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  117. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  118. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  119. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  120. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  122. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  123. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  124. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  125. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/resources/fonts/LICENSE +0 -0
  126. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  127. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/CMakeLists.txt +0 -0
  128. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/booleans.cpp +0 -0
  129. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/builtins.hpp +0 -0
  130. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/call_args.cpp +0 -0
  131. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/color.cpp +0 -0
  132. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/control.cpp +0 -0
  133. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/extrude.cpp +0 -0
  134. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/function_builtins.cpp +0 -0
  135. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/import.cpp +0 -0
  136. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/modifiers.cpp +0 -0
  137. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/primitives_2d.cpp +0 -0
  138. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/primitives_3d.cpp +0 -0
  139. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/registry.cpp +0 -0
  140. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/roof.cpp +0 -0
  141. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/segments.cpp +0 -0
  142. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/surface.cpp +0 -0
  143. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/text.cpp +0 -0
  144. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/topology.cpp +0 -0
  145. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/builtins/transforms.cpp +0 -0
  146. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/bytecode_compiler.cpp +0 -0
  147. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/colored_body.cpp +0 -0
  148. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/css_colors.cpp +0 -0
  149. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/debug/debug_repl.cpp +0 -0
  150. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/debug_profile.cpp +0 -0
  151. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/eval_context.cpp +0 -0
  152. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/eval_use.cpp +0 -0
  153. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/export.cpp +0 -0
  154. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/import/dxf_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/import/mesh_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/import/surface_load.cpp +0 -0
  157. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/import/svg_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/manifold_cache.cpp +0 -0
  159. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/stmt_eval.cpp +0 -0
  160. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/text/stb_font_provider.cpp +0 -0
  161. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/text/text_metrics.cpp +0 -0
  162. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/user_calls.cpp +0 -0
  163. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/value.cpp +0 -0
  164. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/src/zip_stored.cpp +0 -0
  165. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/CMakeLists.txt +0 -0
  166. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_booleans.cpp +0 -0
  167. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_cli.cpp +0 -0
  168. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_control_flow.cpp +0 -0
  169. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_debug_hooks.cpp +0 -0
  170. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_dxf_svg_import.cpp +0 -0
  171. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_eval_context.cpp +0 -0
  172. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_export.cpp +0 -0
  173. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_expr_eval.cpp +0 -0
  174. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_extrude_roof.cpp +0 -0
  175. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_function_builtins.cpp +0 -0
  176. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_helpers.hpp +0 -0
  177. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_import_export.cpp +0 -0
  178. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_multi_color_merge.cpp +0 -0
  180. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_primitives.cpp +0 -0
  181. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_profiling.cpp +0 -0
  182. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_scope_trail.cpp +0 -0
  183. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_scoping.cpp +0 -0
  184. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_surface.cpp +0 -0
  185. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_tail_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_text.cpp +0 -0
  187. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_transforms.cpp +0 -0
  188. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_use.cpp +0 -0
  189. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_value.cpp +0 -0
  190. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_viewport_params.cpp +0 -0
  191. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tests/test_zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tools/cli/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tools/cli/cli_lib.cpp +0 -0
  194. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tools/cli/cli_lib.hpp +0 -0
  195. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/tools/cli/main.cpp +0 -0
  196. {openscad_cpp_evaluator-0.15.0 → openscad_cpp_evaluator-0.17.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.15.0
3
+ Version: 0.17.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -15,6 +15,7 @@ find_package(nanobind CONFIG REQUIRED)
15
15
  # from-source Manifold/Boost rebuild per interpreter in CI.
16
16
  nanobind_add_module(_openscad_cpp_evaluator NB_STATIC STABLE_ABI
17
17
  module.cpp
18
+ ast_to_py.cpp
18
19
  )
19
20
 
20
21
  # The static evaluator lib PUBLIC-links parser + manifold + Boost::polygon,
@@ -0,0 +1,326 @@
1
+ // Snapshots the parser's AST into plain Python dicts.
2
+ //
3
+ // Deliberately a COPY rather than wrappers around live ASTNode pointers.
4
+ // The tree is owned by a `std::vector<std::unique_ptr<ASTNode>>` local to
5
+ // whichever binding parsed it, and dies when that call returns; handing
6
+ // Python raw pointers into it would be a use-after-free the moment anything
7
+ // outlived the call, and this project has already shipped one dangling
8
+ // pointer of exactly that shape (the bodyCtx bug fixed in v0.13.1). It also
9
+ // sidesteps the threading problem entirely -- a render worker thread can
10
+ // parse, hand the snapshot to the GUI thread, and exit, with nothing shared.
11
+ //
12
+ // The cost is one walk plus allocation per parse. That is affordable
13
+ // because callers ask for this explicitly (parse_ast()), not on the render
14
+ // path, which never needs it.
15
+ //
16
+ // Every node dict carries at least:
17
+ // kind -- str, the NodeKind name ("NumberLiteral", "ModularCall", ...)
18
+ // position -- dict(origin, line, column, start_offset, end_offset)
19
+ // plus that kind's own fields, named in snake_case. Child nodes are nested
20
+ // dicts; child lists are lists of dicts; an absent optional child is None.
21
+ //
22
+ // `position.start_offset`/`end_offset` are the point of the whole exercise:
23
+ // they let a caller recover the ORIGINAL source text of any subexpression
24
+ // and reuse it verbatim, rather than re-serialising a parsed value and
25
+ // losing how the author wrote it (1.500 -> 1.5, 1e3 -> 1000).
26
+
27
+ #include "openscad_cpp_parser/api.hpp"
28
+ #include "openscad_cpp_parser/ast/ast_node.hpp"
29
+ #include "openscad_cpp_parser/ast/comments.hpp"
30
+ #include "openscad_cpp_parser/ast/declarations.hpp"
31
+ #include "openscad_cpp_parser/ast/expression.hpp"
32
+ #include "openscad_cpp_parser/ast/module_instantiation.hpp"
33
+ #include "openscad_cpp_parser/ast/vector_element.hpp"
34
+
35
+ #include <nanobind/nanobind.h>
36
+ #include <nanobind/stl/string.h>
37
+
38
+ #include <memory>
39
+ #include <vector>
40
+
41
+ namespace nb = nanobind;
42
+
43
+ namespace oscadbind {
44
+
45
+ namespace {
46
+
47
+ nb::dict positionToPy(const oscad::Position& p) {
48
+ nb::dict d;
49
+ // origin is "" for the main file (matching how the evaluator reports
50
+ // it); left as-is rather than mapped to None, so a caller can compare
51
+ // it against a path without a null check.
52
+ d["origin"] = p.origin;
53
+ d["line"] = p.line;
54
+ d["column"] = p.column;
55
+ d["start_offset"] = p.start_offset;
56
+ d["end_offset"] = p.end_offset;
57
+ return d;
58
+ }
59
+
60
+ nb::object nodeToPy(const oscad::ASTNode* n);
61
+
62
+ // A null unique_ptr child (e.g. a parameter with no default) becomes None
63
+ // rather than being omitted, so a consumer can index the key unconditionally.
64
+ template <typename T>
65
+ nb::object childToPy(const std::unique_ptr<T>& p) {
66
+ return p ? nodeToPy(p.get()) : nb::none();
67
+ }
68
+
69
+ template <typename T>
70
+ nb::list listToPy(const std::vector<std::unique_ptr<T>>& v) {
71
+ nb::list out;
72
+ for (const std::unique_ptr<T>& item : v) out.append(nodeToPy(item.get()));
73
+ return out;
74
+ }
75
+
76
+ nb::dict baseDict(const oscad::ASTNode* n) {
77
+ nb::dict d;
78
+ d["kind"] = oscad::nodeKindName(n->kind());
79
+ d["position"] = positionToPy(n->position());
80
+ return d;
81
+ }
82
+
83
+ nb::object nodeToPy(const oscad::ASTNode* n) {
84
+ if (n == nullptr) return nb::none();
85
+ nb::dict d = baseDict(n);
86
+
87
+ // kind() is authoritative about the concrete type (every leaf class sets
88
+ // its own), so static_cast is safe here and avoids a dynamic_cast per
89
+ // node on what is already an allocation-heavy walk.
90
+ #define OSC_NODE(KindName, Type) case oscad::NodeKind::KindName: { const auto* x = static_cast<const oscad::Type*>(n); (void)x;
91
+ #define OSC_END break; }
92
+
93
+ switch (n->kind()) {
94
+ // -- comments ---------------------------------------------------
95
+ OSC_NODE(CommentLine, CommentLine) d["text"] = x->text; OSC_END
96
+ OSC_NODE(BlankLine, BlankLine) OSC_END
97
+ OSC_NODE(CommentSpan, CommentSpan) d["text"] = x->text; OSC_END
98
+ OSC_NODE(CommentedExpr, CommentedExpr)
99
+ d["leading_comments"] = listToPy(x->leadingComments);
100
+ d["trailing_comments"] = listToPy(x->trailingComments);
101
+ d["expr"] = childToPy(x->expr);
102
+ OSC_END
103
+
104
+ // -- primaries / literals ---------------------------------------
105
+ OSC_NODE(Identifier, Identifier) d["name"] = x->name; OSC_END
106
+ // `val` is the DECODED string minus its quotes; escape sequences
107
+ // are kept raw (a source \n stays two characters). Use the span to
108
+ // recover exactly what was written.
109
+ OSC_NODE(StringLiteral, StringLiteral) d["val"] = x->val; OSC_END
110
+ // Parsed to double, so the source spelling (1.500, 1e3, 0x10) is
111
+ // NOT recoverable from here -- that is what the span is for.
112
+ OSC_NODE(NumberLiteral, NumberLiteral) d["val"] = x->val; OSC_END
113
+ OSC_NODE(BooleanLiteral, BooleanLiteral) d["val"] = x->val; OSC_END
114
+ OSC_NODE(UndefinedLiteral, UndefinedLiteral) OSC_END
115
+ OSC_NODE(RangeLiteral, RangeLiteral)
116
+ d["start"] = childToPy(x->start);
117
+ d["end"] = childToPy(x->end);
118
+ // Never null: the parser defaults an omitted step to 1.
119
+ d["step"] = childToPy(x->step);
120
+ OSC_END
121
+
122
+ // -- declarations / arguments -----------------------------------
123
+ OSC_NODE(ParameterDeclaration, ParameterDeclaration)
124
+ d["name"] = childToPy(x->name);
125
+ d["default_value"] = childToPy(x->defaultValue); // nullable
126
+ d["leading_comments"] = listToPy(x->leadingComments);
127
+ d["trailing_comments"] = listToPy(x->trailingComments);
128
+ OSC_END
129
+ OSC_NODE(PositionalArgument, PositionalArgument) d["expr"] = childToPy(x->expr); OSC_END
130
+ OSC_NODE(NamedArgument, NamedArgument)
131
+ d["name"] = childToPy(x->name);
132
+ d["expr"] = childToPy(x->expr);
133
+ OSC_END
134
+ OSC_NODE(Assignment, Assignment)
135
+ d["name"] = childToPy(x->name);
136
+ d["expr"] = childToPy(x->expr);
137
+ OSC_END
138
+
139
+ // -- prefix expression forms ------------------------------------
140
+ OSC_NODE(LetOp, LetOp)
141
+ d["assignments"] = listToPy(x->assignments);
142
+ d["body"] = childToPy(x->body);
143
+ OSC_END
144
+ OSC_NODE(EchoOp, EchoOp)
145
+ d["arguments"] = listToPy(x->arguments);
146
+ d["body"] = childToPy(x->body);
147
+ OSC_END
148
+ OSC_NODE(AssertOp, AssertOp)
149
+ d["arguments"] = listToPy(x->arguments);
150
+ d["body"] = childToPy(x->body);
151
+ OSC_END
152
+ OSC_NODE(FunctionLiteral, FunctionLiteral)
153
+ d["parameters"] = listToPy(x->parameters);
154
+ d["body"] = childToPy(x->body);
155
+ OSC_END
156
+
157
+ // -- unary operators --------------------------------------------
158
+ #define OSC_UNARY(KindName) OSC_NODE(KindName, KindName) d["expr"] = childToPy(x->expr); OSC_END
159
+ OSC_UNARY(UnaryMinusOp)
160
+ OSC_UNARY(LogicalNotOp)
161
+ OSC_UNARY(BitwiseNotOp)
162
+ #undef OSC_UNARY
163
+
164
+ // -- binary operators -------------------------------------------
165
+ #define OSC_BINARY(KindName) \
166
+ OSC_NODE(KindName, KindName) d["left"] = childToPy(x->left); d["right"] = childToPy(x->right); OSC_END
167
+ OSC_BINARY(AdditionOp)
168
+ OSC_BINARY(SubtractionOp)
169
+ OSC_BINARY(MultiplicationOp)
170
+ OSC_BINARY(DivisionOp)
171
+ OSC_BINARY(ModuloOp)
172
+ OSC_BINARY(ExponentOp)
173
+ OSC_BINARY(BitwiseAndOp)
174
+ OSC_BINARY(BitwiseOrOp)
175
+ OSC_BINARY(BitwiseShiftLeftOp)
176
+ OSC_BINARY(BitwiseShiftRightOp)
177
+ OSC_BINARY(LogicalAndOp)
178
+ OSC_BINARY(LogicalOrOp)
179
+ OSC_BINARY(EqualityOp)
180
+ OSC_BINARY(InequalityOp)
181
+ OSC_BINARY(GreaterThanOp)
182
+ OSC_BINARY(GreaterThanOrEqualOp)
183
+ OSC_BINARY(LessThanOp)
184
+ OSC_BINARY(LessThanOrEqualOp)
185
+ #undef OSC_BINARY
186
+
187
+ OSC_NODE(TernaryOp, TernaryOp)
188
+ d["condition"] = childToPy(x->condition);
189
+ d["true_expr"] = childToPy(x->trueExpr);
190
+ d["false_expr"] = childToPy(x->falseExpr);
191
+ OSC_END
192
+
193
+ // -- postfix ----------------------------------------------------
194
+ OSC_NODE(PrimaryCall, PrimaryCall)
195
+ d["left"] = childToPy(x->left);
196
+ d["arguments"] = listToPy(x->arguments);
197
+ OSC_END
198
+ OSC_NODE(PrimaryIndex, PrimaryIndex)
199
+ d["left"] = childToPy(x->left);
200
+ d["index"] = childToPy(x->index);
201
+ OSC_END
202
+ OSC_NODE(PrimaryMember, PrimaryMember)
203
+ d["left"] = childToPy(x->left);
204
+ d["member"] = childToPy(x->member);
205
+ OSC_END
206
+
207
+ // -- list comprehension clauses ---------------------------------
208
+ OSC_NODE(ListCompLet, ListCompLet)
209
+ d["assignments"] = listToPy(x->assignments);
210
+ d["body"] = childToPy(x->body);
211
+ OSC_END
212
+ OSC_NODE(ListCompEach, ListCompEach) d["body"] = childToPy(x->body); OSC_END
213
+ OSC_NODE(ListCompFor, ListCompFor)
214
+ d["assignments"] = listToPy(x->assignments);
215
+ d["body"] = childToPy(x->body);
216
+ OSC_END
217
+ OSC_NODE(ListCompCFor, ListCompCFor)
218
+ d["inits"] = listToPy(x->inits);
219
+ d["condition"] = childToPy(x->condition);
220
+ d["incrs"] = listToPy(x->incrs);
221
+ d["body"] = childToPy(x->body);
222
+ OSC_END
223
+ OSC_NODE(ListCompIf, ListCompIf)
224
+ d["condition"] = childToPy(x->condition);
225
+ d["true_expr"] = childToPy(x->trueExpr);
226
+ OSC_END
227
+ OSC_NODE(ListCompIfElse, ListCompIfElse)
228
+ d["condition"] = childToPy(x->condition);
229
+ d["true_expr"] = childToPy(x->trueExpr);
230
+ d["false_expr"] = childToPy(x->falseExpr);
231
+ OSC_END
232
+ // Also how a plain vector literal ([1,2,3]) is represented --
233
+ // elements mix comprehension clauses and bare expressions.
234
+ OSC_NODE(ListComprehension, ListComprehension) d["elements"] = listToPy(x->elements); OSC_END
235
+
236
+ // -- module instantiation ---------------------------------------
237
+ OSC_NODE(ModularCall, ModularCall)
238
+ d["name"] = childToPy(x->name);
239
+ d["arguments"] = listToPy(x->arguments);
240
+ d["children"] = listToPy(x->children);
241
+ OSC_END
242
+ OSC_NODE(ModularFor, ModularFor)
243
+ d["assignments"] = listToPy(x->assignments);
244
+ d["body"] = listToPy(x->body);
245
+ OSC_END
246
+ OSC_NODE(ModularIntersectionFor, ModularIntersectionFor)
247
+ d["assignments"] = listToPy(x->assignments);
248
+ d["body"] = listToPy(x->body);
249
+ OSC_END
250
+ OSC_NODE(ModularLet, ModularLet)
251
+ d["assignments"] = listToPy(x->assignments);
252
+ d["children"] = listToPy(x->children);
253
+ OSC_END
254
+ OSC_NODE(ModularEcho, ModularEcho)
255
+ d["arguments"] = listToPy(x->arguments);
256
+ d["children"] = listToPy(x->children);
257
+ OSC_END
258
+ OSC_NODE(ModularAssert, ModularAssert)
259
+ d["arguments"] = listToPy(x->arguments);
260
+ d["children"] = listToPy(x->children);
261
+ OSC_END
262
+ OSC_NODE(ModularIf, ModularIf)
263
+ d["condition"] = childToPy(x->condition);
264
+ d["true_branch"] = listToPy(x->trueBranch);
265
+ OSC_END
266
+ OSC_NODE(ModularIfElse, ModularIfElse)
267
+ d["condition"] = childToPy(x->condition);
268
+ d["true_branch"] = listToPy(x->trueBranch);
269
+ d["false_branch"] = listToPy(x->falseBranch);
270
+ OSC_END
271
+
272
+ // -- modifier wrappers (! # % *) --------------------------------
273
+ #define OSC_MODIFIER(KindName) OSC_NODE(KindName, KindName) d["child"] = childToPy(x->child); OSC_END
274
+ OSC_MODIFIER(ModularModifierShowOnly)
275
+ OSC_MODIFIER(ModularModifierHighlight)
276
+ OSC_MODIFIER(ModularModifierBackground)
277
+ OSC_MODIFIER(ModularModifierDisable)
278
+ #undef OSC_MODIFIER
279
+
280
+ // -- top-level declarations -------------------------------------
281
+ OSC_NODE(ModuleDeclaration, ModuleDeclaration)
282
+ d["name"] = childToPy(x->name);
283
+ d["parameters"] = listToPy(x->parameters);
284
+ d["children"] = listToPy(x->children);
285
+ d["pre_name_comments"] = listToPy(x->preNameComments);
286
+ d["post_name_comments"] = listToPy(x->postNameComments);
287
+ d["post_params_comments"] = listToPy(x->postParamsComments);
288
+ OSC_END
289
+ OSC_NODE(FunctionDeclaration, FunctionDeclaration)
290
+ d["name"] = childToPy(x->name);
291
+ d["parameters"] = listToPy(x->parameters);
292
+ d["expr"] = childToPy(x->expr);
293
+ d["pre_name_comments"] = listToPy(x->preNameComments);
294
+ d["post_name_comments"] = listToPy(x->postNameComments);
295
+ d["post_params_comments"] = listToPy(x->postParamsComments);
296
+ OSC_END
297
+ OSC_NODE(UseStatement, UseStatement) d["filepath"] = childToPy(x->filepath); OSC_END
298
+ OSC_NODE(IncludeStatement, IncludeStatement) d["filepath"] = childToPy(x->filepath); OSC_END
299
+ }
300
+
301
+ #undef OSC_NODE
302
+ #undef OSC_END
303
+
304
+ // No default: above -- the switch is exhaustive over NodeKind, so
305
+ // adding a node kind to the parser without handling it here is a
306
+ // compiler warning rather than a silently field-less dict.
307
+ return d;
308
+ }
309
+
310
+ } // namespace
311
+
312
+ nb::list astToPy(const std::vector<std::unique_ptr<oscad::ASTNode>>& ast) {
313
+ nb::list out;
314
+ for (const std::unique_ptr<oscad::ASTNode>& n : ast) out.append(nodeToPy(n.get()));
315
+ return out;
316
+ }
317
+
318
+ nb::list parseAstFromFile(const std::string& path, bool includeComments) {
319
+ return astToPy(oscad::getASTFromFile(path, includeComments));
320
+ }
321
+
322
+ nb::list parseAstFromString(const std::string& code, bool includeComments) {
323
+ return astToPy(oscad::getASTFromString(code, includeComments));
324
+ }
325
+
326
+ } // namespace oscadbind
@@ -40,6 +40,14 @@
40
40
 
41
41
  namespace nb = nanobind;
42
42
 
43
+ // Defined in ast_to_py.cpp -- snapshots the parser's AST into plain Python
44
+ // dicts. Declared here rather than in a header since these two entry points
45
+ // are the whole interface and only this file calls them.
46
+ namespace oscadbind {
47
+ nb::list parseAstFromFile(const std::string& path, bool includeComments);
48
+ nb::list parseAstFromString(const std::string& code, bool includeComments);
49
+ } // namespace oscadbind
50
+
43
51
  namespace {
44
52
 
45
53
  // Moves a std::vector's storage into a NumPy array that owns it via a
@@ -649,6 +657,12 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
649
657
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).");
650
658
  m.def("parse_decls", &parseDecls, nb::arg("path"),
651
659
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
660
+ m.def("parse_ast", &oscadbind::parseAstFromFile, nb::arg("path"), nb::arg("include_comments") = false,
661
+ "Parse a .scad file; return the AST as a list of plain nested dicts "
662
+ "(kind, position, plus that kind's own fields). A snapshot, not a view -- "
663
+ "safe to keep, share across threads, and outlive the parse.");
664
+ m.def("parse_ast_string", &oscadbind::parseAstFromString, nb::arg("code"), nb::arg("include_comments") = false,
665
+ "As parse_ast(), but parses a source string instead of a file.");
652
666
  m.def("debug_evaluate", &debugEvaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("debug_hook"),
653
667
  nb::arg("error_break"), nb::arg("echo_fn"), nb::arg("manifold_cache") = nullptr,
654
668
  nb::arg("return_hook") = nb::none(), nb::arg("fast_continue_signal") = nullptr,
@@ -34,6 +34,22 @@ struct CSGNode {
34
34
  CSGParams params; // resolve step's plain-data output
35
35
  bool uncacheable = false; // set by a later phase (ManifoldCache, Phase 8)
36
36
 
37
+ // The call site that entered this node's call chain from the top
38
+ // level, captured at RESOLVE time -- non-owning, AST-lifetime-bound
39
+ // like `node`. nullptr for a node resolved at top level.
40
+ //
41
+ // Needed because a GenerateFn runs long after resolve has unwound the
42
+ // call stack, so a warning raised there (an open polyhedron(), a
43
+ // non-manifold import()) has no stack left to walk and could only ever
44
+ // name the library's own line. One pointer per node, deliberately, so
45
+ // this stays affordable on a BOSL2-scale tree; the full frame list a
46
+ // TRACE would need is not worth the per-node cost.
47
+ //
48
+ // Not part of cacheKey() (an explicit allowlist of kind/isBuiltin/
49
+ // params/children), so two identical subtrees reached from different
50
+ // call sites still share a cache entry.
51
+ const oscad::Position* warnEntry = nullptr;
52
+
37
53
  // 1 + the deepest child's own treeDepth (1 for a leaf) -- set once,
38
54
  // at construction, by whichever csg_resolve.cpp site finalizes this
39
55
  // node's own `children` (buildTreeNode, evalModularCall's non-splice
@@ -117,4 +117,16 @@ std::string formatError(const std::string& msg, const oscad::Position* nodePosit
117
117
  const std::vector<CallStackFrame>& callStack,
118
118
  const std::string& innermostFrame = "");
119
119
 
120
+ // "WARNING: {msg}{locSuffix(nodePosition)}, from {entry file}, line {N}"
121
+ // followed by traceLines(). The "from" clause names the OUTERMOST frame's
122
+ // call site -- the user's own line that entered the call chain -- because
123
+ // a warning raised inside a library otherwise reports only the library's
124
+ // own line, which the reader can neither act on nor trace back.
125
+ //
126
+ // Omitted when the stack is empty (a top-level warning, the common case,
127
+ // which stays a single line) or when it would merely repeat the headline.
128
+ // Deliberately richer than real OpenSCAD, which prints neither.
129
+ std::string formatWarning(const std::string& msg, const oscad::Position* nodePosition,
130
+ const std::vector<CallStackFrame>& callStack);
131
+
120
132
  } // namespace oscadeval
@@ -234,6 +234,24 @@ public:
234
234
  // GenerateFn is a free function, not a method -- it needs to call this
235
235
  // on the Evaluator& it's handed (see dispatch.hpp's GenerateFn doc
236
236
  // comment for why that reference exists at all).
237
+ // The call site that entered the current call chain from the top
238
+ // level (callStack_ is outermost-first), or nullptr at top level.
239
+ // Captured into each CSGNode at resolve time so a warning raised
240
+ // during generate -- after the stack has unwound -- can still name the
241
+ // user's own line. See CSGNode::warnEntry.
242
+ const oscad::Position* currentWarnEntry() const {
243
+ return callStack_.empty() ? nullptr : callStack_.front().callPosition;
244
+ }
245
+
246
+ // Set by generateTreeImpl() to the CSGNode currently being generated,
247
+ // so warn() can name the user's own call site during a phase where
248
+ // callStack_ is necessarily empty. Public for the same reason
249
+ // tagGenerated() is: a GenerateFn is a free function holding only an
250
+ // Evaluator&. Read only by warn(); a plain scoped save/restore rather
251
+ // than anything reentrancy-aware, since generateTreeImpl recurses
252
+ // depth-first on one thread.
253
+ const oscad::Position* generateWarnEntry = nullptr;
254
+
237
255
  ColoredBody tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue);
238
256
 
239
257
  // tagGenerated()'s counterpart for a mesh Manifold refused to build --
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.15.0"
7
+ version = "0.17.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -18,7 +18,7 @@ from ._openscad_cpp_evaluator import FastContinueSignal, ManifoldCache
18
18
  __all__ = [
19
19
  "Evaluator", "ColoredBody", "EvalError", "ParseError", "OscObject", "parse", "to_renderable_bodies",
20
20
  "ManifoldCache", "CallSiteProfile", "ProfileResult", "format_csg_tree", "bodies_from_dicts",
21
- "FastContinueSignal",
21
+ "FastContinueSignal", "parse_ast", "parse_ast_string",
22
22
  ]
23
23
 
24
24
 
@@ -256,6 +256,41 @@ def parse(path: str) -> RootScope:
256
256
  return RootScope(decls)
257
257
 
258
258
 
259
+ def parse_ast(path: str, include_comments: bool = False) -> list:
260
+ """Parse a .scad file; return its AST as a list of plain nested dicts.
261
+
262
+ Each node is `{"kind": str, "position": {...}, **fields}`, where fields
263
+ are that kind's own (e.g. NumberLiteral has "val"; ModularCall has
264
+ "name"/"arguments"/"children"). Child nodes nest as dicts, child lists
265
+ as lists, and an absent optional child is None.
266
+
267
+ `position` carries `start_offset`/`end_offset`, which is the point of
268
+ this API: they let a caller slice the ORIGINAL source text of any
269
+ subexpression and reuse it verbatim, instead of re-serialising a parsed
270
+ value and losing how it was written (`1.500` -> `1.5`, `1e3` -> `1000`).
271
+
272
+ This is a SNAPSHOT, not a view of live parser memory: it is safe to
273
+ keep, mutate, share across threads, and outlive the parse. That costs
274
+ one walk per call, which is why it is a separate entry point rather
275
+ than something evaluate() always pays for.
276
+
277
+ Raises ParseError on a syntax error.
278
+ """
279
+ try:
280
+ return _ext.parse_ast(path, include_comments)
281
+ except Exception as e:
282
+ raise ParseError(str(e)) from e
283
+
284
+
285
+ def parse_ast_string(code: str, include_comments: bool = False) -> list:
286
+ """As `parse_ast`, but parses a source string. `position.origin` is
287
+ "<string>" for every node. Raises ParseError on a syntax error."""
288
+ try:
289
+ return _ext.parse_ast_string(code, include_comments)
290
+ except Exception as e:
291
+ raise ParseError(str(e)) from e
292
+
293
+
259
294
  class _MeshShim:
260
295
  """Duck-types a manifold3d mesh: the four arrays the renderer reads."""
261
296
  __slots__ = ("vert_properties", "tri_verts", "run_original_id", "run_index", "num_prop")
@@ -1168,6 +1168,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1168
1168
  treeNode->kind = site.tagName;
1169
1169
  treeNode->node = site.node;
1170
1170
  treeNode->isBuiltin = true;
1171
+ treeNode->warnEntry = ev.currentWarnEntry();
1171
1172
  treeNode->children = std::move(children);
1172
1173
  treeNode->params = std::move(pending.params);
1173
1174
  treeNode->uncacheable = uncacheable;
@@ -1244,6 +1245,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1244
1245
  treeNode->kind = site.op;
1245
1246
  treeNode->node = site.node;
1246
1247
  treeNode->isBuiltin = true;
1248
+ treeNode->warnEntry = ev.currentWarnEntry();
1247
1249
  treeNode->children = std::move(children);
1248
1250
  treeNode->params = std::move(params);
1249
1251
  treeNode->uncacheable = uncacheable;
@@ -59,7 +59,14 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
59
59
 
60
60
  auto it = dispatch.find(node.kind);
61
61
  if (node.isBuiltin && it != dispatch.end()) {
62
+ // Resolve captured which user-level call reached this node;
63
+ // republish it so a warning raised inside the GenerateFn
64
+ // can name that line. Restored after (rather than left set)
65
+ // so a sibling generated at top level doesn't inherit it.
66
+ const oscad::Position* savedWarnEntry = generateWarnEntry;
67
+ generateWarnEntry = node.warnEntry;
62
68
  node.bodies = it->second(*this, node.params, node.children, *node.node);
69
+ generateWarnEntry = savedWarnEntry;
63
70
  } else {
64
71
  // No registered GenerateFn for this kind (a builtin with
65
72
  // display-only semantics like render(), or a non-builtin
@@ -66,6 +66,7 @@ void Evaluator::buildTreeNode(const std::string& kind, const oscad::ASTNode& nod
66
66
  treeNode->kind = kind;
67
67
  treeNode->node = &node;
68
68
  treeNode->isBuiltin = true;
69
+ treeNode->warnEntry = currentWarnEntry();
69
70
  treeNode->children = std::move(children);
70
71
  treeNode->params = std::move(params);
71
72
  treeNode->uncacheable = uncacheable;
@@ -135,6 +136,7 @@ void Evaluator::evalModularCall(const oscad::ModularCall& node, EvalContext& ctx
135
136
  treeNode->kind = name;
136
137
  treeNode->node = &node;
137
138
  treeNode->isBuiltin = true;
139
+ treeNode->warnEntry = currentWarnEntry();
138
140
  treeNode->uncacheable = uncacheable;
139
141
  treeNode->children = std::move(children);
140
142
  treeNode->params = std::move(params);
@@ -159,6 +161,7 @@ void Evaluator::spliceModuleChildren(std::vector<std::unique_ptr<CSGNode>> child
159
161
  unionNode->kind = "union";
160
162
  unionNode->node = &callNode;
161
163
  unionNode->isBuiltin = false;
164
+ unionNode->warnEntry = currentWarnEntry();
162
165
  unionNode->uncacheable = std::any_of(children.begin(), children.end(), [](const auto& c) { return c->uncacheable; });
163
166
  unionNode->children = std::move(children);
164
167
  setTreeDepthOrThrow(*unionNode, callNode);
@@ -78,4 +78,48 @@ std::string formatError(const std::string& msg, const oscad::Position* nodePosit
78
78
  return full;
79
79
  }
80
80
 
81
+ std::string formatWarning(const std::string& msg, const oscad::Position* nodePosition,
82
+ const std::vector<CallStackFrame>& callStack) {
83
+ // The warning keeps its OWN location in the headline: that is where the
84
+ // problem actually is, and it's what you need in order to fix a genuine
85
+ // library bug. But on its own, a warning raised deep inside a library
86
+ // ("in file BOSL2/vnf.scad, line 1624") is close to useless -- the
87
+ // reader's next question is always "which line of MINE caused that?",
88
+ // and nothing in the message answered it.
89
+ //
90
+ // callStack is outermost-first, so front() is the call that entered
91
+ // this chain from the top level -- the user's own line, however deep
92
+ // the warning surfaced. Naming it inline makes the common case a
93
+ // one-line answer; the TRACE lines below carry the full chain for when
94
+ // an intermediate frame is the real culprit.
95
+ //
96
+ // Real OpenSCAD reports only the raising location and no trace at all
97
+ // (verified against 2022.08.22), so this is deliberately more than the
98
+ // reference gives: warnings there routinely point into library
99
+ // internals with no way back to the caller.
100
+ std::string full = "WARNING: " + msg + locSuffix(nodePosition);
101
+
102
+ if (!callStack.empty()) {
103
+ const oscad::Position* entry = callStack.front().callPosition;
104
+ // Skip the clause when it would only repeat the headline (a warning
105
+ // raised directly at the top-level call site), or when the entry
106
+ // frame recorded no call position.
107
+ const bool sameSpot = entry != nullptr && nodePosition != nullptr &&
108
+ entry->origin == nodePosition->origin && entry->line == nodePosition->line;
109
+ if (entry != nullptr && !sameSpot) {
110
+ full += ", from " + entry->origin + ", line " + std::to_string(entry->line);
111
+ }
112
+ }
113
+
114
+ // Same shape and truncation as an error's trace, including the
115
+ // 10-frames-each-end bound for pathologically deep chains. A warning
116
+ // raised outside any call has an empty stack and stays a single line --
117
+ // the overwhelmingly common case, which keeps ordinary warnings from
118
+ // sprouting a trace nobody needs.
119
+ for (const std::string& line : traceLines(nodePosition, callStack, "")) {
120
+ full += "\n" + line;
121
+ }
122
+ return full;
123
+ }
124
+
81
125
  } // namespace oscadeval
@@ -95,7 +95,25 @@ FontProvider& Evaluator::fontProvider() {
95
95
  }
96
96
 
97
97
  void Evaluator::warn(const std::string& message, const oscad::Position* position) {
98
- if (echoFn_) echoFn_("WARNING: " + message + locSuffix(position));
98
+ if (!echoFn_) return;
99
+ // During generate the call stack has already unwound, so fall back to
100
+ // the entry position resolve captured onto the CSGNode being generated
101
+ // (see CSGNode::warnEntry). Passed as a synthetic single-frame stack so
102
+ // formatWarning needs no second code path -- the frame carries no name,
103
+ // which traceLines renders as a bare "called by ''" line, so it is
104
+ // deliberately NOT included there; only the headline's "from" clause
105
+ // uses it.
106
+ if (callStack_.empty() && generateWarnEntry != nullptr) {
107
+ std::string out = formatWarning(message, position, {});
108
+ const bool sameSpot = position != nullptr && generateWarnEntry->origin == position->origin &&
109
+ generateWarnEntry->line == position->line;
110
+ if (!sameSpot) {
111
+ out += ", from " + generateWarnEntry->origin + ", line " + std::to_string(generateWarnEntry->line);
112
+ }
113
+ echoFn_(out);
114
+ return;
115
+ }
116
+ echoFn_(formatWarning(message, position, callStack_));
99
117
  }
100
118
 
101
119
  Value Evaluator::evalIdentifier(const std::string& name, const oscad::Position* position, EvalContext& ctx,
@@ -1623,9 +1623,17 @@ TEST(ModuleBodyCompiles, DollarArgPropagatesIntoCompiledHullWrapChildren) {
1623
1623
  // side effect to preserve).
1624
1624
  TEST(ModuleBodyCompiles, CompiledRoofUnknownMethodWarningStaysOrderedAfterChildrensOwnEcho) {
1625
1625
  ScopedVm vm(true);
1626
- EXPECT_EQ(runCapturingEcho("module m() { roof(method=\"bogus\") { echo(\"child\"); square(4, center=true); } }\n"
1627
- "m();"),
1628
- "ECHO: \"child\"\nWARNING: Unknown roof method 'bogus'. Using 'voronoi'.");
1626
+ const std::string out = runCapturingEcho(
1627
+ "module m() { roof(method=\"bogus\") { echo(\"child\"); square(4, center=true); } }\n"
1628
+ "m();");
1629
+ // Asserted as an ordering rather than one exact string: the warning also
1630
+ // carries call-site attribution (", from ... line N" plus TRACE lines,
1631
+ // see formatWarning), which is orthogonal to what this test pins.
1632
+ const size_t echoAt = out.find("ECHO: \"child\"");
1633
+ const size_t warnAt = out.find("WARNING: Unknown roof method 'bogus'. Using 'voronoi'.");
1634
+ ASSERT_NE(echoAt, std::string::npos) << out;
1635
+ ASSERT_NE(warnAt, std::string::npos) << out;
1636
+ EXPECT_LT(echoAt, warnAt) << out;
1629
1637
  }
1630
1638
 
1631
1639
  // -- intersection_for -- the TRUE last native-reentry gap, closed by -------