openscad-cpp-evaluator 0.7.0__tar.gz → 0.8.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 (194) hide show
  1. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bytecode.hpp +26 -17
  3. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/bytecode_compiler.cpp +69 -24
  5. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/bytecode_vm.cpp +41 -15
  6. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/user_calls.cpp +14 -1
  7. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_bytecode_compiler.cpp +63 -1
  8. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/.github/workflows/ci.yml +0 -0
  9. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/.github/workflows/wheels.yml +0 -0
  10. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/.gitignore +0 -0
  11. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/.gitmodules +0 -0
  12. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/CLAUDE.md +0 -0
  13. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/CMakeLists.txt +0 -0
  14. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/LICENSE +0 -0
  15. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/README.md +0 -0
  16. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/bindings/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/bindings/module.cpp +0 -0
  18. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/cmake/embed_font.cmake +0 -0
  19. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/examples/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/examples/eval_perf_benchmark.cpp +0 -0
  21. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/examples/manifold_cache_reuse.cpp +0 -0
  22. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/examples/minimal_debugger.cpp +0 -0
  23. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  24. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/.gitignore +0 -0
  25. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  26. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  27. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/README.md +0 -0
  28. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  29. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  30. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  31. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  32. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  33. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  34. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  35. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  36. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  37. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  38. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  39. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  40. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  41. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  42. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  43. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  44. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  45. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  46. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  47. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  48. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  49. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  50. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  51. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  52. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  53. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  54. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  55. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  56. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  57. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  58. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  59. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  60. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  61. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  62. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  63. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  64. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  65. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  66. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  67. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  68. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  69. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  70. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  71. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  72. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  73. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  74. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  75. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  76. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  77. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  78. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  79. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  80. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  81. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  82. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  83. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  84. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  85. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  86. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  87. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  88. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  89. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  90. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  91. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  92. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  94. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  95. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  96. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  97. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  98. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  99. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  100. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  101. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  102. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  103. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  104. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  105. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  106. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  107. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  108. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  109. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  110. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  111. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  112. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  113. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  114. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  116. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  117. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  118. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  119. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  120. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/resources/fonts/LICENSE +0 -0
  121. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  122. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/CMakeLists.txt +0 -0
  123. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/booleans.cpp +0 -0
  124. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/builtins.hpp +0 -0
  125. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/call_args.cpp +0 -0
  126. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/color.cpp +0 -0
  127. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/control.cpp +0 -0
  128. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/extrude.cpp +0 -0
  129. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/function_builtins.cpp +0 -0
  130. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/import.cpp +0 -0
  131. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/modifiers.cpp +0 -0
  132. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/primitives_2d.cpp +0 -0
  133. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/primitives_3d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/registry.cpp +0 -0
  135. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/roof.cpp +0 -0
  136. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/segments.cpp +0 -0
  137. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/surface.cpp +0 -0
  138. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/text.cpp +0 -0
  139. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/topology.cpp +0 -0
  140. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/builtins/transforms.cpp +0 -0
  141. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/colored_body.cpp +0 -0
  142. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/csg_generate.cpp +0 -0
  143. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/csg_resolve.cpp +0 -0
  144. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/css_colors.cpp +0 -0
  145. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/debug/debug_repl.cpp +0 -0
  146. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/debug_profile.cpp +0 -0
  147. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/eval_context.cpp +0 -0
  148. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/eval_error.cpp +0 -0
  149. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/eval_use.cpp +0 -0
  150. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/export.cpp +0 -0
  151. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/expr_eval.cpp +0 -0
  152. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/import/dxf_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/import/mesh_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/import/surface_load.cpp +0 -0
  155. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/import/svg_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/manifold_cache.cpp +0 -0
  157. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/stmt_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/text/stb_font_provider.cpp +0 -0
  159. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/text/text_metrics.cpp +0 -0
  160. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/value.cpp +0 -0
  161. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/src/zip_stored.cpp +0 -0
  162. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/CMakeLists.txt +0 -0
  163. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_booleans.cpp +0 -0
  164. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_cli.cpp +0 -0
  165. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_control_flow.cpp +0 -0
  166. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_csg_tree.cpp +0 -0
  167. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_debug_hooks.cpp +0 -0
  168. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_dxf_svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_export.cpp +0 -0
  171. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_extrude_roof.cpp +0 -0
  173. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_function_builtins.cpp +0 -0
  174. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_helpers.hpp +0 -0
  175. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_import_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_multi_color_merge.cpp +0 -0
  178. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_primitives.cpp +0 -0
  179. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.0}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -276,24 +276,24 @@ struct CompiledChunk {
276
276
  // literal's own ClosureSite still needed from beyond `node`'s own scope
277
277
  // (i.e. any capture whose resolution target isn't `node` itself). This
278
278
  // "bubbling" is what makes a closure nested inside another compiled-
279
- // creating closure resolve correctly even though the inner one's body
280
- // is never itself compiled (see this struct's own body-compilation
281
- // note below): by the time a real, running CompiledChunk executes
282
- // Op::MakeClosure for `node`, every one of `captures`'s entries is
283
- // guaranteed to resolve against THAT SAME running chunk's own `slots`
284
- // array, regardless of how many literal-within-literal levels `node`
285
- // was originally nested through in the source.
279
+ // creating closure resolve correctly: at the OUTERMOST level a given
280
+ // capture is actually snapshotted (see Op::MakeClosure's own runtime
281
+ // doc comment, bytecode_vm.cpp), `captures`'s entries resolve against
282
+ // that running chunk's own `slots` array whenever `targetDecl` matches
283
+ // its own declaration -- and, when it doesn't (a capture bubbled up
284
+ // from an even-more-deeply-nested literal), against the enclosing
285
+ // frame via Evaluator::findUpvalue or, failing that, `ctx.let_`
286
+ // (rooted at that OUTER closure's own capturedLet whenever it's itself
287
+ // an escaped invocation) -- regardless of how many literal-within-
288
+ // literal levels `node` was originally nested through in the source.
286
289
  //
287
- // `node`'s own BODY is deliberately never itself compiled/registered
288
- // once it has any capture need (even an empty one bubbled up from
289
- // something it contains) -- it always runs via the ordinary AST
290
- // interpreter when invoked (Evaluator::lookupCompiledLiteralChunk finds
291
- // no cache entry for it), using Closure::capturedLet exactly like any
292
- // other escaping closure the interpreter creates directly. This keeps
293
- // the whole feature to "make closure CREATION safe to compile"; a
294
- // closure's own body additionally getting compiled too (its captures
295
- // would need to read from this snapshot instead of a live-stack walk)
296
- // is a natural follow-on, not attempted here.
290
+ // `node`'s own body IS also registered (Evaluator::
291
+ // lookupCompiledLiteralChunk finds a real cache entry for it, same as
292
+ // a zero-capture literal), so a closure invocation can run compiled
293
+ // too, not just its creation: Op::LoadUpvalue/Op::MakeClosure inside
294
+ // that body fall back to `ctx.let_` exactly as described above
295
+ // whenever the live-call-stack walk misses, which is precisely what
296
+ // happens once this closure has actually escaped its creator.
297
297
  struct ClosureSite {
298
298
  const oscad::FunctionLiteral* node = nullptr;
299
299
  std::vector<UpvalueRef> captures;
@@ -307,6 +307,15 @@ struct CompiledChunk {
307
307
  std::vector<std::optional<std::string>> argNames;
308
308
  };
309
309
 
310
+ // The FunctionDeclaration/FunctionLiteral this chunk's own body was
311
+ // compiled from (bytecode_compiler.cpp's compileFunctionLike sets this
312
+ // to its own `selfDecl` parameter) -- Op::MakeClosure's runtime handler
313
+ // (bytecode_vm.cpp) compares a ClosureSite capture's own `targetDecl`
314
+ // against this to tell "genuinely local to the running chunk" (read
315
+ // `slots` directly) from "bubbled up from a nested literal" (needs the
316
+ // enclosing-frame/capturedLet fallback) apart.
317
+ const oscad::ASTNode* selfDecl = nullptr;
318
+
310
319
  std::vector<Param> params;
311
320
  std::vector<std::vector<Instruction>> defaultCode;
312
321
  std::vector<Instruction> bodyCode;
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -44,11 +44,9 @@ bool isListCompClauseKind(oscad::NodeKind kind) {
44
44
  // `owner` itself (targetDecl != owner) -- see ClosureSite's own doc comment
45
45
  // (bytecode.hpp) for why this "bubbling" is necessary: a literal nested
46
46
  // inside `owner` may need something from further out than `owner`'s own
47
- // scope, and since that inner literal's body is never itself compiled
48
- // (only ever invoked via the interpreter once it has any capture need),
49
- // `owner`'s OWN Op::MakeClosure must snapshot that name too -- otherwise
50
- // the inner literal's interpreter-resolved capturedLet ancestry, rooted at
51
- // whatever `owner` itself captured, would simply never contain it.
47
+ // scope, so `owner`'s OWN Op::MakeClosure must snapshot that name too --
48
+ // otherwise the inner literal's own capturedLet, rooted at whatever `owner`
49
+ // itself captured, would simply never contain it.
52
50
  // Dedupes by (targetDecl, slot): the same enclosing binding reached through
53
51
  // two different nested literals (or the same literal referencing it twice)
54
52
  // is still one capture to make.
@@ -67,6 +65,43 @@ void bubbleEscapingCaptures(const CompiledChunk::ClosureSite& site, const oscad:
67
65
  }
68
66
  }
69
67
 
68
+ // True if any instruction in `chunk`'s own body/defaults is Op::LoadFree --
69
+ // i.e. an identifier that resolved to neither a local, a statically-known
70
+ // enclosing upvalue, nor a dyn ($-prefixed) name at compile time (see
71
+ // compileExpr's own Identifier case). A self-referential recursive closure
72
+ // (`let(a = function(x,i) ... a(...) ...)`, the reduce()/accumulate()/
73
+ // while() idiom) hits exactly this: LetOp's own declareLocal for `a` runs
74
+ // only AFTER compiling `a`'s RHS, so `a`'s reference to itself can't
75
+ // resolve as an upvalue at compile time and falls through to LoadFree --
76
+ // Evaluator::evalIdentifier's own ctx.scope->lookupVariable() fallback,
77
+ // which re-evaluates the let-binding's RHS FRESH on every single call,
78
+ // producing a new Closure whose capturedLet is THIS invocation's own
79
+ // ctx.let_ (see expr_eval.cpp's FunctionLiteral case) rather than a stable
80
+ // snapshot. Registering such a chunk for compiled invocation (see the
81
+ // FunctionLiteral case below) would still be functionally correct -- but
82
+ // each recursive call's own capturedLet->openChild() then nests ONE level
83
+ // deeper than the last (confirmed empirically: an O(n) list reduce()
84
+ // degrading to O(n^2) once compiled), since nothing about that repeated
85
+ // fresh-derivation ever re-roots the trail. Excluding a LoadFree-containing
86
+ // chunk from registration leaves it running interpreted exactly as before
87
+ // (this compile-time gap itself isn't fixed here) -- a real, if imperfect,
88
+ // scope for now; closing it -- letting `a` resolve as a genuine upvalue of
89
+ // its own declaring LetOp -- is future work, not bundled into this change.
90
+ bool containsLoadFree(const std::vector<Instruction>& code) {
91
+ for (const Instruction& ins : code) {
92
+ if (ins.op == Op::LoadFree) return true;
93
+ }
94
+ return false;
95
+ }
96
+
97
+ bool containsLoadFree(const CompiledChunk& chunk) {
98
+ if (containsLoadFree(chunk.bodyCode)) return true;
99
+ for (const auto& defaultCode : chunk.defaultCode) {
100
+ if (containsLoadFree(defaultCode)) return true;
101
+ }
102
+ return false;
103
+ }
104
+
70
105
  // Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
71
106
  // shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
72
107
  // LetOp (pushed/popped around its own assignments+body), innermost frame
@@ -306,32 +341,41 @@ public:
306
341
  for (const auto& nestedSite : literalChunk.closureSites) {
307
342
  bubbleEscapingCaptures(nestedSite, &n, effectiveCaptures);
308
343
  }
309
- if (effectiveCaptures.empty()) {
310
- // Closes over nothing at all, even transitively -- the
311
- // existing fast path: a single frozen constant works
312
- // for every invocation, and this literal's own compiled
313
- // chunk (possibly containing its OWN MakeClosure sites
314
- // for anything nested inside IT that closes only over
315
- // ITS OWN scope) is genuinely usable, registered as
316
- // before.
344
+ // Registered (Evaluator::lookupCompiledLiteralChunk finds
345
+ // this by node pointer whenever the closure is later
346
+ // invoked, whether that's a plain PushConst-created value
347
+ // below or a MakeClosure-snapshotted one) whenever it's
348
+ // actually safe to run compiled: always for a zero-capture
349
+ // literal (a single frozen constant, unaffected by anything
350
+ // below), and for a captures-having one only when its own
351
+ // body contains no Op::LoadFree -- see containsLoadFree's
352
+ // own doc comment, above, for why a LoadFree-containing
353
+ // captures-having closure (the reduce()/accumulate()/
354
+ // while() self-referential-recursion idiom) must still be
355
+ // left interpreted: its own capturedLet-chaining, once
356
+ // registered, degrades an O(n) reduction into O(n^2).
357
+ // Everything else (Op::LoadUpvalue/Op::MakeClosure inside a
358
+ // REGISTERED captures-having chunk) is no longer assumed to
359
+ // resolve only against a still-live creator frame (see
360
+ // those opcodes' own runtime fallback, bytecode_vm.cpp):
361
+ // when the live-call-stack walk misses (the creator's frame
362
+ // is gone -- a genuinely escaped closure), they fall back to
363
+ // `ctx.let_`, which callCtxFor roots at this exact closure's
364
+ // own capturedLet snapshot whenever it's invoked -- the same
365
+ // snapshot `effectiveCaptures` describes below.
366
+ if (effectiveCaptures.empty() || !containsLoadFree(literalChunk)) {
317
367
  chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
368
+ }
369
+ if (effectiveCaptures.empty()) {
370
+ // Closes over nothing at all, even transitively -- a
371
+ // single frozen constant works for every invocation, no
372
+ // per-call snapshot needed.
318
373
  out.push_back(
319
374
  {Op::PushConst, internConst(Value{std::make_shared<const Closure>(Closure{&n, nullptr})}), 0,
320
375
  nullptr});
321
376
  return;
322
377
  }
323
378
  // Escaping-closure support (Op::MakeClosure, bytecode.hpp).
324
- // `literalChunk`'s own bytecode is discarded here -- its own
325
- // upvalue reads would resolve via the live-call-stack walk
326
- // (Op::LoadUpvalue), which is wrong for a closure that
327
- // outlives its creator -- so this literal always runs
328
- // interpreted when invoked instead (Evaluator::
329
- // lookupCompiledLiteralChunk finds no cache entry for it),
330
- // using the exact same Closure::capturedLet machinery every
331
- // other escaping closure the interpreter creates directly
332
- // already relies on. Only `effectiveCaptures` (the names/
333
- // slots to snapshot right now) survives from the discarded
334
- // compile attempt.
335
379
  int siteIdx = static_cast<int>(chunk_.closureSites.size());
336
380
  chunk_.closureSites.push_back(CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures)});
337
381
  out.push_back({Op::MakeClosure, siteIdx, 0, nullptr});
@@ -896,6 +940,7 @@ bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope,
896
940
  if (!p->name->name.empty() && p->name->name[0] == '$') return false;
897
941
  }
898
942
 
943
+ chunk.selfDecl = selfDecl;
899
944
  Compiler compiler(chunk, staticScope, selfDecl, std::move(enclosing));
900
945
  CompileScope bodyScope;
901
946
  bodyScope.push();
@@ -127,8 +127,22 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
127
127
  ++pc;
128
128
  break;
129
129
  case Op::LoadUpvalue: {
130
+ // findUpvalue walks the LIVE call stack -- correct as long
131
+ // as the declaring call is still active, however many
132
+ // upvalue levels out `uv.targetDecl` sits (an ordinary,
133
+ // not-yet-escaped nested closure, e.g. reduce()'s own
134
+ // self-recursive accumulator). If that frame is gone
135
+ // (this chunk is running as an ESCAPED closure's own body
136
+ // -- see the FunctionLiteral case's own doc comment,
137
+ // bytecode_compiler.cpp), fall back to `ctx.let_`: for any
138
+ // closure invocation with a non-null capturedLet,
139
+ // callCtxFor roots `ctx.let_` at that exact snapshot, which
140
+ // is guaranteed (by construction -- effectiveCaptures is a
141
+ // superset of this chunk's own upvalues) to contain this
142
+ // name.
130
143
  const CompiledChunk::UpvalueRef& uv = chunk.upvalues[static_cast<size_t>(ins.a)];
131
144
  const Value* v = ev.findUpvalue(uv.targetDecl, uv.slot);
145
+ if (!v) v = ctx.let_->find(uv.name);
132
146
  stack.push_back(v ? *v : Value{});
133
147
  ++pc;
134
148
  break;
@@ -137,24 +151,36 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
137
151
  // Builds a FRESH captured environment every time this
138
152
  // instruction actually runs (never a compile-time constant
139
153
  // -- a loop creating one closure per iteration must capture
140
- // THAT iteration's own values, not share one snapshot) by
141
- // reading each of the site's own captures straight out of
142
- // THIS frame's own `slots` -- see ClosureSite's own doc
143
- // comment (bytecode.hpp) for why every capture, however
144
- // deeply the literal was originally nested, is guaranteed
145
- // to resolve against this SAME currently-running chunk.
146
- // The resulting Closure::capturedLet is a real, standalone
147
- // TrailView (isolated root -- nothing else shares it, and
148
- // it owes nothing to `ctx.let_`, which a compiled call
149
- // never populates -- see bindCompiledArgs' own doc
150
- // comment), so invoking this closure later works through
151
- // the EXACT same callCtxFor/capturedLetTrail machinery
152
- // every interpreter-created escaping closure already uses,
153
- // unchanged.
154
+ // THAT iteration's own values, not share one snapshot).
155
+ // Each capture is genuinely local to the CURRENTLY-RUNNING
156
+ // chunk only when its own `targetDecl` matches this chunk's
157
+ // `selfDecl` (this literal's direct free-variable
158
+ // references) -- read straight out of `slots` in that case.
159
+ // Anything else is a capture bubbled up from a literal
160
+ // nested even deeper (see ClosureSite's own doc comment,
161
+ // bytecode.hpp): resolved the same way Op::LoadUpvalue
162
+ // resolves any upvalue reference, above -- the live call
163
+ // stack first (still live if THIS closure hasn't itself
164
+ // escaped yet), `ctx.let_` otherwise (this chunk's own
165
+ // capturedLet, when it's running as an escaped closure's
166
+ // body). The resulting Closure::capturedLet is a real,
167
+ // standalone TrailView (isolated root -- nothing else
168
+ // shares it, and it owes nothing to `ctx.let_` itself,
169
+ // which a compiled call never writes to directly -- see
170
+ // bindCompiledArgs' own doc comment), so invoking this
171
+ // closure later works through the EXACT same
172
+ // callCtxFor/capturedLetTrail machinery every interpreter-
173
+ // created escaping closure already uses, unchanged.
154
174
  const CompiledChunk::ClosureSite& site = chunk.closureSites[static_cast<size_t>(ins.a)];
155
175
  auto capturedTrail = TrailView<Value>::makeRoot();
156
176
  for (const auto& cap : site.captures) {
157
- capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
177
+ if (cap.targetDecl == chunk.selfDecl) {
178
+ capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
179
+ continue;
180
+ }
181
+ const Value* v = ev.findUpvalue(cap.targetDecl, cap.slot);
182
+ if (!v) v = ctx.let_->find(cap.name);
183
+ capturedTrail->set(cap.name, v ? *v : Value{});
158
184
  }
159
185
  stack.push_back(Value{std::make_shared<const Closure>(Closure{site.node, std::move(capturedTrail)})});
160
186
  ++pc;
@@ -152,8 +152,21 @@ EvalContext Evaluator::callCtxFor(const oscad::ASTNode& decl, EvalContext& ctx,
152
152
 
153
153
  std::optional<EvalContext> Evaluator::isolatedCallCtxFor(const oscad::ASTNode& declNode, EvalContext& ctx,
154
154
  const std::shared_ptr<TrailView<Value>>& capturedLet) {
155
+ // `declNode.scope()`, not `ctx.scope` -- matches every other call-
156
+ // resolution function's own `fnScope = decl.scope() ? decl.scope() :
157
+ // ctx.scope` pattern (evalUserFunction/evalUserFunctionFromBound/
158
+ // evalFunctionLiteral/evalFunctionLiteralFromBound). Mismatching here
159
+ // was invisible until a captures-having FunctionLiteral's own body
160
+ // could ever actually be trampolined into (Op::MakeClosure's own
161
+ // registration -- bytecode_compiler.cpp's FunctionLiteral case): a
162
+ // self-referential closure's free-variable resolution can fall through
163
+ // to evalIdentifier's ctx.scope->lookupVariable() fallback (see its own
164
+ // doc comment, expr_eval.cpp), which needs the CALLEE's own lexical
165
+ // scope to walk outward from -- the caller's scope at the call site
166
+ // has no relation to it at all.
155
167
  bool usedChildCtx = false;
156
- EvalContext result = callCtxFor(declNode, ctx, ctx.scope, nullptr, nullptr, &usedChildCtx, capturedLet);
168
+ const oscad::Scope* declScope = declNode.scope() ? declNode.scope() : ctx.scope;
169
+ EvalContext result = callCtxFor(declNode, ctx, declScope, nullptr, nullptr, &usedChildCtx, capturedLet);
157
170
  if (usedChildCtx) return std::nullopt;
158
171
  return result;
159
172
  }
@@ -511,10 +511,18 @@ TEST(BytecodeCompiler, MakeClosureLetsContainerCompileDespiteNonEscapingClosure)
511
511
  // above), so fast-continue must be enabled (an empty breakpoint map --
512
512
  // fast-continue "on", nothing set) to actually observe whether `make`
513
513
  // runs compiled here.
514
+ //
515
+ // 3 stops, not 4: `make`'s own body-entry (1), `g`'s own body-entry
516
+ // (1) -- `g` itself now also runs compiled (its captures-having chunk
517
+ // is registered too, not discarded -- see the FunctionLiteral case's
518
+ // own doc comment, bytecode_compiler.cpp), so it costs exactly one
519
+ // stop instead of the extra sub-expression checkpoint an interpreted
520
+ // call used to add on top -- plus the top-level echo() statement's own
521
+ // stop (module-level code is never compiled).
514
522
  const int stops = countDebugHookStops("function make(x) = let(g = function(y) y + x) g(5);\n"
515
523
  "echo(make(10));",
516
524
  std::unordered_map<std::string, std::set<int>>{});
517
- EXPECT_EQ(stops, 4);
525
+ EXPECT_EQ(stops, 3);
518
526
  }
519
527
 
520
528
  TEST(BytecodeCompiler, MakeClosureLetsContainerCompileDespiteEscapingClosure) {
@@ -573,6 +581,60 @@ TEST(BytecodeCompiler, MakeClosureBubblesCaptureThroughAnIntermediateClosureLeve
573
581
  EXPECT_EQ(runCapturingEcho(script), "ECHO: 111");
574
582
  }
575
583
 
584
+ TEST(BytecodeCompiler, MultiLevelCurriedClosureInvocationRunsCompiled) {
585
+ ScopedVm vm(true);
586
+ // fnliterals.scad's f_1arg()-style curry adapter: a closure that
587
+ // returns ANOTHER closure nested inside it, both with real captures.
588
+ // This is exactly the two-level-nesting case Op::MakeClosure's own
589
+ // runtime handler (bytecode_vm.cpp) has to get right when a captures-
590
+ // having chunk's OWN body is registered too -- the inner literal's own
591
+ // MakeClosure instruction runs as part of the OUTER closure's later,
592
+ // separate invocation (not inline within f_1arg's own call, the way a
593
+ // single-level closure's capture always does), so its own captures
594
+ // (bubbled up from `f_1arg` at compile time) are no longer local to
595
+ // whatever frame happens to be running -- they resolve via
596
+ // Evaluator::findUpvalue/`ctx.let_`, not `slots` directly. Correctness
597
+ // (not a stop count -- several calls chain here, and not every shape
598
+ // adds its own extra interpreted-only checkpoint, so the total isn't a
599
+ // reliable compiled-vs-interpreted signal for this particular script)
600
+ // is what actually matters: a wrong result here is exactly what a
601
+ // missed/misdirected capture read would produce.
602
+ EXPECT_EQ(runCapturingEcho("function f_1arg(target_func) = function(a) a==undef? function(x) target_func(x) : "
603
+ "function() target_func(a);\n"
604
+ "function double(x) = x * 2;\n"
605
+ "adder = f_1arg(function(x) double(x));\n"
606
+ "echo(adder(21)());\n"
607
+ "echo(adder(undef)(10));"),
608
+ "ECHO: 42\nECHO: 20");
609
+ }
610
+
611
+ TEST(BytecodeCompiler, SelfReferentialRecursiveClosureStillRunsInterpretedAndCorrectly) {
612
+ ScopedVm vm(true);
613
+ // reduce()'s own idiom: `a` calls itself by name, which LetOp's own
614
+ // declareLocal-after-compiling-the-RHS ordering means never resolves as
615
+ // a genuine upvalue at compile time (falls through to Op::LoadFree
616
+ // instead -- see containsLoadFree's own doc comment, bytecode_compiler.cpp).
617
+ // Registering such a chunk for compiled invocation is what previously
618
+ // (empirically, against a real list) turned an O(n) reduce() into
619
+ // O(n^2): every recursive call re-derives a fresh closure via
620
+ // Evaluator::evalIdentifier's ctx.scope->lookupVariable() fallback,
621
+ // nesting its own capturedLet one level deeper than the last every
622
+ // time. `containsLoadFree` excludes exactly this case from
623
+ // registration, so `a` keeps running interpreted -- more than 1 stop
624
+ // for its own call proves that, and the actual sum must still come out
625
+ // right either way.
626
+ const int stops = countDebugHookStops(
627
+ "function reduce(func, list, init=0) = let(l = len(list), a = function (x,i) i<l? "
628
+ "a(func(x,list[i]), i+1) : x) a(init,0);\n"
629
+ "echo(reduce(function(p,q) p+q, [1,2,3,4], 0));",
630
+ std::unordered_map<std::string, std::set<int>>{});
631
+ EXPECT_GT(stops, 2);
632
+ EXPECT_EQ(runCapturingEcho("function reduce(func, list, init=0) = let(l = len(list), a = function (x,i) "
633
+ "i<l? a(func(x,list[i]), i+1) : x) a(init,0);\n"
634
+ "echo(reduce(function(p,q) p+q, [1,2,3,4], 0));"),
635
+ "ECHO: 10");
636
+ }
637
+
576
638
  TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
577
639
  ScopedVm vm(true);
578
640
  // Residual, deliberate limitation: a FunctionLiteral with its OWN