openscad-cpp-evaluator 0.40.0__tar.gz → 0.42.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 (207) hide show
  1. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/CLAUDE.md +4 -1
  2. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +10 -2
  4. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/expression.cpp +4 -0
  5. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +4 -2
  6. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +3 -1
  7. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +13 -0
  8. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +4 -0
  9. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/csg_node.hpp +18 -0
  10. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/evaluator.hpp +35 -1
  11. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/value.hpp +1 -16
  12. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/pyproject.toml +1 -1
  13. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/booleans.cpp +7 -3
  14. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/control.cpp +1 -4
  15. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/function_builtins.cpp +1 -5
  16. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/registry.cpp +1 -1
  17. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/bytecode_compiler.cpp +1 -1
  18. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/bytecode_vm.cpp +15 -9
  19. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/csg_resolve.cpp +6 -1
  20. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/expr_eval.cpp +24 -4
  21. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/stmt_eval.cpp +0 -2
  22. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/user_calls.cpp +11 -5
  23. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/value.cpp +6 -18
  24. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/CMakeLists.txt +1 -0
  25. openscad_cpp_evaluator-0.42.0/tests/test_children_separate.cpp +278 -0
  26. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_control_flow.cpp +41 -16
  27. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/.github/workflows/ci.yml +0 -0
  28. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/.github/workflows/wheels.yml +0 -0
  29. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/.gitignore +0 -0
  30. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/.gitmodules +0 -0
  31. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/LICENSE +0 -0
  33. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/README.md +0 -0
  34. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/bindings/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/bindings/ast_to_py.cpp +0 -0
  36. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/bindings/module.cpp +0 -0
  37. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/cmake/embed_font.cmake +0 -0
  38. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/examples/CMakeLists.txt +0 -0
  39. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/examples/eval_perf_benchmark.cpp +0 -0
  40. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/examples/manifold_cache_reuse.cpp +0 -0
  41. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/examples/minimal_debugger.cpp +0 -0
  42. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  43. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/.gitignore +0 -0
  44. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  45. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  46. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/README.md +0 -0
  47. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  48. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  49. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  50. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  51. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  52. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  53. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  54. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  55. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  56. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  57. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  58. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  59. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  60. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  61. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  62. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  63. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  64. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  65. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  66. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  67. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  68. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  69. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  70. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  71. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  72. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  73. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  74. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  75. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  76. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  77. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  78. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  79. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  80. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  81. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  82. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  83. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  84. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  85. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  86. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  87. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  88. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  89. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  90. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  91. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  92. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  93. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  94. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  95. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  96. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  97. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  98. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  99. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  100. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  101. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  102. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  103. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  104. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  105. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  106. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  107. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  110. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  111. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  112. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  113. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  114. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  115. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  116. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  117. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  118. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  119. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  122. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  123. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  124. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  125. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  126. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  127. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  128. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  129. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  130. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  131. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  132. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  133. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  134. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/resources/fonts/LICENSE +0 -0
  135. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  136. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/CMakeLists.txt +0 -0
  137. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/builtins.hpp +0 -0
  138. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/call_args.cpp +0 -0
  139. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/color.cpp +0 -0
  140. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/dxf_dim.cpp +0 -0
  141. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/extrude.cpp +0 -0
  142. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/import.cpp +0 -0
  143. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/modifiers.cpp +0 -0
  144. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/primitives_2d.cpp +0 -0
  145. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/primitives_3d.cpp +0 -0
  146. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/roof.cpp +0 -0
  147. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/segments.cpp +0 -0
  148. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/surface.cpp +0 -0
  149. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/text.cpp +0 -0
  150. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/topology.cpp +0 -0
  151. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/builtins/transforms.cpp +0 -0
  152. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/colored_body.cpp +0 -0
  153. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/csg_generate.cpp +0 -0
  154. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/css_colors.cpp +0 -0
  155. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/debug/debug_repl.cpp +0 -0
  156. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/debug_profile.cpp +0 -0
  157. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/eval_context.cpp +0 -0
  158. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/eval_error.cpp +0 -0
  159. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/eval_use.cpp +0 -0
  160. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/export.cpp +0 -0
  161. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/import/dxf_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/import/mesh_import.cpp +0 -0
  163. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/import/surface_load.cpp +0 -0
  164. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/import/svg_import.cpp +0 -0
  165. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/manifold_cache.cpp +0 -0
  166. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/measure_geometry.cpp +0 -0
  167. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/mesh_check.cpp +0 -0
  168. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/text/font_match.cpp +0 -0
  169. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/text/freetype_font_provider.cpp +0 -0
  170. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/text/text_metrics.cpp +0 -0
  171. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/src/zip_stored.cpp +0 -0
  172. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_booleans.cpp +0 -0
  173. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_bytecode_compiler.cpp +0 -0
  174. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_cli.cpp +0 -0
  175. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_csg_tree.cpp +0 -0
  176. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_debug_hooks.cpp +0 -0
  177. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_dxf_svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_eval_context.cpp +0 -0
  179. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_export.cpp +0 -0
  180. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_expr_eval.cpp +0 -0
  181. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_extrude_roof.cpp +0 -0
  182. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_function_builtins.cpp +0 -0
  183. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_helpers.hpp +0 -0
  184. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_import_export.cpp +0 -0
  185. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_manifold_cache.cpp +0 -0
  186. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_mesh_check.cpp +0 -0
  187. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_multi_color_merge.cpp +0 -0
  188. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_primitives.cpp +0 -0
  189. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_profiling.cpp +0 -0
  190. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_python_bindings.py +0 -0
  191. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_render_expr.cpp +0 -0
  192. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_scope_trail.cpp +0 -0
  193. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_scoping.cpp +0 -0
  194. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_surface.cpp +0 -0
  195. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_tail_calls.cpp +0 -0
  196. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_text.cpp +0 -0
  197. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_transforms.cpp +0 -0
  198. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_unexpected_args.cpp +0 -0
  199. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_use.cpp +0 -0
  200. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_value.cpp +0 -0
  201. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_viewport_params.cpp +0 -0
  202. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tests/test_zip_stored.cpp +0 -0
  203. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/CMakeLists.txt +0 -0
  204. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/cli_lib.cpp +0 -0
  205. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/cli_lib.hpp +0 -0
  206. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/tools/cli/main.cpp +0 -0
  207. {openscad_cpp_evaluator-0.40.0 → openscad_cpp_evaluator-0.42.0}/uv.lock +0 -0
@@ -481,7 +481,10 @@ grep for `ponytail:`.
481
481
  lexically nested inside an already-active call," picking `childCtx()` vs `callCtx()`
482
482
  accordingly; read its doc comment before touching it, it's the second-trickiest mechanism in this
483
483
  codebase after the CSG tree stack), `evalUserModule`/`evalUserFunction`/`evalFunctionLiteral`,
484
- and `builtinChildren` (children()/children(N), deferred evaluation). `evalFunctionCall`'s
484
+ and `builtinChildren` (children()/children(N), deferred evaluation; `children(separate=true)`
485
+ additionally marks each forwarded node `separateOperand` so the enclosing
486
+ union/difference/intersection treats them as separate operands rather than one grouped one --
487
+ a BelfrySCAD extension, see `Evaluator::appendGroupSizes`). `evalFunctionCall`'s
485
488
  precedence order (checked in this exact sequence): `import` (special-cased, its own
486
489
  module/expression-context split) → `isBuiltinFunctionName` (function_builtins.hpp — always wins
487
490
  if true) → user function lookup → function-literal *value* probe → "unknown function" warning.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.40.0
3
+ Version: 0.42.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -80,14 +80,22 @@ public:
80
80
  class RangeLiteral : public Primary {
81
81
  public:
82
82
  RangeLiteral(Position position, std::unique_ptr<Expression> start, std::unique_ptr<Expression> end,
83
- std::unique_ptr<Expression> step)
83
+ std::unique_ptr<Expression> step, bool implicitStep = false)
84
84
  : Primary(NodeKind::RangeLiteral, std::move(position)), start(std::move(start)), end(std::move(end)),
85
- step(std::move(step)) {}
85
+ step(std::move(step)), implicitStep(implicitStep) {}
86
86
 
87
87
  std::unique_ptr<Expression> start;
88
88
  std::unique_ptr<Expression> end;
89
89
  std::unique_ptr<Expression> step;
90
90
 
91
+ // True when the source wrote the two-argument form `[a:b]` and the step
92
+ // node below is the literal 1.0 this parser synthesized for it. Consumers
93
+ // that only need the value can ignore this and read `step` as always
94
+ // present; it exists because "the author did not choose a step" is not
95
+ // recoverable from the synthesized node, and the evaluator's backwards-range
96
+ // warning fires only for that case (an explicit step is taken as deliberate).
97
+ bool implicitStep = false;
98
+
91
99
  std::string toString() const override;
92
100
  void buildScope(Scope& parentScope) override;
93
101
  };
@@ -22,6 +22,10 @@ std::string NumberLiteral::toString() const {
22
22
  }
23
23
 
24
24
  std::string RangeLiteral::toString() const {
25
+ // Print back the form that was written. Emitting the synthesized step for
26
+ // a two-argument range would round-trip `[5:0]` into `[5 : 1 : 0]`, which
27
+ // reads identically but suppresses the evaluator's backwards-range warning.
28
+ if (implicitStep) return "[" + start->toString() + " : " + end->toString() + "]";
25
29
  return "[" + start->toString() + " : " + step->toString() + " : " + end->toString() + "]";
26
30
  }
27
31
 
@@ -46,11 +46,13 @@ NodePtr makeUndefinedLiteral(ParserDriver& driver, const OscadLocation& loc) {
46
46
  }
47
47
 
48
48
  NodePtr makeRangeLiteral(ParserDriver& driver, const OscadLocation& loc, NodePtr start, NodePtr end, NodePtr step) {
49
- if (!step) {
49
+ const bool implicitStep = !step;
50
+ if (implicitStep) {
50
51
  step = std::make_unique<NumberLiteral>(driver.toPosition(loc), 1.0);
51
52
  }
52
53
  return std::make_unique<RangeLiteral>(driver.toPosition(loc), nodeCast<Expression>(std::move(start)),
53
- nodeCast<Expression>(std::move(end)), nodeCast<Expression>(std::move(step)));
54
+ nodeCast<Expression>(std::move(end)), nodeCast<Expression>(std::move(step)),
55
+ implicitStep);
54
56
  }
55
57
 
56
58
  NodePtr makePositionalArgument(ParserDriver& driver, const OscadLocation& loc, NodePtr expr) {
@@ -112,6 +112,7 @@ json toJsonImpl(const ASTNode& node, bool includePos) {
112
112
  j["start"] = valueToJson(n.start.get(), includePos);
113
113
  j["end"] = valueToJson(n.end.get(), includePos);
114
114
  j["step"] = valueToJson(n.step.get(), includePos);
115
+ if (n.implicitStep) j["implicitStep"] = true;
115
116
  break;
116
117
  }
117
118
  case NodeKind::ParameterDeclaration: {
@@ -410,7 +411,8 @@ const std::unordered_map<std::string, Builder>& registry() {
410
411
  {"RangeLiteral",
411
412
  [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
412
413
  return std::make_unique<RangeLiteral>(std::move(pos), childFromJson<Expression>(j, "start"),
413
- childFromJson<Expression>(j, "end"), childFromJson<Expression>(j, "step"));
414
+ childFromJson<Expression>(j, "end"), childFromJson<Expression>(j, "step"),
415
+ j.value("implicitStep", false));
414
416
  }},
415
417
  {"ParameterDeclaration",
416
418
  [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
@@ -515,3 +515,16 @@ TEST(PrettyPrint, AssignmentRhsWithLeadingCommentAndTrailingLineComment) {
515
515
  // no other translation unit -- including this test file -- can name them.
516
516
  // Left as a known, permanent coverage ceiling rather than weakening the
517
517
  // encapsulation just to reach 100%.
518
+
519
+ // The two-argument range keeps its shape through a print/reparse cycle.
520
+ // It would be easy to always print the synthesized step, and the result
521
+ // would still be a correct program -- but `[5:0]` and `[5:1:0]` mean
522
+ // different things to the evaluator's backwards-range warning, so
523
+ // reformatting a file must not quietly convert one into the other.
524
+ TEST(PrettyPrint, ImplicitRangeStepSurvivesRoundTrip) {
525
+ auto ast = parseAst("a = [5:0];\nb = [5:1:0];\n");
526
+ const std::string printed = toOpenscad(ast);
527
+ EXPECT_NE(printed.find("[5 : 0]"), std::string::npos) << printed;
528
+ EXPECT_NE(printed.find("[5 : 1 : 0]"), std::string::npos) << printed;
529
+ expectStablePrint("a = [5:0];\nb = [5:1:0];\n");
530
+ }
@@ -219,6 +219,10 @@ struct VmFrame {
219
219
  // mirrors evalModularCall's own `&node` (csg_resolve.cpp). Only
220
220
  // meaningful when ownsModuleSplice is true.
221
221
  const oscad::ASTNode* moduleSpliceCallNode = nullptr;
222
+ // children(separate=true) on the call this frame is forwarding for.
223
+ // Only meaningful while ownsModuleSplice is true, same as the two
224
+ // fields above; reset in releaseVmFrame for the same pooling reason.
225
+ bool separateChildren = false;
222
226
 
223
227
  // Whether callStack_.back() (at push time) genuinely IS this frame's
224
228
  // own logical call, and therefore safe for a tail hop inside this
@@ -34,6 +34,16 @@ struct CSGNode {
34
34
  CSGParams params; // resolve step's plain-data output
35
35
  bool uncacheable = false; // set by a later phase (ManifoldCache, Phase 8)
36
36
 
37
+ // Set only by children(separate=true): this node begins its OWN operand
38
+ // group in an enclosing union/difference/intersection/intersection_for,
39
+ // instead of merging into the single group its enclosing statement
40
+ // would otherwise form. See Evaluator::appendGroupSizes.
41
+ //
42
+ // Deliberately absent from cacheKey()'s allowlist (manifold_cache.cpp):
43
+ // it changes the PARENT's hashed "group_sizes" param, never this node's
44
+ // own geometry, so the parent already re-keys and this node must not.
45
+ bool separateOperand = false;
46
+
37
47
  // The call site that entered this node's call chain from the top
38
48
  // level, captured at RESOLVE time -- non-owning, AST-lifetime-bound
39
49
  // like `node`. nullptr for a node resolved at top level.
@@ -73,4 +83,12 @@ struct CSGNode {
73
83
  std::optional<std::string> cachedKey;
74
84
  };
75
85
 
86
+ // Marks every node from `from` onward as starting its own operand group --
87
+ // what children(separate=true) does to the geometry it just forwarded.
88
+ // A free function, not a member, so all three splice sites (user_calls.cpp
89
+ // and bytecode_vm.cpp's two) can reach it from this header alone.
90
+ inline void markSeparateOperands(std::vector<std::unique_ptr<CSGNode>>& nodes, size_t from) {
91
+ for (size_t i = from; i < nodes.size(); ++i) nodes[i]->separateOperand = true;
92
+ }
93
+
76
94
  } // namespace oscadeval
@@ -190,6 +190,30 @@ public:
190
190
  // statement.
191
191
  size_t currentTreeFrameSize() const { return treeStack_.back().size(); }
192
192
 
193
+ // Records the group(s) one child statement contributed, given the frame
194
+ // size captured before it ran. Normally that is exactly one group, of
195
+ // however many nodes the statement pushed -- including a group of size
196
+ // ZERO for a statement that produced no geometry (a disabled `*cube()`),
197
+ // which generateCsg relies on to reset intersection() and to bail
198
+ // difference(). Only children(separate=true) produces more than one:
199
+ // each node it marked starts a fresh group, so its forwarded children
200
+ // reach the enclosing operator as separate operands.
201
+ //
202
+ // Shared by all four group builders -- resolveCsg, resolveIntersectionFor
203
+ // and the VM's Op::CsgGroupEnd (which serves both) -- because
204
+ // "group_sizes" is otherwise built twice, once per engine, and would
205
+ // drift.
206
+ void appendGroupSizes(std::vector<Value>& groupSizes, size_t before) const {
207
+ const std::vector<std::unique_ptr<CSGNode>>& frame = treeStack_.back();
208
+ size_t start = before;
209
+ for (size_t i = before + 1; i < frame.size(); ++i) {
210
+ if (!frame[i]->separateOperand) continue;
211
+ groupSizes.push_back(Value{static_cast<double>(i - start)});
212
+ start = i;
213
+ }
214
+ groupSizes.push_back(Value{static_cast<double>(frame.size() - start)});
215
+ }
216
+
193
217
  // Generate pass: walks `tree` bottom-up (children before their own
194
218
  // node), calling each node's registered GenerateFn (falling back to
195
219
  // concatenating children's bodies for a kind with none registered).
@@ -342,6 +366,11 @@ public:
342
366
  struct ChildrenForward {
343
367
  EvalContext evalCtx;
344
368
  std::vector<const oscad::ASTNode*> nodes;
369
+ // children(separate=true): hand these to the enclosing union/
370
+ // difference/intersection as SEPARATE operands rather than as one
371
+ // grouped operand. Acted on after the nodes are evaluated, by
372
+ // marking the CSGNodes they produced -- see markSeparateOperands.
373
+ bool separate = false;
345
374
  };
346
375
  std::optional<ChildrenForward> prepareChildrenForward(const CallArgs& args, EvalContext& ctx);
347
376
 
@@ -392,7 +421,12 @@ public:
392
421
  Value applyUnaryOp(oscad::NodeKind kind, const Value& v, const oscad::Position& pos);
393
422
  Value applyIndexAccess(const Value& obj, const Value& idx);
394
423
  Value applyMemberAccess(const Value& obj, const std::string& member);
395
- Value applyRange(const Value& startV, const Value& endV, const Value& stepV);
424
+ // `implicitStep` is the RangeLiteral's own flag: true when the source
425
+ // wrote `[a:b]` and the parser supplied the 1.0. It only affects whether
426
+ // the backwards-range warning fires; the resulting value is the same
427
+ // either way. `pos` locates that warning.
428
+ Value applyRange(const Value& startV, const Value& endV, const Value& stepV, bool implicitStep = false,
429
+ const oscad::Position* pos = nullptr);
396
430
 
397
431
  // Entry point for a call site resolved INSIDE compiled bytecode (the
398
432
  // CALL_FN opcode, bytecode_vm.cpp): `bound` is already fully evaluated
@@ -355,20 +355,6 @@ 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
-
372
358
  // Converts a for()/intersection_for() loop assignment's evaluated RHS into
373
359
  // the sequence of values to iterate: undef -> empty, range -> a LAZY
374
360
  // sequence (matching OscRange's own start/step/end iteration, half-open
@@ -394,8 +380,7 @@ std::string rangeDirectionWarning(bool stepPositive);
394
380
  // one with a live consumer (BelfrySCAD) that surfaced it. The count is
395
381
  // computed in closed form (not IterableValues::size()'s O(n) walk) so a
396
382
  // legitimate huge-but-under-the-limit range never pays an eager pass.
397
- IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr,
398
- const RangeDirectionFn& onWrongDirection = nullptr);
383
+ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr);
399
384
 
400
385
  // `each <body>`'s own flatten-one-level rule, shared by the AST interpreter
401
386
  // (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.40.0"
7
+ version = "0.42.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -68,7 +68,12 @@ std::optional<manifold::CrossSection> toCrossSection(const std::vector<ColoredBo
68
68
  // operand (unioned together), and each subsequent statement's bodies are
69
69
  // unioned then subtracted -- a flat evaluation would lose this grouping and
70
70
  // misbehave when e.g. BOSL2's attachable() returns multiple bodies (parent
71
- // + attached children) as one operand. Mirrors _resolve_csg/_generate_csg,
71
+ // + attached children) as one operand.
72
+ //
73
+ // One statement can contribute MORE than one group: children(separate=true)
74
+ // marks the nodes it forwards so each starts its own operand group, which is
75
+ // how `difference() children(separate=true)` subtracts children 1..n from
76
+ // child 0. See Evaluator::appendGroupSizes, which both engines share. Mirrors _resolve_csg/_generate_csg,
72
77
  // minus _attach_tri_colors' multi-color-merge provenance (ponytail: a
73
78
  // merged body always takes its first contributing child's color, matching
74
79
  // this evaluator's own pre-tri_colors baseline behavior -- revisit
@@ -101,8 +106,7 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
101
106
  for (const oscad::ASTNode* geoNode : geoNodes) {
102
107
  const size_t before = ev.currentTreeFrameSize();
103
108
  ev.evalChildren(std::vector<const oscad::ASTNode*>{geoNode}, effCtx);
104
- const size_t after = ev.currentTreeFrameSize();
105
- groupSizes.push_back(Value{static_cast<double>(after - before)});
109
+ ev.appendGroupSizes(groupSizes, before);
106
110
  }
107
111
 
108
112
  CSGParams params;
@@ -119,8 +119,7 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
119
119
  if (!bodyNodes.empty()) ev.checkDebug(*bodyNodes.front(), parentCtx, /*forced=*/false, /*exprLevel=*/true);
120
120
  const size_t before = ev.currentTreeFrameSize();
121
121
  ev.evalChildren(bodyNodes, parentCtx);
122
- const size_t after = ev.currentTreeFrameSize();
123
- groupSizes.push_back(Value{static_cast<double>(after - before)});
122
+ ev.appendGroupSizes(groupSizes, before);
124
123
  return;
125
124
  }
126
125
  const auto& assign = node.assignments[depth];
@@ -128,8 +127,6 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
128
127
  const oscad::Position* pos = &assign->position();
129
128
  IterableValues iter = expandIterable(values, [&](size_t count) {
130
129
  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);
133
130
  });
134
131
  for (const Value& val : iter) {
135
132
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
@@ -952,11 +952,7 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
952
952
  }
953
953
  if (const OscRange* r = std::get_if<OscRange>(&c)) {
954
954
  std::string out;
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
- });
955
+ const IterableValues seq = expandIterable(Value{*r});
960
956
  for (const Value& item : seq) out += encode(item);
961
957
  return out;
962
958
  }
@@ -112,7 +112,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
112
112
  {"intersection", {}},
113
113
  {"hull", {}},
114
114
  {"minkowski", {"convexity"}},
115
- {"children", {"index"}},
115
+ {"children", {"index", "separate"}},
116
116
  {"render", {"convexity"}},
117
117
  // "repair" is this port's own addition, not an upstream parameter.
118
118
  {"import",
@@ -466,7 +466,7 @@ public:
466
466
  compileExpr(*n.start, out, scope);
467
467
  compileExpr(*n.end, out, scope);
468
468
  compileExpr(*n.step, out, scope);
469
- out.push_back({Op::Range, 0, 0, &n.position()});
469
+ out.push_back({Op::Range, n.implicitStep ? 1 : 0, 0, &n.position()});
470
470
  return;
471
471
  }
472
472
  case NodeKind::PrimaryIndex: {
@@ -360,7 +360,7 @@ void pushBracketedModuleFrame(Evaluator& ev, const CompiledChunk& chunk, const o
360
360
  // because module chunks never contain tail-call opcodes); don't inherit
361
361
  // a pooled function frame's stale true here either.
362
362
  void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalContext evalCtx,
363
- std::uint64_t randsBefore, const oscad::ASTNode& callNode) {
363
+ std::uint64_t randsBefore, const oscad::ASTNode& callNode, bool separate) {
364
364
  if (ev.vmCallStack_.size() >= Evaluator::kMaxVmCallStackDepth) {
365
365
  ev.error("Recursion too deep while forwarding children()", callNode);
366
366
  }
@@ -381,6 +381,7 @@ void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalCon
381
381
  frame->ownsModuleSplice = true;
382
382
  frame->moduleRandsBefore = randsBefore;
383
383
  frame->moduleSpliceCallNode = &callNode;
384
+ frame->separateChildren = separate;
384
385
  ev.vmCallStack_.push_back(std::move(frame));
385
386
  ev.vmCallBrackets_.emplace_back(std::nullopt);
386
387
  }
@@ -505,6 +506,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
505
506
  const bool ownsModuleSplice = finished->ownsModuleSplice;
506
507
  const std::uint64_t moduleRandsBefore = finished->moduleRandsBefore;
507
508
  const oscad::ASTNode* moduleSpliceCallNode = finished->moduleSpliceCallNode;
509
+ const bool separateChildren = finished->separateChildren;
508
510
  while (!finished->ctxChain.empty()) finished->ctxChain.pop_back();
509
511
  // Module frames never fire returnHook (native evalUserModule
510
512
  // never did either -- a module call has no "return value"
@@ -514,6 +516,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
514
516
  if (isModule && ownsModuleSplice) {
515
517
  std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
516
518
  ev.treeStack_.pop_back();
519
+ if (separateChildren) markSeparateOperands(children, 0);
517
520
  ev.spliceModuleChildren(std::move(children), moduleRandsBefore, *moduleSpliceCallNode);
518
521
  }
519
522
  if (isFloorFrame) {
@@ -634,7 +637,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
634
637
  f.stack.pop_back();
635
638
  Value start = std::move(f.stack.back());
636
639
  f.stack.pop_back();
637
- f.stack.push_back(ev.applyRange(start, end, step));
640
+ f.stack.push_back(ev.applyRange(start, end, step, ins.a != 0, ins.pos));
638
641
  ++f.pc;
639
642
  break;
640
643
  }
@@ -740,8 +743,6 @@ Value driveVm(Evaluator& ev, size_t floor) {
740
743
  il.values = expandIterable(v, [&](size_t count) {
741
744
  ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")",
742
745
  ins.pos);
743
- }, [&](bool stepPositive) {
744
- ev.warn(rangeDirectionWarning(stepPositive), ins.pos);
745
746
  });
746
747
  il.index = 0;
747
748
  il.total = il.values.size();
@@ -1025,6 +1026,11 @@ Value driveVm(Evaluator& ev, size_t floor) {
1025
1026
  static_cast<const oscad::ModularCall*>(f.chunk->nativeStatements[static_cast<size_t>(ins.a)]);
1026
1027
  EvalContext scopedCtx = ctx.withScope(callNode->scope() ? callNode->scope() : ctx.scope);
1027
1028
  ev.checkDebug(*callNode, scopedCtx);
1029
+ // Same order as evalModularCall's own (csg_resolve.cpp):
1030
+ // warn, then resolve. Without this a children() typo
1031
+ // warns only under the interpreter, so no test for that
1032
+ // warning could run under both engines.
1033
+ warnUnexpectedBuiltinArgs(ev, *callNode);
1028
1034
  const std::uint64_t randsBefore = ev.randsCallCount();
1029
1035
  auto [args, effCtx] = resolveCallArgs(ev, callNode->arguments, scopedCtx);
1030
1036
  std::optional<Evaluator::ChildrenForward> fwd = ev.prepareChildrenForward(args, effCtx);
@@ -1041,9 +1047,11 @@ Value driveVm(Evaluator& ev, size_t floor) {
1041
1047
  const CompiledChunk* chunk = (ev.useBytecodeVm() && ev.inResolvePass())
1042
1048
  ? ev.lookupOrCompileChildrenListChunk(fwd->nodes)
1043
1049
  : nullptr;
1050
+ // Read before fwd->evalCtx is moved from.
1051
+ const bool separate = fwd->separate;
1044
1052
  if (chunk) {
1045
1053
  ev.treeStack_.emplace_back();
1046
- pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode);
1054
+ pushChildrenForwardFrame(ev, *chunk, std::move(fwd->evalCtx), randsBefore, *callNode, separate);
1047
1055
  // f.pc deliberately NOT advanced -- resumes when
1048
1056
  // the pushed frame completes; driveVm's completion
1049
1057
  // branch runs the splice (isModule &&
@@ -1067,6 +1075,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1067
1075
  }
1068
1076
  std::vector<std::unique_ptr<CSGNode>> children = std::move(ev.treeStack_.back());
1069
1077
  ev.treeStack_.pop_back();
1078
+ if (separate) markSeparateOperands(children, 0);
1070
1079
  ev.spliceModuleChildren(std::move(children), randsBefore, *callNode);
1071
1080
  ++f.pc;
1072
1081
  }
@@ -1306,8 +1315,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
1306
1315
  }
1307
1316
  case Op::CsgGroupEnd: {
1308
1317
  PendingCsgWrap& pending = f.csgWrapStack.back();
1309
- const size_t after = ev.treeStack_.back().size();
1310
- pending.groupSizes.push_back(Value{static_cast<double>(after - pending.groupStartSize)});
1318
+ ev.appendGroupSizes(pending.groupSizes, pending.groupStartSize);
1311
1319
  ++f.pc;
1312
1320
  break;
1313
1321
  }
@@ -1466,8 +1474,6 @@ Value driveVm(Evaluator& ev, size_t floor) {
1466
1474
  il.values = expandIterable(v, [&](size_t count) {
1467
1475
  ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")",
1468
1476
  ins.pos);
1469
- }, [&](bool stepPositive) {
1470
- ev.warn(rangeDirectionWarning(stepPositive), ins.pos);
1471
1477
  });
1472
1478
  il.index = 0;
1473
1479
  il.total = il.values.size();
@@ -158,7 +158,12 @@ void Evaluator::spliceModuleChildren(std::vector<std::unique_ptr<CSGNode>> child
158
158
  // landed on.
159
159
  for (auto& c : children) c->uncacheable = true;
160
160
  }
161
- if (children.size() > 1) {
161
+ // children(separate=true) marked these to start their own operand
162
+ // groups, and the group walk only inspects the enclosing frame's top
163
+ // level -- so the wrapper would hide the marks. Splice instead.
164
+ const bool anySeparate =
165
+ std::any_of(children.begin(), children.end(), [](const auto& c) { return c->separateOperand; });
166
+ if (children.size() > 1 && !anySeparate) {
162
167
  auto unionNode = std::make_unique<CSGNode>();
163
168
  unionNode->kind = "union";
164
169
  unionNode->node = &callNode;
@@ -177,8 +177,6 @@ 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);
182
180
  });
183
181
  for (const Value& val : iter) {
184
182
  EvalContext childCtx = parentCtx.letChildCtx();
@@ -294,16 +292,38 @@ Value Evaluator::evalListLiteral(const oscad::ListComprehension& node, EvalConte
294
292
  }
295
293
 
296
294
  Value Evaluator::evalRangeLiteral(const oscad::RangeLiteral& node, EvalContext& ctx) {
297
- return applyRange(evalExpr(*node.start, ctx), evalExpr(*node.end, ctx), evalExpr(*node.step, ctx));
295
+ return applyRange(evalExpr(*node.start, ctx), evalExpr(*node.end, ctx), evalExpr(*node.step, ctx),
296
+ node.implicitStep, &node.position());
298
297
  }
299
298
 
300
299
  // Shared with the bytecode VM's RANGE opcode -- see applyBinaryOp's own
301
300
  // comment on why these are factored out as plain Value x Value x Value ->
302
301
  // Value functions.
303
- Value Evaluator::applyRange(const Value& startV, const Value& endV, const Value& stepV) {
302
+ Value Evaluator::applyRange(const Value& startV, const Value& endV, const Value& stepV, bool implicitStep,
303
+ const oscad::Position* pos) {
304
304
  double start = std::holds_alternative<std::monostate>(startV) ? 0.0 : toDoubleLenient(startV);
305
305
  double end = std::holds_alternative<std::monostate>(endV) ? 0.0 : toDoubleLenient(endV);
306
306
  double step = std::holds_alternative<std::monostate>(stepV) ? 1.0 : toDoubleLenient(stepV);
307
+ // A range whose begin is already past its end iterates zero times, which
308
+ // is almost always a typo -- [5:0] where [5:-1:0] was meant. Reported
309
+ // here, at construction, because that is where the reference reports it:
310
+ // `r = [5:0];` warns even if nothing ever iterates r.
311
+ //
312
+ // ponytail: only an IMPLICIT step is checked -- a deliberate divergence
313
+ // from the reference, which warns for [5:1:0] and [0:-1:5] as well.
314
+ // Writing the step out is taken as a statement of intent; the warning is
315
+ // for the author who wrote [5:0] meaning [5:-1:0] and got a silent empty
316
+ // loop. An implicit step is always exactly 1, so the reference's other
317
+ // wording ("begin is smaller than the end, but step is negative") cannot
318
+ // arise here and no longer exists in this port -- if it is ever needed
319
+ // again, this gate has been widened back to explicit steps.
320
+ //
321
+ // The epsilon matches the one the iteration path used before this check
322
+ // moved here, so a range built from float arithmetic that lands a hair
323
+ // past its end is still treated as empty-but-fine rather than a typo.
324
+ if (implicitStep && (start - end) > 1e-10) {
325
+ warn("begin is greater than the end, but step is positive", pos);
326
+ }
307
327
  return Value{OscRange{start, step, end}};
308
328
  }
309
329
 
@@ -112,8 +112,6 @@ 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);
117
115
  });
118
116
  for (const Value& val : iter) {
119
117
  EvalContext childCtx = parentCtx.childCtx(nullptr, std::nullopt, ctx.childrenNodes, ctx.childrenCallerCtx);
@@ -179,6 +179,7 @@ void Evaluator::releaseVmFrame(std::unique_ptr<VmFrame> frame) {
179
179
  frame->ownsModuleSplice = false;
180
180
  frame->moduleRandsBefore = 0;
181
181
  frame->moduleSpliceCallNode = nullptr;
182
+ frame->separateChildren = false;
182
183
  vmFramePool_.push_back(std::move(frame));
183
184
  }
184
185
 
@@ -932,6 +933,11 @@ Value Evaluator::parentModuleName(int idx) const {
932
933
 
933
934
  std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(const CallArgs& args, EvalContext& ctx) {
934
935
  Value idxArg = getArg(args, 0, "index", Value{});
936
+ // Positional slot 1 is accepted as well as the name: adding "separate"
937
+ // to the builtin's parameter list already suppresses the "Too many
938
+ // unnamed arguments" warning for children(0, true), so reading it
939
+ // named-only would silently ignore an argument the author wrote.
940
+ const bool separate = truthy(getArg(args, 1, "separate", Value{false}));
935
941
  if (!ctx.childrenNodes || ctx.childrenNodes->empty()) return std::nullopt;
936
942
  const EvalContext* callerCtx = ctx.childrenCallerCtx;
937
943
  if (!callerCtx) return std::nullopt;
@@ -977,7 +983,7 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
977
983
  }
978
984
 
979
985
  if (std::holds_alternative<std::monostate>(idxArg)) {
980
- return ChildrenForward{std::move(evalCtx), *ctx.childrenNodes};
986
+ return ChildrenForward{std::move(evalCtx), *ctx.childrenNodes, separate};
981
987
  }
982
988
 
983
989
  // children(N) indexes child *statements*, not output bodies -- a
@@ -1006,9 +1012,7 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
1006
1012
  if (std::holds_alternative<double>(idxArg)) {
1007
1013
  indexValues.push_back(idxArg);
1008
1014
  } 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
- });
1015
+ const IterableValues iter = expandIterable(idxArg);
1012
1016
  for (const Value& v : iter) indexValues.push_back(v);
1013
1017
  } else {
1014
1018
  warn("Bad parameter type (" + fmtValue(idxArg) +
@@ -1043,13 +1047,15 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
1043
1047
  picked.push_back(geoNodes[static_cast<size_t>(idx)]);
1044
1048
  }
1045
1049
  if (picked.empty()) return std::nullopt;
1046
- return ChildrenForward{std::move(evalCtx), std::move(picked)};
1050
+ return ChildrenForward{std::move(evalCtx), std::move(picked), separate};
1047
1051
  }
1048
1052
 
1049
1053
  void Evaluator::builtinChildren(const CallArgs& args, EvalContext& ctx) {
1050
1054
  std::optional<ChildrenForward> fwd = prepareChildrenForward(args, ctx);
1051
1055
  if (!fwd) return;
1056
+ const size_t before = currentTreeFrameSize();
1052
1057
  evalChildren(fwd->nodes, fwd->evalCtx);
1058
+ if (fwd->separate) markSeparateOperands(treeStack_.back(), before);
1053
1059
  }
1054
1060
 
1055
1061
  } // namespace oscadeval
@@ -490,30 +490,18 @@ 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
- 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) {
493
+ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany) {
500
494
  if (std::holds_alternative<std::monostate>(v)) return IterableValues{};
501
495
  if (const OscRange* r = std::get_if<OscRange>(&v)) {
502
496
  if (const std::optional<size_t> count = rangeElementCount(*r); count && *count >= 1'000'000) {
503
497
  if (onTooMany) onTooMany(*count);
504
498
  return IterableValues{};
505
499
  }
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
+ // A step pointing away from the end yields nothing, and that is
501
+ // almost always a typo -- but the warning for it belongs to the
502
+ // range's CONSTRUCTION, not its iteration: only the range literal
503
+ // knows whether the author chose the step or the parser supplied
504
+ // it. See Evaluator::applyRange.
517
505
  // Lazy -- IterableValues itself reproduces this exact
518
506
  // termination condition (a zero step is naturally empty: neither
519
507
  // `x <= end` nor `x >= end` branch ever fires for it) without
@@ -20,6 +20,7 @@ add_executable(oscad_eval_tests
20
20
  test_transforms.cpp
21
21
  test_render_expr.cpp
22
22
  test_booleans.cpp
23
+ test_children_separate.cpp
23
24
  test_control_flow.cpp
24
25
  test_tail_calls.cpp
25
26
  test_function_builtins.cpp