openscad-cpp-evaluator 0.37.0__tar.gz → 0.38.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 (206) hide show
  1. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/CLAUDE.md +81 -0
  2. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +4 -0
  4. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +27 -0
  5. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +1 -0
  6. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/expression.cpp +42 -0
  7. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +5 -0
  8. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +2 -0
  9. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/lexer.l +5 -0
  10. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/parser.y +30 -0
  11. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +6 -0
  12. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/pretty_print.cpp +11 -0
  13. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +12 -0
  14. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
  15. openscad_cpp_evaluator-0.38.0/external/openscad_cpp_parser/tests/test_render_expression.cpp +186 -0
  16. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bytecode.hpp +15 -0
  17. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +12 -0
  18. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/evaluator.hpp +61 -0
  19. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/pyproject.toml +1 -1
  20. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/CMakeLists.txt +1 -0
  21. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/builtins.hpp +18 -0
  22. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/primitives_2d.cpp +17 -0
  23. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/primitives_3d.cpp +19 -0
  24. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/bytecode_compiler.cpp +53 -2
  25. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/bytecode_vm.cpp +82 -3
  26. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/csg_generate.cpp +20 -7
  27. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/debug_profile.cpp +7 -1
  28. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/expr_eval.cpp +3 -0
  29. openscad_cpp_evaluator-0.38.0/src/measure_geometry.cpp +319 -0
  30. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/CMakeLists.txt +1 -0
  31. openscad_cpp_evaluator-0.38.0/tests/test_render_expr.cpp +386 -0
  32. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/.github/workflows/ci.yml +0 -0
  33. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/.github/workflows/wheels.yml +0 -0
  34. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/.gitignore +0 -0
  35. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/.gitmodules +0 -0
  36. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/CMakeLists.txt +0 -0
  37. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/LICENSE +0 -0
  38. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/README.md +0 -0
  39. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/bindings/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/bindings/ast_to_py.cpp +0 -0
  41. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/bindings/module.cpp +0 -0
  42. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/cmake/embed_font.cmake +0 -0
  43. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/examples/CMakeLists.txt +0 -0
  44. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/examples/eval_perf_benchmark.cpp +0 -0
  45. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/examples/manifold_cache_reuse.cpp +0 -0
  46. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/examples/minimal_debugger.cpp +0 -0
  47. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  48. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/.gitignore +0 -0
  49. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  50. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  51. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/README.md +0 -0
  52. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  53. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  54. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  55. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  56. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  57. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  58. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  59. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  60. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  61. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  62. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  63. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  64. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  65. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  66. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  67. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  68. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  69. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  70. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  71. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  72. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  73. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  74. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  75. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  76. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  77. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  78. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  79. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  80. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  81. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  82. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  83. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  84. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  85. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  86. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  87. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  88. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  89. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  90. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  91. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  92. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  93. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  94. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  95. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  96. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  97. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  98. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  99. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  100. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  101. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  102. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  103. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  104. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  105. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  106. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  107. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  108. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  109. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  115. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  116. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  119. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  120. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  121. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  122. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  123. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  124. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  125. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  126. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  127. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  129. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  134. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/booleans.cpp +0 -0
  135. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/call_args.cpp +0 -0
  136. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/color.cpp +0 -0
  137. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/control.cpp +0 -0
  138. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/dxf_dim.cpp +0 -0
  139. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/extrude.cpp +0 -0
  140. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/function_builtins.cpp +0 -0
  141. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/import.cpp +0 -0
  142. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/modifiers.cpp +0 -0
  143. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/transforms.cpp +0 -0
  150. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/colored_body.cpp +0 -0
  151. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/csg_resolve.cpp +0 -0
  152. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/css_colors.cpp +0 -0
  153. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/debug/debug_repl.cpp +0 -0
  154. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/eval_context.cpp +0 -0
  155. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/eval_error.cpp +0 -0
  156. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/eval_use.cpp +0 -0
  157. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/export.cpp +0 -0
  158. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/import/dxf_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/import/mesh_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/import/surface_load.cpp +0 -0
  161. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/import/svg_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/manifold_cache.cpp +0 -0
  163. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/mesh_check.cpp +0 -0
  164. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/stmt_eval.cpp +0 -0
  165. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/text/font_match.cpp +0 -0
  166. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/text/freetype_font_provider.cpp +0 -0
  167. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/text/text_metrics.cpp +0 -0
  168. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/user_calls.cpp +0 -0
  169. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/value.cpp +0 -0
  170. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/src/zip_stored.cpp +0 -0
  171. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_booleans.cpp +0 -0
  172. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_bytecode_compiler.cpp +0 -0
  173. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_cli.cpp +0 -0
  174. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_control_flow.cpp +0 -0
  175. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_csg_tree.cpp +0 -0
  176. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_debug_hooks.cpp +0 -0
  177. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_dxf_svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_eval_context.cpp +0 -0
  179. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_expr_eval.cpp +0 -0
  181. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_extrude_roof.cpp +0 -0
  182. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_function_builtins.cpp +0 -0
  183. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_helpers.hpp +0 -0
  184. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_import_export.cpp +0 -0
  185. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_manifold_cache.cpp +0 -0
  186. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_mesh_check.cpp +0 -0
  187. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_multi_color_merge.cpp +0 -0
  188. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_primitives.cpp +0 -0
  189. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_profiling.cpp +0 -0
  190. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_python_bindings.py +0 -0
  191. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_scope_trail.cpp +0 -0
  192. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_scoping.cpp +0 -0
  193. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_surface.cpp +0 -0
  194. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_tail_calls.cpp +0 -0
  195. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_text.cpp +0 -0
  196. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_transforms.cpp +0 -0
  197. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_unexpected_args.cpp +0 -0
  198. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_use.cpp +0 -0
  199. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_value.cpp +0 -0
  200. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_viewport_params.cpp +0 -0
  201. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tests/test_zip_stored.cpp +0 -0
  202. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/CMakeLists.txt +0 -0
  203. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/cli_lib.cpp +0 -0
  204. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/cli_lib.hpp +0 -0
  205. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/main.cpp +0 -0
  206. {openscad_cpp_evaluator-0.37.0 → openscad_cpp_evaluator-0.38.0}/uv.lock +0 -0
@@ -1094,3 +1094,84 @@ than the current pause point). Ported identically (same qualifier syntax, same e
1094
1094
  profiling/debugging's own bookkeeping resets happen at the right point too.
1095
1095
  - `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
1096
1096
  real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
1097
+
1098
+ ## `render()` in expression position
1099
+
1100
+ `render()` has two jobs, decided by where it is written:
1101
+
1102
+ ```openscad
1103
+ render() cube(1); // STATEMENT -- draws, as always
1104
+ obj = render() { cube(1); }; // EXPRESSION -- measures, draws NOTHING
1105
+ ```
1106
+
1107
+ The expression form builds its children's geometry, measures it, extracts the
1108
+ mesh, and **discards the geometry**. It is the only way OpenSCAD can inspect its
1109
+ own geometry — volume, surface area, genus, bounding box, and the mesh itself.
1110
+
1111
+ ```openscad
1112
+ obj = render() difference() { cube(100); sphere(20); };
1113
+ echo(obj.volume, obj.genus, obj.boundingbox);
1114
+ polyhedron(obj); // straight back in
1115
+ ```
1116
+
1117
+ Keys — ordered, and the order is part of the API (`ValueObject` is an ordered
1118
+ vector and `oscEqual` is order-sensitive):
1119
+
1120
+ | dim | keys |
1121
+ |---|---|
1122
+ | 3 | `vertices`, `faces`, `volume`, `area`, `genus`, `boundingbox`, `dim`, `vnf` |
1123
+ | 2 | `vertices`, `paths`, `area`, `perimeter`, `boundingbox`, `dim` |
1124
+ | 0 | the 3D key set, all zero, `boundingbox = undef` |
1125
+
1126
+ `vertices`/`faces` are VNF-shaped — `[x,y,z]` points, 0-based N-gon index lists,
1127
+ **clockwise seen from outside** — so they feed `polyhedron()` and BOSL2 unchanged.
1128
+ `vnf` is the `[vertices, faces]` 2-list BOSL2 functions actually take.
1129
+ `polyhedron()` and `polygon()` also accept the object directly, and will accept
1130
+ *any* object carrying the right keys, not just one `render()` produced.
1131
+
1132
+ ### Things that will bite
1133
+
1134
+ - **`render` is a reserved keyword.** It cannot be a variable, module, function,
1135
+ argument, or member name. Required: LALR(1) cannot otherwise tell `render(` in
1136
+ expression position from a function call, and bison's shift-over-reduce would
1137
+ silently turn every function call in an expression into a module instantiation.
1138
+ - **`obj = render() cube(1);` does not parse.** A bare call's `child_statement`
1139
+ swallows the `;`, leaving the assignment unterminated. Use
1140
+ `render() { cube(1); }` or a form ending in `}`. Inherent to OpenSCAD's grammar.
1141
+ - **`genus` is Manifold's, for the whole result.** A cube with a sealed internal
1142
+ cavity reports `-1`, not `0` — its boundary has two components. Correct, but
1143
+ surprising.
1144
+ - **Nothing is drawn from any context** — top level, module body, function body,
1145
+ list comprehension, ternary. That is what preserves function purity, and why no
1146
+ context needs a special case.
1147
+
1148
+ ### Implementation notes
1149
+
1150
+ `Evaluator::measureCsgSubtree` (`src/measure_geometry.cpp`) is the *only*
1151
+ implementation of "CSG subtree → `object()`"; both engines call it — the
1152
+ interpreter from `evalRenderExpr`, the VM from `Op::PopBuiltinWrap`'s
1153
+ `Kind::Measure` branch.
1154
+
1155
+ `Evaluator::measuring_` is set for the whole generate. It suppresses the four
1156
+ writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor` in
1157
+ `tagGenerated` and `tagDisplayOnly`, the `restampCachedIds` call on a cache hit,
1158
+ and `cacheProducer_` — because those tables are cleared once per pass, so a leak
1159
+ is permanent and surfaces later as wrong click-to-source. It also suppresses
1160
+ `checkDebug`, which would otherwise inject stops at the paused statement's own
1161
+ `callStack_` depth. The **geometry cache stays on**: `cacheKey` is
1162
+ content-addressed, so an entry a measurement stores is genuinely reusable by the
1163
+ real render.
1164
+
1165
+ Two details in the mesh conversion are load-bearing and silent when wrong: the
1166
+ winding is **reversed** on the way out (Manifold's `triVerts` is CCW), and
1167
+ vertices are welded by exact position (Manifold splits property-vertices, so
1168
+ without the weld the round-tripped `polyhedron()` is an *open* mesh). A reversed
1169
+ mesh still builds — `Status() == NoError` — but comes back with **negative
1170
+ volume**, so tests assert a positive volume and must never use `abs()`.
1171
+
1172
+ On the VM side, `Kind::Measure` captures `(name, slot)` for every visible local
1173
+ at compile time and republishes them into the children's `EvalContext` at Push.
1174
+ A compiled function keeps its parameters in frame slots, which no `EvalContext`
1175
+ can see, and the children are statement opcodes that resolve names through the
1176
+ context — without this, `function f(w) = render() { cube(w); }.volume;` finds no
1177
+ `w` and silently measures nothing.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.37.0
3
+ Version: 0.38.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -61,6 +61,10 @@ enum class NodeKind {
61
61
  PrimaryCall,
62
62
  PrimaryIndex,
63
63
  PrimaryMember,
64
+ // render() in EXPRESSION position -- evaluates its children as geometry
65
+ // and yields an object() of measurements. The STATEMENT form of render()
66
+ // is a plain ModularCall named "render"; only this one is new.
67
+ RenderExpression,
64
68
  // List comprehension
65
69
  ListCompLet,
66
70
  ListCompEach,
@@ -237,6 +237,33 @@ public:
237
237
  void buildScope(Scope& parentScope) override;
238
238
  };
239
239
 
240
+ // `render()` in EXPRESSION position: `obj = render() { cube(10); };`
241
+ //
242
+ // Evaluates its children as geometry, measures the result, and yields an
243
+ // object() -- it draws nothing. The STATEMENT form (`render() cube(1);`)
244
+ // stays a plain ModularCall named "render"; this class exists only because
245
+ // an Expression cannot be a ModuleInstantiation (they are siblings under
246
+ // ASTNode, not parent/child).
247
+ //
248
+ // `arguments` and `children` deliberately mirror ModularCall's field types
249
+ // so the evaluator can hand them straight to resolveCallArgs()/evalChildren()
250
+ // with no adaptation. `children` is ASTNode, not ModuleInstantiation, for
251
+ // the same reason ModularCall's is -- a `{ ... }` block is `statement*` and
252
+ // may hold Assignments, which is why buildScope() below hoists.
253
+ class RenderExpression : public Expression {
254
+ public:
255
+ RenderExpression(Position position, std::vector<std::unique_ptr<Argument>> arguments,
256
+ std::vector<std::unique_ptr<ASTNode>> children)
257
+ : Expression(NodeKind::RenderExpression, std::move(position)), arguments(std::move(arguments)),
258
+ children(std::move(children)) {}
259
+
260
+ std::vector<std::unique_ptr<Argument>> arguments;
261
+ std::vector<std::unique_ptr<ASTNode>> children;
262
+
263
+ std::string toString() const override;
264
+ void buildScope(Scope& parentScope) override;
265
+ };
266
+
240
267
  // -- Unary operators --------------------------------------------------
241
268
 
242
269
  #define OSCAD_UNARY_OP(ClassName) \
@@ -47,6 +47,7 @@ const char* nodeKindName(NodeKind kind) {
47
47
  case NodeKind::PrimaryCall: return "PrimaryCall";
48
48
  case NodeKind::PrimaryIndex: return "PrimaryIndex";
49
49
  case NodeKind::PrimaryMember: return "PrimaryMember";
50
+ case NodeKind::RenderExpression: return "RenderExpression";
50
51
  case NodeKind::ListCompLet: return "ListCompLet";
51
52
  case NodeKind::ListCompEach: return "ListCompEach";
52
53
  case NodeKind::ListCompFor: return "ListCompFor";
@@ -1,6 +1,7 @@
1
1
  #include "openscad_cpp_parser/ast/expression.hpp"
2
2
 
3
3
  #include "format_utils.hpp"
4
+ #include "openscad_cpp_parser/ast/scope_builder.hpp"
4
5
  #include "openscad_cpp_parser/scope.hpp"
5
6
 
6
7
  #include <array>
@@ -155,6 +156,47 @@ void FunctionLiteral::buildScope(Scope& parentScope) {
155
156
  body->buildScope(funcScope);
156
157
  }
157
158
 
159
+ std::string RenderExpression::toString() const {
160
+ // Deliberately NOT ModuleInstantiation's formatChildBlock, which omits
161
+ // both braces (for a lone child) and every statement terminator, because
162
+ // for a STATEMENT the terminators come from the statement-level printer.
163
+ // A RenderExpression sits inside an expression, where nothing downstream
164
+ // adds them -- and `render() cube(1)` unbraced is precisely the form that
165
+ // does NOT parse (the child_statement swallows the `;`, leaving the
166
+ // enclosing assignment unterminated). So: always braces, always a `;`
167
+ // after every child.
168
+ //
169
+ // This must stay REPARSEABLE -- pretty_print.cpp's fmtExpr falls through
170
+ // to toString() for expression kinds it has no case for, so this string
171
+ // is what a formatter emits. NodeStr.RenderExpressionRoundTrips guards it.
172
+ //
173
+ // The unconditional `;` is safe even after a child that already ends in
174
+ // `}` (a nested block, a module declaration): a bare `;` is itself a legal
175
+ // statement (parser.y's `statement: ";"`), so a redundant one is a no-op.
176
+ std::string s = "render(" + joinToString(arguments, ", ") + ") { ";
177
+ for (const auto& c : children) {
178
+ s += c->toString() + "; ";
179
+ }
180
+ return s + "}";
181
+ }
182
+
183
+ void RenderExpression::buildScope(Scope& parentScope) {
184
+ // Same shape as ModularCall::buildScope minus the name lookup (there is
185
+ // no Identifier -- "render" is a keyword token here). The hoist is
186
+ // required: `render() { x = 1; cube(x); }` must resolve x.
187
+ setScope(parentScope);
188
+ for (auto& a : arguments) {
189
+ a->buildScope(parentScope);
190
+ }
191
+ if (!children.empty()) {
192
+ Scope& childrenScope = parentScope.childScope();
193
+ collectHoistedDeclarations(children, childrenScope);
194
+ for (auto& c : children) {
195
+ c->buildScope(childrenScope);
196
+ }
197
+ }
198
+ }
199
+
158
200
  // -- Operator precedence for minimal-parenthesization toString() ----------
159
201
  //
160
202
  // Matches the reference's nodes.py::_PREC/_lp/_rp: toString() adds parens
@@ -153,6 +153,11 @@ NodePtr makeListComprehension(ParserDriver& driver, const OscadLocation& loc, No
153
153
  return std::make_unique<ListComprehension>(driver.toPosition(loc), std::move(elements));
154
154
  }
155
155
 
156
+ NodePtr makeRenderExpression(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodeList children) {
157
+ return std::make_unique<RenderExpression>(driver.toPosition(loc), nodeListCast<Argument>(std::move(arguments)),
158
+ std::move(children));
159
+ }
160
+
156
161
  NodePtr makeModularCall(ParserDriver& driver, const OscadLocation& loc, const OscadLocation& nameLoc, std::string name,
157
162
  NodeList arguments, NodeList children) {
158
163
  auto nameNode = makeIdentifier(driver, nameLoc, std::move(name));
@@ -132,6 +132,8 @@ NodePtr makeListCompIfElse(ParserDriver& driver, const OscadLocation& loc, NodeP
132
132
  NodePtr falseExpr);
133
133
  NodePtr makeListComprehension(ParserDriver& driver, const OscadLocation& loc, NodeList elements);
134
134
 
135
+ NodePtr makeRenderExpression(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodeList children);
136
+
135
137
  NodePtr makeModularCall(ParserDriver& driver, const OscadLocation& loc, const OscadLocation& nameLoc, std::string name,
136
138
  NodeList arguments, NodeList children);
137
139
  NodePtr makeModularFor(ParserDriver& driver, const OscadLocation& loc, NodeList assignments, NodeList body);
@@ -47,6 +47,11 @@ const std::unordered_map<std::string, KeywordFactory>& keywordTable() {
47
47
  {"for", [](const OscadLocation& l) { return yy::parser::make_KW_FOR(l); }},
48
48
  {"intersection_for", [](const OscadLocation& l) { return yy::parser::make_KW_INTERSECTION_FOR(l); }},
49
49
  {"each", [](const OscadLocation& l) { return yy::parser::make_KW_EACH(l); }},
50
+ // Reserved so `render` can lead an EXPRESSION-position geometry block
51
+ // (`obj = render() { cube(1); };`). LALR(1) cannot otherwise tell that
52
+ // apart from a function call. Note `$render` still lexes as NAME --
53
+ // this table is keyed on the full IDENT text, including the `$`.
54
+ {"render", [](const OscadLocation& l) { return yy::parser::make_KW_RENDER(l); }},
50
55
  {"undef", [](const OscadLocation& l) { return yy::parser::make_KW_UNDEF(l); }},
51
56
  {"true", [](const OscadLocation& l) { return yy::parser::make_KW_TRUE(l); }},
52
57
  {"false", [](const OscadLocation& l) { return yy::parser::make_KW_FALSE(l); }},
@@ -92,6 +92,7 @@
92
92
  KW_FOR "for"
93
93
  KW_INTERSECTION_FOR "intersection_for"
94
94
  KW_EACH "each"
95
+ KW_RENDER "render"
95
96
  KW_UNDEF "undef"
96
97
  KW_TRUE "true"
97
98
  KW_FALSE "false"
@@ -157,6 +158,7 @@
157
158
  %type <NodePtr> modifier_show_only modifier_highlight modifier_background modifier_disable
158
159
  %type <NodePtr> if_statement ifelse_statement
159
160
  %type <NodePtr> modular_for modular_intersection_for modular_let modular_assert modular_echo modular_call
161
+ %type <NodePtr> render_stmt render_expr
160
162
  %type <NodePtr> expr opchain postfix primary
161
163
  %type <NodePtr> range_expr vector_expr vector_element
162
164
  %type <NodePtr> listcomp_elements listcomp_paren_expr listcomp_let listcomp_each
@@ -305,6 +307,7 @@ single_module_instantiation:
305
307
  | modular_assert { $$ = std::move($1); }
306
308
  | modular_echo { $$ = std::move($1); }
307
309
  | modular_call { $$ = std::move($1); }
310
+ | render_stmt { $$ = std::move($1); }
308
311
  ;
309
312
 
310
313
  modular_for:
@@ -343,6 +346,16 @@ modular_call:
343
346
  }
344
347
  ;
345
348
 
349
+ // `render` is a reserved keyword (see lexer.l) purely so the EXPRESSION form
350
+ // below is unambiguous. The STATEMENT form still builds a plain ModularCall
351
+ // named "render", so everything downstream -- builtin dispatch, the argument
352
+ // allowlist, the pretty-printer, json_io -- is unchanged.
353
+ render_stmt:
354
+ "render" "(" arguments ")" child_statement {
355
+ $$ = makeModularCall(driver, @$, @1, "render", std::move($3), std::move($5));
356
+ }
357
+ ;
358
+
346
359
  // -- Expressions ----------------------------------------------------------
347
360
  //
348
361
  // `expr` covers let/assert/echo/funclit_def/ternary plus the operator
@@ -406,6 +419,23 @@ primary:
406
419
  | STRING { $$ = makeStringLiteral(driver, @$, std::move($1)); }
407
420
  | NUMBER { $$ = makeNumberLiteral(driver, @$, $1); }
408
421
  | NAME { $$ = makeIdentifier(driver, @$, std::move($1)); }
422
+ | render_expr { $$ = std::move($1); }
423
+ ;
424
+
425
+ // Same RHS as render_stmt, reached only from `primary`. This is NOT a
426
+ // reduce/reduce conflict: LALR merges states only on identical LR(0) cores,
427
+ // and after shifting "render" the statement and expression kernels differ --
428
+ // no single state closes over both (statement-start closes over
429
+ // module_instantiation; every expr-start position closes over no statement
430
+ // nonterminal). Bison verifies this claim at build time via %expect.
431
+ //
432
+ // It lives in `primary` rather than `expr` so `render(){...}.volume` parses
433
+ // through the existing `postfix "." NAME` rule, and so it can appear as an
434
+ // argument, a list element, or an operand.
435
+ render_expr:
436
+ "render" "(" arguments ")" child_statement {
437
+ $$ = makeRenderExpression(driver, @$, std::move($3), std::move($5));
438
+ }
409
439
  ;
410
440
 
411
441
  range_expr:
@@ -295,6 +295,12 @@ void classifyNode(ASTNode& node, std::vector<ExprSlot>& exprFields, std::vector<
295
295
  addAstNodeList(static_cast<ListComprehension&>(node).elements, exprFields, nonExprChildren);
296
296
  break;
297
297
 
298
+ case NodeKind::RenderExpression: {
299
+ auto& n = static_cast<RenderExpression&>(node);
300
+ addArgumentExprList(n.arguments, exprFields);
301
+ addAstNodeList(n.children, exprFields, nonExprChildren);
302
+ break;
303
+ }
298
304
  case NodeKind::ModularCall: {
299
305
  auto& n = static_cast<ModularCall&>(node);
300
306
  addArgumentExprList(n.arguments, exprFields);
@@ -207,6 +207,7 @@ std::string fmtNode(const ASTNode& node, int indent, int w);
207
207
  std::string fmtExpr(const ASTNode& expr, int indent, int w);
208
208
  std::string fmtInst(const ASTNode& node, int indent, int w, const std::string& prefix);
209
209
  std::string fmtListElem(const ASTNode& elem, int indent, int w);
210
+ std::string fmtBlock(const std::vector<std::unique_ptr<ASTNode>>& nodes, int indent, int w);
210
211
 
211
212
  std::string fmtAssign(const Assignment& a, int indent, int w) {
212
213
  return a.name->name + " = " + fmtExpr(*a.expr, indent, w);
@@ -508,6 +509,16 @@ std::string fmtExpr(const ASTNode& exprNode, int indent, int w) {
508
509
  if (auto* lc = dynamic_cast<const ListComprehension*>(&exprNode)) {
509
510
  return fmtListComprehension(*lc, indent, w);
510
511
  }
512
+ if (auto* r = dynamic_cast<const RenderExpression*>(&exprNode)) {
513
+ // fmtBlock, NOT fmtChild: fmtChild drops the braces for a lone child,
514
+ // and `x = render() cube(1);` does not parse -- the child_statement
515
+ // swallows the `;` and leaves the assignment unterminated. fmtBlock
516
+ // always braces and routes children through fmtNode, which is what
517
+ // supplies their statement terminators. This arm is why toString()'s
518
+ // own (reference-matching, terminator-free) child rendering never
519
+ // reaches emitted source.
520
+ return "render(" + joinToString(r->arguments, ", ") + ") " + fmtBlock(r->children, indent, w);
521
+ }
511
522
  return exprNode.toString();
512
523
  }
513
524
 
@@ -267,6 +267,13 @@ json toJsonImpl(const ASTNode& node, bool includePos) {
267
267
  j["elements"] = listToJson(static_cast<const ListComprehension&>(node).elements, includePos);
268
268
  break;
269
269
 
270
+ case NodeKind::RenderExpression: {
271
+ auto& n = static_cast<const RenderExpression&>(node);
272
+ j["arguments"] = listToJson(n.arguments, includePos);
273
+ j["children"] = listToJson(n.children, includePos);
274
+ break;
275
+ }
276
+
270
277
  case NodeKind::ModularCall: {
271
278
  auto& n = static_cast<const ModularCall&>(node);
272
279
  j["name"] = valueToJson(n.name.get(), includePos);
@@ -543,6 +550,11 @@ const std::unordered_map<std::string, Builder>& registry() {
543
550
  return std::make_unique<ListComprehension>(std::move(pos), listFromJson<ASTNode>(j, "elements"));
544
551
  }},
545
552
 
553
+ {"RenderExpression",
554
+ [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
555
+ return std::make_unique<RenderExpression>(std::move(pos), listFromJson<Argument>(j, "arguments"),
556
+ listFromJson<ASTNode>(j, "children"));
557
+ }},
546
558
  {"ModularCall",
547
559
  [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
548
560
  return std::make_unique<ModularCall>(std::move(pos), childFromJson<Identifier>(j, "name"),
@@ -25,6 +25,7 @@ add_executable(oscad_tests
25
25
  test_scope.cpp
26
26
  test_ast_generation.cpp
27
27
  test_node_str.cpp
28
+ test_render_expression.cpp
28
29
  )
29
30
  target_link_libraries(oscad_tests PRIVATE openscad_cpp_parser GTest::gtest_main)
30
31
 
@@ -0,0 +1,186 @@
1
+ // `render()` in EXPRESSION position: `obj = render() { cube(1); };`
2
+ //
3
+ // The statement form (`render() cube(1);`) is deliberately UNCHANGED -- it
4
+ // still parses to a plain ModularCall named "render", so everything
5
+ // downstream (builtin dispatch, the argument allowlist, json_io) sees what
6
+ // it always saw. Only the expression form is new, and it exists at all
7
+ // because LALR(1) cannot otherwise tell `render(` in expression position
8
+ // apart from a function call -- hence `render` being a reserved keyword.
9
+
10
+ #include "openscad_cpp_parser/api.hpp"
11
+ #include "openscad_cpp_parser/pretty_print.hpp"
12
+ #include "openscad_cpp_parser/serialization.hpp"
13
+ #include "test_helpers.hpp"
14
+
15
+ #include <gtest/gtest.h>
16
+
17
+ using namespace oscad;
18
+
19
+ namespace {
20
+
21
+ const RenderExpression* renderExprOf(const std::vector<std::unique_ptr<ASTNode>>& ast, size_t idx = 0) {
22
+ auto* a = dynamic_cast<Assignment*>(ast[idx].get());
23
+ return a ? dynamic_cast<const RenderExpression*>(a->expr.get()) : nullptr;
24
+ }
25
+
26
+ } // namespace
27
+
28
+ // -- The expression form --------------------------------------------------
29
+
30
+ TEST(RenderExpression, ParsesInAssignment) {
31
+ auto ast = parseSrc("obj = render() { cube(1); };");
32
+ ASSERT_EQ(ast.size(), 1u);
33
+ const RenderExpression* r = renderExprOf(ast);
34
+ ASSERT_NE(r, nullptr);
35
+ EXPECT_EQ(r->kind(), NodeKind::RenderExpression);
36
+ EXPECT_TRUE(r->arguments.empty());
37
+ ASSERT_EQ(r->children.size(), 1u);
38
+ EXPECT_EQ(r->children[0]->kind(), NodeKind::ModularCall);
39
+ }
40
+
41
+ TEST(RenderExpression, ParsesTheOriginalMotivatingExample) {
42
+ // A trailing `}` child needs no `;` of its own, so this form -- unlike a
43
+ // bare call -- terminates its enclosing assignment cleanly.
44
+ auto ast = parseSrc("obj = render() difference() { cube(100); sphere(20); };");
45
+ ASSERT_EQ(ast.size(), 1u);
46
+ const RenderExpression* r = renderExprOf(ast);
47
+ ASSERT_NE(r, nullptr);
48
+ ASSERT_EQ(r->children.size(), 1u);
49
+ auto* diff = dynamic_cast<ModularCall*>(r->children[0].get());
50
+ ASSERT_NE(diff, nullptr);
51
+ EXPECT_EQ(diff->name->name, "difference");
52
+ EXPECT_EQ(diff->children.size(), 2u);
53
+ }
54
+
55
+ TEST(RenderExpression, SupportsMemberAccess) {
56
+ // Lives in `primary`, not `expr`, precisely so the existing
57
+ // `postfix "." NAME` rule applies.
58
+ auto ast = parseSrc("v = render() { cube(1); }.volume;");
59
+ ASSERT_EQ(ast.size(), 1u);
60
+ auto* a = dynamic_cast<Assignment*>(ast[0].get());
61
+ ASSERT_NE(a, nullptr);
62
+ auto* member = dynamic_cast<PrimaryMember*>(a->expr.get());
63
+ ASSERT_NE(member, nullptr);
64
+ EXPECT_EQ(member->member->name, "volume");
65
+ EXPECT_EQ(member->left->kind(), NodeKind::RenderExpression);
66
+ }
67
+
68
+ TEST(RenderExpression, TakesArguments) {
69
+ auto ast = parseSrc("obj = render($fn=32) { sphere(10); };");
70
+ const RenderExpression* r = renderExprOf(ast);
71
+ ASSERT_NE(r, nullptr);
72
+ ASSERT_EQ(r->arguments.size(), 1u);
73
+ auto* named = dynamic_cast<NamedArgument*>(r->arguments[0].get());
74
+ ASSERT_NE(named, nullptr);
75
+ EXPECT_EQ(named->name->name, "$fn");
76
+ }
77
+
78
+ TEST(RenderExpression, AcceptsEmptyAndMultipleChildren) {
79
+ EXPECT_EQ(renderExprOf(parseSrc("e = render() { };"))->children.size(), 0u);
80
+ EXPECT_EQ(renderExprOf(parseSrc("m = render() { cube(1); sphere(2); cylinder(3); };"))->children.size(), 3u);
81
+ }
82
+
83
+ TEST(RenderExpression, WorksAsArgumentAndListElement) {
84
+ EXPECT_NO_THROW(parseSrc("echo(render() { cube(1); });"));
85
+ EXPECT_NO_THROW(parseSrc("v = [render() { cube(1); }, render() { sphere(2); }];"));
86
+ EXPECT_NO_THROW(parseSrc("v = 1 + render() { cube(1); }.volume;"));
87
+ EXPECT_NO_THROW(parseSrc("function f(a) = render() { cube(a); }.volume;"));
88
+ EXPECT_NO_THROW(parseSrc("v = [for (i = [0:2]) render() { cube(i); }];"));
89
+ }
90
+
91
+ // -- The statement form is untouched --------------------------------------
92
+
93
+ TEST(RenderExpression, StatementFormIsStillAModularCall) {
94
+ auto ast = parseSrc("render() cube(1);");
95
+ ASSERT_EQ(ast.size(), 1u);
96
+ auto* call = dynamic_cast<ModularCall*>(ast[0].get());
97
+ ASSERT_NE(call, nullptr);
98
+ EXPECT_EQ(call->kind(), NodeKind::ModularCall);
99
+ EXPECT_EQ(call->name->name, "render");
100
+ EXPECT_EQ(call->children.size(), 1u);
101
+ }
102
+
103
+ TEST(RenderExpression, StatementFormKeepsArgumentsAndModifiers) {
104
+ auto ast = parseSrc("render(convexity=4) cube(1);\n#render() sphere(2);\nrender() { cube(1); sphere(2); }");
105
+ ASSERT_EQ(ast.size(), 3u);
106
+ auto* withArgs = dynamic_cast<ModularCall*>(ast[0].get());
107
+ ASSERT_NE(withArgs, nullptr);
108
+ EXPECT_EQ(withArgs->arguments.size(), 1u);
109
+ EXPECT_EQ(ast[1]->kind(), NodeKind::ModularModifierHighlight);
110
+ auto* braced = dynamic_cast<ModularCall*>(ast[2].get());
111
+ ASSERT_NE(braced, nullptr);
112
+ EXPECT_EQ(braced->children.size(), 2u);
113
+ }
114
+
115
+ // -- The sharp edge, asserted on purpose ----------------------------------
116
+
117
+ TEST(RenderExpression, BareCallChildDoesNotTerminateTheAssignment) {
118
+ // `cube(1);` IS the child_statement, semicolon included, so the enclosing
119
+ // assignment is left unterminated. Inherent to OpenSCAD's grammar, not a
120
+ // choice this implementation makes. The braced form is the documented
121
+ // idiom; this test exists so the behaviour is pinned rather than
122
+ // rediscovered.
123
+ EXPECT_THROW(parseSrc("obj = render() cube(1);"), ParseError);
124
+ EXPECT_NO_THROW(parseSrc("obj = render() { cube(1); };"));
125
+ }
126
+
127
+ // -- Reserving the keyword has costs; pin them ----------------------------
128
+
129
+ TEST(RenderExpression, RenderIsNowAReservedWord) {
130
+ EXPECT_THROW(parseSrc("render = 1;"), ParseError);
131
+ EXPECT_THROW(parseSrc("module render() { cube(1); }"), ParseError);
132
+ EXPECT_THROW(parseSrc("function render() = 1;"), ParseError);
133
+ EXPECT_THROW(parseSrc("cube(render=1);"), ParseError);
134
+ EXPECT_THROW(parseSrc("x = obj.render;"), ParseError);
135
+ }
136
+
137
+ TEST(RenderExpression, DollarRenderIsStillAnOrdinaryName) {
138
+ // keywordTable() is keyed on the full IDENT text, `$` included.
139
+ EXPECT_NO_THROW(parseSrc("$render = 1;"));
140
+ }
141
+
142
+ // -- Round-trips ----------------------------------------------------------
143
+
144
+ TEST(RenderExpression, PrettyPrintOutputReparses) {
145
+ // The whole point: emitted source must parse back. toString()'s own
146
+ // child rendering follows the reference's terminator-free format, so
147
+ // fmtExpr has a dedicated arm that uses fmtBlock instead -- if that arm
148
+ // regresses, the second parse below throws.
149
+ const std::string src =
150
+ "obj = render() difference() { cube(100); sphere(20); };\n"
151
+ "v = render() { cube(1); }.volume;\n"
152
+ "w = render($fn = 32) { x = 5; cube(x); };\n"
153
+ "e = render() { };\n"
154
+ "render() cube(9);\n";
155
+ std::string once = toOpenscad(parseSrc(src));
156
+ std::string twice;
157
+ ASSERT_NO_THROW(twice = toOpenscad(parseSrc(once)));
158
+ EXPECT_EQ(once, twice) << "formatting is not idempotent:\n" << once;
159
+ }
160
+
161
+ TEST(RenderExpression, ToStringIsReparseable) {
162
+ auto ast = parseSrc("obj = render($fn = 32) { cube(1); sphere(2); };");
163
+ const RenderExpression* r = renderExprOf(ast);
164
+ ASSERT_NE(r, nullptr);
165
+ EXPECT_EQ(r->toString(), "render($fn = 32) { cube(1); sphere(2); }");
166
+ EXPECT_NO_THROW(parseSrc("y = " + r->toString() + ";"));
167
+ }
168
+
169
+ TEST(RenderExpression, JsonRoundTrip) {
170
+ const std::string src =
171
+ "obj = render() difference() { cube(100); sphere(20); };\n"
172
+ "w = render($fn = 32) { x = 5; cube(x); };\n"
173
+ "e = render() { };\n";
174
+ auto ast = parseSrc(src);
175
+ std::string before = toOpenscad(ast);
176
+ auto rebuilt = astFromJsonString(astToJsonString(ast));
177
+ EXPECT_EQ(before, toOpenscad(rebuilt));
178
+ }
179
+
180
+ // -- Scope ----------------------------------------------------------------
181
+
182
+ TEST(RenderExpression, HoistsAssignmentsInItsChildBlock) {
183
+ // buildScope() must call collectHoistedDeclarations, exactly as
184
+ // ModularCall does, or `x` below resolves to nothing.
185
+ EXPECT_NO_THROW(parseSrc("obj = render() { cube(x); x = 5; };"));
186
+ }
@@ -892,6 +892,13 @@ struct CompiledChunk {
892
892
  Color,
893
893
  Modifier,
894
894
  Passthrough, // hull()/minkowski()/render() -- empty params, no compute function needed
895
+ // render() in EXPRESSION position. The only Kind whose Pop
896
+ // does NOT append a CSGNode to the parent's accumulator: it
897
+ // discards the subtree and pushes the measurement object() onto
898
+ // the operand stack instead. Its `node` is a RenderExpression,
899
+ // not a ModularCall -- warnUnexpectedBuiltinArgs already
900
+ // self-guards on that (registry.cpp).
901
+ Measure,
895
902
  LinearExtrude,
896
903
  RotateExtrude,
897
904
  Projection,
@@ -901,6 +908,14 @@ struct CompiledChunk {
901
908
  Kind kind;
902
909
  std::string tagName; // "translate"/"rotate"/.../"color"/"highlight"/"background"/"show_only"/"hull"/...
903
910
  const oscad::ASTNode* node = nullptr;
911
+ // Kind::Measure only. (name, slot) for every local visible at this
912
+ // site, so Op::PushBuiltinWrap can publish them into the children's
913
+ // EvalContext. A render() expression's children are STATEMENT
914
+ // opcodes and resolve names through the context, but a compiled
915
+ // FUNCTION keeps its parameters and lets in frame slots instead --
916
+ // without this, `function f(w) = render() { cube(w); }.volume;`
917
+ // cannot see `w` and silently builds nothing.
918
+ std::vector<std::pair<std::string, int>> capturedLocals;
904
919
  };
905
920
 
906
921
  // One Op::PushCsgWrap/PopCsgWrap site pair -- see those ops' own doc
@@ -38,6 +38,18 @@ struct PendingBuiltinWrap {
38
38
  // other kind computes its params at Push time and leaves this default-
39
39
  // empty.
40
40
  CallArgs deferredArgs;
41
+
42
+ // ev.measuring_ as it stood immediately BEFORE this bracket opened.
43
+ // Recorded for EVERY kind, not just Measure, so the exception teardown
44
+ // can restore from front() without inspecting kinds -- a non-Measure
45
+ // entry simply records and restores the same value.
46
+ bool savedMeasuring = false;
47
+ // f.stack.size() and ev.treeStack_.size() at Push time. Kind::Measure's
48
+ // Pop asserts both: it is the first bracket that runs STATEMENT opcodes
49
+ // with a non-empty operand stack beneath it, and nothing structurally
50
+ // enforces that those statements are operand-stack-neutral.
51
+ size_t stackDepthAtPush = 0;
52
+ size_t treeStackDepthAtPush = 0;
41
53
  };
42
54
 
43
55
  // One still-open Op::PushCsgWrap bracket's own state -- see that op's own