openscad-cpp-evaluator 0.4.9__tar.gz → 0.5.1__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.9 → openscad_cpp_evaluator-0.5.1}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/bindings/module.cpp +55 -3
  3. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/bytecode.hpp +18 -0
  4. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/evaluator.hpp +67 -1
  5. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/bytecode_compiler.cpp +37 -0
  7. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/bytecode_vm.cpp +12 -1
  8. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/user_calls.cpp +10 -2
  9. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_bytecode_compiler.cpp +160 -0
  10. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_python_bindings.py +4 -4
  11. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/.github/workflows/ci.yml +0 -0
  12. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/.github/workflows/wheels.yml +0 -0
  13. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/.gitignore +0 -0
  14. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/.gitmodules +0 -0
  15. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/CLAUDE.md +0 -0
  16. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/CMakeLists.txt +0 -0
  17. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/LICENSE +0 -0
  18. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/README.md +0 -0
  19. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/bindings/CMakeLists.txt +0 -0
  20. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/cmake/embed_font.cmake +0 -0
  21. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/examples/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/examples/eval_perf_benchmark.cpp +0 -0
  23. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/examples/manifold_cache_reuse.cpp +0 -0
  24. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/examples/minimal_debugger.cpp +0 -0
  25. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  26. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/.gitignore +0 -0
  27. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  28. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/README.md +0 -0
  30. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  31. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  32. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  33. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  34. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  35. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  36. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  37. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  38. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  39. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  41. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  42. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  43. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  44. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  45. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  46. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  47. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  48. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  49. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  50. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  51. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  52. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  53. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  54. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  55. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  56. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  57. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  58. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  59. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  60. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  61. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  62. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  63. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  64. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  65. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  66. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  67. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  68. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  69. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  70. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  71. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  72. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  73. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  74. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  75. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  76. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  77. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  78. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  79. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  80. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  81. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  82. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  83. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  84. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  85. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  86. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  87. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  88. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  89. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  90. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  91. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  92. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  93. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  94. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  95. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  96. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  97. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  98. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  99. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  100. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  101. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  102. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  103. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  104. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  105. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  106. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  107. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  108. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  109. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  110. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  111. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  112. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  113. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  114. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  115. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  117. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  118. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  119. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  120. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  121. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/resources/fonts/LICENSE +0 -0
  122. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  123. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/CMakeLists.txt +0 -0
  124. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/booleans.cpp +0 -0
  125. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/builtins.hpp +0 -0
  126. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/call_args.cpp +0 -0
  127. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/color.cpp +0 -0
  128. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/control.cpp +0 -0
  129. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/extrude.cpp +0 -0
  130. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/function_builtins.cpp +0 -0
  131. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/import.cpp +0 -0
  132. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/modifiers.cpp +0 -0
  133. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/primitives_2d.cpp +0 -0
  134. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/primitives_3d.cpp +0 -0
  135. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/registry.cpp +0 -0
  136. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/roof.cpp +0 -0
  137. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/segments.cpp +0 -0
  138. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/surface.cpp +0 -0
  139. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/text.cpp +0 -0
  140. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/topology.cpp +0 -0
  141. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/builtins/transforms.cpp +0 -0
  142. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/colored_body.cpp +0 -0
  143. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/csg_generate.cpp +0 -0
  144. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/csg_resolve.cpp +0 -0
  145. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/css_colors.cpp +0 -0
  146. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/debug/debug_repl.cpp +0 -0
  147. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/debug_profile.cpp +0 -0
  148. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/eval_context.cpp +0 -0
  149. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/eval_error.cpp +0 -0
  150. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/eval_use.cpp +0 -0
  151. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/export.cpp +0 -0
  152. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/expr_eval.cpp +0 -0
  153. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/import/dxf_import.cpp +0 -0
  154. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/import/mesh_import.cpp +0 -0
  155. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/import/surface_load.cpp +0 -0
  156. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/import/svg_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/manifold_cache.cpp +0 -0
  158. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/stmt_eval.cpp +0 -0
  159. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/text/stb_font_provider.cpp +0 -0
  160. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/text/text_metrics.cpp +0 -0
  161. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/value.cpp +0 -0
  162. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/src/zip_stored.cpp +0 -0
  163. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/CMakeLists.txt +0 -0
  164. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_cli.cpp +0 -0
  166. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_control_flow.cpp +0 -0
  167. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_csg_tree.cpp +0 -0
  168. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_debug_hooks.cpp +0 -0
  169. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_dxf_svg_import.cpp +0 -0
  170. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_export.cpp +0 -0
  172. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_expr_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_extrude_roof.cpp +0 -0
  174. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_function_builtins.cpp +0 -0
  175. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_helpers.hpp +0 -0
  176. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_import_export.cpp +0 -0
  177. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_multi_color_merge.cpp +0 -0
  179. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_primitives.cpp +0 -0
  180. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_profiling.cpp +0 -0
  181. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_scope_trail.cpp +0 -0
  182. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_scoping.cpp +0 -0
  183. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_surface.cpp +0 -0
  184. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_tail_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_text.cpp +0 -0
  186. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_transforms.cpp +0 -0
  187. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_use.cpp +0 -0
  188. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_value.cpp +0 -0
  189. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.4.9 → openscad_cpp_evaluator-0.5.1}/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.9
3
+ Version: 0.5.1
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>
@@ -252,6 +253,23 @@ struct CompiledChunk {
252
253
  // sizes its own native iterLists/iterIndex scratch vectors to this.
253
254
  int numIterLists = 0;
254
255
 
256
+ // Source-file/line span this chunk's own body+defaults were compiled
257
+ // from -- the min/max of every AST node's own position().line actually
258
+ // visited during compilation (see Compiler::trackSpan, bytecode_compiler.cpp),
259
+ // not just the declaration's own header line. Used by Evaluator::
260
+ // chunkEligibleNow (debug "fast continue" mode) to decide, per
261
+ // function, whether ANY currently-set breakpoint line could fall
262
+ // inside this chunk's own body -- a plain integer-range check, chosen
263
+ // specifically so that decision never needs the original source text
264
+ // or an offset->line conversion at eligibility-check time (called on
265
+ // every single compiled call, not just once). origin empty / minLine >
266
+ // maxLine (never updated) should not happen for any chunk that
267
+ // actually compiled a real body -- every function has at least one
268
+ // expression to visit.
269
+ std::string origin;
270
+ int minLine = std::numeric_limits<int>::max();
271
+ int maxLine = 0;
272
+
255
273
  // FunctionLiteral chunks discovered while compiling THIS chunk (a
256
274
  // FunctionLiteral expression appearing anywhere in its body/defaults),
257
275
  // 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.9"
7
+ version = "0.5.1"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -128,6 +128,20 @@ public:
128
128
  return slot;
129
129
  }
130
130
 
131
+ // Widens chunk_'s own [minLine, maxLine] span to include `node` --
132
+ // called at the top of every AST-node-visiting entry point
133
+ // (compileExpr/compileListElement/compileListCompBody) so the chunk's
134
+ // final span covers every line actually compiled into it, not just the
135
+ // declaration's own header line. See CompiledChunk::origin/minLine/
136
+ // maxLine's own doc comment (bytecode.hpp) for why this matters --
137
+ // Evaluator::chunkEligibleNow's debug "fast continue" check.
138
+ void trackSpan(const oscad::ASTNode& node) {
139
+ const oscad::Position& pos = node.position();
140
+ chunk_.origin = pos.origin;
141
+ if (pos.line < chunk_.minLine) chunk_.minLine = pos.line;
142
+ if (pos.line > chunk_.maxLine) chunk_.maxLine = pos.line;
143
+ }
144
+
131
145
  // Walks `enclosing_` from innermost (back()) to outermost, looking for
132
146
  // a level whose own CompileScope resolves `name` -- the first (most
133
147
  // deeply nested, i.e. most recently active at runtime) match wins,
@@ -171,6 +185,7 @@ public:
171
185
  void compileExpr(const oscad::Expression& node, std::vector<Instruction>& out, CompileScope& scope,
172
186
  bool tail = false) {
173
187
  using oscad::NodeKind;
188
+ trackSpan(node);
174
189
  switch (node.kind()) {
175
190
  case NodeKind::NumberLiteral:
176
191
  out.push_back({Op::PushConst, internConst(Value{static_cast<const oscad::NumberLiteral&>(node).val}), 0,
@@ -609,6 +624,7 @@ public:
609
624
  // list too, when reached via a clause's own nested body.
610
625
  void compileListElement(const oscad::ASTNode& elem, std::vector<Instruction>& out, CompileScope& scope) {
611
626
  using oscad::NodeKind;
627
+ trackSpan(elem);
612
628
  switch (elem.kind()) {
613
629
  case NodeKind::ListCompFor: {
614
630
  auto& n = static_cast<const oscad::ListCompFor&>(elem);
@@ -676,6 +692,27 @@ public:
676
692
  int slot = declareLocal(scope, assign->name->name);
677
693
  out.push_back({Op::StoreLocal, slot, 0, &assign->position()});
678
694
  }
695
+ // Pre-declare every incr-list name NOW, before compiling the
696
+ // condition/body/incr expressions below -- an incr name
697
+ // introduced fresh (not in the init list, e.g. BOSL2's
698
+ // skin.scad: `best_i = result[0]<bestcost ? i : best_i,`)
699
+ // can be READ, including by its OWN incr assignment's RHS
700
+ // (a self-reference to its prior iteration's value) or by
701
+ // the loop body, before its OWN StoreLocal below would
702
+ // otherwise have declared it. Without this, that read
703
+ // compiles to Op::LoadFree (the "not a known local/upvalue/
704
+ // dyn-var, might be dynamic, warn if truly missing"
705
+ // fallback) -- permanently, since compilation happens once
706
+ // but the resulting instruction runs every iteration: every
707
+ // read silently misses the real value stored into the
708
+ // local slot moments later at runtime, not just an extra
709
+ // warning but a genuinely wrong result (confirmed: BOSL2's
710
+ // own best_i accumulator came back undef on every read).
711
+ // Declaring the slot here first means the SAME reads below
712
+ // resolve as ordinary Op::LoadLocal instead.
713
+ for (const auto& assign : n.incrs) {
714
+ if (!scope.resolve(assign->name->name)) declareLocal(scope, assign->name->name);
715
+ }
679
716
  int counterSlot = nextSlot_++;
680
717
  out.push_back({Op::PushConst, internConst(Value{0.0}), 0, nullptr});
681
718
  out.push_back({Op::StoreLocal, counterSlot, 0, nullptr});
@@ -40,6 +40,16 @@ private:
40
40
  struct IterList {
41
41
  IterableValues values;
42
42
  size_t index = 0;
43
+ // Cached once at IterMaterialize time -- IterableValues::size() is O(1)
44
+ // for a list/owned vector, but walks a RANGE's whole sequence from
45
+ // scratch every call (its own doc comment: "no caller in this codebase
46
+ // actually calls size() on a range today" -- Op::IterNext, below, used
47
+ // to be exactly that stale caller). Recomputing it once per IterNext
48
+ // call turned a single range-based for()/list-comp `for` into an O(n^2)
49
+ // walk (a 100,000-element range: interpreter ~0.03s, VM ~9.5s) -- caught
50
+ // via a BOSL2 corpus sweep (test_math.scadtest's own gaussian_rands(),
51
+ // which builds a 100,000-element list via `count()`).
52
+ size_t total = 0;
43
53
  };
44
54
 
45
55
  // Shared by Op::CallFn's isBuiltin (evalBuiltinFunction) and isImport
@@ -244,6 +254,7 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
244
254
  ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", ins.pos);
245
255
  });
246
256
  il.index = 0;
257
+ il.total = il.values.size(); // once here, never per-iteration -- see IterList's own doc comment
247
258
  ++pc;
248
259
  break;
249
260
  }
@@ -254,7 +265,7 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
254
265
  }
255
266
  case Op::IterNext: {
256
267
  IterList& il = iterLists[static_cast<size_t>(ins.b)];
257
- if (il.index < il.values.size()) {
268
+ if (il.index < il.total) {
258
269
  slots[static_cast<size_t>(ins.a)] = il.values[il.index];
259
270
  ++il.index;
260
271
  ++pc;
@@ -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) {
@@ -4,6 +4,7 @@
4
4
 
5
5
  #include "test_helpers.hpp"
6
6
 
7
+ #include <chrono>
7
8
  #include <filesystem>
8
9
  #include <fstream>
9
10
  #include <gtest/gtest.h>
@@ -48,6 +49,33 @@ std::string runCapturingEcho(const std::string& code) {
48
49
  return captured;
49
50
  }
50
51
 
52
+ // Counts total debugHook invocations (statement- and expr-level combined)
53
+ // running `code` with a debugger attached. A VM-compiled function call
54
+ // gets exactly ONE stop -- evalUserFunctionCore's own unconditional
55
+ // body-entry checkDebug(), the only checkpoint compiled bytecode has left
56
+ // (see useBytecodeVm()/chunkEligibleNow's own doc comments, evaluator.hpp)
57
+ // -- while an interpreted call additionally hits every sub-expression
58
+ // checkpoint inside it (ternary condition + chosen branch, at minimum, for
59
+ // the functions these tests use). That gap is the only observable,
60
+ // black-box signal these tests have for "did this call actually run
61
+ // compiled or interpreted" -- there's no other public introspection point.
62
+ int countDebugHookStops(const std::string& code, std::optional<std::unordered_map<std::string, std::set<int>>> fastContinueBreakpoints) {
63
+ int stops = 0;
64
+ DebugHooks hooks;
65
+ hooks.debugHook = [&](int, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&,
66
+ const DebugFramesFn&) {
67
+ ++stops;
68
+ return DebugAction{};
69
+ };
70
+ Evaluator ev(EchoFn{}, nullptr, nullptr, hooks);
71
+ if (fastContinueBreakpoints) ev.setFastContinueBreakpoints(std::move(fastContinueBreakpoints));
72
+ auto ast = parseSrc(code);
73
+ auto scope = oscad::buildScopes(ast);
74
+ EvalContext ctx = EvalContext::makeRoot(scope.get());
75
+ ev.resolveTree(ast, ctx);
76
+ return stops;
77
+ }
78
+
51
79
  std::filesystem::path tempPath(const std::string& name) {
52
80
  return std::filesystem::temp_directory_path() / ("oscad_bytecode_compiler_test_" + name);
53
81
  }
@@ -721,3 +749,135 @@ TEST(BytecodeCompiler, VmOffAndVmOnAgreeOnEchoAssertImportObjectCases) {
721
749
  EXPECT_EQ(offResult, onResult);
722
750
  std::filesystem::remove(path);
723
751
  }
752
+
753
+ TEST(BytecodeCompiler, RangeBasedForListCompDoesNotReMaterializeRangeSizePerIteration) {
754
+ ScopedVm vm(true);
755
+ // Op::IterNext used to bound-check via `il.index < il.values.size()`
756
+ // every single iteration. IterableValues::size() is O(1) for a
757
+ // materialized list, but for a RANGE it walks the whole sequence from
758
+ // scratch (its own doc comment already flagged this as a stale
759
+ // assumption: "no caller in this codebase actually calls size() on a
760
+ // range today" -- Op::IterNext was exactly that caller). Recomputing an
761
+ // O(n) size every one of n iterations is O(n^2) overall -- a
762
+ // 100,000-element range list comprehension took ~9.5s compiled vs
763
+ // ~0.03s interpreted before the fix (caught via a BOSL2 corpus sweep,
764
+ // test_math.scadtest's gaussian_rands()). Fixed by caching the size
765
+ // once at IterMaterialize time (IterList::total). 200,000 elements
766
+ // finishes in well under a second now; the old behavior would have
767
+ // taken tens of seconds, so a generous 5s bound cleanly separates
768
+ // "fixed" from "regressed" without making this test itself slow.
769
+ const auto start = std::chrono::steady_clock::now();
770
+ EXPECT_EQ(runCapturingEcho("function myrange(n) = [for (i=[0:1:n-1]) i];\n"
771
+ "r = myrange(200000);\n"
772
+ "echo(len(r));"),
773
+ "ECHO: 200000");
774
+ const auto elapsed = std::chrono::steady_clock::now() - start;
775
+ EXPECT_LT(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(), 5000);
776
+ }
777
+
778
+ // -- Debug "fast continue" per-function VM gating --------------------------
779
+ //
780
+ // A debugger attached AND actively single-stepping needs every checkDebug()
781
+ // checkpoint the interpreter provides (a compiled function has none for a
782
+ // step to land on) -- that's the pre-existing, always-safe, never-changed
783
+ // behavior when Evaluator::setFastContinueBreakpoints is never called at
784
+ // all (fastContinueBreakpoints_ stays nullopt). But a plain "Continue,
785
+ // pause only at a known breakpoint line" doesn't need that: a function
786
+ // whose own compiled span contains none of the currently-set breakpoint
787
+ // lines can safely run on the VM, since nothing inside it could possibly
788
+ // need a checkpoint right now.
789
+
790
+ // Expected stop counts below are empirically verified (via a temporary
791
+ // direct instrumentation pass through lookupOrCompileChunk/evalUserFunction,
792
+ // not guessed): calling `f(5)` for `function f(x) = x > 0 ? x + 1 : x - 1;`
793
+ // as the lone argument of a top-level `echo(...)` produces 3 checkDebug
794
+ // stops when f runs compiled (the top-level echo statement's own stop,
795
+ // f's call-site stop from evalFunctionCall, and evalUserFunctionCore's
796
+ // unconditional body-entry stop -- no others, since compiled bytecode has
797
+ // no sub-expression checkpoints) versus 5 when f runs interpreted (those
798
+ // same 3, plus the ternary's own condition-check and chosen-branch-entry
799
+ // sub-expression stops). A FunctionDeclaration statement itself never adds
800
+ // its own top-level stop (evalChildren's per-statement checkpoint skips
801
+ // declarations, mirroring $children's own count -- see
802
+ // Evaluator::evalUserModule's childrenCount loop), so adding a second,
803
+ // uncalled function declaration to a script doesn't change either count.
804
+
805
+ TEST(BytecodeCompiler, DebugAttachedWithoutFastContinueAlwaysInterprets) {
806
+ ScopedVm vm(true);
807
+ // No setFastContinueBreakpoints call at all (nullopt, the default):
808
+ // must behave exactly as before this feature existed -- always
809
+ // interpreted -- regardless of the function's own line having no
810
+ // breakpoint anywhere near it.
811
+ const int stops = countDebugHookStops("function f(x) = x > 0 ? x + 1 : x - 1;\n"
812
+ "echo(f(5));",
813
+ std::nullopt);
814
+ EXPECT_EQ(stops, 5);
815
+ }
816
+
817
+ TEST(BytecodeCompiler, FastContinueWithNoBreakpointInFunctionUsesVm) {
818
+ ScopedVm vm(true);
819
+ // Breakpoint set, but only on line 2 (the echo statement) -- f's own
820
+ // body is entirely on line 1, outside that set, so it's eligible to
821
+ // run compiled.
822
+ const int stops = countDebugHookStops("function f(x) = x > 0 ? x + 1 : x - 1;\n"
823
+ "echo(f(5));",
824
+ std::unordered_map<std::string, std::set<int>>{{"<string>", {2}}});
825
+ EXPECT_EQ(stops, 3);
826
+ }
827
+
828
+ TEST(BytecodeCompiler, FastContinueWithBreakpointInsideFunctionStillInterprets) {
829
+ ScopedVm vm(true);
830
+ // Breakpoint on line 1 itself -- inside f's own compiled span -- must
831
+ // still force the interpreter for f specifically, even in fast-continue
832
+ // mode: something on that exact line could need a real checkpoint.
833
+ const int stops = countDebugHookStops("function f(x) = x > 0 ? x + 1 : x - 1;\n"
834
+ "echo(f(5));",
835
+ std::unordered_map<std::string, std::set<int>>{{"<string>", {1}}});
836
+ EXPECT_EQ(stops, 5);
837
+ }
838
+
839
+ TEST(BytecodeCompiler, FastContinueWithBreakpointInDifferentFunctionUsesVmForThisOne) {
840
+ ScopedVm vm(true);
841
+ // Two ternary-bodied functions (so both would show the same VM-vs-
842
+ // interpreter stop-count gap if called), breakpoint only inside g's
843
+ // body (line 2) -- f (line 1) must still run compiled since its own
844
+ // span doesn't contain line 2, even though SOME breakpoint exists in
845
+ // this same file. Confirms the gating is genuinely per-function, not
846
+ // "any breakpoint anywhere disables the whole file." Only f is ever
847
+ // called, so g's own eligibility is never directly observed here.
848
+ const int stops = countDebugHookStops("function f(x) = x > 0 ? x + 1 : x - 1;\n"
849
+ "function g(x) = x > 0 ? x + 1 : x - 1;\n"
850
+ "echo(f(5));",
851
+ std::unordered_map<std::string, std::set<int>>{{"<string>", {2}}});
852
+ EXPECT_EQ(stops, 3);
853
+ }
854
+
855
+ TEST(BytecodeCompiler, CStyleForIncrNameCanReadItsOwnPriorValueInTheSameAssignment) {
856
+ ScopedVm vm(true);
857
+ // A C-style for's incr list can introduce a name that reads its OWN
858
+ // not-yet-assigned-this-iteration value on the right-hand side (its
859
+ // prior iteration's value) -- e.g. BOSL2's skin.scad: `best_i =
860
+ // result[0]<bestcost ? i : best_i,`. The compiler used to compile each
861
+ // incr assignment's RHS BEFORE declaring that assignment's own local
862
+ // slot (declareLocal ran only after compileExpr), so a self-reference
863
+ // like this -- and the loop BODY's own read, compiled even earlier --
864
+ // resolved via Op::LoadFree (the "not a known local, might be dynamic"
865
+ // fallback) instead of Op::LoadLocal. That's not just an extra
866
+ // "unknown variable" warning: LoadFree runs identically on every
867
+ // iteration once compiled, so it NEVER saw what Op::StoreLocal had
868
+ // written moments earlier at runtime -- a genuinely wrong final
869
+ // result (undef), not merely a cosmetic warning-count mismatch. Real
870
+ // OpenSCAD gives [4] here (one "unknown variable" warning for the
871
+ // first, genuinely-unassigned read, then the accumulator works
872
+ // correctly from there). Fixed by pre-declaring every incr name's slot
873
+ // before compiling the condition/body/incr expressions at all.
874
+ EXPECT_EQ(runCapturingEcho("function myloop(n) =\n"
875
+ " [for (i=0, bestcost=1/0;\n"
876
+ " i<=n;\n"
877
+ " bestcost = (i==2||i==4) ? i : bestcost,\n"
878
+ " best_i = (i==2||i==4) ? i : best_i,\n"
879
+ " i=i+1)\n"
880
+ " if (i==n) best_i];\n"
881
+ "echo(myloop(5));"),
882
+ "ECHO: [4]");
883
+ }
@@ -97,7 +97,7 @@ def test_return_hook_fires_for_user_function():
97
97
  returns = []
98
98
 
99
99
  def debug_hook(line, depth, forced=False, expr_level=False, expr_depth=0, origin=None, get_frames=None,
100
- generate_partial=None, get_children_positions=None):
100
+ generate_partial=None, get_children_positions=None, set_fast_continue=None):
101
101
  return ("continue", {})
102
102
 
103
103
  def return_hook(name, value, depth):
@@ -120,7 +120,7 @@ def test_generate_partial_during_a_live_pause():
120
120
  seen_partial_counts = []
121
121
 
122
122
  def debug_hook(line, depth, forced=False, expr_level=False, expr_depth=0, origin=None, get_frames=None,
123
- generate_partial=None, get_children_positions=None):
123
+ generate_partial=None, get_children_positions=None, set_fast_continue=None):
124
124
  if forced:
125
125
  bodies = generate_partial()
126
126
  seen_partial_counts.append(len(bodies))
@@ -144,7 +144,7 @@ def test_generate_partial_on_an_empty_tree_returns_empty_not_an_error():
144
144
  seen = []
145
145
 
146
146
  def debug_hook(line, depth, forced=False, expr_level=False, expr_depth=0, origin=None, get_frames=None,
147
- generate_partial=None, get_children_positions=None):
147
+ generate_partial=None, get_children_positions=None, set_fast_continue=None):
148
148
  if forced:
149
149
  seen.append(generate_partial())
150
150
  return ("continue", {})
@@ -164,7 +164,7 @@ def test_get_children_positions_reports_module_call_block_children():
164
164
  seen = []
165
165
 
166
166
  def debug_hook(line, depth, forced=False, expr_level=False, expr_depth=0, origin=None, get_frames=None,
167
- generate_partial=None, get_children_positions=None):
167
+ generate_partial=None, get_children_positions=None, set_fast_continue=None):
168
168
  positions = get_children_positions()
169
169
  if positions:
170
170
  seen.append((line, positions))