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.
Files changed (213) hide show
  1. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/PKG-INFO +1 -1
  2. openscad_cpp_evaluator-1.3.1/include/openscad_cpp_evaluator/format_closure.hpp +20 -0
  3. openscad_cpp_evaluator-1.3.1/include/openscad_cpp_evaluator/osc_range.hpp +44 -0
  4. openscad_cpp_evaluator-1.3.1/include/openscad_cpp_evaluator/utf8.hpp +35 -0
  5. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/CMakeLists.txt +2 -0
  7. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/function_builtins.cpp +38 -10
  8. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/expr_eval.cpp +8 -2
  9. openscad_cpp_evaluator-1.3.1/src/format_closure.cpp +217 -0
  10. openscad_cpp_evaluator-1.3.1/src/utf8.cpp +61 -0
  11. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/value.cpp +49 -8
  12. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_control_flow.cpp +6 -3
  13. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_python_bindings.py +184 -0
  14. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/uv.lock +1 -1
  15. openscad_cpp_evaluator-1.2.0/include/openscad_cpp_evaluator/osc_range.hpp +0 -19
  16. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/CLAUDE.md +0 -0
  21. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/README.md +0 -0
  24. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/bindings/module.cpp +0 -0
  27. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/cmake/embed_font.cmake +0 -0
  28. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/eval_perf_benchmark.cpp +0 -0
  30. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/manifold_cache_reuse.cpp +0 -0
  31. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/examples/minimal_debugger.cpp +0 -0
  32. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  33. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/.gitignore +0 -0
  34. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  35. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  36. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/README.md +0 -0
  37. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  38. {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
  39. {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
  40. {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
  41. {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
  42. {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
  43. {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
  44. {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
  45. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  46. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  48. {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
  49. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  50. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  51. {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
  52. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  53. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  54. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  55. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  56. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  57. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  58. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  59. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  60. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  61. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  62. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  63. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  64. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  65. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  66. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  67. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  68. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  69. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  70. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  71. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  72. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  73. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  74. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  75. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  76. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  77. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  78. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  79. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  80. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  81. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  82. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  83. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  84. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  85. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  86. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  87. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  91. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  92. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  93. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  94. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  95. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  96. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  97. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  98. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  99. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  100. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  101. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  102. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  103. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  104. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  105. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  106. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  107. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  108. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  109. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  115. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  116. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  117. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  120. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  121. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  122. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  123. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  124. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  125. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  126. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  127. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  129. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  134. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/booleans.cpp +0 -0
  135. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/builtins.hpp +0 -0
  136. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/call_args.cpp +0 -0
  137. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/color.cpp +0 -0
  138. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/control.cpp +0 -0
  139. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/dxf_dim.cpp +0 -0
  140. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/extrude.cpp +0 -0
  141. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/import.cpp +0 -0
  142. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/modifiers.cpp +0 -0
  143. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/primitives_2d.cpp +0 -0
  144. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/primitives_3d.cpp +0 -0
  145. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/registry.cpp +0 -0
  146. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/roof.cpp +0 -0
  147. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/segments.cpp +0 -0
  148. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/surface.cpp +0 -0
  149. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/text.cpp +0 -0
  150. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/topology.cpp +0 -0
  151. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/builtins/transforms.cpp +0 -0
  152. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/bytecode_compiler.cpp +0 -0
  153. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/bytecode_vm.cpp +0 -0
  154. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/colored_body.cpp +0 -0
  155. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/csg_generate.cpp +0 -0
  156. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/csg_resolve.cpp +0 -0
  157. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/css_colors.cpp +0 -0
  158. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/debug/debug_repl.cpp +0 -0
  159. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/debug_profile.cpp +0 -0
  160. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/eval_context.cpp +0 -0
  161. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/eval_error.cpp +0 -0
  162. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/eval_use.cpp +0 -0
  163. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/export.cpp +0 -0
  164. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/dxf_import.cpp +0 -0
  165. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/mesh_import.cpp +0 -0
  166. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/surface_load.cpp +0 -0
  167. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/import/svg_import.cpp +0 -0
  168. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/manifold_cache.cpp +0 -0
  169. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/measure_geometry.cpp +0 -0
  170. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/mesh_check.cpp +0 -0
  171. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/stmt_eval.cpp +0 -0
  172. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/text/font_match.cpp +0 -0
  173. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/text/freetype_font_provider.cpp +0 -0
  174. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/text/text_metrics.cpp +0 -0
  175. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/user_calls.cpp +0 -0
  176. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/src/zip_stored.cpp +0 -0
  177. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/CMakeLists.txt +0 -0
  178. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_booleans.cpp +0 -0
  179. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_bytecode_compiler.cpp +0 -0
  180. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_children_separate.cpp +0 -0
  181. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_cli.cpp +0 -0
  182. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_csg_tree.cpp +0 -0
  183. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_debug_hooks.cpp +0 -0
  184. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_dxf_svg_import.cpp +0 -0
  185. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_eval_context.cpp +0 -0
  186. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_export.cpp +0 -0
  187. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_expr_eval.cpp +0 -0
  188. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_extrude_roof.cpp +0 -0
  189. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_feature_detection.cpp +0 -0
  190. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_function_builtins.cpp +0 -0
  191. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_helpers.hpp +0 -0
  192. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_import_export.cpp +0 -0
  193. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_manifold_cache.cpp +0 -0
  194. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_mesh_check.cpp +0 -0
  195. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_multi_color_merge.cpp +0 -0
  196. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_primitives.cpp +0 -0
  197. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_profiling.cpp +0 -0
  198. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_render_expr.cpp +0 -0
  199. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_scope_trail.cpp +0 -0
  200. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_scoping.cpp +0 -0
  201. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_surface.cpp +0 -0
  202. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_tail_calls.cpp +0 -0
  203. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_text.cpp +0 -0
  204. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_transforms.cpp +0 -0
  205. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_unexpected_args.cpp +0 -0
  206. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_use.cpp +0 -0
  207. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_value.cpp +0 -0
  208. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_viewport_params.cpp +0 -0
  209. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tests/test_zip_stored.cpp +0 -0
  210. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/CMakeLists.txt +0 -0
  211. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/cli_lib.cpp +0 -0
  212. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/tools/cli/cli_lib.hpp +0 -0
  213. {openscad_cpp_evaluator-1.2.0 → openscad_cpp_evaluator-1.3.1}/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.2.0
3
+ Version: 1.3.1
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.2.0"
7
+ version = "1.3.1"
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
@@ -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
  }
@@ -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
@@ -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
- return "<function-literal>"; // OscRange handled above; ClosurePtr has no meaningful textual form either
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
- if (r.step == 0.0) return std::nullopt;
488
- const double n = (r.end - r.start) / r.step;
489
- if (n < -1e-10) return std::nullopt; // wrong direction -- naturally empty
490
- return static_cast<size_t>(std::floor(n + 1e-10)) + 1;
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 (char c : *s) out.push_back(Value{std::string(1, c)});
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
- } else if (!std::holds_alternative<std::monostate>(v)) {
533
- out.push_back(v);
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
- // fmtValue's own final fallback -- a FunctionLiteral* value has no
541
- // meaningful textual form (matches the reference exactly).
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: <function-literal>");
547
+ EXPECT_EQ(captured, "ECHO: function(x) x");
545
548
  }
546
549
 
547
550
  TEST(AssertStatement, PassingAssertionIsANoOp) {