openscad-cpp-evaluator 0.13.7__tar.gz → 0.13.9__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.7 → openscad_cpp_evaluator-0.13.9}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/bytecode.hpp +20 -9
  3. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/control.cpp +16 -13
  5. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/bytecode_compiler.cpp +110 -102
  6. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/expr_eval.cpp +18 -17
  7. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/stmt_eval.cpp +25 -18
  8. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/user_calls.cpp +21 -1
  9. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_control_flow.cpp +99 -0
  10. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/.github/workflows/ci.yml +0 -0
  11. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/.github/workflows/wheels.yml +0 -0
  12. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/.gitignore +0 -0
  13. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/.gitmodules +0 -0
  14. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/CLAUDE.md +0 -0
  15. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/LICENSE +0 -0
  17. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/README.md +0 -0
  18. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/bindings/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/bindings/module.cpp +0 -0
  20. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  44. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  45. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  46. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/api.cpp +0 -0
  47. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  48. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  49. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  50. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  51. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  52. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  53. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  54. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  55. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  56. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  57. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  58. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  59. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  60. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  61. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  62. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  63. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/position.cpp +0 -0
  64. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  65. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  66. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  67. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  69. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  70. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  71. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  72. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  73. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  74. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  75. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  76. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  78. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  79. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  80. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  81. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  82. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  83. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  84. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  85. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  86. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  87. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  88. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  89. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  90. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  91. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  92. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  93. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  94. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  95. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  96. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  97. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  98. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  99. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  100. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  101. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  102. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  103. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  104. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  105. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  106. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/export.hpp +0 -0
  107. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  108. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  109. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  110. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  111. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  112. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  113. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  114. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  115. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  116. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  118. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  119. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/value.hpp +0 -0
  120. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  121. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/python/openscad_cpp_evaluator/__init__.py +0 -0
  122. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/resources/fonts/LICENSE +0 -0
  123. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  124. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/CMakeLists.txt +0 -0
  125. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/booleans.cpp +0 -0
  126. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/builtins.hpp +0 -0
  127. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/call_args.cpp +0 -0
  128. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/color.cpp +0 -0
  129. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/extrude.cpp +0 -0
  130. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/function_builtins.cpp +0 -0
  131. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/import.cpp +0 -0
  132. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/modifiers.cpp +0 -0
  133. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/primitives_2d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/primitives_3d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/registry.cpp +0 -0
  136. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/roof.cpp +0 -0
  137. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/segments.cpp +0 -0
  138. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/surface.cpp +0 -0
  139. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/text.cpp +0 -0
  140. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/topology.cpp +0 -0
  141. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/builtins/transforms.cpp +0 -0
  142. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/bytecode_vm.cpp +0 -0
  143. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/colored_body.cpp +0 -0
  144. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/csg_generate.cpp +0 -0
  145. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/csg_resolve.cpp +0 -0
  146. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/css_colors.cpp +0 -0
  147. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/debug/debug_repl.cpp +0 -0
  148. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/debug_profile.cpp +0 -0
  149. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/eval_context.cpp +0 -0
  150. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/eval_error.cpp +0 -0
  151. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/eval_use.cpp +0 -0
  152. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/export.cpp +0 -0
  153. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/import/dxf_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/import/mesh_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/import/surface_load.cpp +0 -0
  156. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/import/svg_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/manifold_cache.cpp +0 -0
  158. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/text/stb_font_provider.cpp +0 -0
  159. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/text/text_metrics.cpp +0 -0
  160. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/value.cpp +0 -0
  161. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/src/zip_stored.cpp +0 -0
  162. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/CMakeLists.txt +0 -0
  163. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_booleans.cpp +0 -0
  164. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_bytecode_compiler.cpp +0 -0
  165. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_csg_tree.cpp +0 -0
  167. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_debug_hooks.cpp +0 -0
  168. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_dxf_svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_export.cpp +0 -0
  171. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_extrude_roof.cpp +0 -0
  173. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_function_builtins.cpp +0 -0
  174. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_helpers.hpp +0 -0
  175. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_import_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_multi_color_merge.cpp +0 -0
  178. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_primitives.cpp +0 -0
  179. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.13.7 → openscad_cpp_evaluator-0.13.9}/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.7
3
+ Version: 0.13.9
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -104,18 +104,29 @@ enum class Op {
104
104
  // themselves lists).
105
105
  AccumMergeEach,
106
106
 
107
- // ListCompFor's per-assignment iteration -- materialize once (a
108
- // dimension's own RHS expression is evaluated exactly once, matching
109
- // the interpreter's own upfront `pairs.push_back(...)` loop), then
110
- // reset+iterate however many times that dimension is (re-)entered
111
- // (once per outer-dimension iteration for a nested `for`). `a` = an
112
- // iterList id (see CompiledChunk::numIterLists), unique per assignment
113
- // across the whole chunk.
107
+ // ListCompFor's per-assignment iteration. `a` = an iterList id (see
108
+ // CompiledChunk::numIterLists), unique per assignment across the whole
109
+ // chunk. Each dimension's own IterMaterialize is emitted INSIDE the
110
+ // enclosing dimension's own loop body (compileListElement's own
111
+ // emitDim recursion), so it re-executes once per outer-dimension
112
+ // binding -- required for a later dimension's own RHS to see an
113
+ // earlier one's current value (e.g. `[for (p=[1:N], pt=f(p)) pt]`),
114
+ // matching evalFor's own doc comment (stmt_eval.cpp) and real
115
+ // OpenSCAD.app's verified behavior.
114
116
  IterMaterialize, // pops one Value (the assignment's RHS), expandIterable()s it into iterLists[a], resets its index
115
- IterReset, // resets iterLists[a]'s index to 0 WITHOUT re-materializing (a re-entry, not the first entry)
117
+ // Resets iterLists[a]'s index to 0 WITHOUT re-materializing. UNREACHABLE
118
+ // as of the materialize-inside-the-nesting fix above: re-materializing
119
+ // on every dimension re-entry already resets the index as a side
120
+ // effect (see IterMaterialize's own doc comment), so there's no longer
121
+ // a "reset without re-materializing" case left to reach for either
122
+ // ListCompFor (compileListElement) or ModularFor (compileForLoop).
123
+ // Left defined rather than removed in the same change -- a distinct,
124
+ // lower-risk cleanup (same convention as LoadUpvalue's own doc
125
+ // comment, above).
126
+ IterReset,
116
127
  IterNext, // a = loop-variable slot, b = iterList id, c = jump target for exhaustion: if iterLists[b] has a
117
128
  // next value (at its current index), write it to slots[a], advance the index, fall through;
118
- // else jump to c (index is left as-is; the next IterReset for this id will restart it)
129
+ // else jump to c (index is left as-is; the next IterMaterialize for this id will restart it)
119
130
 
120
131
  // ListCompCFor's runaway-loop guard, mirroring evalListElement's own
121
132
  // 1,000,000-iteration safety limit exactly (see its own doc comment
@@ -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.7"
7
+ version = "0.13.9"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -97,24 +97,21 @@ ColoredBody combineBodies(const std::vector<ColoredBody>& bodies) {
97
97
  // children) -- same rationale as booleans.cpp's own group_sizes. Mirrors
98
98
  // _resolve_intersection_for/_generate_intersection_for.
99
99
  CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersectionFor& node, EvalContext& ctx) {
100
- std::vector<std::pair<std::string, IterableValues>> varSeqs;
101
- varSeqs.reserve(node.assignments.size());
102
- for (const auto& assign : node.assignments) {
103
- Value values = ev.evalExpr(*assign->expr, ctx);
104
- const oscad::Position* pos = &assign->position();
105
- varSeqs.emplace_back(assign->name->name, expandIterable(values, [&](size_t count) {
106
- ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
107
- }));
108
- }
109
-
110
100
  std::vector<const oscad::ASTNode*> bodyNodes;
111
101
  bodyNodes.reserve(node.body.size());
112
102
  for (const auto& b : node.body) bodyNodes.push_back(b.get());
113
103
 
114
104
  std::vector<Value> groupSizes;
115
105
 
106
+ // Each dimension's own RHS is evaluated against `parentCtx` (not
107
+ // upfront against the original `ctx`), re-evaluated on every entry
108
+ // into this recursion level -- see evalFor's own doc comment
109
+ // (stmt_eval.cpp) for the full "verified against real OpenSCAD.app"
110
+ // rationale; this is the identical bug in intersection_for's own
111
+ // cartesian loop (e.g. `intersection_for (i=[0:2], j=[0:i]) ...`
112
+ // needs `i` visible in `j`'s own range expression).
116
113
  std::function<void(size_t, EvalContext&)> recurse = [&](size_t depth, EvalContext& parentCtx) {
117
- if (depth == varSeqs.size()) {
114
+ if (depth == node.assignments.size()) {
118
115
  // One body-entry marker per full cartesian-product iteration
119
116
  // and nothing per individual variable binding (unlike
120
117
  // evalFor) -- mirrors _resolve_intersection_for's single
@@ -126,9 +123,15 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
126
123
  groupSizes.push_back(Value{static_cast<double>(after - before)});
127
124
  return;
128
125
  }
129
- for (const Value& val : varSeqs[depth].second) {
126
+ const auto& assign = node.assignments[depth];
127
+ Value values = ev.evalExpr(*assign->expr, parentCtx);
128
+ const oscad::Position* pos = &assign->position();
129
+ IterableValues iter = expandIterable(values, [&](size_t count) {
130
+ ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
131
+ });
132
+ for (const Value& val : iter) {
130
133
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
131
- childCtx.let_->set(varSeqs[depth].first, val);
134
+ childCtx.let_->set(assign->name->name, val);
132
135
  recurse(depth + 1, childCtx);
133
136
  }
134
137
  };
@@ -884,22 +884,27 @@ public:
884
884
  switch (elem.kind()) {
885
885
  case NodeKind::ListCompFor: {
886
886
  auto& n = static_cast<const oscad::ListCompFor&>(elem);
887
- // Each assignment's RHS is evaluated exactly once, against
888
- // the OUTER scope (never seeing an earlier sibling
889
- // assignment's own loop variable) -- mirrors the
890
- // interpreter's own upfront `pairs.push_back(...)` loop,
891
- // and vector_element.hpp's own doc comment on this
892
- // asymmetry with ListCompCFor.
893
- std::vector<int> iterIds;
894
- iterIds.reserve(n.assignments.size());
895
- for (const auto& assign : n.assignments) {
896
- compileExpr(*assign->expr, out, scope);
897
- int id = nextIterList_++;
898
- iterIds.push_back(id);
899
- out.push_back({Op::IterMaterialize, id, 0, &assign->position()});
900
- }
901
887
  scope.push();
902
888
  const bool isNestedLc = (n.body->kind() == NodeKind::ListComprehension);
889
+ // Each dimension's own RHS is compiled+materialized INSIDE
890
+ // the enclosing dimension's own loop body (nested, via the
891
+ // recursive call below), so it naturally re-executes once
892
+ // per outer binding, resolved against `scope` with every
893
+ // OUTER dimension's own slot already declared -- see
894
+ // compileForLoop's own doc comment (this file) / evalFor's
895
+ // (stmt_eval.cpp) for the full "verified against real
896
+ // OpenSCAD.app" rationale. This used to evaluate every
897
+ // dimension's RHS exactly once, upfront, against the outer
898
+ // scope -- justified at the time by appeal to
899
+ // vector_element.hpp's own buildScope() "asymmetric with
900
+ // ListCompCFor" comment, but that comment describes STATIC
901
+ // scope-TREE construction for name resolution (module/
902
+ // function lookups), not RUNTIME value-binding order
903
+ // (always the dynamic ctx.let_ chain regardless of the
904
+ // Scope tree) -- it doesn't justify this, and real
905
+ // OpenSCAD.app confirms a later dimension's range CAN
906
+ // depend on an earlier one's current value (e.g. `[for
907
+ // (p=[1:N], pt=f(p)) pt]`).
903
908
  std::function<void(size_t)> emitDim = [&](size_t depth) {
904
909
  if (depth == n.assignments.size()) {
905
910
  if (isNestedLc) {
@@ -910,17 +915,16 @@ public:
910
915
  }
911
916
  return;
912
917
  }
913
- // Every re-entry (every outer-dimension iteration)
914
- // restarts this dimension from its already-materialized
915
- // values -- IterReset, not another IterMaterialize (the
916
- // RHS expression itself is never re-evaluated).
917
- if (depth > 0) out.push_back({Op::IterReset, iterIds[depth], 0, nullptr});
918
- int loopVarSlot = declareLocal(scope, n.assignments[depth]->name->name);
918
+ const auto& assign = n.assignments[depth];
919
+ compileExpr(*assign->expr, out, scope);
920
+ const int iterId = nextIterList_++;
921
+ out.push_back({Op::IterMaterialize, iterId, 0, &assign->position()});
922
+ int loopVarSlot = declareLocal(scope, assign->name->name);
919
923
  size_t loopStart = out.size();
920
924
  Instruction iterNext;
921
925
  iterNext.op = Op::IterNext;
922
926
  iterNext.a = loopVarSlot;
923
- iterNext.b = iterIds[depth];
927
+ iterNext.b = iterId;
924
928
  size_t iterNextIdx = out.size();
925
929
  out.push_back(iterNext);
926
930
  emitDim(depth + 1);
@@ -1250,23 +1254,27 @@ public:
1250
1254
  // a cartesian-product loop over `node.assignments`, and at the
1251
1255
  // innermost base case, ONE currentTreeFrameSize()-delta group per full
1252
1256
  // iteration. Reuses compileForLoop's own cartesian-loop scaffold
1253
- // verbatim (Op::NativeIterMaterialize/ForIterNext/ForIterEnd/
1254
- // IterReset) -- Op::ForIterNext's own ctx construction,
1255
- // `ctx.childCtx(nullptr, std::nullopt, ctx.childrenNodes,
1256
- // ctx.childrenCallerCtx)`, is exactly what resolveIntersectionFor's
1257
- // own recurse lambda does too (`parentCtx.childCtx(nullptr,
1258
- // std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx)`), since
1259
- // childrenNodes/childrenCallerCtx propagate unchanged through every
1260
- // nested childCtx either way -- just with Op::CsgGroupStart/
1261
- // CsgGroupEnd wrapped around the body instead of a bare
1262
- // compileStatementList. The leading checkDebug is only emitted when
1263
- // node.body isn't empty, mirroring resolveIntersectionFor's own `if
1264
- // (!bodyNodes.empty())` guard exactly (unlike compileForLoop's own
1265
- // ModularFor sibling, which always emits one even for an empty body,
1266
- // against the FOR node itself as a fallback marker -- intersection_for
1267
- // has no such native fallback, so neither does this) -- but
1268
- // CsgGroupStart/CsgGroupEnd themselves are UNCONDITIONAL, since native
1269
- // measures a (possibly zero-size) group regardless of body emptiness.
1257
+ // verbatim (Op::NativeIterMaterialize/ForIterNext/ForIterEnd, each
1258
+ // dimension nested INSIDE the enclosing one's own loop body -- see
1259
+ // compileForLoop's own doc comment for why this shape, not a flat
1260
+ // materialize-everything-upfront pass, is required for a later
1261
+ // dimension's range to see an earlier one's binding) -- Op::
1262
+ // ForIterNext's own ctx construction, `ctx.childCtx(nullptr,
1263
+ // std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx)`, is exactly
1264
+ // what resolveIntersectionFor's own recurse lambda does too
1265
+ // (`parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes,
1266
+ // ctx.childrenCallerCtx)`), since childrenNodes/childrenCallerCtx
1267
+ // propagate unchanged through every nested childCtx either way --
1268
+ // just with Op::CsgGroupStart/CsgGroupEnd wrapped around the body
1269
+ // instead of a bare compileStatementList. The leading checkDebug is
1270
+ // only emitted when node.body isn't empty, mirroring
1271
+ // resolveIntersectionFor's own `if (!bodyNodes.empty())` guard
1272
+ // exactly (unlike compileForLoop's own ModularFor sibling, which
1273
+ // always emits one even for an empty body, against the FOR node
1274
+ // itself as a fallback marker -- intersection_for has no such native
1275
+ // fallback, so neither does this) -- but CsgGroupStart/CsgGroupEnd
1276
+ // themselves are UNCONDITIONAL, since native measures a (possibly
1277
+ // zero-size) group regardless of body emptiness.
1270
1278
  void compileIntersectionForLoop(const oscad::ModularIntersectionFor& n, std::vector<Instruction>& out) {
1271
1279
  out.push_back({Op::CheckDebugStatement, internNativeStatement(&n), 0, nullptr});
1272
1280
  CompiledChunk::CsgWrapSite site;
@@ -1279,35 +1287,32 @@ public:
1279
1287
  out.push_back({Op::PushCsgWrap, idx, 0, &n.position()});
1280
1288
 
1281
1289
  const size_t numDims = n.assignments.size();
1282
- std::vector<int> iterListIds(numDims);
1283
- for (size_t d = 0; d < numDims; ++d) {
1284
- iterListIds[d] = nextIterList_++;
1285
- out.push_back({Op::NativeIterMaterialize, internNativeExpr(n.assignments[d]->expr.get()), iterListIds[d],
1290
+ std::function<void(size_t)> emitDim = [&](size_t d) {
1291
+ if (d == numDims) {
1292
+ if (!n.body.empty()) {
1293
+ out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(n.body.front().get()), 0, nullptr});
1294
+ }
1295
+ out.push_back({Op::CsgGroupStart, 0, 0, nullptr});
1296
+ compileStatementList(n.body, out);
1297
+ out.push_back({Op::CsgGroupEnd, 0, 0, nullptr});
1298
+ return;
1299
+ }
1300
+ const int iterListId = nextIterList_++;
1301
+ out.push_back({Op::NativeIterMaterialize, internNativeExpr(n.assignments[d]->expr.get()), iterListId,
1286
1302
  &n.assignments[d]->position()});
1287
- }
1288
- std::vector<size_t> topIdx(numDims);
1289
- std::vector<size_t> forIterNextIdx(numDims);
1290
- for (size_t d = 0; d < numDims; ++d) {
1291
- if (d > 0) out.push_back({Op::IterReset, iterListIds[d], 0, nullptr});
1292
- topIdx[d] = out.size();
1293
- forIterNextIdx[d] = out.size();
1303
+ const size_t topIdx = out.size();
1304
+ const size_t forIterNextIdx = out.size();
1294
1305
  Instruction ins;
1295
1306
  ins.op = Op::ForIterNext;
1296
1307
  ins.a = internName(n.assignments[d]->name->name);
1297
- ins.b = iterListIds[d];
1308
+ ins.b = iterListId;
1298
1309
  ins.node = n.assignments[d].get();
1299
1310
  out.push_back(ins);
1300
- }
1301
- if (!n.body.empty()) {
1302
- out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(n.body.front().get()), 0, nullptr});
1303
- }
1304
- out.push_back({Op::CsgGroupStart, 0, 0, nullptr});
1305
- compileStatementList(n.body, out);
1306
- out.push_back({Op::CsgGroupEnd, 0, 0, nullptr});
1307
- for (size_t i = numDims; i-- > 0;) {
1308
- out.push_back({Op::ForIterEnd, static_cast<int>(topIdx[i]), 0, nullptr});
1309
- out[forIterNextIdx[i]].c = static_cast<int>(out.size());
1310
- }
1311
+ emitDim(d + 1);
1312
+ out.push_back({Op::ForIterEnd, static_cast<int>(topIdx), 0, nullptr});
1313
+ out[forIterNextIdx].c = static_cast<int>(out.size());
1314
+ };
1315
+ emitDim(0);
1311
1316
  out.push_back({Op::PopCsgWrap, idx, 0, &n.position()});
1312
1317
  }
1313
1318
 
@@ -1643,54 +1648,57 @@ public:
1643
1648
  // based code at COMPILE time (bounded by the source's own for-clause
1644
1649
  // count, always small -- never runtime-driven) rather than the
1645
1650
  // interpreter's own runtime recursion (evalFor's `recurse(depth+1,
1646
- // ...)`, stmt_eval.cpp). Each dimension: materialize once (native RHS
1647
- // eval + expandIterable), then a ForIterNext/ForIterEnd pair wrapping
1648
- // everything inner -- see Op::ForIterNext/ForIterEnd's own doc
1649
- // comments (bytecode.hpp) for exactly why a fresh per-iteration ctx
1650
- // (not an in-place mutation) is required for correctness, not just
1651
- // parity.
1651
+ // ...)`, stmt_eval.cpp) -- but structurally mirroring that SAME
1652
+ // recursion shape at compile time (emitDim, below), not a flat
1653
+ // "materialize every dimension, then loop every dimension" two-pass
1654
+ // shape (what used to be here): dimension d's own Op::
1655
+ // NativeIterMaterialize is emitted INSIDE dimension d-1's own loop
1656
+ // body (nested, via the recursive call), so it naturally re-executes
1657
+ // once per (d-1)-and-outer binding, evaluated against whatever ctx is
1658
+ // current at that point in the instruction stream -- i.e. with every
1659
+ // OUTER dimension's own loop variable already bound. Required for real
1660
+ // OpenSCAD's own documented/verified behavior: a later `for` clause's
1661
+ // range CAN depend on an earlier one (`for (i=[0:2], j=[0:i])` is a
1662
+ // standard triangular-loop idiom) -- the old flat-materialize-upfront
1663
+ // shape evaluated every dimension's RHS before ANY variable was bound,
1664
+ // so a later dimension's own reference to an earlier one always failed
1665
+ // with "unknown variable" (confirmed wrong against real OpenSCAD.app
1666
+ // directly, not just an internal inconsistency; see evalFor's own doc
1667
+ // comment, stmt_eval.cpp, for the fuller story -- same bug, same fix
1668
+ // shape, on the interpreter side).
1669
+ //
1670
+ // No Op::IterReset needed any more: since Materialize now runs exactly
1671
+ // once per entry into this dimension's own block (not once total),
1672
+ // its own index-reset (see that op's own doc comment) already covers
1673
+ // "restart this dimension for the next outer binding" -- the OLD
1674
+ // separate reset-without-rematerializing step existed purely to avoid
1675
+ // an unnecessary re-evaluation under the old (buggy) "materialize
1676
+ // once ever" shape, which no longer exists.
1652
1677
  void compileForLoop(const oscad::ModularFor& n, std::vector<Instruction>& out) {
1653
1678
  const size_t numDims = n.assignments.size();
1654
- std::vector<int> iterListIds(numDims);
1655
- for (size_t d = 0; d < numDims; ++d) {
1656
- iterListIds[d] = nextIterList_++;
1657
- out.push_back({Op::NativeIterMaterialize, internNativeExpr(n.assignments[d]->expr.get()),
1658
- iterListIds[d], &n.assignments[d]->position()});
1659
- }
1660
- std::vector<size_t> topIdx(numDims);
1661
- std::vector<size_t> forIterNextIdx(numDims);
1662
- for (size_t d = 0; d < numDims; ++d) {
1663
- // Every dimension but the outermost needs its own IterList
1664
- // index reset to 0 each time it's (re-)entered from the
1665
- // ENCLOSING dimension's own successful bind -- NativeIterMaterialize
1666
- // only resets it once, up front, before ANY iteration runs; by
1667
- // the second (and every later) outer-dimension value, this
1668
- // dimension's own index is still sitting at "exhausted" from
1669
- // the PREVIOUS pass, and would immediately look exhausted
1670
- // again without this. Placed strictly BEFORE this dimension's
1671
- // own ForIterNext/topIdx (not at it) so this dimension's OWN
1672
- // "try the next value" jump (its own ForIterEnd, below) lands
1673
- // AT ForIterNext directly and skips the reset -- only the
1674
- // fall-through from the enclosing dimension's bind passes
1675
- // through it. Harmless (a no-op) the very first time, since
1676
- // the index is already 0 from NativeIterMaterialize then.
1677
- if (d > 0) out.push_back({Op::IterReset, iterListIds[d], 0, nullptr});
1678
- topIdx[d] = out.size();
1679
- forIterNextIdx[d] = out.size();
1679
+ std::function<void(size_t)> emitDim = [&](size_t d) {
1680
+ if (d == numDims) {
1681
+ const oscad::ASTNode* marker = n.body.empty() ? static_cast<const oscad::ASTNode*>(&n) : n.body.front().get();
1682
+ out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(marker), 0, nullptr});
1683
+ compileStatementList(n.body, out);
1684
+ return;
1685
+ }
1686
+ const int iterListId = nextIterList_++;
1687
+ out.push_back({Op::NativeIterMaterialize, internNativeExpr(n.assignments[d]->expr.get()), iterListId,
1688
+ &n.assignments[d]->position()});
1689
+ const size_t topIdx = out.size();
1690
+ const size_t forIterNextIdx = out.size();
1680
1691
  Instruction ins;
1681
1692
  ins.op = Op::ForIterNext;
1682
1693
  ins.a = internName(n.assignments[d]->name->name);
1683
- ins.b = iterListIds[d];
1694
+ ins.b = iterListId;
1684
1695
  ins.node = n.assignments[d].get();
1685
1696
  out.push_back(ins); // ins.c (exhaustion target) patched below
1686
- }
1687
- const oscad::ASTNode* marker = n.body.empty() ? static_cast<const oscad::ASTNode*>(&n) : n.body.front().get();
1688
- out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(marker), 0, nullptr});
1689
- compileStatementList(n.body, out);
1690
- for (size_t i = numDims; i-- > 0;) {
1691
- out.push_back({Op::ForIterEnd, static_cast<int>(topIdx[i]), 0, nullptr});
1692
- out[forIterNextIdx[i]].c = static_cast<int>(out.size());
1693
- }
1697
+ emitDim(d + 1);
1698
+ out.push_back({Op::ForIterEnd, static_cast<int>(topIdx), 0, nullptr});
1699
+ out[forIterNextIdx].c = static_cast<int>(out.size());
1700
+ };
1701
+ emitDim(0);
1694
1702
  }
1695
1703
 
1696
1704
  private:
@@ -144,23 +144,18 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
144
144
  switch (elem.kind()) {
145
145
  case oscad::NodeKind::ListCompFor: {
146
146
  auto& n = static_cast<const oscad::ListCompFor&>(elem);
147
- struct Pair {
148
- std::string name;
149
- IterableValues values;
150
- };
151
- std::vector<Pair> pairs;
152
- pairs.reserve(n.assignments.size());
153
- for (const auto& assign : n.assignments) {
154
- Value values = evalExpr(*assign->expr, ctx);
155
- const oscad::Position* pos = &assign->position();
156
- pairs.push_back(Pair{assign->name->name, expandIterable(values, [&](size_t count) {
157
- warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
158
- })});
159
- }
160
147
  const bool isNestedLc = (n.body->kind() == oscad::NodeKind::ListComprehension);
161
148
 
149
+ // Each dimension's own RHS is evaluated against `parentCtx`
150
+ // (not upfront against the original `ctx`), re-evaluated on
151
+ // every entry into this recursion level -- see evalFor's own
152
+ // doc comment (stmt_eval.cpp) for the full "verified against
153
+ // real OpenSCAD.app" rationale; this is the identical bug in
154
+ // the list-comprehension sibling of that same construct (e.g.
155
+ // `[for (p=[1:N], pt=f(p)) pt]` needs `p` visible in `pt`'s own
156
+ // range expression).
162
157
  std::function<void(size_t, EvalContext&)> recurse = [&](size_t depth, EvalContext& parentCtx) {
163
- if (depth == pairs.size()) {
158
+ if (depth == n.assignments.size()) {
164
159
  if (isNestedLc) {
165
160
  out.push_back(evalListLiteral(static_cast<const oscad::ListComprehension&>(*n.body), parentCtx));
166
161
  } else {
@@ -168,14 +163,20 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
168
163
  }
169
164
  return;
170
165
  }
171
- for (const Value& val : pairs[depth].values) {
166
+ const auto& assign = n.assignments[depth];
167
+ Value values = evalExpr(*assign->expr, parentCtx);
168
+ const oscad::Position* pos = &assign->position();
169
+ IterableValues iter = expandIterable(values, [&](size_t count) {
170
+ warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
171
+ });
172
+ for (const Value& val : iter) {
172
173
  EvalContext childCtx = parentCtx.letChildCtx();
173
- childCtx.let_->set(pairs[depth].name, val);
174
+ childCtx.let_->set(assign->name->name, val);
174
175
  // Per-binding stop, same shape as evalFor's -- but with
175
176
  // NO separate body-entry marker at depth == pairs.size()
176
177
  // (_eval_listcomp_for has none; the body's own element
177
178
  // check below supplies the expr-level stop instead).
178
- checkDebug(*n.assignments[depth], childCtx);
179
+ checkDebug(*assign, childCtx);
179
180
  recurse(depth + 1, childCtx);
180
181
  }
181
182
  };
@@ -71,20 +71,6 @@ void Evaluator::evalAssertStatement(const oscad::ModularAssert& node, EvalContex
71
71
  }
72
72
 
73
73
  void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
74
- struct AssignPair {
75
- std::string name;
76
- IterableValues values;
77
- };
78
- std::vector<AssignPair> pairs;
79
- pairs.reserve(node.assignments.size());
80
- for (const auto& assign : node.assignments) {
81
- Value values = evalExprMaybeCompiled(*assign->expr, ctx);
82
- const oscad::Position* pos = &assign->position();
83
- pairs.push_back(AssignPair{assign->name->name, expandIterable(values, [&](size_t count) {
84
- warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
85
- })});
86
- }
87
-
88
74
  std::vector<const oscad::ASTNode*> bodyNodes;
89
75
  bodyNodes.reserve(node.body.size());
90
76
  for (const auto& b : node.body) bodyNodes.push_back(b.get());
@@ -96,8 +82,23 @@ void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
96
82
  // _eval_for's `_nested` closure exactly) and binds just that one
97
83
  // variable, so inner levels/the body see all outer bindings already in
98
84
  // `let_`.
85
+ //
86
+ // Each dimension's own RHS is evaluated HERE, against `parentCtx` (not
87
+ // upfront against the original `ctx`), and re-evaluated on every entry
88
+ // into this recursion level -- i.e. once per combination of whatever
89
+ // dimensions 0..depth-1 are currently bound to. This is required for
90
+ // real OpenSCAD's own documented/verified behavior: a later `for`
91
+ // clause's range CAN depend on an earlier one (`for (i=[0:2], j=[0:i])`
92
+ // is a standard triangular-loop idiom; `pt = f(p)` inside `for (p=...,
93
+ // pt=f(p))` needs the same). Evaluating all dimensions upfront in one
94
+ // flat pass (the previous shape here) evaluated every RHS against the
95
+ // ORIGINAL ctx before ANY variable was bound, so a later dimension's
96
+ // own reference to an earlier one always failed with "unknown
97
+ // variable" -- confirmed wrong against real OpenSCAD.app directly
98
+ // (verified both `for(i=[0:2],j=[0:i])` and a dependent list-comp
99
+ // case), not just an internal inconsistency.
99
100
  std::function<void(size_t, EvalContext&)> recurse = [&](size_t depth, EvalContext& parentCtx) {
100
- if (depth == pairs.size()) {
101
+ if (depth == node.assignments.size()) {
101
102
  // Per-full-iteration "entering the body" marker, separate from
102
103
  // (and before) the body's own per-statement checks in
103
104
  // evalChildren -- mirrors _eval_for's
@@ -106,14 +107,20 @@ void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
106
107
  evalChildren(bodyNodes, parentCtx);
107
108
  return;
108
109
  }
109
- for (const Value& val : pairs[depth].values) {
110
+ const auto& assign = node.assignments[depth];
111
+ Value values = evalExprMaybeCompiled(*assign->expr, parentCtx);
112
+ const oscad::Position* pos = &assign->position();
113
+ IterableValues iter = expandIterable(values, [&](size_t count) {
114
+ warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
115
+ });
116
+ for (const Value& val : iter) {
110
117
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
111
- childCtx.let_->set(pairs[depth].name, val);
118
+ childCtx.let_->set(assign->name->name, val);
112
119
  // One statement-level stop per (bound-so-far) combination, on
113
120
  // the loop-variable assignment itself -- so a breakpoint set
114
121
  // directly on an `i=[0:2],`/`j=[0:1]` line fires. Mirrors
115
122
  // _eval_for's `_check_debug(assign_node, child)`.
116
- checkDebug(*node.assignments[depth], childCtx);
123
+ checkDebug(*assign, childCtx);
117
124
  recurse(depth + 1, childCtx);
118
125
  }
119
126
  };
@@ -925,8 +925,28 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
925
925
  // trail level, and TrailView::items() is ancestry-visible, so reading
926
926
  // from effCtx forwards both the call's own overrides AND everything
927
927
  // the wrapper module's body set (`$fn = 100; children();`).
928
+ //
929
+ // EXCLUDING "$children"/"$parent_modules" is required, not incidental:
930
+ // both are set on `ctx` by buildModuleChildCtx for the WRAPPER module's
931
+ // own invocation (e.g. a `left(x) { ... }` positioning helper's own
932
+ // child count/nesting depth), completely unrelated to the forwarded
933
+ // TARGET's real values -- which `evalCtx` already correctly inherited
934
+ // above, from `callerCtx` (the actual forwarding target), a few lines
935
+ // up. Before this exclusion, a bare `children()` forwarded through ANY
936
+ // intermediate wrapper module silently replaced the target's own real
937
+ // `$children` with the wrapper's own (e.g. `left(x) { if ($children >
938
+ // N) children(N); }`, an extremely common BOSL/BOSL2-style guarded-
939
+ // forwarding idiom, would read the WRAPPER's own 1-child count instead
940
+ // of the real caller's, corrupting every such conditional -- silently
941
+ // dropping geometry with no warning of any kind, since the guard just
942
+ // evaluates false instead of erroring. Found via a real user script
943
+ // (snappy-reprap's x_axis_assembly_1, GDMUtils.scad's left()/right()/
944
+ // up() positioning helpers) that render a large fraction of the model
945
+ // through exactly this pattern -- confirmed present since at least
946
+ // v0.13.4 (this port's very first PyPI release), unrelated to any
947
+ // 2026-08-01 VM work.
928
948
  for (const auto& [k, v] : ctx.dyn->items()) {
929
- if (!k.empty() && k[0] == '$') evalCtx.dyn->set(k, v);
949
+ if (!k.empty() && k[0] == '$' && k != "$children" && k != "$parent_modules") evalCtx.dyn->set(k, v);
930
950
  }
931
951
  for (const auto& [k, v] : ctx.let_->items()) {
932
952
  if (!k.empty() && k[0] == '$') evalCtx.let_->set(k, v);