openscad-cpp-evaluator 0.9.0__tar.gz → 0.9.2__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.9.0 → openscad_cpp_evaluator-0.9.2}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/evaluator.hpp +41 -0
  3. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/bytecode_compiler.cpp +99 -38
  5. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_bytecode_compiler.cpp +33 -0
  6. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_tail_calls.cpp +61 -4
  7. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/.github/workflows/ci.yml +0 -0
  8. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/.github/workflows/wheels.yml +0 -0
  9. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/.gitignore +0 -0
  10. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/.gitmodules +0 -0
  11. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/CLAUDE.md +0 -0
  12. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/CMakeLists.txt +0 -0
  13. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/LICENSE +0 -0
  14. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/README.md +0 -0
  15. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/bindings/CMakeLists.txt +0 -0
  16. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/bindings/module.cpp +0 -0
  17. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/cmake/embed_font.cmake +0 -0
  18. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/examples/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/examples/eval_perf_benchmark.cpp +0 -0
  20. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/examples/manifold_cache_reuse.cpp +0 -0
  21. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/examples/minimal_debugger.cpp +0 -0
  22. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  23. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/.gitignore +0 -0
  24. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  25. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/README.md +0 -0
  27. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  28. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  29. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  30. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  31. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  32. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  33. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  34. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  35. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  36. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  38. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  39. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  40. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  41. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  42. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  43. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/api.cpp +0 -0
  44. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  45. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  46. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  47. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  48. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  49. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  50. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  51. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  52. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  53. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  54. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  55. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  56. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  57. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  58. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  59. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  60. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/position.cpp +0 -0
  61. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  62. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  63. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  64. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  65. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  66. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  67. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  68. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  69. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  70. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  71. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  72. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  73. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  74. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  75. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  76. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  77. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  78. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  79. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  80. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  81. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  82. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  83. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  84. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  85. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  86. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  87. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  88. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  89. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  90. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  91. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  92. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  94. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  95. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  96. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  97. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  98. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  99. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  100. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  101. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  102. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  103. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/export.hpp +0 -0
  104. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  105. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  106. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  107. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  108. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  109. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  110. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  111. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  112. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  113. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  115. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  116. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/value.hpp +0 -0
  117. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  118. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/python/openscad_cpp_evaluator/__init__.py +0 -0
  119. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/resources/fonts/LICENSE +0 -0
  120. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  121. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/CMakeLists.txt +0 -0
  122. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/booleans.cpp +0 -0
  123. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/builtins.hpp +0 -0
  124. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/call_args.cpp +0 -0
  125. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/color.cpp +0 -0
  126. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/control.cpp +0 -0
  127. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/extrude.cpp +0 -0
  128. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/function_builtins.cpp +0 -0
  129. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/import.cpp +0 -0
  130. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/modifiers.cpp +0 -0
  131. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/primitives_2d.cpp +0 -0
  132. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/primitives_3d.cpp +0 -0
  133. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/registry.cpp +0 -0
  134. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/roof.cpp +0 -0
  135. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/segments.cpp +0 -0
  136. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/surface.cpp +0 -0
  137. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/text.cpp +0 -0
  138. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/topology.cpp +0 -0
  139. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/builtins/transforms.cpp +0 -0
  140. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/bytecode_vm.cpp +0 -0
  141. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/colored_body.cpp +0 -0
  142. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/csg_generate.cpp +0 -0
  143. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/csg_resolve.cpp +0 -0
  144. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/css_colors.cpp +0 -0
  145. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/debug/debug_repl.cpp +0 -0
  146. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/debug_profile.cpp +0 -0
  147. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/eval_context.cpp +0 -0
  148. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/eval_error.cpp +0 -0
  149. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/eval_use.cpp +0 -0
  150. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/export.cpp +0 -0
  151. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/expr_eval.cpp +0 -0
  152. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/import/dxf_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/import/mesh_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/import/surface_load.cpp +0 -0
  155. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/import/svg_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/manifold_cache.cpp +0 -0
  157. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/stmt_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/text/stb_font_provider.cpp +0 -0
  159. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/text/text_metrics.cpp +0 -0
  160. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/user_calls.cpp +0 -0
  161. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_control_flow.cpp +0 -0
  167. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_csg_tree.cpp +0 -0
  168. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_debug_hooks.cpp +0 -0
  169. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_dxf_svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_export.cpp +0 -0
  172. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_extrude_roof.cpp +0 -0
  174. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_function_builtins.cpp +0 -0
  175. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_helpers.hpp +0 -0
  176. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_import_export.cpp +0 -0
  177. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_multi_color_merge.cpp +0 -0
  179. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_primitives.cpp +0 -0
  180. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_profiling.cpp +0 -0
  181. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_python_bindings.py +0 -0
  182. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_scope_trail.cpp +0 -0
  183. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_scoping.cpp +0 -0
  184. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_surface.cpp +0 -0
  185. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.9.0 → openscad_cpp_evaluator-0.9.2}/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.9.0
3
+ Version: 0.9.2
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -633,6 +633,47 @@ private:
633
633
  Value evalUserFunctionCore(const std::string& name, const oscad::ASTNode& declNode, const oscad::Expression& bodyExpr,
634
634
  EvalContext& childCtx, const oscad::Position* callPos, int upvalueParent,
635
635
  F&& computeResult) {
636
+ // Guards against a genuinely non-tail-recursive user function --
637
+ // each logical call HERE is a real, unavoidable C++ recursive call
638
+ // (a trampolined tail call never re-enters this function at all,
639
+ // see evalFunctionBodyTrampoline/runCompiledFunctionTrampoline) --
640
+ // silently overflowing the native stack: a hard, unrecoverable
641
+ // process crash with no C++ exception to catch. Confirmed present
642
+ // (before this guard existed) for a plain, closure-free `function
643
+ // f(n) = n<=0 ? 0 : 1+f(n-1);`, compiled OR interpreted.
644
+ //
645
+ // kMaxUserCallDepth is a conservative, heuristic cap, NOT a
646
+ // precisely-calibrated one -- and specifically calibrated against
647
+ // the WORST measured platform, not the best. Two rounds of CI
648
+ // diagnostics (temporary test builds that recursed to increasing
649
+ // depths, printing success after each one so the CI log itself
650
+ // would show exactly where a crash happened) found: (1) this
651
+ // evaluator's own C++ recursion is safe into the THOUSANDS on an
652
+ // ordinary macOS/Linux desktop main-thread stack, but only into
653
+ // the low HUNDREDS on CI's windows-latest runner (smaller default
654
+ // thread stack and/or larger MSVC-compiled call frames than
655
+ // Clang's); (2) actually THROWING from that depth and unwinding
656
+ // back out through that many nested try/catch frames (this guard
657
+ // fires from inside evalUserFunctionCore, which every level of
658
+ // the recursion has its own try/catch in) costs meaningfully MORE
659
+ // native stack than simply being that deep without throwing --
660
+ // 300 survived on Windows without throwing, but throwing+
661
+ // unwinding through as few as 100 nested frames did not (80 was
662
+ // the last depth that survived). 50 leaves real margin below that
663
+ // 80 figure for whatever's already on the stack from this call's
664
+ // own caller, and for a smaller stack still (e.g. BelfrySCAD's own
665
+ // debug-session worker thread, plausibly smaller than any CI
666
+ // runner's main thread) that this evaluator has no way to probe
667
+ // for directly. A real OpenSCAD/BOSL2 recursive function is
668
+ // overwhelmingly tail-recursive in practice anyway (exactly why
669
+ // reduce()/accumulate()/while() and this evaluator's own
670
+ // trampolines exist) -- genuine non-tail recursion even
671
+ // approaching this depth is already the rare, likely-runaway case
672
+ // a controlled error serves far better than a crash.
673
+ static constexpr size_t kMaxUserCallDepth = 50;
674
+ if (callStack_.size() >= kMaxUserCallDepth) {
675
+ error("Recursion too deep while calling function '" + name + "'", declNode);
676
+ }
636
677
  std::optional<ProfileHandle> prof = profileEnter("function", name, callPos, &declNode.position());
637
678
  callStack_.push_back(CallStackFrame{CallStackFrame::Kind::Function, name, callPos, &declNode.position(), &declNode,
638
679
  nullptr, upvalueParent});
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.9.0"
7
+ version = "0.9.2"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -69,27 +69,31 @@ void bubbleEscapingCaptures(const CompiledChunk::ClosureSite& site, const oscad:
69
69
  // True if any instruction in `chunk`'s own body/defaults is Op::LoadFree --
70
70
  // i.e. an identifier that resolved to neither a local, a statically-known
71
71
  // enclosing upvalue, nor a dyn ($-prefixed) name at compile time (see
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.
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.
93
97
  bool containsLoadFree(const std::vector<Instruction>& code) {
94
98
  for (const Instruction& ins : code) {
95
99
  if (ins.op == Op::LoadFree) return true;
@@ -105,6 +109,38 @@ bool containsLoadFree(const CompiledChunk& chunk) {
105
109
  return false;
106
110
  }
107
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
+
108
144
  // Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
109
145
  // shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
110
146
  // LetOp (pushed/popped around its own assignments+body), innermost frame
@@ -567,26 +603,39 @@ public:
567
603
  size_t placeholderIdx = out.size();
568
604
  out.push_back({Op::OpenLocalScope, 0, 0, nullptr});
569
605
  int slotStart = nextSlot_;
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).
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.
583
629
  std::vector<int> letrecSlot(n.assignments.size(), -1);
630
+ std::vector<std::vector<const oscad::FunctionLiteral*>> letrecCandidates(n.assignments.size());
584
631
  std::unordered_set<int> pendingLetrecSlots;
585
632
  for (size_t i = 0; i < n.assignments.size(); ++i) {
586
633
  const auto& assign = n.assignments[i];
587
634
  const std::string& name = assign->name->name;
588
- if (!name.empty() && name[0] != '$' && assign->expr->kind() == oscad::NodeKind::FunctionLiteral) {
635
+ if (name.empty() || name[0] == '$') continue;
636
+ if (auto candidates = collectLetrecCandidateLiterals(*assign->expr)) {
589
637
  letrecSlot[i] = declareLocal(scope, name);
638
+ letrecCandidates[i] = std::move(*candidates);
590
639
  pendingLetrecSlots.insert(letrecSlot[i]);
591
640
  }
592
641
  }
@@ -602,9 +651,21 @@ public:
602
651
  const int preDeclaredSlot = letrecSlot[i];
603
652
  const bool selfBinding = preDeclaredSlot >= 0;
604
653
  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;
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;
608
669
  for (auto it = captures.begin(); it != captures.end();) {
609
670
  if (it->targetDecl != selfDecl_ || !pendingLetrecSlots.count(it->slot)) {
610
671
  ++it;
@@ -702,6 +702,39 @@ TEST(BytecodeCompiler, ThreeWaySiblingMutualRecursionResolvesCorrectly) {
702
702
  "ECHO: \"f1\"\nECHO: \"f2\"\nECHO: \"f3\"");
703
703
  }
704
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
+
705
738
  TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
706
739
  ScopedVm vm(true);
707
740
  // Residual, deliberate limitation: a FunctionLiteral with its OWN
@@ -9,10 +9,15 @@
9
9
  // Every test here explicitly forces the interpreter path via ScopedVm(false)
10
10
  // (not the OSCAD_BYTECODE_VM env var, which is cached forever after its
11
11
  // first read within a test binary -- see Evaluator::
12
- // setBytecodeVmEnabledForTesting's own doc comment): the bytecode VM's own
13
- // call machinery (Phase B, not yet implemented as of this file) still does
14
- // genuine C++ recursion for every call, so a deep-recursion test run under
15
- // the default VM-on path would crash for the wrong reason.
12
+ // setBytecodeVmEnabledForTesting's own doc comment). Both the interpreter
13
+ // AND the bytecode VM's own call machinery (Phase B) do genuine C++
14
+ // recursion for a NON-tail call (there is no other way to implement one --
15
+ // see NonTailRecursionIsUnaffectedByTheTrampoline, below) -- a sufficiently
16
+ // deep one used to silently overflow the native stack and crash the whole
17
+ // process; evalUserFunctionCore's own depth guard (evaluator.hpp) now turns
18
+ // that into a controlled EvalError well before that point, shared by both
19
+ // paths, exercised by both compiled- and interpreted-path variants of the
20
+ // same test at the bottom of this file.
16
21
 
17
22
  #include "openscad_cpp_evaluator/eval_error.hpp"
18
23
  #include "openscad_cpp_evaluator/evaluator.hpp"
@@ -267,3 +272,55 @@ TEST(TailCalls, DebugHookFiresPerHopInsideATailChain) {
267
272
  // single body-entry stop.
268
273
  EXPECT_EQ(bodyEntry, 501 + 500 + 1);
269
274
  }
275
+
276
+ TEST(TailCalls, DeepNonTailRecursionHitsAControlledErrorInsteadOfCrashingInterpreted) {
277
+ ScopedVm vm(false);
278
+ // `1 + f(n-1)` is NOT a tail position (the addition happens after the
279
+ // recursive call returns) -- there is no trampoline for this, ever
280
+ // (see NonTailRecursionIsUnaffectedByTheTrampoline, above): every
281
+ // logical call is a real, unavoidable C++ recursive call through
282
+ // evalUserFunctionCore. Before its own depth guard existed, a script
283
+ // shaped exactly like this one reliably segfaulted the whole process
284
+ // (confirmed present in the wild -- a plain, closure-free
285
+ // FunctionDeclaration, nothing specific to closures/letrec/tail-call
286
+ // machinery at all). Interpreted-path variant; see the compiled-path
287
+ // one below. f(500) is comfortably past kMaxUserCallDepth (50, see its
288
+ // own doc comment, evaluator.hpp) -- the actual native recursion never
289
+ // gets anywhere near 500, since the guard fires at 50 regardless of
290
+ // what depth the script asks for.
291
+ Evaluator ev;
292
+ auto ast = parseSrc("function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\nresult = f(500);");
293
+ auto scope = oscad::buildScopes(ast);
294
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
295
+ try {
296
+ ev.resolveTree(ast, ctx);
297
+ FAIL() << "expected EvalError";
298
+ } catch (const EvalError& e) {
299
+ EXPECT_NE(std::string(e.what()).find("Recursion too deep"), std::string::npos);
300
+ EXPECT_NE(std::string(e.what()).find("'f'"), std::string::npos);
301
+ }
302
+ }
303
+
304
+ TEST(TailCalls, DeepNonTailRecursionHitsAControlledErrorInsteadOfCrashingCompiled) {
305
+ ScopedVm vm(true);
306
+ // Same script, same guard (evalUserFunctionCore is shared by both the
307
+ // interpreter and the bytecode VM's own compiled call path) -- the
308
+ // compiled path's own real per-call native stack usage is actually
309
+ // larger (more C++ frames between one logical call and the next, see
310
+ // runChunk/runCompiledFunctionFromBound/runCompiledFunctionFromBoundTrampoline),
311
+ // so this is the MORE exposed of the two paths, not a lesser check.
312
+ Evaluator ev;
313
+ auto ast = parseSrc("function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\nresult = f(500);");
314
+ auto scope = oscad::buildScopes(ast);
315
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
316
+ EXPECT_THROW(ev.resolveTree(ast, ctx), EvalError);
317
+ }
318
+
319
+ TEST(TailCalls, ShallowNonTailRecursionStillWorksUnderTheDepthGuard) {
320
+ ScopedVm vm(true);
321
+ // The guard must not fire for perfectly ordinary, shallow non-tail
322
+ // recursion -- the overwhelmingly common real-world shape (a handful
323
+ // to a few dozen levels, e.g. walking a small nested data structure).
324
+ const std::string script = "function fact(n) = n <= 1 ? 1 : n * fact(n - 1);\necho(fact(10));";
325
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 3.6288e+6");
326
+ }