openscad-cpp-evaluator 0.32.2__tar.gz → 0.33.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 (202) hide show
  1. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/value.hpp +14 -1
  3. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/pyproject.toml +1 -1
  4. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/function_builtins.cpp +359 -112
  5. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/expr_eval.cpp +49 -34
  6. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/value.cpp +169 -65
  7. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_bytecode_compiler.cpp +5 -4
  8. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_control_flow.cpp +2 -2
  9. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_expr_eval.cpp +107 -6
  10. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_function_builtins.cpp +151 -5
  11. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_value.cpp +3 -3
  12. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/uv.lock +1 -1
  13. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/.github/workflows/ci.yml +0 -0
  14. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/.github/workflows/wheels.yml +0 -0
  15. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/.gitignore +0 -0
  16. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/.gitmodules +0 -0
  17. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/CLAUDE.md +0 -0
  18. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/LICENSE +0 -0
  20. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/README.md +0 -0
  21. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/bindings/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/bindings/ast_to_py.cpp +0 -0
  23. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/bindings/module.cpp +0 -0
  24. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/cmake/embed_font.cmake +0 -0
  25. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/examples/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/examples/eval_perf_benchmark.cpp +0 -0
  27. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/examples/manifold_cache_reuse.cpp +0 -0
  28. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/examples/minimal_debugger.cpp +0 -0
  29. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  30. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/.gitignore +0 -0
  31. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  32. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/README.md +0 -0
  34. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  35. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  36. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  38. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  39. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  40. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  41. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  42. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  43. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  45. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  46. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  47. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  48. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  49. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  50. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  51. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  52. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  53. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  54. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  55. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  56. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  57. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  58. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  59. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  60. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  61. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  62. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  63. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  64. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  65. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  66. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  67. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  68. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  69. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  70. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  71. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  72. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  73. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  74. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  75. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  76. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  77. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  78. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  79. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  80. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  81. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  82. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  83. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  84. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  85. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  86. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  87. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  88. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  89. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  90. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  92. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  93. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  94. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  95. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  96. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  97. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  98. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  99. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  100. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  101. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  102. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  103. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  104. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  105. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  106. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  107. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  108. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  109. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  110. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  111. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  112. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  113. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  116. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  117. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  118. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  119. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  120. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  121. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  122. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  123. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  124. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  125. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  126. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  127. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  128. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/resources/fonts/LICENSE +0 -0
  129. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  130. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/CMakeLists.txt +0 -0
  131. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/booleans.cpp +0 -0
  132. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/builtins.hpp +0 -0
  133. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/call_args.cpp +0 -0
  134. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/color.cpp +0 -0
  135. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/control.cpp +0 -0
  136. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/extrude.cpp +0 -0
  137. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/import.cpp +0 -0
  138. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/modifiers.cpp +0 -0
  139. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/primitives_2d.cpp +0 -0
  140. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/primitives_3d.cpp +0 -0
  141. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/registry.cpp +0 -0
  142. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/roof.cpp +0 -0
  143. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/segments.cpp +0 -0
  144. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/surface.cpp +0 -0
  145. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/text.cpp +0 -0
  146. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/topology.cpp +0 -0
  147. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/builtins/transforms.cpp +0 -0
  148. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/bytecode_compiler.cpp +0 -0
  149. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/bytecode_vm.cpp +0 -0
  150. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/colored_body.cpp +0 -0
  151. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/csg_generate.cpp +0 -0
  152. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/csg_resolve.cpp +0 -0
  153. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/css_colors.cpp +0 -0
  154. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/debug/debug_repl.cpp +0 -0
  155. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/debug_profile.cpp +0 -0
  156. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/eval_context.cpp +0 -0
  157. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/eval_error.cpp +0 -0
  158. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/eval_use.cpp +0 -0
  159. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/export.cpp +0 -0
  160. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/import/dxf_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/import/mesh_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/import/surface_load.cpp +0 -0
  163. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/import/svg_import.cpp +0 -0
  164. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/manifold_cache.cpp +0 -0
  165. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/mesh_check.cpp +0 -0
  166. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/stmt_eval.cpp +0 -0
  167. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/text/font_match.cpp +0 -0
  168. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/text/freetype_font_provider.cpp +0 -0
  169. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/text/text_metrics.cpp +0 -0
  170. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/user_calls.cpp +0 -0
  171. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/CMakeLists.txt +0 -0
  173. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_booleans.cpp +0 -0
  174. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_cli.cpp +0 -0
  175. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_csg_tree.cpp +0 -0
  176. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_debug_hooks.cpp +0 -0
  177. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_dxf_svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_eval_context.cpp +0 -0
  179. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_extrude_roof.cpp +0 -0
  181. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_helpers.hpp +0 -0
  182. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_import_export.cpp +0 -0
  183. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_manifold_cache.cpp +0 -0
  184. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_mesh_check.cpp +0 -0
  185. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_multi_color_merge.cpp +0 -0
  186. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_primitives.cpp +0 -0
  187. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_profiling.cpp +0 -0
  188. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_python_bindings.py +0 -0
  189. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_scope_trail.cpp +0 -0
  190. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_scoping.cpp +0 -0
  191. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_surface.cpp +0 -0
  192. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_tail_calls.cpp +0 -0
  193. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_text.cpp +0 -0
  194. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_transforms.cpp +0 -0
  195. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_unexpected_args.cpp +0 -0
  196. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_use.cpp +0 -0
  197. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_viewport_params.cpp +0 -0
  198. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tests/test_zip_stored.cpp +0 -0
  199. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tools/cli/CMakeLists.txt +0 -0
  200. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tools/cli/cli_lib.cpp +0 -0
  201. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.0}/tools/cli/cli_lib.hpp +0 -0
  202. {openscad_cpp_evaluator-0.32.2 → openscad_cpp_evaluator-0.33.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: 0.32.2
3
+ Version: 0.33.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -147,6 +147,12 @@ bool oscComparable(const Value& a, const Value& b);
147
147
  Value scale(double scalarValue, const Value& value);
148
148
  Value divScale(const Value& value, double divisor);
149
149
 
150
+ // `number / vector`, the mirror of divScale's `vector / number` -- the
151
+ // reference divides the scalar by each element in turn (Value.cc's
152
+ // `operator/` NUMBER/VECTOR branch, `*this / vecval`), recursing into
153
+ // nested lists the same way. `5 / [1, 2]` is `[5, 2.5]`, not undef.
154
+ Value divInto(double numerator, const Value& value);
155
+
150
156
  // Elementwise `+`/`-` between two lists (zip semantics: truncates to the
151
157
  // shorter length when lengths differ), or the scalar/mismatched-type
152
158
  // fallback (numeric + numeric, everything else -> undef). `vecAdd` rejects
@@ -167,7 +173,14 @@ Value vecSub(const Value& a, const Value& b);
167
173
  // a Python bool silently). This divergence is undocumented/untested
168
174
  // behavior in the reference implementation -- revisit only if a real
169
175
  // script's `*` on a bool-containing vector is shown to need it.
170
- Value matmul(const Value& a, const Value& b);
176
+ //
177
+ // `error`, when non-null, receives the reference's own diagnostic text for
178
+ // whichever undefined case was hit (multiply_visitor / multvecvec /
179
+ // multmatvec / multvecmat in the reference's Value.cc) -- "vector*vector
180
+ // requires matching lengths (2 != 3)", "Matrix must be rectangular. Problem
181
+ // at row 1", and so on. applyBinaryOp warns with it; a caller that only
182
+ // wants the value can leave it null. It is only written on an undef result.
183
+ Value matmul(const Value& a, const Value& b, std::string* error = nullptr);
171
184
 
172
185
  // OpenSCAD's echo()/str() number formatting: at most 6 significant digits,
173
186
  // fixed-point for exponents in [-5, 5], scientific notation with no leading
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.32.2"
7
+ version = "0.33.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -9,6 +9,7 @@
9
9
 
10
10
  #include <algorithm>
11
11
  #include <cmath>
12
+ #include <functional>
12
13
  #include <limits>
13
14
  #include <numbers>
14
15
  #include <random>
@@ -186,97 +187,130 @@ Value builtinRands(double minv, double maxv, double nArg, const Value& seedArg)
186
187
  return numList(out);
187
188
  }
188
189
 
189
- Value builtinSearch(const CallArgs& args) {
190
+ // Ported branch-for-branch from the reference's builtin_search plus its
191
+ // three static search() overloads (builtin_functions.cc). The dispatch is on
192
+ // what is being searched FOR -- number, string, or vector; every other type
193
+ // (undef, bool, range, function, object) falls off the end into a bare
194
+ // undef. What is being searched IN is never type-checked at all: the
195
+ // reference just calls .toVector() on it, which yields an EMPTY vector for
196
+ // any non-vector, so searching in undef/a number/a bool finds nothing rather
197
+ // than failing. Only the string-in-string form treats a string haystack as
198
+ // a sequence of characters -- a `search(["a"], "abc")` vector needle sees
199
+ // that same string as an empty table, not as characters.
200
+ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position* pos) {
190
201
  const Value matchArg = getArg(args, 0, "match", Value{});
191
- const Value vectorArg = getArg(args, 1, "vector", Value{});
192
- // A string "vector" haystack is searched character-by-character, same
193
- // as the reference (Python strings are natively iterable/indexable by
194
- // character, so `for i, item in enumerate(vector)` just works there
195
- // with no special case at all -- C++ needs this explicit adaptation
196
- // since std::string isn't a Value list). Materialized into a real
197
- // vector of length-1 string Values rather than special-casing every
198
- // access below, so findAll/the match-dispatch code stays oblivious to
199
- // which kind of haystack it's searching.
200
- std::vector<Value> stringHaystack;
201
- const std::vector<Value>* vecItems = nullptr;
202
- if (const ListPtr* vecPtr = std::get_if<ListPtr>(&vectorArg); vecPtr && *vecPtr) {
203
- vecItems = &(*vecPtr)->items;
204
- } else if (const std::string* vecStr = std::get_if<std::string>(&vectorArg)) {
205
- stringHaystack.reserve(vecStr->size());
206
- for (char c : *vecStr) stringHaystack.push_back(Value{std::string(1, c)});
207
- vecItems = &stringHaystack;
208
- } else {
209
- return Value{};
210
- }
211
- const auto& vec = *vecItems;
212
- const int numReturns = static_cast<int>(toDoubleLenient(getArg(args, 2, "num_returns", Value{1.0})));
213
- const int col = static_cast<int>(toDoubleLenient(getArg(args, 3, "index_col", Value{0.0})));
214
-
215
- const auto findAll = [&](const Value& val) {
216
- const bool valIsList = std::holds_alternative<ListPtr>(val);
217
- std::vector<int> results;
218
- for (size_t i = 0; i < vec.size(); ++i) {
219
- Value target;
220
- if (valIsList) {
221
- target = vec[i];
222
- } else if (const ListPtr* itemList = std::get_if<ListPtr>(&vec[i]); itemList && *itemList) {
223
- // ponytail: an out-of-range index_col silently matches
224
- // nothing for that row rather than aborting the whole
225
- // search() call (the reference raises and the outer
226
- // try/except turns the whole call into undef) -- a narrow
227
- // divergence on a malformed-argument edge case.
228
- target = (col >= 0 && static_cast<size_t>(col) < (*itemList)->items.size()) ? (*itemList)->items[static_cast<size_t>(col)]
229
- : Value{};
230
- } else {
231
- target = vec[i];
232
- }
233
- if (oscEqual(target, val)) results.push_back(static_cast<int>(i));
234
- }
235
- return results;
236
- };
202
+ const Value tableArg = getArg(args, 1, "vector", Value{});
203
+ const double nrRaw = toDoubleLenient(getArg(args, 2, "num_returns", Value{1.0}));
204
+ const double icRaw = toDoubleLenient(getArg(args, 3, "index_col", Value{0.0}));
205
+ // Both are `unsigned int` in the reference, so a negative argument wraps
206
+ // to a huge positive rather than meaning "unlimited"/"column 0".
207
+ const unsigned numReturns = static_cast<unsigned>(static_cast<long long>(nrRaw));
208
+ const unsigned indexCol = static_cast<unsigned>(static_cast<long long>(icRaw));
209
+
210
+ static const std::vector<Value> kNoItems;
211
+ const ListPtr* tablePtr = std::get_if<ListPtr>(&tableArg);
212
+ const std::vector<Value>& table = (tablePtr && *tablePtr) ? (*tablePtr)->items : kNoItems;
237
213
 
238
- const auto capped = [](std::vector<int> matches, int limit) {
239
- if (limit >= 0 && static_cast<size_t>(limit) < matches.size()) matches.resize(static_cast<size_t>(limit));
240
- return matches;
214
+ const auto itemsOf = [](const Value& v) -> const std::vector<Value>& {
215
+ const ListPtr* l = std::get_if<ListPtr>(&v);
216
+ return (l && *l) ? (*l)->items : kNoItems;
241
217
  };
242
- const auto toIdxList = [](const std::vector<int>& idxs) {
243
- std::vector<Value> items;
244
- items.reserve(idxs.size());
245
- for (int i : idxs) items.push_back(Value{static_cast<double>(i)});
246
- return listOf(std::move(items));
218
+ // The reference's two-clause hit test, verbatim: the whole entry counts
219
+ // as a match only at index_col 0, and the indexed sub-element counts
220
+ // only when the entry really is a long enough vector.
221
+ const auto hits = [&](const Value& needle, const Value& entry) {
222
+ if (indexCol == 0 && oscEqual(needle, entry)) return true;
223
+ const std::vector<Value>& ev2 = itemsOf(entry);
224
+ return indexCol < ev2.size() && oscEqual(needle, ev2[indexCol]);
247
225
  };
226
+ const auto num = [](size_t j) { return Value{static_cast<double>(j)}; };
248
227
 
249
- // Per-element result for the string-char and list-element match forms:
250
- // num_returns==1 returns a *bare number* on a match, or [] if none --
251
- // a genuinely mixed-type result, matching the reference's
252
- // _result_for exactly (not the always-a-list shape the scalar top-
253
- // level match form uses below).
254
- const auto resultForElement = [&](const Value& val) -> Value {
255
- const std::vector<int> matches = findAll(val);
256
- if (numReturns == 1) return matches.empty() ? toIdxList({}) : Value{static_cast<double>(matches[0])};
257
- if (numReturns == 0) return toIdxList(matches);
258
- return toIdxList(capped(matches, numReturns));
259
- };
228
+ if (std::holds_alternative<double>(matchArg)) {
229
+ std::vector<Value> out;
230
+ unsigned matchCount = 0;
231
+ for (size_t j = 0; j < table.size(); ++j) {
232
+ if (!hits(matchArg, table[j])) continue;
233
+ out.push_back(num(j));
234
+ if (numReturns != 0 && ++matchCount >= numReturns) break;
235
+ }
236
+ return listOf(std::move(out));
237
+ }
260
238
 
261
- if (const std::string* str = std::get_if<std::string>(&matchArg)) {
262
- std::vector<Value> results;
263
- for (char c : *str) {
264
- const Value r = resultForElement(Value{std::string(1, c)});
265
- const bool rIsEmptyList = std::holds_alternative<ListPtr>(r) && std::get<ListPtr>(r) && std::get<ListPtr>(r)->items.empty();
266
- if (numReturns != 1 || !rIsEmptyList) results.push_back(r);
239
+ if (const std::string* needle = std::get_if<std::string>(&matchArg)) {
240
+ std::vector<Value> out;
241
+ if (const std::string* hay = std::get_if<std::string>(&tableArg)) {
242
+ for (size_t i = 0; i < needle->size(); ++i) {
243
+ unsigned matchCount = 0;
244
+ std::vector<Value> resultvec;
245
+ for (size_t j = 0; j < hay->size(); ++j) {
246
+ if ((*needle)[i] != (*hay)[j]) continue;
247
+ ++matchCount;
248
+ if (numReturns == 1) {
249
+ out.push_back(num(j));
250
+ break;
251
+ }
252
+ resultvec.push_back(num(j));
253
+ if (numReturns > 1 && matchCount >= numReturns) break;
254
+ }
255
+ if (numReturns == 0 || numReturns > 1) out.push_back(listOf(std::move(resultvec)));
256
+ }
257
+ return listOf(std::move(out));
267
258
  }
268
- return listOf(std::move(results));
259
+ // String needle, vector table: every entry must itself be a vector
260
+ // with more than index_col elements. A single bad entry aborts the
261
+ // WHOLE call with an empty result, not just that row -- which is
262
+ // why `search("a", ["a","b"])` is [] and not [0].
263
+ for (size_t i = 0; i < needle->size(); ++i) {
264
+ unsigned matchCount = 0;
265
+ std::vector<Value> resultvec;
266
+ for (size_t j = 0; j < table.size(); ++j) {
267
+ const std::vector<Value>& entryVec = itemsOf(table[j]);
268
+ if (entryVec.size() <= indexCol) {
269
+ ev.warn("Invalid entry in search vector at index " + std::to_string(j) +
270
+ ", required number of values in the entry: " + std::to_string(indexCol + 1) +
271
+ ". Invalid entry: " + fmtValue(table[j]),
272
+ pos);
273
+ return listOf({});
274
+ }
275
+ const std::string* entry = std::get_if<std::string>(&entryVec[indexCol]);
276
+ // A type mismatch just doesn't match, exactly as `==` would.
277
+ if (!entry || entry->empty() || (*entry)[0] != (*needle)[i]) continue;
278
+ ++matchCount;
279
+ if (numReturns == 1) {
280
+ out.push_back(num(j));
281
+ break;
282
+ }
283
+ resultvec.push_back(num(j));
284
+ if (numReturns > 1 && matchCount >= numReturns) break;
285
+ }
286
+ if (numReturns == 0 || numReturns > 1) out.push_back(listOf(std::move(resultvec)));
287
+ }
288
+ return listOf(std::move(out));
269
289
  }
270
- if (const ListPtr* matchList = std::get_if<ListPtr>(&matchArg); matchList && *matchList) {
271
- std::vector<Value> results;
272
- results.reserve((*matchList)->items.size());
273
- for (const Value& m : (*matchList)->items) results.push_back(resultForElement(m));
274
- return listOf(std::move(results));
290
+
291
+ if (std::holds_alternative<ListPtr>(matchArg)) {
292
+ std::vector<Value> out;
293
+ for (const Value& needle : itemsOf(matchArg)) {
294
+ unsigned matchCount = 0;
295
+ std::vector<Value> resultvec;
296
+ for (size_t j = 0; j < table.size(); ++j) {
297
+ if (!hits(needle, table[j])) continue;
298
+ ++matchCount;
299
+ if (numReturns == 1) {
300
+ out.push_back(num(j));
301
+ break;
302
+ }
303
+ resultvec.push_back(num(j));
304
+ if (numReturns > 1 && matchCount >= numReturns) break;
305
+ }
306
+ if ((numReturns == 1 && matchCount == 0) || numReturns == 0 || numReturns > 1) {
307
+ out.push_back(listOf(std::move(resultvec)));
308
+ }
309
+ }
310
+ return listOf(std::move(out));
275
311
  }
276
- const std::vector<int> matches = findAll(matchArg);
277
- if (numReturns == 1) return toIdxList(capped(matches, 1));
278
- if (numReturns == 0) return toIdxList(matches);
279
- return toIdxList(capped(matches, numReturns));
312
+
313
+ return Value{};
280
314
  }
281
315
 
282
316
  Value builtinLookup(const CallArgs& args) {
@@ -506,10 +540,174 @@ const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
506
540
  return ids;
507
541
  }
508
542
 
543
+ // -- reference-parity argument diagnostics -------------------------------
544
+ //
545
+ // Real OpenSCAD checks a builtin's arguments before running it and warns in
546
+ // two fixed shapes (Parameters.cc / builtin_functions.cc):
547
+ //
548
+ // NAME() number of parameters does not match: expected N, found M
549
+ // NAME() parameter could not be converted: WHERE: expected T, found T (v)
550
+ //
551
+ // and returns undef either way. We used to do neither -- the value came out
552
+ // undef, silently, so `ord(undef)` and `abs("a")` alike said nothing at all.
553
+ // Every spec below (arity text included, since it is free-form per builtin)
554
+ // was read off OpenSCAD 2026.02.01 directly rather than guessed.
555
+ enum : unsigned {
556
+ TUndef = 1u << 0,
557
+ TBool = 1u << 1,
558
+ TNum = 1u << 2,
559
+ TStr = 1u << 3,
560
+ TVec = 1u << 4,
561
+ TRange = 1u << 5,
562
+ TFunc = 1u << 6,
563
+ TObj = 1u << 7,
564
+ };
565
+
566
+ unsigned typeBit(const Value& v) {
567
+ if (std::holds_alternative<bool>(v)) return TBool;
568
+ if (std::holds_alternative<double>(v)) return TNum;
569
+ if (std::holds_alternative<std::string>(v)) return TStr;
570
+ if (std::holds_alternative<ListPtr>(v)) return TVec;
571
+ if (std::holds_alternative<OscRange>(v)) return TRange;
572
+ if (std::holds_alternative<ClosurePtr>(v)) return TFunc;
573
+ if (std::holds_alternative<ObjectPtr>(v)) return TObj;
574
+ return TUndef;
575
+ }
576
+
577
+ struct ArgReq {
578
+ unsigned allowed;
579
+ const char* expected; // the word the reference prints, which is not
580
+ // always the full allowed set -- len() accepts a
581
+ // vector or an object but still says "string".
582
+ };
583
+
584
+ struct BuiltinCheck {
585
+ int minArgs; // -1 disables the arity check entirely
586
+ int maxArgs; // -1 = unbounded
587
+ const char* arityText; // free-form, e.g. "1", "3 or 4", "between 2 and 4"
588
+ std::vector<ArgReq> types;
589
+ };
590
+
591
+ const ArgReq kNum{TNum, "number"};
592
+ const ArgReq kVec{TVec, "vector"};
593
+
594
+ const std::unordered_map<int, BuiltinCheck>& builtinChecks() {
595
+ static const auto build = [] {
596
+ std::unordered_map<int, BuiltinCheck> m;
597
+ const auto add = [&m](BuiltinFnId id, BuiltinCheck c) { m.emplace(static_cast<int>(id), std::move(c)); };
598
+ for (BuiltinFnId id : {BuiltinFnId::Abs, BuiltinFnId::Sign, BuiltinFnId::Ceil, BuiltinFnId::Floor,
599
+ BuiltinFnId::Round, BuiltinFnId::Sqrt, BuiltinFnId::Ln, BuiltinFnId::Log,
600
+ BuiltinFnId::Exp, BuiltinFnId::Sin, BuiltinFnId::Cos, BuiltinFnId::Tan,
601
+ BuiltinFnId::Asin, BuiltinFnId::Acos, BuiltinFnId::Atan}) {
602
+ add(id, {1, 1, "1", {kNum}});
603
+ }
604
+ add(BuiltinFnId::Atan2, {2, 2, "2", {kNum, kNum}});
605
+ add(BuiltinFnId::Pow, {2, 2, "2", {kNum, kNum}});
606
+ add(BuiltinFnId::Cross, {2, 2, "2", {kVec, kVec}});
607
+ add(BuiltinFnId::Lookup, {2, 2, "2", {kNum, kVec}});
608
+ add(BuiltinFnId::Norm, {1, 1, "1", {kVec}});
609
+ // len() takes a vector or an object happily, and still calls the
610
+ // expected type "string" when handed anything else.
611
+ add(BuiltinFnId::Len, {1, 1, "1", {{TStr | TVec | TObj, "string"}}});
612
+ add(BuiltinFnId::Ord, {1, 1, "1", {{TStr, "string"}}});
613
+ add(BuiltinFnId::Rands, {3, 4, "3 or 4", {kNum, kNum, kNum, kNum}});
614
+ add(BuiltinFnId::Search, {2, 4, "between 2 and 4", {}});
615
+ // parent_module() with no argument defaults to 1 rather than
616
+ // warning, so only the too-many case is an arity error.
617
+ add(BuiltinFnId::ParentModule, {0, 1, "1", {kNum}});
618
+ add(BuiltinFnId::HasKey, {-1, -1, nullptr, {{TObj, "object"}, {TStr, "string"}}});
619
+ for (BuiltinFnId id : {BuiltinFnId::IsUndef, BuiltinFnId::IsNum, BuiltinFnId::IsBool,
620
+ BuiltinFnId::IsString, BuiltinFnId::IsList, BuiltinFnId::IsFunction,
621
+ BuiltinFnId::IsObject}) {
622
+ add(id, {1, 1, "1", {}});
623
+ }
624
+ return m;
625
+ };
626
+ static const std::unordered_map<int, BuiltinCheck> checks = build();
627
+ return checks;
628
+ }
629
+
630
+ void warnArity(Evaluator& ev, const std::string& name, const char* expected, size_t found,
631
+ const oscad::Position* pos) {
632
+ ev.warn(name + "() number of parameters does not match: expected " + expected + ", found " +
633
+ std::to_string(found),
634
+ pos);
635
+ }
636
+
637
+ void warnConversion(Evaluator& ev, const std::string& name, const std::string& where, const char* expected,
638
+ const Value& found, const oscad::Position* pos) {
639
+ ev.warn(name + "() parameter could not be converted: " + where + ": expected " + expected + ", found " +
640
+ oscTypeName(found) + " (" + fmtValue(found) + ")",
641
+ pos);
642
+ }
643
+
644
+ // max()/min() are their own shape: either one vector of numbers, or N bare
645
+ // numbers, with a distinct "at least 1 vector element" arity text for the
646
+ // empty-vector case.
647
+ bool checkMinMax(Evaluator& ev, const std::string& name, const CallArgs& args, const oscad::Position* pos) {
648
+ const std::vector<Value> positional = allPositional(args);
649
+ const size_t count = positional.size() + args.named.size();
650
+ if (count < 1) {
651
+ warnArity(ev, name, "at least 1", count, pos);
652
+ return false;
653
+ }
654
+ if (positional.size() == 1 && std::holds_alternative<ListPtr>(positional[0])) {
655
+ static const std::vector<Value> kEmptyItems;
656
+ const ListPtr& l = std::get<ListPtr>(positional[0]);
657
+ const std::vector<Value>& items = l ? l->items : kEmptyItems;
658
+ if (items.empty()) {
659
+ warnArity(ev, name, "at least 1 vector element", 0, pos);
660
+ return false;
661
+ }
662
+ for (size_t i = 0; i < items.size(); ++i) {
663
+ if (!std::holds_alternative<double>(items[i])) {
664
+ warnConversion(ev, name, "vector element " + std::to_string(i), "number", items[i], pos);
665
+ return false;
666
+ }
667
+ }
668
+ return true;
669
+ }
670
+ for (size_t i = 0; i < positional.size(); ++i) {
671
+ if (!std::holds_alternative<double>(positional[i])) {
672
+ warnConversion(ev, name, "argument " + std::to_string(i), "number", positional[i], pos);
673
+ return false;
674
+ }
675
+ }
676
+ return true;
677
+ }
678
+
679
+ // Returns false when a diagnostic was emitted and the call must answer undef.
680
+ bool checkBuiltinArgs(Evaluator& ev, const std::string& name, BuiltinFnId id, const CallArgs& args,
681
+ const oscad::Position* pos) {
682
+ if (id == BuiltinFnId::Max || id == BuiltinFnId::Min) return checkMinMax(ev, name, args, pos);
683
+ const auto& checks = builtinChecks();
684
+ const auto it = checks.find(static_cast<int>(id));
685
+ if (it == checks.end()) return true;
686
+ const BuiltinCheck& c = it->second;
687
+
688
+ const std::vector<Value> positional = allPositional(args);
689
+ const size_t count = positional.size() + args.named.size();
690
+ if (c.arityText && (static_cast<int>(count) < c.minArgs || (c.maxArgs >= 0 && static_cast<int>(count) > c.maxArgs))) {
691
+ warnArity(ev, name, c.arityText, count, pos);
692
+ return false;
693
+ }
694
+ // Positional only: a named argument's position in the reference's own
695
+ // flat argument list can't be reconstructed from our split
696
+ // positional/named form, and warning on the wrong index would be worse
697
+ // than staying quiet on a spelling nobody uses for these builtins.
698
+ for (size_t i = 0; i < c.types.size() && i < positional.size(); ++i) {
699
+ if (!(typeBit(positional[i]) & c.types[i].allowed)) {
700
+ warnConversion(ev, name, "argument " + std::to_string(i), c.types[i].expected, positional[i], pos);
701
+ return false;
702
+ }
703
+ }
704
+ return true;
705
+ }
706
+
707
+
509
708
  } // namespace
510
709
 
511
710
  Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs& args, const oscad::ASTNode& node) {
512
- (void)node;
513
711
 
514
712
  const auto& ids = builtinFnIds();
515
713
  const auto idIt = ids.find(name);
@@ -529,17 +727,10 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
529
727
  }
530
728
  }
531
729
 
532
- if (const auto arityIt = scalarNumericArity().find(name); arityIt != scalarNumericArity().end()) {
533
- const std::vector<Value> positional = allPositional(args);
534
- if (positional.size() < arityIt->second) return Value{};
535
- for (size_t i = 0; i < arityIt->second; ++i) {
536
- if (!std::holds_alternative<double>(positional[i])) return Value{};
537
- }
538
- } else if (numericOnlyNames().count(name)) {
539
- for (const Value& v : allPositional(args)) {
540
- if (isBoolOrListContainsBool(v)) return Value{};
541
- }
542
- }
730
+ // Arity and argument types, with the reference's own two diagnostics.
731
+ // This replaced a silent version of the same gate (scalarNumericArity /
732
+ // numericOnlyNames), which returned undef without ever saying why.
733
+ if (!checkBuiltinArgs(ev, name, idIt->second, args, &node.position())) return Value{};
543
734
 
544
735
  switch (idIt->second) {
545
736
  case BuiltinFnId::TextMetrics: return builtinTextmetrics(ev, args);
@@ -595,13 +786,39 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
595
786
  case BuiltinFnId::Min: return builtinMinMax(args, false);
596
787
  case BuiltinFnId::Pow: return builtinPow(toDoubleLenient(getArg(args, 0, "x", Value{})), toDoubleLenient(getArg(args, 1, "y", Value{})));
597
788
  case BuiltinFnId::Norm: {
789
+ // The vector-ness of the argument is the table's job; a
790
+ // non-numeric ELEMENT gets this separate, terser message
791
+ // instead of the usual conversion one.
598
792
  const auto v = allNumericList(getArg(args, 0, "v", Value{}));
599
- if (!v) return Value{};
793
+ if (!v) {
794
+ ev.warn("Incorrect arguments to norm()", &node.position());
795
+ return Value{};
796
+ }
600
797
  double sum = 0;
601
798
  for (double x : *v) sum += x * x;
602
799
  return Value{std::sqrt(sum)};
603
800
  }
604
- case BuiltinFnId::Cross: return builtinCross(getArg(args, 0, "a", Value{}), getArg(args, 1, "b", Value{}));
801
+ case BuiltinFnId::Cross: {
802
+ const Value a = getArg(args, 0, "a", Value{});
803
+ const Value b = getArg(args, 1, "b", Value{});
804
+ // Two distinct messages, and the size check runs first: a
805
+ // 4-element operand is a size complaint even when it also holds
806
+ // a string.
807
+ const auto sizeOf = [](const Value& v) {
808
+ const ListPtr* l = std::get_if<ListPtr>(&v);
809
+ return (l && *l) ? (*l)->items.size() : size_t{0};
810
+ };
811
+ const size_t na = sizeOf(a), nb = sizeOf(b);
812
+ if (na != nb || (na != 2 && na != 3)) {
813
+ ev.warn("Invalid vector size of parameter for cross()", &node.position());
814
+ return Value{};
815
+ }
816
+ if (!allNumericList(a) || !allNumericList(b)) {
817
+ ev.warn("Invalid value in parameter vector for cross()", &node.position());
818
+ return Value{};
819
+ }
820
+ return builtinCross(a, b);
821
+ }
605
822
  case BuiltinFnId::Rands: {
606
823
  ev.noteRandsCall();
607
824
  return builtinRands(toDoubleLenient(getArg(args, 0, "min_value", Value{})), toDoubleLenient(getArg(args, 1, "max_value", Value{})),
@@ -637,19 +854,34 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
637
854
  return Value{out};
638
855
  }
639
856
  case BuiltinFnId::Chr: {
640
- const Value x = getArg(args, 0, "x", Value{});
641
- const auto valid = [](const Value& c) {
642
- const double* d = std::get_if<double>(&c);
643
- return d != nullptr && std::isfinite(*d);
644
- };
645
- if (const ListPtr* l = std::get_if<ListPtr>(&x); l && *l) {
646
- std::string out;
647
- for (const Value& c : (*l)->items) {
648
- if (valid(c)) out += utf8Encode(static_cast<uint32_t>(std::get<double>(c)));
857
+ // Variadic, and every argument contributes: chr(65, 66) is "AB".
858
+ // A number only encodes when it is a codepoint g_unichar_validate
859
+ // would accept -- strictly positive, below 0x110000, and not a
860
+ // surrogate. Anything else contributes nothing at all. Without
861
+ // that range check chr(-1)/chr(1e9) emitted raw invalid UTF-8,
862
+ // which propagated out and broke the caller's own decoding.
863
+ const std::function<std::string(const Value&)> encode = [&](const Value& c) -> std::string {
864
+ if (const double* d = std::get_if<double>(&c)) {
865
+ if (!std::isfinite(*d) || *d <= 0) return {};
866
+ const auto cp = static_cast<std::uint32_t>(*d);
867
+ if (cp == 0 || cp > 0x10FFFFu || (cp >= 0xD800u && cp <= 0xDFFFu)) return {};
868
+ return utf8Encode(cp);
649
869
  }
650
- return Value{out};
651
- }
652
- return Value{valid(x) ? utf8Encode(static_cast<uint32_t>(std::get<double>(x))) : std::string{}};
870
+ if (const ListPtr* l = std::get_if<ListPtr>(&c); l && *l) {
871
+ std::string out;
872
+ for (const Value& item : (*l)->items) out += encode(item);
873
+ return out;
874
+ }
875
+ if (const OscRange* r = std::get_if<OscRange>(&c)) {
876
+ std::string out;
877
+ for (const Value& item : expandIterable(Value{*r})) out += encode(item);
878
+ return out;
879
+ }
880
+ return {};
881
+ };
882
+ std::string out;
883
+ for (const Value& a : allPositional(args)) out += encode(a);
884
+ return Value{out};
653
885
  }
654
886
  case BuiltinFnId::Ord: {
655
887
  const Value s = getArg(args, 0, "s", Value{});
@@ -677,15 +909,18 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
677
909
  const Value x = getArg(args, 0, "x", Value{});
678
910
  return Value{std::holds_alternative<ObjectPtr>(x) && std::get<ObjectPtr>(x) != nullptr};
679
911
  }
680
- case BuiltinFnId::Search: return builtinSearch(args);
912
+ case BuiltinFnId::Search: return builtinSearch(args, ev, &node.position());
681
913
  case BuiltinFnId::Lookup: return builtinLookup(args);
682
914
  case BuiltinFnId::HasKey: {
683
915
  const Value objArg = getArg(args, 0, "object", Value{});
684
916
  const ObjectPtr* obj = std::get_if<ObjectPtr>(&objArg);
685
917
  if (!obj || !*obj) return Value{};
686
918
  const Value keyArg = getArg(args, 1, "key", Value{});
919
+ // A non-string key is an argument-conversion failure in the
920
+ // reference (expected string), so it is undef -- not the "no,
921
+ // that key isn't present" false this used to answer.
687
922
  const std::string* key = std::get_if<std::string>(&keyArg);
688
- if (!key) return Value{false};
923
+ if (!key) return Value{};
689
924
  for (const auto& [k, v] : (*obj)->items) {
690
925
  if (k == *key) return Value{true};
691
926
  }
@@ -714,8 +949,20 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
714
949
  if (!v || (v->size() != 2 && v->size() != 3)) return Value{};
715
950
  return Value{(*v)[0] * 10000.0 + (*v)[1] * 100.0 + (v->size() == 3 ? (*v)[2] : 0.0)};
716
951
  }
717
- case BuiltinFnId::ParentModule:
718
- return ev.parentModuleName(static_cast<int>(toDoubleLenient(getArg(args, 0, "index", Value{0.0}))));
952
+ case BuiltinFnId::ParentModule: {
953
+ // Defaults to 1, not 0, when called with no argument at all --
954
+ // and an index past the end of the stack is a warning, not a
955
+ // silent undef.
956
+ const Value* given = args.findPositional(0);
957
+ const int index = given ? static_cast<int>(toDoubleLenient(*given)) : 1;
958
+ Value r = ev.parentModuleName(index);
959
+ if (std::holds_alternative<std::monostate>(r)) {
960
+ ev.warn("Parent module index (" + std::to_string(index) +
961
+ ") greater than the number of modules on the stack",
962
+ &node.position());
963
+ }
964
+ return r;
965
+ }
719
966
  }
720
967
  return Value{}; // unreachable: every BuiltinFnId has a case above
721
968
  }