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