openscad-cpp-evaluator 0.13.2__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.2 → openscad_cpp_evaluator-0.13.4}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bytecode.hpp +46 -2
  3. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/evaluator.hpp +106 -5
  4. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/scope_trail.hpp +19 -1
  5. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/bytecode_compiler.cpp +19 -6
  7. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/bytecode_vm.cpp +108 -0
  8. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/user_calls.cpp +48 -13
  9. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_bytecode_compiler.cpp +121 -0
  10. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_scope_trail.cpp +30 -0
  11. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/CLAUDE.md +0 -0
  16. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/README.md +0 -0
  19. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/bindings/module.cpp +0 -0
  21. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/cmake/embed_font.cmake +0 -0
  22. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/examples/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/examples/eval_perf_benchmark.cpp +0 -0
  24. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/examples/manifold_cache_reuse.cpp +0 -0
  25. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/examples/minimal_debugger.cpp +0 -0
  26. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  27. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/.gitignore +0 -0
  28. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  29. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/README.md +0 -0
  31. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  32. {openscad_cpp_evaluator-0.13.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  40. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  41. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  42. {openscad_cpp_evaluator-0.13.2 → 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.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  44. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-0.13.2 → 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.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  47. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/api.cpp +0 -0
  48. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  49. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  50. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  51. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  52. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  53. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  54. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  55. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  56. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  57. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  58. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  59. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  60. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  61. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  62. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  63. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  64. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/position.cpp +0 -0
  65. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  66. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  67. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  69. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  70. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  71. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  72. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  73. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  74. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  75. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  76. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  78. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  79. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  80. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  81. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  82. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  83. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  84. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  85. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  86. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  87. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  88. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  89. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  90. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  91. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  92. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  94. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  95. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  96. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  97. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  98. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  99. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  100. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  101. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  102. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  103. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  104. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  105. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  106. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/export.hpp +0 -0
  107. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  108. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  109. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  110. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  111. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  112. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  113. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  114. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  115. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  117. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  118. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/value.hpp +0 -0
  119. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  120. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/python/openscad_cpp_evaluator/__init__.py +0 -0
  121. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/resources/fonts/LICENSE +0 -0
  122. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  123. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/CMakeLists.txt +0 -0
  124. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/booleans.cpp +0 -0
  125. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/builtins.hpp +0 -0
  126. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/call_args.cpp +0 -0
  127. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/color.cpp +0 -0
  128. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/control.cpp +0 -0
  129. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/extrude.cpp +0 -0
  130. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/function_builtins.cpp +0 -0
  131. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/import.cpp +0 -0
  132. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/modifiers.cpp +0 -0
  133. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/primitives_2d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/primitives_3d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/registry.cpp +0 -0
  136. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/roof.cpp +0 -0
  137. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/segments.cpp +0 -0
  138. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/surface.cpp +0 -0
  139. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/text.cpp +0 -0
  140. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/topology.cpp +0 -0
  141. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/builtins/transforms.cpp +0 -0
  142. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/colored_body.cpp +0 -0
  143. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/csg_generate.cpp +0 -0
  144. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/csg_resolve.cpp +0 -0
  145. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/css_colors.cpp +0 -0
  146. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/debug/debug_repl.cpp +0 -0
  147. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/debug_profile.cpp +0 -0
  148. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/eval_context.cpp +0 -0
  149. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/eval_error.cpp +0 -0
  150. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/eval_use.cpp +0 -0
  151. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/export.cpp +0 -0
  152. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/expr_eval.cpp +0 -0
  153. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/import/dxf_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/import/mesh_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/import/surface_load.cpp +0 -0
  156. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/import/svg_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/manifold_cache.cpp +0 -0
  158. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/stmt_eval.cpp +0 -0
  159. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/text/stb_font_provider.cpp +0 -0
  160. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/text/text_metrics.cpp +0 -0
  161. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_control_flow.cpp +0 -0
  167. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_csg_tree.cpp +0 -0
  168. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_debug_hooks.cpp +0 -0
  169. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_dxf_svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_export.cpp +0 -0
  172. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_extrude_roof.cpp +0 -0
  174. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_function_builtins.cpp +0 -0
  175. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_helpers.hpp +0 -0
  176. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_import_export.cpp +0 -0
  177. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_multi_color_merge.cpp +0 -0
  179. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_primitives.cpp +0 -0
  180. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_profiling.cpp +0 -0
  181. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_python_bindings.py +0 -0
  182. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.13.2 → openscad_cpp_evaluator-0.13.4}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.13.2 → 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.2
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
@@ -1218,6 +1282,12 @@ public:
1218
1282
  // and callStack_.back() is already gone by the time it runs. See
1219
1283
  // activeDeclRefcount_'s own doc comment.
1220
1284
  const oscad::ASTNode* declNode = nullptr;
1285
+ // True only when THIS call incremented nativeUserCallDepth_ (i.e.
1286
+ // skipDepthGuard was false) -- exitUserCall* needs it to decrement
1287
+ // symmetrically, for the same "callStack_.back() is already gone"
1288
+ // reason as `kind`/`declNode` above. See nativeUserCallDepth_'s
1289
+ // own doc comment for why this can't just be "always decrement".
1290
+ bool countedTowardNativeDepth = false;
1221
1291
  };
1222
1292
  // `skipDepthGuard`: kMaxUserCallDepth=50 exists purely to keep the
1223
1293
  // NATIVE C++ stack from overflowing (see its own doc comment) -- true
@@ -1427,6 +1497,37 @@ private:
1427
1497
  // eval_error.hpp).
1428
1498
  std::vector<CallStackFrame> callStack_;
1429
1499
 
1500
+ // Count of callStack_ entries that ACTUALLY cost native C++ stack --
1501
+ // i.e. pushed with skipDepthGuard=false (enterUserCall's own
1502
+ // interpreted-call sites: evalUserFunctionCore, evalUserModule).
1503
+ // Deliberately NOT the same number as callStack_.size(): a compiled-
1504
+ // to-compiled push (pushBracketedCallFrame/pushBracketedModuleFrame,
1505
+ // bytecode_vm.cpp, skipDepthGuard=true) still grows callStack_ (for
1506
+ // TRACE/closure-detection/$parent_modules bookkeeping) but costs ZERO
1507
+ // native stack, serviced by driveVm's own heap-based loop instead.
1508
+ //
1509
+ // Checking callStack_.size() itself against kMaxUserCallDepth (the
1510
+ // ORIGINAL design) conflates these two: a real BOSL2 script's own
1511
+ // AMBIENT callStack_ depth, inflated by many cheap skip=true compiled
1512
+ // module-call pushes sitting on callStack_ already, could push a
1513
+ // LATER genuinely-native-recursive call (e.g. an interpreted-path
1514
+ // function call like BOSL2's own `ident()`) over the threshold even
1515
+ // though the REAL native C++ nesting at that point was nowhere near
1516
+ // it -- caught for real: Anklet.scad's own ambient callStack_ depth
1517
+ // (mostly cheap Op::CallModule pushes) reached the mid-30s, tripping
1518
+ // kMaxUserCallDepth=30 for an `ident()` call that itself was nested
1519
+ // only a couple of GENUINE native frames deep. Exactly the same class
1520
+ // of "fixed-count guard conflates logical depth with native-stack
1521
+ // cost" bug driveVmNativeDepth_ already exists to avoid for the
1522
+ // OTHER guard site (bytecode_vm.cpp) -- this is that same fix,
1523
+ // applied here. Incremented/decremented symmetrically by
1524
+ // enterUserCall/exitUserCallSuccess/exitUserCallException, gated on
1525
+ // UserCallHandle::countedTowardNativeDepth (skipDepthGuard is only
1526
+ // available at enter time; the handle carries the decision forward
1527
+ // to exit time, since callStack_.back() -- and thus skipDepthGuard's
1528
+ // own original context -- is already gone by then).
1529
+ size_t nativeUserCallDepth_ = 0;
1530
+
1430
1531
  // Running count of Module-kind frames currently on callStack_ --
1431
1532
  // maintained incrementally by enterUserCall/exitUserCall* rather than
1432
1533
  // rescanned from callStack_ on every module call (buildModuleChildCtx's
@@ -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.2"
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 {
@@ -682,13 +696,22 @@ Evaluator::UserCallHandle Evaluator::enterUserCall(const std::string& name, cons
682
696
  // entirely; for what's left, the margin check is the proven-UNSAFE
683
697
  // mechanism on Windows (confirmed via real CI), this fixed count the
684
698
  // proven-safe one.
685
- if (!skipDepthGuard && callStack_.size() >= kMaxUserCallDepth) {
699
+ //
700
+ // Checked against nativeUserCallDepth_, NOT callStack_.size() -- see
701
+ // that field's own doc comment (evaluator.hpp) for why: callStack_
702
+ // also grows from cheap, zero-native-cost skipDepthGuard=true pushes,
703
+ // which must not count toward this native-stack-safety ceiling.
704
+ if (!skipDepthGuard && nativeUserCallDepth_ >= kMaxUserCallDepth) {
686
705
  error("Recursion too deep while calling " + std::string(isModule ? "module" : "function") + " '" + name + "'",
687
706
  declNode);
688
707
  }
689
708
  UserCallHandle h;
690
709
  h.kind = kind;
691
710
  h.declNode = &declNode;
711
+ if (!skipDepthGuard) {
712
+ ++nativeUserCallDepth_;
713
+ h.countedTowardNativeDepth = true;
714
+ }
692
715
  h.prof = profileEnter(isModule ? "module" : "function", name, callPos, &declNode.position());
693
716
  callStack_.push_back(CallStackFrame{kind, name, callPos, &declNode.position(), &declNode, nullptr, upvalueParent});
694
717
  callStack_.back().bodyCtx = &childCtx; // per-frame locals for the debugger
@@ -710,6 +733,7 @@ void Evaluator::exitUserCallSuccess(const std::string& name, const UserCallHandl
710
733
  if (fireReturnHook && debugHooks_.returnHook) debugHooks_.returnHook(name, result, static_cast<int>(callStack_.size()));
711
734
  callStack_.pop_back();
712
735
  if (handle.kind == CallStackFrame::Kind::Module) --moduleCallDepth_;
736
+ if (handle.countedTowardNativeDepth) --nativeUserCallDepth_;
713
737
  noteActiveDeclExit(handle.declNode);
714
738
  if (handle.prof) profileExit(*handle.prof);
715
739
  }
@@ -717,6 +741,7 @@ void Evaluator::exitUserCallSuccess(const std::string& name, const UserCallHandl
717
741
  void Evaluator::exitUserCallException(const UserCallHandle& handle) {
718
742
  callStack_.pop_back();
719
743
  if (handle.kind == CallStackFrame::Kind::Module) --moduleCallDepth_;
744
+ if (handle.countedTowardNativeDepth) --nativeUserCallDepth_;
720
745
  noteActiveDeclExit(handle.declNode);
721
746
  if (handle.prof) profileExit(*handle.prof);
722
747
  }
@@ -886,15 +911,20 @@ Value Evaluator::parentModuleName(int idx) const {
886
911
  return Value{modules[static_cast<size_t>(revIdx)]};
887
912
  }
888
913
 
889
- void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
914
+ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(const CallArgs& args, EvalContext& ctx) {
890
915
  Value idxArg = getArg(args, 0, "index", Value{});
891
- if (!ctx.childrenNodes || ctx.childrenNodes->empty()) return;
916
+ if (!ctx.childrenNodes || ctx.childrenNodes->empty()) return std::nullopt;
892
917
  const EvalContext* callerCtx = ctx.childrenCallerCtx;
893
- if (!callerCtx) return;
918
+ if (!callerCtx) return std::nullopt;
894
919
 
895
920
  // A children() forwarding chain's own dyn/let_/etc. must alias the
896
921
  // *caller's* (not this ctx's) -- see EvalContext::withScope's rationale.
897
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();`).
898
928
  for (const auto& [k, v] : ctx.dyn->items()) {
899
929
  if (!k.empty() && k[0] == '$') evalCtx.dyn->set(k, v);
900
930
  }
@@ -903,8 +933,7 @@ void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
903
933
  }
904
934
 
905
935
  if (std::holds_alternative<std::monostate>(idxArg)) {
906
- evalChildren(*ctx.childrenNodes, evalCtx);
907
- return;
936
+ return ChildrenForward{std::move(evalCtx), *ctx.childrenNodes};
908
937
  }
909
938
 
910
939
  // children(N) indexes child *statements*, not output bodies -- a
@@ -919,8 +948,14 @@ void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
919
948
  geoNodes.push_back(c);
920
949
  }
921
950
  }
922
- if (idx < 0 || static_cast<size_t>(idx) >= geoNodes.size()) return;
923
- 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);
924
959
  }
925
960
 
926
961
  } // namespace oscadeval