openscad-cpp-evaluator 0.51.1__tar.gz → 0.52.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/.github/workflows/wheels.yml +18 -1
  2. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/CLAUDE.md +27 -1
  3. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/bytecode.hpp +4 -1
  5. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +11 -1
  6. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/evaluator.hpp +16 -0
  7. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/pyproject.toml +1 -1
  8. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/booleans.cpp +8 -3
  9. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/color.cpp +3 -1
  10. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/control.cpp +3 -1
  11. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/extrude.cpp +12 -4
  12. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/roof.cpp +3 -1
  13. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/topology.cpp +18 -6
  14. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/transforms.cpp +3 -1
  15. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/bytecode_compiler.cpp +29 -0
  16. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/bytecode_vm.cpp +6 -3
  17. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/measure_geometry.cpp +3 -1
  18. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/stmt_eval.cpp +30 -2
  19. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_control_flow.cpp +204 -0
  20. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_debug_hooks.cpp +42 -0
  21. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_profiling.cpp +24 -3
  22. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_render_expr.cpp +9 -2
  23. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/uv.lock +1 -1
  24. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/.gitignore +0 -0
  26. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/.gitmodules +0 -0
  27. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/LICENSE +0 -0
  29. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/README.md +0 -0
  30. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/bindings/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/bindings/ast_to_py.cpp +0 -0
  32. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/bindings/module.cpp +0 -0
  33. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/cmake/embed_font.cmake +0 -0
  34. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/examples/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/examples/eval_perf_benchmark.cpp +0 -0
  36. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/examples/manifold_cache_reuse.cpp +0 -0
  37. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/examples/minimal_debugger.cpp +0 -0
  38. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  39. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/.gitignore +0 -0
  40. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  41. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  42. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/README.md +0 -0
  43. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  44. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  45. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  46. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  47. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  48. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  49. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  50. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  51. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  52. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  53. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  54. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  55. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  56. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  57. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  58. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  59. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  60. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  61. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  62. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  63. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  64. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  65. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  66. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  67. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  68. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  69. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  70. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  71. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  72. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  73. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  74. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  75. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  76. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  77. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  78. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  79. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  80. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  81. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  82. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  83. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  84. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  85. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  86. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  87. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  88. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  89. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  90. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  91. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  92. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  93. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  94. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  95. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  96. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  97. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  98. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  99. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  100. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  101. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  102. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  103. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  104. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  105. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  106. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  107. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  110. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  111. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  112. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  113. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  114. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  115. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  116. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  117. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  118. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  119. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  120. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  122. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  123. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  124. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  125. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  126. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  127. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  128. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  129. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  130. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  131. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  132. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  133. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  134. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  135. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  136. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/resources/fonts/LICENSE +0 -0
  137. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  138. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/CMakeLists.txt +0 -0
  139. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/builtins.hpp +0 -0
  140. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/call_args.cpp +0 -0
  141. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/dxf_dim.cpp +0 -0
  142. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/function_builtins.cpp +0 -0
  143. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/import.cpp +0 -0
  144. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/modifiers.cpp +0 -0
  145. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/primitives_2d.cpp +0 -0
  146. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/primitives_3d.cpp +0 -0
  147. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/registry.cpp +0 -0
  148. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/segments.cpp +0 -0
  149. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/surface.cpp +0 -0
  150. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/builtins/text.cpp +0 -0
  151. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/colored_body.cpp +0 -0
  152. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/csg_generate.cpp +0 -0
  153. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/csg_resolve.cpp +0 -0
  154. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/css_colors.cpp +0 -0
  155. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/debug/debug_repl.cpp +0 -0
  156. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/debug_profile.cpp +0 -0
  157. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/eval_context.cpp +0 -0
  158. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/eval_error.cpp +0 -0
  159. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/eval_use.cpp +0 -0
  160. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/export.cpp +0 -0
  161. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/expr_eval.cpp +0 -0
  162. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/import/dxf_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/import/mesh_import.cpp +0 -0
  164. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/import/surface_load.cpp +0 -0
  165. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/import/svg_import.cpp +0 -0
  166. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/manifold_cache.cpp +0 -0
  167. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/mesh_check.cpp +0 -0
  168. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/text/font_match.cpp +0 -0
  169. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/text/freetype_font_provider.cpp +0 -0
  170. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/text/text_metrics.cpp +0 -0
  171. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/user_calls.cpp +0 -0
  172. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/value.cpp +0 -0
  173. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/src/zip_stored.cpp +0 -0
  174. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/CMakeLists.txt +0 -0
  175. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_booleans.cpp +0 -0
  176. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_bytecode_compiler.cpp +0 -0
  177. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_children_separate.cpp +0 -0
  178. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_cli.cpp +0 -0
  179. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_csg_tree.cpp +0 -0
  180. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_dxf_svg_import.cpp +0 -0
  181. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_eval_context.cpp +0 -0
  182. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_export.cpp +0 -0
  183. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_expr_eval.cpp +0 -0
  184. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_extrude_roof.cpp +0 -0
  185. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_feature_detection.cpp +0 -0
  186. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_function_builtins.cpp +0 -0
  187. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_helpers.hpp +0 -0
  188. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_import_export.cpp +0 -0
  189. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_manifold_cache.cpp +0 -0
  190. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_mesh_check.cpp +0 -0
  191. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_multi_color_merge.cpp +0 -0
  192. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_primitives.cpp +0 -0
  193. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_python_bindings.py +0 -0
  194. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_scope_trail.cpp +0 -0
  195. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_scoping.cpp +0 -0
  196. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_surface.cpp +0 -0
  197. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_tail_calls.cpp +0 -0
  198. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_text.cpp +0 -0
  199. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_transforms.cpp +0 -0
  200. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_unexpected_args.cpp +0 -0
  201. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_use.cpp +0 -0
  202. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_value.cpp +0 -0
  203. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_viewport_params.cpp +0 -0
  204. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tests/test_zip_stored.cpp +0 -0
  205. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tools/cli/CMakeLists.txt +0 -0
  206. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tools/cli/cli_lib.cpp +0 -0
  207. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tools/cli/cli_lib.hpp +0 -0
  208. {openscad_cpp_evaluator-0.51.1 → openscad_cpp_evaluator-0.52.0}/tools/cli/main.cpp +0 -0
@@ -85,9 +85,26 @@ jobs:
85
85
  # grammar requires %require "3.8". No newer bison package is
86
86
  # available via dnf on this base image, so build one from source
87
87
  # (fast: a few seconds) rather than switching manylinux images.
88
+ # Mirrors first, ftp.gnu.org last: ftp.gnu.org went unreachable
89
+ # (curl 7, connection timed out on 443) on both architectures for
90
+ # two consecutive runs of the v0.51.3 release, and it is a single
91
+ # point of failure for every Linux wheel we ship.
92
+ # ftpmirror.gnu.org is GNU's own redirector to a nearby mirror.
93
+ # Every line stays at THIS indent on purpose -- a more-indented
94
+ # line in a `>-` folded scalar keeps its newline instead of
95
+ # folding to a space, which would split a URL from its curl.
96
+ # CURL FLAG CEILING: this runs against AlmaLinux 8's curl 7.61,
97
+ # NOT the host's. --retry (7.12.3), --retry-connrefused (7.52)
98
+ # and --connect-timeout (7.7) are fine; --retry-all-errors is
99
+ # 7.71 and made every Linux wheel fail with
100
+ # "option --retry-all-errors: is unknown". Check `Added:` in
101
+ # curl/docs/cmdline-opts/<opt>.md before adding a flag here --
102
+ # testing the command on a Mac proves nothing about this curl.
88
103
  CIBW_BEFORE_ALL_LINUX: >-
89
104
  dnf install -y flex &&
90
- curl -fsSL https://ftp.gnu.org/gnu/bison/bison-3.8.2.tar.gz -o /tmp/bison.tar.gz &&
105
+ { curl -fsSL --retry 3 --retry-connrefused --connect-timeout 20 https://ftpmirror.gnu.org/bison/bison-3.8.2.tar.gz -o /tmp/bison.tar.gz ||
106
+ curl -fsSL --retry 3 --retry-connrefused --connect-timeout 20 https://mirrors.kernel.org/gnu/bison/bison-3.8.2.tar.gz -o /tmp/bison.tar.gz ||
107
+ curl -fsSL --retry 3 --retry-connrefused --connect-timeout 20 https://ftp.gnu.org/gnu/bison/bison-3.8.2.tar.gz -o /tmp/bison.tar.gz ; } &&
91
108
  cd /tmp && tar xzf bison.tar.gz && cd bison-3.8.2 &&
92
109
  ./configure --prefix=/usr/local && make -j$(nproc) && make install
93
110
  # Mirrors ci.yml's macOS step: the hosted runner image ships an
@@ -584,7 +584,33 @@ grep for `ponytail:`.
584
584
  mutable state threaded through recursive evaluation. `dyn`/`let_`/`dynPositions`/`dynExplicit`
585
585
  are `shared_ptr`-wrapped, not plain value members — `withScope()` relies on that aliasing to
586
586
  give sibling statements in the same block correct assignment visibility (see the header comment
587
- before reworking this; it's not an arbitrary choice).
587
+ before reworking this; it's not an arbitrary choice). **An `if`/`else` branch body is its own
588
+ scope**: assignments (including `$`-var assignments) inside it are visible for the rest of the
589
+ branch but must not survive the closing brace, so both engines evaluate a taken branch against
590
+ `childCtx()` — `branchScope()` in `stmt_eval.cpp`, `Op::OpenLetScope`/`Op::CloseExprScope`
591
+ around each branch on the compiled path. `let_`/`dyn` open a non-isolated child (the branch
592
+ reads everything outside, writes only inside) while `dynPositions` opens an **isolated** one, so
593
+ shadowing an outer name is not mistaken for reassigning it — two assignments to the same name
594
+ *within* one branch still warn, because they share that fresh level. Getting this wrong is not
595
+ just a spurious warning: before the fix a branch's writes escaped and silently changed later
596
+ geometry (BOSL2's `half_of()` reassigns `v` inside an `else if`, clobbering the outer `v`).
597
+ **A builtin operator's braced child block (`translate() { a = 1; }`) is its own scope by the
598
+ same rule.** Each builtin resolve function derives one `Evaluator::blockScope()` context for its
599
+ children (`booleans.cpp` derives ONE spanning both of `resolveCsg`'s passes, since the assignment
600
+ pass writes what the geometry pass reads); `emitBuiltinWrap`/`emitCsgWrap` bracket their inline
601
+ children with `Op::OpenLetScope`/`Op::CloseExprScope`. The bracket goes *inside* Push/Pop, because
602
+ `Op::PopBuiltinWrap` re-resolves the call's arguments and those must still see the OUTER ctx — an
603
+ assignment in the block must never feed back into the operator's own arguments.
604
+ **Do not "simplify" this into `resolveCallArgs`** by having it always return a child ctx, however
605
+ tempting — every builtin's child context does come from there, but so does every *user*-module
606
+ call's and every pure argument resolution, and the extra dyn level breaks BOSL2's `$`-var
607
+ propagation outright (`$tags_shown`/`$attach_to`/`$transform`/`$anchor_override` stop resolving).
608
+ That was tried: the whole unit suite passed and 130 of ~390 BOSL2 doc examples failed. Scope the
609
+ CHILDREN, not the call. `for`/`let`/`intersection_for` bodies, and a *user* module's child block (evaluated
610
+ through `childrenCallerCtx`), were already correct. `tests/test_control_flow.cpp`'s
611
+ `IfBranchScope.*` and `OperatorBlockScope.*` pin every case under both engines. One older test
612
+ (`RenderExpr.MeasuredSubtreeUsesItsOwnCoordinates`) had encoded the leak by reading a block's
613
+ variable from outside it, and was corrected.
588
614
  - `include/openscad_cpp_evaluator/eval_error.hpp`, `src/eval_error.cpp` — `EvalError` and the
589
615
  exact ERROR/TRACE string formatting real OpenSCAD produces. Also the **unexpected-argument
590
616
  warnings** (`warnUnexpectedNamedArg`/`warnTooManyPositionalArgs`/`warnUnexpectedArgs`), the port
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.51.1
3
+ Version: 0.52.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -687,7 +687,10 @@ enum class Op {
687
687
  // just "pop the current ctx level", identical either way.
688
688
  CloseExprScope,
689
689
 
690
- // ModularLet's own (statement-form) child scope -- mirrors
690
+ // ModularLet's own (statement-form) child scope, ALSO used to bracket
691
+ // an if/else branch body (see ModularIf/ModularIfElse in
692
+ // bytecode_compiler.cpp) -- a branch is its own scope in OpenSCAD and
693
+ // needs exactly this same child ctx, so one opcode covers both -- mirrors
691
694
  // Evaluator::evalLetBlock's own `ctx.childCtx(nullptr, std::nullopt,
692
695
  // ctx.childrenNodes, ctx.childrenCallerCtx)` call exactly, pushed onto
693
696
  // f.ctxChain (Op::CloseExprScope, above, pops it back off once the
@@ -12,6 +12,7 @@
12
12
  #include <cstdint>
13
13
  #include <memory>
14
14
  #include <optional>
15
+ #include <deque>
15
16
  #include <vector>
16
17
 
17
18
  namespace oscadeval {
@@ -155,7 +156,16 @@ struct VmFrame {
155
156
  std::vector<bool> bound;
156
157
  std::vector<std::vector<Value>> accumStack;
157
158
  std::vector<IterList> iterLists;
158
- std::vector<EvalContext> ctxChain;
159
+ // std::deque, NOT vector: Evaluator::enterUserCall stores
160
+ // CallStackFrame::bodyCtx as a pointer to ctxChain.back() (see
161
+ // pushBracketedCallFrame/pushBracketedModuleFrame), and that pointer is
162
+ // read LATER, by any checkDebug() that walks the whole call stack to
163
+ // build per-frame debugger locals. A vector reallocates on the next
164
+ // push -- which every transform inside a compiled body does, via
165
+ // Op::PushBuiltinWrap -- leaving bodyCtx pointing at the moved-from
166
+ // element (null let_) and segfaulting buildDebugFrame. deque never
167
+ // invalidates references to existing elements on push_back/pop_back.
168
+ std::deque<EvalContext> ctxChain;
159
169
  // Still-open Op::PushBuiltinWrap brackets, LIFO, scoped to THIS frame's
160
170
  // own instruction stream -- mirrors accumStack/iterLists' own role for
161
171
  // their respective bracket pairs. Normal execution always drains this
@@ -144,6 +144,22 @@ public:
144
144
  // sub-groups of an existing children list without needing ownership.
145
145
  void evalChildren(const std::vector<const oscad::ASTNode*>& children, EvalContext& ctx);
146
146
 
147
+ // The child context a BRACED BLOCK's statements run in. A `{ ... }`
148
+ // block is a scope in OpenSCAD: assignments (and `$`-var assignments)
149
+ // inside it are visible for the rest of the block but must not survive
150
+ // the closing brace. Every builtin operator's resolve function derives
151
+ // one of these for its children; the compiled path does the same with
152
+ // an Op::OpenLetScope/Op::CloseExprScope bracket.
153
+ //
154
+ // Deliberately NOT folded into resolveCallArgs, though that is the one
155
+ // place every builtin's child context comes from: resolveCallArgs also
156
+ // serves user-module calls and pure argument resolution, and giving
157
+ // those an extra dyn level breaks BOSL2's `$`-var propagation
158
+ // ($tags_shown/$attach_to/$transform all stop resolving). Measured, not
159
+ // reasoned: 130 of ~390 BOSL2 doc examples failed that way. Scope the
160
+ // CHILDREN, not the call.
161
+ EvalContext blockScope(const EvalContext& ctx) const;
162
+
147
163
  // Top-level entry point: resolveTree() + generateTree(), then the
148
164
  // top-level `!` (show_only) filter -- if any returned body has
149
165
  // BodyRole::ShowOnly, the result is filtered down to just
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.51.1"
7
+ version = "0.52.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -91,22 +91,27 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
91
91
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
92
92
  (void)args;
93
93
 
94
+ // ONE scope around both passes -- see Evaluator::blockScope. The
95
+ // assignment pass writes into the same block scope the geometry pass
96
+ // then reads from, so it cannot be scoped per-pass.
97
+ EvalContext blockCtx = ev.blockScope(effCtx);
98
+
94
99
  std::vector<const oscad::ASTNode*> assignNodes;
95
100
  std::vector<const oscad::ASTNode*> geoNodes;
96
- for (const oscad::ASTNode* c : ev.expandChildStatements(node.children, effCtx)) {
101
+ for (const oscad::ASTNode* c : ev.expandChildStatements(node.children, blockCtx)) {
97
102
  if (c->kind() == oscad::NodeKind::Assignment) {
98
103
  assignNodes.push_back(c);
99
104
  } else if (c->kind() != oscad::NodeKind::ModuleDeclaration && c->kind() != oscad::NodeKind::FunctionDeclaration) {
100
105
  geoNodes.push_back(c);
101
106
  }
102
107
  }
103
- if (!assignNodes.empty()) ev.evalChildren(assignNodes, effCtx);
108
+ if (!assignNodes.empty()) ev.evalChildren(assignNodes, blockCtx);
104
109
 
105
110
  std::vector<Value> groupSizes;
106
111
  groupSizes.reserve(geoNodes.size());
107
112
  for (const oscad::ASTNode* geoNode : geoNodes) {
108
113
  const size_t before = ev.currentTreeFrameSize();
109
- ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, effCtx);
114
+ ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, blockCtx);
110
115
  groupSizes.push_back(Value{static_cast<double>(ev.currentTreeFrameSize() - before)});
111
116
  }
112
117
 
@@ -44,7 +44,9 @@ CSGParams resolveColor(Evaluator& ev, const oscad::ModularCall& node, EvalContex
44
44
  // under the color-tagged context -- the returned bodies themselves are
45
45
  // unused here; generate reads them back via each child's own .bodies
46
46
  // and stamps `rgba` onto all of them. Mirrors _resolve_color.
47
- ev.evalChildren(node.children, result.ctx);
47
+ // The child block is its own scope -- see Evaluator::blockScope.
48
+ EvalContext blockCtx = ev.blockScope(result.ctx);
49
+ ev.evalChildren(node.children, blockCtx);
48
50
  return std::move(result.params);
49
51
  }
50
52
 
@@ -31,7 +31,9 @@ std::vector<ColoredBody> generateChildren(Evaluator&, const CSGParams&, const st
31
31
  CSGParams resolveRender(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
32
32
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
33
33
  (void)args;
34
- ev.evalChildren(node.children, effCtx);
34
+ // The child block is its own scope -- see Evaluator::blockScope.
35
+ EvalContext blockCtx = ev.blockScope(effCtx);
36
+ ev.evalChildren(node.children, blockCtx);
35
37
  return CSGParams{};
36
38
  }
37
39
 
@@ -56,7 +56,9 @@ BuiltinWrapParams computeLinearExtrudeParams(Evaluator& ev, const oscad::Modular
56
56
 
57
57
  CSGParams resolveLinearExtrude(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
58
58
  BuiltinWrapParams result = computeLinearExtrudeParams(ev, node, ctx);
59
- ev.evalChildren(node.children, result.ctx);
59
+ // The child block is its own scope -- see Evaluator::blockScope.
60
+ EvalContext blockCtx = ev.blockScope(result.ctx);
61
+ ev.evalChildren(node.children, blockCtx);
60
62
  return std::move(result.params);
61
63
  }
62
64
 
@@ -107,7 +109,9 @@ BuiltinWrapParams computeRotateExtrudeParams(Evaluator& ev, const oscad::Modular
107
109
 
108
110
  CSGParams resolveRotateExtrude(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
109
111
  BuiltinWrapParams result = computeRotateExtrudeParams(ev, node, ctx);
110
- ev.evalChildren(node.children, result.ctx);
112
+ // The child block is its own scope -- see Evaluator::blockScope.
113
+ EvalContext blockCtx = ev.blockScope(result.ctx);
114
+ ev.evalChildren(node.children, blockCtx);
111
115
  return std::move(result.params);
112
116
  }
113
117
 
@@ -143,7 +147,9 @@ BuiltinWrapParams computeProjectionParams(Evaluator& ev, const oscad::ModularCal
143
147
 
144
148
  CSGParams resolveProjection(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
145
149
  BuiltinWrapParams result = computeProjectionParams(ev, node, ctx);
146
- ev.evalChildren(node.children, result.ctx);
150
+ // The child block is its own scope -- see Evaluator::blockScope.
151
+ EvalContext blockCtx = ev.blockScope(result.ctx);
152
+ ev.evalChildren(node.children, blockCtx);
147
153
  return std::move(result.params);
148
154
  }
149
155
 
@@ -209,7 +215,9 @@ BuiltinWrapParams computeOffsetParams(Evaluator& ev, const oscad::ModularCall& n
209
215
 
210
216
  CSGParams resolveOffset(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
211
217
  BuiltinWrapParams result = computeOffsetParams(ev, node, ctx);
212
- ev.evalChildren(node.children, result.ctx);
218
+ // The child block is its own scope -- see Evaluator::blockScope.
219
+ EvalContext blockCtx = ev.blockScope(result.ctx);
220
+ ev.evalChildren(node.children, blockCtx);
213
221
  return std::move(result.params);
214
222
  }
215
223
 
@@ -464,7 +464,9 @@ CSGParams computeRoofParams(Evaluator& ev, const CallArgs& args, EvalContext& ef
464
464
 
465
465
  CSGParams resolveRoof(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
466
466
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
467
- ev.evalChildren(node.children, effCtx);
467
+ // The child block is its own scope -- see Evaluator::blockScope.
468
+ EvalContext blockCtx = ev.blockScope(effCtx);
469
+ ev.evalChildren(node.children, blockCtx);
468
470
  return computeRoofParams(ev, args, effCtx);
469
471
  }
470
472
 
@@ -23,7 +23,9 @@ static constexpr double kDefaultSimplifyFraction = 0.001;
23
23
  CSGParams resolveHull(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
24
24
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
25
25
  (void)args;
26
- ev.evalChildren(node.children, effCtx);
26
+ // The child block is its own scope -- see Evaluator::blockScope.
27
+ EvalContext blockCtx = ev.blockScope(effCtx);
28
+ ev.evalChildren(node.children, blockCtx);
27
29
  return CSGParams{};
28
30
  }
29
31
 
@@ -85,7 +87,9 @@ std::vector<ColoredBody> generateHull(Evaluator&, const CSGParams&, const std::v
85
87
  CSGParams resolveFill(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
86
88
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
87
89
  (void)args;
88
- ev.evalChildren(node.children, effCtx);
90
+ // The child block is its own scope -- see Evaluator::blockScope.
91
+ EvalContext blockCtx = ev.blockScope(effCtx);
92
+ ev.evalChildren(node.children, blockCtx);
89
93
  return CSGParams{};
90
94
  }
91
95
 
@@ -146,7 +150,9 @@ std::vector<ColoredBody> generateFill(Evaluator& ev, const CSGParams&,
146
150
  CSGParams resolveMinkowski(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
147
151
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
148
152
  (void)args;
149
- ev.evalChildren(node.children, effCtx);
153
+ // The child block is its own scope -- see Evaluator::blockScope.
154
+ EvalContext blockCtx = ev.blockScope(effCtx);
155
+ ev.evalChildren(node.children, blockCtx);
150
156
  return CSGParams{};
151
157
  }
152
158
 
@@ -334,7 +340,9 @@ std::vector<ColoredBody> generateMinkowski(Evaluator& ev, const CSGParams&, cons
334
340
  CSGParams resolveMinkowskiDifference(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
335
341
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
336
342
  (void)args;
337
- ev.evalChildren(node.children, effCtx);
343
+ // The child block is its own scope -- see Evaluator::blockScope.
344
+ EvalContext blockCtx = ev.blockScope(effCtx);
345
+ ev.evalChildren(node.children, blockCtx);
338
346
  return CSGParams{};
339
347
  }
340
348
 
@@ -394,7 +402,9 @@ CSGParams resolveSimplify(Evaluator& ev, const oscad::ModularCall& node, EvalCon
394
402
  CSGParams params;
395
403
  // Position 0 so simplify(0.1) reads naturally.
396
404
  params["tolerance"] = getArg(args, 0, "tolerance", Value{});
397
- ev.evalChildren(node.children, effCtx);
405
+ // The child block is its own scope -- see Evaluator::blockScope.
406
+ EvalContext blockCtx = ev.blockScope(effCtx);
407
+ ev.evalChildren(node.children, blockCtx);
398
408
  return params;
399
409
  }
400
410
 
@@ -715,7 +725,9 @@ CSGParams resolveLevelSet(Evaluator& ev, const oscad::ModularCall& node, EvalCon
715
725
  params["isovalue"] = getArg(args, 2, "isovalue", Value{0.0});
716
726
  params["invert"] = getArg(args, 3, "invert", Value{false});
717
727
  params["edge"] = getArg(args, 4, "edge", Value{});
718
- ev.evalChildren(node.children, effCtx);
728
+ // The child block is its own scope -- see Evaluator::blockScope.
729
+ EvalContext blockCtx = ev.blockScope(effCtx);
730
+ ev.evalChildren(node.children, blockCtx);
719
731
  return params;
720
732
  }
721
733
 
@@ -219,7 +219,9 @@ BuiltinWrapParams computeTransformParams(Evaluator& ev, const oscad::ModularCall
219
219
 
220
220
  CSGParams resolveTransform(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
221
221
  BuiltinWrapParams result = computeTransformParams(ev, node, ctx);
222
- ev.evalChildren(node.children, result.ctx);
222
+ // The child block is its own scope -- see Evaluator::blockScope.
223
+ EvalContext blockCtx = ev.blockScope(result.ctx);
224
+ ev.evalChildren(node.children, blockCtx);
223
225
  return std::move(result.params);
224
226
  }
225
227
 
@@ -1289,7 +1289,15 @@ public:
1289
1289
  chunk_.builtinWrapSites.push_back(std::move(site));
1290
1290
  const int idx = static_cast<int>(chunk_.builtinWrapSites.size()) - 1;
1291
1291
  out.push_back({Op::PushBuiltinWrap, idx, 0, &wrapperNode.position()});
1292
+ // The child block is its own scope -- same rule as an if-branch, and
1293
+ // the interpreter gets it from resolveCallArgs' child ctx (see
1294
+ // call_args.cpp). Opened INSIDE the Push/Pop bracket so that
1295
+ // Op::PopBuiltinWrap's own resolveCallArgs call still sees the
1296
+ // OUTER ctx: an assignment in the block must not feed back into the
1297
+ // operator's own arguments.
1298
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1292
1299
  compileStatementList(children, out);
1300
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1293
1301
  out.push_back({Op::PopBuiltinWrap, idx, 0, &wrapperNode.position()});
1294
1302
  }
1295
1303
 
@@ -1312,6 +1320,10 @@ public:
1312
1320
  chunk_.csgWrapSites.push_back(std::move(site));
1313
1321
  const int idx = static_cast<int>(chunk_.csgWrapSites.size()) - 1;
1314
1322
  out.push_back({Op::PushCsgWrap, idx, 0, &call.position()});
1323
+ // One scope around BOTH passes -- see emitBuiltinWrap's own note.
1324
+ // The assignment pass must write into the same block scope the
1325
+ // geometry pass then reads from, so it cannot be bracketed per-pass.
1326
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1315
1327
 
1316
1328
  std::vector<const oscad::ASTNode*> assignNodes;
1317
1329
  std::vector<const oscad::ASTNode*> geoNodes;
@@ -1335,6 +1347,7 @@ public:
1335
1347
  compileStatementList(std::vector<const oscad::ASTNode*>{geoNode}, out);
1336
1348
  out.push_back({Op::CsgGroupEnd, 0, 0, nullptr});
1337
1349
  }
1350
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1338
1351
  out.push_back({Op::PopCsgWrap, idx, 0, &call.position()});
1339
1352
  }
1340
1353
 
@@ -1688,13 +1701,25 @@ public:
1688
1701
  emitBuiltinWrap(CompiledChunk::BuiltinWrapSite::Kind::Modifier, "show_only", n, {n.child.get()}, out);
1689
1702
  return;
1690
1703
  }
1704
+ // Both if-forms bracket the taken branch in Op::OpenLetScope /
1705
+ // Op::CloseExprScope. An if-branch body is its own scope in
1706
+ // OpenSCAD -- assignments inside must not survive the closing
1707
+ // brace -- and the child ctx a let-BLOCK gets is exactly the
1708
+ // one a branch needs, so the same opcode serves both (see
1709
+ // branchScope, stmt_eval.cpp, for the interpreter's identical
1710
+ // rule and the full reasoning). The scope is opened INSIDE the
1711
+ // conditional jump's target range, so a branch that is not
1712
+ // taken never opens one, and each branch's own Close is the
1713
+ // last thing before the jump past its sibling.
1691
1714
  case NodeKind::ModularIf: {
1692
1715
  auto& n = static_cast<const oscad::ModularIf&>(stmt);
1693
1716
  const int jumpFalseIdx = static_cast<int>(out.size());
1694
1717
  out.push_back({Op::NativeCondJumpIfFalse, internNativeExpr(n.condition.get()), 0, &n.condition->position()});
1695
1718
  const oscad::ASTNode* marker = n.trueBranch.empty() ? &stmt : n.trueBranch.front().get();
1696
1719
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(marker), 0, nullptr});
1720
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1697
1721
  compileStatementList(n.trueBranch, out);
1722
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1698
1723
  out[static_cast<size_t>(jumpFalseIdx)].b = static_cast<int>(out.size());
1699
1724
  return;
1700
1725
  }
@@ -1704,13 +1729,17 @@ public:
1704
1729
  out.push_back({Op::NativeCondJumpIfFalse, internNativeExpr(n.condition.get()), 0, &n.condition->position()});
1705
1730
  const oscad::ASTNode* trueMarker = n.trueBranch.empty() ? &stmt : n.trueBranch.front().get();
1706
1731
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(trueMarker), 0, nullptr});
1732
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1707
1733
  compileStatementList(n.trueBranch, out);
1734
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1708
1735
  const int jumpEndIdx = static_cast<int>(out.size());
1709
1736
  out.push_back({Op::Jump, 0, 0, nullptr});
1710
1737
  out[static_cast<size_t>(jumpFalseIdx)].b = static_cast<int>(out.size());
1711
1738
  const oscad::ASTNode* falseMarker = n.falseBranch.empty() ? &stmt : n.falseBranch.front().get();
1712
1739
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(falseMarker), 0, nullptr});
1740
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1713
1741
  compileStatementList(n.falseBranch, out);
1742
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1714
1743
  out[static_cast<size_t>(jumpEndIdx)].a = static_cast<int>(out.size());
1715
1744
  return;
1716
1745
  }
@@ -270,8 +270,11 @@ void pushBracketedCallFrame(Evaluator& ev, const CompiledChunk& chunk, const osc
270
270
  applyCompiledDefaultsToFrame(ev, chunk, *frame);
271
271
 
272
272
  // enterUserCall's own bodyCtx must point at frame->ctxChain's OWN
273
- // storage (stable: this VmFrame lives behind a unique_ptr, so its
274
- // address never moves even if vmCallStack_ itself reallocates), not
273
+ // storage (stable on two counts: this VmFrame lives behind a
274
+ // unique_ptr, so its address never moves even if vmCallStack_ itself
275
+ // reallocates, AND ctxChain is a std::deque, so a later push onto it
276
+ // never moves the element handed over here -- see its own doc comment,
277
+ // bytecode_vm.hpp, for the segfault a vector caused), not
275
278
  // a plain local -- a local `childCtx` here would be destroyed the
276
279
  // instant this function returns, but CallStackFrame::bodyCtx is read
277
280
  // LATER, by ANY subsequent checkDebug() call (this call's own nested
@@ -328,7 +331,7 @@ void pushBracketedModuleFrame(Evaluator& ev, const CompiledChunk& chunk, const o
328
331
  frame->logicalName = decl.name->name;
329
332
  frame->ownsModuleSplice = true;
330
333
  // enterUserCall's own bodyCtx must point at frame->ctxChain's OWN
331
- // storage, not the caller's own (about-to-be-destroyed) local
334
+ // (deque-stable) storage, not the caller's own (about-to-be-destroyed) local
332
335
  // `childCtx` -- see pushBracketedCallFrame's own doc comment, above,
333
336
  // for the full dangling-pointer hazard this avoids (same fix, same
334
337
  // root cause, module side).
@@ -228,7 +228,9 @@ Value Evaluator::evalRenderExpr(const oscad::RenderExpression& node, EvalContext
228
228
  treeStack_.emplace_back();
229
229
  std::vector<std::unique_ptr<CSGNode>> sub;
230
230
  try {
231
- evalChildren(node.children, effCtx);
231
+ // The child block is its own scope -- see blockScope.
232
+ EvalContext blockCtx = blockScope(effCtx);
233
+ evalChildren(node.children, blockCtx);
232
234
  sub = std::move(treeStack_.back());
233
235
  treeStack_.pop_back();
234
236
  } catch (...) {
@@ -6,6 +6,32 @@
6
6
 
7
7
  namespace oscadeval {
8
8
 
9
+ // A braced block is its own scope in OpenSCAD: assignments (and `$`-var
10
+ // assignments) inside it are visible to the rest of the block and to
11
+ // anything it calls, but must NOT survive the closing brace.
12
+ //
13
+ // a = 1;
14
+ // if (true) { a = 2; }
15
+ // echo(a); // 1 in OpenSCAD -- was 2 here
16
+ //
17
+ // Evaluating a block against the enclosing ctx let those writes escape,
18
+ // which silently changed geometry (BOSL2's half_of() reassigns `v` inside
19
+ // an `else if`, clobbering the outer `v` for everything after it) and also
20
+ // produced a bogus "assigned on line N but was overwritten" warning for
21
+ // what is really a shadow, not an overwrite. Verified against OpenSCAD
22
+ // 2026.02.01 for values, `$`-vars, nesting, and the same-scope warning
23
+ // that must still fire.
24
+ //
25
+ // This is exactly the child context a let-BLOCK gets (evalLetBlock, and
26
+ // Op::OpenLetScope on the compiled path): let_/dyn open a non-isolated
27
+ // child so the block reads everything outside and writes only inside,
28
+ // while dynPositions opens an ISOLATED one so shadowing an outer name is
29
+ // not mistaken for reassigning it -- two assignments to the same name
30
+ // within the one block still warn, because they share this fresh level.
31
+ EvalContext Evaluator::blockScope(const EvalContext& ctx) const {
32
+ return ctx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
33
+ }
34
+
9
35
  void Evaluator::evalAssignment(const oscad::Assignment& node, EvalContext& ctx) {
10
36
  const std::string& name = node.name->name;
11
37
  if (!name.empty() && name[0] == '$') {
@@ -196,7 +222,8 @@ void Evaluator::evalStatement(const oscad::ASTNode& node, EvalContext& ctx) {
196
222
  if (truthy(evalExprMaybeCompiled(*n.condition, ctx))) {
197
223
  checkDebug(n.trueBranch.empty() ? node : *n.trueBranch.front(), ctx, /*forced=*/false,
198
224
  /*exprLevel=*/true);
199
- evalChildren(n.trueBranch, ctx);
225
+ EvalContext branchCtx = blockScope(ctx);
226
+ evalChildren(n.trueBranch, branchCtx);
200
227
  }
201
228
  return;
202
229
  }
@@ -204,7 +231,8 @@ void Evaluator::evalStatement(const oscad::ASTNode& node, EvalContext& ctx) {
204
231
  auto& n = static_cast<const oscad::ModularIfElse&>(node);
205
232
  const auto& branch = truthy(evalExprMaybeCompiled(*n.condition, ctx)) ? n.trueBranch : n.falseBranch;
206
233
  checkDebug(branch.empty() ? node : *branch.front(), ctx, /*forced=*/false, /*exprLevel=*/true);
207
- evalChildren(branch, ctx);
234
+ EvalContext branchCtx = blockScope(ctx);
235
+ evalChildren(branch, branchCtx);
208
236
  return;
209
237
  }
210
238
  case oscad::NodeKind::ModularFor: