openscad-cpp-evaluator 0.36.0__tar.gz → 0.38.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.36.0 → openscad_cpp_evaluator-0.38.0}/CLAUDE.md +99 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +4 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +27 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +1 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/expression.cpp +42 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +5 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +2 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/lexer.l +5 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/parser.y +30 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +6 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/pretty_print.cpp +11 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +12 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
- openscad_cpp_evaluator-0.38.0/external/openscad_cpp_parser/tests/test_render_expression.cpp +186 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bytecode.hpp +15 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +12 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/evaluator.hpp +61 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/CMakeLists.txt +1 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/builtins.hpp +23 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/primitives_2d.cpp +17 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/primitives_3d.cpp +19 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/registry.cpp +3 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/topology.cpp +76 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/bytecode_compiler.cpp +53 -2
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/bytecode_vm.cpp +82 -3
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/csg_generate.cpp +20 -7
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/debug_profile.cpp +7 -1
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/expr_eval.cpp +3 -0
- openscad_cpp_evaluator-0.38.0/src/measure_geometry.cpp +319 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/CMakeLists.txt +1 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_booleans.cpp +94 -0
- openscad_cpp_evaluator-0.38.0/tests/test_render_expr.cpp +386 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/uv.lock +1 -1
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/README.md +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.36.0 → openscad_cpp_evaluator-0.38.0}/tools/cli/main.cpp +0 -0
|
@@ -584,6 +584,24 @@ grep for `ponytail:`.
|
|
|
584
584
|
a file that includes BOSL2 to "compare against the reference" is comparing BOSL2 with itself. Call
|
|
585
585
|
`spheroid()` from a BOSL2-including file and `sphere()` from one without.
|
|
586
586
|
|
|
587
|
+
- `simplify()` (also `topology.cpp`) — mesh decimation within a tolerance, wrapping
|
|
588
|
+
`Manifold::Simplify` and `CrossSection::Simplify`, so it works on sections as well as solids.
|
|
589
|
+
Another BelfrySCAD extension: nothing in the OpenSCAD language can reduce a triangle count, and
|
|
590
|
+
library-generated or imported geometry is routinely far denser than the model needs.
|
|
591
|
+
|
|
592
|
+
**The default tolerance is 0.1% of the body's own bounding-box diagonal**, not zero.
|
|
593
|
+
`Manifold::Simplify(0)` falls back to the mesh epsilon and changes nothing, so defaulting to zero
|
|
594
|
+
would make a bare `simplify()` silently do nothing — the worst possible outcome. It has to be
|
|
595
|
+
scale-relative, too: an absolute default sensible in millimetres is catastrophic in metres.
|
|
596
|
+
Measured on a drilled sphere (4764 triangles): the default takes it to 3732 for well under 0.1%
|
|
597
|
+
volume error, an explicit 0.25 halves it for 0.8%.
|
|
598
|
+
|
|
599
|
+
Two properties worth keeping, both covered by tests. Provenance survives — the original-ID runs
|
|
600
|
+
come through simplification intact, so selection and drag-to-edit still work through a
|
|
601
|
+
`simplify()`. So does topology: a drilled part keeps its genus rather than having holes welded
|
|
602
|
+
shut. Note the reduction is NOT monotonic in the tolerance (1.0 gave *more* triangles than 0.5 on
|
|
603
|
+
the test model, since simplification can force re-triangulation), so don't read a jump as a bug.
|
|
604
|
+
|
|
587
605
|
- `src/builtins/dxf_dim.cpp` — `dxf_dim()`/`dxf_cross()`, which read a measurement out of a DXF file
|
|
588
606
|
rather than out of the model. Ported from the reference's `io/dxfdim.cc` plus the DIMENSION/LINE
|
|
589
607
|
half of `io/DxfData.cc`: `dxf_dim()` returns the first matching DIMENSION's value by its type
|
|
@@ -1076,3 +1094,84 @@ than the current pause point). Ported identically (same qualifier syntax, same e
|
|
|
1076
1094
|
profiling/debugging's own bookkeeping resets happen at the right point too.
|
|
1077
1095
|
- `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
|
|
1078
1096
|
real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
|
|
1097
|
+
|
|
1098
|
+
## `render()` in expression position
|
|
1099
|
+
|
|
1100
|
+
`render()` has two jobs, decided by where it is written:
|
|
1101
|
+
|
|
1102
|
+
```openscad
|
|
1103
|
+
render() cube(1); // STATEMENT -- draws, as always
|
|
1104
|
+
obj = render() { cube(1); }; // EXPRESSION -- measures, draws NOTHING
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
The expression form builds its children's geometry, measures it, extracts the
|
|
1108
|
+
mesh, and **discards the geometry**. It is the only way OpenSCAD can inspect its
|
|
1109
|
+
own geometry — volume, surface area, genus, bounding box, and the mesh itself.
|
|
1110
|
+
|
|
1111
|
+
```openscad
|
|
1112
|
+
obj = render() difference() { cube(100); sphere(20); };
|
|
1113
|
+
echo(obj.volume, obj.genus, obj.boundingbox);
|
|
1114
|
+
polyhedron(obj); // straight back in
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
Keys — ordered, and the order is part of the API (`ValueObject` is an ordered
|
|
1118
|
+
vector and `oscEqual` is order-sensitive):
|
|
1119
|
+
|
|
1120
|
+
| dim | keys |
|
|
1121
|
+
|---|---|
|
|
1122
|
+
| 3 | `vertices`, `faces`, `volume`, `area`, `genus`, `boundingbox`, `dim`, `vnf` |
|
|
1123
|
+
| 2 | `vertices`, `paths`, `area`, `perimeter`, `boundingbox`, `dim` |
|
|
1124
|
+
| 0 | the 3D key set, all zero, `boundingbox = undef` |
|
|
1125
|
+
|
|
1126
|
+
`vertices`/`faces` are VNF-shaped — `[x,y,z]` points, 0-based N-gon index lists,
|
|
1127
|
+
**clockwise seen from outside** — so they feed `polyhedron()` and BOSL2 unchanged.
|
|
1128
|
+
`vnf` is the `[vertices, faces]` 2-list BOSL2 functions actually take.
|
|
1129
|
+
`polyhedron()` and `polygon()` also accept the object directly, and will accept
|
|
1130
|
+
*any* object carrying the right keys, not just one `render()` produced.
|
|
1131
|
+
|
|
1132
|
+
### Things that will bite
|
|
1133
|
+
|
|
1134
|
+
- **`render` is a reserved keyword.** It cannot be a variable, module, function,
|
|
1135
|
+
argument, or member name. Required: LALR(1) cannot otherwise tell `render(` in
|
|
1136
|
+
expression position from a function call, and bison's shift-over-reduce would
|
|
1137
|
+
silently turn every function call in an expression into a module instantiation.
|
|
1138
|
+
- **`obj = render() cube(1);` does not parse.** A bare call's `child_statement`
|
|
1139
|
+
swallows the `;`, leaving the assignment unterminated. Use
|
|
1140
|
+
`render() { cube(1); }` or a form ending in `}`. Inherent to OpenSCAD's grammar.
|
|
1141
|
+
- **`genus` is Manifold's, for the whole result.** A cube with a sealed internal
|
|
1142
|
+
cavity reports `-1`, not `0` — its boundary has two components. Correct, but
|
|
1143
|
+
surprising.
|
|
1144
|
+
- **Nothing is drawn from any context** — top level, module body, function body,
|
|
1145
|
+
list comprehension, ternary. That is what preserves function purity, and why no
|
|
1146
|
+
context needs a special case.
|
|
1147
|
+
|
|
1148
|
+
### Implementation notes
|
|
1149
|
+
|
|
1150
|
+
`Evaluator::measureCsgSubtree` (`src/measure_geometry.cpp`) is the *only*
|
|
1151
|
+
implementation of "CSG subtree → `object()`"; both engines call it — the
|
|
1152
|
+
interpreter from `evalRenderExpr`, the VM from `Op::PopBuiltinWrap`'s
|
|
1153
|
+
`Kind::Measure` branch.
|
|
1154
|
+
|
|
1155
|
+
`Evaluator::measuring_` is set for the whole generate. It suppresses the four
|
|
1156
|
+
writes that exist solely to describe *drawn* geometry — `idToNode`/`idToColor` in
|
|
1157
|
+
`tagGenerated` and `tagDisplayOnly`, the `restampCachedIds` call on a cache hit,
|
|
1158
|
+
and `cacheProducer_` — because those tables are cleared once per pass, so a leak
|
|
1159
|
+
is permanent and surfaces later as wrong click-to-source. It also suppresses
|
|
1160
|
+
`checkDebug`, which would otherwise inject stops at the paused statement's own
|
|
1161
|
+
`callStack_` depth. The **geometry cache stays on**: `cacheKey` is
|
|
1162
|
+
content-addressed, so an entry a measurement stores is genuinely reusable by the
|
|
1163
|
+
real render.
|
|
1164
|
+
|
|
1165
|
+
Two details in the mesh conversion are load-bearing and silent when wrong: the
|
|
1166
|
+
winding is **reversed** on the way out (Manifold's `triVerts` is CCW), and
|
|
1167
|
+
vertices are welded by exact position (Manifold splits property-vertices, so
|
|
1168
|
+
without the weld the round-tripped `polyhedron()` is an *open* mesh). A reversed
|
|
1169
|
+
mesh still builds — `Status() == NoError` — but comes back with **negative
|
|
1170
|
+
volume**, so tests assert a positive volume and must never use `abs()`.
|
|
1171
|
+
|
|
1172
|
+
On the VM side, `Kind::Measure` captures `(name, slot)` for every visible local
|
|
1173
|
+
at compile time and republishes them into the children's `EvalContext` at Push.
|
|
1174
|
+
A compiled function keeps its parameters in frame slots, which no `EvalContext`
|
|
1175
|
+
can see, and the children are statement opcodes that resolve names through the
|
|
1176
|
+
context — without this, `function f(w) = render() { cube(w); }.volume;` finds no
|
|
1177
|
+
`w` and silently measures nothing.
|
|
@@ -61,6 +61,10 @@ enum class NodeKind {
|
|
|
61
61
|
PrimaryCall,
|
|
62
62
|
PrimaryIndex,
|
|
63
63
|
PrimaryMember,
|
|
64
|
+
// render() in EXPRESSION position -- evaluates its children as geometry
|
|
65
|
+
// and yields an object() of measurements. The STATEMENT form of render()
|
|
66
|
+
// is a plain ModularCall named "render"; only this one is new.
|
|
67
|
+
RenderExpression,
|
|
64
68
|
// List comprehension
|
|
65
69
|
ListCompLet,
|
|
66
70
|
ListCompEach,
|
|
@@ -237,6 +237,33 @@ public:
|
|
|
237
237
|
void buildScope(Scope& parentScope) override;
|
|
238
238
|
};
|
|
239
239
|
|
|
240
|
+
// `render()` in EXPRESSION position: `obj = render() { cube(10); };`
|
|
241
|
+
//
|
|
242
|
+
// Evaluates its children as geometry, measures the result, and yields an
|
|
243
|
+
// object() -- it draws nothing. The STATEMENT form (`render() cube(1);`)
|
|
244
|
+
// stays a plain ModularCall named "render"; this class exists only because
|
|
245
|
+
// an Expression cannot be a ModuleInstantiation (they are siblings under
|
|
246
|
+
// ASTNode, not parent/child).
|
|
247
|
+
//
|
|
248
|
+
// `arguments` and `children` deliberately mirror ModularCall's field types
|
|
249
|
+
// so the evaluator can hand them straight to resolveCallArgs()/evalChildren()
|
|
250
|
+
// with no adaptation. `children` is ASTNode, not ModuleInstantiation, for
|
|
251
|
+
// the same reason ModularCall's is -- a `{ ... }` block is `statement*` and
|
|
252
|
+
// may hold Assignments, which is why buildScope() below hoists.
|
|
253
|
+
class RenderExpression : public Expression {
|
|
254
|
+
public:
|
|
255
|
+
RenderExpression(Position position, std::vector<std::unique_ptr<Argument>> arguments,
|
|
256
|
+
std::vector<std::unique_ptr<ASTNode>> children)
|
|
257
|
+
: Expression(NodeKind::RenderExpression, std::move(position)), arguments(std::move(arguments)),
|
|
258
|
+
children(std::move(children)) {}
|
|
259
|
+
|
|
260
|
+
std::vector<std::unique_ptr<Argument>> arguments;
|
|
261
|
+
std::vector<std::unique_ptr<ASTNode>> children;
|
|
262
|
+
|
|
263
|
+
std::string toString() const override;
|
|
264
|
+
void buildScope(Scope& parentScope) override;
|
|
265
|
+
};
|
|
266
|
+
|
|
240
267
|
// -- Unary operators --------------------------------------------------
|
|
241
268
|
|
|
242
269
|
#define OSCAD_UNARY_OP(ClassName) \
|
|
@@ -47,6 +47,7 @@ const char* nodeKindName(NodeKind kind) {
|
|
|
47
47
|
case NodeKind::PrimaryCall: return "PrimaryCall";
|
|
48
48
|
case NodeKind::PrimaryIndex: return "PrimaryIndex";
|
|
49
49
|
case NodeKind::PrimaryMember: return "PrimaryMember";
|
|
50
|
+
case NodeKind::RenderExpression: return "RenderExpression";
|
|
50
51
|
case NodeKind::ListCompLet: return "ListCompLet";
|
|
51
52
|
case NodeKind::ListCompEach: return "ListCompEach";
|
|
52
53
|
case NodeKind::ListCompFor: return "ListCompFor";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "openscad_cpp_parser/ast/expression.hpp"
|
|
2
2
|
|
|
3
3
|
#include "format_utils.hpp"
|
|
4
|
+
#include "openscad_cpp_parser/ast/scope_builder.hpp"
|
|
4
5
|
#include "openscad_cpp_parser/scope.hpp"
|
|
5
6
|
|
|
6
7
|
#include <array>
|
|
@@ -155,6 +156,47 @@ void FunctionLiteral::buildScope(Scope& parentScope) {
|
|
|
155
156
|
body->buildScope(funcScope);
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
std::string RenderExpression::toString() const {
|
|
160
|
+
// Deliberately NOT ModuleInstantiation's formatChildBlock, which omits
|
|
161
|
+
// both braces (for a lone child) and every statement terminator, because
|
|
162
|
+
// for a STATEMENT the terminators come from the statement-level printer.
|
|
163
|
+
// A RenderExpression sits inside an expression, where nothing downstream
|
|
164
|
+
// adds them -- and `render() cube(1)` unbraced is precisely the form that
|
|
165
|
+
// does NOT parse (the child_statement swallows the `;`, leaving the
|
|
166
|
+
// enclosing assignment unterminated). So: always braces, always a `;`
|
|
167
|
+
// after every child.
|
|
168
|
+
//
|
|
169
|
+
// This must stay REPARSEABLE -- pretty_print.cpp's fmtExpr falls through
|
|
170
|
+
// to toString() for expression kinds it has no case for, so this string
|
|
171
|
+
// is what a formatter emits. NodeStr.RenderExpressionRoundTrips guards it.
|
|
172
|
+
//
|
|
173
|
+
// The unconditional `;` is safe even after a child that already ends in
|
|
174
|
+
// `}` (a nested block, a module declaration): a bare `;` is itself a legal
|
|
175
|
+
// statement (parser.y's `statement: ";"`), so a redundant one is a no-op.
|
|
176
|
+
std::string s = "render(" + joinToString(arguments, ", ") + ") { ";
|
|
177
|
+
for (const auto& c : children) {
|
|
178
|
+
s += c->toString() + "; ";
|
|
179
|
+
}
|
|
180
|
+
return s + "}";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
void RenderExpression::buildScope(Scope& parentScope) {
|
|
184
|
+
// Same shape as ModularCall::buildScope minus the name lookup (there is
|
|
185
|
+
// no Identifier -- "render" is a keyword token here). The hoist is
|
|
186
|
+
// required: `render() { x = 1; cube(x); }` must resolve x.
|
|
187
|
+
setScope(parentScope);
|
|
188
|
+
for (auto& a : arguments) {
|
|
189
|
+
a->buildScope(parentScope);
|
|
190
|
+
}
|
|
191
|
+
if (!children.empty()) {
|
|
192
|
+
Scope& childrenScope = parentScope.childScope();
|
|
193
|
+
collectHoistedDeclarations(children, childrenScope);
|
|
194
|
+
for (auto& c : children) {
|
|
195
|
+
c->buildScope(childrenScope);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
158
200
|
// -- Operator precedence for minimal-parenthesization toString() ----------
|
|
159
201
|
//
|
|
160
202
|
// Matches the reference's nodes.py::_PREC/_lp/_rp: toString() adds parens
|
|
@@ -153,6 +153,11 @@ NodePtr makeListComprehension(ParserDriver& driver, const OscadLocation& loc, No
|
|
|
153
153
|
return std::make_unique<ListComprehension>(driver.toPosition(loc), std::move(elements));
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
NodePtr makeRenderExpression(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodeList children) {
|
|
157
|
+
return std::make_unique<RenderExpression>(driver.toPosition(loc), nodeListCast<Argument>(std::move(arguments)),
|
|
158
|
+
std::move(children));
|
|
159
|
+
}
|
|
160
|
+
|
|
156
161
|
NodePtr makeModularCall(ParserDriver& driver, const OscadLocation& loc, const OscadLocation& nameLoc, std::string name,
|
|
157
162
|
NodeList arguments, NodeList children) {
|
|
158
163
|
auto nameNode = makeIdentifier(driver, nameLoc, std::move(name));
|
|
@@ -132,6 +132,8 @@ NodePtr makeListCompIfElse(ParserDriver& driver, const OscadLocation& loc, NodeP
|
|
|
132
132
|
NodePtr falseExpr);
|
|
133
133
|
NodePtr makeListComprehension(ParserDriver& driver, const OscadLocation& loc, NodeList elements);
|
|
134
134
|
|
|
135
|
+
NodePtr makeRenderExpression(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodeList children);
|
|
136
|
+
|
|
135
137
|
NodePtr makeModularCall(ParserDriver& driver, const OscadLocation& loc, const OscadLocation& nameLoc, std::string name,
|
|
136
138
|
NodeList arguments, NodeList children);
|
|
137
139
|
NodePtr makeModularFor(ParserDriver& driver, const OscadLocation& loc, NodeList assignments, NodeList body);
|
|
@@ -47,6 +47,11 @@ const std::unordered_map<std::string, KeywordFactory>& keywordTable() {
|
|
|
47
47
|
{"for", [](const OscadLocation& l) { return yy::parser::make_KW_FOR(l); }},
|
|
48
48
|
{"intersection_for", [](const OscadLocation& l) { return yy::parser::make_KW_INTERSECTION_FOR(l); }},
|
|
49
49
|
{"each", [](const OscadLocation& l) { return yy::parser::make_KW_EACH(l); }},
|
|
50
|
+
// Reserved so `render` can lead an EXPRESSION-position geometry block
|
|
51
|
+
// (`obj = render() { cube(1); };`). LALR(1) cannot otherwise tell that
|
|
52
|
+
// apart from a function call. Note `$render` still lexes as NAME --
|
|
53
|
+
// this table is keyed on the full IDENT text, including the `$`.
|
|
54
|
+
{"render", [](const OscadLocation& l) { return yy::parser::make_KW_RENDER(l); }},
|
|
50
55
|
{"undef", [](const OscadLocation& l) { return yy::parser::make_KW_UNDEF(l); }},
|
|
51
56
|
{"true", [](const OscadLocation& l) { return yy::parser::make_KW_TRUE(l); }},
|
|
52
57
|
{"false", [](const OscadLocation& l) { return yy::parser::make_KW_FALSE(l); }},
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
KW_FOR "for"
|
|
93
93
|
KW_INTERSECTION_FOR "intersection_for"
|
|
94
94
|
KW_EACH "each"
|
|
95
|
+
KW_RENDER "render"
|
|
95
96
|
KW_UNDEF "undef"
|
|
96
97
|
KW_TRUE "true"
|
|
97
98
|
KW_FALSE "false"
|
|
@@ -157,6 +158,7 @@
|
|
|
157
158
|
%type <NodePtr> modifier_show_only modifier_highlight modifier_background modifier_disable
|
|
158
159
|
%type <NodePtr> if_statement ifelse_statement
|
|
159
160
|
%type <NodePtr> modular_for modular_intersection_for modular_let modular_assert modular_echo modular_call
|
|
161
|
+
%type <NodePtr> render_stmt render_expr
|
|
160
162
|
%type <NodePtr> expr opchain postfix primary
|
|
161
163
|
%type <NodePtr> range_expr vector_expr vector_element
|
|
162
164
|
%type <NodePtr> listcomp_elements listcomp_paren_expr listcomp_let listcomp_each
|
|
@@ -305,6 +307,7 @@ single_module_instantiation:
|
|
|
305
307
|
| modular_assert { $$ = std::move($1); }
|
|
306
308
|
| modular_echo { $$ = std::move($1); }
|
|
307
309
|
| modular_call { $$ = std::move($1); }
|
|
310
|
+
| render_stmt { $$ = std::move($1); }
|
|
308
311
|
;
|
|
309
312
|
|
|
310
313
|
modular_for:
|
|
@@ -343,6 +346,16 @@ modular_call:
|
|
|
343
346
|
}
|
|
344
347
|
;
|
|
345
348
|
|
|
349
|
+
// `render` is a reserved keyword (see lexer.l) purely so the EXPRESSION form
|
|
350
|
+
// below is unambiguous. The STATEMENT form still builds a plain ModularCall
|
|
351
|
+
// named "render", so everything downstream -- builtin dispatch, the argument
|
|
352
|
+
// allowlist, the pretty-printer, json_io -- is unchanged.
|
|
353
|
+
render_stmt:
|
|
354
|
+
"render" "(" arguments ")" child_statement {
|
|
355
|
+
$$ = makeModularCall(driver, @$, @1, "render", std::move($3), std::move($5));
|
|
356
|
+
}
|
|
357
|
+
;
|
|
358
|
+
|
|
346
359
|
// -- Expressions ----------------------------------------------------------
|
|
347
360
|
//
|
|
348
361
|
// `expr` covers let/assert/echo/funclit_def/ternary plus the operator
|
|
@@ -406,6 +419,23 @@ primary:
|
|
|
406
419
|
| STRING { $$ = makeStringLiteral(driver, @$, std::move($1)); }
|
|
407
420
|
| NUMBER { $$ = makeNumberLiteral(driver, @$, $1); }
|
|
408
421
|
| NAME { $$ = makeIdentifier(driver, @$, std::move($1)); }
|
|
422
|
+
| render_expr { $$ = std::move($1); }
|
|
423
|
+
;
|
|
424
|
+
|
|
425
|
+
// Same RHS as render_stmt, reached only from `primary`. This is NOT a
|
|
426
|
+
// reduce/reduce conflict: LALR merges states only on identical LR(0) cores,
|
|
427
|
+
// and after shifting "render" the statement and expression kernels differ --
|
|
428
|
+
// no single state closes over both (statement-start closes over
|
|
429
|
+
// module_instantiation; every expr-start position closes over no statement
|
|
430
|
+
// nonterminal). Bison verifies this claim at build time via %expect.
|
|
431
|
+
//
|
|
432
|
+
// It lives in `primary` rather than `expr` so `render(){...}.volume` parses
|
|
433
|
+
// through the existing `postfix "." NAME` rule, and so it can appear as an
|
|
434
|
+
// argument, a list element, or an operand.
|
|
435
|
+
render_expr:
|
|
436
|
+
"render" "(" arguments ")" child_statement {
|
|
437
|
+
$$ = makeRenderExpression(driver, @$, std::move($3), std::move($5));
|
|
438
|
+
}
|
|
409
439
|
;
|
|
410
440
|
|
|
411
441
|
range_expr:
|
|
@@ -295,6 +295,12 @@ void classifyNode(ASTNode& node, std::vector<ExprSlot>& exprFields, std::vector<
|
|
|
295
295
|
addAstNodeList(static_cast<ListComprehension&>(node).elements, exprFields, nonExprChildren);
|
|
296
296
|
break;
|
|
297
297
|
|
|
298
|
+
case NodeKind::RenderExpression: {
|
|
299
|
+
auto& n = static_cast<RenderExpression&>(node);
|
|
300
|
+
addArgumentExprList(n.arguments, exprFields);
|
|
301
|
+
addAstNodeList(n.children, exprFields, nonExprChildren);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
298
304
|
case NodeKind::ModularCall: {
|
|
299
305
|
auto& n = static_cast<ModularCall&>(node);
|
|
300
306
|
addArgumentExprList(n.arguments, exprFields);
|
|
@@ -207,6 +207,7 @@ std::string fmtNode(const ASTNode& node, int indent, int w);
|
|
|
207
207
|
std::string fmtExpr(const ASTNode& expr, int indent, int w);
|
|
208
208
|
std::string fmtInst(const ASTNode& node, int indent, int w, const std::string& prefix);
|
|
209
209
|
std::string fmtListElem(const ASTNode& elem, int indent, int w);
|
|
210
|
+
std::string fmtBlock(const std::vector<std::unique_ptr<ASTNode>>& nodes, int indent, int w);
|
|
210
211
|
|
|
211
212
|
std::string fmtAssign(const Assignment& a, int indent, int w) {
|
|
212
213
|
return a.name->name + " = " + fmtExpr(*a.expr, indent, w);
|
|
@@ -508,6 +509,16 @@ std::string fmtExpr(const ASTNode& exprNode, int indent, int w) {
|
|
|
508
509
|
if (auto* lc = dynamic_cast<const ListComprehension*>(&exprNode)) {
|
|
509
510
|
return fmtListComprehension(*lc, indent, w);
|
|
510
511
|
}
|
|
512
|
+
if (auto* r = dynamic_cast<const RenderExpression*>(&exprNode)) {
|
|
513
|
+
// fmtBlock, NOT fmtChild: fmtChild drops the braces for a lone child,
|
|
514
|
+
// and `x = render() cube(1);` does not parse -- the child_statement
|
|
515
|
+
// swallows the `;` and leaves the assignment unterminated. fmtBlock
|
|
516
|
+
// always braces and routes children through fmtNode, which is what
|
|
517
|
+
// supplies their statement terminators. This arm is why toString()'s
|
|
518
|
+
// own (reference-matching, terminator-free) child rendering never
|
|
519
|
+
// reaches emitted source.
|
|
520
|
+
return "render(" + joinToString(r->arguments, ", ") + ") " + fmtBlock(r->children, indent, w);
|
|
521
|
+
}
|
|
511
522
|
return exprNode.toString();
|
|
512
523
|
}
|
|
513
524
|
|
|
@@ -267,6 +267,13 @@ json toJsonImpl(const ASTNode& node, bool includePos) {
|
|
|
267
267
|
j["elements"] = listToJson(static_cast<const ListComprehension&>(node).elements, includePos);
|
|
268
268
|
break;
|
|
269
269
|
|
|
270
|
+
case NodeKind::RenderExpression: {
|
|
271
|
+
auto& n = static_cast<const RenderExpression&>(node);
|
|
272
|
+
j["arguments"] = listToJson(n.arguments, includePos);
|
|
273
|
+
j["children"] = listToJson(n.children, includePos);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
|
|
270
277
|
case NodeKind::ModularCall: {
|
|
271
278
|
auto& n = static_cast<const ModularCall&>(node);
|
|
272
279
|
j["name"] = valueToJson(n.name.get(), includePos);
|
|
@@ -543,6 +550,11 @@ const std::unordered_map<std::string, Builder>& registry() {
|
|
|
543
550
|
return std::make_unique<ListComprehension>(std::move(pos), listFromJson<ASTNode>(j, "elements"));
|
|
544
551
|
}},
|
|
545
552
|
|
|
553
|
+
{"RenderExpression",
|
|
554
|
+
[](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
|
|
555
|
+
return std::make_unique<RenderExpression>(std::move(pos), listFromJson<Argument>(j, "arguments"),
|
|
556
|
+
listFromJson<ASTNode>(j, "children"));
|
|
557
|
+
}},
|
|
546
558
|
{"ModularCall",
|
|
547
559
|
[](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
|
|
548
560
|
return std::make_unique<ModularCall>(std::move(pos), childFromJson<Identifier>(j, "name"),
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// `render()` in EXPRESSION position: `obj = render() { cube(1); };`
|
|
2
|
+
//
|
|
3
|
+
// The statement form (`render() cube(1);`) is deliberately UNCHANGED -- it
|
|
4
|
+
// still parses to a plain ModularCall named "render", so everything
|
|
5
|
+
// downstream (builtin dispatch, the argument allowlist, json_io) sees what
|
|
6
|
+
// it always saw. Only the expression form is new, and it exists at all
|
|
7
|
+
// because LALR(1) cannot otherwise tell `render(` in expression position
|
|
8
|
+
// apart from a function call -- hence `render` being a reserved keyword.
|
|
9
|
+
|
|
10
|
+
#include "openscad_cpp_parser/api.hpp"
|
|
11
|
+
#include "openscad_cpp_parser/pretty_print.hpp"
|
|
12
|
+
#include "openscad_cpp_parser/serialization.hpp"
|
|
13
|
+
#include "test_helpers.hpp"
|
|
14
|
+
|
|
15
|
+
#include <gtest/gtest.h>
|
|
16
|
+
|
|
17
|
+
using namespace oscad;
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
const RenderExpression* renderExprOf(const std::vector<std::unique_ptr<ASTNode>>& ast, size_t idx = 0) {
|
|
22
|
+
auto* a = dynamic_cast<Assignment*>(ast[idx].get());
|
|
23
|
+
return a ? dynamic_cast<const RenderExpression*>(a->expr.get()) : nullptr;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace
|
|
27
|
+
|
|
28
|
+
// -- The expression form --------------------------------------------------
|
|
29
|
+
|
|
30
|
+
TEST(RenderExpression, ParsesInAssignment) {
|
|
31
|
+
auto ast = parseSrc("obj = render() { cube(1); };");
|
|
32
|
+
ASSERT_EQ(ast.size(), 1u);
|
|
33
|
+
const RenderExpression* r = renderExprOf(ast);
|
|
34
|
+
ASSERT_NE(r, nullptr);
|
|
35
|
+
EXPECT_EQ(r->kind(), NodeKind::RenderExpression);
|
|
36
|
+
EXPECT_TRUE(r->arguments.empty());
|
|
37
|
+
ASSERT_EQ(r->children.size(), 1u);
|
|
38
|
+
EXPECT_EQ(r->children[0]->kind(), NodeKind::ModularCall);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
TEST(RenderExpression, ParsesTheOriginalMotivatingExample) {
|
|
42
|
+
// A trailing `}` child needs no `;` of its own, so this form -- unlike a
|
|
43
|
+
// bare call -- terminates its enclosing assignment cleanly.
|
|
44
|
+
auto ast = parseSrc("obj = render() difference() { cube(100); sphere(20); };");
|
|
45
|
+
ASSERT_EQ(ast.size(), 1u);
|
|
46
|
+
const RenderExpression* r = renderExprOf(ast);
|
|
47
|
+
ASSERT_NE(r, nullptr);
|
|
48
|
+
ASSERT_EQ(r->children.size(), 1u);
|
|
49
|
+
auto* diff = dynamic_cast<ModularCall*>(r->children[0].get());
|
|
50
|
+
ASSERT_NE(diff, nullptr);
|
|
51
|
+
EXPECT_EQ(diff->name->name, "difference");
|
|
52
|
+
EXPECT_EQ(diff->children.size(), 2u);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
TEST(RenderExpression, SupportsMemberAccess) {
|
|
56
|
+
// Lives in `primary`, not `expr`, precisely so the existing
|
|
57
|
+
// `postfix "." NAME` rule applies.
|
|
58
|
+
auto ast = parseSrc("v = render() { cube(1); }.volume;");
|
|
59
|
+
ASSERT_EQ(ast.size(), 1u);
|
|
60
|
+
auto* a = dynamic_cast<Assignment*>(ast[0].get());
|
|
61
|
+
ASSERT_NE(a, nullptr);
|
|
62
|
+
auto* member = dynamic_cast<PrimaryMember*>(a->expr.get());
|
|
63
|
+
ASSERT_NE(member, nullptr);
|
|
64
|
+
EXPECT_EQ(member->member->name, "volume");
|
|
65
|
+
EXPECT_EQ(member->left->kind(), NodeKind::RenderExpression);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
TEST(RenderExpression, TakesArguments) {
|
|
69
|
+
auto ast = parseSrc("obj = render($fn=32) { sphere(10); };");
|
|
70
|
+
const RenderExpression* r = renderExprOf(ast);
|
|
71
|
+
ASSERT_NE(r, nullptr);
|
|
72
|
+
ASSERT_EQ(r->arguments.size(), 1u);
|
|
73
|
+
auto* named = dynamic_cast<NamedArgument*>(r->arguments[0].get());
|
|
74
|
+
ASSERT_NE(named, nullptr);
|
|
75
|
+
EXPECT_EQ(named->name->name, "$fn");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
TEST(RenderExpression, AcceptsEmptyAndMultipleChildren) {
|
|
79
|
+
EXPECT_EQ(renderExprOf(parseSrc("e = render() { };"))->children.size(), 0u);
|
|
80
|
+
EXPECT_EQ(renderExprOf(parseSrc("m = render() { cube(1); sphere(2); cylinder(3); };"))->children.size(), 3u);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
TEST(RenderExpression, WorksAsArgumentAndListElement) {
|
|
84
|
+
EXPECT_NO_THROW(parseSrc("echo(render() { cube(1); });"));
|
|
85
|
+
EXPECT_NO_THROW(parseSrc("v = [render() { cube(1); }, render() { sphere(2); }];"));
|
|
86
|
+
EXPECT_NO_THROW(parseSrc("v = 1 + render() { cube(1); }.volume;"));
|
|
87
|
+
EXPECT_NO_THROW(parseSrc("function f(a) = render() { cube(a); }.volume;"));
|
|
88
|
+
EXPECT_NO_THROW(parseSrc("v = [for (i = [0:2]) render() { cube(i); }];"));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// -- The statement form is untouched --------------------------------------
|
|
92
|
+
|
|
93
|
+
TEST(RenderExpression, StatementFormIsStillAModularCall) {
|
|
94
|
+
auto ast = parseSrc("render() cube(1);");
|
|
95
|
+
ASSERT_EQ(ast.size(), 1u);
|
|
96
|
+
auto* call = dynamic_cast<ModularCall*>(ast[0].get());
|
|
97
|
+
ASSERT_NE(call, nullptr);
|
|
98
|
+
EXPECT_EQ(call->kind(), NodeKind::ModularCall);
|
|
99
|
+
EXPECT_EQ(call->name->name, "render");
|
|
100
|
+
EXPECT_EQ(call->children.size(), 1u);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
TEST(RenderExpression, StatementFormKeepsArgumentsAndModifiers) {
|
|
104
|
+
auto ast = parseSrc("render(convexity=4) cube(1);\n#render() sphere(2);\nrender() { cube(1); sphere(2); }");
|
|
105
|
+
ASSERT_EQ(ast.size(), 3u);
|
|
106
|
+
auto* withArgs = dynamic_cast<ModularCall*>(ast[0].get());
|
|
107
|
+
ASSERT_NE(withArgs, nullptr);
|
|
108
|
+
EXPECT_EQ(withArgs->arguments.size(), 1u);
|
|
109
|
+
EXPECT_EQ(ast[1]->kind(), NodeKind::ModularModifierHighlight);
|
|
110
|
+
auto* braced = dynamic_cast<ModularCall*>(ast[2].get());
|
|
111
|
+
ASSERT_NE(braced, nullptr);
|
|
112
|
+
EXPECT_EQ(braced->children.size(), 2u);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// -- The sharp edge, asserted on purpose ----------------------------------
|
|
116
|
+
|
|
117
|
+
TEST(RenderExpression, BareCallChildDoesNotTerminateTheAssignment) {
|
|
118
|
+
// `cube(1);` IS the child_statement, semicolon included, so the enclosing
|
|
119
|
+
// assignment is left unterminated. Inherent to OpenSCAD's grammar, not a
|
|
120
|
+
// choice this implementation makes. The braced form is the documented
|
|
121
|
+
// idiom; this test exists so the behaviour is pinned rather than
|
|
122
|
+
// rediscovered.
|
|
123
|
+
EXPECT_THROW(parseSrc("obj = render() cube(1);"), ParseError);
|
|
124
|
+
EXPECT_NO_THROW(parseSrc("obj = render() { cube(1); };"));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// -- Reserving the keyword has costs; pin them ----------------------------
|
|
128
|
+
|
|
129
|
+
TEST(RenderExpression, RenderIsNowAReservedWord) {
|
|
130
|
+
EXPECT_THROW(parseSrc("render = 1;"), ParseError);
|
|
131
|
+
EXPECT_THROW(parseSrc("module render() { cube(1); }"), ParseError);
|
|
132
|
+
EXPECT_THROW(parseSrc("function render() = 1;"), ParseError);
|
|
133
|
+
EXPECT_THROW(parseSrc("cube(render=1);"), ParseError);
|
|
134
|
+
EXPECT_THROW(parseSrc("x = obj.render;"), ParseError);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
TEST(RenderExpression, DollarRenderIsStillAnOrdinaryName) {
|
|
138
|
+
// keywordTable() is keyed on the full IDENT text, `$` included.
|
|
139
|
+
EXPECT_NO_THROW(parseSrc("$render = 1;"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// -- Round-trips ----------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
TEST(RenderExpression, PrettyPrintOutputReparses) {
|
|
145
|
+
// The whole point: emitted source must parse back. toString()'s own
|
|
146
|
+
// child rendering follows the reference's terminator-free format, so
|
|
147
|
+
// fmtExpr has a dedicated arm that uses fmtBlock instead -- if that arm
|
|
148
|
+
// regresses, the second parse below throws.
|
|
149
|
+
const std::string src =
|
|
150
|
+
"obj = render() difference() { cube(100); sphere(20); };\n"
|
|
151
|
+
"v = render() { cube(1); }.volume;\n"
|
|
152
|
+
"w = render($fn = 32) { x = 5; cube(x); };\n"
|
|
153
|
+
"e = render() { };\n"
|
|
154
|
+
"render() cube(9);\n";
|
|
155
|
+
std::string once = toOpenscad(parseSrc(src));
|
|
156
|
+
std::string twice;
|
|
157
|
+
ASSERT_NO_THROW(twice = toOpenscad(parseSrc(once)));
|
|
158
|
+
EXPECT_EQ(once, twice) << "formatting is not idempotent:\n" << once;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
TEST(RenderExpression, ToStringIsReparseable) {
|
|
162
|
+
auto ast = parseSrc("obj = render($fn = 32) { cube(1); sphere(2); };");
|
|
163
|
+
const RenderExpression* r = renderExprOf(ast);
|
|
164
|
+
ASSERT_NE(r, nullptr);
|
|
165
|
+
EXPECT_EQ(r->toString(), "render($fn = 32) { cube(1); sphere(2); }");
|
|
166
|
+
EXPECT_NO_THROW(parseSrc("y = " + r->toString() + ";"));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
TEST(RenderExpression, JsonRoundTrip) {
|
|
170
|
+
const std::string src =
|
|
171
|
+
"obj = render() difference() { cube(100); sphere(20); };\n"
|
|
172
|
+
"w = render($fn = 32) { x = 5; cube(x); };\n"
|
|
173
|
+
"e = render() { };\n";
|
|
174
|
+
auto ast = parseSrc(src);
|
|
175
|
+
std::string before = toOpenscad(ast);
|
|
176
|
+
auto rebuilt = astFromJsonString(astToJsonString(ast));
|
|
177
|
+
EXPECT_EQ(before, toOpenscad(rebuilt));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// -- Scope ----------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
TEST(RenderExpression, HoistsAssignmentsInItsChildBlock) {
|
|
183
|
+
// buildScope() must call collectHoistedDeclarations, exactly as
|
|
184
|
+
// ModularCall does, or `x` below resolves to nothing.
|
|
185
|
+
EXPECT_NO_THROW(parseSrc("obj = render() { cube(x); x = 5; };"));
|
|
186
|
+
}
|
|
@@ -892,6 +892,13 @@ struct CompiledChunk {
|
|
|
892
892
|
Color,
|
|
893
893
|
Modifier,
|
|
894
894
|
Passthrough, // hull()/minkowski()/render() -- empty params, no compute function needed
|
|
895
|
+
// render() in EXPRESSION position. The only Kind whose Pop
|
|
896
|
+
// does NOT append a CSGNode to the parent's accumulator: it
|
|
897
|
+
// discards the subtree and pushes the measurement object() onto
|
|
898
|
+
// the operand stack instead. Its `node` is a RenderExpression,
|
|
899
|
+
// not a ModularCall -- warnUnexpectedBuiltinArgs already
|
|
900
|
+
// self-guards on that (registry.cpp).
|
|
901
|
+
Measure,
|
|
895
902
|
LinearExtrude,
|
|
896
903
|
RotateExtrude,
|
|
897
904
|
Projection,
|
|
@@ -901,6 +908,14 @@ struct CompiledChunk {
|
|
|
901
908
|
Kind kind;
|
|
902
909
|
std::string tagName; // "translate"/"rotate"/.../"color"/"highlight"/"background"/"show_only"/"hull"/...
|
|
903
910
|
const oscad::ASTNode* node = nullptr;
|
|
911
|
+
// Kind::Measure only. (name, slot) for every local visible at this
|
|
912
|
+
// site, so Op::PushBuiltinWrap can publish them into the children's
|
|
913
|
+
// EvalContext. A render() expression's children are STATEMENT
|
|
914
|
+
// opcodes and resolve names through the context, but a compiled
|
|
915
|
+
// FUNCTION keeps its parameters and lets in frame slots instead --
|
|
916
|
+
// without this, `function f(w) = render() { cube(w); }.volume;`
|
|
917
|
+
// cannot see `w` and silently builds nothing.
|
|
918
|
+
std::vector<std::pair<std::string, int>> capturedLocals;
|
|
904
919
|
};
|
|
905
920
|
|
|
906
921
|
// One Op::PushCsgWrap/PopCsgWrap site pair -- see those ops' own doc
|
|
@@ -38,6 +38,18 @@ struct PendingBuiltinWrap {
|
|
|
38
38
|
// other kind computes its params at Push time and leaves this default-
|
|
39
39
|
// empty.
|
|
40
40
|
CallArgs deferredArgs;
|
|
41
|
+
|
|
42
|
+
// ev.measuring_ as it stood immediately BEFORE this bracket opened.
|
|
43
|
+
// Recorded for EVERY kind, not just Measure, so the exception teardown
|
|
44
|
+
// can restore from front() without inspecting kinds -- a non-Measure
|
|
45
|
+
// entry simply records and restores the same value.
|
|
46
|
+
bool savedMeasuring = false;
|
|
47
|
+
// f.stack.size() and ev.treeStack_.size() at Push time. Kind::Measure's
|
|
48
|
+
// Pop asserts both: it is the first bracket that runs STATEMENT opcodes
|
|
49
|
+
// with a non-empty operand stack beneath it, and nothing structurally
|
|
50
|
+
// enforces that those statements are operand-stack-neutral.
|
|
51
|
+
size_t stackDepthAtPush = 0;
|
|
52
|
+
size_t treeStackDepthAtPush = 0;
|
|
41
53
|
};
|
|
42
54
|
|
|
43
55
|
// One still-open Op::PushCsgWrap bracket's own state -- see that op's own
|