openscad-cpp-evaluator 0.6.1__tar.gz → 0.8.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.6.1 → openscad_cpp_evaluator-0.8.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/bindings/module.cpp +47 -11
  3. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bytecode.hpp +26 -17
  4. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/evaluator.hpp +65 -5
  5. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/python/openscad_cpp_evaluator/__init__.py +15 -3
  7. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/bytecode_compiler.cpp +69 -24
  8. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/bytecode_vm.cpp +41 -15
  9. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/debug_profile.cpp +30 -0
  10. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/user_calls.cpp +14 -1
  11. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_bytecode_compiler.cpp +63 -1
  12. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_debug_hooks.cpp +120 -0
  13. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/.github/workflows/ci.yml +0 -0
  14. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/.github/workflows/wheels.yml +0 -0
  15. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/.gitignore +0 -0
  16. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/.gitmodules +0 -0
  17. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/CLAUDE.md +0 -0
  18. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/LICENSE +0 -0
  20. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/README.md +0 -0
  21. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/bindings/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/cmake/embed_font.cmake +0 -0
  23. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/examples/CMakeLists.txt +0 -0
  24. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/examples/eval_perf_benchmark.cpp +0 -0
  25. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/examples/manifold_cache_reuse.cpp +0 -0
  26. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/examples/minimal_debugger.cpp +0 -0
  27. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  28. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/.gitignore +0 -0
  29. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  30. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/README.md +0 -0
  32. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  33. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  46. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  47. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  48. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  49. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  50. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  51. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  52. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  53. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  54. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  55. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  56. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  57. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  58. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  59. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  60. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  61. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  62. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  63. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  64. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  65. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  66. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  67. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  68. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  69. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  70. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  71. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  72. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  73. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  74. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  75. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  76. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  77. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  78. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  79. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  80. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  81. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  82. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  83. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  84. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  85. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  86. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  87. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  88. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  89. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  90. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  91. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  92. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  93. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  94. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  95. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  96. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  97. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  98. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  99. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  100. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  101. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  102. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  103. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  104. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  105. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  106. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  107. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  108. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  109. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  110. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  111. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  112. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  113. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  114. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  115. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  116. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  117. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  119. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  120. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  121. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  122. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/resources/fonts/LICENSE +0 -0
  123. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  124. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/CMakeLists.txt +0 -0
  125. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/booleans.cpp +0 -0
  126. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/builtins.hpp +0 -0
  127. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/call_args.cpp +0 -0
  128. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/color.cpp +0 -0
  129. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/control.cpp +0 -0
  130. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/extrude.cpp +0 -0
  131. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/function_builtins.cpp +0 -0
  132. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/import.cpp +0 -0
  133. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/modifiers.cpp +0 -0
  134. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/primitives_2d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/primitives_3d.cpp +0 -0
  136. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/registry.cpp +0 -0
  137. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/roof.cpp +0 -0
  138. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/segments.cpp +0 -0
  139. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/surface.cpp +0 -0
  140. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/text.cpp +0 -0
  141. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/topology.cpp +0 -0
  142. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/builtins/transforms.cpp +0 -0
  143. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/colored_body.cpp +0 -0
  144. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/csg_generate.cpp +0 -0
  145. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/csg_resolve.cpp +0 -0
  146. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/css_colors.cpp +0 -0
  147. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/debug/debug_repl.cpp +0 -0
  148. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/eval_context.cpp +0 -0
  149. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/eval_error.cpp +0 -0
  150. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/eval_use.cpp +0 -0
  151. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/export.cpp +0 -0
  152. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/expr_eval.cpp +0 -0
  153. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/import/dxf_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/import/mesh_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/import/surface_load.cpp +0 -0
  156. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/import/svg_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/manifold_cache.cpp +0 -0
  158. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/stmt_eval.cpp +0 -0
  159. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/text/stb_font_provider.cpp +0 -0
  160. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/text/text_metrics.cpp +0 -0
  161. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_control_flow.cpp +0 -0
  167. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_csg_tree.cpp +0 -0
  168. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_dxf_svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_export.cpp +0 -0
  171. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_expr_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_extrude_roof.cpp +0 -0
  173. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_function_builtins.cpp +0 -0
  174. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_helpers.hpp +0 -0
  175. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_import_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_manifold_cache.cpp +0 -0
  177. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_multi_color_merge.cpp +0 -0
  178. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_primitives.cpp +0 -0
  179. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_profiling.cpp +0 -0
  180. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_python_bindings.py +0 -0
  181. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.6.1 → openscad_cpp_evaluator-0.8.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.6.1
3
+ Version: 0.8.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -27,6 +27,7 @@
27
27
 
28
28
  #include "openscad_cpp_parser/api.hpp"
29
29
 
30
+ #include <atomic>
30
31
  #include <cstdint>
31
32
  #include <exception>
32
33
  #include <memory>
@@ -362,17 +363,20 @@ using GetChildrenPositionsFn = std::function<const std::optional<std::vector<std
362
363
  // exactly where it already computes should_pause, without needing to
363
364
  // widen hook()'s existing (cmd, mods) return-tuple contract -- mirrors
364
365
  // generate_partial/get_children_positions's own "extra kwarg the Python
365
- // side may or may not call" shape.
366
- using SetFastContinueFn = std::function<void(std::optional<std::unordered_map<std::string, std::set<int>>>)>;
366
+ // side may or may not call" shape. `hookSkippable` mirrors
367
+ // setFastContinueBreakpoints's own second parameter exactly -- see its
368
+ // doc comment for why this is narrower than "breakpoints is accurate."
369
+ using SetFastContinueFn = std::function<void(std::optional<std::unordered_map<std::string, std::set<int>>>, bool)>;
367
370
 
368
371
  // Wraps a SetFastContinueFn as a Python callable taking either a
369
372
  // dict[str, set[int]]/dict[str, list[int]] (origin -> breakpoint lines) or
370
- // None. Mirrors generatePartialTrampoline's own shape.
373
+ // None, plus a hook_skippable bool (default False). Mirrors
374
+ // generatePartialTrampoline's own shape.
371
375
  nb::object setFastContinueTrampoline(const SetFastContinueFn& setFastContinue) {
372
376
  return nb::cpp_function(
373
- [&setFastContinue](nb::object breakpoints) {
377
+ [&setFastContinue](nb::object breakpoints, bool hookSkippable) {
374
378
  if (breakpoints.is_none()) {
375
- setFastContinue(std::nullopt);
379
+ setFastContinue(std::nullopt, hookSkippable);
376
380
  return;
377
381
  }
378
382
  std::unordered_map<std::string, std::set<int>> bp;
@@ -381,7 +385,7 @@ nb::object setFastContinueTrampoline(const SetFastContinueFn& setFastContinue) {
381
385
  for (nb::handle line : v) lines.insert(nb::cast<int>(line));
382
386
  bp.emplace(nb::cast<std::string>(k), std::move(lines));
383
387
  }
384
- setFastContinue(std::move(bp));
388
+ setFastContinue(std::move(bp), hookSkippable);
385
389
  },
386
390
  // Without .none(), nanobind rejects a Python `None` argument for an
387
391
  // `nb::object`-typed parameter registered this ad-hoc way (unlike a
@@ -391,9 +395,33 @@ nb::object setFastContinueTrampoline(const SetFastContinueFn& setFastContinue) {
391
395
  // was called with breakpoints=None (DebugSession's own
392
396
  // _apply_fast_continue calls it with None whenever fast-continue
393
397
  // mode isn't safe right now -- see debugger.py).
394
- nb::arg("breakpoints").none());
398
+ nb::arg("breakpoints").none(), nb::arg("hook_skippable") = false);
395
399
  }
396
400
 
401
+ // Python-visible handle onto a lock-free, GIL-free interrupt flag -- see
402
+ // Evaluator::setFastContinueInterruptFlag's own doc comment (evaluator.hpp)
403
+ // for why this exists at all: debug_evaluate() runs as one single blocking
404
+ // call with the GIL released for its whole duration, so there is no live,
405
+ // Python-callable Evaluator handle DebugSession.pause()/set_breakpoints()
406
+ // (running on the MAIN/GUI thread) could otherwise invoke directly to
407
+ // interrupt hook-skippable mode. A caller creates ONE of these before
408
+ // calling debug_evaluate() (passing it as fast_continue_signal), keeps it
409
+ // around for the whole debug session, and calls .request() from the main
410
+ // thread any time a hook-skippable checkDebug() call needs to stop
411
+ // skipping and actually consult Python again -- Pause, or a breakpoint
412
+ // being toggled in an editor tab while a render is mid-flight. Nothing
413
+ // else needs to read it back on the Python side; C++ test-and-clears it
414
+ // (see checkDebug's own doc comment, debug_profile.cpp) the moment it acts
415
+ // on it, so there's no separate "acknowledge" step.
416
+ class FastContinueSignal {
417
+ public:
418
+ void request() { flag_->store(true, std::memory_order_release); }
419
+ const std::shared_ptr<std::atomic<bool>>& flag() const { return flag_; }
420
+
421
+ private:
422
+ std::shared_ptr<std::atomic<bool>> flag_ = std::make_shared<std::atomic<bool>>(false);
423
+ };
424
+
397
425
  // GIL held (reacquired by the caller). `getFrame`/`callStack`/`generatePartial`/
398
426
  // `getChildrenPositions` are valid only for this synchronous call, so their
399
427
  // closures are only ever invoked from within the Python hook, before it returns.
@@ -470,7 +498,8 @@ void callPyReturnHook(nb::handle returnHook, const std::string& name, const osca
470
498
  // blocks on the Python side (threading.Event) so the GUI thread keeps running.
471
499
  nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::callable debugHook,
472
500
  nb::callable errorBreak, nb::callable echoFn,
473
- std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, nb::object returnHook) {
501
+ std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, nb::object returnHook,
502
+ FastContinueSignal* fastContinueSignal) {
474
503
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
475
504
 
476
505
  std::vector<oscadeval::ColoredBody> bodies;
@@ -501,8 +530,8 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
501
530
  return evPtr->lastChildrenPositions();
502
531
  };
503
532
  SetFastContinueFn setFastContinue =
504
- [&evPtr](std::optional<std::unordered_map<std::string, std::set<int>>> breakpoints) {
505
- evPtr->setFastContinueBreakpoints(std::move(breakpoints));
533
+ [&evPtr](std::optional<std::unordered_map<std::string, std::set<int>>> breakpoints, bool hookSkippable) {
534
+ evPtr->setFastContinueBreakpoints(std::move(breakpoints), hookSkippable);
506
535
  };
507
536
  oscadeval::DebugHooks hooks;
508
537
  hooks.debugHook = [&](int line, int depth, bool forced, bool exprLevel, const std::string& origin,
@@ -528,6 +557,7 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
528
557
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(ast, path, echoCpp);
529
558
  oscadeval::Evaluator ev(echoCpp, nullptr, manifoldCache, hooks, false);
530
559
  evPtr = &ev;
560
+ if (fastContinueSignal) ev.setFastContinueInterruptFlag(fastContinueSignal->flag());
531
561
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
532
562
  bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
533
563
  collectIdSpans(ev, idSpans);
@@ -602,6 +632,12 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
602
632
  .def(nb::init<>())
603
633
  .def("clear", &oscadeval::ManifoldCache::clear);
604
634
 
635
+ // See FastContinueSignal's own doc comment, above -- only a
636
+ // constructor and request() are ever called from Python.
637
+ nb::class_<FastContinueSignal>(m, "FastContinueSignal")
638
+ .def(nb::init<>())
639
+ .def("request", &FastContinueSignal::request);
640
+
605
641
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
606
642
  nb::arg("profile") = false,
607
643
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).");
@@ -609,6 +645,6 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
609
645
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
610
646
  m.def("debug_evaluate", &debugEvaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("debug_hook"),
611
647
  nb::arg("error_break"), nb::arg("echo_fn"), nb::arg("manifold_cache") = nullptr,
612
- nb::arg("return_hook") = nb::none(),
648
+ nb::arg("return_hook") = nb::none(), nb::arg("fast_continue_signal") = nullptr,
613
649
  "Evaluate with the debugger wired in; returns (bodies, [], id_to_node, dyn, dyn_explicit). Callbacks fire under the GIL.");
614
650
  }
@@ -276,24 +276,24 @@ struct CompiledChunk {
276
276
  // literal's own ClosureSite still needed from beyond `node`'s own scope
277
277
  // (i.e. any capture whose resolution target isn't `node` itself). This
278
278
  // "bubbling" is what makes a closure nested inside another compiled-
279
- // creating closure resolve correctly even though the inner one's body
280
- // is never itself compiled (see this struct's own body-compilation
281
- // note below): by the time a real, running CompiledChunk executes
282
- // Op::MakeClosure for `node`, every one of `captures`'s entries is
283
- // guaranteed to resolve against THAT SAME running chunk's own `slots`
284
- // array, regardless of how many literal-within-literal levels `node`
285
- // was originally nested through in the source.
279
+ // creating closure resolve correctly: at the OUTERMOST level a given
280
+ // capture is actually snapshotted (see Op::MakeClosure's own runtime
281
+ // doc comment, bytecode_vm.cpp), `captures`'s entries resolve against
282
+ // that running chunk's own `slots` array whenever `targetDecl` matches
283
+ // its own declaration -- and, when it doesn't (a capture bubbled up
284
+ // from an even-more-deeply-nested literal), against the enclosing
285
+ // frame via Evaluator::findUpvalue or, failing that, `ctx.let_`
286
+ // (rooted at that OUTER closure's own capturedLet whenever it's itself
287
+ // an escaped invocation) -- regardless of how many literal-within-
288
+ // literal levels `node` was originally nested through in the source.
286
289
  //
287
- // `node`'s own BODY is deliberately never itself compiled/registered
288
- // once it has any capture need (even an empty one bubbled up from
289
- // something it contains) -- it always runs via the ordinary AST
290
- // interpreter when invoked (Evaluator::lookupCompiledLiteralChunk finds
291
- // no cache entry for it), using Closure::capturedLet exactly like any
292
- // other escaping closure the interpreter creates directly. This keeps
293
- // the whole feature to "make closure CREATION safe to compile"; a
294
- // closure's own body additionally getting compiled too (its captures
295
- // would need to read from this snapshot instead of a live-stack walk)
296
- // is a natural follow-on, not attempted here.
290
+ // `node`'s own body IS also registered (Evaluator::
291
+ // lookupCompiledLiteralChunk finds a real cache entry for it, same as
292
+ // a zero-capture literal), so a closure invocation can run compiled
293
+ // too, not just its creation: Op::LoadUpvalue/Op::MakeClosure inside
294
+ // that body fall back to `ctx.let_` exactly as described above
295
+ // whenever the live-call-stack walk misses, which is precisely what
296
+ // happens once this closure has actually escaped its creator.
297
297
  struct ClosureSite {
298
298
  const oscad::FunctionLiteral* node = nullptr;
299
299
  std::vector<UpvalueRef> captures;
@@ -307,6 +307,15 @@ struct CompiledChunk {
307
307
  std::vector<std::optional<std::string>> argNames;
308
308
  };
309
309
 
310
+ // The FunctionDeclaration/FunctionLiteral this chunk's own body was
311
+ // compiled from (bytecode_compiler.cpp's compileFunctionLike sets this
312
+ // to its own `selfDecl` parameter) -- Op::MakeClosure's runtime handler
313
+ // (bytecode_vm.cpp) compares a ClosureSite capture's own `targetDecl`
314
+ // against this to tell "genuinely local to the running chunk" (read
315
+ // `slots` directly) from "bubbled up from a nested literal" (needs the
316
+ // enclosing-frame/capturedLet fallback) apart.
317
+ const oscad::ASTNode* selfDecl = nullptr;
318
+
310
319
  std::vector<Param> params;
311
320
  std::vector<std::vector<Instruction>> defaultCode;
312
321
  std::vector<Instruction> bodyCode;
@@ -15,6 +15,7 @@
15
15
 
16
16
  #include "openscad_cpp_parser/ast.hpp"
17
17
 
18
+ #include <atomic>
18
19
  #include <chrono>
19
20
  #include <cstdint>
20
21
  #include <functional>
@@ -329,12 +330,61 @@ public:
329
330
  // exception at all" (today's original, always-safe behavior) --
330
331
  // e.g. the instant a step command starts or a pause is requested.
331
332
  // Cheap to call as often as the debugger's own state changes: this
332
- // just updates one member, no cache invalidation needed (chunkEligibleNow
333
- // is re-checked on every lookupOrCompileChunk/lookupCompiledLiteralChunk
334
- // call, not cached itself -- only the compiled bytecode ITSELF, which
335
- // never depends on debugger state, is cached forever).
336
- void setFastContinueBreakpoints(std::optional<std::unordered_map<std::string, std::set<int>>> breakpoints) {
333
+ // just updates two members, no cache invalidation needed (chunkEligibleNow/
334
+ // checkDebug are re-checked on every call, not cached themselves -- only
335
+ // the compiled bytecode ITSELF, which never depends on debugger state,
336
+ // is cached forever).
337
+ //
338
+ // `hookSkippable`: a STRICTLY narrower claim than "breakpoints is
339
+ // accurate" above -- true only when NOTHING needs to inspect a
340
+ // statement-level checkpoint's own line/depth at all, not just "nothing
341
+ // needs to inspect it unless it's a compiled function's entry." This is
342
+ // false for step_over/step_out even though THEY also pass a real
343
+ // breakpoints set (chunkEligibleNow/useBytecodeVm still apply for them):
344
+ // both need checkDebug() to keep calling into the debug hook on every
345
+ // statement so the caller's own step_hit logic (line/depth comparison
346
+ // against the step's own starting point) can run -- there is no way to
347
+ // decide that in advance the way a breakpoint LOCATION can be. Only a
348
+ // plain "Continue" with no step pending can safely skip the call
349
+ // entirely for a line with no breakpoint -- see checkDebug's own doc
350
+ // comment (debug_profile.cpp) for where this is actually consulted.
351
+ void setFastContinueBreakpoints(std::optional<std::unordered_map<std::string, std::set<int>>> breakpoints,
352
+ bool hookSkippable = false) {
337
353
  fastContinueBreakpoints_ = std::move(breakpoints);
354
+ fastContinueHookSkippable_ = hookSkippable;
355
+ }
356
+
357
+ // The other half of hook-skippable mode's safety net. checkDebug()'s
358
+ // whole premise (see its own doc comment, debug_profile.cpp) is that it
359
+ // can skip calling into Python for a line with no breakpoint -- but the
360
+ // caller that decided that (DebugSession, on the MAIN/GUI thread) needs
361
+ // a way to say "actually, don't skip the very next one" from OUTSIDE any
362
+ // hook call, since debug_evaluate() runs as one single blocking call
363
+ // with the GIL released for its whole duration: there is no live,
364
+ // Python-callable Evaluator handle to invoke setFastContinueBreakpoints
365
+ // on directly the way a synchronous API would allow. A user clicking
366
+ // Pause, or toggling a breakpoint in an editor tab, while a hook-
367
+ // skippable render is mid-flight needs to take effect on the very next
368
+ // checkpoint, not "whenever a breakpoint happens to be hit next" (which,
369
+ // in hook-skippable mode, could be never, for a script with none set).
370
+ //
371
+ // `flag` is a plain shared_ptr<atomic<bool>> -- lock-free and GIL-free
372
+ // by construction, so the caller (bindings/module.cpp wraps it in a
373
+ // small Python-visible class) can set it from the main thread at any
374
+ // moment with no synchronization needed beyond the atomic itself.
375
+ // checkDebug() atomically test-and-clears it (exchange) on every call
376
+ // that would otherwise skip: if it was set, this call falls through and
377
+ // actually invokes the Python hook instead, which re-derives and pushes
378
+ // fresh breakpoints/hookSkippable state via its own existing logic --
379
+ // clearing it here (not from Python) means there's no separate
380
+ // "acknowledge" round-trip needed. Never itself compared against
381
+ // anything else; nullptr (the default, and what a plain debugger
382
+ // attach that never wires this up leaves it at) simply means hook-
383
+ // skippable mode -- if ever engaged at all -- can't be interrupted this
384
+ // way, which is only actually reachable if a caller opts into
385
+ // hookSkippable=true above without also providing this.
386
+ void setFastContinueInterruptFlag(std::shared_ptr<std::atomic<bool>> flag) {
387
+ fastContinueInterrupt_ = std::move(flag);
338
388
  }
339
389
 
340
390
  // Checks whether a debug pause should happen at `node` (via the
@@ -952,6 +1002,16 @@ private:
952
1002
  // setter leaves it at) means "no fast-continue exception" -- exactly
953
1003
  // today's original, always-safe behavior.
954
1004
  std::optional<std::unordered_map<std::string, std::set<int>>> fastContinueBreakpoints_;
1005
+ // See setFastContinueBreakpoints's own doc comment for why this is a
1006
+ // separate, narrower flag from fastContinueBreakpoints_ itself -- false
1007
+ // (the default) whenever a debugger is attached without explicitly
1008
+ // opting in, matching today's always-safe "checkDebug always calls the
1009
+ // hook" behavior.
1010
+ bool fastContinueHookSkippable_ = false;
1011
+ // See setFastContinueInterruptFlag's own doc comment. nullptr (the
1012
+ // default) means hook-skippable mode, if ever engaged, can't be
1013
+ // interrupted from outside a hook call.
1014
+ std::shared_ptr<std::atomic<bool>> fastContinueInterrupt_;
955
1015
  // The root EvalContext resolveTree() was called with -- lets
956
1016
  // checkDebug()'s locals snapshot fall back to top-level script
957
1017
  // 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 = "0.6.1"
7
+ version = "0.8.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -13,11 +13,12 @@ from dataclasses import dataclass
13
13
  from typing import Optional
14
14
 
15
15
  from . import _openscad_cpp_evaluator as _ext
16
- from ._openscad_cpp_evaluator import ManifoldCache
16
+ from ._openscad_cpp_evaluator import 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",
21
22
  ]
22
23
 
23
24
 
@@ -317,6 +318,16 @@ class Evaluator:
317
318
  `return_hook`: fires after a user function/function-literal call
318
319
  computes its result, before returning -- (name, value, depth). Only
319
320
  meaningful with debug_hook set (the plain evaluate() path never calls it).
321
+ `fast_continue_signal`: an optional FastContinueSignal the caller keeps
322
+ across the whole debug session and calls `.request()` on (e.g. from
323
+ DebugSession.pause()/set_breakpoints(), BelfrySCAD's own debugger.py)
324
+ any time a checkDebug() checkpoint skipped via hook-skippable fast-
325
+ continue mode (see debug_hook's own `set_fast_continue(breakpoints,
326
+ hook_skippable)` kwarg) needs to stop skipping and consult Python
327
+ again -- there is no other way to reach a running debug_evaluate() call
328
+ from outside a hook invocation, since it runs as one single blocking
329
+ call with the GIL released for its duration. Only meaningful with
330
+ debug_hook set; ignored otherwise.
320
331
 
321
332
  After evaluate() returns, `self.csg_tree` (list of _CSGNode, see
322
333
  format_csg_tree), `self.dyn` (dict of every currently-visible
@@ -328,13 +339,14 @@ class Evaluator:
328
339
  """
329
340
 
330
341
  def __init__(self, echo_fn=None, debug_hook=None, error_break_fn=None, return_hook=None,
331
- manifold_cache=None, profile=False):
342
+ manifold_cache=None, profile=False, fast_continue_signal=None):
332
343
  self._echo_fn = echo_fn
333
344
  self._debug_hook = debug_hook
334
345
  self._error_break_fn = error_break_fn
335
346
  self._return_hook = return_hook
336
347
  self._manifold_cache = manifold_cache
337
348
  self._profile = profile
349
+ self._fast_continue_signal = fast_continue_signal
338
350
  self.csg_tree = []
339
351
  self.profile_result = None
340
352
  self.dyn = {}
@@ -350,7 +362,7 @@ class Evaluator:
350
362
  source_path, vp, self._debug_hook,
351
363
  self._error_break_fn or (lambda *a, **k: None),
352
364
  self._echo_fn or (lambda _m: None),
353
- self._manifold_cache, self._return_hook)
365
+ self._manifold_cache, self._return_hook, self._fast_continue_signal)
354
366
  self.csg_tree = []
355
367
  self.profile_result = None
356
368
  else:
@@ -44,11 +44,9 @@ bool isListCompClauseKind(oscad::NodeKind kind) {
44
44
  // `owner` itself (targetDecl != owner) -- see ClosureSite's own doc comment
45
45
  // (bytecode.hpp) for why this "bubbling" is necessary: a literal nested
46
46
  // inside `owner` may need something from further out than `owner`'s own
47
- // scope, and since that inner literal's body is never itself compiled
48
- // (only ever invoked via the interpreter once it has any capture need),
49
- // `owner`'s OWN Op::MakeClosure must snapshot that name too -- otherwise
50
- // the inner literal's interpreter-resolved capturedLet ancestry, rooted at
51
- // whatever `owner` itself captured, would simply never contain it.
47
+ // scope, so `owner`'s OWN Op::MakeClosure must snapshot that name too --
48
+ // otherwise the inner literal's own capturedLet, rooted at whatever `owner`
49
+ // itself captured, would simply never contain it.
52
50
  // Dedupes by (targetDecl, slot): the same enclosing binding reached through
53
51
  // two different nested literals (or the same literal referencing it twice)
54
52
  // is still one capture to make.
@@ -67,6 +65,43 @@ void bubbleEscapingCaptures(const CompiledChunk::ClosureSite& site, const oscad:
67
65
  }
68
66
  }
69
67
 
68
+ // True if any instruction in `chunk`'s own body/defaults is Op::LoadFree --
69
+ // i.e. an identifier that resolved to neither a local, a statically-known
70
+ // enclosing upvalue, nor a dyn ($-prefixed) name at compile time (see
71
+ // compileExpr's own Identifier case). A self-referential recursive closure
72
+ // (`let(a = function(x,i) ... a(...) ...)`, the reduce()/accumulate()/
73
+ // while() idiom) hits exactly this: LetOp's own declareLocal for `a` runs
74
+ // only AFTER compiling `a`'s RHS, so `a`'s reference to itself can't
75
+ // resolve as an upvalue at compile time and falls through to LoadFree --
76
+ // Evaluator::evalIdentifier's own ctx.scope->lookupVariable() fallback,
77
+ // which re-evaluates the let-binding's RHS FRESH on every single call,
78
+ // producing a new Closure whose capturedLet is THIS invocation's own
79
+ // ctx.let_ (see expr_eval.cpp's FunctionLiteral case) rather than a stable
80
+ // snapshot. Registering such a chunk for compiled invocation (see the
81
+ // FunctionLiteral case below) would still be functionally correct -- but
82
+ // each recursive call's own capturedLet->openChild() then nests ONE level
83
+ // deeper than the last (confirmed empirically: an O(n) list reduce()
84
+ // degrading to O(n^2) once compiled), since nothing about that repeated
85
+ // fresh-derivation ever re-roots the trail. Excluding a LoadFree-containing
86
+ // chunk from registration leaves it running interpreted exactly as before
87
+ // (this compile-time gap itself isn't fixed here) -- a real, if imperfect,
88
+ // scope for now; closing it -- letting `a` resolve as a genuine upvalue of
89
+ // its own declaring LetOp -- is future work, not bundled into this change.
90
+ bool containsLoadFree(const std::vector<Instruction>& code) {
91
+ for (const Instruction& ins : code) {
92
+ if (ins.op == Op::LoadFree) return true;
93
+ }
94
+ return false;
95
+ }
96
+
97
+ bool containsLoadFree(const CompiledChunk& chunk) {
98
+ if (containsLoadFree(chunk.bodyCode)) return true;
99
+ for (const auto& defaultCode : chunk.defaultCode) {
100
+ if (containsLoadFree(defaultCode)) return true;
101
+ }
102
+ return false;
103
+ }
104
+
70
105
  // Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
71
106
  // shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
72
107
  // LetOp (pushed/popped around its own assignments+body), innermost frame
@@ -306,32 +341,41 @@ public:
306
341
  for (const auto& nestedSite : literalChunk.closureSites) {
307
342
  bubbleEscapingCaptures(nestedSite, &n, effectiveCaptures);
308
343
  }
309
- if (effectiveCaptures.empty()) {
310
- // Closes over nothing at all, even transitively -- the
311
- // existing fast path: a single frozen constant works
312
- // for every invocation, and this literal's own compiled
313
- // chunk (possibly containing its OWN MakeClosure sites
314
- // for anything nested inside IT that closes only over
315
- // ITS OWN scope) is genuinely usable, registered as
316
- // before.
344
+ // Registered (Evaluator::lookupCompiledLiteralChunk finds
345
+ // this by node pointer whenever the closure is later
346
+ // invoked, whether that's a plain PushConst-created value
347
+ // below or a MakeClosure-snapshotted one) whenever it's
348
+ // actually safe to run compiled: always for a zero-capture
349
+ // literal (a single frozen constant, unaffected by anything
350
+ // below), and for a captures-having one only when its own
351
+ // body contains no Op::LoadFree -- see containsLoadFree's
352
+ // own doc comment, above, for why a LoadFree-containing
353
+ // captures-having closure (the reduce()/accumulate()/
354
+ // while() self-referential-recursion idiom) must still be
355
+ // left interpreted: its own capturedLet-chaining, once
356
+ // registered, degrades an O(n) reduction into O(n^2).
357
+ // Everything else (Op::LoadUpvalue/Op::MakeClosure inside a
358
+ // REGISTERED captures-having chunk) is no longer assumed to
359
+ // resolve only against a still-live creator frame (see
360
+ // those opcodes' own runtime fallback, bytecode_vm.cpp):
361
+ // when the live-call-stack walk misses (the creator's frame
362
+ // is gone -- a genuinely escaped closure), they fall back to
363
+ // `ctx.let_`, which callCtxFor roots at this exact closure's
364
+ // own capturedLet snapshot whenever it's invoked -- the same
365
+ // snapshot `effectiveCaptures` describes below.
366
+ if (effectiveCaptures.empty() || !containsLoadFree(literalChunk)) {
317
367
  chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
368
+ }
369
+ if (effectiveCaptures.empty()) {
370
+ // Closes over nothing at all, even transitively -- a
371
+ // single frozen constant works for every invocation, no
372
+ // per-call snapshot needed.
318
373
  out.push_back(
319
374
  {Op::PushConst, internConst(Value{std::make_shared<const Closure>(Closure{&n, nullptr})}), 0,
320
375
  nullptr});
321
376
  return;
322
377
  }
323
378
  // Escaping-closure support (Op::MakeClosure, bytecode.hpp).
324
- // `literalChunk`'s own bytecode is discarded here -- its own
325
- // upvalue reads would resolve via the live-call-stack walk
326
- // (Op::LoadUpvalue), which is wrong for a closure that
327
- // outlives its creator -- so this literal always runs
328
- // interpreted when invoked instead (Evaluator::
329
- // lookupCompiledLiteralChunk finds no cache entry for it),
330
- // using the exact same Closure::capturedLet machinery every
331
- // other escaping closure the interpreter creates directly
332
- // already relies on. Only `effectiveCaptures` (the names/
333
- // slots to snapshot right now) survives from the discarded
334
- // compile attempt.
335
379
  int siteIdx = static_cast<int>(chunk_.closureSites.size());
336
380
  chunk_.closureSites.push_back(CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures)});
337
381
  out.push_back({Op::MakeClosure, siteIdx, 0, nullptr});
@@ -896,6 +940,7 @@ bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope,
896
940
  if (!p->name->name.empty() && p->name->name[0] == '$') return false;
897
941
  }
898
942
 
943
+ chunk.selfDecl = selfDecl;
899
944
  Compiler compiler(chunk, staticScope, selfDecl, std::move(enclosing));
900
945
  CompileScope bodyScope;
901
946
  bodyScope.push();
@@ -127,8 +127,22 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
127
127
  ++pc;
128
128
  break;
129
129
  case Op::LoadUpvalue: {
130
+ // findUpvalue walks the LIVE call stack -- correct as long
131
+ // as the declaring call is still active, however many
132
+ // upvalue levels out `uv.targetDecl` sits (an ordinary,
133
+ // not-yet-escaped nested closure, e.g. reduce()'s own
134
+ // self-recursive accumulator). If that frame is gone
135
+ // (this chunk is running as an ESCAPED closure's own body
136
+ // -- see the FunctionLiteral case's own doc comment,
137
+ // bytecode_compiler.cpp), fall back to `ctx.let_`: for any
138
+ // closure invocation with a non-null capturedLet,
139
+ // callCtxFor roots `ctx.let_` at that exact snapshot, which
140
+ // is guaranteed (by construction -- effectiveCaptures is a
141
+ // superset of this chunk's own upvalues) to contain this
142
+ // name.
130
143
  const CompiledChunk::UpvalueRef& uv = chunk.upvalues[static_cast<size_t>(ins.a)];
131
144
  const Value* v = ev.findUpvalue(uv.targetDecl, uv.slot);
145
+ if (!v) v = ctx.let_->find(uv.name);
132
146
  stack.push_back(v ? *v : Value{});
133
147
  ++pc;
134
148
  break;
@@ -137,24 +151,36 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
137
151
  // Builds a FRESH captured environment every time this
138
152
  // instruction actually runs (never a compile-time constant
139
153
  // -- a loop creating one closure per iteration must capture
140
- // THAT iteration's own values, not share one snapshot) by
141
- // reading each of the site's own captures straight out of
142
- // THIS frame's own `slots` -- see ClosureSite's own doc
143
- // comment (bytecode.hpp) for why every capture, however
144
- // deeply the literal was originally nested, is guaranteed
145
- // to resolve against this SAME currently-running chunk.
146
- // The resulting Closure::capturedLet is a real, standalone
147
- // TrailView (isolated root -- nothing else shares it, and
148
- // it owes nothing to `ctx.let_`, which a compiled call
149
- // never populates -- see bindCompiledArgs' own doc
150
- // comment), so invoking this closure later works through
151
- // the EXACT same callCtxFor/capturedLetTrail machinery
152
- // every interpreter-created escaping closure already uses,
153
- // unchanged.
154
+ // THAT iteration's own values, not share one snapshot).
155
+ // Each capture is genuinely local to the CURRENTLY-RUNNING
156
+ // chunk only when its own `targetDecl` matches this chunk's
157
+ // `selfDecl` (this literal's direct free-variable
158
+ // references) -- read straight out of `slots` in that case.
159
+ // Anything else is a capture bubbled up from a literal
160
+ // nested even deeper (see ClosureSite's own doc comment,
161
+ // bytecode.hpp): resolved the same way Op::LoadUpvalue
162
+ // resolves any upvalue reference, above -- the live call
163
+ // stack first (still live if THIS closure hasn't itself
164
+ // escaped yet), `ctx.let_` otherwise (this chunk's own
165
+ // capturedLet, when it's running as an escaped closure's
166
+ // body). The resulting Closure::capturedLet is a real,
167
+ // standalone TrailView (isolated root -- nothing else
168
+ // shares it, and it owes nothing to `ctx.let_` itself,
169
+ // which a compiled call never writes to directly -- see
170
+ // bindCompiledArgs' own doc comment), so invoking this
171
+ // closure later works through the EXACT same
172
+ // callCtxFor/capturedLetTrail machinery every interpreter-
173
+ // created escaping closure already uses, unchanged.
154
174
  const CompiledChunk::ClosureSite& site = chunk.closureSites[static_cast<size_t>(ins.a)];
155
175
  auto capturedTrail = TrailView<Value>::makeRoot();
156
176
  for (const auto& cap : site.captures) {
157
- capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
177
+ if (cap.targetDecl == chunk.selfDecl) {
178
+ capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
179
+ continue;
180
+ }
181
+ const Value* v = ev.findUpvalue(cap.targetDecl, cap.slot);
182
+ if (!v) v = ctx.let_->find(cap.name);
183
+ capturedTrail->set(cap.name, v ? *v : Value{});
158
184
  }
159
185
  stack.push_back(Value{std::make_shared<const Closure>(Closure{site.node, std::move(capturedTrail)})});
160
186
  ++pc;
@@ -77,6 +77,36 @@ std::vector<DebugFrame> Evaluator::buildDebugFrames(const EvalContext* ctx) cons
77
77
  void Evaluator::checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool forced, bool exprLevel) {
78
78
  if (!debugHooks_.debugHook) return;
79
79
  const oscad::Position& pos = node.position();
80
+ // Fast-continue's hook-skippable mode (setFastContinueBreakpoints' own
81
+ // doc comment): a plain "Continue" with no step pending needs the debug
82
+ // hook called ONLY for a line that actually has a breakpoint -- every
83
+ // other checkpoint is guaranteed to do nothing (no forced/breakpoint/
84
+ // step_hit condition on the Python side can possibly fire), so skip the
85
+ // call (and the childStatementPositions/getFrame setup below, all
86
+ // wasted work otherwise) entirely rather than crossing into Python just
87
+ // to be told "continue". `forced` (the explicit breakpoint() builtin)
88
+ // always bypasses this, matching its own "bypasses nothing" contract.
89
+ // Never applies to step_over/step_out (hookSkippable is false for those
90
+ // even though they also set a real breakpoints set, see the setter's
91
+ // own doc comment) or step_into/step_to_child (fastContinueBreakpoints_
92
+ // itself is nullopt then) -- both need every statement inspected.
93
+ if (!forced && fastContinueHookSkippable_ && fastContinueBreakpoints_) {
94
+ // Test-and-clear: if the main thread requested an interrupt (Pause,
95
+ // or a breakpoint edit -- see setFastContinueInterruptFlag's own doc
96
+ // comment) since the last checkDebug() call, this call falls
97
+ // through and actually invokes the hook below instead of skipping,
98
+ // regardless of whether THIS specific line has a breakpoint --
99
+ // that's what lets the hook's own logic (pause_now, or a freshly
100
+ // updated breakpoints dict) run at all in hook-skippable mode.
101
+ const bool interrupted =
102
+ fastContinueInterrupt_ && fastContinueInterrupt_->exchange(false, std::memory_order_acq_rel);
103
+ if (!interrupted) {
104
+ auto originIt = fastContinueBreakpoints_->find(pos.origin);
105
+ if (originIt == fastContinueBreakpoints_->end() || !originIt->second.count(pos.line)) {
106
+ return;
107
+ }
108
+ }
109
+ }
80
110
  const int depth = static_cast<int>(callStack_.size());
81
111
  const DebugFramesFn getFrame = [this, &ctx]() { return buildDebugFrames(&ctx); };
82
112
  lastChildrenPositions_ = childStatementPositions(node);
@@ -152,8 +152,21 @@ EvalContext Evaluator::callCtxFor(const oscad::ASTNode& decl, EvalContext& ctx,
152
152
 
153
153
  std::optional<EvalContext> Evaluator::isolatedCallCtxFor(const oscad::ASTNode& declNode, EvalContext& ctx,
154
154
  const std::shared_ptr<TrailView<Value>>& capturedLet) {
155
+ // `declNode.scope()`, not `ctx.scope` -- matches every other call-
156
+ // resolution function's own `fnScope = decl.scope() ? decl.scope() :
157
+ // ctx.scope` pattern (evalUserFunction/evalUserFunctionFromBound/
158
+ // evalFunctionLiteral/evalFunctionLiteralFromBound). Mismatching here
159
+ // was invisible until a captures-having FunctionLiteral's own body
160
+ // could ever actually be trampolined into (Op::MakeClosure's own
161
+ // registration -- bytecode_compiler.cpp's FunctionLiteral case): a
162
+ // self-referential closure's free-variable resolution can fall through
163
+ // to evalIdentifier's ctx.scope->lookupVariable() fallback (see its own
164
+ // doc comment, expr_eval.cpp), which needs the CALLEE's own lexical
165
+ // scope to walk outward from -- the caller's scope at the call site
166
+ // has no relation to it at all.
155
167
  bool usedChildCtx = false;
156
- EvalContext result = callCtxFor(declNode, ctx, ctx.scope, nullptr, nullptr, &usedChildCtx, capturedLet);
168
+ const oscad::Scope* declScope = declNode.scope() ? declNode.scope() : ctx.scope;
169
+ EvalContext result = callCtxFor(declNode, ctx, declScope, nullptr, nullptr, &usedChildCtx, capturedLet);
157
170
  if (usedChildCtx) return std::nullopt;
158
171
  return result;
159
172
  }