openscad-cpp-evaluator 1.1.0__tar.gz → 1.3.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.
Files changed (213) hide show
  1. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/PKG-INFO +1 -1
  2. openscad_cpp_evaluator-1.3.0/include/openscad_cpp_evaluator/format_closure.hpp +20 -0
  3. openscad_cpp_evaluator-1.3.0/include/openscad_cpp_evaluator/osc_range.hpp +44 -0
  4. openscad_cpp_evaluator-1.3.0/include/openscad_cpp_evaluator/utf8.hpp +35 -0
  5. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/CMakeLists.txt +2 -0
  7. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/booleans.cpp +30 -16
  8. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/function_builtins.cpp +38 -10
  9. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/csg_generate.cpp +25 -2
  10. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/expr_eval.cpp +8 -2
  11. openscad_cpp_evaluator-1.3.0/src/format_closure.cpp +217 -0
  12. openscad_cpp_evaluator-1.3.0/src/utf8.cpp +61 -0
  13. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/value.cpp +28 -4
  14. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_booleans.cpp +48 -35
  15. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_control_flow.cpp +6 -3
  16. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_python_bindings.py +151 -0
  17. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/uv.lock +1 -1
  18. openscad_cpp_evaluator-1.1.0/include/openscad_cpp_evaluator/osc_range.hpp +0 -19
  19. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/.github/workflows/ci.yml +0 -0
  20. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/.github/workflows/wheels.yml +0 -0
  21. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/.gitignore +0 -0
  22. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/.gitmodules +0 -0
  23. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/CLAUDE.md +0 -0
  24. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/LICENSE +0 -0
  26. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/README.md +0 -0
  27. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/bindings/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/bindings/ast_to_py.cpp +0 -0
  29. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/bindings/module.cpp +0 -0
  30. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/cmake/embed_font.cmake +0 -0
  31. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/examples/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/examples/eval_perf_benchmark.cpp +0 -0
  33. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/examples/manifold_cache_reuse.cpp +0 -0
  34. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/examples/minimal_debugger.cpp +0 -0
  35. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  36. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/.gitignore +0 -0
  37. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  38. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  39. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/README.md +0 -0
  40. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  41. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  42. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  43. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  44. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  45. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  46. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  47. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  48. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  49. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  50. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  51. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  52. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  53. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  54. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  55. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  56. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  57. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  58. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  59. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  60. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  61. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  62. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  63. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  64. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  65. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  66. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  67. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  68. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  69. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  70. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  71. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  72. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  73. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  74. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  75. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  76. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  77. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  78. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  79. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  80. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  81. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  82. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  83. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  84. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  85. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  86. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  87. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  88. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  89. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  90. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  91. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  92. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  93. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  94. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  95. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  96. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  97. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  98. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  99. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  100. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  101. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  102. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  103. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  104. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  105. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  106. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  107. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  108. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  109. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  110. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  111. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  112. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  113. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  114. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  115. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  116. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  117. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  118. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  119. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  120. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  122. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  123. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  124. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  125. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  126. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  127. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  128. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  129. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  130. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  131. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  132. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  133. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  134. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  135. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/resources/fonts/LICENSE +0 -0
  136. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  137. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/builtins.hpp +0 -0
  138. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/call_args.cpp +0 -0
  139. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/color.cpp +0 -0
  140. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/control.cpp +0 -0
  141. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/dxf_dim.cpp +0 -0
  142. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/extrude.cpp +0 -0
  143. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/import.cpp +0 -0
  144. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/modifiers.cpp +0 -0
  145. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/primitives_2d.cpp +0 -0
  146. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/primitives_3d.cpp +0 -0
  147. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/registry.cpp +0 -0
  148. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/roof.cpp +0 -0
  149. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/segments.cpp +0 -0
  150. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/surface.cpp +0 -0
  151. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/text.cpp +0 -0
  152. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/topology.cpp +0 -0
  153. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/builtins/transforms.cpp +0 -0
  154. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/bytecode_compiler.cpp +0 -0
  155. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/bytecode_vm.cpp +0 -0
  156. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/colored_body.cpp +0 -0
  157. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/csg_resolve.cpp +0 -0
  158. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/css_colors.cpp +0 -0
  159. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/debug/debug_repl.cpp +0 -0
  160. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/debug_profile.cpp +0 -0
  161. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/eval_context.cpp +0 -0
  162. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/eval_error.cpp +0 -0
  163. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/eval_use.cpp +0 -0
  164. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/export.cpp +0 -0
  165. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/import/dxf_import.cpp +0 -0
  166. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/import/mesh_import.cpp +0 -0
  167. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/import/surface_load.cpp +0 -0
  168. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/import/svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/manifold_cache.cpp +0 -0
  170. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/measure_geometry.cpp +0 -0
  171. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/mesh_check.cpp +0 -0
  172. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/stmt_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/text/font_match.cpp +0 -0
  174. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/text/freetype_font_provider.cpp +0 -0
  175. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/text/text_metrics.cpp +0 -0
  176. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/user_calls.cpp +0 -0
  177. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/src/zip_stored.cpp +0 -0
  178. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/CMakeLists.txt +0 -0
  179. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_bytecode_compiler.cpp +0 -0
  180. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_children_separate.cpp +0 -0
  181. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_cli.cpp +0 -0
  182. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_csg_tree.cpp +0 -0
  183. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_debug_hooks.cpp +0 -0
  184. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_dxf_svg_import.cpp +0 -0
  185. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_eval_context.cpp +0 -0
  186. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_export.cpp +0 -0
  187. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_expr_eval.cpp +0 -0
  188. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_extrude_roof.cpp +0 -0
  189. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_feature_detection.cpp +0 -0
  190. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_function_builtins.cpp +0 -0
  191. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_helpers.hpp +0 -0
  192. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_import_export.cpp +0 -0
  193. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_manifold_cache.cpp +0 -0
  194. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_mesh_check.cpp +0 -0
  195. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_multi_color_merge.cpp +0 -0
  196. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_primitives.cpp +0 -0
  197. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_profiling.cpp +0 -0
  198. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_render_expr.cpp +0 -0
  199. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_scope_trail.cpp +0 -0
  200. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_scoping.cpp +0 -0
  201. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_surface.cpp +0 -0
  202. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_tail_calls.cpp +0 -0
  203. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_text.cpp +0 -0
  204. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_transforms.cpp +0 -0
  205. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_unexpected_args.cpp +0 -0
  206. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_use.cpp +0 -0
  207. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_value.cpp +0 -0
  208. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_viewport_params.cpp +0 -0
  209. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tests/test_zip_stored.cpp +0 -0
  210. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tools/cli/CMakeLists.txt +0 -0
  211. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tools/cli/cli_lib.cpp +0 -0
  212. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tools/cli/cli_lib.hpp +0 -0
  213. {openscad_cpp_evaluator-1.1.0 → openscad_cpp_evaluator-1.3.0}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -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
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.1.0"
7
+ version = "1.3.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -1,5 +1,7 @@
1
1
  add_library(openscad_cpp_evaluator STATIC
2
2
  value.cpp
3
+ utf8.cpp
4
+ format_closure.cpp
3
5
  eval_error.cpp
4
6
  eval_context.cpp
5
7
  expr_eval.cpp
@@ -214,7 +214,15 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
214
214
  : &std::get<ListPtr>(emptyIsAGroupIt->second)->items;
215
215
 
216
216
  std::vector<ColoredBody> allBg, allHi, allSo, allDo;
217
- std::optional<ColoredBody> csgResult;
217
+ // Two accumulators, one per dimension. union() may legitimately carry
218
+ // both -- `translate(...) { part(); text("label"); }` is how a docs
219
+ // figure annotates a 3D part, and the reference draws both -- and a
220
+ // single optional that switched between body and section is exactly
221
+ // what used to be dereferenced empty when a group changed dimension
222
+ // part-way. difference and intersection still see one dimension only:
223
+ // applyDimensionRules drops the mismatch before it gets here, so for
224
+ // them just one of these is ever populated.
225
+ std::optional<ColoredBody> res3d, res2d;
218
226
  size_t idx = 0;
219
227
 
220
228
  size_t stmtIndex = 0;
@@ -273,50 +281,56 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
273
281
  // discards while no positive operand has been established yet;
274
282
  // union just skips the empty contributor and keeps going.
275
283
  if (op == "intersection") {
276
- csgResult.reset();
284
+ res3d.reset();
285
+ res2d.reset();
277
286
  break;
278
287
  }
279
- if (op == "difference" && !csgResult) break;
288
+ if (op == "difference" && !res3d && !res2d) break;
280
289
  continue;
281
290
  }
282
291
 
292
+ // Each dimension accumulates on its own, so a statement holding
293
+ // both contributes to both rather than the 2D half falling off the
294
+ // end of an if/else.
283
295
  if (!bodies3d.empty()) {
284
296
  manifold::Manifold grp = *bodies3d.front().body;
285
297
  for (size_t i = 1; i < bodies3d.size(); ++i) grp = grp + *bodies3d[i].body;
286
- if (!csgResult) {
298
+ if (!res3d) {
287
299
  ColoredBody cb;
288
300
  cb.body = std::move(grp);
289
301
  cb.color = bodies3d.front().color;
290
- csgResult = std::move(cb);
302
+ res3d = std::move(cb);
291
303
  } else if (op == "union") {
292
- csgResult->body = *csgResult->body + grp;
304
+ res3d->body = *res3d->body + grp;
293
305
  } else if (op == "difference") {
294
- csgResult->body = *csgResult->body - grp;
306
+ res3d->body = *res3d->body - grp;
295
307
  } else if (op == "intersection") {
296
- csgResult->body = *csgResult->body ^ grp;
308
+ res3d->body = *res3d->body ^ grp;
297
309
  }
298
- } else {
310
+ }
311
+ if (!sections2d.empty()) {
299
312
  manifold::CrossSection grp = *sections2d.front().section;
300
313
  for (size_t i = 1; i < sections2d.size(); ++i) grp = grp + *sections2d[i].section;
301
- if (!csgResult) {
314
+ if (!res2d) {
302
315
  ColoredBody cb;
303
316
  cb.section = std::move(grp);
304
317
  cb.color = sections2d.front().color;
305
- csgResult = std::move(cb);
318
+ res2d = std::move(cb);
306
319
  } else if (op == "union") {
307
- csgResult->section = *csgResult->section + grp;
320
+ res2d->section = *res2d->section + grp;
308
321
  } else if (op == "difference") {
309
- csgResult->section = *csgResult->section - grp;
322
+ res2d->section = *res2d->section - grp;
310
323
  } else if (op == "intersection") {
311
- csgResult->section = *csgResult->section ^ grp;
324
+ res2d->section = *res2d->section ^ grp;
312
325
  }
313
326
  }
314
327
  }
315
328
 
316
- if (csgResult && csgResult->body) attachTriColors(ev, *csgResult);
329
+ if (res3d && res3d->body) attachTriColors(ev, *res3d);
317
330
 
318
331
  std::vector<ColoredBody> result;
319
- if (csgResult) result.push_back(std::move(*csgResult));
332
+ if (res3d) result.push_back(std::move(*res3d));
333
+ if (res2d) result.push_back(std::move(*res2d));
320
334
  result.insert(result.end(), allBg.begin(), allBg.end());
321
335
  result.insert(result.end(), allHi.begin(), allHi.end());
322
336
  result.insert(result.end(), allSo.begin(), allSo.end());
@@ -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
- const Value matchArg = getArg(args, 0, "match", Value{});
617
- const Value tableArg = getArg(args, 1, "vector", Value{});
618
- const double nrRaw = toDoubleLenient(getArg(args, 2, "num_returns", Value{1.0}));
619
- const double icRaw = toDoubleLenient(getArg(args, 3, "index_col", Value{0.0}));
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
- for (size_t i = 0; i < needle->size(); ++i) {
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 < hay->size(); ++j) {
661
- if ((*needle)[i] != (*hay)[j]) continue;
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
- for (size_t i = 0; i < needle->size(); ++i) {
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
- if (!entry || entry->empty() || (*entry)[0] != (*needle)[i]) continue;
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
- if (const std::string* s = std::get_if<std::string>(&x)) return Value{static_cast<double>(s->size())};
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
  }
@@ -129,6 +129,20 @@ enum class DimRule {
129
129
  None, // no dimension diagnostics at all
130
130
  };
131
131
 
132
+ // Nodes that hand their children on as a list instead of combining them
133
+ // into one result, so a mixed 2D/3D list reaches nothing that has to pick a
134
+ // single dimension. generateTransform and generateColor map over the
135
+ // bodies; generateCsg's union keeps a 3D and a 2D accumulator side by side.
136
+ //
137
+ // difference and intersection are deliberately absent: subtracting or
138
+ // intersecting across dimensions has no meaning, and they still collapse to
139
+ // one result. hull and minkowski likewise build a single hull.
140
+ bool keepsMixedDimensions(const std::string& kind) {
141
+ return kind == "union" || kind == "color" || kind == "translate" ||
142
+ kind == "rotate" || kind == "scale" || kind == "mirror" ||
143
+ kind == "multmatrix" || kind == "resize";
144
+ }
145
+
132
146
  DimRule dimRuleFor(const std::string& kind) {
133
147
  if (kind == "linear_extrude" || kind == "rotate_extrude" || kind == "offset") return DimRule::Only2D;
134
148
  if (kind == "projection") return DimRule::Only3D;
@@ -168,7 +182,11 @@ void Evaluator::applyDimensionRules(CSGNode& node) {
168
182
  std::vector<CSGNode*> kids;
169
183
  kids.reserve(node.children.size());
170
184
  for (const std::unique_ptr<CSGNode>& c : node.children) kids.push_back(c.get());
171
- applyDimensionRulesTo(kids, static_cast<int>(rule));
185
+ // Only a Group-rule node can keep a mix. linear_extrude and friends take
186
+ // one dimension by definition, so a wrong-dimension child there is still
187
+ // dropped with its warning.
188
+ const bool keepMixed = rule == DimRule::Group && keepsMixedDimensions(node.kind);
189
+ applyDimensionRulesTo(kids, static_cast<int>(rule), keepMixed);
172
190
  }
173
191
 
174
192
  // `keepMixed` warns about a mixed group but drops nothing -- the top level's
@@ -199,7 +217,12 @@ void Evaluator::applyDimensionRulesTo(const std::vector<CSGNode*>& children, int
199
217
  continue;
200
218
  }
201
219
  const oscad::Position* pos = child->node ? &child->node->position() : nullptr;
202
- if (rule == DimRule::Group && !warnedMixing) {
220
+ // Silent when both are kept: the reference says nothing for
221
+ // `cube(1); square(4);`, `translate(...) { ... }` or `union()
222
+ // { ... }` -- checked by running it -- and a warning here would
223
+ // fail any docs example that legitimately draws a 2D annotation
224
+ // beside a 3D part.
225
+ if (rule == DimRule::Group && !keepMixed && !warnedMixing) {
203
226
  warn("Mixing 2D and 3D objects is not supported", pos);
204
227
  warnedMixing = true;
205
228
  }
@@ -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 || static_cast<size_t>(i) >= s->size()) return Value{};
807
- return Value{std::string(1, (*s)[static_cast<size_t>(i)])};
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