openscad-cpp-evaluator 0.6.0__tar.gz → 0.7.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.0 → openscad_cpp_evaluator-0.7.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/bindings/module.cpp +47 -11
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/evaluator.hpp +65 -5
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/python/openscad_cpp_evaluator/__init__.py +15 -3
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/bytecode_vm.cpp +29 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/debug_profile.cpp +30 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/user_calls.cpp +26 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_debug_hooks.cpp +120 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_tail_calls.cpp +29 -16
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/text/stb_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.6.0 → openscad_cpp_evaluator-0.7.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
|
}
|
|
@@ -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:
|
|
@@ -664,10 +664,38 @@ Value runCompiledFunctionFromBound(Evaluator& ev, const CompiledChunk& chunk, co
|
|
|
664
664
|
// *stack* growth (what this trampoline exists to eliminate) for heap
|
|
665
665
|
// growth instead -- O(iteration count) EvalContext objects, bounded by
|
|
666
666
|
// available RAM rather than a ~few-MB thread stack.
|
|
667
|
+
namespace {
|
|
668
|
+
// Tears a trampoline's own `chain` down back-to-front on EVERY exit path --
|
|
669
|
+
// normal return OR exception unwind (recordTailCallHop's own recursion-guard
|
|
670
|
+
// error is the exact case that matters: an infinite tail-recursive function
|
|
671
|
+
// with no base case throws out of the loop entirely, bypassing any teardown
|
|
672
|
+
// code placed after it). Relying on `chain` simply falling out of scope
|
|
673
|
+
// instead is NOT equivalent: std::vector<T>'s own element-destruction order
|
|
674
|
+
// is unspecified by the standard -- libc++ destroys back-to-front (matching
|
|
675
|
+
// ScopeTrailStorage::popLevel's own "scan from the back" doc comment, which
|
|
676
|
+
// assumes the level being popped is usually the most recently pushed one --
|
|
677
|
+
// O(1) per pop that way), but libstdc++ destroys front-to-back, the
|
|
678
|
+
// adversarial order for that same scan -- each pop then walks almost the
|
|
679
|
+
// entire remaining vector, turning an intended O(N) teardown into a real,
|
|
680
|
+
// measured O(N^2) (see issue #50: an N-scaling experiment plus a minimal
|
|
681
|
+
// repro proving libc++/libstdc++ disagree on std::vector<T>::clear()'s
|
|
682
|
+
// destruction order for identical source). Popping back-to-front explicitly,
|
|
683
|
+
// via a destructor that always runs before `chain`'s own, is correct AND
|
|
684
|
+
// O(N) on every standard library and every exit path, not just the ones
|
|
685
|
+
// that happen to agree with libc++ and return normally.
|
|
686
|
+
struct ChainTeardown {
|
|
687
|
+
std::vector<EvalContext>& chain;
|
|
688
|
+
~ChainTeardown() {
|
|
689
|
+
while (!chain.empty()) chain.pop_back();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
} // namespace
|
|
693
|
+
|
|
667
694
|
Value runCompiledFunctionTrampoline(Evaluator& ev, const CompiledChunk& chunk,
|
|
668
695
|
const std::vector<std::unique_ptr<oscad::Argument>>& arguments,
|
|
669
696
|
EvalContext& callerCtx, EvalContext& childCtx) {
|
|
670
697
|
std::vector<EvalContext> chain{childCtx};
|
|
698
|
+
ChainTeardown teardown{chain};
|
|
671
699
|
TailCallRequest req;
|
|
672
700
|
Value result = runCompiledFunction(ev, chunk, arguments, callerCtx, chain.back(), &req);
|
|
673
701
|
unsigned recursionGuard = 0;
|
|
@@ -689,6 +717,7 @@ Value runCompiledFunctionTrampoline(Evaluator& ev, const CompiledChunk& chunk,
|
|
|
689
717
|
Value runCompiledFunctionFromBoundTrampoline(Evaluator& ev, const CompiledChunk& chunk, const BoundArgs& bound,
|
|
690
718
|
EvalContext& childCtx) {
|
|
691
719
|
std::vector<EvalContext> chain{childCtx};
|
|
720
|
+
ChainTeardown teardown{chain};
|
|
692
721
|
TailCallRequest req;
|
|
693
722
|
Value result = runCompiledFunctionFromBound(ev, chunk, bound, chain.back(), &req);
|
|
694
723
|
unsigned recursionGuard = 0;
|
|
@@ -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);
|
|
@@ -443,6 +443,32 @@ Value Evaluator::evalFunctionBodyTrampoline(const oscad::Expression& bodyExpr, E
|
|
|
443
443
|
// dropped there) if memory ever measurably matters more than this
|
|
444
444
|
// simpler all-or-nothing approach.
|
|
445
445
|
std::vector<EvalContext> chain{ctx};
|
|
446
|
+
// Tears `chain` down back-to-front on EVERY exit path -- normal return
|
|
447
|
+
// OR exception unwind (recordTailCallHop's own recursion-guard error,
|
|
448
|
+
// below, is the exact case that matters: an infinite tail-recursive
|
|
449
|
+
// function with no base case throws out of this loop entirely,
|
|
450
|
+
// bypassing any teardown code placed after it). Relying on `chain`
|
|
451
|
+
// simply falling out of scope instead is NOT equivalent: std::vector<T>'s
|
|
452
|
+
// own element-destruction order is unspecified by the standard --
|
|
453
|
+
// libc++ destroys back-to-front (matching ScopeTrailStorage::popLevel's
|
|
454
|
+
// own "scan from the back" doc comment, which assumes the level being
|
|
455
|
+
// popped is usually the most recently pushed one -- O(1) per pop that
|
|
456
|
+
// way), but libstdc++ destroys front-to-back, the adversarial order for
|
|
457
|
+
// that same scan -- each pop then walks almost the entire remaining
|
|
458
|
+
// vector, turning an intended O(N) teardown into a real, measured
|
|
459
|
+
// O(N^2) (see issue #50: an N-scaling experiment plus a minimal repro
|
|
460
|
+
// proving libc++/libstdc++ disagree on std::vector<T>::clear()'s
|
|
461
|
+
// destruction order for identical source). Popping back-to-front
|
|
462
|
+
// explicitly, via a destructor that always runs before `chain`'s own,
|
|
463
|
+
// is correct AND O(N) on every standard library and every exit path,
|
|
464
|
+
// not just the ones that happen to agree with libc++ and return
|
|
465
|
+
// normally.
|
|
466
|
+
struct ChainTeardown {
|
|
467
|
+
std::vector<EvalContext>& chain;
|
|
468
|
+
~ChainTeardown() {
|
|
469
|
+
while (!chain.empty()) chain.pop_back();
|
|
470
|
+
}
|
|
471
|
+
} teardown{chain};
|
|
446
472
|
unsigned recursionGuard = 0;
|
|
447
473
|
while (true) {
|
|
448
474
|
auto result = simplifyTailStep(*expr, ctx);
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
#include <gtest/gtest.h>
|
|
7
7
|
|
|
8
8
|
#include <algorithm>
|
|
9
|
+
#include <atomic>
|
|
10
|
+
#include <memory>
|
|
9
11
|
#include <ostream>
|
|
12
|
+
#include <set>
|
|
10
13
|
#include <string>
|
|
14
|
+
#include <unordered_map>
|
|
11
15
|
#include <vector>
|
|
12
16
|
|
|
13
17
|
using namespace oscadeval;
|
|
@@ -230,6 +234,122 @@ TEST(DebugHooks, NoHooksInstalledMeansZeroOverheadCodePathStillWorks) {
|
|
|
230
234
|
EXPECT_EQ(e.bodies.size(), 1u);
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Fast-continue's hook-skippable mode (issue found via BelfrySCAD's own
|
|
239
|
+
// nurbs.scad-heavy debug session: 462k checkDebug() calls survived even
|
|
240
|
+
// with per-function VM fast-continue fully engaged, since module/geometry
|
|
241
|
+
// evaluation never compiles and every one of those still crossed into
|
|
242
|
+
// Python just to be told "continue"). See setFastContinueBreakpoints's own
|
|
243
|
+
// doc comment (evaluator.hpp) for the full contract.
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
TEST(DebugHooks, FastContinueHookSkippableSkipsCheckpointsWithNoMatchingBreakpoint) {
|
|
247
|
+
int calls = 0;
|
|
248
|
+
DebugHooks hooks;
|
|
249
|
+
hooks.debugHook = [&](int, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
|
|
250
|
+
++calls;
|
|
251
|
+
return DebugAction{};
|
|
252
|
+
};
|
|
253
|
+
Evaluator ev(EchoFn{}, nullptr, nullptr, hooks);
|
|
254
|
+
ev.setFastContinueBreakpoints(std::unordered_map<std::string, std::set<int>>{}, /*hookSkippable=*/true);
|
|
255
|
+
auto ast = parseSrc("cube(1);\ntranslate([1,0,0]) sphere(r=1,$fn=8);\necho(\"x\");");
|
|
256
|
+
auto scope = oscad::buildScopes(ast);
|
|
257
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
258
|
+
ev.evaluate(ast, ctx);
|
|
259
|
+
// Same script as HookFiresOnceForEachStatementAtEveryNestingLevel (7
|
|
260
|
+
// checkpoints without fast-continue) -- with hook-skippable mode and no
|
|
261
|
+
// breakpoints anywhere, every single one is a plain C++ skip.
|
|
262
|
+
EXPECT_EQ(calls, 0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
TEST(DebugHooks, FastContinueHookSkippableStillFiresAtMatchingBreakpointLine) {
|
|
266
|
+
std::vector<int> firedLines;
|
|
267
|
+
DebugHooks hooks;
|
|
268
|
+
hooks.debugHook = [&](int line, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
|
|
269
|
+
firedLines.push_back(line);
|
|
270
|
+
return DebugAction{};
|
|
271
|
+
};
|
|
272
|
+
Evaluator ev(EchoFn{}, nullptr, nullptr, hooks);
|
|
273
|
+
ev.setFastContinueBreakpoints(std::unordered_map<std::string, std::set<int>>{{"<string>", {2}}}, /*hookSkippable=*/true);
|
|
274
|
+
auto ast = parseSrc("cube(1);\ntranslate([1,0,0]) sphere(r=1,$fn=8);\necho(\"x\");");
|
|
275
|
+
auto scope = oscad::buildScopes(ast);
|
|
276
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
277
|
+
ev.evaluate(ast, ctx);
|
|
278
|
+
// Every line-2 checkpoint (translate itself, its 3 expr-level [1,0,0]
|
|
279
|
+
// list elements, and the nested sphere child -- 5 total, matching
|
|
280
|
+
// HookFiresOnceForEachStatementAtEveryNestingLevel's own sequence)
|
|
281
|
+
// survives the skip; line 1 (cube) and line 3 (echo) have no
|
|
282
|
+
// breakpoint and are skipped entirely.
|
|
283
|
+
EXPECT_EQ(firedLines, (std::vector<int>{2, 2, 2, 2, 2}));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
TEST(DebugHooks, FastContinueNotHookSkippableStillFiresEveryCheckpoint) {
|
|
287
|
+
int calls = 0;
|
|
288
|
+
DebugHooks hooks;
|
|
289
|
+
hooks.debugHook = [&](int, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
|
|
290
|
+
++calls;
|
|
291
|
+
return DebugAction{};
|
|
292
|
+
};
|
|
293
|
+
Evaluator ev(EchoFn{}, nullptr, nullptr, hooks);
|
|
294
|
+
// Mirrors step_over/step_out: a real (here, empty) breakpoints set is
|
|
295
|
+
// provided -- so chunkEligibleNow/useBytecodeVm still apply -- but
|
|
296
|
+
// hookSkippable stays false, since both need checkDebug() to keep
|
|
297
|
+
// calling into the hook on every statement so their own step_hit logic
|
|
298
|
+
// (line/depth comparison against the step's own starting point) can
|
|
299
|
+
// run; there is no way to decide that in advance.
|
|
300
|
+
ev.setFastContinueBreakpoints(std::unordered_map<std::string, std::set<int>>{}, /*hookSkippable=*/false);
|
|
301
|
+
auto ast = parseSrc("cube(1);\ntranslate([1,0,0]) sphere(r=1,$fn=8);\necho(\"x\");");
|
|
302
|
+
auto scope = oscad::buildScopes(ast);
|
|
303
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
304
|
+
ev.evaluate(ast, ctx);
|
|
305
|
+
EXPECT_EQ(calls, 7); // unaffected -- same total as HookFiresOnceForEachStatementAtEveryNestingLevel
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
TEST(DebugHooks, FastContinueInterruptFlagForcesTheNextCheckpointThrough) {
|
|
309
|
+
std::vector<int> firedLines;
|
|
310
|
+
DebugHooks hooks;
|
|
311
|
+
hooks.debugHook = [&](int line, int, bool, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
|
|
312
|
+
firedLines.push_back(line);
|
|
313
|
+
return DebugAction{};
|
|
314
|
+
};
|
|
315
|
+
Evaluator ev(EchoFn{}, nullptr, nullptr, hooks);
|
|
316
|
+
ev.setFastContinueBreakpoints(std::unordered_map<std::string, std::set<int>>{}, /*hookSkippable=*/true);
|
|
317
|
+
// Pre-armed, as if Pause (or a breakpoint edit) was requested from the
|
|
318
|
+
// main thread before this run even started -- see
|
|
319
|
+
// setFastContinueInterruptFlag's own doc comment (evaluator.hpp) for
|
|
320
|
+
// why this exists: there is no other way to reach a running
|
|
321
|
+
// debug_evaluate() call from outside a hook invocation.
|
|
322
|
+
auto flag = std::make_shared<std::atomic<bool>>(true);
|
|
323
|
+
ev.setFastContinueInterruptFlag(flag);
|
|
324
|
+
auto ast = parseSrc("cube(1);\ntranslate([1,0,0]) sphere(r=1,$fn=8);\necho(\"x\");");
|
|
325
|
+
auto scope = oscad::buildScopes(ast);
|
|
326
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
327
|
+
ev.evaluate(ast, ctx);
|
|
328
|
+
// Only the FIRST checkpoint (line 1, cube) fires -- checkDebug's own
|
|
329
|
+
// test-and-clear means the flag is consumed there, so every subsequent
|
|
330
|
+
// checkpoint goes back to being skipped normally (no breakpoints
|
|
331
|
+
// anywhere).
|
|
332
|
+
EXPECT_EQ(firedLines, (std::vector<int>{1}));
|
|
333
|
+
EXPECT_FALSE(flag->load());
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
TEST(DebugHooks, ForcedBreakpointBuiltinAlwaysFiresEvenInHookSkippableMode) {
|
|
337
|
+
int forcedCalls = 0, normalCalls = 0;
|
|
338
|
+
DebugHooks hooks;
|
|
339
|
+
hooks.debugHook = [&](int, int, bool forced, bool, const std::string&, const std::vector<CallStackFrame>&, const DebugFramesFn&) {
|
|
340
|
+
(forced ? forcedCalls : normalCalls)++;
|
|
341
|
+
return DebugAction{};
|
|
342
|
+
};
|
|
343
|
+
Evaluator ev(EchoFn{}, nullptr, nullptr, hooks);
|
|
344
|
+
ev.setFastContinueBreakpoints(std::unordered_map<std::string, std::set<int>>{}, /*hookSkippable=*/true);
|
|
345
|
+
auto ast = parseSrc("cube(1);\nbreakpoint();\nsphere(r=1,$fn=8);");
|
|
346
|
+
auto scope = oscad::buildScopes(ast);
|
|
347
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
348
|
+
ev.evaluate(ast, ctx);
|
|
349
|
+
EXPECT_EQ(forcedCalls, 1); // breakpoint() always bypasses hook-skippable mode
|
|
350
|
+
EXPECT_EQ(normalCalls, 0); // cube/breakpoint()-as-statement/sphere: no breakpoint line matches, all skipped
|
|
351
|
+
}
|
|
352
|
+
|
|
233
353
|
// ---------------------------------------------------------------------------
|
|
234
354
|
// Full parity with the Python reference's _check_debug call sites.
|
|
235
355
|
//
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
#include "test_helpers.hpp"
|
|
21
21
|
|
|
22
|
+
#include <chrono>
|
|
22
23
|
#include <gtest/gtest.h>
|
|
23
24
|
|
|
24
25
|
using namespace oscadeval;
|
|
@@ -126,17 +127,6 @@ TEST(TailCalls, NonTailRecursionIsUnaffectedByTheTrampoline) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
TEST(TailCalls, InfiniteTailRecursionHitsTheIterationCapInsteadOfHanging) {
|
|
129
|
-
#ifndef __APPLE__
|
|
130
|
-
// Skipped on non-Apple platforms: this drives the interpreter trampoline
|
|
131
|
-
// to the full 1,000,000-iteration recursion-guard cap, which is O(N)
|
|
132
|
-
// under Clang/libc++ (~1s) but a confirmed O(N^2) under GCC/libstdc++
|
|
133
|
-
// (and, empirically, MSVC's STL too) -- 25-50+ minutes instead of
|
|
134
|
-
// seconds. See https://github.com/BelfrySCAD/openscad_cpp_evaluator/issues/50
|
|
135
|
-
// for the full investigation (peak RSS/CPU/instruction-count evidence
|
|
136
|
-
// and an N-scaling experiment proving the asymptotic-complexity gap).
|
|
137
|
-
// Re-enable once that's root-caused and fixed.
|
|
138
|
-
GTEST_SKIP() << "O(N^2) under this toolchain -- see issue #50";
|
|
139
|
-
#endif
|
|
140
130
|
ScopedVm vm(false);
|
|
141
131
|
Evaluator ev;
|
|
142
132
|
auto ast = parseSrc("function loop(n) = loop(n + 1);\nresult = loop(0);");
|
|
@@ -150,11 +140,6 @@ TEST(TailCalls, InfiniteTailRecursionHitsTheIterationCapInsteadOfHanging) {
|
|
|
150
140
|
}
|
|
151
141
|
|
|
152
142
|
TEST(TailCalls, InfiniteTailRecursionErrorMentionsTheFunctionName) {
|
|
153
|
-
#ifndef __APPLE__
|
|
154
|
-
// See InfiniteTailRecursionHitsTheIterationCapInsteadOfHanging's own
|
|
155
|
-
// comment just above -- same O(N^2)-under-this-toolchain issue (#50).
|
|
156
|
-
GTEST_SKIP() << "O(N^2) under this toolchain -- see issue #50";
|
|
157
|
-
#endif
|
|
158
143
|
ScopedVm vm(false);
|
|
159
144
|
Evaluator ev;
|
|
160
145
|
auto ast = parseSrc("function loop(n) = loop(n + 1);\nresult = loop(0);");
|
|
@@ -169,6 +154,34 @@ TEST(TailCalls, InfiniteTailRecursionErrorMentionsTheFunctionName) {
|
|
|
169
154
|
}
|
|
170
155
|
}
|
|
171
156
|
|
|
157
|
+
TEST(TailCalls, InfiniteTailRecursionHitsTheCapInBoundedWallTime) {
|
|
158
|
+
// Regression tripwire for issue #50: evalFunctionBodyTrampoline's own
|
|
159
|
+
// `chain` (every hop's EvalContext, kept alive for $-var ancestry --
|
|
160
|
+
// see its own doc comment) used to rely on std::vector<EvalContext>'s
|
|
161
|
+
// OWN destructor to tear itself down, whose element-destruction order
|
|
162
|
+
// is unspecified by the standard. libstdc++ (GCC) and, per CI evidence,
|
|
163
|
+
// MSVC destroy front-to-back -- the adversarial order for
|
|
164
|
+
// ScopeTrailStorage::popLevel's "scan from the back" optimization
|
|
165
|
+
// (its own doc comment), turning an intended O(N) teardown into a
|
|
166
|
+
// real, measured O(N^2): the exact same 1,000,000-iteration script
|
|
167
|
+
// below took 25-50+ minutes on GCC/MSVC CI runners before the fix
|
|
168
|
+
// (an explicit back-to-front teardown, immune to the underlying
|
|
169
|
+
// vector's own unspecified order), vs ~1s on Clang/libc++ throughout.
|
|
170
|
+
// ponytail: generous fixed ceiling, not a strict perf target -- trips
|
|
171
|
+
// only on a real regression (this exact O(N^2) reintroduced), not
|
|
172
|
+
// machine noise; raise if a slower CI runner ever needs it.
|
|
173
|
+
ScopedVm vm(false);
|
|
174
|
+
Evaluator ev;
|
|
175
|
+
auto ast = parseSrc("function loop(n) = loop(n + 1);\nresult = loop(0);");
|
|
176
|
+
auto scope = oscad::buildScopes(ast);
|
|
177
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
178
|
+
const auto start = std::chrono::steady_clock::now();
|
|
179
|
+
EXPECT_THROW(ev.resolveTree(ast, ctx), EvalError);
|
|
180
|
+
const double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
|
181
|
+
constexpr double kCeilingMs = 30000.0;
|
|
182
|
+
EXPECT_LT(ms, kCeilingMs) << "took " << ms << "ms -- possible O(N^2) regression, see issue #50";
|
|
183
|
+
}
|
|
184
|
+
|
|
172
185
|
TEST(TailCalls, ErrorThrownDeepInATailChainProducesABoundedTrace) {
|
|
173
186
|
ScopedVm vm(false);
|
|
174
187
|
Evaluator ev;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|