openscad-cpp-evaluator 0.8.0__tar.gz → 0.9.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.8.0 → openscad_cpp_evaluator-0.9.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/bytecode.hpp +32 -4
  3. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/bytecode_compiler.cpp +108 -26
  5. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/bytecode_vm.cpp +41 -1
  6. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_bytecode_compiler.cpp +83 -16
  7. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/.github/workflows/ci.yml +0 -0
  8. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/.github/workflows/wheels.yml +0 -0
  9. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/.gitignore +0 -0
  10. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/.gitmodules +0 -0
  11. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/CLAUDE.md +0 -0
  12. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/CMakeLists.txt +0 -0
  13. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/LICENSE +0 -0
  14. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/README.md +0 -0
  15. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/bindings/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/bindings/module.cpp +0 -0
  17. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/cmake/embed_font.cmake +0 -0
  18. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/examples/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/examples/eval_perf_benchmark.cpp +0 -0
  20. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/examples/manifold_cache_reuse.cpp +0 -0
  21. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/examples/minimal_debugger.cpp +0 -0
  22. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  23. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/.gitignore +0 -0
  24. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  25. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/README.md +0 -0
  27. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  28. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  29. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  30. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  31. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  32. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  33. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  34. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  35. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  36. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  38. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  39. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  40. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  41. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  42. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  43. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  44. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  45. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  46. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  47. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  48. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  49. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  50. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  51. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  52. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  53. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  54. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  55. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  56. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  57. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  58. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  59. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  60. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  61. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  62. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  63. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  64. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  65. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  66. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  67. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  68. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  69. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  70. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  71. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  72. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  73. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  74. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  75. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  76. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  77. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  78. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  79. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  80. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  81. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  82. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  83. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  84. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  85. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  86. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  87. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  88. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  89. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  90. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  91. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  92. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  93. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  94. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  95. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  96. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  97. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  98. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  99. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  100. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  101. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  102. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  103. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  104. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  105. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  106. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  107. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  108. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  109. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  110. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  111. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  112. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  113. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  115. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  116. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  117. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  118. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  119. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/resources/fonts/LICENSE +0 -0
  120. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  121. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/CMakeLists.txt +0 -0
  122. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/booleans.cpp +0 -0
  123. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/builtins.hpp +0 -0
  124. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/call_args.cpp +0 -0
  125. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/color.cpp +0 -0
  126. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/control.cpp +0 -0
  127. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/extrude.cpp +0 -0
  128. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/function_builtins.cpp +0 -0
  129. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/import.cpp +0 -0
  130. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/modifiers.cpp +0 -0
  131. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/primitives_2d.cpp +0 -0
  132. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/primitives_3d.cpp +0 -0
  133. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/registry.cpp +0 -0
  134. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/roof.cpp +0 -0
  135. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/segments.cpp +0 -0
  136. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/surface.cpp +0 -0
  137. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/text.cpp +0 -0
  138. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/topology.cpp +0 -0
  139. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/builtins/transforms.cpp +0 -0
  140. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/colored_body.cpp +0 -0
  141. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/csg_generate.cpp +0 -0
  142. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/csg_resolve.cpp +0 -0
  143. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/css_colors.cpp +0 -0
  144. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/debug/debug_repl.cpp +0 -0
  145. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/debug_profile.cpp +0 -0
  146. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/eval_context.cpp +0 -0
  147. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/eval_error.cpp +0 -0
  148. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/eval_use.cpp +0 -0
  149. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/export.cpp +0 -0
  150. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/expr_eval.cpp +0 -0
  151. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/import/dxf_import.cpp +0 -0
  152. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/import/mesh_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/import/surface_load.cpp +0 -0
  154. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/import/svg_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/manifold_cache.cpp +0 -0
  156. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/stmt_eval.cpp +0 -0
  157. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/text/stb_font_provider.cpp +0 -0
  158. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/text/text_metrics.cpp +0 -0
  159. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/user_calls.cpp +0 -0
  160. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/value.cpp +0 -0
  161. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/src/zip_stored.cpp +0 -0
  162. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/CMakeLists.txt +0 -0
  163. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_booleans.cpp +0 -0
  164. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_cli.cpp +0 -0
  165. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_control_flow.cpp +0 -0
  166. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_csg_tree.cpp +0 -0
  167. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_debug_hooks.cpp +0 -0
  168. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_dxf_svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_export.cpp +0 -0
  171. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_extrude_roof.cpp +0 -0
  173. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_function_builtins.cpp +0 -0
  174. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_helpers.hpp +0 -0
  175. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_import_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_multi_color_merge.cpp +0 -0
  178. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_primitives.cpp +0 -0
  179. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.8.0 → openscad_cpp_evaluator-0.9.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.8.0
3
+ Version: 0.9.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -131,16 +131,33 @@ enum class Op {
131
131
  // chunk actually running Op::MakeClosure), and constructs
132
132
  // Closure{node, capturedTrail} -- reusing Closure::capturedLet (value.hpp)
133
133
  // and callCtxFor's existing capturedLet-rooting (user_calls.cpp)
134
- // UNCHANGED: invoking this closure later (always via the interpreter,
135
- // see ClosureSite's own doc comment for why its body is never itself
136
- // compiled) works exactly like any interpreter-created escaping closure
137
- // always has, no new invocation-side code needed at all. Replaces
134
+ // UNCHANGED: invoking this closure later works through the exact same
135
+ // machinery any interpreter-created escaping closure always has, no new
136
+ // invocation-side code needed at all (its own body may itself run
137
+ // compiled too now, see Evaluator::lookupCompiledLiteralChunk). Replaces
138
138
  // Op::PushConst's frozen `Closure{&n, nullptr}` specifically for a
139
139
  // literal whose (transitively merged) capture set is non-empty; a
140
140
  // literal that closes over nothing at all -- even transitively -- still
141
141
  // takes the cheaper PushConst path, unchanged.
142
142
  MakeClosure,
143
143
 
144
+ // Letrec support for TWO OR MORE sibling `let`-bound closures that
145
+ // reference each other by name (fnliterals.scad-style mutual
146
+ // recursion, e.g. isEven/isOdd each calling the other) -- see the
147
+ // LetOp compile case's own doc comment (bytecode_compiler.cpp) for why
148
+ // a forward reference (an EARLIER closure referencing a LATER sibling
149
+ // that doesn't exist yet at the point the earlier one is constructed)
150
+ // can't be resolved by Op::MakeClosure itself the way a self-reference
151
+ // is: there's nothing to read OR self-patch into, since the later
152
+ // sibling hasn't even been created yet. a = consumer's own local slot
153
+ // (already holds a MakeClosure-created closure, via a preceding
154
+ // Op::StoreLocal), b = name pool index (the captured name, inside the
155
+ // consumer's own capturedTrail, that's finally ready), c = the local
156
+ // slot the just-constructed sibling was JUST stored into (also via a
157
+ // preceding Op::StoreLocal) -- patches slots[a]'s own capturedTrail
158
+ // with slots[c], keyed by name b.
159
+ PatchClosureCapture,
160
+
144
161
  // A call whose callee isn't statically resolvable to a builtin or a
145
162
  // named FunctionDeclaration (see CallFn) -- the callee expression is
146
163
  // compiled to push its own Value first, THEN each argument (so at
@@ -266,6 +283,17 @@ struct CompiledChunk {
266
283
  const oscad::ASTNode* targetDecl = nullptr;
267
284
  int slot = 0;
268
285
  std::string name;
286
+ // Set only when this entry is a `let(name = function(...) ...)`
287
+ // closure's own reference to ITSELF (bytecode_compiler.cpp's LetOp
288
+ // case, the letrec-style pre-declared-slot path) -- Op::MakeClosure
289
+ // can't read this one out of `slots` the way it does every other
290
+ // capture: at the moment it runs, the closure it's building hasn't
291
+ // been constructed yet (StoreLocal for `name` hasn't executed), so
292
+ // `slots[slot]` would still hold whatever was there before this
293
+ // call. See Op::MakeClosure's own runtime doc comment
294
+ // (bytecode_vm.cpp) for how this is actually resolved (deferred,
295
+ // then patched in after construction).
296
+ bool isSelfReference = false;
269
297
  };
270
298
 
271
299
  // One FunctionLiteral's own capture list for Op::MakeClosure, computed
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.8.0"
7
+ version = "0.9.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -10,6 +10,7 @@
10
10
 
11
11
  #include <functional>
12
12
  #include <unordered_map>
13
+ #include <unordered_set>
13
14
 
14
15
  namespace oscadeval {
15
16
 
@@ -68,25 +69,27 @@ void bubbleEscapingCaptures(const CompiledChunk::ClosureSite& site, const oscad:
68
69
  // True if any instruction in `chunk`'s own body/defaults is Op::LoadFree --
69
70
  // i.e. an identifier that resolved to neither a local, a statically-known
70
71
  // 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.
72
+ // compileExpr's own Identifier case). A DIRECT `let(a = function(...)
73
+ // ... a(...) ...)` self-reference (reduce()/accumulate()/while()'s own
74
+ // idiom) and mutual recursion between two-or-more sibling DIRECT
75
+ // `let`-bound closures (fnliterals.scad-style isEven/isOdd, each calling
76
+ // the other) no longer hit this -- see the LetOp case's own letrec
77
+ // pre-declare, below -- but anything the pre-declare doesn't cover still
78
+ // does: `a` wrapped in a ternary/let/anything other than a bare
79
+ // FunctionLiteral RHS (`let(a = cond ? function(...) ...a(...)... :
80
+ // function(...) ...)`), or any other free variable this phase simply can't
81
+ // resolve statically. For any of those, Evaluator::evalIdentifier's own
82
+ // ctx.scope->lookupVariable() fallback re-evaluates the let-binding's RHS
83
+ // FRESH on every single call, producing a new Closure whose capturedLet is
84
+ // THIS invocation's own ctx.let_ (see expr_eval.cpp's FunctionLiteral case)
85
+ // rather than a stable snapshot -- registering such a chunk for compiled
86
+ // invocation would still be functionally correct, but each recursive
87
+ // call's own capturedLet->openChild() then nests ONE level deeper than the
88
+ // last (confirmed empirically: an O(n) list reduce() degrading to O(n^2)
89
+ // once compiled, before the letrec pre-declare existed), since nothing
90
+ // about that repeated fresh-derivation ever re-roots the trail. Excluding
91
+ // a LoadFree-containing chunk from registration leaves it running
92
+ // interpreted exactly as before -- correct, if not optimized.
90
93
  bool containsLoadFree(const std::vector<Instruction>& code) {
91
94
  for (const Instruction& ins : code) {
92
95
  if (ins.op == Op::LoadFree) return true;
@@ -350,10 +353,14 @@ public:
350
353
  // below), and for a captures-having one only when its own
351
354
  // body contains no Op::LoadFree -- see containsLoadFree's
352
355
  // 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).
356
+ // captures-having closure must still be left interpreted:
357
+ // its own capturedLet-chaining, once registered, degrades
358
+ // an O(n) reduction into O(n^2). (The reduce()/
359
+ // accumulate()/while() idiom itself -- a closure directly
360
+ // assigned via `let` that calls itself by name -- no longer
361
+ // hits this: see the LetOp case's own letrec pre-declare,
362
+ // below, which lets that specific self-reference resolve as
363
+ // a real upvalue instead of falling through to LoadFree.)
357
364
  // Everything else (Op::LoadUpvalue/Op::MakeClosure inside a
358
365
  // REGISTERED captures-having chunk) is no longer assumed to
359
366
  // resolve only against a still-live creator frame (see
@@ -560,14 +567,89 @@ public:
560
567
  size_t placeholderIdx = out.size();
561
568
  out.push_back({Op::OpenLocalScope, 0, 0, nullptr});
562
569
  int slotStart = nextSlot_;
563
- for (const auto& assign : n.assignments) {
564
- compileExpr(*assign->expr, out, scope); // RHS is never tail
570
+ // Letrec pre-pass: every DIRECT `name = function(...) ...`
571
+ // assignment in this LetOp gets its own slot declared up
572
+ // front, before ANY of them are compiled -- not just so a
573
+ // closure can reference itself (see the self-reference
574
+ // handling below), but so an EARLIER one can also reference
575
+ // a LATER sibling (mutual recursion, e.g. fnliterals.scad-
576
+ // style isEven/isOdd calling each other). `letrecSlot[i]`
577
+ // stays -1 for anything else, which keeps the existing
578
+ // after-the-fact declareLocal path below completely
579
+ // unchanged (`let(x = x + 1)` must still see the OUTER x,
580
+ // not a fresh, not-yet-assigned local shadowing it -- only
581
+ // a function-literal RHS has "see myself/a sibling" as a
582
+ // sensible reading at all).
583
+ std::vector<int> letrecSlot(n.assignments.size(), -1);
584
+ std::unordered_set<int> pendingLetrecSlots;
585
+ for (size_t i = 0; i < n.assignments.size(); ++i) {
586
+ const auto& assign = n.assignments[i];
587
+ const std::string& name = assign->name->name;
588
+ if (!name.empty() && name[0] != '$' && assign->expr->kind() == oscad::NodeKind::FunctionLiteral) {
589
+ letrecSlot[i] = declareLocal(scope, name);
590
+ pendingLetrecSlots.insert(letrecSlot[i]);
591
+ }
592
+ }
593
+ // consumerSlot/name pairs an EARLIER closure's own
594
+ // Op::MakeClosure left unresolved because they targeted a
595
+ // sibling that didn't exist yet at that point -- resolved
596
+ // (an Op::PatchClosureCapture emitted) the moment that
597
+ // sibling's own StoreLocal runs, below.
598
+ std::unordered_map<int, std::vector<std::pair<int, std::string>>> pendingWaiters;
599
+ for (size_t i = 0; i < n.assignments.size(); ++i) {
600
+ const auto& assign = n.assignments[i];
565
601
  const std::string& name = assign->name->name;
602
+ const int preDeclaredSlot = letrecSlot[i];
603
+ const bool selfBinding = preDeclaredSlot >= 0;
604
+ compileExpr(*assign->expr, out, scope); // RHS is never tail
605
+ if (selfBinding && out.back().op == Op::MakeClosure) {
606
+ CompiledChunk::ClosureSite& site = chunk_.closureSites[static_cast<size_t>(out.back().a)];
607
+ auto& captures = site.captures;
608
+ for (auto it = captures.begin(); it != captures.end();) {
609
+ if (it->targetDecl != selfDecl_ || !pendingLetrecSlots.count(it->slot)) {
610
+ ++it;
611
+ continue;
612
+ }
613
+ if (it->slot == preDeclaredSlot) {
614
+ // See Op::MakeClosure's own runtime handler
615
+ // (bytecode_vm.cpp): the closure being
616
+ // created doesn't exist yet at the moment
617
+ // its own captures are normally snapshotted,
618
+ // so this one is deferred and patched in
619
+ // right after construction instead of read
620
+ // eagerly like every other capture.
621
+ it->isSelfReference = true;
622
+ ++it;
623
+ } else {
624
+ // Forward reference to a sibling that hasn't
625
+ // been constructed AT ALL yet -- unlike the
626
+ // self-reference case above, there's nothing
627
+ // to read OR self-patch into here. Left out
628
+ // of `captures` entirely (nothing for
629
+ // Op::MakeClosure to even attempt) and
630
+ // deferred to a real Op::PatchClosureCapture
631
+ // once that sibling's own StoreLocal runs.
632
+ pendingWaiters[it->slot].emplace_back(preDeclaredSlot, it->name);
633
+ it = captures.erase(it);
634
+ }
635
+ }
636
+ }
566
637
  if (!name.empty() && name[0] == '$') {
567
638
  out.push_back({Op::StoreDyn, internName(name), 0, &assign->position()});
568
639
  } else {
569
- int slot = declareLocal(scope, name);
640
+ int slot = selfBinding ? preDeclaredSlot : declareLocal(scope, name);
570
641
  out.push_back({Op::StoreLocal, slot, 0, &assign->position()});
642
+ if (selfBinding) {
643
+ pendingLetrecSlots.erase(slot);
644
+ auto waitersIt = pendingWaiters.find(slot);
645
+ if (waitersIt != pendingWaiters.end()) {
646
+ for (const auto& [consumerSlot, capName] : waitersIt->second) {
647
+ out.push_back({Op::PatchClosureCapture, consumerSlot, internName(capName),
648
+ &assign->position(), nullptr, slot});
649
+ }
650
+ pendingWaiters.erase(waitersIt);
651
+ }
652
+ }
571
653
  }
572
654
  }
573
655
  out[placeholderIdx].a = slotStart;
@@ -173,7 +173,20 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
173
173
  // created escaping closure already uses, unchanged.
174
174
  const CompiledChunk::ClosureSite& site = chunk.closureSites[static_cast<size_t>(ins.a)];
175
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;
176
185
  for (const auto& cap : site.captures) {
186
+ if (cap.isSelfReference) {
187
+ selfNames.push_back(&cap.name);
188
+ continue;
189
+ }
177
190
  if (cap.targetDecl == chunk.selfDecl) {
178
191
  capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
179
192
  continue;
@@ -182,7 +195,34 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
182
195
  if (!v) v = ctx.let_->find(cap.name);
183
196
  capturedTrail->set(cap.name, v ? *v : Value{});
184
197
  }
185
- stack.push_back(Value{std::make_shared<const Closure>(Closure{site.node, std::move(capturedTrail)})});
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});
201
+ }
202
+ stack.push_back(Value{std::move(closure)});
203
+ ++pc;
204
+ break;
205
+ }
206
+ case Op::PatchClosureCapture: {
207
+ // Mutual-recursion support (see the LetOp compile case's
208
+ // own doc comment, bytecode_compiler.cpp): `slots[ins.a]`
209
+ // (the consumer, e.g. isEven) already holds a real
210
+ // MakeClosure-created closure whose own capturedTrail is
211
+ // missing this ONE entry -- it was left out entirely at
212
+ // compile time because the sibling it names (e.g. isOdd)
213
+ // hadn't been constructed yet. `slots[ins.c]` (that
214
+ // sibling's own slot) does now, since this instruction only
215
+ // ever runs immediately after that sibling's own
216
+ // Op::StoreLocal. Mutates the SAME shared TrailView the
217
+ // consumer's own capturedLet already points at -- visible
218
+ // to it exactly like every other capture, no different
219
+ // from Op::MakeClosure's own self-reference patch, above.
220
+ const Value& consumerVal = slots[static_cast<size_t>(ins.a)];
221
+ if (const auto* closurePtr = std::get_if<ClosurePtr>(&consumerVal); closurePtr && *closurePtr) {
222
+ if (auto trail = capturedLetTrail(**closurePtr)) {
223
+ trail->set(chunk.names[static_cast<size_t>(ins.b)], slots[static_cast<size_t>(ins.c)]);
224
+ }
225
+ }
186
226
  ++pc;
187
227
  break;
188
228
  }
@@ -608,33 +608,100 @@ TEST(BytecodeCompiler, MultiLevelCurriedClosureInvocationRunsCompiled) {
608
608
  "ECHO: 42\nECHO: 20");
609
609
  }
610
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.
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).
626
638
  const int stops = countDebugHookStops(
627
639
  "function reduce(func, list, init=0) = let(l = len(list), a = function (x,i) i<l? "
628
640
  "a(func(x,list[i]), i+1) : x) a(init,0);\n"
629
641
  "echo(reduce(function(p,q) p+q, [1,2,3,4], 0));",
630
642
  std::unordered_map<std::string, std::set<int>>{});
631
- EXPECT_GT(stops, 2);
643
+ EXPECT_EQ(stops, 11);
632
644
  EXPECT_EQ(runCapturingEcho("function reduce(func, list, init=0) = let(l = len(list), a = function (x,i) "
633
645
  "i<l? a(func(x,list[i]), i+1) : x) a(init,0);\n"
634
646
  "echo(reduce(function(p,q) p+q, [1,2,3,4], 0));"),
635
647
  "ECHO: 10");
636
648
  }
637
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
+
662
+ TEST(BytecodeCompiler, MutualRecursionBetweenSiblingLetBoundClosuresResolvesCorrectly) {
663
+ ScopedVm vm(true);
664
+ // fnliterals.scad-style isEven()/isOdd(): TWO sibling closures in the
665
+ // SAME let(), each calling the OTHER by name. `isEven` (compiled
666
+ // FIRST) references `isOdd`, which doesn't exist at all yet at that
667
+ // point -- unlike a self-reference, there's nothing to read OR self-
668
+ // patch into (see Op::PatchClosureCapture's own doc comment,
669
+ // bytecode.hpp): resolved instead by a real patch instruction emitted
670
+ // right after `isOdd`'s own StoreLocal, once it actually exists.
671
+ // 3 stops, not one per logical call (isEvenTest's own entry, plus one
672
+ // per level of isEven(6)->isOdd(5)->isEven(4)->...->isEven(0)):
673
+ // isEven<->isOdd calling each other in tail position is exactly what
674
+ // the tail-call trampoline collapses into ONE evalUserFunctionCore
675
+ // invocation for the WHOLE chain (see runCompiledFunctionTrampoline,
676
+ // bytecode_vm.cpp) -- its own unconditional body-entry checkDebug()
677
+ // fires once for that entire loop, not once per hop. isEvenTest's own
678
+ // entry (1) + that one trampoline loop (1) + the top-level echo()
679
+ // statement's own stop (module code is never compiled) = 3.
680
+ const int stops = countDebugHookStops(
681
+ "function isEvenTest(n) = let(isEven = function(k) k==0 ? true : isOdd(k-1), isOdd = function(k) "
682
+ "k==0 ? false : isEven(k-1)) isEven(n);\n"
683
+ "echo(isEvenTest(6));",
684
+ std::unordered_map<std::string, std::set<int>>{});
685
+ EXPECT_EQ(stops, 3);
686
+ EXPECT_EQ(runCapturingEcho("function isEvenTest(n) = let(isEven = function(k) k==0 ? true : isOdd(k-1), "
687
+ "isOdd = function(k) k==0 ? false : isEven(k-1)) isEven(n);\n"
688
+ "echo(isEvenTest(6));\necho(isEvenTest(7));"),
689
+ "ECHO: true\nECHO: false");
690
+ }
691
+
692
+ TEST(BytecodeCompiler, ThreeWaySiblingMutualRecursionResolvesCorrectly) {
693
+ ScopedVm vm(true);
694
+ // The patch mechanism isn't hardcoded to pairs -- a THIRD sibling
695
+ // (f3, itself forward-referencing f1, the FIRST one compiled) proves
696
+ // pendingWaiters/Op::PatchClosureCapture generalizes to an arbitrary
697
+ // cycle length, not just mutual (2-closure) recursion specifically.
698
+ EXPECT_EQ(runCapturingEcho("function test(n) = let(f1 = function(k) k==0 ? \"f1\" : f2(k-1), f2 = "
699
+ "function(k) k==0 ? \"f2\" : f3(k-1), f3 = function(k) k==0 ? \"f3\" : "
700
+ "f1(k-1)) f1(n);\n"
701
+ "echo(test(9));\necho(test(10));\necho(test(11));"),
702
+ "ECHO: \"f1\"\nECHO: \"f2\"\nECHO: \"f3\"");
703
+ }
704
+
638
705
  TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
639
706
  ScopedVm vm(true);
640
707
  // Residual, deliberate limitation: a FunctionLiteral with its OWN