openscad-cpp-evaluator 1.2.0__tar.gz → 1.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/PKG-INFO +1 -1
- openscad_cpp_evaluator-1.3.1/include/openscad_cpp_evaluator/format_closure.hpp +20 -0
- openscad_cpp_evaluator-1.3.1/include/openscad_cpp_evaluator/osc_range.hpp +44 -0
- openscad_cpp_evaluator-1.3.1/include/openscad_cpp_evaluator/utf8.hpp +35 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/CMakeLists.txt +2 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/function_builtins.cpp +38 -10
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/expr_eval.cpp +8 -2
- openscad_cpp_evaluator-1.3.1/src/format_closure.cpp +217 -0
- openscad_cpp_evaluator-1.3.1/src/utf8.cpp +61 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/value.cpp +49 -8
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_control_flow.cpp +6 -3
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_python_bindings.py +184 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/uv.lock +1 -1
- openscad_cpp_evaluator-1.2.0/include/openscad_cpp_evaluator/osc_range.hpp +0 -19
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/README.md +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_feature_detection.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/main.cpp +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
|
|
5
|
+
namespace oscad {
|
|
6
|
+
class FunctionLiteral;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
namespace oscadeval {
|
|
10
|
+
|
|
11
|
+
// A function literal spelled the way the reference spells it when a script
|
|
12
|
+
// does `str(f)` or `echo(f)`: its own source, re-printed from the AST with
|
|
13
|
+
// every binary operator and ternary parenthesised.
|
|
14
|
+
//
|
|
15
|
+
// Deliberately not the parser's ASTNode::toString(), which is
|
|
16
|
+
// precedence-minimal and which 94 places inside the parser depend on. See
|
|
17
|
+
// format_closure.cpp for where each rule came from.
|
|
18
|
+
std::string formatFunctionLiteral(const oscad::FunctionLiteral& fn);
|
|
19
|
+
|
|
20
|
+
} // namespace oscadeval
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
|
|
5
|
+
namespace oscadeval {
|
|
6
|
+
|
|
7
|
+
// `[start:step:end]` -- a plain 3-slot struct, not an expanded list. Mirrors
|
|
8
|
+
// the Python reference's OscRange object exactly: echoed as
|
|
9
|
+
// "[start : step : end]", only expanded to a list when iterated (for/list
|
|
10
|
+
// comprehension/intersection_for).
|
|
11
|
+
struct OscRange {
|
|
12
|
+
double start = 0.0;
|
|
13
|
+
double step = 1.0;
|
|
14
|
+
double end = 0.0;
|
|
15
|
+
|
|
16
|
+
// Yields nothing: a zero step, a direction that disagrees with the
|
|
17
|
+
// bounds, or a NaN anywhere in it.
|
|
18
|
+
bool isEmpty() const {
|
|
19
|
+
if (std::isnan(start) || std::isnan(step) || std::isnan(end)) return true;
|
|
20
|
+
if (step == 0.0) return true;
|
|
21
|
+
return (end - start) / step < -1e-10;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Two EMPTY ranges are equal whatever their bounds -- the reference
|
|
25
|
+
// compares element counts first, so `[5:1:0] == [10:1:0]` is true, and
|
|
26
|
+
// so is `[0:nan:inf] == [5:1:0]`. Only if both yield something does it
|
|
27
|
+
// compare start/step/end.
|
|
28
|
+
//
|
|
29
|
+
// This is not a NaN rule, though a NaN range is caught by it: checked
|
|
30
|
+
// against the reference, `nan == nan` is false and `[nan] == [nan]` is
|
|
31
|
+
// false, so nothing else treats NaN as equal to itself. Ranges are
|
|
32
|
+
// special because emptiness is.
|
|
33
|
+
//
|
|
34
|
+
// It matters well beyond range comparison: BOSL2 defines
|
|
35
|
+
// `is_nan(x) = (x != x)`, so a self-unequal range made typeof() answer
|
|
36
|
+
// "nan" for one, where the reference says "invalid".
|
|
37
|
+
friend bool operator==(const OscRange& a, const OscRange& b) {
|
|
38
|
+
const bool ae = a.isEmpty(), be = b.isEmpty();
|
|
39
|
+
if (ae || be) return ae && be;
|
|
40
|
+
return a.start == b.start && a.step == b.step && a.end == b.end;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
} // namespace oscadeval
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
namespace oscadeval {
|
|
7
|
+
|
|
8
|
+
// A string is a sequence of CHARACTERS to a script, not of bytes. The
|
|
9
|
+
// reference reports len("aé—z") as 4, indexes s[1] to "é", and iterates the
|
|
10
|
+
// same four characters -- while a std::string holds 8 bytes for that. These
|
|
11
|
+
// three are what keeps the two views apart.
|
|
12
|
+
//
|
|
13
|
+
// Storage stays UTF-8. Indexing therefore scans, which is O(n) per index and
|
|
14
|
+
// so O(n^2) over a loop -- BOSL2's _str_count_leading is exactly that loop.
|
|
15
|
+
// The saving grace is that a pure-ASCII string needs no scan at all: byte i
|
|
16
|
+
// IS character i, and every function here takes that path first. Only a
|
|
17
|
+
// string that actually holds non-ASCII pays, and those are short in
|
|
18
|
+
// practice.
|
|
19
|
+
|
|
20
|
+
// True when every byte is < 0x80, so byte offsets and character offsets are
|
|
21
|
+
// the same thing.
|
|
22
|
+
bool utf8IsAscii(const std::string& s);
|
|
23
|
+
|
|
24
|
+
// Characters, not bytes. Counts lead bytes, so a malformed sequence counts
|
|
25
|
+
// the same way the reference's own decoder would rather than throwing.
|
|
26
|
+
size_t utf8Length(const std::string& s);
|
|
27
|
+
|
|
28
|
+
// The i-th character as its own string; empty if i is past the end. Empty is
|
|
29
|
+
// distinguishable from a real result because a character is never zero-length.
|
|
30
|
+
std::string utf8CharAt(const std::string& s, size_t i);
|
|
31
|
+
|
|
32
|
+
// Every character as its own string.
|
|
33
|
+
std::vector<std::string> utf8Chars(const std::string& s);
|
|
34
|
+
|
|
35
|
+
} // namespace oscadeval
|
{openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/function_builtins.cpp
RENAMED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
7
7
|
#include "openscad_cpp_evaluator/segments.hpp"
|
|
8
8
|
#include "openscad_cpp_evaluator/text_metrics.hpp"
|
|
9
|
+
#include "openscad_cpp_evaluator/utf8.hpp"
|
|
9
10
|
|
|
10
11
|
#include "openscad_cpp_parser/ast/ast_node.hpp"
|
|
11
12
|
|
|
@@ -613,10 +614,24 @@ Value builtinRands(double minv, double maxv, double nArg, const Value& seedArg)
|
|
|
613
614
|
// a sequence of characters -- a `search(["a"], "abc")` vector needle sees
|
|
614
615
|
// that same string as an empty table, not as characters.
|
|
615
616
|
Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position* pos) {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
617
|
+
// The reference's own documented parameter names. These used to be
|
|
618
|
+
// "match"/"vector"/"num_returns"/"index_col", which are not names
|
|
619
|
+
// OpenSCAD ever documented, so a script written against the manual --
|
|
620
|
+
// BOSL2's in_list(), which passes num_returns_per_match and
|
|
621
|
+
// index_col_num -- bound neither and silently got the defaults.
|
|
622
|
+
//
|
|
623
|
+
// Worth knowing, though deliberately NOT copied: the reference ignores
|
|
624
|
+
// these names altogether and binds search() purely by position.
|
|
625
|
+
// `search(zzz=m, qqq=t, www=1, eee=1)` returns the right answer there,
|
|
626
|
+
// while the correct names in the wrong order return the wrong one.
|
|
627
|
+
// CallArgs keeps positional and named arguments apart and does not
|
|
628
|
+
// record where a named one was written, so matching that would mean
|
|
629
|
+
// changing how every call collects its arguments -- for a quirk no
|
|
630
|
+
// script relies on deliberately.
|
|
631
|
+
const Value matchArg = getArg(args, 0, "match_value", Value{});
|
|
632
|
+
const Value tableArg = getArg(args, 1, "string_or_vector", Value{});
|
|
633
|
+
const double nrRaw = toDoubleLenient(getArg(args, 2, "num_returns_per_match", Value{1.0}));
|
|
634
|
+
const double icRaw = toDoubleLenient(getArg(args, 3, "index_col_num", Value{0.0}));
|
|
620
635
|
// Both are `unsigned int` in the reference, so a negative argument wraps
|
|
621
636
|
// to a huge positive rather than meaning "unlimited"/"column 0".
|
|
622
637
|
const unsigned numReturns = static_cast<unsigned>(static_cast<long long>(nrRaw));
|
|
@@ -654,11 +669,17 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
|
|
|
654
669
|
if (const std::string* needle = std::get_if<std::string>(&matchArg)) {
|
|
655
670
|
std::vector<Value> out;
|
|
656
671
|
if (const std::string* hay = std::get_if<std::string>(&tableArg)) {
|
|
657
|
-
|
|
672
|
+
// Both sides walked as CHARACTERS, and the indices reported are
|
|
673
|
+
// character indices. Comparing bytes made a multi-byte needle
|
|
674
|
+
// search once per byte -- search("é", "aé—z") answered [1, 2]
|
|
675
|
+
// where the reference answers [1].
|
|
676
|
+
const std::vector<std::string> needleChars = utf8Chars(*needle);
|
|
677
|
+
const std::vector<std::string> hayChars = utf8Chars(*hay);
|
|
678
|
+
for (size_t i = 0; i < needleChars.size(); ++i) {
|
|
658
679
|
unsigned matchCount = 0;
|
|
659
680
|
std::vector<Value> resultvec;
|
|
660
|
-
for (size_t j = 0; j <
|
|
661
|
-
if (
|
|
681
|
+
for (size_t j = 0; j < hayChars.size(); ++j) {
|
|
682
|
+
if (needleChars[i] != hayChars[j]) continue;
|
|
662
683
|
++matchCount;
|
|
663
684
|
if (numReturns == 1) {
|
|
664
685
|
out.push_back(num(j));
|
|
@@ -675,7 +696,10 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
|
|
|
675
696
|
// with more than index_col elements. A single bad entry aborts the
|
|
676
697
|
// WHOLE call with an empty result, not just that row -- which is
|
|
677
698
|
// why `search("a", ["a","b"])` is [] and not [0].
|
|
678
|
-
|
|
699
|
+
// Character-wise here too: a needle character is what gets looked
|
|
700
|
+
// up in each row, so a multi-byte one must not be split.
|
|
701
|
+
const std::vector<std::string> needleChars = utf8Chars(*needle);
|
|
702
|
+
for (size_t i = 0; i < needleChars.size(); ++i) {
|
|
679
703
|
unsigned matchCount = 0;
|
|
680
704
|
std::vector<Value> resultvec;
|
|
681
705
|
for (size_t j = 0; j < table.size(); ++j) {
|
|
@@ -689,7 +713,10 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
|
|
|
689
713
|
}
|
|
690
714
|
const std::string* entry = std::get_if<std::string>(&entryVec[indexCol]);
|
|
691
715
|
// A type mismatch just doesn't match, exactly as `==` would.
|
|
692
|
-
|
|
716
|
+
// First CHARACTER of the entry against the needle character:
|
|
717
|
+
// comparing first bytes made "é" and any other character
|
|
718
|
+
// sharing a lead byte look equal.
|
|
719
|
+
if (!entry || entry->empty() || utf8CharAt(*entry, 0) != needleChars[i]) continue;
|
|
693
720
|
++matchCount;
|
|
694
721
|
if (numReturns == 1) {
|
|
695
722
|
out.push_back(num(j));
|
|
@@ -1367,7 +1394,8 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
|
|
|
1367
1394
|
case BuiltinFnId::Len: {
|
|
1368
1395
|
const Value x = getArg(args, 0, "x", Value{});
|
|
1369
1396
|
if (const ListPtr* l = std::get_if<ListPtr>(&x); l && *l) return Value{static_cast<double>((*l)->items.size())};
|
|
1370
|
-
|
|
1397
|
+
// Characters, not bytes: the reference reports len("aé—z") as 4.
|
|
1398
|
+
if (const std::string* s = std::get_if<std::string>(&x)) return Value{static_cast<double>(utf8Length(*s))};
|
|
1371
1399
|
if (const ObjectPtr* o = std::get_if<ObjectPtr>(&x); o && *o) return Value{static_cast<double>((*o)->items.size())};
|
|
1372
1400
|
return Value{};
|
|
1373
1401
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
2
|
+
#include "openscad_cpp_evaluator/utf8.hpp"
|
|
2
3
|
|
|
3
4
|
#include "openscad_cpp_evaluator/call_args.hpp"
|
|
4
5
|
#include "openscad_cpp_evaluator/eval_error.hpp"
|
|
@@ -803,8 +804,13 @@ Value Evaluator::applyIndexAccess(const Value& obj, const Value& idx) {
|
|
|
803
804
|
if (const std::string* s = std::get_if<std::string>(&obj)) {
|
|
804
805
|
if (const double* d = std::get_if<double>(&idx)) {
|
|
805
806
|
const int i = static_cast<int>(*d);
|
|
806
|
-
if (i < 0
|
|
807
|
-
|
|
807
|
+
if (i < 0) return Value{};
|
|
808
|
+
// The i-th CHARACTER. Taking byte i split a multi-byte one and
|
|
809
|
+
// handed back half of it, which then failed to decode at the
|
|
810
|
+
// boundary -- an error, not merely a wrong answer.
|
|
811
|
+
std::string ch = utf8CharAt(*s, static_cast<size_t>(i));
|
|
812
|
+
if (ch.empty()) return Value{};
|
|
813
|
+
return Value{std::move(ch)};
|
|
808
814
|
}
|
|
809
815
|
return Value{};
|
|
810
816
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Printing a function literal back out as source, the way the reference
|
|
2
|
+
// spells it when you `str()` or `echo()` one.
|
|
3
|
+
//
|
|
4
|
+
// Neither existing printer would do. The parser's own ASTNode::toString()
|
|
5
|
+
// is precedence-MINIMAL -- it drops any parenthesis the grammar does not
|
|
6
|
+
// need -- and 94 places inside the parser depend on that spelling. The
|
|
7
|
+
// reference is the opposite: it parenthesises every binary operator and
|
|
8
|
+
// every ternary whether or not precedence requires it, so `f(a + b)` comes
|
|
9
|
+
// back as `f((a + b))`.
|
|
10
|
+
//
|
|
11
|
+
// BOSL2's fnliterals tests compare these strings exactly (61 of them), so
|
|
12
|
+
// this has to agree with the reference character for character. Every rule
|
|
13
|
+
// below was read off the reference by echoing the shape and copying what it
|
|
14
|
+
// printed -- see test_format_closure.cpp, which carries the same shapes as
|
|
15
|
+
// cases.
|
|
16
|
+
#include "openscad_cpp_evaluator/format_closure.hpp"
|
|
17
|
+
|
|
18
|
+
#include <string>
|
|
19
|
+
|
|
20
|
+
#include "openscad_cpp_parser/ast.hpp"
|
|
21
|
+
|
|
22
|
+
namespace oscadeval {
|
|
23
|
+
namespace {
|
|
24
|
+
|
|
25
|
+
using oscad::ASTNode;
|
|
26
|
+
using oscad::NodeKind;
|
|
27
|
+
|
|
28
|
+
std::string fmt(const ASTNode& n);
|
|
29
|
+
|
|
30
|
+
// `(a + b)`, always. The reference parenthesises a binary operator
|
|
31
|
+
// unconditionally -- nesting one inside another gives `((a + b) + c)`, and
|
|
32
|
+
// even a call argument gets them: `f((a + b))`.
|
|
33
|
+
//
|
|
34
|
+
// Templated on the node type rather than casting every operator to one of
|
|
35
|
+
// them. The binary classes are generated by a single macro so they do share
|
|
36
|
+
// a layout, but they are distinct types with no common binary base, and
|
|
37
|
+
// casting a MultiplicationOp to an AdditionOp would be undefined however
|
|
38
|
+
// reliably it happens to work.
|
|
39
|
+
template <typename T>
|
|
40
|
+
std::string binary(const ASTNode& n, const char* op) {
|
|
41
|
+
const auto& e = static_cast<const T&>(n);
|
|
42
|
+
return "(" + fmt(*e.left) + " " + op + " " + fmt(*e.right) + ")";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// `-x`, not `(-x)`. A unary operator is never parenthesised itself, though
|
|
46
|
+
// its operand still follows the rules: `-(a + b)`.
|
|
47
|
+
template <typename T>
|
|
48
|
+
std::string unary(const ASTNode& n, const char* op) {
|
|
49
|
+
const auto& e = static_cast<const T&>(n);
|
|
50
|
+
return std::string(op) + fmt(*e.expr);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
template <typename Seq>
|
|
54
|
+
std::string join(const Seq& items, const char* sep) {
|
|
55
|
+
std::string out;
|
|
56
|
+
for (size_t i = 0; i < items.size(); ++i) {
|
|
57
|
+
if (i) out += sep;
|
|
58
|
+
out += fmt(*items[i]);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A list-comprehension body is wrapped, on top of whatever the body itself
|
|
64
|
+
// prints: `[for(i = [0 : a]) ((i + 1))]`, and `(i)` for a bare identifier.
|
|
65
|
+
// A plain vector element is NOT wrapped -- `[1, (2 + 3), a]` -- so the
|
|
66
|
+
// parentheses belong to for/if/each rather than to the vector.
|
|
67
|
+
std::string wrapped(const ASTNode& n) { return "(" + fmt(n) + ")"; }
|
|
68
|
+
|
|
69
|
+
std::string fmt(const ASTNode& n) {
|
|
70
|
+
switch (n.kind()) {
|
|
71
|
+
case NodeKind::Identifier:
|
|
72
|
+
return static_cast<const oscad::Identifier&>(n).name;
|
|
73
|
+
case NodeKind::StringLiteral:
|
|
74
|
+
case NodeKind::NumberLiteral:
|
|
75
|
+
case NodeKind::BooleanLiteral:
|
|
76
|
+
case NodeKind::UndefinedLiteral:
|
|
77
|
+
// The parser already spells these the way the reference does,
|
|
78
|
+
// escaping and number formatting included.
|
|
79
|
+
return n.toString();
|
|
80
|
+
|
|
81
|
+
case NodeKind::RangeLiteral: {
|
|
82
|
+
const auto& r = static_cast<const oscad::RangeLiteral&>(n);
|
|
83
|
+
// implicitStep, not just `step != nullptr`: the parser fills in a
|
|
84
|
+
// step of 1 for `[0:a]`, and printing it back gives `[0 : 1 : a]`
|
|
85
|
+
// where the reference says `[0 : a]`. The flag is there to tell the
|
|
86
|
+
// two apart.
|
|
87
|
+
std::string s = "[" + fmt(*r.start) + " : ";
|
|
88
|
+
if (r.step && !r.implicitStep) s += fmt(*r.step) + " : ";
|
|
89
|
+
return s + fmt(*r.end) + "]";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case NodeKind::UnaryMinusOp: return unary<oscad::UnaryMinusOp>(n, "-");
|
|
93
|
+
case NodeKind::LogicalNotOp: return unary<oscad::LogicalNotOp>(n, "!");
|
|
94
|
+
case NodeKind::BitwiseNotOp: return unary<oscad::BitwiseNotOp>(n, "~");
|
|
95
|
+
|
|
96
|
+
case NodeKind::AdditionOp: return binary<oscad::AdditionOp>(n, "+");
|
|
97
|
+
case NodeKind::SubtractionOp: return binary<oscad::SubtractionOp>(n, "-");
|
|
98
|
+
case NodeKind::MultiplicationOp: return binary<oscad::MultiplicationOp>(n, "*");
|
|
99
|
+
case NodeKind::DivisionOp: return binary<oscad::DivisionOp>(n, "/");
|
|
100
|
+
case NodeKind::ModuloOp: return binary<oscad::ModuloOp>(n, "%");
|
|
101
|
+
case NodeKind::ExponentOp: return binary<oscad::ExponentOp>(n, "^");
|
|
102
|
+
case NodeKind::BitwiseAndOp: return binary<oscad::BitwiseAndOp>(n, "&");
|
|
103
|
+
case NodeKind::BitwiseOrOp: return binary<oscad::BitwiseOrOp>(n, "|");
|
|
104
|
+
case NodeKind::BitwiseShiftLeftOp: return binary<oscad::BitwiseShiftLeftOp>(n, "<<");
|
|
105
|
+
case NodeKind::BitwiseShiftRightOp: return binary<oscad::BitwiseShiftRightOp>(n, ">>");
|
|
106
|
+
case NodeKind::LogicalAndOp: return binary<oscad::LogicalAndOp>(n, "&&");
|
|
107
|
+
case NodeKind::LogicalOrOp: return binary<oscad::LogicalOrOp>(n, "||");
|
|
108
|
+
case NodeKind::EqualityOp: return binary<oscad::EqualityOp>(n, "==");
|
|
109
|
+
case NodeKind::InequalityOp: return binary<oscad::InequalityOp>(n, "!=");
|
|
110
|
+
case NodeKind::GreaterThanOp: return binary<oscad::GreaterThanOp>(n, ">");
|
|
111
|
+
case NodeKind::GreaterThanOrEqualOp: return binary<oscad::GreaterThanOrEqualOp>(n, ">=");
|
|
112
|
+
case NodeKind::LessThanOp: return binary<oscad::LessThanOp>(n, "<");
|
|
113
|
+
case NodeKind::LessThanOrEqualOp: return binary<oscad::LessThanOrEqualOp>(n, "<=");
|
|
114
|
+
|
|
115
|
+
case NodeKind::TernaryOp: {
|
|
116
|
+
const auto& t = static_cast<const oscad::TernaryOp&>(n);
|
|
117
|
+
return "(" + fmt(*t.condition) + " ? " + fmt(*t.trueExpr) + " : " + fmt(*t.falseExpr) + ")";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case NodeKind::PrimaryCall: {
|
|
121
|
+
const auto& c = static_cast<const oscad::PrimaryCall&>(n);
|
|
122
|
+
return fmt(*c.left) + "(" + join(c.arguments, ", ") + ")";
|
|
123
|
+
}
|
|
124
|
+
case NodeKind::PrimaryIndex: {
|
|
125
|
+
const auto& i = static_cast<const oscad::PrimaryIndex&>(n);
|
|
126
|
+
return fmt(*i.left) + "[" + fmt(*i.index) + "]";
|
|
127
|
+
}
|
|
128
|
+
case NodeKind::PrimaryMember: {
|
|
129
|
+
const auto& m = static_cast<const oscad::PrimaryMember&>(n);
|
|
130
|
+
return fmt(*m.left) + "." + m.member->name;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case NodeKind::PositionalArgument:
|
|
134
|
+
return fmt(*static_cast<const oscad::PositionalArgument&>(n).expr);
|
|
135
|
+
case NodeKind::NamedArgument: {
|
|
136
|
+
const auto& a = static_cast<const oscad::NamedArgument&>(n);
|
|
137
|
+
return a.name->name + " = " + fmt(*a.expr);
|
|
138
|
+
}
|
|
139
|
+
case NodeKind::Assignment: {
|
|
140
|
+
const auto& a = static_cast<const oscad::Assignment&>(n);
|
|
141
|
+
return a.name->name + " = " + fmt(*a.expr);
|
|
142
|
+
}
|
|
143
|
+
case NodeKind::ParameterDeclaration: {
|
|
144
|
+
const auto& p = static_cast<const oscad::ParameterDeclaration&>(n);
|
|
145
|
+
return p.defaultValue ? p.name->name + " = " + fmt(*p.defaultValue) : p.name->name;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case NodeKind::FunctionLiteral: {
|
|
149
|
+
const auto& f = static_cast<const oscad::FunctionLiteral&>(n);
|
|
150
|
+
return "function(" + join(f.parameters, ", ") + ") " + fmt(*f.body);
|
|
151
|
+
}
|
|
152
|
+
case NodeKind::LetOp: {
|
|
153
|
+
const auto& l = static_cast<const oscad::LetOp&>(n);
|
|
154
|
+
// Body bare: `let(c = (a + b)) c`. What looks like a wrapped body
|
|
155
|
+
// in `let(a = 1) (a + x)` is the binary parenthesising itself.
|
|
156
|
+
return "let(" + join(l.assignments, ", ") + ") " + fmt(*l.body);
|
|
157
|
+
}
|
|
158
|
+
case NodeKind::EchoOp: {
|
|
159
|
+
const auto& e = static_cast<const oscad::EchoOp&>(n);
|
|
160
|
+
return "echo(" + join(e.arguments, ", ") + ") " + fmt(*e.body);
|
|
161
|
+
}
|
|
162
|
+
case NodeKind::AssertOp: {
|
|
163
|
+
const auto& a = static_cast<const oscad::AssertOp&>(n);
|
|
164
|
+
return "assert(" + join(a.arguments, ", ") + ") " + fmt(*a.body);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case NodeKind::ListComprehension: {
|
|
168
|
+
const auto& c = static_cast<const oscad::ListComprehension&>(n);
|
|
169
|
+
return "[" + join(c.elements, ", ") + "]";
|
|
170
|
+
}
|
|
171
|
+
case NodeKind::ListCompFor: {
|
|
172
|
+
const auto& f = static_cast<const oscad::ListCompFor&>(n);
|
|
173
|
+
return "for(" + join(f.assignments, ", ") + ") " + wrapped(*f.body);
|
|
174
|
+
}
|
|
175
|
+
case NodeKind::ListCompCFor: {
|
|
176
|
+
const auto& f = static_cast<const oscad::ListCompCFor&>(n);
|
|
177
|
+
// The odd one out: no space after the semicolons, and the body is
|
|
178
|
+
// NOT wrapped -- `[for(i = 0;(i < a);i = (i + 1)) i]`.
|
|
179
|
+
return "for(" + join(f.inits, ", ") + ";" + fmt(*f.condition) + ";" +
|
|
180
|
+
join(f.incrs, ", ") + ") " + fmt(*f.body);
|
|
181
|
+
}
|
|
182
|
+
case NodeKind::ListCompIf: {
|
|
183
|
+
const auto& i = static_cast<const oscad::ListCompIf&>(n);
|
|
184
|
+
return "if(" + fmt(*i.condition) + ") " + wrapped(*i.trueExpr);
|
|
185
|
+
}
|
|
186
|
+
case NodeKind::ListCompIfElse: {
|
|
187
|
+
const auto& i = static_cast<const oscad::ListCompIfElse&>(n);
|
|
188
|
+
return "if(" + fmt(*i.condition) + ") " + wrapped(*i.trueExpr) +
|
|
189
|
+
" else " + wrapped(*i.falseExpr);
|
|
190
|
+
}
|
|
191
|
+
case NodeKind::ListCompEach: {
|
|
192
|
+
const auto& e = static_cast<const oscad::ListCompEach&>(n);
|
|
193
|
+
return "each " + wrapped(*e.body);
|
|
194
|
+
}
|
|
195
|
+
case NodeKind::ListCompLet: {
|
|
196
|
+
const auto& l = static_cast<const oscad::ListCompLet&>(n);
|
|
197
|
+
return "let(" + join(l.assignments, ", ") + ") " + fmt(*l.body);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case NodeKind::CommentedExpr:
|
|
201
|
+
// Comments are not part of what the reference prints.
|
|
202
|
+
return fmt(*static_cast<const oscad::CommentedExpr&>(n).expr);
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
// Anything not reachable from inside a function literal's body --
|
|
206
|
+
// statements, module instantiations, include directives. Falling
|
|
207
|
+
// back to the parser's own spelling is closer than an empty string,
|
|
208
|
+
// and the value here is diagnostic rather than compared.
|
|
209
|
+
return n.toString();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
} // namespace
|
|
214
|
+
|
|
215
|
+
std::string formatFunctionLiteral(const oscad::FunctionLiteral& fn) { return fmt(fn); }
|
|
216
|
+
|
|
217
|
+
} // namespace oscadeval
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#include "openscad_cpp_evaluator/utf8.hpp"
|
|
2
|
+
|
|
3
|
+
namespace oscadeval {
|
|
4
|
+
namespace {
|
|
5
|
+
|
|
6
|
+
// A continuation byte is 0b10xxxxxx. Every other byte starts a character,
|
|
7
|
+
// which is what makes counting and stepping possible without decoding.
|
|
8
|
+
inline bool isContinuation(unsigned char c) { return (c & 0xC0) == 0x80; }
|
|
9
|
+
|
|
10
|
+
// Past the end of the character starting at `i`. Advances at least one byte
|
|
11
|
+
// even for a malformed sequence, so no caller can loop forever on bad input.
|
|
12
|
+
size_t nextCharStart(const std::string& s, size_t i) {
|
|
13
|
+
++i;
|
|
14
|
+
while (i < s.size() && isContinuation(static_cast<unsigned char>(s[i]))) ++i;
|
|
15
|
+
return i;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
} // namespace
|
|
19
|
+
|
|
20
|
+
bool utf8IsAscii(const std::string& s) {
|
|
21
|
+
for (char c : s) {
|
|
22
|
+
if (static_cast<unsigned char>(c) >= 0x80) return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
size_t utf8Length(const std::string& s) {
|
|
28
|
+
if (utf8IsAscii(s)) return s.size();
|
|
29
|
+
size_t n = 0;
|
|
30
|
+
for (size_t i = 0; i < s.size(); i = nextCharStart(s, i)) ++n;
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
std::string utf8CharAt(const std::string& s, size_t i) {
|
|
35
|
+
if (utf8IsAscii(s)) return i < s.size() ? std::string(1, s[i]) : std::string{};
|
|
36
|
+
size_t at = 0;
|
|
37
|
+
for (size_t b = 0; b < s.size(); b = nextCharStart(s, b)) {
|
|
38
|
+
if (at++ == i) {
|
|
39
|
+
const size_t end = nextCharStart(s, b);
|
|
40
|
+
return s.substr(b, end - b);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
std::vector<std::string> utf8Chars(const std::string& s) {
|
|
47
|
+
std::vector<std::string> out;
|
|
48
|
+
if (utf8IsAscii(s)) {
|
|
49
|
+
out.reserve(s.size());
|
|
50
|
+
for (char c : s) out.emplace_back(1, c);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
for (size_t b = 0; b < s.size();) {
|
|
54
|
+
const size_t end = nextCharStart(s, b);
|
|
55
|
+
out.push_back(s.substr(b, end - b));
|
|
56
|
+
b = end;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
} // namespace oscadeval
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#include "openscad_cpp_evaluator/value.hpp"
|
|
2
2
|
|
|
3
|
+
#include "openscad_cpp_evaluator/format_closure.hpp"
|
|
4
|
+
#include "openscad_cpp_evaluator/utf8.hpp"
|
|
5
|
+
|
|
3
6
|
#include <algorithm>
|
|
4
7
|
#include <cmath>
|
|
5
8
|
#include <limits>
|
|
@@ -475,7 +478,13 @@ std::string fmtValue(const Value& v) {
|
|
|
475
478
|
return s + "}";
|
|
476
479
|
}
|
|
477
480
|
if (const std::string* s = std::get_if<std::string>(&v)) return "\"" + *s + "\"";
|
|
478
|
-
|
|
481
|
+
if (const ClosurePtr* c = std::get_if<ClosurePtr>(&v)) {
|
|
482
|
+
// The reference prints a function literal as its own source. Falling
|
|
483
|
+
// back only when the closure has no node, which nothing constructs
|
|
484
|
+
// today but the field is a pointer.
|
|
485
|
+
if (*c && (*c)->node) return formatFunctionLiteral(*(*c)->node);
|
|
486
|
+
}
|
|
487
|
+
return "<function-literal>"; // OscRange handled above
|
|
479
488
|
}
|
|
480
489
|
|
|
481
490
|
// (Range::numValues()'s own count, closed-form) -- 1 + floor((end-start)/step),
|
|
@@ -484,10 +493,27 @@ std::string fmtValue(const Value& v) {
|
|
|
484
493
|
// nullopt for a step of 0 or a direction that disagrees with start/end (a
|
|
485
494
|
// naturally-empty range -- 0 elements, never "too many").
|
|
486
495
|
std::optional<size_t> rangeElementCount(const OscRange& r) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
496
|
+
// Zero step, wrong direction, or a NaN anywhere: nothing to iterate, and
|
|
497
|
+
// the reference says nothing about it -- `[for (i = [nan:1:-1]) i]` is
|
|
498
|
+
// [] with no warning there. BOSL2 reaches exactly that through
|
|
499
|
+
// list_rotate() on an empty list, where ((n % 0) + 0) % 0 is NaN.
|
|
500
|
+
if (r.isEmpty()) return std::nullopt;
|
|
501
|
+
|
|
502
|
+
const double count = std::floor((r.end - r.start) / r.step + 1e-10) + 1.0;
|
|
503
|
+
|
|
504
|
+
// Casting a double past size_t's range is undefined, and the platforms
|
|
505
|
+
// disagree about it in opposite directions: x86_64 wraps to 2^63 -- the
|
|
506
|
+
// "too many elements (9223372036854775809)" a CI run reported -- while
|
|
507
|
+
// arm64 produced something small enough that an INFINITE range iterated
|
|
508
|
+
// forever instead of being rejected. Neither is a wrong number so much
|
|
509
|
+
// as an unpredictable one.
|
|
510
|
+
//
|
|
511
|
+
// Clamp to what the reference reports for such a range, its own uint32
|
|
512
|
+
// ceiling: `[0:1:1/0]` warns "too many elements (4294967295)" there and
|
|
513
|
+
// yields [].
|
|
514
|
+
constexpr double kCap = 4294967295.0;
|
|
515
|
+
if (!(count < kCap)) return static_cast<size_t>(kCap);
|
|
516
|
+
return static_cast<size_t>(count);
|
|
491
517
|
}
|
|
492
518
|
|
|
493
519
|
IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany) {
|
|
@@ -516,8 +542,9 @@ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany) {
|
|
|
516
542
|
return IterableValues{std::move(out)};
|
|
517
543
|
}
|
|
518
544
|
if (const std::string* s = std::get_if<std::string>(&v)) {
|
|
545
|
+
// Characters, not bytes -- `[for (c = "aé—z") c]` yields four.
|
|
519
546
|
std::vector<Value> out;
|
|
520
|
-
for (
|
|
547
|
+
for (std::string& ch : utf8Chars(*s)) out.push_back(Value{std::move(ch)});
|
|
521
548
|
return IterableValues{std::move(out)};
|
|
522
549
|
}
|
|
523
550
|
if (const ListPtr* l = std::get_if<ListPtr>(&v)) {
|
|
@@ -529,9 +556,23 @@ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany) {
|
|
|
529
556
|
void appendEachInto(std::vector<Value>& out, const Value& v) {
|
|
530
557
|
if (const ListPtr* l = std::get_if<ListPtr>(&v); l && *l) {
|
|
531
558
|
for (const Value& x : (*l)->items) out.push_back(x);
|
|
532
|
-
|
|
533
|
-
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
// A string and a range expand too, not just a list: the reference gives
|
|
562
|
+
// ["1", "2"] for `[each "12"]` and [0, 1, 2] for `[each [0:2]]`, where
|
|
563
|
+
// this used to hand back the string and the range whole. Reusing the
|
|
564
|
+
// expansion `for` already does keeps the two agreeing -- `[for (x = c)
|
|
565
|
+
// x]` was right over a string while `[each c]` was not, which is what
|
|
566
|
+
// broke BOSL2's str_strip(): _str_count_leading tests
|
|
567
|
+
// `in_list(s[i], [each c])` and so never matched a character.
|
|
568
|
+
//
|
|
569
|
+
// A number, a boolean and an object stay whole (`[each 5]` is [5]), and
|
|
570
|
+
// undef contributes nothing.
|
|
571
|
+
if (std::holds_alternative<std::string>(v) || std::holds_alternative<OscRange>(v)) {
|
|
572
|
+
for (const Value& x : expandIterable(v, nullptr)) out.push_back(x);
|
|
573
|
+
return;
|
|
534
574
|
}
|
|
575
|
+
if (!std::holds_alternative<std::monostate>(v)) out.push_back(v);
|
|
535
576
|
}
|
|
536
577
|
|
|
537
578
|
} // namespace oscadeval
|
|
@@ -537,11 +537,14 @@ TEST(Echo, FormatsEmptyObjectValue) {
|
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
TEST(Echo, FormatsFunctionLiteralValue) {
|
|
540
|
-
//
|
|
541
|
-
//
|
|
540
|
+
// The reference prints a function literal as its own source, and this
|
|
541
|
+
// used to assert "<function-literal>" while claiming that matched it.
|
|
542
|
+
// It does not: `echo(function(x) x);` run through the real binary
|
|
543
|
+
// prints `ECHO: function(x) x`, byte for byte what is expected here.
|
|
544
|
+
// See format_closure.cpp.
|
|
542
545
|
std::string captured;
|
|
543
546
|
runScript("echo(function(x) x);", [&](const std::string& msg) { captured = msg; });
|
|
544
|
-
EXPECT_EQ(captured, "ECHO:
|
|
547
|
+
EXPECT_EQ(captured, "ECHO: function(x) x");
|
|
545
548
|
}
|
|
546
549
|
|
|
547
550
|
TEST(AssertStatement, PassingAssertionIsANoOp) {
|