openscad-cpp-evaluator 0.4.10__tar.gz → 0.6.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.4.10 → openscad_cpp_evaluator-0.6.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/bindings/module.cpp +55 -3
  3. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/bytecode.hpp +96 -9
  4. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/evaluator.hpp +67 -1
  5. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/bytecode_compiler.cpp +90 -42
  7. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/bytecode_vm.cpp +28 -0
  8. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/user_calls.cpp +10 -2
  9. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_bytecode_compiler.cpp +235 -21
  10. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_python_bindings.py +4 -4
  11. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_tail_calls.cpp +16 -0
  12. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/.github/workflows/ci.yml +0 -0
  13. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/.github/workflows/wheels.yml +0 -0
  14. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/.gitignore +0 -0
  15. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/.gitmodules +0 -0
  16. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/CLAUDE.md +0 -0
  17. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/LICENSE +0 -0
  19. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/README.md +0 -0
  20. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/bindings/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/cmake/embed_font.cmake +0 -0
  22. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/examples/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/examples/eval_perf_benchmark.cpp +0 -0
  24. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/examples/manifold_cache_reuse.cpp +0 -0
  25. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/examples/minimal_debugger.cpp +0 -0
  26. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  27. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/.gitignore +0 -0
  28. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  29. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/README.md +0 -0
  31. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  32. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  33. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  34. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  35. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  36. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  37. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  38. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  39. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  40. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  41. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  42. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  43. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  44. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  45. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  46. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  47. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  48. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  49. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  50. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  51. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  52. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  53. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  54. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  55. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  56. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  57. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  58. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  59. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  60. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  61. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  62. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  63. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  64. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  65. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  66. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  67. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  69. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  70. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  71. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  72. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  73. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  74. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  75. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  76. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  78. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  79. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  80. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  81. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  82. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  83. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  84. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  85. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  86. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  87. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  88. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  89. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  90. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  91. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  92. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  93. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  94. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  95. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  96. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  97. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  98. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  99. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  100. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  101. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  102. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  103. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  104. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  105. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  106. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  107. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  108. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  109. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  110. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  111. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  112. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  113. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  114. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  115. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  116. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  118. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  119. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  120. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  121. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  122. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/resources/fonts/LICENSE +0 -0
  123. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  124. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/CMakeLists.txt +0 -0
  125. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/booleans.cpp +0 -0
  126. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/builtins.hpp +0 -0
  127. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/call_args.cpp +0 -0
  128. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/color.cpp +0 -0
  129. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/control.cpp +0 -0
  130. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/extrude.cpp +0 -0
  131. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/function_builtins.cpp +0 -0
  132. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/import.cpp +0 -0
  133. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/modifiers.cpp +0 -0
  134. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/primitives_2d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/primitives_3d.cpp +0 -0
  136. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/registry.cpp +0 -0
  137. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/roof.cpp +0 -0
  138. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/segments.cpp +0 -0
  139. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/surface.cpp +0 -0
  140. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/text.cpp +0 -0
  141. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/topology.cpp +0 -0
  142. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/builtins/transforms.cpp +0 -0
  143. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/colored_body.cpp +0 -0
  144. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/csg_generate.cpp +0 -0
  145. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/csg_resolve.cpp +0 -0
  146. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/css_colors.cpp +0 -0
  147. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/debug/debug_repl.cpp +0 -0
  148. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/debug_profile.cpp +0 -0
  149. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/eval_context.cpp +0 -0
  150. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/eval_error.cpp +0 -0
  151. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/eval_use.cpp +0 -0
  152. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/export.cpp +0 -0
  153. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/expr_eval.cpp +0 -0
  154. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/import/dxf_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/import/mesh_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/import/surface_load.cpp +0 -0
  157. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/import/svg_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/manifold_cache.cpp +0 -0
  159. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/stmt_eval.cpp +0 -0
  160. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/text/stb_font_provider.cpp +0 -0
  161. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/text/text_metrics.cpp +0 -0
  162. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/value.cpp +0 -0
  163. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/src/zip_stored.cpp +0 -0
  164. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/CMakeLists.txt +0 -0
  165. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_booleans.cpp +0 -0
  166. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_cli.cpp +0 -0
  167. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_control_flow.cpp +0 -0
  168. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_csg_tree.cpp +0 -0
  169. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_debug_hooks.cpp +0 -0
  170. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_dxf_svg_import.cpp +0 -0
  171. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_eval_context.cpp +0 -0
  172. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_export.cpp +0 -0
  173. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_expr_eval.cpp +0 -0
  174. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_extrude_roof.cpp +0 -0
  175. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_function_builtins.cpp +0 -0
  176. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_helpers.hpp +0 -0
  177. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_import_export.cpp +0 -0
  178. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_multi_color_merge.cpp +0 -0
  180. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_primitives.cpp +0 -0
  181. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_profiling.cpp +0 -0
  182. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_scope_trail.cpp +0 -0
  183. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_scoping.cpp +0 -0
  184. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_surface.cpp +0 -0
  185. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.4.10 → openscad_cpp_evaluator-0.6.0}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.4.10
3
+ Version: 0.6.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -349,6 +349,51 @@ nb::object generatePartialTrampoline(const GeneratePartialFn& generatePartial) {
349
349
  // synchronously within the current hook call, same as get_frames.
350
350
  using GetChildrenPositionsFn = std::function<const std::optional<std::vector<std::pair<std::string, int>>>&()>;
351
351
 
352
+ // Trampoline: lets the Python hook tell the Evaluator it's safe to speed
353
+ // up function calls with the bytecode VM right now -- see
354
+ // Evaluator::setFastContinueBreakpoints's own doc comment (evaluator.hpp)
355
+ // for the exact contract (only when no step is pending/no pause was
356
+ // requested/the initial break-on-first stop is already consumed) and
357
+ // Evaluator::chunkEligibleNow for how the breakpoint set is used (a
358
+ // per-function span check, not a blanket switch). `breakpoints=None`
359
+ // means "no exception right now" -- i.e. every function call runs
360
+ // interpreted, same as if this were never called at all. A callable
361
+ // (not a return-tuple field) so DebugSession's own hook() can call it
362
+ // exactly where it already computes should_pause, without needing to
363
+ // widen hook()'s existing (cmd, mods) return-tuple contract -- mirrors
364
+ // generate_partial/get_children_positions's own "extra kwarg the Python
365
+ // side may or may not call" shape.
366
+ using SetFastContinueFn = std::function<void(std::optional<std::unordered_map<std::string, std::set<int>>>)>;
367
+
368
+ // Wraps a SetFastContinueFn as a Python callable taking either a
369
+ // dict[str, set[int]]/dict[str, list[int]] (origin -> breakpoint lines) or
370
+ // None. Mirrors generatePartialTrampoline's own shape.
371
+ nb::object setFastContinueTrampoline(const SetFastContinueFn& setFastContinue) {
372
+ return nb::cpp_function(
373
+ [&setFastContinue](nb::object breakpoints) {
374
+ if (breakpoints.is_none()) {
375
+ setFastContinue(std::nullopt);
376
+ return;
377
+ }
378
+ std::unordered_map<std::string, std::set<int>> bp;
379
+ for (auto [k, v] : nb::cast<nb::dict>(breakpoints)) {
380
+ std::set<int> lines;
381
+ for (nb::handle line : v) lines.insert(nb::cast<int>(line));
382
+ bp.emplace(nb::cast<std::string>(k), std::move(lines));
383
+ }
384
+ setFastContinue(std::move(bp));
385
+ },
386
+ // Without .none(), nanobind rejects a Python `None` argument for an
387
+ // `nb::object`-typed parameter registered this ad-hoc way (unlike a
388
+ // `.def()`-declared function parameter, which accepts None by
389
+ // default) -- confirmed via a real call: "incompatible function
390
+ // arguments ... Invoked with types: NoneType" the first time this
391
+ // was called with breakpoints=None (DebugSession's own
392
+ // _apply_fast_continue calls it with None whenever fast-continue
393
+ // mode isn't safe right now -- see debugger.py).
394
+ nb::arg("breakpoints").none());
395
+ }
396
+
352
397
  // GIL held (reacquired by the caller). `getFrame`/`callStack`/`generatePartial`/
353
398
  // `getChildrenPositions` are valid only for this synchronous call, so their
354
399
  // closures are only ever invoked from within the Python hook, before it returns.
@@ -357,7 +402,8 @@ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, boo
357
402
  const std::vector<oscadeval::CallStackFrame>& callStack,
358
403
  const oscadeval::DebugFramesFn& getFrame,
359
404
  const GeneratePartialFn& generatePartial,
360
- const GetChildrenPositionsFn& getChildrenPositions) {
405
+ const GetChildrenPositionsFn& getChildrenPositions,
406
+ const SetFastContinueFn& setFastContinue) {
361
407
  auto getFramesPy = nb::cpp_function([&callStack, &getFrame]() -> nb::object {
362
408
  std::vector<oscadeval::DebugFrame> frames = getFrame();
363
409
  nb::list allFrameLocals;
@@ -380,6 +426,7 @@ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, boo
380
426
  for (const auto& [origin, line] : *positions) out.append(nb::make_tuple(origin, line));
381
427
  return out;
382
428
  });
429
+ nb::object setFastContinuePy = setFastContinueTrampoline(setFastContinue);
383
430
 
384
431
  // expr_depth stays hardcoded at 0 -- this port deliberately doesn't
385
432
  // track the reference's _expr_depth counter (no consumer reads it);
@@ -387,7 +434,8 @@ oscadeval::DebugAction callPyDebugHook(nb::handle hook, int line, int depth, boo
387
434
  nb::object ret = hook(line, depth, nb::arg("forced") = forced, nb::arg("expr_level") = exprLevel,
388
435
  nb::arg("expr_depth") = 0, nb::arg("origin") = origin, nb::arg("get_frames") = getFramesPy,
389
436
  nb::arg("generate_partial") = generatePartialPy,
390
- nb::arg("get_children_positions") = getChildrenPositionsPy);
437
+ nb::arg("get_children_positions") = getChildrenPositionsPy,
438
+ nb::arg("set_fast_continue") = setFastContinuePy);
391
439
  nb::tuple t = nb::cast<nb::tuple>(ret);
392
440
  oscadeval::DebugAction action;
393
441
  action.stop = (nb::cast<std::string>(t[0]) == "stop");
@@ -452,13 +500,17 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
452
500
  [&evPtr]() -> const std::optional<std::vector<std::pair<std::string, int>>>& {
453
501
  return evPtr->lastChildrenPositions();
454
502
  };
503
+ SetFastContinueFn setFastContinue =
504
+ [&evPtr](std::optional<std::unordered_map<std::string, std::set<int>>> breakpoints) {
505
+ evPtr->setFastContinueBreakpoints(std::move(breakpoints));
506
+ };
455
507
  oscadeval::DebugHooks hooks;
456
508
  hooks.debugHook = [&](int line, int depth, bool forced, bool exprLevel, const std::string& origin,
457
509
  const std::vector<oscadeval::CallStackFrame>& cs,
458
510
  const oscadeval::DebugFramesFn& gf) -> oscadeval::DebugAction {
459
511
  nb::gil_scoped_acquire g;
460
512
  return callPyDebugHook(debugHook, line, depth, forced, exprLevel, origin, cs, gf, generatePartial,
461
- getChildrenPositions);
513
+ getChildrenPositions, setFastContinue);
462
514
  };
463
515
  hooks.errorBreak = [&](int line, const std::string& header, const std::string& origin,
464
516
  const std::vector<oscadeval::CallStackFrame>& cs, const oscadeval::DebugFramesFn& gf) {
@@ -4,6 +4,7 @@
4
4
 
5
5
  #include "openscad_cpp_parser/ast/ast_node.hpp"
6
6
 
7
+ #include <limits>
7
8
  #include <optional>
8
9
  #include <string>
9
10
  #include <vector>
@@ -99,16 +100,47 @@ enum class Op {
99
100
  // a = index into CompiledChunk::upvalues. At runtime, searches the live
100
101
  // call stack (Evaluator::findUpvalue) for a still-active call of the
101
102
  // upvalue's own target declaration and reads that call's own slot --
102
- // undef if that call already returned (this codebase has no escaping
103
- // closures, matching the interpreter's existing childCtx()-based
104
- // semantics exactly; see scope_trail.hpp's own module comment). Never
105
- // needs a STORE counterpart: a captured variable is always a
106
- // parameter/let-binding of some ENCLOSING, already-executing call,
107
- // and nothing this phase compiles ever writes back into an enclosing
108
- // call's own slot (let/ListCompLet assignments always target a
109
- // freshly-opened scope of their OWN, never a parent's).
103
+ // undef if that call already returned. Never needs a STORE counterpart:
104
+ // a captured variable is always a parameter/let-binding of some
105
+ // ENCLOSING, already-executing call, and nothing this phase compiles
106
+ // ever writes back into an enclosing call's own slot (let/ListCompLet
107
+ // assignments always target a freshly-opened scope of their OWN, never
108
+ // a parent's).
109
+ //
110
+ // NOTE: as of Op::MakeClosure (below), no CompiledChunk that's ever
111
+ // actually registered (chunkCache_/literalChunkCache_) has a non-empty
112
+ // `upvalues` list any more -- a FunctionLiteral with any real capture
113
+ // need (direct or transitively bubbled up from a nested literal, see
114
+ // bytecode_compiler.cpp's FunctionLiteral case) is now handled via
115
+ // Op::MakeClosure instead, and always runs interpreted when invoked
116
+ // (Evaluator::lookupCompiledLiteralChunk finds no entry for it). This
117
+ // opcode and Evaluator::findUpvalue/CallStackFrame::upvalueParent are
118
+ // therefore unreachable dead code today, left in place rather than
119
+ // removed in the same change that made them unreachable -- deleting
120
+ // them is a distinct, lower-risk cleanup, not bundled in here.
110
121
  LoadUpvalue,
111
122
 
123
+ // Escaping-closure support. a = index into CompiledChunk::closureSites.
124
+ // Builds a FRESH captured environment every time this instruction
125
+ // actually runs (never a compile-time constant -- a loop creating one
126
+ // closure per iteration must capture THAT iteration's own values, not
127
+ // share one snapshot) by reading each of the site's own captures
128
+ // straight out of the CURRENTLY EXECUTING frame's own `slots` (see
129
+ // ClosureSite's own doc comment for why every capture, however deeply
130
+ // the literal was originally nested, always resolves against the one
131
+ // chunk actually running Op::MakeClosure), and constructs
132
+ // Closure{node, capturedTrail} -- reusing Closure::capturedLet (value.hpp)
133
+ // and callCtxFor's existing capturedLet-rooting (user_calls.cpp)
134
+ // UNCHANGED: invoking this closure later (always via the interpreter,
135
+ // see ClosureSite's own doc comment for why its body is never itself
136
+ // compiled) works exactly like any interpreter-created escaping closure
137
+ // always has, no new invocation-side code needed at all. Replaces
138
+ // Op::PushConst's frozen `Closure{&n, nullptr}` specifically for a
139
+ // literal whose (transitively merged) capture set is non-empty; a
140
+ // literal that closes over nothing at all -- even transitively -- still
141
+ // takes the cheaper PushConst path, unchanged.
142
+ MakeClosure,
143
+
112
144
  // A call whose callee isn't statically resolvable to a builtin or a
113
145
  // named FunctionDeclaration (see CallFn) -- the callee expression is
114
146
  // compiled to push its own Value first, THEN each argument (so at
@@ -224,10 +256,47 @@ struct CompiledChunk {
224
256
  // inside AT COMPILE TIME (matched at runtime via Evaluator::
225
257
  // findUpvalue's exact-identity search over the live call stack, the
226
258
  // same identity CallStackFrame::declNode is stamped with), `slot` is
227
- // that declaration's own slot number for the captured variable.
259
+ // that declaration's own slot number for the captured variable. `name`
260
+ // is that same variable's source name -- unused by Op::LoadUpvalue
261
+ // itself, needed by Op::MakeClosure's ClosureSite (below), which builds
262
+ // a NAME-keyed captured environment (Closure::capturedLet is a
263
+ // TrailView<Value>, looked up by name at invocation time, exactly like
264
+ // every other escaping closure) rather than a slot-indexed one.
228
265
  struct UpvalueRef {
229
266
  const oscad::ASTNode* targetDecl = nullptr;
230
267
  int slot = 0;
268
+ std::string name;
269
+ };
270
+
271
+ // One FunctionLiteral's own capture list for Op::MakeClosure, computed
272
+ // at compile time (bytecode_compiler.cpp's FunctionLiteral case) as the
273
+ // TRANSITIVE union of: every free variable `node` itself references
274
+ // from outside its own body, PLUS -- for each FunctionLiteral nested
275
+ // anywhere within `node` (however deep) -- whatever THAT nested
276
+ // literal's own ClosureSite still needed from beyond `node`'s own scope
277
+ // (i.e. any capture whose resolution target isn't `node` itself). This
278
+ // "bubbling" is what makes a closure nested inside another compiled-
279
+ // creating closure resolve correctly even though the inner one's body
280
+ // is never itself compiled (see this struct's own body-compilation
281
+ // note below): by the time a real, running CompiledChunk executes
282
+ // Op::MakeClosure for `node`, every one of `captures`'s entries is
283
+ // guaranteed to resolve against THAT SAME running chunk's own `slots`
284
+ // array, regardless of how many literal-within-literal levels `node`
285
+ // was originally nested through in the source.
286
+ //
287
+ // `node`'s own BODY is deliberately never itself compiled/registered
288
+ // once it has any capture need (even an empty one bubbled up from
289
+ // something it contains) -- it always runs via the ordinary AST
290
+ // interpreter when invoked (Evaluator::lookupCompiledLiteralChunk finds
291
+ // no cache entry for it), using Closure::capturedLet exactly like any
292
+ // other escaping closure the interpreter creates directly. This keeps
293
+ // the whole feature to "make closure CREATION safe to compile"; a
294
+ // closure's own body additionally getting compiled too (its captures
295
+ // would need to read from this snapshot instead of a live-stack walk)
296
+ // is a natural follow-on, not attempted here.
297
+ struct ClosureSite {
298
+ const oscad::FunctionLiteral* node = nullptr;
299
+ std::vector<UpvalueRef> captures;
231
300
  };
232
301
 
233
302
  // One echo() expression form's own argument names, in source order --
@@ -245,6 +314,7 @@ struct CompiledChunk {
245
314
  std::vector<std::string> names;
246
315
  std::vector<CallSite> callSites;
247
316
  std::vector<UpvalueRef> upvalues;
317
+ std::vector<ClosureSite> closureSites;
248
318
  std::vector<EchoSite> echoSites;
249
319
  int numSlots = 0;
250
320
  // Count of distinct ListCompFor-assignment iterLists allocated across
@@ -252,6 +322,23 @@ struct CompiledChunk {
252
322
  // sizes its own native iterLists/iterIndex scratch vectors to this.
253
323
  int numIterLists = 0;
254
324
 
325
+ // Source-file/line span this chunk's own body+defaults were compiled
326
+ // from -- the min/max of every AST node's own position().line actually
327
+ // visited during compilation (see Compiler::trackSpan, bytecode_compiler.cpp),
328
+ // not just the declaration's own header line. Used by Evaluator::
329
+ // chunkEligibleNow (debug "fast continue" mode) to decide, per
330
+ // function, whether ANY currently-set breakpoint line could fall
331
+ // inside this chunk's own body -- a plain integer-range check, chosen
332
+ // specifically so that decision never needs the original source text
333
+ // or an offset->line conversion at eligibility-check time (called on
334
+ // every single compiled call, not just once). origin empty / minLine >
335
+ // maxLine (never updated) should not happen for any chunk that
336
+ // actually compiled a real body -- every function has at least one
337
+ // expression to visit.
338
+ std::string origin;
339
+ int minLine = std::numeric_limits<int>::max();
340
+ int maxLine = 0;
341
+
255
342
  // FunctionLiteral chunks discovered while compiling THIS chunk (a
256
343
  // FunctionLiteral expression appearing anywhere in its body/defaults),
257
344
  // recursively containing their own nested literals in turn. The
@@ -315,6 +315,28 @@ public:
315
315
  // the env var.
316
316
  static void setBytecodeVmEnabledForTesting(std::optional<bool> enabled);
317
317
 
318
+ // Tells this Evaluator it's safe to speed up function calls with the
319
+ // bytecode VM even though a debugger is attached, PROVIDED the caller
320
+ // has already verified none of the reasons that's normally unsafe
321
+ // apply right now: no step command (`into`/`over`/`out`/`to_child`)
322
+ // pending, no explicit pause requested, and the session's own initial
323
+ // break-on-first stop already consumed. `breakpoints` is exactly the
324
+ // caller's own current breakpoint set (origin file -> line numbers) --
325
+ // see useBytecodeVm()/chunkEligibleNow's own doc comments for how it's
326
+ // used (a per-function span check, not a blanket switch: a function
327
+ // whose own body could contain one of these lines still always runs
328
+ // interpreted). Call with std::nullopt to go back to "no debugger
329
+ // exception at all" (today's original, always-safe behavior) --
330
+ // e.g. the instant a step command starts or a pause is requested.
331
+ // Cheap to call as often as the debugger's own state changes: this
332
+ // just updates one member, no cache invalidation needed (chunkEligibleNow
333
+ // is re-checked on every lookupOrCompileChunk/lookupCompiledLiteralChunk
334
+ // call, not cached itself -- only the compiled bytecode ITSELF, which
335
+ // never depends on debugger state, is cached forever).
336
+ void setFastContinueBreakpoints(std::optional<std::unordered_map<std::string, std::set<int>>> breakpoints) {
337
+ fastContinueBreakpoints_ = std::move(breakpoints);
338
+ }
339
+
318
340
  // Checks whether a debug pause should happen at `node` (via the
319
341
  // injected DebugHooks::debugHook, if any -- a no-op otherwise),
320
342
  // applying any `mods` the hook returns to `ctx.let_` and throwing
@@ -815,7 +837,46 @@ private:
815
837
  // therefore always takes the interpreter path, where the port matches
816
838
  // the reference's _check_debug placement exactly. Costs nothing when
817
839
  // no debugger is attached, which is every non-debug render.
818
- bool useBytecodeVm() const { return !debugHooks_.debugHook && bytecodeVmEnabled(); }
840
+ //
841
+ // EXCEPT: a caller that knows none of that applies right now -- no
842
+ // step command pending, no explicit pause requested, the session's
843
+ // own initial break-on-first stop already consumed -- can call
844
+ // setFastContinueBreakpoints() with the CURRENT breakpoint set instead
845
+ // of leaving it unset. That alone doesn't turn the VM on for
846
+ // everything; see chunkEligibleNow, which still forces the
847
+ // interpreter for any specific function whose own compiled span could
848
+ // contain one of those lines. This only ever helps a plain "Continue,
849
+ // pause only at a known breakpoint" -- the far more common case in a
850
+ // long debug session than active single-stepping, and exactly the
851
+ // case where "every function pays the interpreter tax because a
852
+ // debugger happens to be attached somewhere" wastes the most time
853
+ // (see docs/debugger.md and this method's own callers for the full
854
+ // story).
855
+ bool useBytecodeVm() const {
856
+ return bytecodeVmEnabled() && (!debugHooks_.debugHook || fastContinueBreakpoints_.has_value());
857
+ }
858
+
859
+ // The fine-grained half of the check above: even when useBytecodeVm()
860
+ // says compiling/using bytecode is on the table at all, a SPECIFIC
861
+ // chunk is only actually safe to run compiled if nothing about it
862
+ // could matter to the debugger right now. No debugger attached at
863
+ // all -- always fine, unaffected by any of this. Debugger attached: only
864
+ // safe in fast-continue mode (useBytecodeVm() already checked that),
865
+ // and only if the chunk's own [minLine, maxLine] span (see
866
+ // CompiledChunk's own doc comment, bytecode.hpp) contains none of the
867
+ // breakpoint lines currently set for its origin file -- a checkpoint
868
+ // for one of those lines could fire mid-body, and compiled code has no
869
+ // way to stop there.
870
+ bool chunkEligibleNow(const CompiledChunk& chunk) const {
871
+ if (!debugHooks_.debugHook) return true;
872
+ if (!fastContinueBreakpoints_) return false;
873
+ auto it = fastContinueBreakpoints_->find(chunk.origin);
874
+ if (it == fastContinueBreakpoints_->end()) return true;
875
+ for (int line : it->second) {
876
+ if (line >= chunk.minLine && line <= chunk.maxLine) return false;
877
+ }
878
+ return true;
879
+ }
819
880
 
820
881
  // -- Profiling (Phase 9) --------------------------------------------
821
882
 
@@ -886,6 +947,11 @@ private:
886
947
  // -- Debugging (Phase 9) --------------------------------------------
887
948
 
888
949
  DebugHooks debugHooks_;
950
+ // See setFastContinueBreakpoints's own doc comment. nullopt (the
951
+ // default, and what a plain debugger attach without ever calling that
952
+ // setter leaves it at) means "no fast-continue exception" -- exactly
953
+ // today's original, always-safe behavior.
954
+ std::optional<std::unordered_map<std::string, std::set<int>>> fastContinueBreakpoints_;
889
955
  // The root EvalContext resolveTree() was called with -- lets
890
956
  // checkDebug()'s locals snapshot fall back to top-level script
891
957
  // variables when paused inside a nested user call, the same way the
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.4.10"
7
+ version = "0.6.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -40,6 +40,33 @@ bool isListCompClauseKind(oscad::NodeKind kind) {
40
40
  }
41
41
  }
42
42
 
43
+ // Merges `site`'s own captures into `out` for every entry that escapes
44
+ // `owner` itself (targetDecl != owner) -- see ClosureSite's own doc comment
45
+ // (bytecode.hpp) for why this "bubbling" is necessary: a literal nested
46
+ // inside `owner` may need something from further out than `owner`'s own
47
+ // scope, and since that inner literal's body is never itself compiled
48
+ // (only ever invoked via the interpreter once it has any capture need),
49
+ // `owner`'s OWN Op::MakeClosure must snapshot that name too -- otherwise
50
+ // the inner literal's interpreter-resolved capturedLet ancestry, rooted at
51
+ // whatever `owner` itself captured, would simply never contain it.
52
+ // Dedupes by (targetDecl, slot): the same enclosing binding reached through
53
+ // two different nested literals (or the same literal referencing it twice)
54
+ // is still one capture to make.
55
+ void bubbleEscapingCaptures(const CompiledChunk::ClosureSite& site, const oscad::ASTNode* owner,
56
+ std::vector<CompiledChunk::UpvalueRef>& out) {
57
+ for (const auto& cap : site.captures) {
58
+ if (cap.targetDecl == owner) continue;
59
+ bool already = false;
60
+ for (const auto& existing : out) {
61
+ if (existing.targetDecl == cap.targetDecl && existing.slot == cap.slot) {
62
+ already = true;
63
+ break;
64
+ }
65
+ }
66
+ if (!already) out.push_back(cap);
67
+ }
68
+ }
69
+
43
70
  // Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
44
71
  // shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
45
72
  // LetOp (pushed/popped around its own assignments+body), innermost frame
@@ -128,6 +155,20 @@ public:
128
155
  return slot;
129
156
  }
130
157
 
158
+ // Widens chunk_'s own [minLine, maxLine] span to include `node` --
159
+ // called at the top of every AST-node-visiting entry point
160
+ // (compileExpr/compileListElement/compileListCompBody) so the chunk's
161
+ // final span covers every line actually compiled into it, not just the
162
+ // declaration's own header line. See CompiledChunk::origin/minLine/
163
+ // maxLine's own doc comment (bytecode.hpp) for why this matters --
164
+ // Evaluator::chunkEligibleNow's debug "fast continue" check.
165
+ void trackSpan(const oscad::ASTNode& node) {
166
+ const oscad::Position& pos = node.position();
167
+ chunk_.origin = pos.origin;
168
+ if (pos.line < chunk_.minLine) chunk_.minLine = pos.line;
169
+ if (pos.line > chunk_.maxLine) chunk_.maxLine = pos.line;
170
+ }
171
+
131
172
  // Walks `enclosing_` from innermost (back()) to outermost, looking for
132
173
  // a level whose own CompileScope resolves `name` -- the first (most
133
174
  // deeply nested, i.e. most recently active at runtime) match wins,
@@ -135,7 +176,7 @@ public:
135
176
  // naturally resolve to the innermost active binding too.
136
177
  std::optional<CompiledChunk::UpvalueRef> resolveEnclosing(const std::string& name) const {
137
178
  for (auto it = enclosing_.rbegin(); it != enclosing_.rend(); ++it) {
138
- if (auto slot = it->scope->resolve(name)) return CompiledChunk::UpvalueRef{it->decl, *slot};
179
+ if (auto slot = it->scope->resolve(name)) return CompiledChunk::UpvalueRef{it->decl, *slot, name};
139
180
  }
140
181
  return std::nullopt;
141
182
  }
@@ -171,6 +212,7 @@ public:
171
212
  void compileExpr(const oscad::Expression& node, std::vector<Instruction>& out, CompileScope& scope,
172
213
  bool tail = false) {
173
214
  using oscad::NodeKind;
215
+ trackSpan(node);
174
216
  switch (node.kind()) {
175
217
  case NodeKind::NumberLiteral:
176
218
  out.push_back({Op::PushConst, internConst(Value{static_cast<const oscad::NumberLiteral&>(node).val}), 0,
@@ -238,20 +280,14 @@ public:
238
280
  // other point where its own free-variable references could be
239
281
  // resolved against this enclosing chain (AST nodes have no
240
282
  // parent pointers to discover it from later, see this file's
241
- // own module notes). If the literal itself fails to compile
242
- // (contains a call/echo/assert/unsupported construct, or its
243
- // own free-variable resolution needs an upvalue chain going
244
- // even deeper than what's captured here), the WHOLE containing
245
- // declaration must also bail -- propagated by simply
246
- // rethrowing, since an interpreter-executed literal nested
247
- // inside a compiled container would silently read stale/undef
248
- // values for the container's own (slot-only, never written to
249
- // ctx.let_) locals otherwise. On success, the literal's own
250
- // chunk is stashed in chunk_.nestedLiterals for
251
- // Evaluator::lookupOrCompileChunk to flatten into
252
- // literalChunkCache_; the emitted instruction just pushes the
253
- // AST pointer as a Value, exactly like the interpreter's own
254
- // `Value{&node}` (evalExpr's own FunctionLiteral case).
283
+ // own module notes). If the literal itself fails to compile as
284
+ // bytecode at all (contains an echo/assert/unsupported
285
+ // construct), the WHOLE containing declaration must also bail
286
+ // -- propagated by simply rethrowing -- since there's no lighter
287
+ // way left to find out what it captures (see
288
+ // bubbleEscapingCaptures' own doc comment for why a literal
289
+ // that DOES compile as bytecode no longer needs this bail just
290
+ // for referencing enclosing state).
255
291
  case NodeKind::FunctionLiteral: {
256
292
  auto& n = static_cast<const oscad::FunctionLiteral&>(node);
257
293
  std::vector<EnclosingLevel> childEnclosing = enclosing_;
@@ -261,33 +297,44 @@ public:
261
297
  *n.body)) {
262
298
  throw NotCompilable{};
263
299
  }
264
- // A nested literal that reads ANY enclosing variable
265
- // (literalChunk.upvalues non-empty) is compiled via
266
- // Op::LoadUpvalue -- a live-call-stack walk (findUpvalue)
267
- // that only ever resolves a still-active enclosing call,
268
- // never a captured environment (see LoadUpvalue/
269
- // findUpvalue's own doc comments: "this codebase has no
270
- // escaping closures"). A closure that escapes its creating
271
- // call (returned, stored, passed on -- see Closure's own
272
- // doc comment, value.hpp, for the motivating BOSL2
273
- // example) would silently read undef for every captured
274
- // variable through this path. Bailing the WHOLE containing
275
- // compilation here forces such a function to run through
276
- // the interpreter instead, which DOES support escaping
277
- // closures correctly (evalExpr's FunctionLiteral case
278
- // captures ctx.let_ itself). A literal with no upvalues at
279
- // all (doesn't reference anything from an enclosing scope)
280
- // has nothing that needs escaping-capture support, so it's
281
- // unaffected and keeps compiling normally.
282
- if (!literalChunk.upvalues.empty()) throw NotCompilable{};
283
- chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
284
- // No captured `let_` trail here (nullptr) -- a compile-time
285
- // constant can't carry per-invocation runtime state, but
286
- // (per the upvalues check just above) this literal doesn't
287
- // reference anything outside itself, so there is nothing
288
- // capture. Not a regression; just not (yet) extended here.
289
- out.push_back({Op::PushConst, internConst(Value{std::make_shared<const Closure>(Closure{&n, nullptr})}),
290
- 0, nullptr});
300
+ // Effective capture set: `n`'s own direct free-variable
301
+ // references, plus -- transitively -- whatever any literal
302
+ // nested inside `n` still needs from beyond `n`'s own scope
303
+ // (see bubbleEscapingCaptures' own doc comment, above, and
304
+ // ClosureSite's, bytecode.hpp).
305
+ std::vector<CompiledChunk::UpvalueRef> effectiveCaptures = literalChunk.upvalues;
306
+ for (const auto& nestedSite : literalChunk.closureSites) {
307
+ bubbleEscapingCaptures(nestedSite, &n, effectiveCaptures);
308
+ }
309
+ if (effectiveCaptures.empty()) {
310
+ // Closes over nothing at all, even transitively -- the
311
+ // existing fast path: a single frozen constant works
312
+ // for every invocation, and this literal's own compiled
313
+ // chunk (possibly containing its OWN MakeClosure sites
314
+ // for anything nested inside IT that closes only over
315
+ // ITS OWN scope) is genuinely usable, registered as
316
+ // before.
317
+ chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
318
+ out.push_back(
319
+ {Op::PushConst, internConst(Value{std::make_shared<const Closure>(Closure{&n, nullptr})}), 0,
320
+ nullptr});
321
+ return;
322
+ }
323
+ // Escaping-closure support (Op::MakeClosure, bytecode.hpp).
324
+ // `literalChunk`'s own bytecode is discarded here -- its own
325
+ // upvalue reads would resolve via the live-call-stack walk
326
+ // (Op::LoadUpvalue), which is wrong for a closure that
327
+ // outlives its creator -- so this literal always runs
328
+ // interpreted when invoked instead (Evaluator::
329
+ // lookupCompiledLiteralChunk finds no cache entry for it),
330
+ // using the exact same Closure::capturedLet machinery every
331
+ // other escaping closure the interpreter creates directly
332
+ // already relies on. Only `effectiveCaptures` (the names/
333
+ // slots to snapshot right now) survives from the discarded
334
+ // compile attempt.
335
+ int siteIdx = static_cast<int>(chunk_.closureSites.size());
336
+ chunk_.closureSites.push_back(CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures)});
337
+ out.push_back({Op::MakeClosure, siteIdx, 0, nullptr});
291
338
  return;
292
339
  }
293
340
  case NodeKind::RangeLiteral: {
@@ -609,6 +656,7 @@ public:
609
656
  // list too, when reached via a clause's own nested body.
610
657
  void compileListElement(const oscad::ASTNode& elem, std::vector<Instruction>& out, CompileScope& scope) {
611
658
  using oscad::NodeKind;
659
+ trackSpan(elem);
612
660
  switch (elem.kind()) {
613
661
  case NodeKind::ListCompFor: {
614
662
  auto& n = static_cast<const oscad::ListCompFor&>(elem);
@@ -4,6 +4,7 @@
4
4
  #include "openscad_cpp_evaluator/evaluator.hpp"
5
5
  #include "openscad_cpp_evaluator/function_builtins.hpp"
6
6
  #include "openscad_cpp_evaluator/import_builtin.hpp"
7
+ #include "openscad_cpp_evaluator/scope_trail.hpp"
7
8
 
8
9
  #include "openscad_cpp_parser/ast/declarations.hpp"
9
10
 
@@ -132,6 +133,33 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
132
133
  ++pc;
133
134
  break;
134
135
  }
136
+ case Op::MakeClosure: {
137
+ // Builds a FRESH captured environment every time this
138
+ // instruction actually runs (never a compile-time constant
139
+ // -- a loop creating one closure per iteration must capture
140
+ // THAT iteration's own values, not share one snapshot) by
141
+ // reading each of the site's own captures straight out of
142
+ // THIS frame's own `slots` -- see ClosureSite's own doc
143
+ // comment (bytecode.hpp) for why every capture, however
144
+ // deeply the literal was originally nested, is guaranteed
145
+ // to resolve against this SAME currently-running chunk.
146
+ // The resulting Closure::capturedLet is a real, standalone
147
+ // TrailView (isolated root -- nothing else shares it, and
148
+ // it owes nothing to `ctx.let_`, which a compiled call
149
+ // never populates -- see bindCompiledArgs' own doc
150
+ // comment), so invoking this closure later works through
151
+ // the EXACT same callCtxFor/capturedLetTrail machinery
152
+ // every interpreter-created escaping closure already uses,
153
+ // unchanged.
154
+ const CompiledChunk::ClosureSite& site = chunk.closureSites[static_cast<size_t>(ins.a)];
155
+ auto capturedTrail = TrailView<Value>::makeRoot();
156
+ for (const auto& cap : site.captures) {
157
+ capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
158
+ }
159
+ stack.push_back(Value{std::make_shared<const Closure>(Closure{site.node, std::move(capturedTrail)})});
160
+ ++pc;
161
+ break;
162
+ }
135
163
  case Op::Range: {
136
164
  Value step = std::move(stack.back());
137
165
  stack.pop_back();
@@ -14,12 +14,20 @@ const CompiledChunk* Evaluator::lookupOrCompileChunk(const oscad::FunctionDeclar
14
14
  it = chunkCache_.emplace(&decl, tryCompileFunction(decl)).first;
15
15
  if (it->second) flattenNestedLiterals(*it->second);
16
16
  }
17
- return it->second ? &*it->second : nullptr;
17
+ if (!it->second) return nullptr;
18
+ // Compilation itself is cached forever above (never depends on
19
+ // debugger state); ELIGIBILITY to actually use that compiled chunk
20
+ // right now is re-checked every call instead -- see chunkEligibleNow's
21
+ // own doc comment for why that's still cheap (no debugger: one bool
22
+ // check; fast-continue mode: a handful of integer comparisons against
23
+ // whatever breakpoints happen to be set right now).
24
+ return chunkEligibleNow(*it->second) ? &*it->second : nullptr;
18
25
  }
19
26
 
20
27
  const CompiledChunk* Evaluator::lookupCompiledLiteralChunk(const oscad::FunctionLiteral& node) const {
21
28
  auto it = literalChunkCache_.find(&node);
22
- return it != literalChunkCache_.end() ? &it->second : nullptr;
29
+ if (it == literalChunkCache_.end()) return nullptr;
30
+ return chunkEligibleNow(it->second) ? &it->second : nullptr;
23
31
  }
24
32
 
25
33
  void Evaluator::flattenNestedLiterals(CompiledChunk& chunk) {