openscad-cpp-evaluator 1.15.0__tar.gz → 1.19.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-1.15.0 → openscad_cpp_evaluator-1.19.0}/CLAUDE.md +88 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/bindings/module.cpp +68 -14
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bytecode.hpp +6 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +5 -5
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/colored_body.hpp +43 -0
- openscad_cpp_evaluator-1.19.0/include/openscad_cpp_evaluator/coverage.hpp +116 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/eval_use.hpp +12 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/evaluator.hpp +50 -1
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/scope_trail.hpp +40 -4
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/python/openscad_cpp_evaluator/__init__.py +18 -5
- openscad_cpp_evaluator-1.19.0/src/.DS_Store +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/CMakeLists.txt +1 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/booleans.cpp +32 -3
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/color.cpp +2 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/control.cpp +1 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/bytecode_compiler.cpp +35 -16
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/bytecode_vm.cpp +5 -1
- openscad_cpp_evaluator-1.19.0/src/coverage.cpp +397 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/csg_generate.cpp +22 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/csg_resolve.cpp +13 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/eval_use.cpp +6 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/expr_eval.cpp +42 -2
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/stmt_eval.cpp +9 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/user_calls.cpp +13 -7
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/CMakeLists.txt +1 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_booleans.cpp +27 -0
- openscad_cpp_evaluator-1.19.0/tests/test_coverage.cpp +234 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_include_cache.cpp +2 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_manifold_cache.cpp +7 -8
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_python_bindings.py +33 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_scope_trail.cpp +64 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_use.cpp +56 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/cli_lib.cpp +1 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.15.0 → openscad_cpp_evaluator-1.19.0}/uv.lock +0 -0
|
@@ -449,6 +449,39 @@ closed in one pass after a fresh "anything still unimplemented?" audit. Each bel
|
|
|
449
449
|
all (`resolve_use_scopes`/`UseStatement` had zero hits in `openscad_evaluator`'s own `tests/`), so
|
|
450
450
|
these were verified against real reference *behavior*, not ported from an existing test.
|
|
451
451
|
|
|
452
|
+
**File-level globals are evaluated ONCE per run (v1.16.0).** The main file's and every
|
|
453
|
+
`include`'s are, by the top-level statement pass, into the root `let_` level; a `use`d file's
|
|
454
|
+
are not -- `use` injects only declarations -- so `Evaluator::fileGlobal` evaluates ALL of that
|
|
455
|
+
file's top-level assignments, in source order, into a context of its own the first time anything
|
|
456
|
+
in the file reads one, and keeps it for the run. `ResolvedUseScopes::usedFileGlobals` carries
|
|
457
|
+
the per-file assignment lists and every entry point hands them over with
|
|
458
|
+
`Evaluator::setUsedFileGlobals` (CLI, both binding paths, the tests); a caller that skips that
|
|
459
|
+
keeps the old per-read fallback. Before this, `evalIdentifier`'s scope fallback re-evaluated the
|
|
460
|
+
assignment's expression on EVERY read from inside a function -- `callCtx()` isolates `let_` so the
|
|
461
|
+
root level was invisible -- which made `r = rands(0,1,1); function g() = r;` give `g() != g()`
|
|
462
|
+
(OpenSCAD: equal) and a 20K-element global list read 2000 times from a function cost 9.7s
|
|
463
|
+
(now 0.02s). Two deliberate divergences from OpenSCAD 2026.02.01, both verified against it:
|
|
464
|
+
a used file's globals are evaluated once, not once per call into the file (upstream's per-call
|
|
465
|
+
re-evaluation is a long-standing bug there, so its forward-read warning prints once here, not
|
|
466
|
+
per call); and a global read from a function BEFORE its assignment has run is `undef` with an
|
|
467
|
+
"Ignoring unknown variable" warning, exactly as upstream, where the old fallback had quietly
|
|
468
|
+
computed the not-yet-assigned value.
|
|
469
|
+
**Scope-trail level table shrinks (v1.18.0).** `ScopeTrailStorage::parent_` and its indexed twin
|
|
470
|
+
map level -> parent level in a vector indexed by level. Levels came from `++nextLevel_` and were
|
|
471
|
+
never reused, so the vector grew 4 bytes per level for the whole run -- 3 x 64MB on a 13M-call
|
|
472
|
+
script, plus the copy at every doubling -- and was 192MB of fractal_tree.scad's peak. A popped
|
|
473
|
+
level is now marked -1 (0 still means "live, no parent": an isolated root), and when the NEWEST
|
|
474
|
+
level dies `markDead` pops the vector back past every dead level above the newest live one, whose
|
|
475
|
+
numbers `openLevel` then hands out again. Every invariant `lookup()`'s sorted-merge walk relies on
|
|
476
|
+
holds: a reused number is greater than every level still holding an entry (all higher levels were
|
|
477
|
+
dead and `popLevel` had already removed their entries), so a name's push-stack stays ascending,
|
|
478
|
+
and a live level's number never changes -- an escaping closure that keeps a level alive simply
|
|
479
|
+
stops the shrink at it (`tests/test_scope_trail.cpp` covers both). The ancestry walk now stops at
|
|
480
|
+
`ancestor > 0` rather than `!= 0`. Same PR: `callCtxFor`'s per-call scan over active declarations
|
|
481
|
+
does the integer span test before the origin STRING compare (`_platform_memcmp` was 1.4% of
|
|
482
|
+
fractal_tree), and driveVm's completion branch no longer copies the finished frame's name. Anklet
|
|
483
|
+
74 -> 63MB, a 4M-call synthetic 380 -> 258MB, fractal_tree -3.4% wall.
|
|
484
|
+
|
|
452
485
|
**Known gap closed in Phase 3, worth remembering for Phase 5+**: every builtin resolve function must
|
|
453
486
|
call `resolveCallArgs` (call_args.hpp), never bare `resolveArgs`, or a `$`-prefixed named call
|
|
454
487
|
argument (`circle(r=2, $fn=64)`) silently fails to reach `ctx.dyn`. Caught by a geometric-correctness
|
|
@@ -910,6 +943,28 @@ grep for `ponytail:`.
|
|
|
910
943
|
operation`. `linear_extrude`/`rotate_extrude`/`offset` are always-2D and `projection` always-3D, so
|
|
911
944
|
they skip the "mixing" line and emit only the second; `roof()` emits neither, matching the
|
|
912
945
|
reference. Background (`%`) children are exempt.
|
|
946
|
+
**The emptiness test is cached on the body (`ColoredBody::knownEmpty`, v1.17.0).** It used to
|
|
947
|
+
call `Manifold::NumTri()` on every child at every level, and every Manifold accessor goes through
|
|
948
|
+
`GetImpl()`, which materialises a lazy transform (a full mesh + collider copy) and runs a lazy
|
|
949
|
+
boolean -- so each leaf was copied once per ancestor transform, and because every node kept its
|
|
950
|
+
`bodies` until the tree died, all those copies stayed alive: fractal_tree.scad held 72,929
|
|
951
|
+
transformed copies of 1024 leaves (3.1GB). Now a body is asked once, the flag rides along on the
|
|
952
|
+
struct copies transform/colour/flatten make (emptiness is transform-invariant, and every generate
|
|
953
|
+
function that builds a new body starts from a fresh `ColoredBody`), and `generateTreeImpl` clears
|
|
954
|
+
each child's `bodies` once the parent has consumed them (move-assigning a fresh vector: `clear()`
|
|
955
|
+
and `= {}` both keep the capacity, and 35K nodes x 592-byte slots was 230MB) -- only top-level
|
|
956
|
+
bodies leave, a cache hit re-fills from the cache, and the bindings' `csg_tree` facade no longer
|
|
957
|
+
converts per-node bodies (it used to `GetMeshGL()` every node into numpy on every evaluate(), for
|
|
958
|
+
a dump that never read them). Three more forcing points had to go for the chain to stay lazy: the
|
|
959
|
+
booleans' `Status()` operand filter (now `bodyStatus()`, same cache), the boolean RESULT's own
|
|
960
|
+
emptiness (derived from the operands' flags -- a union is non-empty if any operand is -- rather
|
|
961
|
+
than asked), and `attachTriColors`, which `GetMeshGL()`'d every boolean result and now runs only
|
|
962
|
+
when the operands could actually disagree on colour. `Evaluator::generatedNodeCount` replaced
|
|
963
|
+
"does the child have bodies" as the cache-hit proxy in tests. Transforms compose lazily inside
|
|
964
|
+
Manifold and each leaf is transformed once, at the boolean that needs it. fractal_tree 3.1GB ->
|
|
965
|
+
0.67GB, Dalek 370 -> 277MB, snappy-reprap full_assembly 1.6GB -> 0.87GB; wall time neutral to
|
|
966
|
+
-3% interleaved. The output is geometrically identical (triangle count, area, volume) but not
|
|
967
|
+
byte-identical: Manifold evaluates one flat batch union instead of our old level-by-level order.
|
|
913
968
|
|
|
914
969
|
Doing this centrally is also what keeps `generateCsg` safe: it builds ONE result and switches on
|
|
915
970
|
whether it holds a `body` or a `section`, so a group that changed dimension part-way dereferenced
|
|
@@ -1425,6 +1480,39 @@ than the current pause point). Ported identically (same qualifier syntax, same e
|
|
|
1425
1480
|
- `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
|
|
1426
1481
|
real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
|
|
1427
1482
|
|
|
1483
|
+
## Coverage
|
|
1484
|
+
|
|
1485
|
+
`Evaluator(coverage=true)` records which parts of a run's source actually executed and
|
|
1486
|
+
fills `coverageResult`: `spans` (one per coverable node), `files` (one `CoverageFileSummary`
|
|
1487
|
+
per origin, sorted: statements/branches/bodies/spans, each with a hit count and a percent;
|
|
1488
|
+
`branches` counts Branch spans plus arm Statements, `percent` is over every span) and `total`
|
|
1489
|
+
(the same over every file). `summarizeCoverage()` is public so an aggregated span list can be
|
|
1490
|
+
re-summarised. Bindings: `Evaluator(coverage=True)` → `ev.coverage_result`, a dict
|
|
1491
|
+
`{spans, files, total}` of plain dicts. The vocabulary is fixed in one place,
|
|
1492
|
+
`coverage.hpp`/`coverage.cpp`, and used by both the recorder and the universe walk, so
|
|
1493
|
+
"covered" and "uncovered" never disagree about what counts:
|
|
1494
|
+
|
|
1495
|
+
- **statement** — every node in a statement list (top level, module/if/for/let bodies,
|
|
1496
|
+
`render()` children). A modifier (`%`/`#`/`!`/`*`) is the statement; the call it wraps is
|
|
1497
|
+
not one of its own. The first statement of an if/else arm carries `arm=true`.
|
|
1498
|
+
- **branch** — a ternary arm, a comprehension if/else arm, the right operand of `&&`/`||`.
|
|
1499
|
+
- **body** — a function, module or function-literal declaration: was it ever entered?
|
|
1500
|
+
|
|
1501
|
+
Hits are recorded at deliberate points, once per execution: `evalChildren`'s statement loop
|
|
1502
|
+
and `Op::Cover` (emitted per statement by `compileOneStatement`/`tryCompileAssignmentBlock`
|
|
1503
|
+
and at every arm, only when the Evaluator was built with coverage — chunks are per-Evaluator)
|
|
1504
|
+
on the two paths; `enterUserCall` for bodies; the arm sites in `evalExpr`, the tail
|
|
1505
|
+
trampoline (`simplifyTailStep`, which bypasses `evalExpr`'s own TernaryOp case) and the
|
|
1506
|
+
comprehension if/else cases. **Not** from `checkDebug`: the debugger checkpoints an if-arm's
|
|
1507
|
+
first statement twice and the `if` itself never, so piggybacking on it gave counts that
|
|
1508
|
+
differed between the VM and the interpreter. `Coverage.VmAndInterpreterAgree` compares the
|
|
1509
|
+
covered set under both and is the test that keeps them honest. The VM stays on under
|
|
1510
|
+
coverage (unlike a debug hook). The hit table is indexed by the node's `(treeId, slot)`
|
|
1511
|
+
numbering, so a hit is an array increment. Used-file globals are in the universe via
|
|
1512
|
+
`usedFileGlobals_`; everything else comes from the processed node list with each node's own
|
|
1513
|
+
`origin`, so includes and `use`d files report under their own paths. Let-bindings and plain
|
|
1514
|
+
comprehension elements are not in the vocabulary; hits on them are simply not reported.
|
|
1515
|
+
|
|
1428
1516
|
## `render()` in expression position
|
|
1429
1517
|
|
|
1430
1518
|
`render()` has two jobs, decided by where it is written:
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
#include "openscad_cpp_evaluator/freetype_font_provider.hpp"
|
|
27
27
|
#include "openscad_cpp_evaluator/mesh_check.hpp"
|
|
28
28
|
#include "openscad_cpp_evaluator/manifold_cache.hpp"
|
|
29
|
+
#include "openscad_cpp_evaluator/coverage.hpp"
|
|
29
30
|
#include "openscad_cpp_evaluator/profile.hpp"
|
|
30
31
|
#include "openscad_cpp_evaluator/value.hpp"
|
|
31
32
|
|
|
@@ -223,8 +224,9 @@ nb::dict idSpansToDict(const std::vector<IdSpan>& idSpans) {
|
|
|
223
224
|
return d;
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
// CSGNode -> a facade `_CSGNode` (kind/params/
|
|
227
|
-
// recursively, eagerly converting every field
|
|
227
|
+
// CSGNode -> a facade `_CSGNode` (kind/params/is_builtin/children; `bodies`
|
|
228
|
+
// is always empty, see below), recursively, eagerly converting every field
|
|
229
|
+
// to plain Python data. No
|
|
228
230
|
// lifetime extension needed -- CSGParams (Value-only, see csg_node.hpp) and
|
|
229
231
|
// ColoredBody never hold AST pointers, so the result is fully self-contained
|
|
230
232
|
// once built, unlike CSGNode::node itself (deliberately not exposed: the AST
|
|
@@ -234,15 +236,14 @@ nb::dict idSpansToDict(const std::vector<IdSpan>& idSpans) {
|
|
|
234
236
|
nb::object csgNodeToPy(const oscadeval::CSGNode& node) {
|
|
235
237
|
nb::dict params;
|
|
236
238
|
for (const auto& [k, v] : node.params) params[nb::str(k.c_str())] = valueToPy(v);
|
|
239
|
+
// Deliberately no bodies. This used to GetMeshGL() every body of every
|
|
240
|
+
// node in the tree into numpy on every evaluate() -- and the only
|
|
241
|
+
// consumer, format_csg_tree(), never read them. Since v1.17.0 an inner
|
|
242
|
+
// node's bodies are released once its parent has consumed them (see
|
|
243
|
+
// generateTreeImpl), so there is nothing here to convert anyway; and a
|
|
244
|
+
// body the tree still holds may be a LAZY Manifold whose GetMeshGL()
|
|
245
|
+
// would materialise the very transform that release exists to avoid.
|
|
237
246
|
nb::list bodies;
|
|
238
|
-
for (const oscadeval::ColoredBody& cb : node.bodies) {
|
|
239
|
-
if (!cb.isDisplayOnly() && (!cb.body || cb.body->IsEmpty())) continue;
|
|
240
|
-
// ColoredBody here is const in this walk (tree ownership stays with
|
|
241
|
-
// the caller) -- GetMeshGL() itself is logically read-only, so a
|
|
242
|
-
// const_cast here is safe/local, mirroring bodyToDict's mutating
|
|
243
|
-
// signature only because manifold3d's own API isn't const-qualified.
|
|
244
|
-
bodies.append(bodyToDict(const_cast<oscadeval::ColoredBody&>(cb)));
|
|
245
|
-
}
|
|
246
247
|
nb::list children;
|
|
247
248
|
for (const std::unique_ptr<oscadeval::CSGNode>& child : node.children) children.append(csgNodeToPy(*child));
|
|
248
249
|
return facadeAttr("_CSGNode")(node.kind, params, bodies, node.isBuiltin, children);
|
|
@@ -432,9 +433,51 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
|
|
|
432
433
|
return out;
|
|
433
434
|
}
|
|
434
435
|
|
|
436
|
+
nb::dict coverageSummaryToPy(const oscadeval::CoverageFileSummary& f) {
|
|
437
|
+
nb::dict d;
|
|
438
|
+
d["origin"] = f.origin;
|
|
439
|
+
d["statements"] = f.statements;
|
|
440
|
+
d["statements_hit"] = f.statements_hit;
|
|
441
|
+
d["branches"] = f.branches;
|
|
442
|
+
d["branches_hit"] = f.branches_hit;
|
|
443
|
+
d["bodies"] = f.bodies;
|
|
444
|
+
d["bodies_hit"] = f.bodies_hit;
|
|
445
|
+
d["spans"] = f.spans;
|
|
446
|
+
d["spans_hit"] = f.spans_hit;
|
|
447
|
+
d["percent"] = f.percent();
|
|
448
|
+
d["statement_percent"] = f.statement_percent();
|
|
449
|
+
d["branch_percent"] = f.branch_percent();
|
|
450
|
+
d["body_percent"] = f.body_percent();
|
|
451
|
+
return d;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
nb::object coverageResultToPy(const std::optional<oscadeval::CoverageResult>& cr) {
|
|
455
|
+
if (!cr) return nb::none();
|
|
456
|
+
nb::list files;
|
|
457
|
+
for (const oscadeval::CoverageFileSummary& f : cr->files) files.append(coverageSummaryToPy(f));
|
|
458
|
+
nb::list out;
|
|
459
|
+
for (const oscadeval::CoverageSpan& s : cr->spans) {
|
|
460
|
+
nb::dict d;
|
|
461
|
+
d["origin"] = s.origin;
|
|
462
|
+
d["line"] = s.line;
|
|
463
|
+
d["column"] = s.column;
|
|
464
|
+
d["start"] = s.start_offset;
|
|
465
|
+
d["end"] = s.end_offset;
|
|
466
|
+
d["kind"] = oscadeval::coverageKindName(s.kind);
|
|
467
|
+
d["arm"] = s.arm;
|
|
468
|
+
d["hits"] = s.hits;
|
|
469
|
+
out.append(d);
|
|
470
|
+
}
|
|
471
|
+
nb::dict result;
|
|
472
|
+
result["spans"] = out;
|
|
473
|
+
result["files"] = files;
|
|
474
|
+
result["total"] = coverageSummaryToPy(cr->total);
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
|
|
435
478
|
nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
436
479
|
std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
|
|
437
|
-
bool generate, bool strictCommas) {
|
|
480
|
+
bool generate, bool strictCommas, bool coverage) {
|
|
438
481
|
std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
|
|
439
482
|
|
|
440
483
|
std::vector<oscadeval::ColoredBody> bodies;
|
|
@@ -442,6 +485,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
|
442
485
|
std::vector<IdSpan> idSpans;
|
|
443
486
|
std::vector<std::unique_ptr<oscadeval::CSGNode>> csgTree;
|
|
444
487
|
std::optional<oscadeval::ProfileResult> profileResult;
|
|
488
|
+
std::optional<oscadeval::CoverageResult> coverageResult;
|
|
445
489
|
nb::dict dyn;
|
|
446
490
|
nb::object dynExplicit;
|
|
447
491
|
{
|
|
@@ -456,7 +500,8 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
|
456
500
|
if (strictCommas) strict.emplace();
|
|
457
501
|
oscad::ParsedProgram program = oscad::getProgramFromFile(path);
|
|
458
502
|
oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
|
|
459
|
-
oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
|
|
503
|
+
oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile, coverage);
|
|
504
|
+
ev.setUsedFileGlobals(used.usedFileGlobals);
|
|
460
505
|
oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
|
|
461
506
|
bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp, generate));
|
|
462
507
|
collectIdSpans(ev, idSpans);
|
|
@@ -465,6 +510,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
|
465
510
|
// which a resolve-only caller has no use for.
|
|
466
511
|
if (generate) csgTree = std::move(ev.csgTree);
|
|
467
512
|
profileResult = std::move(ev.profileResult);
|
|
513
|
+
coverageResult = std::move(ev.coverageResult);
|
|
468
514
|
{
|
|
469
515
|
nb::gil_scoped_acquire g; // building Python objects needs the GIL back
|
|
470
516
|
std::tie(dyn, dynExplicit) = dynStateToPy(ctx);
|
|
@@ -494,7 +540,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
|
|
|
494
540
|
auto geom = std::make_shared<Geometry>();
|
|
495
541
|
geom->bodies = std::move(bodies);
|
|
496
542
|
return nb::make_tuple(bodiesToList(geom->bodies), echoList, idSpansToDict(idSpans), csgTreeToPy(csgTree),
|
|
497
|
-
profileResultToPy(profileResult), dyn, dynExplicit, geom);
|
|
543
|
+
profileResultToPy(profileResult), dyn, dynExplicit, geom, coverageResultToPy(coverageResult));
|
|
498
544
|
}
|
|
499
545
|
|
|
500
546
|
// ------------------------------------------------------------------------
|
|
@@ -764,6 +810,7 @@ nb::object debugEvaluate(const std::string& path, nb::dict viewportParams, nb::c
|
|
|
764
810
|
oscadeval::Evaluator ev(echoCpp, nullptr, manifoldCache, hooks, false);
|
|
765
811
|
evPtr = &ev;
|
|
766
812
|
if (fastContinueSignal) ev.setFastContinueInterruptFlag(fastContinueSignal->flag());
|
|
813
|
+
ev.setUsedFileGlobals(used.usedFileGlobals);
|
|
767
814
|
oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
|
|
768
815
|
bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp));
|
|
769
816
|
collectIdSpans(ev, idSpans);
|
|
@@ -908,7 +955,14 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
|
|
|
908
955
|
|
|
909
956
|
m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
|
|
910
957
|
nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
|
|
911
|
-
"
|
|
958
|
+
nb::arg("coverage") = false,
|
|
959
|
+
"Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit, "
|
|
960
|
+
"geometry, coverage_result).\n"
|
|
961
|
+
"coverage=True records which statements, branch arms and bodies ran: coverage_result is a dict "
|
|
962
|
+
"{spans, files, total}: spans is a list of dicts (origin, line, column, start, end, kind, arm, hits), "
|
|
963
|
+
"one per coverable node of every file the run touched; files is one summary per origin, sorted "
|
|
964
|
+
"(statements/statements_hit, branches/branches_hit, bodies/bodies_hit, spans/spans_hit and the four "
|
|
965
|
+
"percent fields); total is the same over every file. None when off.\n"
|
|
912
966
|
"generate=False stops after the resolve pass: the script runs and reports everything "
|
|
913
967
|
"it normally would, but no Manifold geometry is built, and neither bodies nor csg_tree "
|
|
914
968
|
"are populated.\n\n"
|
|
@@ -728,6 +728,12 @@ enum class Op {
|
|
|
728
728
|
// on failure, otherwise runs any chained node.children natively (rare;
|
|
729
729
|
// not worth its own compiled path -- see AssertSite's own doc comment).
|
|
730
730
|
AssertStatement,
|
|
731
|
+
// a = nativeStatements index of the AST node whose arm this is. Emitted
|
|
732
|
+
// only when the Evaluator was built with coverage on (compile-time
|
|
733
|
+
// flag, and chunks are per-Evaluator): Evaluator::coverHit(node), nothing
|
|
734
|
+
// else. Marks the arms the debugger never checkpoints in compiled code
|
|
735
|
+
// -- ternary arms, comprehension if/else arms, `&&`/`||` right operands.
|
|
736
|
+
Cover,
|
|
731
737
|
};
|
|
732
738
|
|
|
733
739
|
struct Instruction {
|
|
@@ -21,7 +21,7 @@ namespace oscadeval {
|
|
|
21
21
|
// whole function, unconditionally correct since nothing about this
|
|
22
22
|
// function's behavior changes based on whether it happened to compile.
|
|
23
23
|
std::optional<CompiledChunk> tryCompileFunction(const oscad::FunctionDeclaration& decl,
|
|
24
|
-
const oscad::ScopeTable* scopeTable);
|
|
24
|
+
const oscad::ScopeTable* scopeTable, bool coverage = false);
|
|
25
25
|
|
|
26
26
|
// Compiles a bare STATEMENT-context expression -- an assignment's RHS, an
|
|
27
27
|
// if/for condition, a module-call or echo()/assert() argument -- rather
|
|
@@ -50,7 +50,7 @@ std::optional<CompiledChunk> tryCompileFunction(const oscad::FunctionDeclaration
|
|
|
50
50
|
// any callee inside it statically, the same way a function body's call
|
|
51
51
|
// sites are.
|
|
52
52
|
std::optional<CompiledChunk> tryCompileStatementExpr(const oscad::Expression& expr, const oscad::Scope* scope,
|
|
53
|
-
const oscad::ScopeTable* scopeTable);
|
|
53
|
+
const oscad::ScopeTable* scopeTable, bool coverage = false);
|
|
54
54
|
|
|
55
55
|
// Compiles a run of SIBLING assignment statements sharing one scope --
|
|
56
56
|
// exactly Evaluator::evalChildren's own `assignments` sub-list (stmt_eval.
|
|
@@ -85,7 +85,7 @@ std::optional<CompiledChunk> tryCompileStatementExpr(const oscad::Expression& ex
|
|
|
85
85
|
// instead of just for that one assignment's own RHS.
|
|
86
86
|
std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const oscad::Assignment*>& assigns,
|
|
87
87
|
const oscad::ScopeTable* scopeTable,
|
|
88
|
-
const oscad::Scope* scope);
|
|
88
|
+
const oscad::Scope* scope, bool coverage = false);
|
|
89
89
|
|
|
90
90
|
// Attempts to compile `decl`'s parameter defaults + STATEMENT-list body
|
|
91
91
|
// (decl.children) to bytecode (Stage 2) -- the module-side analog of
|
|
@@ -101,7 +101,7 @@ std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const o
|
|
|
101
101
|
// EXPRESSION (inside an if-condition/for-range/argument) can still bail
|
|
102
102
|
// (via tryCompileStatementExpr's own NotCompilable, propagated up).
|
|
103
103
|
std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration& decl,
|
|
104
|
-
const oscad::ScopeTable* scopeTable);
|
|
104
|
+
const oscad::ScopeTable* scopeTable, bool coverage = false);
|
|
105
105
|
|
|
106
106
|
// Same compilation (assignment/if/for/resolved-module-call get real
|
|
107
107
|
// bytecode, everything else a native passthrough), but for an ARBITRARY
|
|
@@ -126,6 +126,6 @@ std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration
|
|
|
126
126
|
// scope()`, mirroring tryRunCompiledAssignmentBlock's own convention.
|
|
127
127
|
std::optional<CompiledChunk> tryCompileChildrenList(const std::vector<const oscad::ASTNode*>& children,
|
|
128
128
|
const oscad::ScopeTable* scopeTable,
|
|
129
|
-
const oscad::Scope* scope);
|
|
129
|
+
const oscad::Scope* scope, bool coverage = false);
|
|
130
130
|
|
|
131
131
|
} // namespace oscadeval
|
|
@@ -53,6 +53,22 @@ struct ColoredBody {
|
|
|
53
53
|
BodyRole role = BodyRole::Normal;
|
|
54
54
|
std::optional<std::vector<std::array<float, 4>>> triColors; // per-triangle RGBA, multi-color CSG merges only
|
|
55
55
|
|
|
56
|
+
// Whether `body` is empty, once anything has asked (isEmptyBody,
|
|
57
|
+
// csg_generate.cpp). Asking Manifold is not free: every accessor goes
|
|
58
|
+
// through GetImpl(), which MATERIALIZES a lazy transform (a full copy of
|
|
59
|
+
// the mesh and its collider) and, for a boolean, runs it. Emptiness is
|
|
60
|
+
// invariant under transforms and colour, and every generate function
|
|
61
|
+
// that produces a genuinely new body starts from a fresh ColoredBody,
|
|
62
|
+
// so a copy carrying this flag is always still right. Before this, the
|
|
63
|
+
// dimension check at every tree level materialised every child at
|
|
64
|
+
// every level: fractal_tree.scad held 72,929 transformed copies of its
|
|
65
|
+
// 1024 leaf meshes at once (3.1GB).
|
|
66
|
+
std::optional<bool> knownEmpty;
|
|
67
|
+
// Same idea for Manifold::Status(), which the booleans consult to keep an
|
|
68
|
+
// invalid operand from poisoning a union: validity is transform-invariant
|
|
69
|
+
// too, and a boolean of valid operands is valid.
|
|
70
|
+
std::optional<manifold::Manifold::Error> knownStatus;
|
|
71
|
+
|
|
56
72
|
// Set ONLY when a mesh could not be built into a valid Manifold -- in
|
|
57
73
|
// practice a polyhedron() whose faces don't close the surface (it has
|
|
58
74
|
// boundary edges), which Manifold reports by returning an *empty* body
|
|
@@ -75,6 +91,33 @@ struct ColoredBody {
|
|
|
75
91
|
bool isDisplayOnly() const { return rawMesh.has_value(); }
|
|
76
92
|
};
|
|
77
93
|
|
|
94
|
+
// The cached questions -- see ColoredBody::knownEmpty. Ask Manifold once per
|
|
95
|
+
// body, never once per tree level.
|
|
96
|
+
inline bool bodyIsEmpty(ColoredBody& b) {
|
|
97
|
+
if (!b.knownEmpty) b.knownEmpty = !b.body || b.body->IsEmpty();
|
|
98
|
+
return *b.knownEmpty;
|
|
99
|
+
}
|
|
100
|
+
inline manifold::Manifold::Error bodyStatus(ColoredBody& b) {
|
|
101
|
+
if (!b.knownStatus) b.knownStatus = b.body ? b.body->Status() : manifold::Manifold::Error::NoError;
|
|
102
|
+
return *b.knownStatus;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Emptiness of a union, from its operands' own flags and nothing else:
|
|
106
|
+
// non-empty if any operand is known non-empty, empty if every one is known
|
|
107
|
+
// empty, otherwise unknown (asked of Manifold later, if anyone ever asks).
|
|
108
|
+
// `get` maps a range element to its ColoredBody.
|
|
109
|
+
template <typename Range, typename Get>
|
|
110
|
+
std::optional<bool> unionEmptyOf(const Range& ops, Get get) {
|
|
111
|
+
bool allKnownEmpty = true;
|
|
112
|
+
for (const auto& o : ops) {
|
|
113
|
+
const ColoredBody& b = get(o);
|
|
114
|
+
if (b.knownEmpty && !*b.knownEmpty) return false;
|
|
115
|
+
if (!b.knownEmpty) allKnownEmpty = false;
|
|
116
|
+
}
|
|
117
|
+
if (allKnownEmpty) return true;
|
|
118
|
+
return std::nullopt;
|
|
119
|
+
}
|
|
120
|
+
|
|
78
121
|
// `EvalContext::color`/CSGParams round-trip: a resolve function reads
|
|
79
122
|
// ctx.color (double-precision, matching Value's own numeric type) but a
|
|
80
123
|
// generate function only sees CSGParams (Value-typed, no ctx access) -- so
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Coverage: which parts of a script (and of the libraries it pulls in) ran.
|
|
4
|
+
//
|
|
5
|
+
// The unit of coverage is the checkpoint set the debugger already uses --
|
|
6
|
+
// Evaluator::checkDebug fires for every statement, every function/module
|
|
7
|
+
// body entered, every if/else arm's first statement, every ternary arm and
|
|
8
|
+
// list-comprehension if/else arm -- plus two arms the debugger never needed
|
|
9
|
+
// to stop at, the right operands of `&&`/`||`. Recording a hit is an array
|
|
10
|
+
// increment indexed by the node's (treeId, slot), the dense per-parse
|
|
11
|
+
// numbering ScopeTable relies on, so the bytecode VM stays on and a hit
|
|
12
|
+
// costs no hashing and no string.
|
|
13
|
+
//
|
|
14
|
+
// What COULD have run -- the universe the report counts against -- comes
|
|
15
|
+
// from a walk over the same trees (collectCoverable) using one predicate
|
|
16
|
+
// for both sides, so "covered" and "uncovered" are one vocabulary. A hit
|
|
17
|
+
// on a node outside that vocabulary (a let-binding, a plain comprehension
|
|
18
|
+
// element) is simply not reported.
|
|
19
|
+
|
|
20
|
+
#include "openscad_cpp_parser/ast/ast_node.hpp"
|
|
21
|
+
|
|
22
|
+
#include <cstdint>
|
|
23
|
+
#include <string>
|
|
24
|
+
#include <vector>
|
|
25
|
+
|
|
26
|
+
namespace oscadeval {
|
|
27
|
+
|
|
28
|
+
enum class CoverageKind {
|
|
29
|
+
Statement, // a statement in a module body, an if/for/let body, or at top level
|
|
30
|
+
Branch, // a ternary arm, a comprehension if/else arm, a `&&`/`||` right operand
|
|
31
|
+
Body, // a function, module or function-literal body: was it ever entered?
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const char* coverageKindName(CoverageKind kind);
|
|
35
|
+
|
|
36
|
+
struct CoverageSpan {
|
|
37
|
+
std::string origin;
|
|
38
|
+
int line = 0;
|
|
39
|
+
int column = 0;
|
|
40
|
+
int start_offset = 0;
|
|
41
|
+
int end_offset = 0;
|
|
42
|
+
CoverageKind kind = CoverageKind::Statement;
|
|
43
|
+
// A Statement that is the first of an if/else arm. Branch coverage counts
|
|
44
|
+
// these alongside the Branch kind; they stay Statements so a statement
|
|
45
|
+
// count is still a statement count.
|
|
46
|
+
bool arm = false;
|
|
47
|
+
std::uint32_t hits = 0;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Per-file totals. "branches" counts Branch spans plus arm Statements (an
|
|
51
|
+
// if/else arm is a branch too); those arm statements are ALSO in
|
|
52
|
+
// "statements", since a statement count should be a statement count.
|
|
53
|
+
// "percent" is over every span in the file, whatever its kind.
|
|
54
|
+
struct CoverageFileSummary {
|
|
55
|
+
std::string origin;
|
|
56
|
+
std::uint32_t statements = 0, statements_hit = 0;
|
|
57
|
+
std::uint32_t branches = 0, branches_hit = 0;
|
|
58
|
+
std::uint32_t bodies = 0, bodies_hit = 0;
|
|
59
|
+
std::uint32_t spans = 0, spans_hit = 0;
|
|
60
|
+
double percent() const { return spans ? 100.0 * spans_hit / spans : 100.0; }
|
|
61
|
+
double statement_percent() const { return statements ? 100.0 * statements_hit / statements : 100.0; }
|
|
62
|
+
double branch_percent() const { return branches ? 100.0 * branches_hit / branches : 100.0; }
|
|
63
|
+
double body_percent() const { return bodies ? 100.0 * bodies_hit / bodies : 100.0; }
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
struct CoverageResult {
|
|
67
|
+
std::vector<CoverageSpan> spans; // one per coverable node, source order per tree
|
|
68
|
+
std::vector<CoverageFileSummary> files; // one per origin, sorted by origin
|
|
69
|
+
CoverageFileSummary total; // every file together (origin empty)
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// The per-file and total summaries for `spans`; buildCoverageResult calls
|
|
73
|
+
// this, and a merged/aggregated span list (BelfrySCAD's --test --coverage)
|
|
74
|
+
// can call it again.
|
|
75
|
+
void summarizeCoverage(CoverageResult& result);
|
|
76
|
+
|
|
77
|
+
class CoverageRecorder {
|
|
78
|
+
public:
|
|
79
|
+
void clear() { hits_.clear(); }
|
|
80
|
+
void hit(const oscad::ASTNode& node) {
|
|
81
|
+
const std::uint32_t tree = node.treeId(), slot = node.slot();
|
|
82
|
+
if (tree >= hits_.size()) hits_.resize(tree + 1);
|
|
83
|
+
std::vector<std::uint32_t>& slots = hits_[tree];
|
|
84
|
+
if (slot >= slots.size()) slots.resize(slot + 1, 0);
|
|
85
|
+
++slots[slot];
|
|
86
|
+
}
|
|
87
|
+
std::uint32_t hitsFor(const oscad::ASTNode& node) const {
|
|
88
|
+
const std::uint32_t tree = node.treeId(), slot = node.slot();
|
|
89
|
+
if (tree >= hits_.size()) return 0;
|
|
90
|
+
const std::vector<std::uint32_t>& slots = hits_[tree];
|
|
91
|
+
return slot < slots.size() ? slots[slot] : 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private:
|
|
95
|
+
std::vector<std::vector<std::uint32_t>> hits_;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
struct Coverable {
|
|
99
|
+
const oscad::ASTNode* node = nullptr;
|
|
100
|
+
CoverageKind kind = CoverageKind::Statement;
|
|
101
|
+
bool arm = false;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Every coverable node under `roots` (a top-level statement list: the main
|
|
105
|
+
// file with its includes spliced in and any use-injected declarations) plus
|
|
106
|
+
// `extraStatements` (a used file's own top-level assignments, which `use`
|
|
107
|
+
// never splices anywhere -- see Evaluator::setUsedFileGlobals). Source
|
|
108
|
+
// order within each tree; a node reachable twice is listed once.
|
|
109
|
+
std::vector<Coverable> collectCoverable(const std::vector<const oscad::ASTNode*>& roots,
|
|
110
|
+
const std::vector<const oscad::ASTNode*>& extraStatements);
|
|
111
|
+
|
|
112
|
+
CoverageResult buildCoverageResult(const std::vector<const oscad::ASTNode*>& roots,
|
|
113
|
+
const std::vector<const oscad::ASTNode*>& extraStatements,
|
|
114
|
+
const CoverageRecorder& recorder);
|
|
115
|
+
|
|
116
|
+
} // namespace oscadeval
|
|
@@ -15,9 +15,21 @@ namespace oscadeval {
|
|
|
15
15
|
// must keep this struct alive for as long as evaluation runs against
|
|
16
16
|
// .rootScope/.processedNodes, since declarations injected from a used file
|
|
17
17
|
// are raw (non-owning) pointers into it.
|
|
18
|
+
// One used file's own top-level assignments, in source order, keyed by
|
|
19
|
+
// that file's root Scope (the one its injected declarations were
|
|
20
|
+
// re-anchored onto). Evaluator::setUsedFileGlobals takes the list so a
|
|
21
|
+
// used file's globals can be evaluated ONCE per run, eagerly, the first
|
|
22
|
+
// time anything in that file reads one -- see Evaluator::fileGlobal.
|
|
23
|
+
// Raw pointers into usedFileAsts, same lifetime contract as processedNodes.
|
|
24
|
+
struct UsedFileGlobals {
|
|
25
|
+
const oscad::Scope* root = nullptr;
|
|
26
|
+
std::vector<const oscad::ASTNode*> assignments;
|
|
27
|
+
};
|
|
28
|
+
|
|
18
29
|
struct ResolvedUseScopes {
|
|
19
30
|
std::vector<std::vector<std::unique_ptr<oscad::ASTNode>>> usedFileAsts;
|
|
20
31
|
std::vector<std::unique_ptr<oscad::Scope>> usedFileScopes;
|
|
32
|
+
std::vector<UsedFileGlobals> usedFileGlobals;
|
|
21
33
|
// What currentFile should actually be evaluated as: declarations
|
|
22
34
|
// injected via `use` (raw pointers into usedFileAsts, above), followed
|
|
23
35
|
// by currentFile's own nodes (raw pointers into the caller-owned
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
#include "openscad_cpp_evaluator/csg_node.hpp"
|
|
8
8
|
#include "openscad_cpp_evaluator/debug_hooks.hpp"
|
|
9
9
|
#include "openscad_cpp_evaluator/eval_context.hpp"
|
|
10
|
+
#include "openscad_cpp_evaluator/eval_use.hpp"
|
|
11
|
+
#include "openscad_cpp_evaluator/coverage.hpp"
|
|
10
12
|
#include "openscad_cpp_evaluator/eval_error.hpp"
|
|
11
13
|
#include "openscad_cpp_evaluator/font_provider.hpp"
|
|
12
14
|
#include "openscad_cpp_evaluator/manifold_cache.hpp"
|
|
@@ -83,7 +85,7 @@ public:
|
|
|
83
85
|
// work at all.
|
|
84
86
|
explicit Evaluator(EchoFn echoFn = {}, std::shared_ptr<FontProvider> fontProvider = nullptr,
|
|
85
87
|
std::shared_ptr<ManifoldCache> manifoldCache = nullptr, DebugHooks debugHooks = {},
|
|
86
|
-
bool profiling = false);
|
|
88
|
+
bool profiling = false, bool coverage = false);
|
|
87
89
|
|
|
88
90
|
// Text/font builtins' shared access to the resolved FontProvider
|
|
89
91
|
// (lazily constructing the built-in default on first call if none was
|
|
@@ -285,6 +287,11 @@ public:
|
|
|
285
287
|
const oscad::ASTNode* producer);
|
|
286
288
|
|
|
287
289
|
std::unordered_map<uint32_t, const oscad::ASTNode*> idToNode;
|
|
290
|
+
// Nodes whose bodies were actually generated this run (not served from
|
|
291
|
+
// the ManifoldCache). Reset per run; a diagnostic, and the only honest
|
|
292
|
+
// way for a test to tell a cache hit from a miss now that an inner
|
|
293
|
+
// node's bodies are released the moment its parent has consumed them.
|
|
294
|
+
std::size_t generatedNodeCount = 0;
|
|
288
295
|
std::unordered_map<uint32_t, std::optional<std::array<float, 4>>> idToColor;
|
|
289
296
|
|
|
290
297
|
// Called by primitive-construction generate functions (cube, sphere,
|
|
@@ -653,6 +660,22 @@ public:
|
|
|
653
660
|
// resolve+generate bracket evaluate() itself runs). Mirrors the
|
|
654
661
|
// reference's Evaluator.profile_result.
|
|
655
662
|
std::optional<ProfileResult> profileResult;
|
|
663
|
+
// Filled by evaluate() when constructed with coverage=true: one span per
|
|
664
|
+
// coverable node of every tree the run touched, with its hit count.
|
|
665
|
+
// See coverage.hpp for the vocabulary.
|
|
666
|
+
std::optional<CoverageResult> coverageResult;
|
|
667
|
+
|
|
668
|
+
bool coverageEnabled() const { return coverage_; }
|
|
669
|
+
// Called once per statement execution (evalChildren on the interpreter
|
|
670
|
+
// side, Op::Cover on the compiled side), once per body entry
|
|
671
|
+
// (enterUserCall), and once per branch arm taken (ternary, comprehension
|
|
672
|
+
// if/else, `&&`/`||` right operand). Deliberately NOT from checkDebug:
|
|
673
|
+
// the debugger's checkpoints double up on an if-arm's first statement
|
|
674
|
+
// and never land on the `if` itself. Free when coverage is off: one
|
|
675
|
+
// predictable branch.
|
|
676
|
+
void coverHit(const oscad::ASTNode& node) {
|
|
677
|
+
if (coverage_) coverageRecorder_.hit(node);
|
|
678
|
+
}
|
|
656
679
|
|
|
657
680
|
// The resolved (and, after evaluate() runs, generated) CSG tree from the
|
|
658
681
|
// most recent evaluate() call -- moved here from evaluateImpl's local
|
|
@@ -1396,6 +1419,11 @@ public:
|
|
|
1396
1419
|
// the same decision evalUserFunction*/evalFunctionLiteral* already
|
|
1397
1420
|
// make before calling lookupOrCompileChunk/lookupCompiledLiteralChunk
|
|
1398
1421
|
// themselves.
|
|
1422
|
+
// Every used file's own top-level assignments (ResolvedUseScopes::
|
|
1423
|
+
// usedFileGlobals), so fileGlobal() can evaluate a file's globals once.
|
|
1424
|
+
// A caller that skips this keeps the old per-read fallback for used files.
|
|
1425
|
+
void setUsedFileGlobals(std::vector<UsedFileGlobals> globals) { usedFileGlobals_ = std::move(globals); }
|
|
1426
|
+
|
|
1399
1427
|
bool useBytecodeVm() const {
|
|
1400
1428
|
return bytecodeVmEnabled() && (!debugHooks_.debugHook || fastContinueBreakpoints_.has_value());
|
|
1401
1429
|
}
|
|
@@ -1941,6 +1969,25 @@ private:
|
|
|
1941
1969
|
// lifetime contract as every other raw AST/EvalContext pointer this
|
|
1942
1970
|
// class holds).
|
|
1943
1971
|
const EvalContext* rootCtx_ = nullptr;
|
|
1972
|
+
|
|
1973
|
+
// File-level globals are evaluated ONCE per run. The main file's (and
|
|
1974
|
+
// every include's) already are, by the top-level statement pass, into
|
|
1975
|
+
// rootCtx_'s own let_ level. A used file's are not -- `use` injects only
|
|
1976
|
+
// its declarations -- so the first read of any global from that file
|
|
1977
|
+
// evaluates ALL of its assignments, in source order, into a context of
|
|
1978
|
+
// its own, kept here for the rest of the run. Before this, a function
|
|
1979
|
+
// reading a global re-evaluated the assignment's expression on every
|
|
1980
|
+
// read (evalIdentifier's scope fallback): `r = rands(0,1,1); function
|
|
1981
|
+
// g() = r;` gave `g() != g()`, and a 20K-element global list read 2000
|
|
1982
|
+
// times from a function cost 9.7s. OpenSCAD itself re-evaluates a used
|
|
1983
|
+
// file's globals per CALL, which is a long-standing upstream bug, not
|
|
1984
|
+
// behaviour to match. Returns nullopt for a file this evaluator was
|
|
1985
|
+
// never told about (setUsedFileGlobals), so such a caller keeps the
|
|
1986
|
+
// old fallback; nullptr for a known file whose global is not (yet)
|
|
1987
|
+
// assigned -- a forward read, undef in OpenSCAD too.
|
|
1988
|
+
std::optional<const Value*> fileGlobal(const oscad::Scope& fileRoot, const std::string& name);
|
|
1989
|
+
std::vector<UsedFileGlobals> usedFileGlobals_;
|
|
1990
|
+
std::unordered_map<const oscad::Scope*, EvalContext> usedFileCtx_;
|
|
1944
1991
|
// The most recent ctx seen by evalStatement() -- error()'s only way to
|
|
1945
1992
|
// reach a locals snapshot for its own errorBreak hook call, since
|
|
1946
1993
|
// error() itself (called from dozens of builtin resolve functions)
|
|
@@ -1954,6 +2001,8 @@ private:
|
|
|
1954
2001
|
// -- Profiling (Phase 9) ----------------------------------------------
|
|
1955
2002
|
|
|
1956
2003
|
bool profiling_ = false;
|
|
2004
|
+
bool coverage_ = false;
|
|
2005
|
+
CoverageRecorder coverageRecorder_;
|
|
1957
2006
|
std::map<ProfileSiteKey, CallSiteProfile> profileSites_;
|
|
1958
2007
|
std::set<ProfileSiteKey> profileActive_; // site keys with a call currently on callStack_
|
|
1959
2008
|
std::vector<double> profileChildTime_; // parallel aux stack to callStack_
|