openscad-cpp-evaluator 0.51.3__tar.gz → 0.53.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.3 → openscad_cpp_evaluator-0.53.0}/CLAUDE.md +48 -1
  2. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/bytecode.hpp +19 -1
  4. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/evaluator.hpp +16 -0
  5. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/booleans.cpp +8 -3
  7. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/color.cpp +3 -1
  8. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/control.cpp +3 -1
  9. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/extrude.cpp +12 -4
  10. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/function_builtins.cpp +142 -28
  11. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/roof.cpp +3 -1
  12. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/topology.cpp +18 -6
  13. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/transforms.cpp +3 -1
  14. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/bytecode_compiler.cpp +35 -3
  15. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/bytecode_vm.cpp +9 -1
  16. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/measure_geometry.cpp +3 -1
  17. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/stmt_eval.cpp +30 -2
  18. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_control_flow.cpp +282 -0
  19. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_function_builtins.cpp +68 -0
  20. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_render_expr.cpp +9 -2
  21. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/.github/workflows/ci.yml +0 -0
  22. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/.github/workflows/wheels.yml +0 -0
  23. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/.gitignore +0 -0
  24. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/.gitmodules +0 -0
  25. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/LICENSE +0 -0
  27. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/README.md +0 -0
  28. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/bindings/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/bindings/ast_to_py.cpp +0 -0
  30. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/bindings/module.cpp +0 -0
  31. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/cmake/embed_font.cmake +0 -0
  32. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/examples/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/examples/eval_perf_benchmark.cpp +0 -0
  34. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/examples/manifold_cache_reuse.cpp +0 -0
  35. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/examples/minimal_debugger.cpp +0 -0
  36. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  37. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/.gitignore +0 -0
  38. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  39. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/README.md +0 -0
  41. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  42. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  43. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  45. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  46. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  47. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  48. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  49. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  50. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  51. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  52. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  53. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  54. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  55. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  56. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  57. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  58. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  59. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  60. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  61. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  62. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  63. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  64. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  65. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  66. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  67. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  68. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  69. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  70. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  71. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  72. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  73. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  74. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  75. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  76. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  77. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  78. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  79. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  80. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  81. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  82. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  83. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  84. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  85. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  86. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  87. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  88. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  89. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  90. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  91. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  92. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  93. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  94. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  95. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  96. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  97. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  98. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  99. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  100. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  101. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  102. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  103. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  104. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  105. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  106. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  107. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  108. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  109. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  110. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  111. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  112. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  113. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  114. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  115. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  116. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  117. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  118. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  119. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  122. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  123. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  124. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  125. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  126. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  127. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  128. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  129. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  130. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  131. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  132. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  135. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  137. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/CMakeLists.txt +0 -0
  138. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/builtins.hpp +0 -0
  139. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/call_args.cpp +0 -0
  140. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/dxf_dim.cpp +0 -0
  141. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/import.cpp +0 -0
  142. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/modifiers.cpp +0 -0
  143. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/primitives_2d.cpp +0 -0
  144. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/primitives_3d.cpp +0 -0
  145. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/registry.cpp +0 -0
  146. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/segments.cpp +0 -0
  147. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/surface.cpp +0 -0
  148. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/builtins/text.cpp +0 -0
  149. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/colored_body.cpp +0 -0
  150. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/csg_generate.cpp +0 -0
  151. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/csg_resolve.cpp +0 -0
  152. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/css_colors.cpp +0 -0
  153. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/debug/debug_repl.cpp +0 -0
  154. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/debug_profile.cpp +0 -0
  155. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/eval_context.cpp +0 -0
  156. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/eval_error.cpp +0 -0
  157. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/eval_use.cpp +0 -0
  158. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/export.cpp +0 -0
  159. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/expr_eval.cpp +0 -0
  160. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/import/dxf_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/import/mesh_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/import/surface_load.cpp +0 -0
  163. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/import/svg_import.cpp +0 -0
  164. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/manifold_cache.cpp +0 -0
  165. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/mesh_check.cpp +0 -0
  166. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/text/font_match.cpp +0 -0
  167. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/text/freetype_font_provider.cpp +0 -0
  168. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/text/text_metrics.cpp +0 -0
  169. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/user_calls.cpp +0 -0
  170. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/value.cpp +0 -0
  171. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/CMakeLists.txt +0 -0
  173. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_booleans.cpp +0 -0
  174. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_bytecode_compiler.cpp +0 -0
  175. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_children_separate.cpp +0 -0
  176. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_cli.cpp +0 -0
  177. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_csg_tree.cpp +0 -0
  178. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_debug_hooks.cpp +0 -0
  179. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_dxf_svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_eval_context.cpp +0 -0
  181. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_export.cpp +0 -0
  182. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_expr_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_extrude_roof.cpp +0 -0
  184. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_feature_detection.cpp +0 -0
  185. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_primitives.cpp +0 -0
  191. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_profiling.cpp +0 -0
  192. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_python_bindings.py +0 -0
  193. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_scope_trail.cpp +0 -0
  194. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_scoping.cpp +0 -0
  195. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_surface.cpp +0 -0
  196. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_tail_calls.cpp +0 -0
  197. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_text.cpp +0 -0
  198. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_transforms.cpp +0 -0
  199. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_unexpected_args.cpp +0 -0
  200. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_use.cpp +0 -0
  201. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.51.3 → openscad_cpp_evaluator-0.53.0}/uv.lock +0 -0
@@ -577,6 +577,17 @@ grep for `ponytail:`.
577
577
  profiled is BOSL2's **argument type-checking**: 25–45%, millions of
578
578
  `is_finite`/`is_num`/`is_nan` calls.
579
579
 
580
+ - **Degree trigonometry is a direct port of OpenSCAD's `src/utils/degree_trig.cc`**
581
+ (`sinDegrees`/`cosDegrees`/`tanDegrees` and the inverses, `builtins/function_builtins.cpp`), not
582
+ `std::sin(x * pi / 180)`. The structure is load-bearing: exact values at 30/45/60, the >45 range
583
+ computed through the COMPLEMENT function, and OpenSCAD's own literal `M_DEG2RAD` constant rather
584
+ than `pi / 180.0` (which rounds differently). That is what makes `sin(45) == cos(45)` and
585
+ `cos(30) == sqrt(3)/2` hold EXACTLY, and the exactness matters to real libraries: BOSL2's
586
+ `rect(rounding=...)` filters corner intersections with an exact `isect != seg[0]`, so one ULP of
587
+ drift made a shared vertex compare unequal, kept two corner points instead of one, and killed
588
+ `rect()` on its own `assert(len(cornerpt)==1, "Cannot find corner point to anchor")`. The
589
+ inverses also snap to a whole number of degrees when the forward function reproduces the input
590
+ exactly, so `asin(sin(30))` is `30`. Pinned by `DegreeTrig.*` in `tests/test_function_builtins.cpp`.
580
591
  - `include/openscad_cpp_evaluator/value.hpp`, `src/value.cpp` — the `Value` variant (OpenSCAD's
581
592
  dynamic value type) and its free-function arithmetic/comparison helpers. See the plan's §1 for
582
593
  the type-mapping table and why there's no numpy-equivalent library.
@@ -584,7 +595,33 @@ grep for `ponytail:`.
584
595
  mutable state threaded through recursive evaluation. `dyn`/`let_`/`dynPositions`/`dynExplicit`
585
596
  are `shared_ptr`-wrapped, not plain value members — `withScope()` relies on that aliasing to
586
597
  give sibling statements in the same block correct assignment visibility (see the header comment
587
- before reworking this; it's not an arbitrary choice).
598
+ before reworking this; it's not an arbitrary choice). **An `if`/`else` branch body is its own
599
+ scope**: assignments (including `$`-var assignments) inside it are visible for the rest of the
600
+ branch but must not survive the closing brace, so both engines evaluate a taken branch against
601
+ `childCtx()` — `branchScope()` in `stmt_eval.cpp`, `Op::OpenLetScope`/`Op::CloseExprScope`
602
+ around each branch on the compiled path. `let_`/`dyn` open a non-isolated child (the branch
603
+ reads everything outside, writes only inside) while `dynPositions` opens an **isolated** one, so
604
+ shadowing an outer name is not mistaken for reassigning it — two assignments to the same name
605
+ *within* one branch still warn, because they share that fresh level. Getting this wrong is not
606
+ just a spurious warning: before the fix a branch's writes escaped and silently changed later
607
+ geometry (BOSL2's `half_of()` reassigns `v` inside an `else if`, clobbering the outer `v`).
608
+ **A builtin operator's braced child block (`translate() { a = 1; }`) is its own scope by the
609
+ same rule.** Each builtin resolve function derives one `Evaluator::blockScope()` context for its
610
+ children (`booleans.cpp` derives ONE spanning both of `resolveCsg`'s passes, since the assignment
611
+ pass writes what the geometry pass reads); `emitBuiltinWrap`/`emitCsgWrap` bracket their inline
612
+ children with `Op::OpenLetScope`/`Op::CloseExprScope`. The bracket goes *inside* Push/Pop, because
613
+ `Op::PopBuiltinWrap` re-resolves the call's arguments and those must still see the OUTER ctx — an
614
+ assignment in the block must never feed back into the operator's own arguments.
615
+ **Do not "simplify" this into `resolveCallArgs`** by having it always return a child ctx, however
616
+ tempting — every builtin's child context does come from there, but so does every *user*-module
617
+ call's and every pure argument resolution, and the extra dyn level breaks BOSL2's `$`-var
618
+ propagation outright (`$tags_shown`/`$attach_to`/`$transform`/`$anchor_override` stop resolving).
619
+ That was tried: the whole unit suite passed and 130 of ~390 BOSL2 doc examples failed. Scope the
620
+ CHILDREN, not the call. `for`/`let`/`intersection_for` bodies, and a *user* module's child block (evaluated
621
+ through `childrenCallerCtx`), were already correct. `tests/test_control_flow.cpp`'s
622
+ `IfBranchScope.*` and `OperatorBlockScope.*` pin every case under both engines. One older test
623
+ (`RenderExpr.MeasuredSubtreeUsesItsOwnCoordinates`) had encoded the leak by reading a block's
624
+ variable from outside it, and was corrected.
588
625
  - `include/openscad_cpp_evaluator/eval_error.hpp`, `src/eval_error.cpp` — `EvalError` and the
589
626
  exact ERROR/TRACE string formatting real OpenSCAD produces. Also the **unexpected-argument
590
627
  warnings** (`warnUnexpectedNamedArg`/`warnTooManyPositionalArgs`/`warnUnexpectedArgs`), the port
@@ -618,6 +655,16 @@ grep for `ponytail:`.
618
655
  accordingly; read its doc comment before touching it, it's the second-trickiest mechanism in this
619
656
  codebase after the CSG tree stack), `evalUserModule`/`evalUserFunction`/`evalFunctionLiteral`,
620
657
  and `builtinChildren` (children()/children(N), deferred evaluation).
658
+ **A function literal declared in a MODULE body captures that body's bindings**, its own
659
+ parameters included, and keeps them when it escapes (passed to another module and called there).
660
+ The interpreter gets this for free — it captures `ctx.let_` wholesale. The VM snapshots only the
661
+ names it can resolve, and inside a module body it can resolve NONE: module parameters are bound
662
+ natively into `ctx.let_` rather than slot-addressed, and a module chunk compiles with no
663
+ enclosing level. So such a literal used to be frozen as a captureless constant whose free names
664
+ resolved against whatever ctx CALLED it — silently `undef`, which is how BOSL2's
665
+ `attachable(override=function(anchor) ...)` broke. `ClosureSite::captureDefiningLet` (set
666
+ whenever the literal's body still contains `Op::LoadFree`) makes `Op::MakeClosure` root the
667
+ captured trail at the DEFINING `ctx.let_`. Pinned by `EscapingClosure.*` under both engines.
621
668
  **`children(..., separate=true)`** is a BelfrySCAD extension handled *before* any of that, by
622
669
  `expandChildStatements`: a block is read with each separating forward replaced by one synthetic
623
670
  `children(k)` statement per child it selects (`makeChildrenIndexCall`, owned by
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.51.3
3
+ Version: 0.53.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
@@ -842,6 +845,21 @@ struct CompiledChunk {
842
845
  struct ClosureSite {
843
846
  const oscad::FunctionLiteral* node = nullptr;
844
847
  std::vector<UpvalueRef> captures;
848
+ // True when the literal's body still contains Op::LoadFree -- a name
849
+ // the compiler could not resolve to a slot or an upvalue. Such a
850
+ // closure is NOT self-contained: LoadFree resolves against whatever
851
+ // ctx is current when the closure is CALLED, so if it escapes the
852
+ // scope that defined it, those names silently resolve to the
853
+ // caller's bindings (or to undef).
854
+ //
855
+ // That is exactly what happens to a function literal declared in a
856
+ // MODULE body: module parameters are bound natively into ctx.let_
857
+ // rather than slot-addressed, and a module chunk is compiled with no
858
+ // enclosing level, so the compiler can resolve none of them. With
859
+ // this set, Op::MakeClosure roots the captured trail at the DEFINING
860
+ // ctx.let_ -- which is what the interpreter has always done
861
+ // unconditionally (see the FunctionLiteral case in expr_eval.cpp).
862
+ bool captureDefiningLet = false;
845
863
  };
846
864
 
847
865
  // One echo() expression form's own argument names, in source order --
@@ -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.3"
7
+ version = "0.53.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
 
@@ -55,28 +55,141 @@ Value numList(const std::vector<double>& xs) {
55
55
  return listOf(std::move(items));
56
56
  }
57
57
 
58
- double degrees(double rad) { return rad * 180.0 / std::numbers::pi; }
59
- double radians(double deg) { return deg * std::numbers::pi / 180.0; }
60
-
61
- // At exact multiples of 90 degrees, sin/cos/tan use exact table values
62
- // instead of sin/cos/tan(radians(x)), which accumulate floating-point
63
- // noise (cos(90) -> 6.12e-17 etc) -- matches real OpenSCAD's degree-based
64
- // trig. Mirrors _deg_trig.
65
- constexpr std::array<double, 4> kSin90 = {0.0, 1.0, 0.0, -1.0};
66
- constexpr std::array<double, 4> kCos90 = {1.0, 0.0, -1.0, 0.0};
67
- const std::array<double, 4> kTan90 = {0.0, std::numeric_limits<double>::infinity(), 0.0,
68
- -std::numeric_limits<double>::infinity()};
69
-
70
- double degTrig(double x, const std::array<double, 4>& table, double (*fallback)(double)) {
71
- if (std::isnan(x) || std::isinf(x)) return std::numeric_limits<double>::quiet_NaN();
72
- const double n = x / 90.0;
73
- const double rn = std::round(n);
74
- if (rn == n) {
75
- int idx = static_cast<int>(rn) % 4;
76
- if (idx < 0) idx += 4;
77
- return table[static_cast<size_t>(idx)];
58
+ // Degree trigonometry, a direct port of real OpenSCAD's
59
+ // src/utils/degree_trig.cc. Not `std::sin(x * pi / 180)`: that is off by an
60
+ // ULP from OpenSCAD for most angles, and OpenSCAD's own extra structure --
61
+ // exact values at 30/45/60, and computing the >45 range through the
62
+ // COMPLEMENT function -- is what makes identities like sin(45) == cos(45)
63
+ // and cos(30) == sqrt(3)/2 hold EXACTLY there.
64
+ //
65
+ // That exactness is load-bearing for real libraries, not cosmetic. BOSL2's
66
+ // rect(rounding=...) builds a corner arc and then filters intersections
67
+ // with `isect != seg[0]`, an exact float comparison. One ULP of drift makes
68
+ // the shared vertex of two adjacent segments compare unequal, the filter
69
+ // keeps two corner points instead of one, and rect() dies on its own
70
+ // `assert(len(cornerpt)==1, "Cannot find corner point to anchor")`.
71
+ //
72
+ // The constants are OpenSCAD's own literals, deliberately -- M_DEG2RAD as a
73
+ // single pre-rounded double is not bitwise identical to `pi / 180.0`.
74
+ constexpr double kSqrt3 = 1.73205080756887719318; // sqrt(3)
75
+ constexpr double kSqrt3_4 = 0.86602540378443859659; // sqrt(3/4) == sqrt(3)/2
76
+ constexpr double kSqrt1_3 = 0.57735026918962573106; // sqrt(1/3) == sqrt(3)/3
77
+ constexpr double kSqrt1_2 = 0.70710678118654752440; // sqrt(1/2)
78
+ constexpr double kRad2Deg = 57.2957795130823208767; // 180/PI
79
+ constexpr double kDeg2Rad = 0.017453292519943295769; // PI/180
80
+
81
+ // Assumes a 26+26=52-bit mantissa; beyond it, reduction loses all accuracy
82
+ // and OpenSCAD returns NaN rather than a meaningless answer.
83
+ constexpr double kTrigHugeVal = (1L << 26) * 360.0 * (1L << 26);
84
+
85
+ double degrees(double rad) { return rad * kRad2Deg; }
86
+ double radians(double deg) { return deg * kDeg2Rad; }
87
+
88
+ double sinDegrees(double x) {
89
+ // Positive tests, so Inf/NaN fall through to the domain check.
90
+ if (x < 360.0 && x >= 0.0) {
91
+ // already reduced
92
+ } else if (x < kTrigHugeVal && x > -kTrigHugeVal) {
93
+ x -= 360.0 * std::floor(x / 360.0);
94
+ } else {
95
+ return std::numeric_limits<double>::quiet_NaN();
96
+ }
97
+ bool oppose = x >= 180.0;
98
+ if (oppose) x -= 180.0;
99
+ if (x > 90.0) x = 180.0 - x;
100
+ if (x < 45.0) {
101
+ x = (x == 30.0) ? 0.5 : std::sin(radians(x));
102
+ } else if (x == 45.0) {
103
+ x = kSqrt1_2;
104
+ } else if (x == 60.0) {
105
+ x = kSqrt3_4;
106
+ } else { // Inf/NaN would fall here
107
+ x = std::cos(radians(90.0 - x));
108
+ }
109
+ return oppose ? -x : x;
110
+ }
111
+
112
+ double cosDegrees(double x) {
113
+ if (x < 360.0 && x >= 0.0) {
114
+ // already reduced
115
+ } else if (x < kTrigHugeVal && x > -kTrigHugeVal) {
116
+ x -= 360.0 * std::floor(x / 360.0);
117
+ } else {
118
+ return std::numeric_limits<double>::quiet_NaN();
119
+ }
120
+ bool oppose = x >= 180.0;
121
+ if (oppose) x -= 180.0;
122
+ if (x > 90.0) {
123
+ x = 180.0 - x;
124
+ oppose = !oppose;
125
+ }
126
+ if (x > 45.0) {
127
+ x = (x == 60.0) ? 0.5 : std::sin(radians(90.0 - x));
128
+ } else if (x == 45.0) {
129
+ x = kSqrt1_2;
130
+ } else if (x == 30.0) {
131
+ x = kSqrt3_4;
132
+ } else { // Inf/NaN would fall here
133
+ x = std::cos(radians(x));
134
+ }
135
+ return oppose ? -x : x;
136
+ }
137
+
138
+ double tanDegrees(double x) {
139
+ const double cycles = std::floor(x / 180.0);
140
+ if (x < 180.0 && x >= 0.0) {
141
+ // already reduced
142
+ } else if (x < kTrigHugeVal && x > -kTrigHugeVal) {
143
+ x -= 180.0 * cycles;
144
+ } else {
145
+ return std::numeric_limits<double>::quiet_NaN();
146
+ }
147
+ const bool evenCycle = std::fmod(cycles, 2.0) == 0.0;
148
+ bool oppose = x > 90.0;
149
+ if (oppose) x = 180.0 - x;
150
+ if (x == 0.0) {
151
+ x = evenCycle ? 0.0 : -0.0;
152
+ } else if (x == 30.0) {
153
+ x = kSqrt1_3;
154
+ } else if (x == 45.0) {
155
+ x = 1.0;
156
+ } else if (x == 60.0) {
157
+ x = kSqrt3;
158
+ } else if (x == 90.0) {
159
+ x = evenCycle ? std::numeric_limits<double>::infinity()
160
+ : -std::numeric_limits<double>::infinity();
161
+ } else {
162
+ x = std::tan(radians(x));
78
163
  }
79
- return fallback(radians(x));
164
+ return oppose ? -x : x;
165
+ }
166
+
167
+ // The inverses snap to a whole number of degrees whenever the forward
168
+ // function reproduces the input exactly, so asin(sin(30)) is 30 and not
169
+ // 29.999999999999996.
170
+ double asinDegrees(double x) {
171
+ const double degs = degrees(std::asin(x));
172
+ const double whole = std::round(degs);
173
+ return sinDegrees(whole) == x ? whole : degs;
174
+ }
175
+
176
+ double acosDegrees(double x) {
177
+ const double degs = degrees(std::acos(x));
178
+ const double whole = std::round(degs);
179
+ return cosDegrees(whole) == x ? whole : degs;
180
+ }
181
+
182
+ double atanDegrees(double x) {
183
+ const double degs = degrees(std::atan(x));
184
+ const double whole = std::round(degs);
185
+ return tanDegrees(whole) == x ? whole : degs;
186
+ }
187
+
188
+ // atan2 snaps on a tolerance rather than a round-trip: OpenSCAD's own rule.
189
+ double atan2Degrees(double y, double x) {
190
+ const double degs = degrees(std::atan2(y, x));
191
+ const double whole = std::round(degs);
192
+ return std::fabs(degs - whole) < 3.0E-14 ? whole : degs;
80
193
  }
81
194
 
82
195
  // UTF-8 encode/decode for chr()/ord() -- OpenSCAD strings are unicode text,
@@ -1183,20 +1296,21 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
1183
1296
  return Value{x < 0 ? std::numeric_limits<double>::quiet_NaN() : std::log10(x)};
1184
1297
  }
1185
1298
  case BuiltinFnId::Exp: return Value{std::exp(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1186
- case BuiltinFnId::Sin: return Value{degTrig(toDoubleLenient(getArg(args, 0, "x", Value{})), kSin90, &std::sin)};
1187
- case BuiltinFnId::Cos: return Value{degTrig(toDoubleLenient(getArg(args, 0, "x", Value{})), kCos90, &std::cos)};
1188
- case BuiltinFnId::Tan: return Value{degTrig(toDoubleLenient(getArg(args, 0, "x", Value{})), kTan90, &std::tan)};
1299
+ case BuiltinFnId::Sin: return Value{sinDegrees(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1300
+ case BuiltinFnId::Cos: return Value{cosDegrees(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1301
+ case BuiltinFnId::Tan: return Value{tanDegrees(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1189
1302
  case BuiltinFnId::Asin: {
1190
1303
  const double x = toDoubleLenient(getArg(args, 0, "x", Value{}));
1191
- return Value{std::fabs(x) > 1 ? std::numeric_limits<double>::quiet_NaN() : degrees(std::asin(x))};
1304
+ return Value{std::fabs(x) > 1 ? std::numeric_limits<double>::quiet_NaN() : asinDegrees(x)};
1192
1305
  }
1193
1306
  case BuiltinFnId::Acos: {
1194
1307
  const double x = toDoubleLenient(getArg(args, 0, "x", Value{}));
1195
- return Value{std::fabs(x) > 1 ? std::numeric_limits<double>::quiet_NaN() : degrees(std::acos(x))};
1308
+ return Value{std::fabs(x) > 1 ? std::numeric_limits<double>::quiet_NaN() : acosDegrees(x)};
1196
1309
  }
1197
- case BuiltinFnId::Atan: return Value{degrees(std::atan(toDoubleLenient(getArg(args, 0, "x", Value{}))))};
1310
+ case BuiltinFnId::Atan: return Value{atanDegrees(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1198
1311
  case BuiltinFnId::Atan2:
1199
- return Value{degrees(std::atan2(toDoubleLenient(getArg(args, 0, "y", Value{})), toDoubleLenient(getArg(args, 1, "x", Value{}))))};
1312
+ return Value{atan2Degrees(toDoubleLenient(getArg(args, 0, "y", Value{})),
1313
+ toDoubleLenient(getArg(args, 1, "x", Value{})))};
1200
1314
  case BuiltinFnId::Max: return builtinMinMax(args, true);
1201
1315
  case BuiltinFnId::Min: return builtinMinMax(args, false);
1202
1316
  case BuiltinFnId::Pow: return builtinPow(toDoubleLenient(getArg(args, 0, "x", Value{})), toDoubleLenient(getArg(args, 1, "y", Value{})));
@@ -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
 
@@ -447,10 +447,12 @@ public:
447
447
  // `ctx.let_`, which callCtxFor roots at this exact closure's
448
448
  // own capturedLet snapshot whenever it's invoked -- the same
449
449
  // snapshot `effectiveCaptures` describes below.
450
- if (effectiveCaptures.empty() || !containsLoadFree(literalChunk)) {
450
+ // Must be read BEFORE literalChunk is moved from below.
451
+ const bool needsDefiningLet = containsLoadFree(literalChunk);
452
+ if (effectiveCaptures.empty() || !needsDefiningLet) {
451
453
  chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
452
454
  }
453
- if (effectiveCaptures.empty()) {
455
+ if (effectiveCaptures.empty() && !needsDefiningLet) {
454
456
  // Closes over nothing at all, even transitively -- a
455
457
  // single frozen constant works for every invocation, no
456
458
  // per-call snapshot needed.
@@ -461,7 +463,8 @@ public:
461
463
  }
462
464
  // Escaping-closure support (Op::MakeClosure, bytecode.hpp).
463
465
  int siteIdx = static_cast<int>(chunk_.closureSites.size());
464
- chunk_.closureSites.push_back(CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures)});
466
+ chunk_.closureSites.push_back(
467
+ CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures), needsDefiningLet});
465
468
  out.push_back({Op::MakeClosure, siteIdx, 0, nullptr});
466
469
  return;
467
470
  }
@@ -1289,7 +1292,15 @@ public:
1289
1292
  chunk_.builtinWrapSites.push_back(std::move(site));
1290
1293
  const int idx = static_cast<int>(chunk_.builtinWrapSites.size()) - 1;
1291
1294
  out.push_back({Op::PushBuiltinWrap, idx, 0, &wrapperNode.position()});
1295
+ // The child block is its own scope -- same rule as an if-branch, and
1296
+ // the interpreter gets it from resolveCallArgs' child ctx (see
1297
+ // call_args.cpp). Opened INSIDE the Push/Pop bracket so that
1298
+ // Op::PopBuiltinWrap's own resolveCallArgs call still sees the
1299
+ // OUTER ctx: an assignment in the block must not feed back into the
1300
+ // operator's own arguments.
1301
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1292
1302
  compileStatementList(children, out);
1303
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1293
1304
  out.push_back({Op::PopBuiltinWrap, idx, 0, &wrapperNode.position()});
1294
1305
  }
1295
1306
 
@@ -1312,6 +1323,10 @@ public:
1312
1323
  chunk_.csgWrapSites.push_back(std::move(site));
1313
1324
  const int idx = static_cast<int>(chunk_.csgWrapSites.size()) - 1;
1314
1325
  out.push_back({Op::PushCsgWrap, idx, 0, &call.position()});
1326
+ // One scope around BOTH passes -- see emitBuiltinWrap's own note.
1327
+ // The assignment pass must write into the same block scope the
1328
+ // geometry pass then reads from, so it cannot be bracketed per-pass.
1329
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1315
1330
 
1316
1331
  std::vector<const oscad::ASTNode*> assignNodes;
1317
1332
  std::vector<const oscad::ASTNode*> geoNodes;
@@ -1335,6 +1350,7 @@ public:
1335
1350
  compileStatementList(std::vector<const oscad::ASTNode*>{geoNode}, out);
1336
1351
  out.push_back({Op::CsgGroupEnd, 0, 0, nullptr});
1337
1352
  }
1353
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1338
1354
  out.push_back({Op::PopCsgWrap, idx, 0, &call.position()});
1339
1355
  }
1340
1356
 
@@ -1688,13 +1704,25 @@ public:
1688
1704
  emitBuiltinWrap(CompiledChunk::BuiltinWrapSite::Kind::Modifier, "show_only", n, {n.child.get()}, out);
1689
1705
  return;
1690
1706
  }
1707
+ // Both if-forms bracket the taken branch in Op::OpenLetScope /
1708
+ // Op::CloseExprScope. An if-branch body is its own scope in
1709
+ // OpenSCAD -- assignments inside must not survive the closing
1710
+ // brace -- and the child ctx a let-BLOCK gets is exactly the
1711
+ // one a branch needs, so the same opcode serves both (see
1712
+ // branchScope, stmt_eval.cpp, for the interpreter's identical
1713
+ // rule and the full reasoning). The scope is opened INSIDE the
1714
+ // conditional jump's target range, so a branch that is not
1715
+ // taken never opens one, and each branch's own Close is the
1716
+ // last thing before the jump past its sibling.
1691
1717
  case NodeKind::ModularIf: {
1692
1718
  auto& n = static_cast<const oscad::ModularIf&>(stmt);
1693
1719
  const int jumpFalseIdx = static_cast<int>(out.size());
1694
1720
  out.push_back({Op::NativeCondJumpIfFalse, internNativeExpr(n.condition.get()), 0, &n.condition->position()});
1695
1721
  const oscad::ASTNode* marker = n.trueBranch.empty() ? &stmt : n.trueBranch.front().get();
1696
1722
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(marker), 0, nullptr});
1723
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1697
1724
  compileStatementList(n.trueBranch, out);
1725
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1698
1726
  out[static_cast<size_t>(jumpFalseIdx)].b = static_cast<int>(out.size());
1699
1727
  return;
1700
1728
  }
@@ -1704,13 +1732,17 @@ public:
1704
1732
  out.push_back({Op::NativeCondJumpIfFalse, internNativeExpr(n.condition.get()), 0, &n.condition->position()});
1705
1733
  const oscad::ASTNode* trueMarker = n.trueBranch.empty() ? &stmt : n.trueBranch.front().get();
1706
1734
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(trueMarker), 0, nullptr});
1735
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1707
1736
  compileStatementList(n.trueBranch, out);
1737
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1708
1738
  const int jumpEndIdx = static_cast<int>(out.size());
1709
1739
  out.push_back({Op::Jump, 0, 0, nullptr});
1710
1740
  out[static_cast<size_t>(jumpFalseIdx)].b = static_cast<int>(out.size());
1711
1741
  const oscad::ASTNode* falseMarker = n.falseBranch.empty() ? &stmt : n.falseBranch.front().get();
1712
1742
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(falseMarker), 0, nullptr});
1743
+ out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1713
1744
  compileStatementList(n.falseBranch, out);
1745
+ out.push_back({Op::CloseExprScope, 0, 0, nullptr});
1714
1746
  out[static_cast<size_t>(jumpEndIdx)].a = static_cast<int>(out.size());
1715
1747
  return;
1716
1748
  }