openscad-cpp-evaluator 1.29.0__tar.gz → 1.30.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 (225) hide show
  1. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/CLAUDE.md +15 -0
  2. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/bindings/module.cpp +22 -3
  4. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/evaluator.hpp +30 -0
  5. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/python/openscad_cpp_evaluator/__init__.py +9 -4
  7. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/bytecode_vm.cpp +1 -0
  8. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/csg_generate.cpp +1 -0
  9. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/debug_profile.cpp +1 -0
  10. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/user_calls.cpp +3 -0
  11. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_control_flow.cpp +99 -0
  12. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_csg_tree.cpp +4 -3
  13. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_python_bindings.py +22 -0
  14. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/uv.lock +1 -1
  15. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/.github/workflows/ci.yml +0 -0
  16. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/.github/workflows/wheels.yml +0 -0
  17. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/.gitignore +0 -0
  18. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/.gitmodules +0 -0
  19. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/LICENSE +0 -0
  21. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/README.md +0 -0
  22. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/bindings/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/bindings/ast_to_py.cpp +0 -0
  24. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/cmake/embed_font.cmake +0 -0
  25. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/examples/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/examples/eval_perf_benchmark.cpp +0 -0
  27. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/examples/manifold_cache_reuse.cpp +0 -0
  28. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/examples/minimal_debugger.cpp +0 -0
  29. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  30. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/.gitignore +0 -0
  31. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  32. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/README.md +0 -0
  34. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  35. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  36. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  38. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  39. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  40. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  41. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  42. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  43. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  45. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  46. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  47. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  48. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  49. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  50. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  51. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  53. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  54. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  55. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  56. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  57. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  58. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  59. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  60. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  61. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  62. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  63. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  64. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  68. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  69. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  70. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  71. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  72. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  73. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  74. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  75. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  76. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  77. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  78. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  79. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  80. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  81. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  82. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  83. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  84. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  85. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  86. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  87. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  91. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  92. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  93. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  94. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  95. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  96. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  97. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  98. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  99. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  100. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  102. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  103. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  104. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  105. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  106. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  107. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  108. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  109. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  110. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  111. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  112. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  113. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  114. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  115. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  116. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  117. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  118. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  120. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  122. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  123. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  124. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  125. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  126. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  127. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  128. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  129. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  130. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  131. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  132. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  133. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  134. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  135. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  137. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  138. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  139. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  140. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/CMakeLists.txt +0 -0
  141. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/booleans.cpp +0 -0
  142. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/builtins.hpp +0 -0
  143. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/call_args.cpp +0 -0
  144. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/color.cpp +0 -0
  145. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/control.cpp +0 -0
  146. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/dxf_dim.cpp +0 -0
  147. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/extrude.cpp +0 -0
  148. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/function_builtins.cpp +0 -0
  149. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/import.cpp +0 -0
  150. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/modifiers.cpp +0 -0
  151. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/primitives_2d.cpp +0 -0
  152. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/primitives_3d.cpp +0 -0
  153. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/registry.cpp +0 -0
  154. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/roof.cpp +0 -0
  155. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/segments.cpp +0 -0
  156. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/surface.cpp +0 -0
  157. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/text.cpp +0 -0
  158. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/topology.cpp +0 -0
  159. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/builtins/transforms.cpp +0 -0
  160. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/bytecode_compiler.cpp +0 -0
  161. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/colored_body.cpp +0 -0
  162. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/coverage.cpp +0 -0
  163. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/csg_resolve.cpp +0 -0
  164. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/css_colors.cpp +0 -0
  165. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/debug/debug_repl.cpp +0 -0
  166. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/eval_context.cpp +0 -0
  167. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/eval_error.cpp +0 -0
  168. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/eval_use.cpp +0 -0
  169. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/export.cpp +0 -0
  170. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/expr_eval.cpp +0 -0
  171. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/format_closure.cpp +0 -0
  172. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/import/dxf_import.cpp +0 -0
  173. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/import/mesh_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/import/surface_load.cpp +0 -0
  175. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/import/svg_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/measure_geometry.cpp +0 -0
  178. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/mesh_check.cpp +0 -0
  179. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/stmt_eval.cpp +0 -0
  180. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/text/font_match.cpp +0 -0
  181. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/text/freetype_font_provider.cpp +0 -0
  182. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/text/text_metrics.cpp +0 -0
  183. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/utf8.cpp +0 -0
  184. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/value.cpp +0 -0
  185. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/xkcd_colors.cpp +0 -0
  186. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/xkcd_colors.hpp +0 -0
  187. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/src/zip_stored.cpp +0 -0
  188. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/CMakeLists.txt +0 -0
  189. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_booleans.cpp +0 -0
  190. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_bytecode_compiler.cpp +0 -0
  191. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_children_separate.cpp +0 -0
  192. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_cli.cpp +0 -0
  193. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_coverage.cpp +0 -0
  194. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_debug_hooks.cpp +0 -0
  195. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_dxf_svg_import.cpp +0 -0
  196. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_eval_context.cpp +0 -0
  197. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_export.cpp +0 -0
  198. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_expr_eval.cpp +0 -0
  199. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_extrude_roof.cpp +0 -0
  200. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_feature_detection.cpp +0 -0
  201. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_function_builtins.cpp +0 -0
  202. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_helpers.hpp +0 -0
  203. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_import_export.cpp +0 -0
  204. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_include_cache.cpp +0 -0
  205. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_manifold_cache.cpp +0 -0
  206. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_mesh_check.cpp +0 -0
  207. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_multi_color_merge.cpp +0 -0
  208. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_primitives.cpp +0 -0
  209. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_profiling.cpp +0 -0
  210. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_render_expr.cpp +0 -0
  211. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_scope_trail.cpp +0 -0
  212. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_scoping.cpp +0 -0
  213. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_surface.cpp +0 -0
  214. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_tail_calls.cpp +0 -0
  215. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_text.cpp +0 -0
  216. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_transforms.cpp +0 -0
  217. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_unexpected_args.cpp +0 -0
  218. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_use.cpp +0 -0
  219. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_value.cpp +0 -0
  220. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_viewport_params.cpp +0 -0
  221. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tests/test_zip_stored.cpp +0 -0
  222. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tools/cli/CMakeLists.txt +0 -0
  223. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tools/cli/cli_lib.cpp +0 -0
  224. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tools/cli/cli_lib.hpp +0 -0
  225. {openscad_cpp_evaluator-1.29.0 → openscad_cpp_evaluator-1.30.0}/tools/cli/main.cpp +0 -0
@@ -1569,6 +1569,21 @@ than the current pause point). Ported identically (same qualifier syntax, same e
1569
1569
  - `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
1570
1570
  real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
1571
1571
 
1572
+ ## Cancel and the module recursion limit (#554)
1573
+
1574
+ - **`Evaluator::setCancelFlag(shared_ptr<atomic<bool>>)`** -- bound as `CancelSignal` /
1575
+ `Evaluator(cancel_signal=...)`. Once the flag is set, from any thread, evaluate() throws
1576
+ `EvalError("Render cancelled")` at its next check: the top of `checkDebug` (every interpreted
1577
+ statement and loop pass already calls it, hooks or not), `pollCancel()` every 1,024 VM
1578
+ instructions in `driveVm`, and each top-level node in `generateTreeImpl`. A single Manifold op
1579
+ already under way still finishes. Sticky, unlike `setFastContinueInterruptFlag`'s test-and-clear.
1580
+ A host's echo callback was the only way in before, so a script that printed nothing could not
1581
+ be stopped at all.
1582
+ - **`kMaxModuleCallDepth = 16384`** nested user module calls, checked in `enterUserCall` against
1583
+ `moduleCallDepth_` -- "Recursion detected calling module 'm'", OpenSCAD's wording. Functions
1584
+ keep `kMaxVmCallStackDepth` (1e6). `module m() { m(); }` went from 4.2 s / 3.3 GB to stopping
1585
+ at once; OpenSCAD 2026.02.01 stops the same script near 14,000.
1586
+
1572
1587
  ## Coverage
1573
1588
 
1574
1589
  `Evaluator(coverage=true)` records which parts of a run's source actually executed and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.29.0
3
+ Version: 1.30.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -503,10 +503,22 @@ nb::object coverageResultToPy(const std::optional<oscadeval::CoverageResult>& cr
503
503
  return result;
504
504
  }
505
505
 
506
+ // A render's Cancel: .request() from any thread makes the evaluate() it was
507
+ // passed to throw EvalError("Render cancelled") at its next check -- see
508
+ // Evaluator::setCancelFlag. One per render; it is never cleared.
509
+ class CancelSignal {
510
+ public:
511
+ void request() { flag_->store(true, std::memory_order_relaxed); }
512
+ const std::shared_ptr<std::atomic<bool>>& flag() const { return flag_; }
513
+
514
+ private:
515
+ std::shared_ptr<std::atomic<bool>> flag_ = std::make_shared<std::atomic<bool>>(false);
516
+ };
517
+
506
518
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
507
519
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
508
520
  bool generate, bool strictCommas, bool coverage, bool keepMinuendColor,
509
- double flatPreviewHeight) {
521
+ double flatPreviewHeight, CancelSignal* cancelSignal) {
510
522
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
511
523
 
512
524
  std::vector<oscadeval::ColoredBody> bodies;
@@ -535,6 +547,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
535
547
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
536
548
  oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile, coverage);
537
549
  ev.keepMinuendColor = keepMinuendColor;
550
+ if (cancelSignal) ev.setCancelFlag(cancelSignal->flag());
538
551
  ev.setUsedFileGlobals(used.usedFileGlobals);
539
552
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
540
553
  std::vector<oscadeval::ColoredBody> raw = ev.evaluate(used.processedNodes, ctx, vp, generate);
@@ -924,6 +937,10 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
924
937
 
925
938
  // See FastContinueSignal's own doc comment, above -- only a
926
939
  // constructor and request() are ever called from Python.
940
+ nb::class_<CancelSignal>(m, "CancelSignal")
941
+ .def(nb::init<>())
942
+ .def("request", &CancelSignal::request);
943
+
927
944
  nb::class_<FastContinueSignal>(m, "FastContinueSignal")
928
945
  .def(nb::init<>())
929
946
  .def("request", &FastContinueSignal::request);
@@ -993,7 +1010,7 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
993
1010
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
994
1011
  nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
995
1012
  nb::arg("coverage") = false, nb::arg("keep_minuend_color") = false,
996
- nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight,
1013
+ nb::arg("flat_preview_height") = oscadeval::kTopLevel2dHeight, nb::arg("cancel_signal") = nullptr,
997
1014
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit, "
998
1015
  "geometry, coverage_result).\n"
999
1016
  "coverage=True records which statements, branch arms and bodies ran: coverage_result is a dict "
@@ -1006,7 +1023,9 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
1006
1023
  "are populated.\n\n"
1007
1024
  "strict_commas=True makes a trailing comma in a call argument list or a let/for "
1008
1025
  "assignment list a syntax error, as OpenSCAD 2021.01 did. List literals and parameter "
1009
- "declarations keep theirs, which 2021.01 accepted too.");
1026
+ "declarations keep theirs, which 2021.01 accepted too.\n\n"
1027
+ "cancel_signal: a CancelSignal whose request(), from any thread, stops this call with "
1028
+ "EvalError('Render cancelled') -- even for a script that prints nothing.");
1010
1029
  m.def("parse_decls", &parseDecls, nb::arg("path"),
1011
1030
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
1012
1031
  m.def("strip_slivers", &stripSliversPy, nb::arg("verts"), nb::arg("tris"),
@@ -703,6 +703,23 @@ public:
703
703
  fastContinueInterrupt_ = std::move(flag);
704
704
  }
705
705
 
706
+ // A host's Cancel button. Set from any thread; the running evaluate()
707
+ // then throws EvalError("Render cancelled") at its next check: every
708
+ // interpreted statement and loop pass (checkDebug, which all of them
709
+ // already call), every 1024th VM instruction, and between top-level
710
+ // generate-pass nodes. A single Manifold operation already under way
711
+ // still runs to its end. Before this existed a host could only stop a
712
+ // script from its echo callback, so one that printed nothing ran to
713
+ // completion however long that took (#554). Sticky: never cleared here.
714
+ void setCancelFlag(std::shared_ptr<std::atomic<bool>> flag) { cancel_ = std::move(flag); }
715
+ void checkCancel() const {
716
+ if (cancel_ && cancel_->load(std::memory_order_relaxed)) throw EvalError("Render cancelled");
717
+ }
718
+ // The VM's per-instruction form: one relaxed load per 1024 instructions.
719
+ void pollCancel() {
720
+ if (cancel_ && (++cancelTicks_ & 0x3FF) == 0) checkCancel();
721
+ }
722
+
706
723
  // Checks whether a debug pause should happen at `node` (via the
707
724
  // injected DebugHooks::debugHook, if any -- a no-op otherwise),
708
725
  // applying any `mods` the hook returns to `ctx.let_` and throwing
@@ -1759,6 +1776,16 @@ public:
1759
1776
  // recalibrating for real.
1760
1777
  static constexpr size_t kMaxVmCallStackDepth = 1'000'000;
1761
1778
 
1779
+ // Nested user MODULE calls, of any modules. Functions keep
1780
+ // kMaxVmCallStackDepth: a deep function recursion builds nothing, but a
1781
+ // module frame holds its whole subtree, so `module m() { m(); }` used
1782
+ // to run to a million frames and 3.3 GB before stopping. OpenSCAD stops
1783
+ // the same script at ~14,000 ("Recursion detected calling module"),
1784
+ // 45 MB; this is that order, and far past any real library (BOSL2
1785
+ // nests tens deep). Checked in enterUserCall, which every module call
1786
+ // -- interpreted or compiled -- goes through (#554).
1787
+ static constexpr int kMaxModuleCallDepth = 16384;
1788
+
1762
1789
  // Counts native C++ nesting of driveVm itself -- NOT logical call
1763
1790
  // depth (that's vmCallStack_.size()/callStack_.size(), both of which
1764
1791
  // grow just as much for a pure VM-internal Op::CallModule hop, which
@@ -2074,6 +2101,9 @@ private:
2074
2101
  // default) means hook-skippable mode, if ever engaged, can't be
2075
2102
  // interrupted from outside a hook call.
2076
2103
  std::shared_ptr<std::atomic<bool>> fastContinueInterrupt_;
2104
+ // See setCancelFlag. nullptr (the default): nothing can cancel.
2105
+ std::shared_ptr<std::atomic<bool>> cancel_;
2106
+ unsigned cancelTicks_ = 0;
2077
2107
  // The root EvalContext resolveTree() was called with -- lets
2078
2108
  // checkDebug()'s locals snapshot fall back to top-level script
2079
2109
  // variables when paused inside a nested user call, the same way the
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.29.0"
7
+ version = "1.30.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -13,12 +13,12 @@ from dataclasses import dataclass, field
13
13
  from typing import Optional
14
14
 
15
15
  from . import _openscad_cpp_evaluator as _ext
16
- from ._openscad_cpp_evaluator import FastContinueSignal, ManifoldCache
16
+ from ._openscad_cpp_evaluator import CancelSignal, FastContinueSignal, ManifoldCache
17
17
 
18
18
  __all__ = [
19
19
  "Evaluator", "ColoredBody", "EvalError", "ParseError", "OscObject", "parse", "to_renderable_bodies",
20
20
  "ManifoldCache", "CallSiteProfile", "ProfileResult", "format_csg_tree", "bodies_from_dicts",
21
- "FastContinueSignal", "parse_ast", "parse_ast_string", "format_source",
21
+ "CancelSignal", "FastContinueSignal", "parse_ast", "parse_ast_string", "format_source",
22
22
  "check_mesh", "strip_slivers",
23
23
  "export_model", "export_extensions", "list_fonts",
24
24
  ]
@@ -565,7 +565,7 @@ class Evaluator:
565
565
 
566
566
  def __init__(self, echo_fn=None, debug_hook=None, error_break_fn=None, return_hook=None,
567
567
  manifold_cache=None, profile=False, fast_continue_signal=None, coverage=False,
568
- keep_minuend_color=False, flat_preview_height=1.0):
568
+ keep_minuend_color=False, flat_preview_height=1.0, cancel_signal=None):
569
569
  self._echo_fn = echo_fn
570
570
  self._debug_hook = debug_hook
571
571
  self._error_break_fn = error_break_fn
@@ -574,6 +574,11 @@ class Evaluator:
574
574
  self._profile = profile
575
575
  self._fast_continue_signal = fast_continue_signal
576
576
  self._coverage = coverage
577
+ # A CancelSignal: request() it from any thread and a running
578
+ # evaluate() raises EvalError("Render cancelled") -- the only way to
579
+ # stop a script that prints nothing. Plain path only; the debugger
580
+ # has its own stop.
581
+ self._cancel_signal = cancel_signal
577
582
  # keep_minuend_color=True: difference() paints the faces a
578
583
  # subtrahend exposes with the MINUEND's colour rather than the
579
584
  # subtrahend's (or the cut green). A viewer option; see
@@ -640,7 +645,7 @@ class Evaluator:
640
645
  dyn_explicit, geometry, coverage_result) = _ext.evaluate(
641
646
  source_path, vp, self._manifold_cache, self._profile, generate,
642
647
  strict_commas, self._coverage, self._keep_minuend_color,
643
- self._flat_preview_height)
648
+ self._flat_preview_height, self._cancel_signal)
644
649
  # The evaluated bodies, still on the C++ side. Stashed like
645
650
  # csg_tree/profile_result rather than returned, so
646
651
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -609,6 +609,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
609
609
  continue;
610
610
  }
611
611
 
612
+ ev.pollCancel();
612
613
  const Instruction& ins = f.code[f.pc];
613
614
  EvalContext& ctx = f.ctxChain.back();
614
615
  switch (ins.op) {
@@ -35,6 +35,7 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
35
35
  const auto& dispatch = generateDispatch();
36
36
  for (CSGNode* nodePtr : tree) {
37
37
  CSGNode& node = *nodePtr;
38
+ checkCancel();
38
39
 
39
40
  // ManifoldCache lookup: node.uncacheable (rands() taint, set
40
41
  // during resolve -- see csg_resolve.cpp) always forces a miss,
@@ -104,6 +104,7 @@ void Evaluator::checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool fo
104
104
  // injecting stops the user never wrote and corrupting lastStmtByDepth_'s
105
105
  // duplicate-collapse state below. Rides the existing null-hook
106
106
  // short-circuit so it costs nothing on the normal path.
107
+ checkCancel(); // every interpreted statement and loop pass comes through here
107
108
  if (measuring_ || !debugHooks_.debugHook) return;
108
109
  const oscad::Position& pos = node.position();
109
110
  const size_t stmtDepth = callStack_.size();
@@ -733,6 +733,9 @@ Evaluator::UserCallHandle Evaluator::enterUserCall(const std::string& name, cons
733
733
  error("Recursion too deep while calling " + std::string(isModule ? "module" : "function") + " '" + name + "'",
734
734
  declNode);
735
735
  }
736
+ if (isModule && moduleCallDepth_ >= kMaxModuleCallDepth) {
737
+ error("Recursion detected calling module '" + name + "'", declNode);
738
+ }
736
739
  UserCallHandle h;
737
740
  h.kind = kind;
738
741
  h.declNode = &declNode;
@@ -3,6 +3,10 @@
3
3
  #include "test_helpers.hpp"
4
4
 
5
5
  #include <algorithm>
6
+ #include <atomic>
7
+ #include <chrono>
8
+ #include <memory>
9
+ #include <thread>
6
10
  #include <gtest/gtest.h>
7
11
 
8
12
  using namespace oscadeval;
@@ -1204,6 +1208,101 @@ TEST(UserModule, DeepNonTailRecursionHitsAControlledErrorInterpreted) {
1204
1208
  }
1205
1209
  }
1206
1210
 
1211
+ // #554: a module recursing without end used to run to kMaxVmCallStackDepth
1212
+ // (a million frames, 3.3 GB); OpenSCAD stops it near 14,000.
1213
+ TEST(UserModule, EndlessModuleRecursionStopsAtTheModuleDepthLimit) {
1214
+ ScopedVm vm(true);
1215
+ try {
1216
+ evalSrc("module m() { m(); }\nm();");
1217
+ FAIL() << "expected EvalError";
1218
+ } catch (const EvalError& e) {
1219
+ EXPECT_NE(std::string(e.what()).find("Recursion detected calling module 'm'"), std::string::npos);
1220
+ }
1221
+ }
1222
+
1223
+ TEST(UserModule, ModuleRecursionJustUnderTheLimitSucceeds) {
1224
+ ScopedVm vm(true);
1225
+ Evaluated e = evalSrc("module recur(n) { if (n>0) recur(n-1); else cube(1); }\n"
1226
+ "recur(" + std::to_string(Evaluator::kMaxModuleCallDepth - 2) + ");");
1227
+ EXPECT_EQ(e.bodies.size(), 1u);
1228
+ }
1229
+
1230
+ // -- Evaluator::setCancelFlag (#554) ---------------------------------------
1231
+
1232
+ namespace {
1233
+ std::string cancelledMessage(const std::string& src, std::shared_ptr<std::atomic<bool>> flag) {
1234
+ auto ast = parseSrc(src);
1235
+ auto scope = oscad::buildScopes(ast);
1236
+ Evaluator ev;
1237
+ ev.setCancelFlag(std::move(flag));
1238
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
1239
+ try {
1240
+ ev.evaluate(ast, ctx);
1241
+ } catch (const EvalError& e) {
1242
+ return e.what();
1243
+ }
1244
+ return "";
1245
+ }
1246
+ } // namespace
1247
+
1248
+ TEST(Cancel, AnArmedFlagStopsASilentScriptCompiledAndInterpreted) {
1249
+ // Prints nothing, so a host's echo callback never gets a chance to stop it.
1250
+ const std::string src = "function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\n"
1251
+ "x = [for (i = [0:200]) f(20)];\ncube(1);";
1252
+ for (bool useVm : {true, false}) {
1253
+ ScopedVm vm(useVm);
1254
+ EXPECT_EQ(cancelledMessage(src, std::make_shared<std::atomic<bool>>(true)), "Render cancelled") << useVm;
1255
+ EXPECT_EQ(cancelledMessage(src, std::make_shared<std::atomic<bool>>(false)), "") << useVm;
1256
+ }
1257
+ }
1258
+
1259
+ TEST(Cancel, StopsTheGeneratePass) {
1260
+ auto ast = parseSrc("cube(1);\nsphere(1);");
1261
+ auto scope = oscad::buildScopes(ast);
1262
+ Evaluator ev;
1263
+ auto flag = std::make_shared<std::atomic<bool>>(false);
1264
+ ev.setCancelFlag(flag);
1265
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
1266
+ auto tree = ev.resolveTree(ast, ctx);
1267
+ flag->store(true);
1268
+ EXPECT_THROW(ev.generateTree(tree), EvalError);
1269
+ }
1270
+
1271
+ TEST(Cancel, AnotherThreadStopsARunningExpression) {
1272
+ ScopedVm vm(true);
1273
+ // ~minutes of silent work uncancelled; must stop within moments of the request.
1274
+ auto flag = std::make_shared<std::atomic<bool>>(false);
1275
+ std::thread canceller([flag] {
1276
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
1277
+ flag->store(true);
1278
+ });
1279
+ const auto t0 = std::chrono::steady_clock::now();
1280
+ const std::string msg = cancelledMessage("function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\n"
1281
+ "x = [for (i = [0:99999], j = [0:9999]) f(20)];", flag);
1282
+ const auto elapsed = std::chrono::steady_clock::now() - t0;
1283
+ canceller.join();
1284
+ EXPECT_EQ(msg, "Render cancelled");
1285
+ EXPECT_LT(elapsed, std::chrono::seconds(2));
1286
+ }
1287
+
1288
+ TEST(Cancel, AnotherThreadStopsARunningScriptCompiledAndInterpreted) {
1289
+ for (bool useVm : {true, false}) {
1290
+ ScopedVm vm(useVm);
1291
+ auto flag = std::make_shared<std::atomic<bool>>(false);
1292
+ std::thread canceller([flag] {
1293
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
1294
+ flag->store(true);
1295
+ });
1296
+ const auto t0 = std::chrono::steady_clock::now();
1297
+ const std::string msg = cancelledMessage("module m(n) { if (n > 0) m(n - 1); }\n"
1298
+ "for (i = [0:99999], j = [0:9999]) m(20);", flag);
1299
+ const auto elapsed = std::chrono::steady_clock::now() - t0;
1300
+ canceller.join();
1301
+ EXPECT_EQ(msg, "Render cancelled") << useVm;
1302
+ EXPECT_LT(elapsed, std::chrono::seconds(2)) << useVm;
1303
+ }
1304
+ }
1305
+
1207
1306
  TEST(UserModule, DefaultParameterApplied) {
1208
1307
  Evaluated e = evalSrc("module box(size=5) { cube(size); }\nbox();");
1209
1308
  manifold::Box bbox = e.bodies[0].body->BoundingBox();
@@ -189,12 +189,13 @@ TEST(CsgTree, LinearExtrudeNestsItsChildUnderneath) {
189
189
  // caught DURING resolve, at tree-construction time, not by a later walk.
190
190
 
191
191
  TEST(CsgTree, BareModuleRecursionStaysTreeDepthOneRegardlessOfCallDepth) {
192
- ScopedVm vm(true); // 20,000-deep recursion needs the compiled path
193
- // Splicing collapses every level away -- 20,000 calls deep, but the
192
+ ScopedVm vm(true); // 16,000-deep recursion needs the compiled path
193
+ // Splicing collapses every level away -- 16,000 calls deep (just under
194
+ // kMaxModuleCallDepth), but the
194
195
  // FINAL tree is still just the one leaf cube. Proves the new guard
195
196
  // (kMaxCsgTreeDepth = 2000) doesn't false-positive on the shape Stage
196
197
  // 2's own deep-recursion tests already rely on.
197
- Evaluated e = evalSrc("module recur(n) { if (n>0) recur(n-1); else cube(1); }\nrecur(20000);");
198
+ Evaluated e = evalSrc("module recur(n) { if (n>0) recur(n-1); else cube(1); }\nrecur(16000);");
198
199
  ASSERT_EQ(e.tree.size(), 1u);
199
200
  EXPECT_EQ(e.tree[0]->kind, "cube");
200
201
  EXPECT_EQ(e.tree[0]->treeDepth, 1);
@@ -1155,3 +1155,25 @@ print("errors:", errors); sys.exit(1 if errors else 0)
1155
1155
  src.write_text("".join(f"x{i} = [{i}, {i} + 1, \"s{i}\"];\n" for i in range(200)))
1156
1156
  r = subprocess.run([sys.executable, "-c", code, str(src)], capture_output=True, text=True, timeout=300)
1157
1157
  assert r.returncode == 0, (r.returncode, r.stdout[-500:], r.stderr[-1500:])
1158
+
1159
+
1160
+ def test_a_cancel_signal_stops_a_silent_script_from_another_thread(tmp_path):
1161
+ """#554: a script that prints nothing never reaches a host's echo
1162
+ callback, the only place Cancel could act before. CancelSignal.request()
1163
+ reaches the evaluator directly; evaluate() releases the GIL, so the
1164
+ requesting thread gets to run while it works."""
1165
+ import threading
1166
+ import time
1167
+ from openscad_cpp_evaluator import CancelSignal, EvalError
1168
+ src = tmp_path / "silent.scad"
1169
+ src.write_text("function f(n) = n <= 0 ? 0 : 1 + f(n - 1);\n"
1170
+ "x = [for (i = [0:99999], j = [0:9999]) f(20)];\n")
1171
+ signal = CancelSignal()
1172
+ threading.Timer(0.1, signal.request).start()
1173
+ t0 = time.perf_counter()
1174
+ try:
1175
+ Evaluator(cancel_signal=signal).evaluate(str(src))
1176
+ raise AssertionError("expected EvalError")
1177
+ except EvalError as e:
1178
+ assert "Render cancelled" in str(e), e
1179
+ assert time.perf_counter() - t0 < 5
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.29.0"
58
+ version = "1.30.0"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },