openscad-cpp-evaluator 0.3.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.3.0 → openscad_cpp_evaluator-0.4.0}/CLAUDE.md +26 -9
  2. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/bindings/module.cpp +32 -8
  4. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/examples/minimal_debugger.cpp +3 -3
  5. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +22 -16
  6. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/debug_repl.hpp +1 -1
  7. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/evaluator.hpp +21 -7
  8. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/pyproject.toml +1 -1
  9. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/control.cpp +5 -0
  10. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/csg_resolve.cpp +10 -0
  11. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/debug/debug_repl.cpp +13 -7
  12. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/debug_profile.cpp +2 -2
  13. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/expr_eval.cpp +54 -6
  14. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/stmt_eval.cpp +40 -15
  15. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/user_calls.cpp +24 -6
  16. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_cli.cpp +1 -1
  17. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_control_flow.cpp +2 -2
  18. openscad_cpp_evaluator-0.4.0/tests/test_debug_hooks.cpp +422 -0
  19. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_python_bindings.py +27 -3
  20. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_tail_calls.cpp +22 -10
  21. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/cli_lib.cpp +2 -2
  22. openscad_cpp_evaluator-0.3.0/tests/test_debug_hooks.cpp +0 -170
  23. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/.github/workflows/ci.yml +0 -0
  24. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/.github/workflows/wheels.yml +0 -0
  25. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/.gitignore +0 -0
  26. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/.gitmodules +0 -0
  27. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/LICENSE +0 -0
  29. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/README.md +0 -0
  30. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/bindings/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/examples/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/examples/eval_perf_benchmark.cpp +0 -0
  33. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/examples/manifold_cache_reuse.cpp +0 -0
  34. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  35. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/.gitignore +0 -0
  36. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  37. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  38. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/README.md +0 -0
  39. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  40. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  41. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  43. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  44. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  45. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  46. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  47. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  48. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  49. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  50. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  51. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  52. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  53. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  54. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  55. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  56. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  57. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  58. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  59. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  60. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  61. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  62. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  63. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  64. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  65. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  66. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  67. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  68. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  69. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  70. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  71. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  72. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  73. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  74. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  75. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  76. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  77. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  78. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  79. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  80. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  81. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  82. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  83. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  84. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  85. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  86. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  87. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  88. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  89. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  90. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  91. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  92. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  93. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  94. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  95. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  96. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  97. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  98. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  99. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  100. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  101. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bundled_font_path.hpp.in +0 -0
  102. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  103. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  104. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  105. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  106. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  107. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  108. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  109. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  115. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  117. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  118. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  119. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  120. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  121. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  122. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  123. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  124. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  125. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  126. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  127. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  128. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  129. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  130. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/resources/fonts/LICENSE +0 -0
  131. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  132. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/CMakeLists.txt +0 -0
  133. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/booleans.cpp +0 -0
  134. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/builtins.hpp +0 -0
  135. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/call_args.cpp +0 -0
  136. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/color.cpp +0 -0
  137. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/extrude.cpp +0 -0
  138. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/function_builtins.cpp +0 -0
  139. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/import.cpp +0 -0
  140. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/modifiers.cpp +0 -0
  141. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/primitives_2d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/primitives_3d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/builtins/transforms.cpp +0 -0
  150. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/bytecode_compiler.cpp +0 -0
  151. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/bytecode_vm.cpp +0 -0
  152. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/colored_body.cpp +0 -0
  153. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/csg_generate.cpp +0 -0
  154. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/css_colors.cpp +0 -0
  155. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/eval_context.cpp +0 -0
  156. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/eval_error.cpp +0 -0
  157. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/eval_use.cpp +0 -0
  158. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/export.cpp +0 -0
  159. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/import/dxf_import.cpp +0 -0
  160. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/import/mesh_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/import/surface_load.cpp +0 -0
  162. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/import/svg_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/manifold_cache.cpp +0 -0
  164. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/text/stb_font_provider.cpp +0 -0
  165. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/text/text_metrics.cpp +0 -0
  166. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/value.cpp +0 -0
  167. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/src/zip_stored.cpp +0 -0
  168. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/CMakeLists.txt +0 -0
  169. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_booleans.cpp +0 -0
  170. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_bytecode_compiler.cpp +0 -0
  171. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_csg_tree.cpp +0 -0
  172. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_dxf_svg_import.cpp +0 -0
  173. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_eval_context.cpp +0 -0
  174. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_export.cpp +0 -0
  175. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_expr_eval.cpp +0 -0
  176. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_extrude_roof.cpp +0 -0
  177. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_function_builtins.cpp +0 -0
  178. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_helpers.hpp +0 -0
  179. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_import_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_manifold_cache.cpp +0 -0
  181. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_multi_color_merge.cpp +0 -0
  182. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_primitives.cpp +0 -0
  183. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_profiling.cpp +0 -0
  184. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_scoping.cpp +0 -0
  185. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_surface.cpp +0 -0
  186. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_text.cpp +0 -0
  187. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_transforms.cpp +0 -0
  188. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_use.cpp +0 -0
  189. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_value.cpp +0 -0
  190. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_viewport_params.cpp +0 -0
  191. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tests/test_zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/CMakeLists.txt +0 -0
  193. {openscad_cpp_evaluator-0.3.0 → openscad_cpp_evaluator-0.4.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.3.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.3.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
@@ -313,13 +313,21 @@ nb::object generatePartialTrampoline(const GeneratePartialFn& generatePartial) {
313
313
  }
314
314
 
315
315
  // Trampoline: C++ debug hook -> the Python DebugSession hook. Runs with the
316
- // GIL held (reacquired by the caller). `getFrame`/`callStack`/`generatePartial`
317
- // are valid only for this synchronous call, so the get_frames/generate_partial
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
318
324
  // closures are only ever invoked from within the Python hook, before it returns.
319
- oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, bool forced, const std::string& origin,
325
+ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, bool forced, bool exprLevel,
326
+ const std::string& origin,
320
327
  const std::vector<oscadeval::CallStackFrame>& callStack,
321
328
  const oscadeval::DebugFramesFn& getFrame,
322
- const GeneratePartialFn& generatePartial) {
329
+ const GeneratePartialFn& generatePartial,
330
+ const GetChildrenPositionsFn& getChildrenPositions) {
323
331
  auto getFramesPy = nb::cpp_function([&callStack, &getFrame]() -> nb::object {
324
332
  std::vector<oscadeval::DebugFrame> frames = getFrame();
325
333
  nb::list allFrameLocals;
@@ -335,10 +343,21 @@ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, boo
335
343
  return nb::make_tuple(nb::make_tuple(lastLocals, allFrameLocals), callStackToPy(callStack));
336
344
  });
337
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
+ });
338
353
 
339
- nb::object ret = hook(line, depth, nb::arg("forced") = forced, nb::arg("expr_level") = false,
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,
340
358
  nb::arg("expr_depth") = 0, nb::arg("origin") = origin, nb::arg("get_frames") = getFramesPy,
341
- nb::arg("generate_partial") = generatePartialPy);
359
+ nb::arg("generate_partial") = generatePartialPy,
360
+ nb::arg("get_children_positions") = getChildrenPositionsPy);
342
361
  nb::tuple t = nb::cast<nb::tuple>(ret);
343
362
  oscadeval::DebugAction action;
344
363
  action.stop = (nb::cast<std::string>(t[0]) == "stop");
@@ -399,12 +418,17 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
399
418
  GeneratePartialFn generatePartial = [&evPtr]() -> std::vector<oscadeval::ColoredBody> {
400
419
  return evPtr->generatePartialTree();
401
420
  };
421
+ GetChildrenPositionsFn getChildrenPositions =
422
+ [&evPtr]() -> const std::optional<std::vector<std::pair<std::string, int>>>& {
423
+ return evPtr->lastChildrenPositions();
424
+ };
402
425
  oscadeval::DebugHooks hooks;
403
- 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,
404
427
  const std::vector<oscadeval::CallStackFrame>& cs,
405
428
  const oscadeval::DebugFramesFn& gf) -> oscadeval::DebugAction {
406
429
  nb::gil_scoped_acquire g;
407
- return callPyDebugHook(debugHook, line, depth, forced, origin, cs, gf, generatePartial);
430
+ return callPyDebugHook(debugHook, line, depth, forced, exprLevel, origin, cs, gf, generatePartial,
431
+ getChildrenPositions);
408
432
  };
409
433
  hooks.errorBreak = [&](int line, const std::string& header, const std::string& origin,
410
434
  const std::vector<oscadeval::CallStackFrame>& cs, const oscadeval::DebugFramesFn& gf) {
@@ -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);
@@ -323,13 +323,16 @@ public:
323
323
  // filtering, which lives entirely in the injected hook, not here) --
324
324
  // it's simply passed through so the hook itself can distinguish an
325
325
  // unconditional breakpoint()/function-entry pause from a normal
326
- // statement-boundary check. Public: called from evalChildren() (every
327
- // ordinary statement), evalUserFunction()/evalFunctionLiteral() (function
328
- // body entry), and builtins/control.cpp's resolveBreakpoint (a free
329
- // function). See debug_hooks.hpp's DebugHookFn doc comment for the
330
- // exact statement-level-only scope decision. Mirrors
331
- // Evaluator._check_debug.
332
- 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);
333
336
 
334
337
  // (origin, line) for each top-level, non-declaration child of the
335
338
  // node checkDebug() was just called with -- i.e., if that node is a
@@ -789,6 +792,17 @@ private:
789
792
  // strategy this enables).
790
793
  static bool bytecodeVmEnabled();
791
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
+
792
806
  // -- Profiling (Phase 9) --------------------------------------------
793
807
 
794
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.3.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"
@@ -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();
@@ -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
  }
@@ -119,6 +119,11 @@ Value Evaluator::evalIdentifier(const std::string& name, const oscad::Position*
119
119
 
120
120
  // -- List comprehensions ----------------------------------------------
121
121
 
122
+ // Debug checkpoints below mirror the reference's _eval_list_comp AND its
123
+ // near-duplicate twin _eval_list_comp_body (both dispatch over the same
124
+ // ListComp* kinds with identical _check_debug placement; this port folds
125
+ // them into this one function plus evalListCompBody's ListComprehension
126
+ // early-out, so one set of calls covers both).
122
127
  void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, std::vector<Value>& out) {
123
128
  switch (elem.kind()) {
124
129
  case oscad::NodeKind::ListCompFor: {
@@ -147,6 +152,11 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
147
152
  for (const Value& val : pairs[depth].values) {
148
153
  EvalContext childCtx = parentCtx.letChildCtx();
149
154
  childCtx.let_->set(pairs[depth].name, val);
155
+ // Per-binding stop, same shape as evalFor's -- but with
156
+ // NO separate body-entry marker at depth == pairs.size()
157
+ // (_eval_listcomp_for has none; the body's own element
158
+ // check below supplies the expr-level stop instead).
159
+ checkDebug(*n.assignments[depth], childCtx);
150
160
  recurse(depth + 1, childCtx);
151
161
  }
152
162
  };
@@ -154,47 +164,73 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
154
164
  return;
155
165
  }
156
166
  case oscad::NodeKind::ListCompCFor: {
167
+ // Stop order per _eval_listcomp_cfor, exactly:
168
+ // init(s) -> [cond(true) -> body -> incr(s)]* -> cond(false)
169
+ // The condition is checked (expr-level) on EVERY pass including
170
+ // the final false one that ends the loop; body entry uses the
171
+ // whole ListCompCFor node, not the body expression.
157
172
  auto& n = static_cast<const oscad::ListCompCFor&>(elem);
158
173
  EvalContext loopCtx = ctx.letChildCtx();
159
- for (const auto& assign : n.inits) loopCtx.let_->set(assign->name->name, evalExpr(*assign->expr, loopCtx));
174
+ for (const auto& assign : n.inits) {
175
+ checkDebug(*assign, loopCtx);
176
+ loopCtx.let_->set(assign->name->name, evalExpr(*assign->expr, loopCtx));
177
+ }
160
178
 
161
179
  const bool isNestedLc = (n.body->kind() == oscad::NodeKind::ListComprehension);
162
180
  constexpr int kMaxCForIterations = 1'000'000;
163
181
  int iterations = 0;
164
- while (truthy(evalExpr(*n.condition, loopCtx))) {
182
+ while (true) {
183
+ checkDebug(*n.condition, loopCtx, /*forced=*/false, /*exprLevel=*/true);
184
+ if (!truthy(evalExpr(*n.condition, loopCtx))) break;
165
185
  ++iterations;
166
186
  if (iterations > kMaxCForIterations) {
167
187
  error("C-style for loop exceeded maximum iteration count", n);
168
188
  }
189
+ checkDebug(n, loopCtx);
169
190
  if (isNestedLc) {
170
191
  out.push_back(evalListLiteral(static_cast<const oscad::ListComprehension&>(*n.body), loopCtx));
171
192
  } else {
172
193
  appendAll(out, evalListCompBody(*n.body, loopCtx));
173
194
  }
174
- for (const auto& assign : n.incrs) loopCtx.let_->set(assign->name->name, evalExpr(*assign->expr, loopCtx));
195
+ for (const auto& assign : n.incrs) {
196
+ checkDebug(*assign, loopCtx);
197
+ loopCtx.let_->set(assign->name->name, evalExpr(*assign->expr, loopCtx));
198
+ }
175
199
  }
176
200
  return;
177
201
  }
178
202
  case oscad::NodeKind::ListCompIf: {
179
203
  auto& n = static_cast<const oscad::ListCompIf&>(elem);
180
- if (truthy(evalExpr(*n.condition, ctx))) appendAll(out, evalListCompBody(*n.trueExpr, ctx));
204
+ checkDebug(n, ctx);
205
+ if (truthy(evalExpr(*n.condition, ctx))) {
206
+ checkDebug(*n.trueExpr, ctx, /*forced=*/false, /*exprLevel=*/true);
207
+ appendAll(out, evalListCompBody(*n.trueExpr, ctx));
208
+ }
181
209
  return;
182
210
  }
183
211
  case oscad::NodeKind::ListCompIfElse: {
184
212
  auto& n = static_cast<const oscad::ListCompIfElse&>(elem);
213
+ checkDebug(n, ctx);
185
214
  const oscad::ASTNode& branch = truthy(evalExpr(*n.condition, ctx)) ? *n.trueExpr : *n.falseExpr;
215
+ checkDebug(branch, ctx, /*forced=*/false, /*exprLevel=*/true);
186
216
  appendAll(out, evalListCompBody(branch, ctx));
187
217
  return;
188
218
  }
189
219
  case oscad::NodeKind::ListCompLet: {
190
220
  auto& n = static_cast<const oscad::ListCompLet&>(elem);
191
221
  EvalContext letCtx = ctx.letChildCtx();
192
- for (const auto& assign : n.assignments) bindLetName(letCtx, assign->name->name, evalExpr(*assign->expr, letCtx));
222
+ // Per-assignment stops only -- no expr-level body marker here,
223
+ // unlike the if/if-else/each clauses (matches the reference).
224
+ for (const auto& assign : n.assignments) {
225
+ checkDebug(*assign, letCtx);
226
+ bindLetName(letCtx, assign->name->name, evalExpr(*assign->expr, letCtx));
227
+ }
193
228
  appendAll(out, evalListCompBody(*n.body, letCtx));
194
229
  return;
195
230
  }
196
231
  case oscad::NodeKind::ListCompEach: {
197
232
  auto& n = static_cast<const oscad::ListCompEach&>(elem);
233
+ checkDebug(n, ctx, /*forced=*/false, /*exprLevel=*/true);
198
234
  const oscad::ASTNode& inner = *n.body;
199
235
  if (isListCompClauseKind(inner.kind())) {
200
236
  for (const Value& item : evalListCompBody(inner, ctx)) appendEachInto(out, item);
@@ -204,6 +240,7 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
204
240
  return;
205
241
  }
206
242
  default:
243
+ checkDebug(elem, ctx, /*forced=*/false, /*exprLevel=*/true);
207
244
  out.push_back(evalExpr(static_cast<const oscad::Expression&>(elem), ctx));
208
245
  return;
209
246
  }
@@ -265,6 +302,9 @@ Value Evaluator::evalLetExpr(const oscad::LetOp& node, EvalContext& ctx) {
265
302
  // Unlike the *statement* form (evalLetBlock), which does not --
266
303
  // see that method's own doc comment for why this isn't a copy/paste
267
304
  // mistake in one direction or the other.
305
+ // Checked against childCtx (not ctx) -- _expr_let does the same,
306
+ // the mirror image of _eval_let_block's `_check_debug(assign, ctx)`.
307
+ checkDebug(*assign, childCtx);
268
308
  Value v = evalExpr(*assign->expr, childCtx);
269
309
  bindLetName(childCtx, assign->name->name, v);
270
310
  }
@@ -272,6 +312,7 @@ Value Evaluator::evalLetExpr(const oscad::LetOp& node, EvalContext& ctx) {
272
312
  }
273
313
 
274
314
  Value Evaluator::evalEchoExpr(const oscad::EchoOp& node, EvalContext& ctx) {
315
+ checkDebug(node, ctx); // _expr_echo
275
316
  doEcho(node.arguments, ctx);
276
317
  return evalExpr(*node.body, ctx);
277
318
  }
@@ -281,6 +322,7 @@ Value Evaluator::evalAssertExpr(const oscad::AssertOp& node, EvalContext& ctx) {
281
322
  // arguments via getArg()/CallArgs, the expression form indexes raw
282
323
  // arguments positionally -- mirrors the reference's _expr_assert
283
324
  // exactly (`raw[0].expr`/`raw[1].expr`, not _get_arg).
325
+ checkDebug(node, ctx); // _expr_assert, before the condition is evaluated
284
326
  const auto& raw = node.arguments;
285
327
  const bool condition = raw.empty() || truthy(evalExpr(*argExpr(*raw[0]), ctx));
286
328
  if (!condition) {
@@ -431,8 +473,14 @@ Value Evaluator::evalExpr(const oscad::Expression& node, EvalContext& ctx) {
431
473
  }
432
474
 
433
475
  case NodeKind::TernaryOp: {
476
+ // Mirrors _expr_ternary: a statement-level stop on the whole
477
+ // ternary before the condition runs, then an expr-level one on
478
+ // whichever branch was chosen, before evaluating it.
434
479
  auto& n = static_cast<const oscad::TernaryOp&>(node);
435
- return truthy(evalExpr(*n.condition, ctx)) ? evalExpr(*n.trueExpr, ctx) : evalExpr(*n.falseExpr, ctx);
480
+ checkDebug(n, ctx);
481
+ const oscad::Expression& branch = truthy(evalExpr(*n.condition, ctx)) ? *n.trueExpr : *n.falseExpr;
482
+ checkDebug(branch, ctx, /*forced=*/false, /*exprLevel=*/true);
483
+ return evalExpr(branch, ctx);
436
484
  }
437
485
 
438
486
  case NodeKind::BitwiseOrOp: {
@@ -95,12 +95,22 @@ void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
95
95
  // `let_`.
96
96
  std::function<void(size_t, EvalContext&)> recurse = [&](size_t depth, EvalContext& parentCtx) {
97
97
  if (depth == pairs.size()) {
98
+ // Per-full-iteration "entering the body" marker, separate from
99
+ // (and before) the body's own per-statement checks in
100
+ // evalChildren -- mirrors _eval_for's
101
+ // `_check_debug(node.body[0], parent_ctx, expr_level=True)`.
102
+ if (!bodyNodes.empty()) checkDebug(*bodyNodes.front(), parentCtx, /*forced=*/false, /*exprLevel=*/true);
98
103
  evalChildren(bodyNodes, parentCtx);
99
104
  return;
100
105
  }
101
106
  for (const Value& val : pairs[depth].values) {
102
107
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
103
108
  childCtx.let_->set(pairs[depth].name, val);
109
+ // One statement-level stop per (bound-so-far) combination, on
110
+ // the loop-variable assignment itself -- so a breakpoint set
111
+ // directly on an `i=[0:2],`/`j=[0:1]` line fires. Mirrors
112
+ // _eval_for's `_check_debug(assign_node, child)`.
113
+ checkDebug(*node.assignments[depth], childCtx);
104
114
  recurse(depth + 1, childCtx);
105
115
  }
106
116
  };
@@ -119,6 +129,9 @@ void Evaluator::evalLetBlock(const oscad::ModularLet& node, EvalContext& ctx) {
119
129
  // _eval_let_block, not an oversight -- it evaluates every RHS against
120
130
  // `ctx` in the loop, only ever writing into `child_ctx`.
121
131
  for (const auto& assign : node.assignments) {
132
+ // Checked against the ORIGINAL ctx (like the RHS eval below), not
133
+ // childCtx -- mirrors _eval_let_block's `_check_debug(assign, ctx)`.
134
+ checkDebug(*assign, ctx);
122
135
  Value v = evalExpr(*assign->expr, ctx);
123
136
  const std::string& name = assign->name->name;
124
137
  if (!name.empty() && name[0] == '$') {
@@ -163,18 +176,25 @@ void Evaluator::evalStatement(const oscad::ASTNode& node, EvalContext& ctx) {
163
176
  case oscad::NodeKind::ModularIntersectionFor:
164
177
  evalIntersectionForNode(static_cast<const oscad::ModularIntersectionFor&>(node), ctx);
165
178
  return;
179
+ // Both if-forms fire a branch-entry marker after the condition and
180
+ // before the branch's own per-statement checks, on the branch's
181
+ // first statement (or the if node itself for an empty branch) --
182
+ // mirrors _eval_statement_impl's
183
+ // `_check_debug(branch[0] if branch else node, ctx, expr_level=True)`.
166
184
  case oscad::NodeKind::ModularIf: {
167
185
  auto& n = static_cast<const oscad::ModularIf&>(node);
168
- if (truthy(evalExpr(*n.condition, ctx))) evalChildren(n.trueBranch, ctx);
186
+ if (truthy(evalExpr(*n.condition, ctx))) {
187
+ checkDebug(n.trueBranch.empty() ? node : *n.trueBranch.front(), ctx, /*forced=*/false,
188
+ /*exprLevel=*/true);
189
+ evalChildren(n.trueBranch, ctx);
190
+ }
169
191
  return;
170
192
  }
171
193
  case oscad::NodeKind::ModularIfElse: {
172
194
  auto& n = static_cast<const oscad::ModularIfElse&>(node);
173
- if (truthy(evalExpr(*n.condition, ctx))) {
174
- evalChildren(n.trueBranch, ctx);
175
- } else {
176
- evalChildren(n.falseBranch, ctx);
177
- }
195
+ const auto& branch = truthy(evalExpr(*n.condition, ctx)) ? n.trueBranch : n.falseBranch;
196
+ checkDebug(branch.empty() ? node : *branch.front(), ctx, /*forced=*/false, /*exprLevel=*/true);
197
+ evalChildren(branch, ctx);
178
198
  return;
179
199
  }
180
200
  case oscad::NodeKind::ModularFor:
@@ -220,15 +240,20 @@ void Evaluator::evalChildren(const std::vector<const oscad::ASTNode*>& children,
220
240
  // always overwrites it before any subsequent read could see a
221
241
  // dangling value.
222
242
  lastCtx_ = &childCtx;
223
- // Every real statement flows through this one call site
224
- // (module/function bodies, for-loop bodies, if/else branches,
225
- // let-block bodies, the top-level script) -- see
226
- // debug_hooks.hpp's DebugHookFn doc comment for why this is
227
- // the port's single statement-level debug checkpoint rather
228
- // than the reference's many. Declarations are pure no-ops at
229
- // statement-eval time (already hoisted into scope), so
230
- // there's nothing useful to pause on.
231
- if (child->kind() != oscad::NodeKind::ModuleDeclaration && child->kind() != oscad::NodeKind::FunctionDeclaration) {
243
+ // The port's statement-level debug checkpoint: every real
244
+ // statement flows through here (module/function bodies,
245
+ // for-loop bodies, if/else branches, let-block bodies, the
246
+ // top-level script). Declarations are pure no-ops at
247
+ // statement-eval time (already hoisted into scope), so there's
248
+ // nothing useful to pause on. ModularLet is excluded for a
249
+ // different reason: the reference's _eval_statement_impl
250
+ // explicitly skips it too (`t is not ModularLet` in its own
251
+ // guard) because evalLetBlock pauses on each of the let's
252
+ // ASSIGNMENTS instead -- checking here as well would produce
253
+ // one extra stop the reference never fires.
254
+ const oscad::NodeKind k = child->kind();
255
+ if (k != oscad::NodeKind::ModuleDeclaration && k != oscad::NodeKind::FunctionDeclaration &&
256
+ k != oscad::NodeKind::ModularLet) {
232
257
  checkDebug(*child, childCtx);
233
258
  }
234
259
  evalStatement(*child, childCtx);