openscad-cpp-evaluator 0.8.1__tar.gz → 0.9.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.8.1 → openscad_cpp_evaluator-0.9.1}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/bytecode.hpp +21 -4
  3. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/bytecode_compiler.cpp +157 -48
  5. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/bytecode_vm.cpp +23 -0
  6. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_bytecode_compiler.cpp +76 -0
  7. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/.github/workflows/ci.yml +0 -0
  8. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/.github/workflows/wheels.yml +0 -0
  9. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/.gitignore +0 -0
  10. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/.gitmodules +0 -0
  11. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/CLAUDE.md +0 -0
  12. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/CMakeLists.txt +0 -0
  13. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/LICENSE +0 -0
  14. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/README.md +0 -0
  15. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/bindings/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/bindings/module.cpp +0 -0
  17. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/cmake/embed_font.cmake +0 -0
  18. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/examples/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/examples/eval_perf_benchmark.cpp +0 -0
  20. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/examples/manifold_cache_reuse.cpp +0 -0
  21. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/examples/minimal_debugger.cpp +0 -0
  22. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  23. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/.gitignore +0 -0
  24. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  25. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/README.md +0 -0
  27. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  28. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  29. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  30. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  31. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  32. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  33. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  34. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  35. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  36. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  38. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  39. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  40. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  41. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  42. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  43. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  44. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  45. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  46. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  47. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  48. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  49. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  50. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  51. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  52. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  53. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  54. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  55. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  56. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  57. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  58. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  59. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  60. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  61. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  62. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  63. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  64. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  65. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  66. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  67. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  68. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  69. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  70. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  71. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  72. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  73. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  74. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  75. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  76. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  77. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  78. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  79. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  80. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  81. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  82. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  83. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  84. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  85. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  86. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  87. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  88. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  89. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  90. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  91. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  92. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  93. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  94. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  95. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  96. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  97. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  98. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  99. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  100. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  101. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  102. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  103. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  104. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  105. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  106. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  107. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  108. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  109. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  110. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  111. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  112. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  113. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  115. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  116. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  117. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  118. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  119. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/resources/fonts/LICENSE +0 -0
  120. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  121. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/CMakeLists.txt +0 -0
  122. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/booleans.cpp +0 -0
  123. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/builtins.hpp +0 -0
  124. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/call_args.cpp +0 -0
  125. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/color.cpp +0 -0
  126. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/control.cpp +0 -0
  127. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/extrude.cpp +0 -0
  128. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/function_builtins.cpp +0 -0
  129. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/import.cpp +0 -0
  130. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/modifiers.cpp +0 -0
  131. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/primitives_2d.cpp +0 -0
  132. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/primitives_3d.cpp +0 -0
  133. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/registry.cpp +0 -0
  134. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/roof.cpp +0 -0
  135. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/segments.cpp +0 -0
  136. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/surface.cpp +0 -0
  137. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/text.cpp +0 -0
  138. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/topology.cpp +0 -0
  139. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/builtins/transforms.cpp +0 -0
  140. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/colored_body.cpp +0 -0
  141. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/csg_generate.cpp +0 -0
  142. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/csg_resolve.cpp +0 -0
  143. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/css_colors.cpp +0 -0
  144. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/debug/debug_repl.cpp +0 -0
  145. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/debug_profile.cpp +0 -0
  146. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/eval_context.cpp +0 -0
  147. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/eval_error.cpp +0 -0
  148. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/eval_use.cpp +0 -0
  149. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/export.cpp +0 -0
  150. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/expr_eval.cpp +0 -0
  151. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/import/dxf_import.cpp +0 -0
  152. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/import/mesh_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/import/surface_load.cpp +0 -0
  154. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/import/svg_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/manifold_cache.cpp +0 -0
  156. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/stmt_eval.cpp +0 -0
  157. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/text/stb_font_provider.cpp +0 -0
  158. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/text/text_metrics.cpp +0 -0
  159. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/user_calls.cpp +0 -0
  160. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/value.cpp +0 -0
  161. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/src/zip_stored.cpp +0 -0
  162. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/CMakeLists.txt +0 -0
  163. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_booleans.cpp +0 -0
  164. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_cli.cpp +0 -0
  165. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_control_flow.cpp +0 -0
  166. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_csg_tree.cpp +0 -0
  167. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_debug_hooks.cpp +0 -0
  168. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_dxf_svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_export.cpp +0 -0
  171. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_extrude_roof.cpp +0 -0
  173. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_function_builtins.cpp +0 -0
  174. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_helpers.hpp +0 -0
  175. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_import_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_multi_color_merge.cpp +0 -0
  178. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_primitives.cpp +0 -0
  179. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.1}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.8.1 → openscad_cpp_evaluator-0.9.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.8.1
3
+ Version: 0.9.1
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
@@ -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.1"
7
+ version = "0.9.1"
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,27 +69,31 @@ 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). 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.
72
+ // compileExpr's own Identifier case). A `let(a = function(...) ...
73
+ // a(...) ...)` self-reference, mutual recursion between two-or-more
74
+ // sibling `let`-bound closures (fnliterals.scad-style isEven/isOdd, each
75
+ // calling the other), and either of those wrapped in a ternary (`let(a =
76
+ // cond ? function(...) ...a(...)... : function(...) ...)`, every reachable
77
+ // branch unambiguously a closure) no longer hit this -- see the LetOp
78
+ // case's own letrec pre-declare, below, and collectLetrecCandidateLiterals'
79
+ // own doc comment for exactly which RHS shapes qualify. Anything the
80
+ // pre-declare doesn't cover still does: a reference buried in something
81
+ // other than a direct-or-ternary closure RHS (`let(a = some_call() ?
82
+ // function(...) ...a(...)... : function(...) ...)`, a non-ternary
83
+ // conditional shape, etc.), or any other free variable this phase simply
84
+ // can't resolve statically. For any of those, Evaluator::evalIdentifier's
85
+ // own ctx.scope->lookupVariable() fallback re-evaluates the let-binding's
86
+ // RHS FRESH on every single call, producing a new Closure whose
87
+ // capturedLet is THIS invocation's own ctx.let_ (see expr_eval.cpp's
88
+ // FunctionLiteral case) rather than a stable snapshot -- registering such
89
+ // a chunk for compiled invocation would still be functionally correct,
90
+ // but each recursive call's own capturedLet->openChild() then nests ONE
91
+ // level deeper than the last (confirmed empirically: an O(n) list
92
+ // reduce() degrading to O(n^2) once compiled, before the letrec pre-
93
+ // declare existed), since nothing about that repeated fresh-derivation
94
+ // ever re-roots the trail. Excluding a LoadFree-containing chunk from
95
+ // registration leaves it running interpreted exactly as before --
96
+ // correct, if not optimized.
92
97
  bool containsLoadFree(const std::vector<Instruction>& code) {
93
98
  for (const Instruction& ins : code) {
94
99
  if (ins.op == Op::LoadFree) return true;
@@ -104,6 +109,38 @@ bool containsLoadFree(const CompiledChunk& chunk) {
104
109
  return false;
105
110
  }
106
111
 
112
+ // Recursively collects every FunctionLiteral `expr` could actually
113
+ // evaluate to at runtime -- a bare literal, or a ternary (or nested
114
+ // ternaries) whose branches ALL recursively qualify, e.g. `cond ?
115
+ // function(...) ...a(...)... : function(...) ...` (the ternary-wrapped
116
+ // self/mutual-reference case the direct-RHS-only letrec pre-declare,
117
+ // below, doesn't reach on its own). Returns std::nullopt -- not just an
118
+ // empty vector -- the moment ANY reachable branch ISN'T reliably a
119
+ // closure (a plain value, a call, anything else): letrec pre-declaration
120
+ // only makes sense when EVERY runtime path through `expr` really does
121
+ // produce a fresh closure that might reference the let-binding's own
122
+ // name, never a mix. A CommentedExpr wrapper is transparent, matching
123
+ // compileExpr's own handling of it everywhere else.
124
+ std::optional<std::vector<const oscad::FunctionLiteral*>> collectLetrecCandidateLiterals(const oscad::ASTNode& expr) {
125
+ using oscad::NodeKind;
126
+ if (expr.kind() == NodeKind::CommentedExpr) {
127
+ return collectLetrecCandidateLiterals(*static_cast<const oscad::CommentedExpr&>(expr).expr);
128
+ }
129
+ if (expr.kind() == NodeKind::FunctionLiteral) {
130
+ return std::vector<const oscad::FunctionLiteral*>{static_cast<const oscad::FunctionLiteral*>(&expr)};
131
+ }
132
+ if (expr.kind() == NodeKind::TernaryOp) {
133
+ auto& t = static_cast<const oscad::TernaryOp&>(expr);
134
+ auto trueLits = collectLetrecCandidateLiterals(*t.trueExpr);
135
+ if (!trueLits) return std::nullopt;
136
+ auto falseLits = collectLetrecCandidateLiterals(*t.falseExpr);
137
+ if (!falseLits) return std::nullopt;
138
+ trueLits->insert(trueLits->end(), falseLits->begin(), falseLits->end());
139
+ return trueLits;
140
+ }
141
+ return std::nullopt;
142
+ }
143
+
107
144
  // Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
108
145
  // shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
109
146
  // LetOp (pushed/popped around its own assignments+body), innermost frame
@@ -566,35 +603,96 @@ public:
566
603
  size_t placeholderIdx = out.size();
567
604
  out.push_back({Op::OpenLocalScope, 0, 0, nullptr});
568
605
  int slotStart = nextSlot_;
569
- for (const auto& assign : n.assignments) {
606
+ // Letrec pre-pass: every assignment whose RHS is GUARANTEED
607
+ // to evaluate to a closure -- a direct `name =
608
+ // function(...) ...`, or `name = cond ? function(...) ... :
609
+ // function(...) ...` (or nested ternaries of the same
610
+ // shape) -- gets its own slot declared up front, before ANY
611
+ // of them are compiled -- not just so a closure can
612
+ // reference itself (see the self-reference handling
613
+ // below), but so an EARLIER one can also reference a LATER
614
+ // sibling (mutual recursion, e.g. fnliterals.scad-style
615
+ // isEven/isOdd calling each other). `letrecSlot[i]` stays
616
+ // -1 for anything else, which keeps the existing after-the-
617
+ // fact declareLocal path below completely unchanged
618
+ // (`let(x = x + 1)` must still see the OUTER x, not a
619
+ // fresh, not-yet-assigned local shadowing it -- only an
620
+ // RHS that's UNAMBIGUOUSLY always a closure has "see
621
+ // myself/a sibling" as a sensible reading at all; see
622
+ // collectLetrecCandidateLiterals' own doc comment, above,
623
+ // for why a mixed/uncertain RHS shape doesn't qualify).
624
+ // `letrecCandidates[i]` is every FunctionLiteral node `i`'s
625
+ // own RHS could actually construct at runtime -- more than
626
+ // one for a ternary -- each independently checked for a
627
+ // self/sibling reference below, since either one might be
628
+ // the value `name` ends up holding.
629
+ std::vector<int> letrecSlot(n.assignments.size(), -1);
630
+ std::vector<std::vector<const oscad::FunctionLiteral*>> letrecCandidates(n.assignments.size());
631
+ std::unordered_set<int> pendingLetrecSlots;
632
+ for (size_t i = 0; i < n.assignments.size(); ++i) {
633
+ const auto& assign = n.assignments[i];
634
+ const std::string& name = assign->name->name;
635
+ if (name.empty() || name[0] == '$') continue;
636
+ if (auto candidates = collectLetrecCandidateLiterals(*assign->expr)) {
637
+ letrecSlot[i] = declareLocal(scope, name);
638
+ letrecCandidates[i] = std::move(*candidates);
639
+ pendingLetrecSlots.insert(letrecSlot[i]);
640
+ }
641
+ }
642
+ // consumerSlot/name pairs an EARLIER closure's own
643
+ // Op::MakeClosure left unresolved because they targeted a
644
+ // sibling that didn't exist yet at that point -- resolved
645
+ // (an Op::PatchClosureCapture emitted) the moment that
646
+ // sibling's own StoreLocal runs, below.
647
+ std::unordered_map<int, std::vector<std::pair<int, std::string>>> pendingWaiters;
648
+ for (size_t i = 0; i < n.assignments.size(); ++i) {
649
+ const auto& assign = n.assignments[i];
570
650
  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);
651
+ const int preDeclaredSlot = letrecSlot[i];
652
+ const bool selfBinding = preDeclaredSlot >= 0;
593
653
  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;
654
+ for (const oscad::FunctionLiteral* candidate : letrecCandidates[i]) {
655
+ CompiledChunk::ClosureSite* site = nullptr;
656
+ for (auto& s : chunk_.closureSites) {
657
+ if (s.node == candidate) {
658
+ site = &s;
659
+ break;
660
+ }
661
+ }
662
+ // No entry at all means this particular candidate
663
+ // (e.g. one branch of a ternary) never referenced
664
+ // itself/a sibling/anything else -- zero captures,
665
+ // still the cheaper Op::PushConst path, nothing
666
+ // here to patch.
667
+ if (!site) continue;
668
+ auto& captures = site->captures;
669
+ for (auto it = captures.begin(); it != captures.end();) {
670
+ if (it->targetDecl != selfDecl_ || !pendingLetrecSlots.count(it->slot)) {
671
+ ++it;
672
+ continue;
673
+ }
674
+ if (it->slot == preDeclaredSlot) {
675
+ // See Op::MakeClosure's own runtime handler
676
+ // (bytecode_vm.cpp): the closure being
677
+ // created doesn't exist yet at the moment
678
+ // its own captures are normally snapshotted,
679
+ // so this one is deferred and patched in
680
+ // right after construction instead of read
681
+ // eagerly like every other capture.
682
+ it->isSelfReference = true;
683
+ ++it;
684
+ } else {
685
+ // Forward reference to a sibling that hasn't
686
+ // been constructed AT ALL yet -- unlike the
687
+ // self-reference case above, there's nothing
688
+ // to read OR self-patch into here. Left out
689
+ // of `captures` entirely (nothing for
690
+ // Op::MakeClosure to even attempt) and
691
+ // deferred to a real Op::PatchClosureCapture
692
+ // once that sibling's own StoreLocal runs.
693
+ pendingWaiters[it->slot].emplace_back(preDeclaredSlot, it->name);
694
+ it = captures.erase(it);
695
+ }
598
696
  }
599
697
  }
600
698
  if (!name.empty() && name[0] == '$') {
@@ -602,6 +700,17 @@ public:
602
700
  } else {
603
701
  int slot = selfBinding ? preDeclaredSlot : declareLocal(scope, name);
604
702
  out.push_back({Op::StoreLocal, slot, 0, &assign->position()});
703
+ if (selfBinding) {
704
+ pendingLetrecSlots.erase(slot);
705
+ auto waitersIt = pendingWaiters.find(slot);
706
+ if (waitersIt != pendingWaiters.end()) {
707
+ for (const auto& [consumerSlot, capName] : waitersIt->second) {
708
+ out.push_back({Op::PatchClosureCapture, consumerSlot, internName(capName),
709
+ &assign->position(), nullptr, slot});
710
+ }
711
+ pendingWaiters.erase(waitersIt);
712
+ }
713
+ }
605
714
  }
606
715
  }
607
716
  out[placeholderIdx].a = slotStart;
@@ -203,6 +203,29 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
203
203
  ++pc;
204
204
  break;
205
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
+ }
226
+ ++pc;
227
+ break;
228
+ }
206
229
  case Op::Range: {
207
230
  Value step = std::move(stack.back());
208
231
  stack.pop_back();
@@ -659,6 +659,82 @@ TEST(BytecodeCompiler, PlainLetSelfReferenceStillSeesTheOuterBindingNotItself) {
659
659
  EXPECT_EQ(runCapturingEcho("x = 100;\nfunction f() = let(x = x + 1) x;\necho(f());"), "ECHO: 101");
660
660
  }
661
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
+
705
+ TEST(BytecodeCompiler, TernaryWrappedSelfReferenceResolvesAsUpvalue) {
706
+ ScopedVm vm(true);
707
+ // The letrec pre-declare's own eligibility check isn't limited to a
708
+ // bare `name = function(...) ...` RHS -- `cond ? function(...) ... :
709
+ // function(...) ...` (EVERY reachable branch is unambiguously a
710
+ // closure, see collectLetrecCandidateLiterals' own doc comment,
711
+ // bytecode_compiler.cpp) also pre-declares, and BOTH branches'
712
+ // FunctionLiteral nodes are independently found and marked afterward
713
+ // (a ternary can produce two structurally distinct closures, not just
714
+ // one). Deep, genuinely TAIL-recursive (`acc` accumulates on the way
715
+ // down, not after the recursive call returns -- see this file's own
716
+ // DeepSelfTailRecursionUnderVmDoesNotOverflowTheNativeStack, above, for
717
+ // why that distinction matters at this depth) to prove it actually
718
+ // runs compiled, not just correctly-but-slowly interpreted.
719
+ const std::string script =
720
+ "function make(n) = let(a = n > 0 ? function(k, acc) k<=0 ? acc : a(k-1, acc+n) : function(k, acc) acc) "
721
+ "a(200000, 0);\necho(make(1));";
722
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 200000");
723
+ }
724
+
725
+ TEST(BytecodeCompiler, TernaryWrappedMutualRecursionResolvesCorrectly) {
726
+ ScopedVm vm(true);
727
+ // Same ternary-wrapped-RHS eligibility as the self-reference case
728
+ // above, but for TWO siblings that reference each other -- both
729
+ // Op::PatchClosureCapture's forward-reference deferral and the
730
+ // ternary's own multi-candidate handling apply simultaneously.
731
+ const std::string script =
732
+ "function test(n) = let(isEven = n >= 0 ? function(k) k==0 ? true : isOdd(k-1) : function(k) undef, "
733
+ "isOdd = n >= 0 ? function(k) k==0 ? false : isEven(k-1) : function(k) undef) isEven(n);\n"
734
+ "echo(test(10));\necho(test(7));";
735
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: true\nECHO: false");
736
+ }
737
+
662
738
  TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
663
739
  ScopedVm vm(true);
664
740
  // Residual, deliberate limitation: a FunctionLiteral with its OWN