openscad-cpp-evaluator 0.42.1__tar.gz → 0.44.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 (208) hide show
  1. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/CLAUDE.md +28 -7
  2. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/CMakeLists.txt +13 -1
  3. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bytecode.hpp +15 -0
  5. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -4
  6. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -16
  7. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/evaluator.hpp +53 -33
  8. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/pyproject.toml +1 -1
  9. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/CMakeLists.txt +8 -0
  10. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/booleans.cpp +8 -7
  11. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/control.cpp +5 -4
  12. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/function_builtins.cpp +43 -2
  13. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/bytecode_compiler.cpp +25 -9
  14. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/bytecode_vm.cpp +26 -16
  15. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/csg_resolve.cpp +5 -21
  16. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/eval_context.cpp +28 -0
  17. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/expr_eval.cpp +10 -0
  18. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/user_calls.cpp +104 -9
  19. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/CMakeLists.txt +1 -0
  20. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_bytecode_compiler.cpp +7 -7
  21. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_children_separate.cpp +119 -0
  22. openscad_cpp_evaluator-0.44.0/tests/test_feature_detection.cpp +161 -0
  23. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_use.cpp +6 -3
  24. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/.github/workflows/wheels.yml +0 -0
  26. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/.gitignore +0 -0
  27. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/.gitmodules +0 -0
  28. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/LICENSE +0 -0
  29. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/README.md +0 -0
  30. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/bindings/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/bindings/ast_to_py.cpp +0 -0
  32. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/bindings/module.cpp +0 -0
  33. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/cmake/embed_font.cmake +0 -0
  34. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/examples/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/examples/eval_perf_benchmark.cpp +0 -0
  36. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/examples/manifold_cache_reuse.cpp +0 -0
  37. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/examples/minimal_debugger.cpp +0 -0
  38. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  39. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/.gitignore +0 -0
  40. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  41. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  42. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/README.md +0 -0
  43. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  44. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  45. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  47. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  48. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  49. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  50. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  51. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  52. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  53. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  54. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  55. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  56. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  57. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  58. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  59. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  60. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  61. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  62. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  63. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  64. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  65. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  66. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  67. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  68. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  69. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  70. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  71. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  72. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  73. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  74. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  75. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  76. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  77. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  78. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  79. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  80. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  81. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  82. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  83. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  84. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  85. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  86. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  87. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  88. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  89. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  90. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  91. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  92. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  93. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  94. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  95. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  96. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  97. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  98. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  99. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  100. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  101. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  102. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  103. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  104. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  105. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  106. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  107. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  110. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  111. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  112. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  113. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  114. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  115. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  116. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  117. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  118. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  119. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  122. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  123. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  124. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  125. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  126. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  127. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  128. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  129. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  130. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  131. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  132. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  135. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  137. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/builtins.hpp +0 -0
  138. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/call_args.cpp +0 -0
  139. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/color.cpp +0 -0
  140. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/dxf_dim.cpp +0 -0
  141. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/extrude.cpp +0 -0
  142. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/import.cpp +0 -0
  143. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/modifiers.cpp +0 -0
  144. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/primitives_2d.cpp +0 -0
  145. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/primitives_3d.cpp +0 -0
  146. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/registry.cpp +0 -0
  147. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/roof.cpp +0 -0
  148. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/segments.cpp +0 -0
  149. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/surface.cpp +0 -0
  150. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/text.cpp +0 -0
  151. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/topology.cpp +0 -0
  152. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/builtins/transforms.cpp +0 -0
  153. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/colored_body.cpp +0 -0
  154. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/csg_generate.cpp +0 -0
  155. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/css_colors.cpp +0 -0
  156. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/debug/debug_repl.cpp +0 -0
  157. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/debug_profile.cpp +0 -0
  158. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/eval_error.cpp +0 -0
  159. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/eval_use.cpp +0 -0
  160. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/export.cpp +0 -0
  161. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/import/dxf_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/import/mesh_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/import/surface_load.cpp +0 -0
  164. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/import/svg_import.cpp +0 -0
  165. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/manifold_cache.cpp +0 -0
  166. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/measure_geometry.cpp +0 -0
  167. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/mesh_check.cpp +0 -0
  168. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/stmt_eval.cpp +0 -0
  169. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/text/font_match.cpp +0 -0
  170. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/text/freetype_font_provider.cpp +0 -0
  171. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/text/text_metrics.cpp +0 -0
  172. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/value.cpp +0 -0
  173. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/src/zip_stored.cpp +0 -0
  174. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_booleans.cpp +0 -0
  175. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_control_flow.cpp +0 -0
  177. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_csg_tree.cpp +0 -0
  178. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_debug_hooks.cpp +0 -0
  179. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_dxf_svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_eval_context.cpp +0 -0
  181. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_export.cpp +0 -0
  182. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_expr_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_extrude_roof.cpp +0 -0
  184. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_primitives.cpp +0 -0
  191. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_profiling.cpp +0 -0
  192. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_python_bindings.py +0 -0
  193. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_render_expr.cpp +0 -0
  194. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_scope_trail.cpp +0 -0
  195. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_scoping.cpp +0 -0
  196. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_surface.cpp +0 -0
  197. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_tail_calls.cpp +0 -0
  198. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_text.cpp +0 -0
  199. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_transforms.cpp +0 -0
  200. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_unexpected_args.cpp +0 -0
  201. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.42.1 → openscad_cpp_evaluator-0.44.0}/uv.lock +0 -0
@@ -441,6 +441,21 @@ handling in arithmetic, the closure-detection rule for `call_ctx` vs `child_ctx`
441
441
  verbatim port, not an independent design. Deliberate divergences are called out in code comments —
442
442
  grep for `ponytail:`.
443
443
 
444
+ - **Feature detection** — `$_SUPPORTED_FEATURE` (just `true`; names the CAPABILITY rather than the
445
+ vendor, so any evaluator adding `supported_feature()` is meant to set it, and checking it first
446
+ solves the bootstrapping problem of calling a function you don't know exists. Written
447
+ `!is_undef($_SUPPORTED_FEATURE) && supported_feature(...)` the guard is **silent** in OpenSCAD --
448
+ `is_undef()` is the one way to read an unknown variable there without a warning, and `&&`
449
+ short-circuits past the unknown call; verified against the reference), `$_BELFRYSCAD` (seeded in `EvalContext::makeRoot`, `src/eval_context.cpp`, as
450
+ `[major, minor, patch]` from `OSCAD_EVAL_VERSION_*` compile definitions that the top-level
451
+ `CMakeLists.txt` parses out of `pyproject.toml`, so wheel and local builds cannot disagree and a
452
+ bump needs one edit) and `supported_feature("name")` (`featureLevels()` in
453
+ `src/builtins/function_builtins.cpp`, returning a LEVEL so a feature whose semantics change can
454
+ be told from its earlier self; 0 for unknown names and non-string arguments, deliberately
455
+ silent so probing for a future feature is safe). Both exist because OpenSCAD **silently ignores**
456
+ unknown arguments -- `children(separate=true)` renders the wrong shape there with no warning --
457
+ and both read as `undef` in OpenSCAD, so a guard written against them works in both.
458
+
444
459
  - `include/openscad_cpp_evaluator/value.hpp`, `src/value.cpp` — the `Value` variant (OpenSCAD's
445
460
  dynamic value type) and its free-function arithmetic/comparison helpers. See the plan's §1 for
446
461
  the type-mapping table and why there's no numpy-equivalent library.
@@ -481,13 +496,19 @@ grep for `ponytail:`.
481
496
  lexically nested inside an already-active call," picking `childCtx()` vs `callCtx()`
482
497
  accordingly; read its doc comment before touching it, it's the second-trickiest mechanism in this
483
498
  codebase after the CSG tree stack), `evalUserModule`/`evalUserFunction`/`evalFunctionLiteral`,
484
- and `builtinChildren` (children()/children(N), deferred evaluation; `children(separate=true)`
485
- additionally marks each forwarded node `separateOperand` so the enclosing
486
- union/difference/intersection treats them as separate operands rather than one grouped one --
487
- a BelfrySCAD extension, see `Evaluator::appendGroupSizes`. The marks stop at a user-module
488
- boundary: only the children() call's own splice honours them
489
- (`spliceModuleChildren`'s `honorSeparateMarks`), so a module wrapping such a call wraps its
490
- children as usual and its own caller sees one operand). `evalFunctionCall`'s
499
+ and `builtinChildren` (children()/children(N), deferred evaluation).
500
+ **`children(..., separate=true)`** is a BelfrySCAD extension handled *before* any of that, by
501
+ `expandChildStatements`: a block is read with each separating forward replaced by one synthetic
502
+ `children(k)` statement per child it selects (`makeChildrenIndexCall`, owned by
503
+ `syntheticNodes_` and cleared with `idToNode` at `resolveTreeImpl` entry). Everything the
504
+ feature does follows from their being real statements -- `$children` counts them,
505
+ `children(i)` indexes them, and `union`/`difference`/`intersection` give each its own operand
506
+ group -- with no flag on `CSGNode` and nothing to leak. Called from `buildModuleChildCtx`,
507
+ `resolveCsg`, `resolveIntersectionFor`, and the VM's `Op::CsgGroupChildren`, which exists
508
+ because the compiler fixes group boundaries long before `$children` (and so the expansion size)
509
+ is knowable; it shares `expandSeparatingChildren` with the interpreter so the engines cannot
510
+ drift. Synthetic calls carry the author's own `Position` and no scope, so they resolve in the
511
+ evaluating context (`evalChildren`'s `runAll`). `evalFunctionCall`'s
491
512
  precedence order (checked in this exact sequence): `import` (special-cased, its own
492
513
  module/expression-context split) → `isBuiltinFunctionName` (function_builtins.hpp — always wins
493
514
  if true) → user function lookup → function-literal *value* probe → "unknown function" warning.
@@ -1,5 +1,17 @@
1
1
  cmake_minimum_required(VERSION 3.24)
2
- project(openscad_cpp_evaluator CXX)
2
+
3
+ # Read the version straight out of pyproject.toml rather than repeating it
4
+ # here. It reaches C++ as OSCAD_EVAL_VERSION_{MAJOR,MINOR,PATCH} (see
5
+ # src/CMakeLists.txt), which is what seeds $_BELFRYSCAD -- so a released
6
+ # wheel and a local `cmake --build` always report the same numbers, and a
7
+ # version bump needs one edit, not three.
8
+ file(READ "${CMAKE_CURRENT_SOURCE_DIR}/pyproject.toml" _oscad_pyproject)
9
+ if(NOT _oscad_pyproject MATCHES "\nversion = \"([0-9]+)\.([0-9]+)\.([0-9]+)\"")
10
+ message(FATAL_ERROR "could not read version = \"X.Y.Z\" from pyproject.toml")
11
+ endif()
12
+ set(OSCAD_EVAL_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
13
+
14
+ project(openscad_cpp_evaluator VERSION ${OSCAD_EVAL_VERSION} LANGUAGES CXX)
3
15
 
4
16
  # On Apple Silicon, warn (don't fail -- a universal or intentionally-x86_64
5
17
  # build is legitimate) if the effective target architecture is x86_64 while
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.42.1
3
+ Version: 0.44.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -426,6 +426,21 @@ enum class Op {
426
426
  // own `groupSizes.push_back(Value{...})` loop.
427
427
  CsgGroupEnd,
428
428
 
429
+ // One `children(..., separate=true)` statement inside a CSG bracket.
430
+ // `a` interns the ModularCall in nativeStatements.
431
+ //
432
+ // A statement of its own, rather than a CsgGroupStart/End pair around
433
+ // compiled code, because how many groups it contributes is only known
434
+ // at RUNTIME -- `children([2:$children-1], separate=true)` reads
435
+ // $children, and the compiler emits its group boundaries long before
436
+ // any of that exists. So this op expands the call (the same
437
+ // Evaluator::expandSeparatingChildren the interpreter uses, so the two
438
+ // engines cannot drift) and records one group per expanded statement.
439
+ //
440
+ // Not an interpreter bail: the forwarded statements go through
441
+ // evalChildren, which self-gates and retries its own compiled chunks.
442
+ CsgGroupChildren,
443
+
429
444
  // Closes the matching Op::PushCsgWrap bracket: pops
430
445
  // VmFrame::csgWrapStack's own top entry FIRST (before anything that can
431
446
  // itself throw, e.g. setTreeDepthOrThrow below -- same "exception-
@@ -219,10 +219,6 @@ struct VmFrame {
219
219
  // mirrors evalModularCall's own `&node` (csg_resolve.cpp). Only
220
220
  // meaningful when ownsModuleSplice is true.
221
221
  const oscad::ASTNode* moduleSpliceCallNode = nullptr;
222
- // children(separate=true) on the call this frame is forwarding for.
223
- // Only meaningful while ownsModuleSplice is true, same as the two
224
- // fields above; reset in releaseVmFrame for the same pooling reason.
225
- bool separateChildren = false;
226
222
 
227
223
  // Whether callStack_.back() (at push time) genuinely IS this frame's
228
224
  // own logical call, and therefore safe for a tail hop inside this
@@ -34,15 +34,6 @@ 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
- // Set only by children(separate=true): this node begins its OWN operand
38
- // group in an enclosing union/difference/intersection/intersection_for,
39
- // instead of merging into the single group its enclosing statement
40
- // would otherwise form. See Evaluator::appendGroupSizes.
41
- //
42
- // Deliberately absent from cacheKey()'s allowlist (manifold_cache.cpp):
43
- // it changes the PARENT's hashed "group_sizes" param, never this node's
44
- // own geometry, so the parent already re-keys and this node must not.
45
- bool separateOperand = false;
46
37
 
47
38
  // The call site that entered this node's call chain from the top
48
39
  // level, captured at RESOLVE time -- non-owning, AST-lifetime-bound
@@ -83,12 +74,5 @@ struct CSGNode {
83
74
  std::optional<std::string> cachedKey;
84
75
  };
85
76
 
86
- // Marks every node from `from` onward as starting its own operand group --
87
- // what children(separate=true) does to the geometry it just forwarded.
88
- // A free function, not a member, so all three splice sites (user_calls.cpp
89
- // and bytecode_vm.cpp's two) can reach it from this header alone.
90
- inline void markSeparateOperands(std::vector<std::unique_ptr<CSGNode>>& nodes, size_t from) {
91
- for (size_t i = from; i < nodes.size(); ++i) nodes[i]->separateOperand = true;
92
- }
93
77
 
94
78
  } // namespace oscadeval
@@ -190,29 +190,6 @@ public:
190
190
  // statement.
191
191
  size_t currentTreeFrameSize() const { return treeStack_.back().size(); }
192
192
 
193
- // Records the group(s) one child statement contributed, given the frame
194
- // size captured before it ran. Normally that is exactly one group, of
195
- // however many nodes the statement pushed -- including a group of size
196
- // ZERO for a statement that produced no geometry (a disabled `*cube()`),
197
- // which generateCsg relies on to reset intersection() and to bail
198
- // difference(). Only children(separate=true) produces more than one:
199
- // each node it marked starts a fresh group, so its forwarded children
200
- // reach the enclosing operator as separate operands.
201
- //
202
- // Shared by all four group builders -- resolveCsg, resolveIntersectionFor
203
- // and the VM's Op::CsgGroupEnd (which serves both) -- because
204
- // "group_sizes" is otherwise built twice, once per engine, and would
205
- // drift.
206
- void appendGroupSizes(std::vector<Value>& groupSizes, size_t before) const {
207
- const std::vector<std::unique_ptr<CSGNode>>& frame = treeStack_.back();
208
- size_t start = before;
209
- for (size_t i = before + 1; i < frame.size(); ++i) {
210
- if (!frame[i]->separateOperand) continue;
211
- groupSizes.push_back(Value{static_cast<double>(i - start)});
212
- start = i;
213
- }
214
- groupSizes.push_back(Value{static_cast<double>(frame.size() - start)});
215
- }
216
193
 
217
194
  // Generate pass: walks `tree` bottom-up (children before their own
218
195
  // node), calling each node's registered GenerateFn (falling back to
@@ -366,14 +343,56 @@ public:
366
343
  struct ChildrenForward {
367
344
  EvalContext evalCtx;
368
345
  std::vector<const oscad::ASTNode*> nodes;
369
- // children(separate=true): hand these to the enclosing union/
370
- // difference/intersection as SEPARATE operands rather than as one
371
- // grouped operand. Acted on after the nodes are evaluated, by
372
- // marking the CSGNodes they produced -- see markSeparateOperands.
346
+ // The `separate` flag as written at the call. Read by
347
+ // expandSeparatingChildren, which turns the forward into one
348
+ // statement per child; builtinChildren itself ignores it, since by
349
+ // the time a forward runs the expansion has already happened.
373
350
  bool separate = false;
351
+ // Which of the caller's geometry statements `nodes` came from, in
352
+ // the same order. Needed by expandChildStatements, which turns each
353
+ // one back into its own `children(k)` statement.
354
+ std::vector<size_t> pickedIndices;
374
355
  };
375
356
  std::optional<ChildrenForward> prepareChildrenForward(const CallArgs& args, EvalContext& ctx);
376
357
 
358
+ // A child-statement block with every `children(..., separate=true)`
359
+ // replaced by one synthetic `children(k)` statement per child it
360
+ // selects. That is the whole of what separate=true does: the forwarded
361
+ // children become real, countable statements at this call site, so
362
+ // $children sees them, children(i) indexes them, and a boolean operator
363
+ // gives each its own operand group -- all for free, because they ARE
364
+ // statements rather than nodes wearing a flag.
365
+ //
366
+ // Called wherever a block is consumed: a module call's children
367
+ // (buildModuleChildCtx), and union/difference/intersection/
368
+ // intersection_for's own child list.
369
+ std::vector<const oscad::ASTNode*> expandChildStatements(
370
+ const std::vector<std::unique_ptr<oscad::ASTNode>>& block, EvalContext& ctx);
371
+
372
+ // One synthetic `children(index)` statement, owned by syntheticNodes_.
373
+ const oscad::ASTNode* makeChildrenIndexCall(const oscad::ModularCall& origin, size_t index);
374
+
375
+ // The sole argument of `is_undef(x)` when x is a bare identifier, else
376
+ // nullptr. That call is a PROBE -- asking whether a name is defined must
377
+ // not itself complain that it isn't -- so the name is read with
378
+ // warnIfUndef=false. Static and syntactic so the bytecode compiler can
379
+ // ask at compile time and emit LoadFreeNoWarn.
380
+ static const oscad::Identifier* undefProbeTarget(const oscad::PrimaryCall& call);
381
+
382
+ // Syntactic gate: is this statement a `children(..., separate=true)`?
383
+ // Cheap and side-effect-free, so the bytecode compiler can ask it at
384
+ // COMPILE time to decide which opcode to emit, long before the flag's
385
+ // value (or the child count it selects) is knowable.
386
+ static bool isSeparatingChildrenCall(const oscad::ASTNode& stmt);
387
+
388
+ // The runtime half: the statements `call` expands to. nullopt means the
389
+ // `separate` flag resolved falsey after all, and the caller should run
390
+ // the original statement unchanged. Shared by expandChildStatements
391
+ // (interpreter) and Op::CsgGroupChildren (VM) so the engines cannot
392
+ // drift on what expansion means.
393
+ std::optional<std::vector<const oscad::ASTNode*>> expandSeparatingChildren(const oscad::ModularCall& call,
394
+ EvalContext& ctx);
395
+
377
396
  // "WARNING: {message}{locSuffix(position)}" via echoFn_, no-op if unset.
378
397
  // Public: builtins/import.cpp's not-manifold warning is emitted from a
379
398
  // free function, same reasoning as tagGenerated()/builtinChildren().
@@ -999,13 +1018,8 @@ public:
999
1018
  // (evalModularCall's OWN "is this splice or wrap-as-a-tagged-node"
1000
1019
  // branch already decided `splice` before ever reaching a user module,
1001
1020
  // so this helper never needs that decision itself).
1002
- // `honorSeparateMarks` is true only for a children() call's OWN splice.
1003
- // A user module's splice passes false, so it wraps its marked children
1004
- // as usual and the marks stop there -- children(separate=true) applies
1005
- // to the operator enclosing that call, not to whatever encloses the
1006
- // module the call happens to sit in.
1007
1021
  void spliceModuleChildren(std::vector<std::unique_ptr<CSGNode>> children, std::uint64_t randsBefore,
1008
- const oscad::ASTNode& callNode, bool honorSeparateMarks = false);
1022
+ const oscad::ASTNode& callNode);
1009
1023
  private:
1010
1024
  Value evalUserFunction(const std::string& name, const oscad::FunctionDeclaration& decl,
1011
1025
  const std::vector<std::unique_ptr<oscad::Argument>>& arguments, EvalContext& ctx,
@@ -1661,6 +1675,12 @@ public:
1661
1675
  // is on top *at that moment* -- the frame this node's own build call
1662
1676
  // pushed just before resolving. See csg_resolve.cpp for the full
1663
1677
  // push/call/pop sequence.
1678
+ // Synthetic `children(k)` calls built by expandChildStatements. Owned
1679
+ // here because CSGNode::node and idToNode hold non-owning pointers into
1680
+ // them, so they must outlive the pass; cleared with those maps at
1681
+ // resolveTreeImpl entry.
1682
+ std::vector<std::unique_ptr<oscad::ASTNode>> syntheticNodes_;
1683
+
1664
1684
  std::vector<std::vector<std::unique_ptr<CSGNode>>> treeStack_;
1665
1685
 
1666
1686
  private:
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.42.1"
7
+ version = "0.44.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -46,6 +46,14 @@ add_library(openscad_cpp_evaluator STATIC
46
46
  builtins/surface.cpp
47
47
  )
48
48
 
49
+ # Seeds $_BELFRYSCAD (eval_context.cpp). Parsed from pyproject.toml by the
50
+ # top-level CMakeLists so there is one source of truth for the version.
51
+ target_compile_definitions(openscad_cpp_evaluator PRIVATE
52
+ OSCAD_EVAL_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
53
+ OSCAD_EVAL_VERSION_MINOR=${PROJECT_VERSION_MINOR}
54
+ OSCAD_EVAL_VERSION_PATCH=${PROJECT_VERSION_PATCH}
55
+ )
56
+
49
57
  target_include_directories(openscad_cpp_evaluator PUBLIC
50
58
  $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
51
59
  )
@@ -70,10 +70,11 @@ std::optional<manifold::CrossSection> toCrossSection(const std::vector<ColoredBo
70
70
  // misbehave when e.g. BOSL2's attachable() returns multiple bodies (parent
71
71
  // + attached children) as one operand.
72
72
  //
73
- // One statement can contribute MORE than one group: children(separate=true)
74
- // marks the nodes it forwards so each starts its own operand group, which is
73
+ // One statement can still turn into several: children(separate=true) is
74
+ // expanded into one `children(k)` statement per child it forwards, before
75
+ // this loop ever sees the block (Evaluator::expandChildStatements), which is
75
76
  // how `difference() children(separate=true)` subtracts children 1..n from
76
- // child 0. See Evaluator::appendGroupSizes, which both engines share. Mirrors _resolve_csg/_generate_csg,
77
+ // child 0. Nothing special happens here -- they are simply statements. Mirrors _resolve_csg/_generate_csg,
77
78
  // minus _attach_tri_colors' multi-color-merge provenance (ponytail: a
78
79
  // merged body always takes its first contributing child's color, matching
79
80
  // this evaluator's own pre-tri_colors baseline behavior -- revisit
@@ -92,11 +93,11 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
92
93
 
93
94
  std::vector<const oscad::ASTNode*> assignNodes;
94
95
  std::vector<const oscad::ASTNode*> geoNodes;
95
- for (const auto& c : node.children) {
96
+ for (const oscad::ASTNode* c : ev.expandChildStatements(node.children, effCtx)) {
96
97
  if (c->kind() == oscad::NodeKind::Assignment) {
97
- assignNodes.push_back(c.get());
98
+ assignNodes.push_back(c);
98
99
  } else if (c->kind() != oscad::NodeKind::ModuleDeclaration && c->kind() != oscad::NodeKind::FunctionDeclaration) {
99
- geoNodes.push_back(c.get());
100
+ geoNodes.push_back(c);
100
101
  }
101
102
  }
102
103
  if (!assignNodes.empty()) ev.evalChildren(assignNodes, effCtx);
@@ -106,7 +107,7 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
106
107
  for (const oscad::ASTNode* geoNode : geoNodes) {
107
108
  const size_t before = ev.currentTreeFrameSize();
108
109
  ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, effCtx);
109
- ev.appendGroupSizes(groupSizes, before);
110
+ groupSizes.push_back(Value{static_cast<double>(ev.currentTreeFrameSize() - before)});
110
111
  }
111
112
 
112
113
  CSGParams params;
@@ -97,9 +97,10 @@ ColoredBody combineBodies(const std::vector<ColoredBody>& bodies) {
97
97
  // children) -- same rationale as booleans.cpp's own group_sizes. Mirrors
98
98
  // _resolve_intersection_for/_generate_intersection_for.
99
99
  CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersectionFor& node, EvalContext& ctx) {
100
- std::vector<const oscad::ASTNode*> bodyNodes;
101
- bodyNodes.reserve(node.body.size());
102
- for (const auto& b : node.body) bodyNodes.push_back(b.get());
100
+ // Expanded for the same reason resolveCsg expands its own block: a
101
+ // children(..., separate=true) here contributes one statement -- and so
102
+ // one operand group per iteration -- per child it selects.
103
+ const std::vector<const oscad::ASTNode*> bodyNodes = ev.expandChildStatements(node.body, ctx);
103
104
 
104
105
  std::vector<Value> groupSizes;
105
106
 
@@ -119,7 +120,7 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
119
120
  if (!bodyNodes.empty()) ev.checkDebug(*bodyNodes.front(), parentCtx, /*forced=*/false, /*exprLevel=*/true);
120
121
  const size_t before = ev.currentTreeFrameSize();
121
122
  ev.evalChildren(bodyNodes, parentCtx);
122
- ev.appendGroupSizes(groupSizes, before);
123
+ groupSizes.push_back(Value{static_cast<double>(ev.currentTreeFrameSize() - before)});
123
124
  return;
124
125
  }
125
126
  const auto& assign = node.assignments[depth];
@@ -449,7 +449,7 @@ bool isBuiltinFunctionName(const std::string& name) {
449
449
  "acos", "atan", "atan2", "max", "min", "pow", "norm", "cross", "rands", "concat", "len", "str",
450
450
  "chr", "ord", "is_undef", "is_num", "is_bool", "is_string", "is_list", "is_function", "is_object",
451
451
  "search", "lookup", "has_key", "version", "version_num", "parent_module",
452
- "object", "textmetrics", "fontmetrics", "dxf_dim", "dxf_cross",
452
+ "object", "textmetrics", "fontmetrics", "dxf_dim", "dxf_cross", "supported_feature",
453
453
  };
454
454
  return names.count(name) > 0;
455
455
  }
@@ -591,9 +591,37 @@ enum class BuiltinFnId {
591
591
  TextMetrics, FontMetrics, Abs, Sign, Ceil, Floor, Round, Sqrt, Ln, Log, Exp, Sin, Cos, Tan,
592
592
  Asin, Acos, Atan, Atan2, Max, Min, Pow, Norm, Cross, Rands, Concat, Len, Str, Chr, Ord,
593
593
  IsUndef, IsNum, IsBool, IsString, IsList, IsFunction, IsObject, Search, Lookup, HasKey,
594
- Version, VersionNum, ParentModule, DxfDim, DxfCross,
594
+ Version, VersionNum, ParentModule, DxfDim, DxfCross, SupportedFeature,
595
595
  };
596
596
 
597
+ // supported_feature("name") -> the level at which this build implements that
598
+ // feature, or 0 for one it does not implement (and for a name it has never
599
+ // heard of, which is deliberate: probing for a feature from a future release
600
+ // is supposed to be safe).
601
+ //
602
+ // A LEVEL rather than a boolean so a feature whose semantics change later
603
+ // can be told apart from its earlier self. Everything here is 1 today;
604
+ // nothing has changed since this function existed, and a script cannot
605
+ // observe what a build without supported_feature() did anyway.
606
+ //
607
+ // This is the answer to a real hazard: OpenSCAD does not reject arguments or
608
+ // names it doesn't know -- children(separate=true) is silently ignored there
609
+ // -- so a script using an extension runs and quietly renders something else.
610
+ // Guarding on supported_feature() is how a script says so out loud.
611
+ const std::unordered_map<std::string, double>& featureLevels() {
612
+ static const std::unordered_map<std::string, double> levels = {
613
+ {"separate-children", 1.0}, // children(..., separate=true)
614
+ {"minkowski-diff", 1.0}, // minkowski_difference()
615
+ {"sphere-styles", 1.0}, // sphere(style=)
616
+ {"export-name", 1.0}, // $export_name
617
+ {"simplify-op", 1.0}, // simplify()
618
+ {"expr-import", 1.0}, // import() in expression position
619
+ {"object-function", 1.0}, // object(), unconditional here
620
+ {"roof-op", 1.0}, // roof(), method="voronoi" only
621
+ };
622
+ return levels;
623
+ }
624
+
597
625
  const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
598
626
  static const std::unordered_map<std::string, BuiltinFnId> ids = {
599
627
  {"textmetrics", BuiltinFnId::TextMetrics}, {"fontmetrics", BuiltinFnId::FontMetrics},
@@ -614,6 +642,7 @@ const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
614
642
  {"version", BuiltinFnId::Version}, {"version_num", BuiltinFnId::VersionNum},
615
643
  {"parent_module", BuiltinFnId::ParentModule},
616
644
  {"dxf_dim", BuiltinFnId::DxfDim}, {"dxf_cross", BuiltinFnId::DxfCross},
645
+ {"supported_feature", BuiltinFnId::SupportedFeature},
617
646
  };
618
647
  return ids;
619
648
  }
@@ -694,6 +723,7 @@ const std::unordered_map<int, BuiltinCheck>& builtinChecks() {
694
723
  // warning, so only the too-many case is an arity error.
695
724
  add(BuiltinFnId::ParentModule, {0, 1, "1", {kNum}});
696
725
  add(BuiltinFnId::HasKey, {-1, -1, nullptr, {{TObj, "object"}, {TStr, "string"}}});
726
+ add(BuiltinFnId::SupportedFeature, {1, 1, "1", {}});
697
727
  for (BuiltinFnId id : {BuiltinFnId::IsUndef, BuiltinFnId::IsNum, BuiltinFnId::IsBool,
698
728
  BuiltinFnId::IsString, BuiltinFnId::IsList, BuiltinFnId::IsFunction,
699
729
  BuiltinFnId::IsObject}) {
@@ -1008,6 +1038,17 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
1008
1038
  // The OpenSCAD release we track. version_num() is that same
1009
1039
  // year/month/day folded as y * 10000 + m * 100 + d, exactly like the
1010
1040
  // reference's own builtin_version_num (builtin_functions.cc).
1041
+ case BuiltinFnId::SupportedFeature: {
1042
+ const Value name = getArg(args, 0, "feature", Value{});
1043
+ const std::string* s = std::get_if<std::string>(&name);
1044
+ // A non-string is 0 rather than an error, same as an unknown
1045
+ // name: the whole point of this function is that asking is
1046
+ // always safe.
1047
+ if (!s) return Value{0.0};
1048
+ const auto& levels = featureLevels();
1049
+ const auto it = levels.find(*s);
1050
+ return Value{it == levels.end() ? 0.0 : it->second};
1051
+ }
1011
1052
  case BuiltinFnId::Version: return numList({2026.0, 1.0, 1.0});
1012
1053
  case BuiltinFnId::VersionNum: {
1013
1054
  // The optional vector argument the reference also accepts: with
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "openscad_cpp_evaluator/call_args.hpp"
4
4
  #include "openscad_cpp_evaluator/dispatch.hpp"
5
+ #include "openscad_cpp_evaluator/evaluator.hpp"
5
6
  #include "openscad_cpp_evaluator/function_builtins.hpp"
6
7
 
7
8
  #include "builtins/builtins.hpp"
@@ -619,15 +620,24 @@ public:
619
620
  }
620
621
  }
621
622
 
622
- for (const auto& argPtr : n.arguments) {
623
- if (argPtr->kind() == NodeKind::NamedArgument) {
624
- auto& a = static_cast<const oscad::NamedArgument&>(*argPtr);
625
- compileExpr(*a.expr, out, scope); // an argument is never tail
626
- site.argNames.push_back(a.name->name);
627
- } else {
628
- auto& a = static_cast<const oscad::PositionalArgument&>(*argPtr);
629
- compileExpr(*a.expr, out, scope);
630
- site.argNames.push_back(std::nullopt);
623
+ // is_undef(name) probes rather than reads -- the same quiet
624
+ // load the interpreter uses (Evaluator::undefProbeTarget),
625
+ // so the two engines agree on which reads warn.
626
+ if (const oscad::Identifier* undefProbe = Evaluator::undefProbeTarget(n)) {
627
+ compileIdentifierLoad(undefProbe->name, undefProbe->position(), out, scope,
628
+ /*warnIfUndef=*/false);
629
+ site.argNames.push_back(std::nullopt);
630
+ } else {
631
+ for (const auto& argPtr : n.arguments) {
632
+ if (argPtr->kind() == NodeKind::NamedArgument) {
633
+ auto& a = static_cast<const oscad::NamedArgument&>(*argPtr);
634
+ compileExpr(*a.expr, out, scope); // an argument is never tail
635
+ site.argNames.push_back(a.name->name);
636
+ } else {
637
+ auto& a = static_cast<const oscad::PositionalArgument&>(*argPtr);
638
+ compileExpr(*a.expr, out, scope);
639
+ site.argNames.push_back(std::nullopt);
640
+ }
631
641
  }
632
642
  }
633
643
  int siteIdx = static_cast<int>(chunk_.callSites.size());
@@ -1292,6 +1302,12 @@ public:
1292
1302
  }
1293
1303
  compileStatementList(assignNodes, out);
1294
1304
  for (const oscad::ASTNode* geoNode : geoNodes) {
1305
+ // A separating children() forward contributes a runtime-decided
1306
+ // NUMBER of groups, so it cannot be bracketed at compile time.
1307
+ if (Evaluator::isSeparatingChildrenCall(*geoNode)) {
1308
+ out.push_back({Op::CsgGroupChildren, internNativeStatement(geoNode), 0, &geoNode->position()});
1309
+ continue;
1310
+ }
1295
1311
  out.push_back({Op::CsgGroupStart, 0, 0, nullptr});
1296
1312
  compileStatementList(std::vector<const oscad::ASTNode*>{geoNode}, out);
1297
1313
  out.push_back({Op::CsgGroupEnd, 0, 0, nullptr});
@@ -360,7 +360,7 @@ void pushBracketedModuleFrame(Evaluator& ev, const CompiledChunk& chunk, const o
360
360
  // because module chunks never contain tail-call opcodes); don't inherit
361
361
  // a pooled function frame's stale true here either.
362
362
  void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalContext evalCtx,
363
- std::uint64_t randsBefore, const oscad::ASTNode& callNode, bool separate) {
363
+ std::uint64_t randsBefore, const oscad::ASTNode& callNode) {
364
364
  if (ev.vmCallStack_.size() >= Evaluator::kMaxVmCallStackDepth) {
365
365
  ev.error("Recursion too deep while forwarding children()", callNode);
366
366
  }
@@ -381,7 +381,6 @@ void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalCon
381
381
  frame->ownsModuleSplice = true;
382
382
  frame->moduleRandsBefore = randsBefore;
383
383
  frame->moduleSpliceCallNode = &callNode;
384
- frame->separateChildren = separate;
385
384
  ev.vmCallStack_.push_back(std::move(frame));
386
385
  ev.vmCallBrackets_.emplace_back(std::nullopt);
387
386
  }
@@ -506,7 +505,6 @@ Value driveVm(Evaluator& ev, size_t floor) {
506
505
  const bool ownsModuleSplice = finished->ownsModuleSplice;
507
506
  const std::uint64_t moduleRandsBefore = finished->moduleRandsBefore;
508
507
  const oscad::ASTNode* moduleSpliceCallNode = finished->moduleSpliceCallNode;
509
- const bool separateChildren = finished->separateChildren;
510
508
  while (!finished->ctxChain.empty()) finished->ctxChain.pop_back();
511
509
  // Module frames never fire returnHook (native evalUserModule
512
510
  // never did either -- a module call has no "return value"
@@ -516,12 +514,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
516
514
  if (isModule && ownsModuleSplice) {
517
515
  std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
518
516
  ev.treeStack_.pop_back();
519
- // separateChildren is set only by pushChildrenForwardFrame,
520
- // so it also answers "is this a children() splice?" -- a
521
- // user module's frame always passes false and wraps.
522
- if (separateChildren) markSeparateOperands(children, 0);
523
- ev.spliceModuleChildren(std::move(children), moduleRandsBefore, *moduleSpliceCallNode,
524
- /*honorSeparateMarks=*/separateChildren);
517
+ ev.spliceModuleChildren(std::move(children), moduleRandsBefore, *moduleSpliceCallNode);
525
518
  }
526
519
  if (isFloorFrame) {
527
520
  finalResult = std::move(result); // unused by a module caller (runCompiledModuleBody ignores it)
@@ -1051,11 +1044,9 @@ Value driveVm(Evaluator& ev, size_t floor) {
1051
1044
  const CompiledChunk* chunk = (ev.useBytecodeVm() && ev.inResolvePass())
1052
1045
  ? ev.lookupOrCompileChildrenListChunk(fwd->nodes)
1053
1046
  : nullptr;
1054
- // Read before fwd->evalCtx is moved from.
1055
- const bool separate = fwd->separate;
1056
1047
  if (chunk) {
1057
1048
  ev.treeStack_.emplace_back();
1058
- pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode, separate);
1049
+ pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode);
1059
1050
  // f.pc deliberately NOT advanced -- resumes when
1060
1051
  // the pushed frame completes; driveVm's completion
1061
1052
  // branch runs the splice (isModule &&
@@ -1079,9 +1070,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1079
1070
  }
1080
1071
  std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
1081
1072
  ev.treeStack_.pop_back();
1082
- if (separate) markSeparateOperands(children, 0);
1083
- ev.spliceModuleChildren(std::move(children), randsBefore, *callNode,
1084
- /*honorSeparateMarks=*/true);
1073
+ ev.spliceModuleChildren(std::move(children), randsBefore, *callNode);
1085
1074
  ++f.pc;
1086
1075
  }
1087
1076
  break;
@@ -1318,9 +1307,30 @@ Value driveVm(Evaluator& ev, size_t floor) {
1318
1307
  ++f.pc;
1319
1308
  break;
1320
1309
  }
1310
+ case Op::CsgGroupChildren: {
1311
+ const auto& call =
1312
+ static_cast<const oscad::ModularCall&>(*f.chunk->nativeStatements[static_cast<size_t>(ins.a)]);
1313
+ PendingCsgWrap& pending = f.csgWrapStack.back();
1314
+ std::optional<std::vector<const oscad::ASTNode*>> expanded =
1315
+ ev.expandSeparatingChildren(call, ctx);
1316
+ // nullopt = `separate` resolved falsey: one group, the
1317
+ // original statement, exactly as CsgGroupStart/End would
1318
+ // have done for it.
1319
+ const std::vector<const oscad::ASTNode*> stmts =
1320
+ expanded ? *expanded : std::vector<const oscad::ASTNode*>{&call};
1321
+ for (const oscad::ASTNode* stmt : stmts) {
1322
+ const size_t before = ev.treeStack_.back().size();
1323
+ ev.evalChildren(std::vector<const oscad::ASTNode*>{stmt}, ctx);
1324
+ pending.groupSizes.push_back(
1325
+ Value{static_cast<double>(ev.treeStack_.back().size() - before)});
1326
+ }
1327
+ ++f.pc;
1328
+ break;
1329
+ }
1321
1330
  case Op::CsgGroupEnd: {
1322
1331
  PendingCsgWrap& pending = f.csgWrapStack.back();
1323
- ev.appendGroupSizes(pending.groupSizes, pending.groupStartSize);
1332
+ const size_t after = ev.treeStack_.back().size();
1333
+ pending.groupSizes.push_back(Value{static_cast<double>(after - pending.groupStartSize)});
1324
1334
  ++f.pc;
1325
1335
  break;
1326
1336
  }