openscad-cpp-evaluator 0.2.0__tar.gz → 0.4.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.2.0 → openscad_cpp_evaluator-0.4.0}/CLAUDE.md +26 -9
  2. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/bindings/module.cpp +74 -12
  4. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/examples/minimal_debugger.cpp +3 -3
  5. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +22 -16
  6. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/debug_repl.hpp +1 -1
  7. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/evaluator.hpp +47 -7
  8. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/pyproject.toml +1 -1
  9. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/python/openscad_cpp_evaluator/__init__.py +12 -3
  10. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/control.cpp +5 -0
  11. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/csg_generate.cpp +37 -6
  12. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/csg_resolve.cpp +10 -0
  13. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/debug/debug_repl.cpp +13 -7
  14. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/debug_profile.cpp +2 -2
  15. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/expr_eval.cpp +54 -6
  16. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/stmt_eval.cpp +40 -15
  17. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/user_calls.cpp +24 -6
  18. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_cli.cpp +1 -1
  19. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_control_flow.cpp +2 -2
  20. openscad_cpp_evaluator-0.4.0/tests/test_debug_hooks.cpp +422 -0
  21. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_python_bindings.py +72 -1
  22. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_tail_calls.cpp +22 -10
  23. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/cli_lib.cpp +2 -2
  24. openscad_cpp_evaluator-0.2.0/tests/test_debug_hooks.cpp +0 -170
  25. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/.github/workflows/wheels.yml +0 -0
  27. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/.gitignore +0 -0
  28. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/.gitmodules +0 -0
  29. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/LICENSE +0 -0
  31. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/README.md +0 -0
  32. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/bindings/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/examples/CMakeLists.txt +0 -0
  34. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/examples/eval_perf_benchmark.cpp +0 -0
  35. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/examples/manifold_cache_reuse.cpp +0 -0
  36. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  37. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/.gitignore +0 -0
  38. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  39. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/README.md +0 -0
  41. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  42. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  43. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  45. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  46. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  47. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  48. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  49. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  50. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  51. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  52. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  53. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  54. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  55. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  56. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  57. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  58. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  59. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  60. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  61. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  62. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  63. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  64. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  65. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  66. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  67. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  68. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  69. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  70. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  71. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  72. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  73. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  74. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  75. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  76. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  77. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  78. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  79. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  80. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  81. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  82. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  83. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  84. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  85. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  86. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  87. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  88. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  89. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  90. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  91. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  92. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  93. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  94. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  95. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  96. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  97. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  98. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  99. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  100. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  101. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  102. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  103. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bundled_font_path.hpp.in +0 -0
  104. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  105. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  106. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  107. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  110. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  111. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  112. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  113. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  114. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  115. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  116. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  117. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  119. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  120. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  121. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  122. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  123. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  124. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  125. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  126. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  127. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  129. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/resources/fonts/LICENSE +0 -0
  132. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  133. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/CMakeLists.txt +0 -0
  134. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/booleans.cpp +0 -0
  135. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/builtins.hpp +0 -0
  136. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/call_args.cpp +0 -0
  137. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/color.cpp +0 -0
  138. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/extrude.cpp +0 -0
  139. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/function_builtins.cpp +0 -0
  140. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/import.cpp +0 -0
  141. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/modifiers.cpp +0 -0
  142. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/primitives_2d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/primitives_3d.cpp +0 -0
  144. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/registry.cpp +0 -0
  145. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/roof.cpp +0 -0
  146. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/segments.cpp +0 -0
  147. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/surface.cpp +0 -0
  148. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/text.cpp +0 -0
  149. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/topology.cpp +0 -0
  150. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/transforms.cpp +0 -0
  151. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/bytecode_compiler.cpp +0 -0
  152. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/bytecode_vm.cpp +0 -0
  153. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/colored_body.cpp +0 -0
  154. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/css_colors.cpp +0 -0
  155. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/eval_context.cpp +0 -0
  156. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/eval_error.cpp +0 -0
  157. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/eval_use.cpp +0 -0
  158. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/export.cpp +0 -0
  159. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/import/dxf_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/import/mesh_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/import/surface_load.cpp +0 -0
  162. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/import/svg_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/manifold_cache.cpp +0 -0
  164. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/text/stb_font_provider.cpp +0 -0
  165. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/text/text_metrics.cpp +0 -0
  166. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/value.cpp +0 -0
  167. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/src/zip_stored.cpp +0 -0
  168. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/CMakeLists.txt +0 -0
  169. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_booleans.cpp +0 -0
  170. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_bytecode_compiler.cpp +0 -0
  171. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_csg_tree.cpp +0 -0
  172. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_dxf_svg_import.cpp +0 -0
  173. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_eval_context.cpp +0 -0
  174. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_export.cpp +0 -0
  175. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_expr_eval.cpp +0 -0
  176. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_extrude_roof.cpp +0 -0
  177. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_function_builtins.cpp +0 -0
  178. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_helpers.hpp +0 -0
  179. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_import_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_manifold_cache.cpp +0 -0
  181. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_multi_color_merge.cpp +0 -0
  182. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_primitives.cpp +0 -0
  183. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_profiling.cpp +0 -0
  184. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_scoping.cpp +0 -0
  185. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_surface.cpp +0 -0
  186. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_text.cpp +0 -0
  187. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_transforms.cpp +0 -0
  188. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_use.cpp +0 -0
  189. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_value.cpp +0 -0
  190. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_viewport_params.cpp +0 -0
  191. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tests/test_zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.2.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/main.cpp +0 -0
@@ -241,15 +241,32 @@ Roman:style=Bold"`) silently gets Liberation Sans instead under this default pro
241
241
  path needed in this repo: the fix is a Qt host application supplying its own `FontProvider`, which
242
242
  is exactly what the injection seam exists for.
243
243
 
244
- **Debugger/profiler, Phase 9 (the last phase)**: `checkDebug()` fires from exactly one call site
245
- (`evalChildren`'s `runAll`, plus one explicit call each at user-function/function-literal body entry
246
- and inside `breakpoint()`'s resolve) rather than the reference's several dozen `_check_debug` call
247
- sites scattered across nearly every statement/expression-eval function a deliberate scope
248
- reduction (see `debug_hooks.hpp`'s `DebugHookFn` doc comment for the full reasoning): the reference's
249
- extra sites exist for `expr_level` sub-statement stepping (individual list-comprehension clauses,
250
- ternary branches) that neither this port's own `debug_repl.cpp` REPL nor the reference's own
251
- `_debug_repl.py` REPL ever actually uses (both only ever pause/step at statement granularity in
252
- practice). Verified this is a faithful, not just simplified, port by running an *identical*
244
+ **Debugger/profiler, Phase 9 (the last phase)**: `checkDebug()` fires at **full parity with the
245
+ reference's `_check_debug` call sites** the statement checkpoint in `evalChildren`'s `runAll`,
246
+ user-function/function-literal body entry, `breakpoint()`'s resolve, *and* the reference's
247
+ sub-statement sites: if/else branch entry (`evalStatement`), for-loop per-variable bindings and
248
+ per-iteration body entry (`evalFor`), `intersection_for` body entry, statement- and
249
+ expression-form `let()` assignments, expression-form `echo()`/`assert()`, ternary
250
+ condition/chosen-branch, every list-comprehension clause including the C-style `for`
251
+ (`evalListElement`), modifier-wrapped children (`evalModifier`), and user function/function-literal
252
+ call sites (`evalFunctionCall`, plus their duplicates in `simplifyTailStep` so a tail chain isn't
253
+ debug-invisible). Each carries the reference's `expr_level` flag, threaded all the way through
254
+ `DebugHookFn` and the nanobind trampoline. `expr_depth` is *not* tracked (hardcoded 0 through the
255
+ Python surface) — no consumer reads it.
256
+
257
+ An earlier revision of this file argued the sub-statement sites were unnecessary because no REPL
258
+ steps at that granularity. That was wrong: BelfrySCAD's debugger UI consumes them, and one case was
259
+ an outright bug — a C-style `for` *inside a list comprehension* got zero debug checks anywhere,
260
+ so a breakpoint set on it never fired. Two consequences of full parity worth knowing:
261
+ `useBytecodeVm()` returns false whenever a debug hook is installed (compiled chunks flatten away the
262
+ AST nodes these checkpoints hang off, so debugging always takes the interpreter path), and
263
+ `evalChildren` deliberately skips its own check for `ModularLet` — the reference's
264
+ `_eval_statement_impl` skips it too, pausing on the let's assignments instead. Parity is verified
265
+ test-by-test in `tests/test_debug_hooks.cpp`'s `DebugHooksParity` suite, whose expected
266
+ `(line, expr_level, forced)` sequences were captured by running the Python reference over the same
267
+ sources, not written down from this port's own output.
268
+
269
+ The port was also verified end to end by running an *identical*
253
270
  `--debug` session (same script, same `break`/`run`/`print`/`continue`/`step`/`backtrace`/`set`
254
271
  sequence) against both this port's CLI and the real Python reference's own `openscad-evaluator
255
272
  --debug` side by side: transcripts matched exactly, **including** an unexpected extra pause neither
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -292,13 +292,42 @@ nb::list callStackToPy(const std::vector<oscadeval::CallStackFrame>& cs) {
292
292
  return out;
293
293
  }
294
294
 
295
+ // Called from within a debug_hook/error_break callback (never after it
296
+ // returns) to best-effort generate a live render of whatever's been
297
+ // resolved so far -- see Evaluator::generatePartialTree()'s own doc
298
+ // comment for why this is safe to call mid-resolve.
299
+ using GeneratePartialFn = std::function<std::vector<oscadeval::ColoredBody>()>;
300
+
301
+ // Wraps a GeneratePartialFn as a Python callable returning a plain list of
302
+ // body-dicts (bodiesToList's own shape) -- the facade's _generate_partial_render
303
+ // converts these to ColoredBody and catches whatever exception a GenerateFn
304
+ // raises, the same try/except shape it already had calling generate_tree()
305
+ // directly. No error handling needed here: a C++ exception crossing into
306
+ // Python via this cpp_function is nanobind's normal std::exception ->
307
+ // RuntimeError mapping, which that Python try/except already catches.
308
+ nb::object generatePartialTrampoline(const GeneratePartialFn& generatePartial) {
309
+ return nb::cpp_function([&generatePartial]() -> nb::list {
310
+ std::vector<oscadeval::ColoredBody> bodies = generatePartial();
311
+ return bodiesToList(bodies);
312
+ });
313
+ }
314
+
295
315
  // Trampoline: C++ debug hook -> the Python DebugSession hook. Runs with the
296
- // GIL held (reacquired by the caller). `getFrame`/`callStack` are valid only
297
- // for this synchronous call, so the get_frames closure is only ever invoked
298
- // from within the Python hook, before it returns.
299
- oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, bool forced, const std::string& origin,
316
+ // (origin, line) targets children()/children(N) might forward control to
317
+ // from the just-checked debug-hook node, for the "step to child" command.
318
+ // See Evaluator::lastChildrenPositions()'s own doc comment: valid only
319
+ // synchronously within the current hook call, same as get_frames.
320
+ using GetChildrenPositionsFn = std::function<const std::optional<std::vector<std::pair<std::string, int>>>&()>;
321
+
322
+ // GIL held (reacquired by the caller). `getFrame`/`callStack`/`generatePartial`/
323
+ // `getChildrenPositions` are valid only for this synchronous call, so their
324
+ // closures are only ever invoked from within the Python hook, before it returns.
325
+ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, bool forced, bool exprLevel,
326
+ const std::string& origin,
300
327
  const std::vector<oscadeval::CallStackFrame>& callStack,
301
- const oscadeval::DebugFramesFn& getFrame) {
328
+ const oscadeval::DebugFramesFn& getFrame,
329
+ const GeneratePartialFn& generatePartial,
330
+ const GetChildrenPositionsFn& getChildrenPositions) {
302
331
  auto getFramesPy = nb::cpp_function([&callStack, &getFrame]() -> nb::object {
303
332
  std::vector<oscadeval::DebugFrame> frames = getFrame();
304
333
  nb::list allFrameLocals;
@@ -313,9 +342,22 @@ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, boo
313
342
  }
314
343
  return nb::make_tuple(nb::make_tuple(lastLocals, allFrameLocals), callStackToPy(callStack));
315
344
  });
345
+ nb::object generatePartialPy = generatePartialTrampoline(generatePartial);
346
+ auto getChildrenPositionsPy = nb::cpp_function([&getChildrenPositions]() -> nb::object {
347
+ const std::optional<std::vector<std::pair<std::string, int>>>& positions = getChildrenPositions();
348
+ if (!positions) return nb::none();
349
+ nb::list out;
350
+ for (const auto& [origin, line] : *positions) out.append(nb::make_tuple(origin, line));
351
+ return out;
352
+ });
316
353
 
317
- nb::object ret = hook(line, depth, nb::arg("forced") = forced, nb::arg("expr_level") = false,
318
- nb::arg("expr_depth") = 0, nb::arg("origin") = origin, nb::arg("get_frames") = getFramesPy);
354
+ // expr_depth stays hardcoded at 0 -- this port deliberately doesn't
355
+ // track the reference's _expr_depth counter (no consumer reads it);
356
+ // expr_level, which consumers do read, is the real threaded value.
357
+ nb::object ret = hook(line, depth, nb::arg("forced") = forced, nb::arg("expr_level") = exprLevel,
358
+ nb::arg("expr_depth") = 0, nb::arg("origin") = origin, nb::arg("get_frames") = getFramesPy,
359
+ nb::arg("generate_partial") = generatePartialPy,
360
+ nb::arg("get_children_positions") = getChildrenPositionsPy);
319
361
  nb::tuple t = nb::cast<nb::tuple>(ret);
320
362
  oscadeval::DebugAction action;
321
363
  action.stop = (nb::cast<std::string>(t[0]) == "stop");
@@ -327,11 +369,13 @@ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, boo
327
369
  // Trampoline: C++ errorBreak -> the Python DebugSession error hook.
328
370
  void callPyErrorBreak(nb::handle errorBreak, int line, const std::string& header, const std::string& origin,
329
371
  const std::vector<oscadeval::CallStackFrame>& callStack,
330
- const oscadeval::DebugFramesFn& getFrame) {
372
+ const oscadeval::DebugFramesFn& getFrame, const GeneratePartialFn& generatePartial) {
331
373
  std::vector<oscadeval::DebugFrame> frames = getFrame();
332
374
  nb::list allFrameLocals;
333
375
  for (const oscadeval::DebugFrame& f : frames) allFrameLocals.append(frameToDict(f));
334
- errorBreak(line, header, allFrameLocals, callStackToPy(callStack), nb::arg("origin") = origin);
376
+ nb::object generatePartialPy = generatePartialTrampoline(generatePartial);
377
+ errorBreak(line, header, allFrameLocals, callStackToPy(callStack), nb::arg("origin") = origin,
378
+ nb::arg("generate_partial") = generatePartialPy);
335
379
  }
336
380
 
337
381
  // Trampoline: C++ returnHook -> the Python DebugSession return hook. Only
@@ -362,17 +406,34 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
362
406
  nb::gil_scoped_acquire g;
363
407
  echoFn(m);
364
408
  };
409
+ // Set right after `ev` is constructed below, before evaluate() runs
410
+ // -- every hook fires only from within that call, by which point
411
+ // this is always valid. Needed because `hooks` (captured by the
412
+ // lambdas below) must be fully built and passed into Evaluator's
413
+ // constructor before `ev` exists, so the lambdas can't capture `ev`
414
+ // itself; they capture this pointer's address instead, which DOES
415
+ // already exist (see generatePartial's own doc comment on why
416
+ // GeneratePartialFn needs a live Evaluator& at all).
417
+ oscadeval::Evaluator* evPtr = nullptr;
418
+ GeneratePartialFn generatePartial = [&evPtr]() -> std::vector<oscadeval::ColoredBody> {
419
+ return evPtr->generatePartialTree();
420
+ };
421
+ GetChildrenPositionsFn getChildrenPositions =
422
+ [&evPtr]() -> const std::optional<std::vector<std::pair<std::string, int>>>& {
423
+ return evPtr->lastChildrenPositions();
424
+ };
365
425
  oscadeval::DebugHooks hooks;
366
- hooks.debugHook = [&](int line, int depth, bool forced, const std::string& origin,
426
+ hooks.debugHook = [&](int line, int depth, bool forced, bool exprLevel, const std::string& origin,
367
427
  const std::vector<oscadeval::CallStackFrame>& cs,
368
428
  const oscadeval::DebugFramesFn& gf) -> oscadeval::DebugAction {
369
429
  nb::gil_scoped_acquire g;
370
- return callPyDebugHook(debugHook, line, depth, forced, origin, cs, gf);
430
+ return callPyDebugHook(debugHook, line, depth, forced, exprLevel, origin, cs, gf, generatePartial,
431
+ getChildrenPositions);
371
432
  };
372
433
  hooks.errorBreak = [&](int line, const std::string& header, const std::string& origin,
373
434
  const std::vector<oscadeval::CallStackFrame>& cs, const oscadeval::DebugFramesFn& gf) {
374
435
  nb::gil_scoped_acquire g;
375
- callPyErrorBreak(errorBreak, line, header, origin, cs, gf);
436
+ callPyErrorBreak(errorBreak, line, header, origin, cs, gf, generatePartial);
376
437
  };
377
438
  if (!returnHook.is_none()) {
378
439
  hooks.returnHook = [&](const std::string& name, const oscadeval::Value& result, int depth) {
@@ -384,6 +445,7 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
384
445
  std::vector<std::unique_ptr<oscad::ASTNode>> ast = oscad::getASTFromFile(path);
385
446
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(ast, path, echoCpp);
386
447
  oscadeval::Evaluator ev(echoCpp, nullptr, manifoldCache, hooks, false);
448
+ evPtr = &ev;
387
449
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
388
450
  bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
389
451
  collectIdSpans(ev, idSpans);
@@ -46,7 +46,7 @@ std::string fmtLocals(const std::unordered_map<std::string, Value>& locals) {
46
46
  void traceEveryStatement() {
47
47
  std::cout << "1. trace every statement:\n";
48
48
  DebugHooks hooks;
49
- hooks.debugHook = [](int line, int depth, bool, const std::string&, const std::vector<CallStackFrame>&,
49
+ hooks.debugHook = [](int line, int depth, bool, bool, const std::string&, const std::vector<CallStackFrame>&,
50
50
  const DebugFramesFn& getFrame) {
51
51
  std::vector<DebugFrame> frames = getFrame();
52
52
  DebugFrame frame = frames.empty() ? DebugFrame{} : frames.front();
@@ -69,7 +69,7 @@ void stopAtBreakpoint() {
69
69
  constexpr int kBreakLine = 3; // the `translate(...) sphere(...)` statement
70
70
 
71
71
  DebugHooks hooks;
72
- hooks.debugHook = [](int line, int, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
72
+ hooks.debugHook = [](int line, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
73
73
  DebugAction action;
74
74
  if (line == kBreakLine) {
75
75
  std::cout << " breakpoint hit at line " << line << "\n";
@@ -97,7 +97,7 @@ void stopAtBreakpoint() {
97
97
  void overrideVariableViaMods() {
98
98
  std::cout << "3. override a variable via mods:\n";
99
99
  DebugHooks hooks;
100
- hooks.debugHook = [](int line, int, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
100
+ hooks.debugHook = [](int line, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
101
101
  DebugAction action;
102
102
  if (line == 2) action.mods["width"] = Value{2.0}; // about to run `cube([width, width, width])`
103
103
  return action;
@@ -86,25 +86,31 @@ inline constexpr const char* kDebuggingStoppedMessage = "Debugging stopped.";
86
86
  // genuine (non-debugger-triggered) EvalError leaves it at.
87
87
  enum class PostRunAction { None, Stopped, Restart, Quit };
88
88
 
89
- // Called at (approximately) every top-level statement in every block --
90
- // see Evaluator::evalChildren's single call site for the exact mechanism,
91
- // and its own comment for why this port checks at statement granularity
92
- // only, not the reference's additional expr_level sub-expression checks
93
- // (list-comprehension clauses, ternary branches, ...): those exist in the
94
- // reference to let a debugger single-step through individual expression
95
- // evaluation, which no consumer of this seam (debug_repl.cpp's REPL,
96
- // nor a plausible future GUI one) actually needs -- "pause on any
97
- // statement, inspect locals, set a breakpoint" is fully served by
98
- // statement-level granularity alone, at a small fraction of the
99
- // reference's own call-site count. Also called once, with `forced=true`,
100
- // right before evaluating a user function/function-literal's body
101
- // expression (functions have no statement-level granularity to hang a
102
- // hook off otherwise) and by breakpoint() (forced=true, unconditionally,
103
- // matching the reference's own _check_debug(..., forced=True) call).
89
+ // Called at every point the Python reference calls _check_debug: every
90
+ // top-level statement in every block (Evaluator::evalChildren's single
91
+ // statement checkpoint), plus the reference's finer-grained sub-statement
92
+ // sites -- ternary condition and chosen branch, if/else branch entry,
93
+ // for-loop per-variable bindings and per-iteration body entry,
94
+ // statement- and expression-form let() assignments, expression-form
95
+ // echo()/assert(), every list-comprehension clause (if / if-else / let /
96
+ // each / for / C-style-for, plus bare elements), modifier-wrapped
97
+ // children, and user function / function-literal call sites. Also called
98
+ // once right before evaluating a user function/function-literal's body
99
+ // expression, and by breakpoint() (forced=true, unconditionally, matching
100
+ // the reference's own _check_debug(..., forced=True) call).
101
+ //
102
+ // `exprLevel` mirrors the reference's `expr_level` keyword exactly: true
103
+ // for the sub-expression markers a debugger should NOT treat as a
104
+ // steppable/breakpointable statement (a chosen ternary or if branch, a
105
+ // for-loop body entry, a list-comprehension body element, a C-style-for
106
+ // condition re-check). Both this port's own debug_repl.cpp and the
107
+ // reference's _debug_repl.py gate every step/breakpoint decision on
108
+ // `!exprLevel`, while still pausing unconditionally on `forced`.
109
+ //
104
110
  // `depth` is the live user-call-stack depth (Evaluator::callStack_.size())
105
111
  // at the pause point. Mirrors Evaluator._check_debug/the debug_hook
106
112
  // callback contract.
107
- using DebugHookFn = std::function<DebugAction(int line, int depth, bool forced, const std::string& origin,
113
+ using DebugHookFn = std::function<DebugAction(int line, int depth, bool forced, bool exprLevel, const std::string& origin,
108
114
  const std::vector<CallStackFrame>& callStack, const DebugFramesFn& getFrame)>;
109
115
 
110
116
  // Called from Evaluator::error() right before it throws, if set -- gives a
@@ -128,7 +128,7 @@ public:
128
128
  bool runPrompt();
129
129
 
130
130
  // Wire these three into Evaluator's DebugHooks (see debug_hooks.hpp).
131
- DebugAction debugHook(int line, int depth, bool forced, const std::string& origin,
131
+ DebugAction debugHook(int line, int depth, bool forced, bool exprLevel, const std::string& origin,
132
132
  const std::vector<CallStackFrame>& callStack, const DebugFramesFn& getFrame);
133
133
  void errorBreak(int line, const std::string& header, const std::string& origin, const std::vector<CallStackFrame>& callStack,
134
134
  const DebugFramesFn& getFrame);
@@ -174,6 +174,26 @@ public:
174
174
  // top-level show_only filter -- see evaluate().
175
175
  std::vector<ColoredBody> generateTree(const std::vector<std::unique_ptr<CSGNode>>& tree);
176
176
 
177
+ // Best-effort generate over whatever's been resolved SO FAR, for a live
178
+ // partial render while genuinely paused inside a debug hook mid-
179
+ // evaluation (resolveTree()/evaluate() haven't returned yet) -- flattens
180
+ // treeStack_ across every nesting level and runs the same bottom-up
181
+ // generate walk generateTree() uses, via generateTreeImpl(), over
182
+ // non-owning pointers into nodes treeStack_ (not this call) still owns.
183
+ // Safe to call mid-resolve: the resolve pass never reads CSGNode::bodies,
184
+ // only ever writes kind/params/children, so populating .bodies early
185
+ // here cannot corrupt or race the resolve that's paused around this
186
+ // call -- and if a ManifoldCache is set, the real generateTree() call at
187
+ // the end of evaluate() gets a cache hit for any subtree this already
188
+ // generated, so a debug session pays no repeated Manifold work session-
189
+ // wide (see manifold_cache.hpp's own doc comment on this exact use
190
+ // case). Only meaningful to call synchronously from within a
191
+ // DebugHookFn/ErrorBreakFn callback; the caller must not retain a
192
+ // reference into anything treeStack_ owns past that callback's return.
193
+ // Mirrors the reference's `ev.generate_tree(partial_nodes)` called from
194
+ // `_generate_partial_render`.
195
+ std::vector<ColoredBody> generatePartialTree();
196
+
177
197
  // Provenance tables populated by tagGenerated() during generate --
178
198
  // originalID -> the AST node that produced it / that node's own
179
199
  // resolved color. Public, read by a caller (CLI, WYSIWYG picking) after
@@ -303,13 +323,16 @@ public:
303
323
  // filtering, which lives entirely in the injected hook, not here) --
304
324
  // it's simply passed through so the hook itself can distinguish an
305
325
  // unconditional breakpoint()/function-entry pause from a normal
306
- // statement-boundary check. Public: called from evalChildren() (every
307
- // ordinary statement), evalUserFunction()/evalFunctionLiteral() (function
308
- // body entry), and builtins/control.cpp's resolveBreakpoint (a free
309
- // function). See debug_hooks.hpp's DebugHookFn doc comment for the
310
- // exact statement-level-only scope decision. Mirrors
311
- // Evaluator._check_debug.
312
- void checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool forced = false);
326
+ // statement-boundary check. `exprLevel=true` marks a sub-statement
327
+ // (expression-granularity) checkpoint that a stepping debugger should
328
+ // not treat as a statement boundary -- see debug_hooks.hpp's
329
+ // DebugHookFn doc comment for the full list of call sites and the
330
+ // exprLevel contract. Public: called from evalChildren() (every
331
+ // ordinary statement), evalUserFunction()/evalFunctionLiteral()
332
+ // (function body entry), and builtins/control.cpp's
333
+ // resolveBreakpoint/resolveIntersectionFor (free functions). Mirrors
334
+ // Evaluator._check_debug, including its parameter defaults.
335
+ void checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool forced = false, bool exprLevel = false);
313
336
 
314
337
  // (origin, line) for each top-level, non-declaration child of the
315
338
  // node checkDebug() was just called with -- i.e., if that node is a
@@ -357,6 +380,12 @@ private:
357
380
  std::vector<ColoredBody> evaluateImpl(const NodeList& nodes, EvalContext& ctx,
358
381
  const std::unordered_map<std::string, Value>& viewportParams);
359
382
 
383
+ // Shared bottom-up generate walk behind generateTree()/generatePartialTree()
384
+ // -- see generatePartialTree()'s own doc comment and this function's
385
+ // definition (csg_generate.cpp) for why non-owning pointers, not
386
+ // unique_ptr, let both callers share one implementation.
387
+ std::vector<ColoredBody> generateTreeImpl(const std::vector<CSGNode*>& tree);
388
+
360
389
  // Evaluates one top-level element of a `[...]` list literal (a plain
361
390
  // expression, or one of the 6 list-comprehension clause kinds --
362
391
  // for/c-for/let/if/if-else/each), appending whatever it contributes to
@@ -763,6 +792,17 @@ private:
763
792
  // strategy this enables).
764
793
  static bool bytecodeVmEnabled();
765
794
 
795
+ // Per-evaluator gate actually consulted before using a compiled chunk.
796
+ // Compiled bytecode has no per-AST-node debug checkpoints (its whole
797
+ // point is to flatten those nodes away), so a function body run on the
798
+ // VM would silently skip every checkDebug() call site the interpreter
799
+ // path makes -- ternary branches, let()/echo()/assert() expression
800
+ // forms, list-comprehension clauses, nested call sites. Debugging
801
+ // therefore always takes the interpreter path, where the port matches
802
+ // the reference's _check_debug placement exactly. Costs nothing when
803
+ // no debugger is attached, which is every non-debug render.
804
+ bool useBytecodeVm() const { return !debugHooks_.debugHook && bytecodeVmEnabled(); }
805
+
766
806
  // -- Profiling (Phase 9) --------------------------------------------
767
807
 
768
808
  // (kind, name, callOrigin, callLine) -- the exact call-site identity a
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -17,7 +17,7 @@ from ._openscad_cpp_evaluator import ManifoldCache
17
17
 
18
18
  __all__ = [
19
19
  "Evaluator", "ColoredBody", "EvalError", "ParseError", "OscObject", "parse", "to_renderable_bodies",
20
- "ManifoldCache", "CallSiteProfile", "ProfileResult", "format_csg_tree",
20
+ "ManifoldCache", "CallSiteProfile", "ProfileResult", "format_csg_tree", "bodies_from_dicts",
21
21
  ]
22
22
 
23
23
 
@@ -132,7 +132,7 @@ class _CSGNode:
132
132
  def __init__(self, kind, params, bodies, is_builtin, children):
133
133
  self.kind = kind
134
134
  self.params = params
135
- self.bodies = [ColoredBody(_BodyShim(d), d["color"], None, d["flat_preview"]) for d in bodies]
135
+ self.bodies = bodies_from_dicts(bodies)
136
136
  self.is_builtin = is_builtin
137
137
  self.children = children
138
138
 
@@ -289,6 +289,15 @@ class ColoredBody:
289
289
  flat_preview: bool = False
290
290
 
291
291
 
292
+ def bodies_from_dicts(body_dicts) -> list:
293
+ """Convert raw body-dicts (the shape both evaluate()/debug_evaluate() and
294
+ a debug hook's `generate_partial()` callable return) to ColoredBody
295
+ instances. Shared by Evaluator.evaluate() and a debug session's own live
296
+ partial-render (see debug_evaluate()'s `generate_partial` hook kwarg,
297
+ module.cpp) so both paths convert identically."""
298
+ return [ColoredBody(_BodyShim(d), d["color"], None, d["flat_preview"]) for d in body_dicts]
299
+
300
+
292
301
  class Evaluator:
293
302
  """Drop-in for belfryscad.engine.evaluator.Evaluator, backed by C++.
294
303
 
@@ -355,7 +364,7 @@ class Evaluator:
355
364
  raise
356
365
  except Exception as e: # ParseError/EvalError from C++ arrive as RuntimeError
357
366
  raise EvalError(str(e)) from e
358
- bodies = [ColoredBody(_BodyShim(d), d["color"], None, d["flat_preview"]) for d in body_dicts]
367
+ bodies = bodies_from_dicts(body_dicts)
359
368
  # originalID -> a node with `.position` (start/end offsets) for WYSIWYG
360
369
  # picking and gizmo write-back.
361
370
  id_to_node = {
@@ -112,6 +112,11 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
112
112
 
113
113
  std::function<void(size_t, EvalContext&)> recurse = [&](size_t depth, EvalContext& parentCtx) {
114
114
  if (depth == varSeqs.size()) {
115
+ // One body-entry marker per full cartesian-product iteration
116
+ // and nothing per individual variable binding (unlike
117
+ // evalFor) -- mirrors _resolve_intersection_for's single
118
+ // `_check_debug(body_node[0], loop_ctx, expr_level=True)`.
119
+ if (!bodyNodes.empty()) ev.checkDebug(*bodyNodes.front(), parentCtx, /*forced=*/false, /*exprLevel=*/true);
115
120
  const size_t before = ev.currentTreeFrameSize();
116
121
  ev.evalChildren(bodyNodes, parentCtx);
117
122
  const size_t after = ev.currentTreeFrameSize();
@@ -22,10 +22,18 @@ std::vector<ColoredBody> flattenCsgTree(const std::vector<std::unique_ptr<CSGNod
22
22
  return result;
23
23
  }
24
24
 
25
- std::vector<ColoredBody> Evaluator::generateTree(const std::vector<std::unique_ptr<CSGNode>>& tree) {
25
+ // Shared bottom-up walk behind generateTree() (owning unique_ptr tree) and
26
+ // generatePartialTree() (a live, still-being-resolved treeStack_ level,
27
+ // which the resolve pass -- not this function -- continues to own): takes
28
+ // non-owning CSGNode* so both callers share one implementation without
29
+ // either transferring ownership. unique_ptr's constness doesn't propagate
30
+ // to the pointee, so mutating node.bodies here is legal even from a const
31
+ // vector<unique_ptr<CSGNode>>& (see csg_node.hpp) -- this overload just
32
+ // makes that non-owning access explicit for the treeStack_ caller too.
33
+ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>& tree) {
26
34
  std::vector<ColoredBody> topLevelBodies;
27
35
  const auto& dispatch = generateDispatch();
28
- for (const auto& nodePtr : tree) {
36
+ for (CSGNode* nodePtr : tree) {
29
37
  CSGNode& node = *nodePtr;
30
38
 
31
39
  // ManifoldCache lookup: node.uncacheable (rands() taint, set
@@ -43,10 +51,11 @@ std::vector<ColoredBody> Evaluator::generateTree(const std::vector<std::unique_p
43
51
  } else {
44
52
  // Recurse into children first (bottom-up) -- populates each
45
53
  // child's own .bodies in place so flattenCsgTree/a GenerateFn
46
- // can read them back. unique_ptr's constness doesn't propagate
47
- // to the pointee, so this mutation is legal even though `tree`
48
- // is a const& (see csg_node.hpp).
49
- generateTree(node.children);
54
+ // can read them back.
55
+ std::vector<CSGNode*> childPtrs;
56
+ childPtrs.reserve(node.children.size());
57
+ for (const std::unique_ptr<CSGNode>& c : node.children) childPtrs.push_back(c.get());
58
+ generateTreeImpl(childPtrs);
50
59
 
51
60
  auto it = dispatch.find(node.kind);
52
61
  if (node.isBuiltin && it != dispatch.end()) {
@@ -66,6 +75,28 @@ std::vector<ColoredBody> Evaluator::generateTree(const std::vector<std::unique_p
66
75
  return topLevelBodies;
67
76
  }
68
77
 
78
+ std::vector<ColoredBody> Evaluator::generateTree(const std::vector<std::unique_ptr<CSGNode>>& tree) {
79
+ std::vector<CSGNode*> ptrs;
80
+ ptrs.reserve(tree.size());
81
+ for (const std::unique_ptr<CSGNode>& n : tree) ptrs.push_back(n.get());
82
+ return generateTreeImpl(ptrs);
83
+ }
84
+
85
+ std::vector<ColoredBody> Evaluator::generatePartialTree() {
86
+ // Flatten treeStack_ across every nesting level (mirrors the reference's
87
+ // `[node for level in ev._tree_stack for node in level]`): a CSGNode
88
+ // only lands in its parent's own accumulator once the parent's resolve
89
+ // finishes, so for a script whose whole geometry is one deeply-nested
90
+ // top-level statement, the *top* of the tree stays empty for the entire
91
+ // time spent stepping through its innermost leaves -- flattening every
92
+ // level picks up already-resolved leaves (e.g. a finished cube() sitting
93
+ // in a still-in-progress union()'s accumulator) regardless of how deep.
94
+ std::vector<CSGNode*> flat;
95
+ for (const std::vector<std::unique_ptr<CSGNode>>& level : treeStack_)
96
+ for (const std::unique_ptr<CSGNode>& node : level) flat.push_back(node.get());
97
+ return generateTreeImpl(flat);
98
+ }
99
+
69
100
  ColoredBody Evaluator::tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue) {
70
101
  manifold::MeshGL mesh = body.GetMeshGL();
71
102
  std::optional<std::array<float, 4>> color = valueToColor(colorValue);
@@ -140,6 +140,16 @@ void Evaluator::evalModifier(const oscad::ModuleInstantiation& child, const osca
140
140
  // always empty (role tagging happens entirely in the matching
141
141
  // generate function -- generateHighlight/Background/ShowOnly).
142
142
  buildTreeNode(kind, wrapperNode, [&]() {
143
+ // The wrapped child gets its own statement-level stop, in addition
144
+ // to the modifier node's (which evalChildren already fired before
145
+ // dispatching here) -- so `#cube(1);` pauses twice, like the
146
+ // reference. There, _resolve_modifier_child has no _check_debug of
147
+ // its own: it calls _eval_statement(node.child, ctx), and _that_
148
+ // dispatcher independently checks every _TREE_NODE_TYPES node it
149
+ // is handed. This port's evalStatement has no such blanket check
150
+ // (evalChildren owns it), so the equivalent stop is made explicit
151
+ // here.
152
+ checkDebug(child, ctx);
143
153
  evalStatement(child, ctx);
144
154
  return CSGParams{};
145
155
  });
@@ -425,18 +425,24 @@ bool DebugRepl::runPrompt() {
425
425
  }
426
426
  }
427
427
 
428
- DebugAction DebugRepl::debugHook(int line, int depth, bool forced, const std::string& origin,
428
+ DebugAction DebugRepl::debugHook(int line, int depth, bool forced, bool exprLevel, const std::string& origin,
429
429
  const std::vector<CallStackFrame>& callStack, const DebugFramesFn& getFrame) {
430
430
  if (quit_) return DebugAction{true, {}};
431
431
 
432
+ // Every step/breakpoint/first-statement decision below is gated on
433
+ // !exprLevel -- a sub-expression marker (chosen ternary/if branch,
434
+ // loop body entry, list-comprehension body element) is not a statement
435
+ // this REPL steps to or breaks on. Only `forced` (breakpoint()) and an
436
+ // async pause request ignore it. Mirrors _debug_repl.py's debug_hook
437
+ // exactly, clause for clause.
432
438
  const std::string resolved = resolveOrigin(origin);
433
439
  bool stepHit = false;
434
440
  if (stepCmd_ == "over") {
435
- stepHit = depth <= stepDepth_ && resolved == stepOrigin_ && line != stepLine_;
441
+ stepHit = depth <= stepDepth_ && resolved == stepOrigin_ && line != stepLine_ && !exprLevel;
436
442
  } else if (stepCmd_ == "into") {
437
- stepHit = line != stepLine_ || resolved != stepOrigin_;
443
+ stepHit = (line != stepLine_ || resolved != stepOrigin_) && !exprLevel;
438
444
  } else if (stepCmd_ == "out") {
439
- stepHit = depth < stepDepth_;
445
+ stepHit = depth < stepDepth_ && !exprLevel;
440
446
  } else if (stepCmd_ == "to_child") {
441
447
  // Pause the first time control reaches one of the paused call's
442
448
  // own children (wherever children()/children(N) forwards to
@@ -444,7 +450,7 @@ DebugAction DebugRepl::debugHook(int line, int depth, bool forced, const std::st
444
450
  // back to the same "call returned" safety net step-out uses, so
445
451
  // this can never hang. Mirrors BelfrySCAD's DebugSession::
446
452
  // _make_hook exactly.
447
- stepHit = stepToChildTargets_.count({resolved, line}) > 0 || depth < stepDepth_;
453
+ stepHit = !exprLevel && (stepToChildTargets_.count({resolved, line}) > 0 || depth < stepDepth_);
448
454
  }
449
455
 
450
456
  // Read-and-clear, same as BelfrySCAD's own DebugSession.pause()/
@@ -454,8 +460,8 @@ DebugAction DebugRepl::debugHook(int line, int depth, bool forced, const std::st
454
460
  // immediate re-pause; a harmless quirk, not a hang or crash.
455
461
  const bool pauseRequested = pauseRequested_.exchange(false, std::memory_order_relaxed);
456
462
 
457
- const bool shouldPause = forced || pauseRequested || (breakOnFirst_ && resolved == sourcePath_) ||
458
- breakpoints_[resolved].count(line) > 0 || stepHit;
463
+ const bool shouldPause = forced || pauseRequested || (breakOnFirst_ && !exprLevel && resolved == sourcePath_) ||
464
+ (breakpoints_[resolved].count(line) > 0 && !exprLevel) || stepHit;
459
465
  if (!shouldPause) return DebugAction{};
460
466
 
461
467
  breakOnFirst_ = false;
@@ -74,14 +74,14 @@ std::vector<DebugFrame> Evaluator::buildDebugFrames(const EvalContext* ctx) cons
74
74
  return frames;
75
75
  }
76
76
 
77
- void Evaluator::checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool forced) {
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
80
  const int depth = static_cast<int>(callStack_.size());
81
81
  const DebugFramesFn getFrame = [this, &ctx]() { return buildDebugFrames(&ctx); };
82
82
  lastChildrenPositions_ = childStatementPositions(node);
83
83
 
84
- DebugAction action = debugHooks_.debugHook(pos.line, depth, forced, pos.origin, callStack_, getFrame);
84
+ DebugAction action = debugHooks_.debugHook(pos.line, depth, forced, exprLevel, pos.origin, callStack_, getFrame);
85
85
  for (auto& [k, v] : action.mods) ctx.let_->set(k, v);
86
86
  if (action.stop) throw EvalError(kDebuggingStoppedMessage);
87
87
  }