openscad-cpp-evaluator 0.7.0__tar.gz → 0.8.1__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.1}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/bytecode.hpp +37 -17
  3. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/bytecode_compiler.cpp +105 -26
  5. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/bytecode_vm.cpp +59 -16
  6. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/user_calls.cpp +14 -1
  7. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_bytecode_compiler.cpp +87 -1
  8. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/.github/workflows/ci.yml +0 -0
  9. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/.github/workflows/wheels.yml +0 -0
  10. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/.gitignore +0 -0
  11. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/.gitmodules +0 -0
  12. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/CLAUDE.md +0 -0
  13. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/CMakeLists.txt +0 -0
  14. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/LICENSE +0 -0
  15. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/README.md +0 -0
  16. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/bindings/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/bindings/module.cpp +0 -0
  18. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/cmake/embed_font.cmake +0 -0
  19. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/examples/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/examples/eval_perf_benchmark.cpp +0 -0
  21. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/examples/manifold_cache_reuse.cpp +0 -0
  22. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/examples/minimal_debugger.cpp +0 -0
  23. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  24. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/.gitignore +0 -0
  25. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  26. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  27. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/README.md +0 -0
  28. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  29. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  37. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  38. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  39. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/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.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  41. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  42. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/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.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  44. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  45. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  46. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  47. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  48. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  49. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  50. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  51. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  52. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  53. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  54. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  55. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  56. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  57. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  58. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  59. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  60. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  61. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  62. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  63. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  64. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  65. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  66. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  67. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  68. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  69. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  70. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  71. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  72. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  73. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  74. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  75. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  76. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  77. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  78. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  79. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  80. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  81. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  82. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  83. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  84. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  85. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  86. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  87. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  88. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  89. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  90. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  91. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  92. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  94. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  95. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  96. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  97. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  98. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  99. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  100. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  101. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  102. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  103. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  104. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  105. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  106. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  107. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  108. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  109. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  110. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  111. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  112. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  113. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  114. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  116. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  117. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  118. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  119. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  120. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/resources/fonts/LICENSE +0 -0
  121. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  122. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/CMakeLists.txt +0 -0
  123. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/booleans.cpp +0 -0
  124. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/builtins.hpp +0 -0
  125. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/call_args.cpp +0 -0
  126. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/color.cpp +0 -0
  127. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/control.cpp +0 -0
  128. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/extrude.cpp +0 -0
  129. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/function_builtins.cpp +0 -0
  130. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/import.cpp +0 -0
  131. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/modifiers.cpp +0 -0
  132. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/primitives_2d.cpp +0 -0
  133. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/primitives_3d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/registry.cpp +0 -0
  135. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/roof.cpp +0 -0
  136. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/segments.cpp +0 -0
  137. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/surface.cpp +0 -0
  138. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/text.cpp +0 -0
  139. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/topology.cpp +0 -0
  140. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/builtins/transforms.cpp +0 -0
  141. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/colored_body.cpp +0 -0
  142. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/csg_generate.cpp +0 -0
  143. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/csg_resolve.cpp +0 -0
  144. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/css_colors.cpp +0 -0
  145. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/debug/debug_repl.cpp +0 -0
  146. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/debug_profile.cpp +0 -0
  147. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/eval_context.cpp +0 -0
  148. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/eval_error.cpp +0 -0
  149. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/eval_use.cpp +0 -0
  150. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/export.cpp +0 -0
  151. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/expr_eval.cpp +0 -0
  152. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/import/dxf_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/import/mesh_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/import/surface_load.cpp +0 -0
  155. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/import/svg_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/manifold_cache.cpp +0 -0
  157. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/stmt_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/text/stb_font_provider.cpp +0 -0
  159. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/text/text_metrics.cpp +0 -0
  160. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/value.cpp +0 -0
  161. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/src/zip_stored.cpp +0 -0
  162. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/CMakeLists.txt +0 -0
  163. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_booleans.cpp +0 -0
  164. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_cli.cpp +0 -0
  165. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_control_flow.cpp +0 -0
  166. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_csg_tree.cpp +0 -0
  167. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_debug_hooks.cpp +0 -0
  168. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_dxf_svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_export.cpp +0 -0
  171. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_extrude_roof.cpp +0 -0
  173. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_function_builtins.cpp +0 -0
  174. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_helpers.hpp +0 -0
  175. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_import_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_multi_color_merge.cpp +0 -0
  178. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_primitives.cpp +0 -0
  179. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.7.0 → openscad_cpp_evaluator-0.8.1}/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.1
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -266,6 +266,17 @@ struct CompiledChunk {
266
266
  const oscad::ASTNode* targetDecl = nullptr;
267
267
  int slot = 0;
268
268
  std::string name;
269
+ // Set only when this entry is a `let(name = function(...) ...)`
270
+ // closure's own reference to ITSELF (bytecode_compiler.cpp's LetOp
271
+ // case, the letrec-style pre-declared-slot path) -- Op::MakeClosure
272
+ // can't read this one out of `slots` the way it does every other
273
+ // capture: at the moment it runs, the closure it's building hasn't
274
+ // been constructed yet (StoreLocal for `name` hasn't executed), so
275
+ // `slots[slot]` would still hold whatever was there before this
276
+ // call. See Op::MakeClosure's own runtime doc comment
277
+ // (bytecode_vm.cpp) for how this is actually resolved (deferred,
278
+ // then patched in after construction).
279
+ bool isSelfReference = false;
269
280
  };
270
281
 
271
282
  // One FunctionLiteral's own capture list for Op::MakeClosure, computed
@@ -276,24 +287,24 @@ struct CompiledChunk {
276
287
  // literal's own ClosureSite still needed from beyond `node`'s own scope
277
288
  // (i.e. any capture whose resolution target isn't `node` itself). This
278
289
  // "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.
290
+ // creating closure resolve correctly: at the OUTERMOST level a given
291
+ // capture is actually snapshotted (see Op::MakeClosure's own runtime
292
+ // doc comment, bytecode_vm.cpp), `captures`'s entries resolve against
293
+ // that running chunk's own `slots` array whenever `targetDecl` matches
294
+ // its own declaration -- and, when it doesn't (a capture bubbled up
295
+ // from an even-more-deeply-nested literal), against the enclosing
296
+ // frame via Evaluator::findUpvalue or, failing that, `ctx.let_`
297
+ // (rooted at that OUTER closure's own capturedLet whenever it's itself
298
+ // an escaped invocation) -- regardless of how many literal-within-
299
+ // literal levels `node` was originally nested through in the source.
286
300
  //
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.
301
+ // `node`'s own body IS also registered (Evaluator::
302
+ // lookupCompiledLiteralChunk finds a real cache entry for it, same as
303
+ // a zero-capture literal), so a closure invocation can run compiled
304
+ // too, not just its creation: Op::LoadUpvalue/Op::MakeClosure inside
305
+ // that body fall back to `ctx.let_` exactly as described above
306
+ // whenever the live-call-stack walk misses, which is precisely what
307
+ // happens once this closure has actually escaped its creator.
297
308
  struct ClosureSite {
298
309
  const oscad::FunctionLiteral* node = nullptr;
299
310
  std::vector<UpvalueRef> captures;
@@ -307,6 +318,15 @@ struct CompiledChunk {
307
318
  std::vector<std::optional<std::string>> argNames;
308
319
  };
309
320
 
321
+ // The FunctionDeclaration/FunctionLiteral this chunk's own body was
322
+ // compiled from (bytecode_compiler.cpp's compileFunctionLike sets this
323
+ // to its own `selfDecl` parameter) -- Op::MakeClosure's runtime handler
324
+ // (bytecode_vm.cpp) compares a ClosureSite capture's own `targetDecl`
325
+ // against this to tell "genuinely local to the running chunk" (read
326
+ // `slots` directly) from "bubbled up from a nested literal" (needs the
327
+ // enclosing-frame/capturedLet fallback) apart.
328
+ const oscad::ASTNode* selfDecl = nullptr;
329
+
310
330
  std::vector<Param> params;
311
331
  std::vector<std::vector<Instruction>> defaultCode;
312
332
  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.1"
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,45 @@ 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). The direct `let(a = function(...)
72
+ // ... a(...) ...)` self-reference (reduce()/accumulate()/while()'s own
73
+ // idiom) no longer hits this -- see the LetOp case's own letrec pre-declare,
74
+ // below -- but anything the pre-declare doesn't cover still does: `a`
75
+ // wrapped in a ternary/let/anything other than a bare FunctionLiteral RHS
76
+ // (`let(a = cond ? function(...) ...a(...)... : function(...) ...)`),
77
+ // mutual recursion between two sibling let-bound closures (each references
78
+ // the OTHER, which isn't declared yet when compiling the first), or any
79
+ // other free variable this phase simply can't resolve statically. For any
80
+ // of those, Evaluator::evalIdentifier's own ctx.scope->lookupVariable()
81
+ // fallback re-evaluates the let-binding's RHS FRESH on every single call,
82
+ // producing a new Closure whose capturedLet is THIS invocation's own
83
+ // ctx.let_ (see expr_eval.cpp's FunctionLiteral case) rather than a stable
84
+ // snapshot -- registering such a chunk for compiled invocation would still
85
+ // be functionally correct, but each recursive call's own
86
+ // capturedLet->openChild() then nests ONE level deeper than the last
87
+ // (confirmed empirically: an O(n) list reduce() degrading to O(n^2) once
88
+ // compiled, before the letrec pre-declare existed), since nothing about
89
+ // that repeated fresh-derivation ever re-roots the trail. Excluding a
90
+ // LoadFree-containing chunk from registration leaves it running
91
+ // interpreted exactly as before -- correct, if not optimized.
92
+ bool containsLoadFree(const std::vector<Instruction>& code) {
93
+ for (const Instruction& ins : code) {
94
+ if (ins.op == Op::LoadFree) return true;
95
+ }
96
+ return false;
97
+ }
98
+
99
+ bool containsLoadFree(const CompiledChunk& chunk) {
100
+ if (containsLoadFree(chunk.bodyCode)) return true;
101
+ for (const auto& defaultCode : chunk.defaultCode) {
102
+ if (containsLoadFree(defaultCode)) return true;
103
+ }
104
+ return false;
105
+ }
106
+
70
107
  // Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
71
108
  // shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
72
109
  // LetOp (pushed/popped around its own assignments+body), innermost frame
@@ -306,32 +343,45 @@ public:
306
343
  for (const auto& nestedSite : literalChunk.closureSites) {
307
344
  bubbleEscapingCaptures(nestedSite, &n, effectiveCaptures);
308
345
  }
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.
346
+ // Registered (Evaluator::lookupCompiledLiteralChunk finds
347
+ // this by node pointer whenever the closure is later
348
+ // invoked, whether that's a plain PushConst-created value
349
+ // below or a MakeClosure-snapshotted one) whenever it's
350
+ // actually safe to run compiled: always for a zero-capture
351
+ // literal (a single frozen constant, unaffected by anything
352
+ // below), and for a captures-having one only when its own
353
+ // body contains no Op::LoadFree -- see containsLoadFree's
354
+ // own doc comment, above, for why a LoadFree-containing
355
+ // captures-having closure must still be left interpreted:
356
+ // its own capturedLet-chaining, once registered, degrades
357
+ // an O(n) reduction into O(n^2). (The reduce()/
358
+ // accumulate()/while() idiom itself -- a closure directly
359
+ // assigned via `let` that calls itself by name -- no longer
360
+ // hits this: see the LetOp case's own letrec pre-declare,
361
+ // below, which lets that specific self-reference resolve as
362
+ // a real upvalue instead of falling through to LoadFree.)
363
+ // Everything else (Op::LoadUpvalue/Op::MakeClosure inside a
364
+ // REGISTERED captures-having chunk) is no longer assumed to
365
+ // resolve only against a still-live creator frame (see
366
+ // those opcodes' own runtime fallback, bytecode_vm.cpp):
367
+ // when the live-call-stack walk misses (the creator's frame
368
+ // is gone -- a genuinely escaped closure), they fall back to
369
+ // `ctx.let_`, which callCtxFor roots at this exact closure's
370
+ // own capturedLet snapshot whenever it's invoked -- the same
371
+ // snapshot `effectiveCaptures` describes below.
372
+ if (effectiveCaptures.empty() || !containsLoadFree(literalChunk)) {
317
373
  chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
374
+ }
375
+ if (effectiveCaptures.empty()) {
376
+ // Closes over nothing at all, even transitively -- a
377
+ // single frozen constant works for every invocation, no
378
+ // per-call snapshot needed.
318
379
  out.push_back(
319
380
  {Op::PushConst, internConst(Value{std::make_shared<const Closure>(Closure{&n, nullptr})}), 0,
320
381
  nullptr});
321
382
  return;
322
383
  }
323
384
  // 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
385
  int siteIdx = static_cast<int>(chunk_.closureSites.size());
336
386
  chunk_.closureSites.push_back(CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures)});
337
387
  out.push_back({Op::MakeClosure, siteIdx, 0, nullptr});
@@ -517,12 +567,40 @@ public:
517
567
  out.push_back({Op::OpenLocalScope, 0, 0, nullptr});
518
568
  int slotStart = nextSlot_;
519
569
  for (const auto& assign : n.assignments) {
520
- compileExpr(*assign->expr, out, scope); // RHS is never tail
521
570
  const std::string& name = assign->name->name;
571
+ // A DIRECT `name = function(...) ...` RHS -- the
572
+ // reduce()/accumulate()/while() idiom, and count_to()-
573
+ // style helpers generally -- gets its own slot declared
574
+ // BEFORE compiling that RHS (letrec-style), so a
575
+ // self-reference inside the closure's own body resolves
576
+ // as a genuine upvalue (Op::LoadUpvalue) instead of
577
+ // falling through to Op::LoadFree. Every other RHS shape
578
+ // keeps the existing after-the-fact declareLocal below
579
+ // unchanged (`let(x = x + 1)` must still see the OUTER
580
+ // x, not a fresh, not-yet-assigned local shadowing it --
581
+ // only a function-literal RHS has "see myself" as a
582
+ // sensible reading at all). See Op::MakeClosure's own
583
+ // runtime handler (bytecode_vm.cpp) for how the
584
+ // resulting self-capture is actually resolved -- the
585
+ // closure being created doesn't exist yet at the moment
586
+ // its own captures are normally snapshotted, so this
587
+ // one is deferred and patched in after construction
588
+ // instead of read eagerly like every other capture.
589
+ const bool selfBinding = !name.empty() && name[0] != '$' &&
590
+ assign->expr->kind() == oscad::NodeKind::FunctionLiteral;
591
+ int preDeclaredSlot = -1;
592
+ if (selfBinding) preDeclaredSlot = declareLocal(scope, name);
593
+ compileExpr(*assign->expr, out, scope); // RHS is never tail
594
+ if (selfBinding && out.back().op == Op::MakeClosure) {
595
+ CompiledChunk::ClosureSite& site = chunk_.closureSites[static_cast<size_t>(out.back().a)];
596
+ for (auto& cap : site.captures) {
597
+ if (cap.targetDecl == selfDecl_ && cap.slot == preDeclaredSlot) cap.isSelfReference = true;
598
+ }
599
+ }
522
600
  if (!name.empty() && name[0] == '$') {
523
601
  out.push_back({Op::StoreDyn, internName(name), 0, &assign->position()});
524
602
  } else {
525
- int slot = declareLocal(scope, name);
603
+ int slot = selfBinding ? preDeclaredSlot : declareLocal(scope, name);
526
604
  out.push_back({Op::StoreLocal, slot, 0, &assign->position()});
527
605
  }
528
606
  }
@@ -896,6 +974,7 @@ bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope,
896
974
  if (!p->name->name.empty() && p->name->name[0] == '$') return false;
897
975
  }
898
976
 
977
+ chunk.selfDecl = selfDecl;
899
978
  Compiler compiler(chunk, staticScope, selfDecl, std::move(enclosing));
900
979
  CompileScope bodyScope;
901
980
  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,26 +151,55 @@ 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();
176
+ // A letrec-style self-reference (UpvalueRef::isSelfReference,
177
+ // see its own doc comment) is skipped here, not read -- the
178
+ // closure it names is THIS instruction's own result, which
179
+ // doesn't exist until the shared_ptr below is constructed.
180
+ // Patched in immediately afterward via the exact same
181
+ // capturedTrail (a live, shared TrailView -- mutating it
182
+ // after `closure` wraps it is exactly as visible to that
183
+ // closure's own later invocations as any other entry).
184
+ std::vector<const std::string*> selfNames;
156
185
  for (const auto& cap : site.captures) {
157
- capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
186
+ if (cap.isSelfReference) {
187
+ selfNames.push_back(&cap.name);
188
+ continue;
189
+ }
190
+ if (cap.targetDecl == chunk.selfDecl) {
191
+ capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
192
+ continue;
193
+ }
194
+ const Value* v = ev.findUpvalue(cap.targetDecl, cap.slot);
195
+ if (!v) v = ctx.let_->find(cap.name);
196
+ capturedTrail->set(cap.name, v ? *v : Value{});
197
+ }
198
+ auto closure = std::make_shared<const Closure>(Closure{site.node, capturedTrail});
199
+ for (const std::string* selfName : selfNames) {
200
+ capturedTrail->set(*selfName, Value{closure});
158
201
  }
159
- stack.push_back(Value{std::make_shared<const Closure>(Closure{site.node, std::move(capturedTrail)})});
202
+ stack.push_back(Value{std::move(closure)});
160
203
  ++pc;
161
204
  break;
162
205
  }
@@ -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,84 @@ 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, SelfReferentialRecursiveClosureNowResolvesAsUpvalueAndRunsCompiled) {
612
+ ScopedVm vm(true);
613
+ // reduce()'s own idiom: `a` calls itself by name. A direct
614
+ // `let(a = function(...) ... a(...) ...)` RHS gets `a`'s own slot
615
+ // pre-declared BEFORE compiling that RHS (bytecode_compiler.cpp's
616
+ // LetOp case), letrec-style -- so this self-reference now resolves as
617
+ // a genuine upvalue (Op::LoadUpvalue) instead of falling through to
618
+ // Op::LoadFree, and `a`'s own body is eligible for registration
619
+ // (containsLoadFree no longer sees any unresolved reference in it).
620
+ // Op::MakeClosure's own runtime handler defers this ONE capture
621
+ // (the closure being built doesn't exist yet when its own captures
622
+ // are normally snapshotted) and patches it in immediately after
623
+ // constructing the real closure -- see its own doc comment,
624
+ // bytecode_vm.cpp. This is also what fixed a real O(n) -> O(n^2)
625
+ // regression found while building this feature: every recursive call
626
+ // used to re-derive a FRESH closure via Evaluator::evalIdentifier's
627
+ // ctx.scope->lookupVariable() fallback, nesting its own capturedLet
628
+ // one level deeper than the last -- confirmed via the CLI against a
629
+ // 32,000-element list (7+ seconds -> 0.05s once `a` genuinely
630
+ // resolves itself as an upvalue instead).
631
+ //
632
+ // 11 stops, not 30 (confirmed by diffing against the previous,
633
+ // containsLoadFree-excluded behavior with the same script): `reduce`'s
634
+ // own body-entry (1) + `a`'s own 5 recursive calls (1 each, compiled)
635
+ // + `func`'s own 4 calls (1 each, already compiled even before this --
636
+ // zero captures) + the top-level echo() statement's own stop (module
637
+ // code is never compiled).
638
+ const int stops = countDebugHookStops(
639
+ "function reduce(func, list, init=0) = let(l = len(list), a = function (x,i) i<l? "
640
+ "a(func(x,list[i]), i+1) : x) a(init,0);\n"
641
+ "echo(reduce(function(p,q) p+q, [1,2,3,4], 0));",
642
+ std::unordered_map<std::string, std::set<int>>{});
643
+ EXPECT_EQ(stops, 11);
644
+ EXPECT_EQ(runCapturingEcho("function reduce(func, list, init=0) = let(l = len(list), a = function (x,i) "
645
+ "i<l? a(func(x,list[i]), i+1) : x) a(init,0);\n"
646
+ "echo(reduce(function(p,q) p+q, [1,2,3,4], 0));"),
647
+ "ECHO: 10");
648
+ }
649
+
650
+ TEST(BytecodeCompiler, PlainLetSelfReferenceStillSeesTheOuterBindingNotItself) {
651
+ ScopedVm vm(true);
652
+ // The letrec pre-declare above is gated on the RHS being a DIRECT
653
+ // FunctionLiteral specifically because a non-function `let(x = x + 1)`
654
+ // must keep seeing the OUTER x (real OpenSCAD/letOp shadowing
655
+ // semantics -- each assignment's RHS sees only what preceded it in the
656
+ // same let, never itself), not a fresh, not-yet-assigned local
657
+ // shadowing it. Confirms that ordinary case is untouched by the
658
+ // FunctionLiteral-only pre-declare.
659
+ EXPECT_EQ(runCapturingEcho("x = 100;\nfunction f() = let(x = x + 1) x;\necho(f());"), "ECHO: 101");
660
+ }
661
+
576
662
  TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
577
663
  ScopedVm vm(true);
578
664
  // Residual, deliberate limitation: a FunctionLiteral with its OWN