openscad-cpp-evaluator 0.38.1__tar.gz → 0.40.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 (206) hide show
  1. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/CLAUDE.md +14 -0
  2. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/function_builtins.hpp +4 -2
  4. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/value.hpp +16 -1
  5. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/control.cpp +2 -0
  7. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/function_builtins.cpp +95 -15
  8. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/primitives_3d.cpp +31 -0
  9. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/bytecode_vm.cpp +5 -1
  10. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/expr_eval.cpp +2 -0
  11. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/stmt_eval.cpp +2 -0
  12. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/user_calls.cpp +54 -4
  13. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/value.cpp +18 -1
  14. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_control_flow.cpp +244 -0
  15. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_expr_eval.cpp +106 -0
  16. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_render_expr.cpp +35 -0
  17. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/.github/workflows/ci.yml +0 -0
  18. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/.github/workflows/wheels.yml +0 -0
  19. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/.gitignore +0 -0
  20. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/.gitmodules +0 -0
  21. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/README.md +0 -0
  24. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/bindings/module.cpp +0 -0
  27. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/cmake/embed_font.cmake +0 -0
  28. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/examples/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/examples/eval_perf_benchmark.cpp +0 -0
  30. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/examples/manifold_cache_reuse.cpp +0 -0
  31. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/examples/minimal_debugger.cpp +0 -0
  32. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  33. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/.gitignore +0 -0
  34. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  35. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  36. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/README.md +0 -0
  37. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  38. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  39. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  40. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  41. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  42. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  43. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  44. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  45. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  46. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  48. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  49. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  50. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  51. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  52. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  53. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  54. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  55. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  56. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  57. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  58. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  59. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  60. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  61. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  62. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  63. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  64. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  65. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  66. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  67. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  68. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  69. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  70. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  71. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  72. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  73. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  74. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  75. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  76. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  77. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  78. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  79. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  80. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  81. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  82. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  83. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  84. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  85. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  86. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  87. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  91. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  92. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  93. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  94. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  95. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  96. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  97. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  98. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  99. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  100. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  101. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  102. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  103. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  104. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  105. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  106. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  107. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  108. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  109. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  115. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  116. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  117. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  118. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  120. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  121. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  122. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  123. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  124. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  125. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  126. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  127. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  129. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  130. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  131. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/resources/fonts/LICENSE +0 -0
  132. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  133. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/CMakeLists.txt +0 -0
  134. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/booleans.cpp +0 -0
  135. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/builtins.hpp +0 -0
  136. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/call_args.cpp +0 -0
  137. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/color.cpp +0 -0
  138. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/dxf_dim.cpp +0 -0
  139. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/extrude.cpp +0 -0
  140. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/import.cpp +0 -0
  141. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/modifiers.cpp +0 -0
  142. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/primitives_2d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/builtins/transforms.cpp +0 -0
  150. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/bytecode_compiler.cpp +0 -0
  151. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/colored_body.cpp +0 -0
  152. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/csg_generate.cpp +0 -0
  153. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/csg_resolve.cpp +0 -0
  154. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/css_colors.cpp +0 -0
  155. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/debug/debug_repl.cpp +0 -0
  156. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/debug_profile.cpp +0 -0
  157. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/eval_context.cpp +0 -0
  158. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/eval_error.cpp +0 -0
  159. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/eval_use.cpp +0 -0
  160. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/export.cpp +0 -0
  161. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/import/dxf_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/import/mesh_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/import/surface_load.cpp +0 -0
  164. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/import/svg_import.cpp +0 -0
  165. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/manifold_cache.cpp +0 -0
  166. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/measure_geometry.cpp +0 -0
  167. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/mesh_check.cpp +0 -0
  168. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/text/font_match.cpp +0 -0
  169. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/text/freetype_font_provider.cpp +0 -0
  170. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/text/text_metrics.cpp +0 -0
  171. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/CMakeLists.txt +0 -0
  173. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_booleans.cpp +0 -0
  174. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_bytecode_compiler.cpp +0 -0
  175. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_csg_tree.cpp +0 -0
  177. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_debug_hooks.cpp +0 -0
  178. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_dxf_svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_eval_context.cpp +0 -0
  180. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_export.cpp +0 -0
  181. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_extrude_roof.cpp +0 -0
  182. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_function_builtins.cpp +0 -0
  183. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_helpers.hpp +0 -0
  184. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_import_export.cpp +0 -0
  185. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_manifold_cache.cpp +0 -0
  186. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_mesh_check.cpp +0 -0
  187. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_multi_color_merge.cpp +0 -0
  188. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_primitives.cpp +0 -0
  189. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_profiling.cpp +0 -0
  190. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_python_bindings.py +0 -0
  191. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_scope_trail.cpp +0 -0
  192. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_scoping.cpp +0 -0
  193. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_surface.cpp +0 -0
  194. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_tail_calls.cpp +0 -0
  195. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_text.cpp +0 -0
  196. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_transforms.cpp +0 -0
  197. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_unexpected_args.cpp +0 -0
  198. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_use.cpp +0 -0
  199. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_value.cpp +0 -0
  200. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_viewport_params.cpp +0 -0
  201. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tests/test_zip_stored.cpp +0 -0
  202. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tools/cli/CMakeLists.txt +0 -0
  203. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tools/cli/cli_lib.cpp +0 -0
  204. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tools/cli/cli_lib.hpp +0 -0
  205. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/tools/cli/main.cpp +0 -0
  206. {openscad_cpp_evaluator-0.38.1 → openscad_cpp_evaluator-0.40.0}/uv.lock +0 -0
@@ -1128,6 +1128,20 @@ vector and `oscEqual` is order-sensitive):
1128
1128
  `vnf` is the `[vertices, faces]` 2-list BOSL2 functions actually take.
1129
1129
  `polyhedron()` and `polygon()` also accept the object directly, and will accept
1130
1130
  *any* object carrying the right keys, not just one `render()` produced.
1131
+ `polyhedron()` additionally accepts the bare `[vertices, faces]` 2-list, so a
1132
+ BOSL2 VNF goes straight in — all four of these are the same call:
1133
+
1134
+ ```openscad
1135
+ polyhedron(obj); // the object
1136
+ polyhedron(obj.vnf); // the 2-list
1137
+ polyhedron(obj.vertices, obj.faces); // the halves
1138
+ polyhedron(spheroid(d=30)); // any BOSL2 VNF
1139
+ ```
1140
+
1141
+ The 2-list form is only considered when `faces` was not given separately, so the
1142
+ two-argument call can never be reinterpreted. The discriminator is BOSL2's own
1143
+ `is_vnf` test — a VNF's second element is a list of *lists*, where a plain points
1144
+ list has a point (bare numbers) there.
1131
1145
 
1132
1146
  ### Things that will bite
1133
1147
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.38.1
3
+ Version: 0.40.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -41,7 +41,8 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
41
41
  // evaluated exactly once (resolving into a CallArgs first and then
42
42
  // re-evaluating from the raw list here would run every argument expression
43
43
  // twice -- wrong for anything with a side effect, e.g. rands()).
44
- Value builtinObject(Evaluator& ev, const std::vector<std::unique_ptr<oscad::Argument>>& arguments, EvalContext& ctx);
44
+ Value builtinObject(Evaluator& ev, const std::vector<std::unique_ptr<oscad::Argument>>& arguments, EvalContext& ctx,
45
+ const oscad::ASTNode& node);
45
46
 
46
47
  // The shared merge core builtinObject wraps: given ALREADY-EVALUATED
47
48
  // (name-or-nullopt, Value) pairs in exact call-site order, merges them the
@@ -52,6 +53,7 @@ Value builtinObject(Evaluator& ev, const std::vector<std::unique_ptr<oscad::Argu
52
53
  // logic against its own already-evaluated (argNames[i], args[i]) pairs
53
54
  // (already in call-site order by construction -- see CallSite::argNames)
54
55
  // without re-deriving the merge rules or touching raw AST nodes.
55
- Value mergeObjectArgs(const std::vector<std::pair<std::optional<std::string>, Value>>& evaluated);
56
+ Value mergeObjectArgs(Evaluator& ev, const std::vector<std::pair<std::optional<std::string>, Value>>& evaluated,
57
+ const oscad::Position* pos);
56
58
 
57
59
  } // namespace oscadeval
@@ -355,6 +355,20 @@ private:
355
355
  // it, keeping expandIterable() itself free of any Evaluator/echo coupling.
356
356
  using RangeTooManyFn = std::function<void(size_t count)>;
357
357
 
358
+ // Called when a range's step points AWAY from its end -- begin > end with a
359
+ // positive step, or begin < end with a negative step. Such a range is
360
+ // naturally empty, and the reference warns rather than iterating zero times
361
+ // in silence. `stepPositive` picks which of its two messages applies; use
362
+ // rangeDirectionWarning() to build the text.
363
+ //
364
+ // Separate from RangeTooManyFn for the same reason that one exists: it
365
+ // keeps expandIterable() free of any Evaluator/echo coupling.
366
+ using RangeDirectionFn = std::function<void(bool stepPositive)>;
367
+
368
+ // The reference's own wording, quoted verbatim, for a range whose step
369
+ // points away from its end. Shared so the five call sites cannot drift.
370
+ std::string rangeDirectionWarning(bool stepPositive);
371
+
358
372
  // Converts a for()/intersection_for() loop assignment's evaluated RHS into
359
373
  // the sequence of values to iterate: undef -> empty, range -> a LAZY
360
374
  // sequence (matching OscRange's own start/step/end iteration, half-open
@@ -380,7 +394,8 @@ using RangeTooManyFn = std::function<void(size_t count)>;
380
394
  // one with a live consumer (BelfrySCAD) that surfaced it. The count is
381
395
  // computed in closed form (not IterableValues::size()'s O(n) walk) so a
382
396
  // legitimate huge-but-under-the-limit range never pays an eager pass.
383
- IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr);
397
+ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr,
398
+ const RangeDirectionFn& onWrongDirection = nullptr);
384
399
 
385
400
  // `each <body>`'s own flatten-one-level rule, shared by the AST interpreter
386
401
  // (evalListLiteral/evalListElement's ListCompEach handling, expr_eval.cpp)
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.38.1"
7
+ version = "0.40.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -128,6 +128,8 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
128
128
  const oscad::Position* pos = &assign->position();
129
129
  IterableValues iter = expandIterable(values, [&](size_t count) {
130
130
  ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
131
+ }, [&](bool stepPositive) {
132
+ ev.warn(rangeDirectionWarning(stepPositive), pos);
131
133
  });
132
134
  for (const Value& val : iter) {
133
135
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
@@ -454,7 +454,33 @@ bool isBuiltinFunctionName(const std::string& name) {
454
454
  return names.count(name) > 0;
455
455
  }
456
456
 
457
- Value mergeObjectArgs(const std::vector<std::pair<std::optional<std::string>, Value>>& evaluated) {
457
+ // object(...) argument merging, matching the reference's own semantics and
458
+ // diagnostics (Builtins.cc's builtin_object).
459
+ //
460
+ // An unnamed argument is either another object (its keys are merged in) or
461
+ // a LIST of entries, where each entry is:
462
+ // [key, value] -- set (or overwrite) that key
463
+ // [key] -- DELETE that key
464
+ //
465
+ // The single-element delete form is the part that is easy to miss. Deleting
466
+ // removes the key outright rather than blanking it, so a later re-set
467
+ // appends at the end: object(a, [["b"], ["b", 99]]) puts b last, while
468
+ // object(a, [["b", 99], ["b"]]) has no b at all. That ordering is
469
+ // observable -- ValueObject is insertion-ordered and oscEqual is
470
+ // order-sensitive.
471
+ //
472
+ // Deleting a key that is not there is a silent no-op, as it is upstream.
473
+ // Every malformed entry warns and abandons the whole call (returning undef),
474
+ // stopping at the first one. The warning text is quoted verbatim from the
475
+ // reference, including its own inconsistent spacing -- the "not a list"
476
+ // case really does put spaces inside the parens where the others do not,
477
+ // and the "unnamed argument" case really does end with a trailing space.
478
+ Value mergeObjectArgs(Evaluator& ev, const std::vector<std::pair<std::optional<std::string>, Value>>& evaluated,
479
+ const oscad::Position* pos) {
480
+ static const char* kEntryRules =
481
+ " In an unnamed list, entries must be [key,value] to set or [key] to delete."
482
+ " The key must be <string>.";
483
+
458
484
  std::vector<std::pair<std::string, Value>> result;
459
485
  const auto setKey = [&](const std::string& k, const Value& v) {
460
486
  for (auto& [ek, ev2] : result) {
@@ -465,30 +491,79 @@ Value mergeObjectArgs(const std::vector<std::pair<std::optional<std::string>, Va
465
491
  }
466
492
  result.emplace_back(k, v);
467
493
  };
468
- for (const auto& [name, v] : evaluated) {
494
+ const auto deleteKey = [&](const std::string& k) {
495
+ for (auto it = result.begin(); it != result.end(); ++it) {
496
+ if (it->first == k) {
497
+ result.erase(it);
498
+ return;
499
+ }
500
+ }
501
+ // Deleting an absent key is deliberately silent.
502
+ };
503
+
504
+ for (size_t argIdx = 0; argIdx < evaluated.size(); ++argIdx) {
505
+ const auto& [name, v] = evaluated[argIdx];
469
506
  if (name) {
470
507
  setKey(*name, v);
471
508
  continue;
472
509
  }
510
+ const std::string argPrefix = "object(Argument " + std::to_string(argIdx) + " ";
473
511
  if (const ObjectPtr* o = std::get_if<ObjectPtr>(&v); o && *o) {
474
512
  for (const auto& [k, kv] : (*o)->items) setKey(k, kv);
475
- } else if (const ListPtr* l = std::get_if<ListPtr>(&v); l && *l) {
476
- for (const Value& entry : (*l)->items) {
477
- const ListPtr* pair = std::get_if<ListPtr>(&entry);
478
- if (pair && *pair && (*pair)->items.size() == 2 && std::holds_alternative<std::string>((*pair)->items[0])) {
479
- setKey(std::get<std::string>((*pair)->items[0]), (*pair)->items[1]);
480
- } else {
481
- return Value{};
482
- }
483
- }
484
- } else if (!std::holds_alternative<std::monostate>(v)) {
513
+ continue;
514
+ }
515
+ const ListPtr* l = std::get_if<ListPtr>(&v);
516
+ if (!l || !*l) {
517
+ // undef is accepted and contributes nothing, as upstream.
518
+ if (std::holds_alternative<std::monostate>(v)) continue;
519
+ ev.warn(argPrefix + "<" + oscTypeName(v) + ">) An unnamed argument must be either <object> or"
520
+ " <list>, it is <" + oscTypeName(v) + ">. ",
521
+ pos);
485
522
  return Value{};
486
523
  }
524
+ for (size_t elemIdx = 0; elemIdx < (*l)->items.size(); ++elemIdx) {
525
+ const Value& entry = (*l)->items[elemIdx];
526
+ const std::string where = "[Element " + std::to_string(elemIdx) + " ";
527
+ const ListPtr* pair = std::get_if<ListPtr>(&entry);
528
+ if (!pair || !*pair) {
529
+ // Note the spaces inside the parens: upstream's own quirk.
530
+ ev.warn("object( Argument " + std::to_string(argIdx) + " " + where + "<" + oscTypeName(entry) +
531
+ ">] ) Entry type is not a list, it is <" + oscTypeName(entry) + ">." + kEntryRules,
532
+ pos);
533
+ return Value{};
534
+ }
535
+ const size_t n = (*pair)->items.size();
536
+ if (n == 0) {
537
+ ev.warn(argPrefix + where + "[]]) Entry is empty." + kEntryRules, pos);
538
+ return Value{};
539
+ }
540
+ if (n > 2) {
541
+ ev.warn(argPrefix + where + "[...]]) Entry length is " + std::to_string(n) +
542
+ ", must be 1 [key] or 2 [key,value]." + kEntryRules,
543
+ pos);
544
+ return Value{};
545
+ }
546
+ const Value& key = (*pair)->items[0];
547
+ if (!std::holds_alternative<std::string>(key)) {
548
+ const std::string shape = n == 2 ? "[<" + oscTypeName(key) + ">,value]"
549
+ : "[<" + oscTypeName(key) + ">]";
550
+ ev.warn(argPrefix + where + shape + "]) The key of the entry is not <string> but <" +
551
+ oscTypeName(key) + ">." + kEntryRules,
552
+ pos);
553
+ return Value{};
554
+ }
555
+ if (n == 2) {
556
+ setKey(std::get<std::string>(key), (*pair)->items[1]);
557
+ } else {
558
+ deleteKey(std::get<std::string>(key));
559
+ }
560
+ }
487
561
  }
488
562
  return Value{std::make_shared<const ValueObject>(ValueObject{std::move(result)})};
489
563
  }
490
564
 
491
- Value builtinObject(Evaluator& ev, const std::vector<std::unique_ptr<oscad::Argument>>& arguments, EvalContext& ctx) {
565
+ Value builtinObject(Evaluator& ev, const std::vector<std::unique_ptr<oscad::Argument>>& arguments, EvalContext& ctx,
566
+ const oscad::ASTNode& node) {
492
567
  std::vector<std::pair<std::optional<std::string>, Value>> evaluated;
493
568
  evaluated.reserve(arguments.size());
494
569
  for (const auto& argPtr : arguments) {
@@ -499,7 +574,7 @@ Value builtinObject(Evaluator& ev, const std::vector<std::unique_ptr<oscad::Argu
499
574
  }
500
575
  evaluated.emplace_back(std::move(name), std::move(v));
501
576
  }
502
- return mergeObjectArgs(evaluated);
577
+ return mergeObjectArgs(ev, evaluated, &node.position());
503
578
  }
504
579
 
505
580
  namespace {
@@ -877,7 +952,12 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
877
952
  }
878
953
  if (const OscRange* r = std::get_if<OscRange>(&c)) {
879
954
  std::string out;
880
- for (const Value& item : expandIterable(Value{*r})) out += encode(item);
955
+ // The reference warns here too -- chr([70:1:65]) is
956
+ // as much a typo as for(i=[70:1:65]) would be.
957
+ const IterableValues seq = expandIterable(Value{*r}, nullptr, [&](bool stepPositive) {
958
+ ev.warn(rangeDirectionWarning(stepPositive), &node.position());
959
+ });
960
+ for (const Value& item : seq) out += encode(item);
881
961
  return out;
882
962
  }
883
963
  return {};
@@ -630,6 +630,37 @@ CSGParams resolvePolyhedron(Evaluator& ev, const oscad::ModularCall& node, EvalC
630
630
  facesArg = std::move(newFaces);
631
631
  }
632
632
 
633
+ // polyhedron(vnf) -- BOSL2's [vertices, faces] 2-list, which is what
634
+ // every BOSL2 function passes around and what obj.vnf holds. Only
635
+ // considered when `faces` was not given separately, so the two-argument
636
+ // form always wins and can never be reinterpreted.
637
+ //
638
+ // The discriminator is BOSL2's own is_vnf test: a VNF's second element
639
+ // is a list of FACES, i.e. a list of lists. A plain points list of
640
+ // length 2 has a point there instead -- bare numbers, not lists -- and
641
+ // is left alone. (Two points cannot describe a polyhedron anyway, so
642
+ // nothing legitimate is being taken over.)
643
+ if (isUndef(facesArg)) {
644
+ if (const ListPtr* outer = std::get_if<ListPtr>(&pointsArg); outer && *outer &&
645
+ (*outer)->items.size() == 2) {
646
+ const ListPtr* maybeVerts = std::get_if<ListPtr>(&(*outer)->items[0]);
647
+ const ListPtr* maybeFaces = std::get_if<ListPtr>(&(*outer)->items[1]);
648
+ const bool facesAreLists =
649
+ maybeFaces && *maybeFaces && !(*maybeFaces)->items.empty() &&
650
+ std::holds_alternative<ListPtr>((*maybeFaces)->items[0]);
651
+ const bool vertsArePoints =
652
+ maybeVerts && *maybeVerts && !(*maybeVerts)->items.empty() &&
653
+ std::holds_alternative<ListPtr>((*maybeVerts)->items[0]);
654
+ if (facesAreLists && vertsArePoints) {
655
+ // Copy before assigning: both borrow into pointsArg's list.
656
+ Value newPoints = (*outer)->items[0];
657
+ Value newFaces = (*outer)->items[1];
658
+ pointsArg = std::move(newPoints);
659
+ facesArg = std::move(newFaces);
660
+ }
661
+ }
662
+ }
663
+
633
664
  const ListPtr* pointsList = std::get_if<ListPtr>(&pointsArg);
634
665
  const ListPtr* facesList = std::get_if<ListPtr>(&facesArg);
635
666
  if (!pointsList || !*pointsList || !facesList || !*facesList) {
@@ -740,6 +740,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
740
740
  il.values = expandIterable(v, [&](size_t count) {
741
741
  ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")",
742
742
  ins.pos);
743
+ }, [&](bool stepPositive) {
744
+ ev.warn(rangeDirectionWarning(stepPositive), ins.pos);
743
745
  });
744
746
  il.index = 0;
745
747
  il.total = il.values.size();
@@ -787,7 +789,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
787
789
  std::vector<std::pair<std::optional<std::string>, Value>> pairs;
788
790
  pairs.reserve(argCount);
789
791
  for (size_t i = 0; i < argCount; ++i) pairs.emplace_back(site.argNames[i], std::move(args[i]));
790
- f.stack.push_back(mergeObjectArgs(pairs));
792
+ f.stack.push_back(mergeObjectArgs(ev, pairs, &site.callNode->position()));
791
793
  ++f.pc;
792
794
  } else if (site.isBuiltin) {
793
795
  CallArgs callArgs = buildCallArgs(site, args, argCount);
@@ -1464,6 +1466,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
1464
1466
  il.values = expandIterable(v, [&](size_t count) {
1465
1467
  ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")",
1466
1468
  ins.pos);
1469
+ }, [&](bool stepPositive) {
1470
+ ev.warn(rangeDirectionWarning(stepPositive), ins.pos);
1467
1471
  });
1468
1472
  il.index = 0;
1469
1473
  il.total = il.values.size();
@@ -177,6 +177,8 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
177
177
  const oscad::Position* pos = &assign->position();
178
178
  IterableValues iter = expandIterable(values, [&](size_t count) {
179
179
  warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
180
+ }, [&](bool stepPositive) {
181
+ warn(rangeDirectionWarning(stepPositive), pos);
180
182
  });
181
183
  for (const Value& val : iter) {
182
184
  EvalContext childCtx = parentCtx.letChildCtx();
@@ -112,6 +112,8 @@ void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
112
112
  const oscad::Position* pos = &assign->position();
113
113
  IterableValues iter = expandIterable(values, [&](size_t count) {
114
114
  warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
115
+ }, [&](bool stepPositive) {
116
+ warn(rangeDirectionWarning(stepPositive), pos);
115
117
  });
116
118
  for (const Value& val : iter) {
117
119
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
@@ -886,7 +886,7 @@ Value Evaluator::evalFunctionCall(const oscad::PrimaryCall& node, EvalContext& c
886
886
  // call-site interleaved order (see builtinObject's own
887
887
  // comment) -- called directly, before resolveArgs, so
888
888
  // arguments aren't evaluated twice.
889
- if (leftId->name == "object") return builtinObject(*this, node.arguments, ctx);
889
+ if (leftId->name == "object") return builtinObject(*this, node.arguments, ctx, node);
890
890
  CallArgs args = resolveArgs(*this, node.arguments, ctx);
891
891
  return evalBuiltinFunction(*this, leftId->name, args, node);
892
892
  }
@@ -984,7 +984,6 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
984
984
  // filtered statement may produce 0 bodies, which would shift every
985
985
  // subsequent body-index lookup, so the Nth statement is evaluated
986
986
  // directly instead.
987
- const int idx = static_cast<int>(toDoubleLenient(idxArg));
988
987
  std::vector<const oscad::ASTNode*> geoNodes;
989
988
  for (const oscad::ASTNode* c : *ctx.childrenNodes) {
990
989
  if (c->kind() != oscad::NodeKind::Assignment && c->kind() != oscad::NodeKind::ModuleDeclaration &&
@@ -992,8 +991,59 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
992
991
  geoNodes.push_back(c);
993
992
  }
994
993
  }
995
- if (idx < 0 || static_cast<size_t>(idx) >= geoNodes.size()) return std::nullopt;
996
- return ChildrenForward{std::move(evalCtx), {geoNodes[static_cast<size_t>(idx)]}};
994
+
995
+ // The argument is a number, a VECTOR of numbers, or a RANGE --
996
+ // children([3:1:5]) is children(3); children(4); children(5), and
997
+ // children([3:-1:1]) is 3, 2, 1 in that order. Only a plain number was
998
+ // handled before, and toDoubleLenient collapses a list or a range to 0,
999
+ // so every vector/range form silently rendered child 0 instead.
1000
+ //
1001
+ // Ranges go through expandIterable, the same path a for-loop uses, so
1002
+ // step direction, fractional steps and naturally-empty ranges all
1003
+ // behave identically in both places rather than growing a second
1004
+ // interpretation here.
1005
+ std::vector<Value> indexValues;
1006
+ if (std::holds_alternative<double>(idxArg)) {
1007
+ indexValues.push_back(idxArg);
1008
+ } else if (std::holds_alternative<ListPtr>(idxArg) || std::holds_alternative<OscRange>(idxArg)) {
1009
+ const IterableValues iter = expandIterable(idxArg, nullptr, [&](bool stepPositive) {
1010
+ warn(rangeDirectionWarning(stepPositive), currentWarnEntry());
1011
+ });
1012
+ for (const Value& v : iter) indexValues.push_back(v);
1013
+ } else {
1014
+ warn("Bad parameter type (" + fmtValue(idxArg) +
1015
+ ") for children, only accept: empty, number, vector, range.",
1016
+ currentWarnEntry());
1017
+ return std::nullopt;
1018
+ }
1019
+
1020
+ // Order is preserved and duplicates are kept: children([2,2,2]) really
1021
+ // does evaluate child 2 three times, and the order shows through in the
1022
+ // CSG tree even though a union usually hides it.
1023
+ std::vector<const oscad::ASTNode*> picked;
1024
+ picked.reserve(indexValues.size());
1025
+ for (const Value& v : indexValues) {
1026
+ if (!std::holds_alternative<double>(v)) {
1027
+ warn("Bad parameter type (" + fmtValue(v) +
1028
+ ") for children, only accept: empty, number, vector, range.",
1029
+ currentWarnEntry());
1030
+ return std::nullopt;
1031
+ }
1032
+ // Truncates, matching the reference: children([1.7]) is child 1.
1033
+ const long long idx = static_cast<long long>(std::get<double>(v));
1034
+ if (idx < 0 || static_cast<size_t>(idx) >= geoNodes.size()) {
1035
+ // An out-of-range index is skipped, not fatal -- children([0,99])
1036
+ // still draws child 0. Warned about either way; this used to
1037
+ // return silently.
1038
+ warn("Children index (" + fmtValue(v) + ") out of bounds (" +
1039
+ std::to_string(geoNodes.size()) + " children)",
1040
+ currentWarnEntry());
1041
+ continue;
1042
+ }
1043
+ picked.push_back(geoNodes[static_cast<size_t>(idx)]);
1044
+ }
1045
+ if (picked.empty()) return std::nullopt;
1046
+ return ChildrenForward{std::move(evalCtx), std::move(picked)};
997
1047
  }
998
1048
 
999
1049
  void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
@@ -490,13 +490,30 @@ std::optional<size_t> rangeElementCount(const OscRange& r) {
490
490
  return static_cast<size_t>(std::floor(n + 1e-10)) + 1;
491
491
  }
492
492
 
493
- IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany) {
493
+ std::string rangeDirectionWarning(bool stepPositive) {
494
+ return stepPositive ? "begin is greater than the end, but step is positive"
495
+ : "begin is smaller than the end, but step is negative";
496
+ }
497
+
498
+ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany,
499
+ const RangeDirectionFn& onWrongDirection) {
494
500
  if (std::holds_alternative<std::monostate>(v)) return IterableValues{};
495
501
  if (const OscRange* r = std::get_if<OscRange>(&v)) {
496
502
  if (const std::optional<size_t> count = rangeElementCount(*r); count && *count >= 1'000'000) {
497
503
  if (onTooMany) onTooMany(*count);
498
504
  return IterableValues{};
499
505
  }
506
+ // A step pointing away from the end yields nothing. That is almost
507
+ // always a typo -- [1:0] where [1:-1:0] was meant -- so the
508
+ // reference says so rather than silently running zero times.
509
+ //
510
+ // A zero step is deliberately NOT reported here: it is a different
511
+ // failure (the reference calls it "too many elements") and shares
512
+ // rangeElementCount's nullopt with this case only by coincidence.
513
+ if (onWrongDirection && r->step != 0.0) {
514
+ const double n = (r->end - r->start) / r->step;
515
+ if (n < -1e-10) onWrongDirection(r->step > 0.0);
516
+ }
500
517
  // Lazy -- IterableValues itself reproduces this exact
501
518
  // termination condition (a zero step is naturally empty: neither
502
519
  // `x <= end` nor `x >= end` branch ever fires for it) without