openscad-cpp-evaluator 0.9.1__tar.gz → 0.10.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.9.1 → openscad_cpp_evaluator-0.10.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/bytecode.hpp +5 -1
  3. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/evaluator.hpp +41 -0
  4. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/bytecode_compiler.cpp +11 -11
  6. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/bytecode_vm.cpp +54 -27
  7. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_bytecode_compiler.cpp +46 -23
  8. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_tail_calls.cpp +102 -4
  9. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/.github/workflows/ci.yml +0 -0
  10. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/.github/workflows/wheels.yml +0 -0
  11. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/.gitignore +0 -0
  12. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/.gitmodules +0 -0
  13. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/CLAUDE.md +0 -0
  14. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/CMakeLists.txt +0 -0
  15. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/LICENSE +0 -0
  16. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/README.md +0 -0
  17. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/bindings/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/bindings/module.cpp +0 -0
  19. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/cmake/embed_font.cmake +0 -0
  20. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/examples/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/examples/eval_perf_benchmark.cpp +0 -0
  22. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/examples/manifold_cache_reuse.cpp +0 -0
  23. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/examples/minimal_debugger.cpp +0 -0
  24. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  25. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/.gitignore +0 -0
  26. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  27. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/README.md +0 -0
  29. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  30. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  31. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  32. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  33. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  34. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  35. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  36. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  37. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  38. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  39. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  40. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  41. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  42. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  43. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  44. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  45. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  46. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  47. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  48. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  49. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  50. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  51. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  52. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  53. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  54. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  55. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  56. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  57. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  58. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  59. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  60. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  61. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  62. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  63. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  64. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  65. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  66. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  67. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  68. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  69. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  70. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  71. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  72. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  73. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  74. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  75. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  76. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  77. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  78. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  79. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  80. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  81. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  82. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  83. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  84. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  85. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  86. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  87. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  88. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  89. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  90. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  91. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  92. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  93. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  94. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  95. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  96. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  97. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  98. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  99. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  100. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  101. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  102. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  103. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  104. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  105. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  106. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  107. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  108. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  109. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  110. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  111. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  112. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  113. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  114. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  116. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  117. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  118. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  119. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  120. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/resources/fonts/LICENSE +0 -0
  121. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  122. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/CMakeLists.txt +0 -0
  123. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/booleans.cpp +0 -0
  124. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/builtins.hpp +0 -0
  125. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/call_args.cpp +0 -0
  126. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/color.cpp +0 -0
  127. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/control.cpp +0 -0
  128. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/extrude.cpp +0 -0
  129. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/function_builtins.cpp +0 -0
  130. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/import.cpp +0 -0
  131. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/modifiers.cpp +0 -0
  132. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/primitives_2d.cpp +0 -0
  133. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/primitives_3d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/registry.cpp +0 -0
  135. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/roof.cpp +0 -0
  136. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/segments.cpp +0 -0
  137. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/surface.cpp +0 -0
  138. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/text.cpp +0 -0
  139. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/topology.cpp +0 -0
  140. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/builtins/transforms.cpp +0 -0
  141. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/colored_body.cpp +0 -0
  142. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/csg_generate.cpp +0 -0
  143. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/csg_resolve.cpp +0 -0
  144. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/css_colors.cpp +0 -0
  145. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/debug/debug_repl.cpp +0 -0
  146. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/debug_profile.cpp +0 -0
  147. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/eval_context.cpp +0 -0
  148. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/eval_error.cpp +0 -0
  149. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/eval_use.cpp +0 -0
  150. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/export.cpp +0 -0
  151. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/expr_eval.cpp +0 -0
  152. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/import/dxf_import.cpp +0 -0
  153. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/import/mesh_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/import/surface_load.cpp +0 -0
  155. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/import/svg_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/manifold_cache.cpp +0 -0
  157. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/stmt_eval.cpp +0 -0
  158. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/text/stb_font_provider.cpp +0 -0
  159. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/text/text_metrics.cpp +0 -0
  160. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/user_calls.cpp +0 -0
  161. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_control_flow.cpp +0 -0
  167. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_csg_tree.cpp +0 -0
  168. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_debug_hooks.cpp +0 -0
  169. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_dxf_svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_export.cpp +0 -0
  172. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_extrude_roof.cpp +0 -0
  174. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_function_builtins.cpp +0 -0
  175. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_helpers.hpp +0 -0
  176. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_import_export.cpp +0 -0
  177. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_multi_color_merge.cpp +0 -0
  179. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_primitives.cpp +0 -0
  180. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_profiling.cpp +0 -0
  181. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_python_bindings.py +0 -0
  182. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_scope_trail.cpp +0 -0
  183. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_scoping.cpp +0 -0
  184. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_surface.cpp +0 -0
  185. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.9.1 → openscad_cpp_evaluator-0.10.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.9.1
3
+ Version: 0.10.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -241,7 +241,11 @@ struct Instruction {
241
241
  struct CompiledChunk {
242
242
  struct Param {
243
243
  std::string name;
244
- int slot = 0;
244
+ int slot = 0; // unused when isDyn is true -- $-params are never slot-addressed
245
+ // $-prefixed parameter: binds through ctx.dyn (dynamically scoped)
246
+ // instead of a slot -- see compileFunctionLike's own doc comment,
247
+ // bytecode_compiler.cpp, and bindCompiledArgs', bytecode_vm.cpp.
248
+ bool isDyn = false;
245
249
  };
246
250
 
247
251
  // A call site statically resolved at compile time (see
@@ -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.1"
7
+ version = "0.10.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -193,7 +193,7 @@ class Compiler;
193
193
  // (a non-empty `enclosing` chain, letting the literal's own free-variable
194
194
  // references resolve as upvalues into whatever lexically contains it).
195
195
  // Returns false (chunk left partially populated, discarded by the caller)
196
- // for a $-prefixed parameter or any NotCompilable thrown while compiling.
196
+ // for any NotCompilable thrown while compiling.
197
197
  bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope, const oscad::ASTNode* selfDecl,
198
198
  std::vector<EnclosingLevel> enclosing,
199
199
  const std::vector<std::unique_ptr<oscad::ParameterDeclaration>>& params,
@@ -1075,21 +1075,21 @@ bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope,
1075
1075
  std::vector<EnclosingLevel> enclosing,
1076
1076
  const std::vector<std::unique_ptr<oscad::ParameterDeclaration>>& params,
1077
1077
  const oscad::Expression& bodyExpr) {
1078
- for (const auto& p : params) {
1079
- // $-prefixed parameters bind through ctx.dyn (dynamically scoped,
1080
- // never slot-addressed) via the existing applyDefaults/bindArgs
1081
- // machinery -- out of scope for this design, see this file's
1082
- // header comment.
1083
- if (!p->name->name.empty() && p->name->name[0] == '$') return false;
1084
- }
1085
-
1086
1078
  chunk.selfDecl = selfDecl;
1087
1079
  Compiler compiler(chunk, staticScope, selfDecl, std::move(enclosing));
1088
1080
  CompileScope bodyScope;
1089
1081
  bodyScope.push();
1090
1082
  for (const auto& p : params) {
1091
- int slot = compiler.declareLocal(bodyScope, p->name->name);
1092
- chunk.params.push_back(CompiledChunk::Param{p->name->name, slot});
1083
+ // $-prefixed parameters bind through ctx.dyn (dynamically scoped)
1084
+ // rather than a slot -- never declared as a local, so a reference
1085
+ // to one inside the body already compiles to Op::LoadDyn via the
1086
+ // Identifier case's own $-check regardless of scope visibility.
1087
+ // Still occupies its declared POSITION among all parameters (see
1088
+ // bindCompiledArgs, bytecode_vm.cpp) for positional-argument
1089
+ // matching, exactly like the interpreter's own bindArgs.
1090
+ const bool isDyn = !p->name->name.empty() && p->name->name[0] == '$';
1091
+ const int slot = isDyn ? 0 : compiler.declareLocal(bodyScope, p->name->name);
1092
+ chunk.params.push_back(CompiledChunk::Param{p->name->name, slot, isDyn});
1093
1093
  }
1094
1094
  chunk.defaultCode.resize(params.size());
1095
1095
 
@@ -604,17 +604,18 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
604
604
  // Mirrors Evaluator::bindArgs' own positional/named matching exactly (same
605
605
  // "later write for a repeated name wins," same "evaluate every argument
606
606
  // expression even if its name doesn't match a declared parameter" side-
607
- // effect rule), but writes matched-plain-parameter values directly into
608
- // `slots` instead of a fresh unordered_map, and routes a matched-$-name
609
- // straight to `childCtx.dyn` (childCtx.dyn->set) since $-parameters never
610
- // reach compilation (see tryCompileFunction) -- so ANY $-named argument here
611
- // is necessarily an undeclared override, exactly like today's interpreter
612
- // path. An undeclared plain (non-$) name has no slot and nothing in this
613
- // chunk can ever reference it by name, so its already-evaluated value is
614
- // simply discarded -- ponytail: ctx.let_ isn't written for it either
615
- // (unlike the interpreter path), which changes nothing observable, since
616
- // nothing downstream of a compiled call ever reads ctx.let_ for a name a
617
- // compiled body didn't itself resolve to a slot.
607
+ // effect rule), but writes a matched plain-parameter value directly into
608
+ // `slots` instead of a fresh unordered_map, and routes a matched DECLARED
609
+ // $-parameter (chunk.params[i].isDyn) straight to `childCtx.dyn` instead of
610
+ // a slot -- see compileFunctionLike's own doc comment, bytecode_compiler.cpp.
611
+ // A $-named argument that doesn't match any declared parameter is still an
612
+ // undeclared override, exactly like today's interpreter path -- routed to
613
+ // `childCtx.dyn` too. An undeclared plain (non-$) name has no slot and
614
+ // nothing in this chunk can ever reference it by name, so its already-
615
+ // evaluated value is simply discarded -- ponytail: ctx.let_ isn't written
616
+ // for it either (unlike the interpreter path), which changes nothing
617
+ // observable, since nothing downstream of a compiled call ever reads
618
+ // ctx.let_ for a name a compiled body didn't itself resolve to a slot.
618
619
  void bindCompiledArgs(Evaluator& ev, const CompiledChunk& chunk,
619
620
  const std::vector<std::unique_ptr<oscad::Argument>>& arguments, EvalContext& callerCtx,
620
621
  EvalContext& childCtx, std::vector<Value>& slots, std::vector<bool>& bound) {
@@ -628,7 +629,11 @@ void bindCompiledArgs(Evaluator& ev, const CompiledChunk& chunk,
628
629
  bool matched = false;
629
630
  for (size_t i = 0; i < nparams; ++i) {
630
631
  if (chunk.params[i].name == name) {
631
- slots[static_cast<size_t>(chunk.params[i].slot)] = std::move(v);
632
+ if (chunk.params[i].isDyn) {
633
+ childCtx.dyn->set(name, std::move(v));
634
+ } else {
635
+ slots[static_cast<size_t>(chunk.params[i].slot)] = std::move(v);
636
+ }
632
637
  bound[i] = true;
633
638
  matched = true;
634
639
  break;
@@ -641,7 +646,12 @@ void bindCompiledArgs(Evaluator& ev, const CompiledChunk& chunk,
641
646
  auto& a = static_cast<const oscad::PositionalArgument&>(*argPtr);
642
647
  Value v = ev.evalExpr(*a.expr, callerCtx);
643
648
  if (positionalIdx < nparams) {
644
- slots[static_cast<size_t>(chunk.params[positionalIdx].slot)] = std::move(v);
649
+ const CompiledChunk::Param& p = chunk.params[positionalIdx];
650
+ if (p.isDyn) {
651
+ childCtx.dyn->set(p.name, std::move(v));
652
+ } else {
653
+ slots[static_cast<size_t>(p.slot)] = std::move(v);
654
+ }
645
655
  bound[positionalIdx] = true;
646
656
  }
647
657
  ++positionalIdx;
@@ -649,6 +659,28 @@ void bindCompiledArgs(Evaluator& ev, const CompiledChunk& chunk,
649
659
  }
650
660
  }
651
661
 
662
+ // Shared by runCompiledFunction/runCompiledFunctionFromBound: applies each
663
+ // unbound parameter's default (or, for a $-parameter with none, an explicit
664
+ // undef -- mirrors Evaluator::applyDefaults' own "declaring a parameter
665
+ // always creates a fresh binding" rule, which for a slot-addressed plain
666
+ // parameter is already true for free via frame.slots' own zero-initialized
667
+ // Value{} fill, but for ctx.dyn needs the same explicit write applyDefaults
668
+ // makes, or a stale ancestor-level $-var would incorrectly show through).
669
+ void applyCompiledDefaults(Evaluator& ev, const CompiledChunk& chunk, VmFrame& frame, EvalContext& childCtx) {
670
+ for (size_t i = 0; i < chunk.params.size(); ++i) {
671
+ if (frame.bound[i]) continue;
672
+ const CompiledChunk::Param& p = chunk.params[i];
673
+ if (p.isDyn) {
674
+ childCtx.dyn->set(p.name, chunk.defaultCode[i].empty()
675
+ ? Value{}
676
+ : runChunk(ev, chunk, chunk.defaultCode[i], frame.slots, childCtx, frame.stack));
677
+ } else if (!chunk.defaultCode[i].empty()) {
678
+ frame.slots[static_cast<size_t>(p.slot)] =
679
+ runChunk(ev, chunk, chunk.defaultCode[i], frame.slots, childCtx, frame.stack);
680
+ }
681
+ }
682
+ }
683
+
652
684
  } // namespace
653
685
 
654
686
  Value runCompiledFunction(Evaluator& ev, const CompiledChunk& chunk,
@@ -667,12 +699,7 @@ Value runCompiledFunction(Evaluator& ev, const CompiledChunk& chunk,
667
699
  // started running, never during argument binding itself.
668
700
  ev.setCurrentCallVmFrame(&frame);
669
701
  bindCompiledArgs(ev, chunk, arguments, callerCtx, childCtx, frame.slots, frame.bound);
670
- for (size_t i = 0; i < chunk.params.size(); ++i) {
671
- if (!frame.bound[i] && !chunk.defaultCode[i].empty()) {
672
- frame.slots[static_cast<size_t>(chunk.params[i].slot)] =
673
- runChunk(ev, chunk, chunk.defaultCode[i], frame.slots, childCtx, frame.stack);
674
- }
675
- }
702
+ applyCompiledDefaults(ev, chunk, frame, childCtx);
676
703
  return runChunk(ev, chunk, chunk.bodyCode, frame.slots, childCtx, frame.stack, tailOut);
677
704
  }
678
705
 
@@ -684,8 +711,13 @@ Value runCompiledFunctionFromBound(Evaluator& ev, const CompiledChunk& chunk, co
684
711
  frame.bound.assign(chunk.params.size(), false);
685
712
  ev.setCurrentCallVmFrame(&frame);
686
713
  for (size_t i = 0; i < chunk.params.size(); ++i) {
687
- if (const Value* v = bound.find(chunk.params[i].name)) {
688
- frame.slots[static_cast<size_t>(chunk.params[i].slot)] = *v;
714
+ const CompiledChunk::Param& p = chunk.params[i];
715
+ if (const Value* v = bound.find(p.name)) {
716
+ if (p.isDyn) {
717
+ childCtx.dyn->set(p.name, *v);
718
+ } else {
719
+ frame.slots[static_cast<size_t>(p.slot)] = *v;
720
+ }
689
721
  frame.bound[i] = true;
690
722
  }
691
723
  }
@@ -699,12 +731,7 @@ Value runCompiledFunctionFromBound(Evaluator& ev, const CompiledChunk& chunk, co
699
731
  }
700
732
  if (!matched && !k.empty() && k[0] == '$') childCtx.dyn->set(k, v);
701
733
  }
702
- for (size_t i = 0; i < chunk.params.size(); ++i) {
703
- if (!frame.bound[i] && !chunk.defaultCode[i].empty()) {
704
- frame.slots[static_cast<size_t>(chunk.params[i].slot)] =
705
- runChunk(ev, chunk, chunk.defaultCode[i], frame.slots, childCtx, frame.stack);
706
- }
707
- }
734
+ applyCompiledDefaults(ev, chunk, frame, childCtx);
708
735
  return runChunk(ev, chunk, chunk.bodyCode, frame.slots, childCtx, frame.stack, tailOut);
709
736
  }
710
737
 
@@ -175,10 +175,34 @@ TEST(BytecodeCompiler, NonCompilableFunctionContainingACallStillFallsBackCorrect
175
175
  "ECHO: 8");
176
176
  }
177
177
 
178
- TEST(BytecodeCompiler, DollarPrefixedParameterBailsCompilationAndStillWorks) {
179
- ScopedVm vm(true);
178
+ TEST(BytecodeCompiler, DollarPrefixedParameterCompiles) {
179
+ ScopedVm vm(true);
180
+ // $-prefixed parameters used to bail compileFunctionLike outright
181
+ // (bound through ctx.dyn, never slot-addressed) -- now they compile:
182
+ // never declared as a local (skipped in the params/declareLocal loop),
183
+ // so every reference inside the body still reaches Op::LoadDyn via the
184
+ // Identifier case's own $-check, exactly as an undeclared override
185
+ // already did.
180
186
  EXPECT_EQ(runCapturingEcho("function withFn($fn = 8) = $fn;\necho(withFn());"), "ECHO: 8");
181
187
  EXPECT_EQ(runCapturingEcho("function withFn($fn = 8) = $fn;\necho(withFn($fn = 20));"), "ECHO: 20");
188
+ // Positional binding: a $-param still occupies its declared POSITION
189
+ // among all parameters for a caller's positional arguments, exactly
190
+ // like the interpreter's own bindArgs.
191
+ EXPECT_EQ(runCapturingEcho("function withFn($fn, x) = $fn + x;\necho(withFn(10, 1));"), "ECHO: 11");
192
+ // Declared without a default and not passed: shadows to undef locally
193
+ // rather than inheriting an ancestor's dyn binding -- mirrors
194
+ // Evaluator::applyDefaults' explicit "declaring a parameter always
195
+ // creates a fresh binding" write.
196
+ EXPECT_EQ(runCapturingEcho("function withFn($fn) = $fn;\necho(let($fn = 99) withFn());"), "ECHO: undef");
197
+ // The proof this is actually running compiled, not silently falling
198
+ // back: same ternary-bodied shape and technique as
199
+ // FastContinueWithNoBreakpointInFunctionUsesVm above (3 stops =
200
+ // compiled, 5 = interpreted), with $fn substituted in for the plain
201
+ // parameter both as the declared name and every body reference.
202
+ const int stops = countDebugHookStops("function f($fn) = $fn > 0 ? $fn + 1 : $fn - 1;\n"
203
+ "echo(f(5));",
204
+ std::unordered_map<std::string, std::set<int>>{{"<string>", {2}}});
205
+ EXPECT_EQ(stops, 3);
182
206
  }
183
207
 
184
208
  TEST(BytecodeCompiler, UndeclaredDollarNamedArgumentReachesDynInsideCompiledFunction) {
@@ -735,27 +759,26 @@ TEST(BytecodeCompiler, TernaryWrappedMutualRecursionResolvesCorrectly) {
735
759
  EXPECT_EQ(runCapturingEcho(script), "ECHO: true\nECHO: false");
736
760
  }
737
761
 
738
- TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
739
- ScopedVm vm(true);
740
- // Residual, deliberate limitation: a FunctionLiteral with its OWN
741
- // dollar-prefixed parameter still fails compileFunctionLike outright
742
- // (same $-prefixed-parameter rule as a plain named function, see
743
- // DollarPrefixedParameterBailsCompilationAndStillWorks above) -- there's
744
- // no lighter way left to discover what it captures in that case, so the
745
- // WHOLE containing declaration still bails (throw NotCompilable), same
746
- // as before Op::MakeClosure. Value must still be correct via the
747
- // interpreter fallback.
748
- const int stops = countDebugHookStops(
749
- "function outer(x) = let(g = function(y, $fn) y + x + $fn) g(5, $fn=2);\n"
750
- "echo(outer(10));",
751
- std::nullopt);
752
- // `outer` never compiles at all here (more than 1 stop for its own
753
- // call), unlike MakeClosureLetsContainerCompileDespiteNonEscapingClosure
754
- // above where the analogous ($-free) container compiles to 1.
755
- EXPECT_GT(stops, 2);
756
- EXPECT_EQ(runCapturingEcho("function outer(x) = let(g = function(y, $fn) y + x + $fn) g(5, $fn=2);\n"
757
- "echo(outer(10));"),
758
- "ECHO: 17");
762
+ TEST(BytecodeCompiler, ClosureWithDollarParameterNowCompilesToo) {
763
+ ScopedVm vm(true);
764
+ // A FunctionLiteral with its OWN dollar-prefixed parameter used to fail
765
+ // compileFunctionLike outright, bailing the WHOLE containing
766
+ // declaration (see DollarPrefixedParameterCompiles above for the plain-
767
+ // function case) -- now compileFunctionLike has no $-specific bail left
768
+ // at all, so this compiles too, container and closure both.
769
+ const std::string script = "function outer(x) = let(g = function(y, $fn) y + x + $fn) g(5, $fn=2);\n"
770
+ "echo(outer(10));";
771
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 17");
772
+ // Proof via the same fast-continue stop-count technique used
773
+ // throughout this file (nullopt is NOT a reliable signal here -- it
774
+ // forces every function to interpret regardless of eligibility, see
775
+ // DebugAttachedWithoutFastContinueAlwaysInterprets above -- a real
776
+ // breakpoint map on a non-matching line is what actually distinguishes
777
+ // compiled from interpreted). 3 stops: outer's own body-entry, g's
778
+ // call-site, g's own body-entry -- no ternary/sub-expression stops for
779
+ // either, since both now run compiled.
780
+ const int stops = countDebugHookStops(script, std::unordered_map<std::string, std::set<int>>{{"<string>", {2}}});
781
+ EXPECT_EQ(stops, 3);
759
782
  }
760
783
 
761
784
  // -- Tail-call optimization, VM path (Phase B) -----------------------------
@@ -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"
@@ -114,6 +119,21 @@ TEST(TailCalls, DollarVarSetEarlyInATailChainStaysVisibleManyHopsLater) {
114
119
  EXPECT_EQ(runCapturingEcho(script), "ECHO: 77");
115
120
  }
116
121
 
122
+ TEST(TailCalls, DollarParameterThreadsThroughCompiledTailRecursion) {
123
+ ScopedVm vm(true);
124
+ // $fn declared as its own PARAMETER (not just read as an ambient dyn
125
+ // var, unlike the test above) rebinds via childCtx.dyn at every tail
126
+ // hop -- compileFunctionLike no longer bails on a $-prefixed parameter
127
+ // (see its own doc comment, bytecode_compiler.cpp). No tail-call-
128
+ // specific hazard: every hop's bindCompiledArgs call is identical to
129
+ // what a genuine non-tail call already does, and dyn already threads
130
+ // correctly across hops (proved by the test above).
131
+ const std::string script =
132
+ "function sum_with_fn($fn, n, acc=0) = n == 0 ? $fn + acc : sum_with_fn($fn, n - 1, acc + n);\n"
133
+ "echo(sum_with_fn(1000, 50000));";
134
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 1.25003e+9");
135
+ }
136
+
117
137
  TEST(TailCalls, NonTailRecursionIsUnaffectedByTheTrampoline) {
118
138
  ScopedVm vm(false);
119
139
  // `n * fact(n-1)` is NOT a tail position -- the multiply happens after
@@ -267,3 +287,81 @@ TEST(TailCalls, DebugHookFiresPerHopInsideATailChain) {
267
287
  // single body-entry stop.
268
288
  EXPECT_EQ(bodyEntry, 501 + 500 + 1);
269
289
  }
290
+
291
+ TEST(TailCalls, DeepNonTailRecursionHitsAControlledErrorInsteadOfCrashingInterpreted) {
292
+ ScopedVm vm(false);
293
+ // `1 + f(n-1)` is NOT a tail position (the addition happens after the
294
+ // recursive call returns) -- there is no trampoline for this, ever
295
+ // (see NonTailRecursionIsUnaffectedByTheTrampoline, above): every
296
+ // logical call is a real, unavoidable C++ recursive call through
297
+ // evalUserFunctionCore. Before its own depth guard existed, a script
298
+ // shaped exactly like this one reliably segfaulted the whole process
299
+ // (confirmed present in the wild -- a plain, closure-free
300
+ // FunctionDeclaration, nothing specific to closures/letrec/tail-call
301
+ // machinery at all). Interpreted-path variant; see the compiled-path
302
+ // one below. f(500) is comfortably past kMaxUserCallDepth (50, see its
303
+ // own doc comment, evaluator.hpp) -- the actual native recursion never
304
+ // gets anywhere near 500, since the guard fires at 50 regardless of
305
+ // what depth the script asks for.
306
+ Evaluator ev;
307
+ auto ast = parseSrc("function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\nresult = f(500);");
308
+ auto scope = oscad::buildScopes(ast);
309
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
310
+ try {
311
+ ev.resolveTree(ast, ctx);
312
+ FAIL() << "expected EvalError";
313
+ } catch (const EvalError& e) {
314
+ EXPECT_NE(std::string(e.what()).find("Recursion too deep"), std::string::npos);
315
+ EXPECT_NE(std::string(e.what()).find("'f'"), std::string::npos);
316
+ }
317
+ }
318
+
319
+ TEST(TailCalls, DeepNonTailRecursionHitsAControlledErrorInsteadOfCrashingCompiled) {
320
+ ScopedVm vm(true);
321
+ // Same script, same guard (evalUserFunctionCore is shared by both the
322
+ // interpreter and the bytecode VM's own compiled call path) -- the
323
+ // compiled path's own real per-call native stack usage is actually
324
+ // larger (more C++ frames between one logical call and the next, see
325
+ // runChunk/runCompiledFunctionFromBound/runCompiledFunctionFromBoundTrampoline),
326
+ // so this is the MORE exposed of the two paths, not a lesser check.
327
+ Evaluator ev;
328
+ auto ast = parseSrc("function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\nresult = f(500);");
329
+ auto scope = oscad::buildScopes(ast);
330
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
331
+ EXPECT_THROW(ev.resolveTree(ast, ctx), EvalError);
332
+ }
333
+
334
+ TEST(TailCalls, CollatzStepsRecursesFromBothTernaryBranchesInterpreted) {
335
+ ScopedVm vm(false);
336
+ // Real-world example exercising tail calls from BOTH ternary branches at
337
+ // once (the even and odd cases each recurse) -- came up when auditing
338
+ // for un-trampolined tail positions (see this file's header). Scans
339
+ // every n < 10000 and takes the longest chain; the known record holder
340
+ // in that range is n=6171 at 261 steps, calling collatz_steps 10000
341
+ // times over.
342
+ const std::string script =
343
+ "function collatz_steps(n, steps=0) = n == 1 ? steps : "
344
+ "(n % 2 == 0 ? collatz_steps(n / 2, steps + 1) : collatz_steps(3 * n + 1, steps + 1));\n"
345
+ "lengths = [for (i = [1:10000]) collatz_steps(i)];\n"
346
+ "echo(max(lengths));";
347
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 261");
348
+ }
349
+
350
+ TEST(TailCalls, CollatzStepsRecursesFromBothTernaryBranchesCompiled) {
351
+ ScopedVm vm(true);
352
+ const std::string script =
353
+ "function collatz_steps(n, steps=0) = n == 1 ? steps : "
354
+ "(n % 2 == 0 ? collatz_steps(n / 2, steps + 1) : collatz_steps(3 * n + 1, steps + 1));\n"
355
+ "lengths = [for (i = [1:10000]) collatz_steps(i)];\n"
356
+ "echo(max(lengths));";
357
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 261");
358
+ }
359
+
360
+ TEST(TailCalls, ShallowNonTailRecursionStillWorksUnderTheDepthGuard) {
361
+ ScopedVm vm(true);
362
+ // The guard must not fire for perfectly ordinary, shallow non-tail
363
+ // recursion -- the overwhelmingly common real-world shape (a handful
364
+ // to a few dozen levels, e.g. walking a small nested data structure).
365
+ const std::string script = "function fact(n) = n <= 1 ? 1 : n * fact(n - 1);\necho(fact(10));";
366
+ EXPECT_EQ(runCapturingEcho(script), "ECHO: 3.6288e+6");
367
+ }