openscad-cpp-evaluator 0.13.3__tar.gz → 0.13.4__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 (194) hide show
  1. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bytecode.hpp +46 -2
  3. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/evaluator.hpp +69 -5
  4. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/scope_trail.hpp +19 -1
  5. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/bytecode_compiler.cpp +19 -6
  7. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/bytecode_vm.cpp +108 -0
  8. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/user_calls.cpp +36 -12
  9. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_bytecode_compiler.cpp +82 -0
  10. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_scope_trail.cpp +30 -0
  11. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/CLAUDE.md +0 -0
  16. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/README.md +0 -0
  19. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/bindings/module.cpp +0 -0
  21. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/cmake/embed_font.cmake +0 -0
  22. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/examples/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/examples/eval_perf_benchmark.cpp +0 -0
  24. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/examples/manifold_cache_reuse.cpp +0 -0
  25. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/examples/minimal_debugger.cpp +0 -0
  26. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  27. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/.gitignore +0 -0
  28. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  29. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/README.md +0 -0
  31. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  32. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  33. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  34. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  35. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  36. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  37. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  38. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  39. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  40. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  41. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  42. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  43. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  44. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  46. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  47. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/api.cpp +0 -0
  48. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  49. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  50. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  51. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  52. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  53. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  54. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  55. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  56. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  57. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  58. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  59. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  60. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  61. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  62. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  63. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  64. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/position.cpp +0 -0
  65. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  66. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  67. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  69. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  70. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  71. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  72. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  73. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  74. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  75. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  76. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  78. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  79. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  80. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  81. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  82. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  83. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  84. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  85. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  86. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  87. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  88. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  89. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  90. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  91. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  92. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  94. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  95. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  96. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  97. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  98. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  99. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  100. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  101. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  102. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  103. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  104. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  105. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  106. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/export.hpp +0 -0
  107. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  108. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  109. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  110. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  111. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  112. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  113. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  114. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  115. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  117. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  118. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/value.hpp +0 -0
  119. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  120. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/python/openscad_cpp_evaluator/__init__.py +0 -0
  121. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/resources/fonts/LICENSE +0 -0
  122. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  123. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/CMakeLists.txt +0 -0
  124. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/booleans.cpp +0 -0
  125. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/builtins.hpp +0 -0
  126. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/call_args.cpp +0 -0
  127. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/color.cpp +0 -0
  128. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/control.cpp +0 -0
  129. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/extrude.cpp +0 -0
  130. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/function_builtins.cpp +0 -0
  131. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/import.cpp +0 -0
  132. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/modifiers.cpp +0 -0
  133. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/primitives_2d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/primitives_3d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/registry.cpp +0 -0
  136. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/roof.cpp +0 -0
  137. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/segments.cpp +0 -0
  138. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/surface.cpp +0 -0
  139. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/text.cpp +0 -0
  140. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/topology.cpp +0 -0
  141. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/builtins/transforms.cpp +0 -0
  142. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/colored_body.cpp +0 -0
  143. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/csg_generate.cpp +0 -0
  144. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/csg_resolve.cpp +0 -0
  145. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/css_colors.cpp +0 -0
  146. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/debug/debug_repl.cpp +0 -0
  147. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/debug_profile.cpp +0 -0
  148. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/eval_context.cpp +0 -0
  149. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/eval_error.cpp +0 -0
  150. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/eval_use.cpp +0 -0
  151. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/export.cpp +0 -0
  152. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/expr_eval.cpp +0 -0
  153. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/import/dxf_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/import/mesh_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/import/surface_load.cpp +0 -0
  156. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/import/svg_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/manifold_cache.cpp +0 -0
  158. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/stmt_eval.cpp +0 -0
  159. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/text/stb_font_provider.cpp +0 -0
  160. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/text/text_metrics.cpp +0 -0
  161. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_control_flow.cpp +0 -0
  167. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_csg_tree.cpp +0 -0
  168. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_debug_hooks.cpp +0 -0
  169. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_dxf_svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_export.cpp +0 -0
  172. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_extrude_roof.cpp +0 -0
  174. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_function_builtins.cpp +0 -0
  175. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_helpers.hpp +0 -0
  176. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_import_export.cpp +0 -0
  177. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_multi_color_merge.cpp +0 -0
  179. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_primitives.cpp +0 -0
  180. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_profiling.cpp +0 -0
  181. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_python_bindings.py +0 -0
  182. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.13.3 → openscad_cpp_evaluator-0.13.4}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.13.3
3
+ Version: 0.13.4
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -325,8 +325,50 @@ enum class Op {
325
325
  // same seam, now a counter).
326
326
  PopBuiltinWrap,
327
327
 
328
+ // A `children()` / `children(N)` statement -- the runtime-varying
329
+ // sibling of Op::CallModule/Op::PushBuiltinWrap, closing the LAST
330
+ // dominant native-reentry source (BOSL2's attachable() calls
331
+ // children() at nearly every wrapper level; measured 85 of 93 native
332
+ // reentries in a real script). Unlike PushBuiltinWrap's constructs,
333
+ // the forwarded children aren't known at compile time (they're the
334
+ // CALLER's own call-site statements, carried on ctx.childrenNodes/
335
+ // childrenCallerCtx -- see Evaluator::buildModuleChildCtx), so they
336
+ // can't compile inline; instead the handler resolves the list at
337
+ // RUNTIME, looks up/compiles its chunk (the same
338
+ // childrenListChunkCache_ tryRunCompiledChildren uses, via
339
+ // lookupOrCompileChildrenListChunk -- gated on useBytecodeVm() &&
340
+ // inResolvePass_, the cache is pass-scoped), and pushes it onto
341
+ // vmCallStack_ directly, mirroring Op::CallModule's own zero-native-
342
+ // call push -- but with a THIRD frame shape: splice-owning like a
343
+ // module frame (ownsModuleSplice=true, mirroring evalModularCall's
344
+ // own unconditional splice branch for "children"), yet bracketless
345
+ // like a bare frame (children() never gets a callStack_/profiling
346
+ // entry natively either -- only enterUserCall pushes those, and
347
+ // resolveChildren/builtinChildren never call it). driveVm's existing
348
+ // completion branch and teardownVmCallStackDownTo both already
349
+ // handle that combination (their bracket and splice concerns are
350
+ // independent).
351
+ //
352
+ // Handler ordering is load-bearing: checkDebug fires in-handler
353
+ // against the scope-wrapped ctx (byte-for-byte what Op::
354
+ // NativeStatement does -- NOT via an emitted Op::CheckDebugStatement,
355
+ // whose handler passes the un-wrapped ctx); randsBefore is captured
356
+ // BEFORE argument resolution (rands-in-args taint, same lesson
357
+ // PushBuiltinWrap already encodes); and treeStack_ is pushed LAST,
358
+ // immediately before the frame push / native fallback (an early-out
359
+ // or a throw during arg resolution then has nothing to clean up --
360
+ // the native path's own catch{pop;throw} has no equivalent here, so
361
+ // the reorder IS the exception-safety mechanism). The not-eligible
362
+ // fallback reuses the ALREADY-resolved args (never evalStatement,
363
+ // which would re-resolve them: double rands(), double side effects)
364
+ // by inlining evalModularCall's own children branch around a native
365
+ // evalChildren call. a = index into CompiledChunk::nativeStatements
366
+ // (the ModularCall node -- arguments, error position, splice node;
367
+ // no separate site table needed).
368
+ CallChildren,
369
+
328
370
  // A single "native passthrough" statement -- intersection_for,
329
- // children(), union/difference/intersection and every other builtin
371
+ // union/difference/intersection and every other builtin
330
372
  // module call NOT covered by Op::PushBuiltinWrap (see that op's own
331
373
  // doc comment for exactly which builtins ARE covered), the `*`
332
374
  // modifier's own no-op case, or a user-module call that didn't
@@ -344,7 +386,9 @@ enum class Op {
344
386
  // pattern that made this op's own native reentry a genuine Windows
345
387
  // crash risk in practice, not just a missed optimization. See
346
388
  // Op::PushBuiltinWrap's own doc comment for the real story and the
347
- // fix.)
389
+ // fix. children() fell here too, and was the LAST and largest such
390
+ // reentry source once PushBuiltinWrap's own set was covered -- it
391
+ // now has Op::CallChildren, above.)
348
392
  // a = index into CompiledChunk::nativeStatements. Runtime just does
349
393
  // what Evaluator::evalChildren's own per-statement loop already does
350
394
  // for one node: derive childCtx via ctx.withScope(...), checkDebug,
@@ -271,6 +271,25 @@ public:
271
271
  // _builtin_children/_eval_children_lazy.
272
272
  void builtinChildren(const CallArgs& args, EvalContext& ctx);
273
273
 
274
+ // The "which nodes, evaluated against what context" half of
275
+ // builtinChildren, shared with Op::CallChildren's runtime handler
276
+ // (bytecode_vm.cpp) so the native and compiled paths cannot drift on
277
+ // any of the subtle parts: the caller-ctx re-derivation, the
278
+ // $-forwarding loop (which must read the post-resolveCallArgs effCtx
279
+ // -- a children($fn=12) named-$ override lives only there), and the
280
+ // children(N) statement-index filtering/bounds-check. nullopt means
281
+ // "nothing to evaluate" (no forwarded children in scope, empty list,
282
+ // or index out of range) -- a silent no-op in every existing caller,
283
+ // exactly matching builtinChildren's own early returns. `ctx` must be
284
+ // the effCtx resolveCallArgs returned, same as builtinChildren's own
285
+ // parameter today. Public for the same free-function reasoning as
286
+ // builtinChildren itself.
287
+ struct ChildrenForward {
288
+ EvalContext evalCtx;
289
+ std::vector<const oscad::ASTNode*> nodes;
290
+ };
291
+ std::optional<ChildrenForward> prepareChildrenForward(const CallArgs& args, EvalContext& ctx);
292
+
274
293
  // "WARNING: {message}{locSuffix(position)}" via echoFn_, no-op if unset.
275
294
  // Public: builtins/import.cpp's not-manifold warning is emitted from a
276
295
  // free function, same reasoning as tagGenerated()/builtinChildren().
@@ -532,6 +551,17 @@ private:
532
551
  // into the caller's scope exactly the way the native per-statement
533
552
  // loop's own writes already do.
534
553
  bool tryRunCompiledChildren(const std::vector<const oscad::ASTNode*>& children, EvalContext& ctx);
554
+ public:
555
+ // The cache-lookup half of tryRunCompiledChildren, shared with
556
+ // Op::CallChildren's runtime handler (bytecode_vm.cpp, a free
557
+ // function -- public for the same no-friend-declaration reasoning as
558
+ // vmCallStack_/treeStack_). Returns the eligible compiled chunk for
559
+ // `children` or nullptr. Caller owns the useBytecodeVm()/
560
+ // inResolvePass_ gate -- the pass gate is load-bearing
561
+ // (childrenListChunkCache_ is pass-scoped, see its own doc comment),
562
+ // not defensive.
563
+ const CompiledChunk* lookupOrCompileChildrenListChunk(const std::vector<const oscad::ASTNode*>& children);
564
+ private:
535
565
  void evalModularCall(const oscad::ModularCall& node, EvalContext& ctx);
536
566
  void evalFor(const oscad::ModularFor& node, EvalContext& ctx);
537
567
  void evalLetBlock(const oscad::ModularLet& node, EvalContext& ctx);
@@ -951,15 +981,38 @@ private:
951
981
  // body -- so a resolvable module call anywhere in that list gets
952
982
  // Op::CallModule bytecode, and any if/for control flow around it gets
953
983
  // real Jump-based bytecode too, instead of falling through to the
954
- // native per-statement loop. Keyed by the list's own FIRST element,
955
- // same convention as assignBlockChunkCache_ (stable and unique per
956
- // list: a given evalChildren() call site is always handed the same
957
- // leading element across repeated evaluations). Same nullopt-means-
984
+ // native per-statement loop.
985
+ //
986
+ // Keyed by (FIRST element, list SIZE) -- NOT the first element alone,
987
+ // the original convention borrowed from assignBlockChunkCache_
988
+ // (where it IS sufficient: a leading-assignment run's first member
989
+ // uniquely determines the run). Here it wasn't: children()'s
990
+ // forwarding produces two DIFFERENT lists sharing a first element --
991
+ // bare `children()` forwards the caller's whole list, `children(0)`
992
+ // a single-element slice of it -- and keying by front alone made
993
+ // whichever form ran first poison the cache for the other (caught
994
+ // for real: `module m() { children(); children(0); }\n
995
+ // m() { cube(); sphere(); }` made children(0) emit BOTH shapes,
996
+ // or bare children() emit only one, depending on statement order).
997
+ // (front, size) fully disambiguates every real producer: fixed AST
998
+ // lists have unique fronts per call site, and the only same-front
999
+ // pair (bare vs indexed forwarding) always differs in size except
1000
+ // when the lists are literally identical anyway. Same nullopt-means-
958
1001
  // tried-and-failed convention, same dangling-pointer hazard, same
959
1002
  // two-part fix (cleared alongside stmtExprChunkCache_ at the top of
960
1003
  // every resolveTreeImpl call, only read/written while inResolvePass_)
961
1004
  // as stmtExprChunkCache_ itself.
962
- std::unordered_map<const oscad::ASTNode*, std::optional<CompiledChunk>> childrenListChunkCache_;
1005
+ struct ChildrenListKeyHash {
1006
+ size_t operator()(const std::pair<const oscad::ASTNode*, size_t>& k) const {
1007
+ // Standard hash-combine (boost-style golden-ratio mix) --
1008
+ // either half alone collides by construction here.
1009
+ const size_t h1 = std::hash<const oscad::ASTNode*>{}(k.first);
1010
+ const size_t h2 = std::hash<size_t>{}(k.second);
1011
+ return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
1012
+ }
1013
+ };
1014
+ std::unordered_map<std::pair<const oscad::ASTNode*, size_t>, std::optional<CompiledChunk>, ChildrenListKeyHash>
1015
+ childrenListChunkCache_;
963
1016
 
964
1017
  // See stmtExprChunkCache_'s own doc comment, immediately above, for why
965
1018
  // this exists. Not a reentrancy guard (resolveTreeImpl is never called
@@ -1115,6 +1168,17 @@ public:
1115
1168
  return bytecodeVmEnabled() && (!debugHooks_.debugHook || fastContinueBreakpoints_.has_value());
1116
1169
  }
1117
1170
 
1171
+ // Read accessor for inResolvePass_ (private, below) -- Op::CallChildren's
1172
+ // runtime handler (bytecode_vm.cpp, a free function) must gate its
1173
+ // childrenListChunkCache_ access on it, exactly like
1174
+ // tryRunCompiledChildren's own self-gate: the cache is pass-scoped
1175
+ // (cleared per resolveTreeImpl, AST-address-reuse hazard -- see
1176
+ // stmtExprChunkCache_'s own doc comment), and driveVm CAN run outside
1177
+ // the resolve pass (a host calling evalChildren directly reaches
1178
+ // module opcodes via lookupOrCompileModuleChunk, which is NOT
1179
+ // pass-scoped).
1180
+ bool inResolvePass() const { return inResolvePass_; }
1181
+
1118
1182
  private:
1119
1183
  // The fine-grained half of the check above: even when useBytecodeVm()
1120
1184
  // says compiling/using bytecode is on the table at all, a SPECIFIC
@@ -368,12 +368,30 @@ public:
368
368
  dirty_[level].push_back(id);
369
369
  }
370
370
 
371
+ // Level-aware, NOT a blind pop_back() -- the exact fix
372
+ // ScopeTrailStorage::popLevel (above) already carries, for the exact
373
+ // out-of-order-pop bug class its own doc comment describes; this
374
+ // indexed twin never got the same fix because nothing violated LIFO
375
+ // view destruction on the dyn trail until Op::CallChildren's
376
+ // forwarding frame (bytecode_vm.cpp): its evalCtx (a dyn level opened
377
+ // AFTER the call's own effCtx level) is moved into a VmFrame and
378
+ // OUTLIVES effCtx, so effCtx's pop runs while evalCtx's later entry
379
+ // is still physically on top of the same name's stack -- the blind
380
+ // pop_back() silently removed the still-live forwarded value instead
381
+ // of the one actually being popped (caught for real: a
382
+ // children($fn=9) named-$ override read back as the root default 0
383
+ // inside the forwarded child).
371
384
  void popLevel(int level) {
372
385
  auto it = dirty_.find(level);
373
386
  if (it != dirty_.end()) {
374
387
  for (int id : it->second) {
375
388
  auto& stack = stacks_[static_cast<size_t>(id)];
376
- if (!stack.empty()) stack.pop_back();
389
+ for (auto rit = stack.rbegin(); rit != stack.rend(); ++rit) {
390
+ if (rit->level == level) {
391
+ stack.erase(std::next(rit).base());
392
+ break;
393
+ }
394
+ }
377
395
  }
378
396
  dirty_.erase(it);
379
397
  }
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.13.3"
7
+ version = "0.13.4"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -1420,12 +1420,25 @@ public:
1420
1420
  emitBuiltinWrap(*wrapKind, call.name->name, call, children, out);
1421
1421
  return;
1422
1422
  }
1423
- // Every other builtin, children(), or a name that didn't
1424
- // resolve to a user module statically -- native
1425
- // passthrough, exactly like echo/assert/etc. below. Never
1426
- // the recursion-depth risk this compiler targets for
1427
- // THESE (see NativeStatement's own doc comment,
1428
- // bytecode.hpp).
1423
+ // children() -- the runtime-varying forwarding builtin,
1424
+ // detected by the same function-pointer-identity pattern
1425
+ // as Transform/Color above. Deliberately NO preceding
1426
+ // Op::CheckDebugStatement (unlike emitBuiltinWrap's own
1427
+ // emission): CallChildren's handler fires checkDebug
1428
+ // itself against the SCOPE-WRAPPED ctx, byte-for-byte
1429
+ // matching Op::NativeStatement's own handler -- the
1430
+ // CheckDebugStatement handler passes the un-wrapped ctx,
1431
+ // which would be a subtle behavior change for this node.
1432
+ // See Op::CallChildren's own doc comment (bytecode.hpp).
1433
+ if (dispatchIt != dispatch.end() && dispatchIt->second == &resolveChildren) {
1434
+ out.push_back({Op::CallChildren, internNativeStatement(&stmt), 0, &stmt.position()});
1435
+ return;
1436
+ }
1437
+ // Every other builtin, or a name that didn't resolve to a
1438
+ // user module statically -- native passthrough, exactly
1439
+ // like echo/assert/etc. below. Never the recursion-depth
1440
+ // risk this compiler targets for THESE (see
1441
+ // NativeStatement's own doc comment, bytecode.hpp).
1429
1442
  out.push_back({Op::NativeStatement, internNativeStatement(&stmt), 0, &stmt.position()});
1430
1443
  return;
1431
1444
  }
@@ -329,6 +329,49 @@ void pushBracketedModuleFrame(Evaluator& ev, const CompiledChunk& chunk, const o
329
329
  ev.vmCallBrackets_.push_back(std::move(handle));
330
330
  }
331
331
 
332
+ // Op::CallChildren's own push -- a THIRD frame shape, distinct from both
333
+ // existing helpers: splice-owning like pushBracketedModuleFrame's
334
+ // (ownsModuleSplice=true, mirroring evalModularCall's own unconditional
335
+ // splice branch for "children", csg_resolve.cpp) but BRACKETLESS like
336
+ // pushBareFrame's (nullopt in vmCallBrackets_ -- children() never gets a
337
+ // callStack_/profiling entry natively either: only enterUserCall pushes
338
+ // those, and resolveChildren/builtinChildren never call it, so no TRACE
339
+ // frame, no profile site, no $parent_modules bump, exactly matching the
340
+ // native path). driveVm's completion branch and teardownVmCallStackDownTo
341
+ // both already handle this combination -- their bracket (`if (bracket)`)
342
+ // and splice (`if (ownsModuleSplice)`) concerns are independent at both
343
+ // sites. `evalCtx` is prepareChildrenForward's result (the caller-derived,
344
+ // $-forwarded context the children must run under -- see that helper's
345
+ // own doc comment, evaluator.hpp). hopEligible set explicitly false --
346
+ // pushBracketedModuleFrame itself omits the reset (benign there only
347
+ // because module chunks never contain tail-call opcodes); don't inherit
348
+ // a pooled function frame's stale true here either.
349
+ void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalContext evalCtx,
350
+ std::uint64_t randsBefore, const oscad::ASTNode& callNode) {
351
+ if (ev.vmCallStack_.size() >= Evaluator::kMaxVmCallStackDepth) {
352
+ ev.error("Recursion too deep while forwarding children()", callNode);
353
+ }
354
+ auto frame = ev.acquireVmFrame();
355
+ frame->chunk = &chunk;
356
+ frame->code = &chunk.bodyCode;
357
+ frame->pc = 0;
358
+ frame->slots.assign(static_cast<size_t>(chunk.numSlots), Value{});
359
+ frame->stack.clear();
360
+ frame->bound.clear();
361
+ frame->accumStack.clear();
362
+ frame->iterLists.assign(static_cast<size_t>(chunk.numIterLists), IterList{});
363
+ frame->ctxChain.clear();
364
+ frame->ctxChain.push_back(std::move(evalCtx));
365
+ frame->tailHopGuard = 0;
366
+ frame->logicalName.clear();
367
+ frame->hopEligible = false;
368
+ frame->ownsModuleSplice = true;
369
+ frame->moduleRandsBefore = randsBefore;
370
+ frame->moduleSpliceCallNode = &callNode;
371
+ ev.vmCallStack_.push_back(std::move(frame));
372
+ ev.vmCallBrackets_.emplace_back(std::nullopt);
373
+ }
374
+
332
375
  // Tears down every frame from vmCallStack_'s own top down to (but not
333
376
  // including) `floor`, on the exception path -- releases each VmFrame to
334
377
  // the pool, tears down its ctxChain back-to-front (never relying on
@@ -933,6 +976,71 @@ Value driveVm(Evaluator& ev, size_t floor) {
933
976
  }
934
977
  break;
935
978
  }
979
+ case Op::CallChildren: {
980
+ // Ordering is load-bearing throughout -- see this op's
981
+ // own doc comment (bytecode.hpp): checkDebug against
982
+ // the SCOPE-WRAPPED ctx (byte-for-byte what Op::
983
+ // NativeStatement does for this same node today);
984
+ // randsBefore BEFORE argument resolution (rands-in-args
985
+ // taint); treeStack_ pushed LAST, immediately before
986
+ // the frame push / native fallback, so an early-out or
987
+ // a throw during arg resolution has nothing to clean
988
+ // up (the native path's own catch{pop;throw} has no
989
+ // equivalent out here -- this reorder IS the
990
+ // exception-safety mechanism; arg resolution never
991
+ // appends CSG nodes, so pushing after it is
992
+ // unobservable).
993
+ const auto* callNode =
994
+ static_cast<const oscad::ModularCall*>(f.chunk->nativeStatements[static_cast<size_t>(ins.a)]);
995
+ EvalContext scopedCtx = ctx.withScope(callNode->scope() ? callNode->scope() : ctx.scope);
996
+ ev.checkDebug(*callNode, scopedCtx);
997
+ const std::uint64_t randsBefore = ev.randsCallCount();
998
+ auto [args, effCtx] = resolveCallArgs(ev, callNode->arguments, scopedCtx);
999
+ std::optional<Evaluator::ChildrenForward> fwd = ev.prepareChildrenForward(args, effCtx);
1000
+ if (!fwd) {
1001
+ // No forwarded children in scope / empty / index
1002
+ // out of range -- a silent no-op, exactly matching
1003
+ // builtinChildren's own early returns.
1004
+ ++f.pc;
1005
+ break;
1006
+ }
1007
+ // Pass gate is load-bearing, not defensive -- the
1008
+ // chunk cache is pass-scoped; see inResolvePass()'s
1009
+ // own doc comment (evaluator.hpp).
1010
+ const CompiledChunk* chunk = (ev.useBytecodeVm() && ev.inResolvePass())
1011
+ ? ev.lookupOrCompileChildrenListChunk(fwd->nodes)
1012
+ : nullptr;
1013
+ if (chunk) {
1014
+ ev.treeStack_.emplace_back();
1015
+ pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode);
1016
+ // f.pc deliberately NOT advanced -- resumes when
1017
+ // the pushed frame completes; driveVm's completion
1018
+ // branch runs the splice (isModule &&
1019
+ // ownsModuleSplice), mirroring evalModularCall's
1020
+ // own "children" splice branch exactly.
1021
+ } else {
1022
+ // Fallback reuses the ALREADY-resolved args --
1023
+ // never evalStatement, which would re-resolve them
1024
+ // (double rands(), double echo/assert side
1025
+ // effects, double expr-level debug stops).
1026
+ // Inlines evalModularCall's own children branch:
1027
+ // push accumulator, run natively, pop, splice.
1028
+ // The native evalChildren still self-gates and
1029
+ // retries tryRunCompiledChildren internally.
1030
+ ev.treeStack_.emplace_back();
1031
+ try {
1032
+ ev.evalChildren(fwd->nodes, fwd->evalCtx);
1033
+ } catch (...) {
1034
+ ev.treeStack_.pop_back();
1035
+ throw;
1036
+ }
1037
+ std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
1038
+ ev.treeStack_.pop_back();
1039
+ ev.spliceModuleChildren(std::move(children), randsBefore, *callNode);
1040
+ ++f.pc;
1041
+ }
1042
+ break;
1043
+ }
936
1044
  case Op::PushBuiltinWrap: {
937
1045
  const CompiledChunk::BuiltinWrapSite& site = f.chunk->builtinWrapSites[static_cast<size_t>(ins.a)];
938
1046
  // Captured BEFORE any argument resolution -- mirrors
@@ -84,15 +84,29 @@ bool Evaluator::tryRunCompiledAssignmentBlock(const std::vector<const oscad::AST
84
84
 
85
85
  bool Evaluator::tryRunCompiledChildren(const std::vector<const oscad::ASTNode*>& children, EvalContext& ctx) {
86
86
  if (children.empty() || !useBytecodeVm() || !inResolvePass_) return false;
87
+ const CompiledChunk* chunk = lookupOrCompileChildrenListChunk(children);
88
+ if (!chunk) return false;
89
+ runCompiledModuleBody(*this, *chunk, ctx);
90
+ return true;
91
+ }
92
+
93
+ // The cache-lookup half of tryRunCompiledChildren, shared with
94
+ // Op::CallChildren's own runtime handler (bytecode_vm.cpp) -- both need
95
+ // exactly this "list -> eligible chunk or nullptr" step, differing only
96
+ // in how they then RUN the chunk (native runCompiledModuleBody reentry
97
+ // here, a direct vmCallStack_ push there). Caller is responsible for the
98
+ // useBytecodeVm()/inResolvePass_ gate (see childrenListChunkCache_'s own
99
+ // doc comment for why the pass gate is load-bearing, not defensive).
100
+ const CompiledChunk* Evaluator::lookupOrCompileChildrenListChunk(const std::vector<const oscad::ASTNode*>& children) {
87
101
  const oscad::ASTNode* first = children.front();
88
- auto it = childrenListChunkCache_.find(first);
102
+ const auto key = std::make_pair(first, children.size());
103
+ auto it = childrenListChunkCache_.find(key);
89
104
  if (it == childrenListChunkCache_.end()) {
90
- it = childrenListChunkCache_.emplace(first, tryCompileChildrenList(children, first->scope())).first;
105
+ it = childrenListChunkCache_.emplace(key, tryCompileChildrenList(children, first->scope())).first;
91
106
  if (it->second) flattenNestedLiterals(*it->second);
92
107
  }
93
- if (!it->second || !chunkEligibleNow(*it->second)) return false;
94
- runCompiledModuleBody(*this, *it->second, ctx);
95
- return true;
108
+ if (!it->second || !chunkEligibleNow(*it->second)) return nullptr;
109
+ return &*it->second;
96
110
  }
97
111
 
98
112
  const Value* Evaluator::findUpvalue(const oscad::ASTNode* targetDecl, int slot) const {
@@ -897,15 +911,20 @@ Value Evaluator::parentModuleName(int idx) const {
897
911
  return Value{modules[static_cast<size_t>(revIdx)]};
898
912
  }
899
913
 
900
- void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
914
+ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(const CallArgs& args, EvalContext& ctx) {
901
915
  Value idxArg = getArg(args, 0, "index", Value{});
902
- if (!ctx.childrenNodes || ctx.childrenNodes->empty()) return;
916
+ if (!ctx.childrenNodes || ctx.childrenNodes->empty()) return std::nullopt;
903
917
  const EvalContext* callerCtx = ctx.childrenCallerCtx;
904
- if (!callerCtx) return;
918
+ if (!callerCtx) return std::nullopt;
905
919
 
906
920
  // A children() forwarding chain's own dyn/let_/etc. must alias the
907
921
  // *caller's* (not this ctx's) -- see EvalContext::withScope's rationale.
908
922
  EvalContext evalCtx = callerCtx->childCtx(nullptr, std::nullopt, callerCtx->childrenNodes, callerCtx->childrenCallerCtx);
923
+ // `ctx` here is the post-resolveCallArgs effCtx, deliberately: a
924
+ // `children($fn=12)`-style named-$ override lives only at effCtx's own
925
+ // trail level, and TrailView::items() is ancestry-visible, so reading
926
+ // from effCtx forwards both the call's own overrides AND everything
927
+ // the wrapper module's body set (`$fn = 100; children();`).
909
928
  for (const auto& [k, v] : ctx.dyn->items()) {
910
929
  if (!k.empty() && k[0] == '$') evalCtx.dyn->set(k, v);
911
930
  }
@@ -914,8 +933,7 @@ void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
914
933
  }
915
934
 
916
935
  if (std::holds_alternative<std::monostate>(idxArg)) {
917
- evalChildren(*ctx.childrenNodes, evalCtx);
918
- return;
936
+ return ChildrenForward{std::move(evalCtx), *ctx.childrenNodes};
919
937
  }
920
938
 
921
939
  // children(N) indexes child *statements*, not output bodies -- a
@@ -930,8 +948,14 @@ void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
930
948
  geoNodes.push_back(c);
931
949
  }
932
950
  }
933
- if (idx < 0 || static_cast<size_t>(idx) >= geoNodes.size()) return;
934
- evalChildren(std::vector<const oscad::ASTNode*>{geoNodes[static_cast<size_t>(idx)]}, evalCtx);
951
+ if (idx < 0 || static_cast<size_t>(idx) >= geoNodes.size()) return std::nullopt;
952
+ return ChildrenForward{std::move(evalCtx), {geoNodes[static_cast<size_t>(idx)]}};
953
+ }
954
+
955
+ void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
956
+ std::optional<ChildrenForward> fwd = prepareChildrenForward(args, ctx);
957
+ if (!fwd) return;
958
+ evalChildren(fwd->nodes, fwd->evalCtx);
935
959
  }
936
960
 
937
961
  } // namespace oscadeval
@@ -1299,6 +1299,35 @@ TEST(ModuleBodyCompiles, ReassignmentInsideForLoopBodyDoesNotSpuriouslyWarn) {
1299
1299
  for (const std::string& w : warnings) EXPECT_EQ(w.find("overwritten"), std::string::npos) << w;
1300
1300
  }
1301
1301
 
1302
+ // Regression test for a real cache-collision bug: childrenListChunkCache_
1303
+ // was keyed by the list's FIRST element alone (a convention borrowed from
1304
+ // assignBlockChunkCache_, where it IS sufficient) -- but children()'s
1305
+ // forwarding produces two DIFFERENT lists sharing a first element: bare
1306
+ // `children()` forwards the caller's whole list, `children(0)` a single-
1307
+ // element slice of it. Whichever form ran first poisoned the cache for
1308
+ // the other: here, bare children() cached a two-statement chunk keyed by
1309
+ // &cubeStmt, then children(0)'s single-element {&cubeStmt} lookup HIT
1310
+ // that key and emitted BOTH shapes. Fixed by keying on (front, size).
1311
+ TEST(ModuleBodyCompiles, BareAndIndexedChildrenForwardingDoNotShareACachedChunk) {
1312
+ ScopedVm vm(true);
1313
+ // m() emits: children() -> cube+sphere, then children(0) -> cube only.
1314
+ // 3 bodies total; the collision bug produced 4 (children(0) emitting
1315
+ // sphere too).
1316
+ Evaluated e = evalSrc("module m() { children(); children(0); }\n"
1317
+ "m() { cube(1); sphere(r=1, $fn=8); }");
1318
+ EXPECT_EQ(e.bodies.size(), 3u);
1319
+ }
1320
+
1321
+ // Same collision, opposite statement order -- children(0) caching its
1322
+ // single-element chunk first used to TRUNCATE the later bare children()
1323
+ // (2 bodies where 3 belong).
1324
+ TEST(ModuleBodyCompiles, IndexedThenBareChildrenForwardingDoNotShareACachedChunk) {
1325
+ ScopedVm vm(true);
1326
+ Evaluated e = evalSrc("module m() { children(0); children(); }\n"
1327
+ "m() { cube(1); sphere(r=1, $fn=8); }");
1328
+ EXPECT_EQ(e.bodies.size(), 3u);
1329
+ }
1330
+
1302
1331
  TEST(ModuleBodyCompiles, RecursiveModuleWithIfSucceedsWellPastTheOldNativeLimitCompiled) {
1303
1332
  ScopedVm vm(true);
1304
1333
  Evaluated e = evalSrc("module recur(n) { if (n > 0) { recur(n - 1); } else { cube(1); } }\nrecur(10000);");
@@ -1390,6 +1419,59 @@ TEST(ModuleBodyCompiles, UnionWrappedRecursionStillHitsTheNativeReentryGuardCont
1390
1419
  }
1391
1420
  }
1392
1421
 
1422
+ // The BOSL2 attachable() shape this whole effort targets: a wrapper module
1423
+ // whose body is just `children();`, applied at every level of a recursive
1424
+ // chain. children() used to fall to Op::NativeStatement -- one genuine
1425
+ // native C++ reentry (evalStatement -> evalModularCall -> builtinChildren
1426
+ // -> evalChildren -> runCompiledModuleBody -> a fresh nested driveVm) per
1427
+ // level, measured as 85 of 93 native-reentry hits in a real BOSL2 script.
1428
+ // Op::CallChildren resolves the forwarded list at runtime and pushes its
1429
+ // chunk onto vmCallStack_ directly (zero native frames), so this now
1430
+ // SUCCEEDS well past the old ~40-level Windows-safe ceiling. Depth 1500:
1431
+ // same figure the PushBuiltinWrap per-construct tests use -- comfortably
1432
+ // past the old ceiling, and tree depth stays flat regardless (children()
1433
+ // and user-module calls both splice, no kMaxCsgTreeDepth interaction).
1434
+ TEST(ModuleBodyCompiles, RecursiveChildrenForwardingChainSucceedsWellPastTheOldNativeReentryLimit) {
1435
+ ScopedVm vm(true);
1436
+ Evaluated e = evalSrc("module wrap() { children(); }\n"
1437
+ "module recur(n) { if (n > 0) { wrap() recur(n - 1); } else { cube(1); } }\n"
1438
+ "recur(1500);");
1439
+ ASSERT_EQ(e.bodies.size(), 1u);
1440
+ }
1441
+
1442
+ // Same chain through the INDEXED form -- children(0) shares Op::
1443
+ // CallChildren with the bare form (the index is resolved at runtime by
1444
+ // the same prepareChildrenForward helper the native path uses), so it
1445
+ // gets the same zero-native-reentry treatment, not just bare children().
1446
+ TEST(ModuleBodyCompiles, RecursiveIndexedChildrenForwardingChainSucceedsWellPastTheOldNativeReentryLimit) {
1447
+ ScopedVm vm(true);
1448
+ Evaluated e = evalSrc("module wrap() { children(0); }\n"
1449
+ "module recur(n) { if (n > 0) { wrap() recur(n - 1); } else { cube(1); } }\n"
1450
+ "recur(1500);");
1451
+ ASSERT_EQ(e.bodies.size(), 1u);
1452
+ }
1453
+
1454
+ // $-forwarding parity for the compiled children() path: a wrapper module's
1455
+ // own $-writes (`$fn = 7; children();`) and a children($fn=9)-style
1456
+ // named-$ override must both reach the forwarded child, exactly as the
1457
+ // native builtinChildren path forwards them (prepareChildrenForward's own
1458
+ // $-loop, shared by both paths precisely so they can't drift -- these
1459
+ // tests are the proof it actually holds end-to-end through the compiled
1460
+ // opcode, not just by construction).
1461
+ TEST(ModuleBodyCompiles, ChildrenForwardingCarriesWrapperDollarWritesCompiled) {
1462
+ ScopedVm vm(true);
1463
+ EXPECT_EQ(runCapturingEcho("module w() { $fn = 7; children(); }\n"
1464
+ "w() echo($fn);"),
1465
+ "ECHO: 7");
1466
+ }
1467
+
1468
+ TEST(ModuleBodyCompiles, ChildrenForwardingCarriesNamedDollarArgCompiled) {
1469
+ ScopedVm vm(true);
1470
+ EXPECT_EQ(runCapturingEcho("module w() { children($fn = 9); }\n"
1471
+ "w() echo($fn);"),
1472
+ "ECHO: 9");
1473
+ }
1474
+
1393
1475
  // Regression test for a real bug this same investigation caught:
1394
1476
  // enterUserCall's own depth guard used to check callStack_.size()
1395
1477
  // directly -- but callStack_ also grows from cheap, zero-native-cost
@@ -75,6 +75,36 @@ TEST(IndexedScopeTrailStorage, SetAtSameLevelDoesNotAccumulateEntries) {
75
75
  EXPECT_EQ(*storage.lookup("$fn", level), 999);
76
76
  }
77
77
 
78
+ // popLevel must remove the entry belonging to THE LEVEL BEING POPPED, not
79
+ // blindly pop_back() whatever happens to be physically last -- the exact
80
+ // out-of-order-pop bug class ScopeTrailStorage::popLevel's own doc comment
81
+ // already describes (and fixed) for the non-indexed twin; this indexed
82
+ // variant kept the blind pop until Op::CallChildren's forwarding frame
83
+ // (bytecode_vm.cpp) became the first real caller to violate LIFO view
84
+ // destruction on the dyn trail. Level shape mirrors that real scenario
85
+ // exactly: E (a children() call's own effCtx, opened first) and L (the
86
+ // forwarding evalCtx, opened later, moved into a VmFrame) both set the
87
+ // same name; E pops FIRST while L is still live. The blind pop removed
88
+ // L's entry -- the still-live forwarded value -- leaving a lookup from L
89
+ // falling through to the root default (caught for real: children($fn=9)
90
+ // read back as 0 inside the forwarded child).
91
+ TEST(IndexedScopeTrailStorage, PopLevelRemovesItsOwnEntryNotThePhysicallyLastOne) {
92
+ auto intern = std::make_shared<DynNameIntern>();
93
+ IndexedScopeTrailStorage<int> storage(intern);
94
+ const int root = storage.openLevel(0);
95
+ storage.set("$fn", 0, root);
96
+ const int e = storage.openLevel(root); // effCtx's own level
97
+ storage.set("$fn", 9, e);
98
+ const int l = storage.openLevel(root); // forwarding evalCtx's level, opened AFTER e
99
+ storage.set("$fn", 9, l);
100
+ storage.popLevel(e); // e dies while l is still live -- non-LIFO
101
+ ASSERT_NE(storage.lookup("$fn", l), nullptr);
102
+ EXPECT_EQ(*storage.lookup("$fn", l), 9);
103
+ storage.popLevel(l);
104
+ ASSERT_NE(storage.lookup("$fn", root), nullptr);
105
+ EXPECT_EQ(*storage.lookup("$fn", root), 0);
106
+ }
107
+
78
108
  // A captured (closure) level's own ancestor must stay alive even after
79
109
  // EVERY other TrailView referencing that ancestor has gone out of scope --
80
110
  // see TrailView::openChild's own doc comment (parentView_) for why. This