openscad-cpp-evaluator 1.41.0__tar.gz → 1.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 (233) hide show
  1. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/bytecode.hpp +14 -46
  3. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/evaluator.hpp +26 -0
  4. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/value.hpp +3 -1
  5. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/control.cpp +1 -0
  7. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/function_builtins.cpp +116 -62
  8. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/bytecode_compiler.cpp +38 -80
  9. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/bytecode_vm.cpp +9 -28
  10. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/eval_context.cpp +2 -1
  11. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/expr_eval.cpp +51 -25
  12. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/stmt_eval.cpp +36 -15
  13. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/text/freetype_font_provider.cpp +18 -3
  14. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/user_calls.cpp +38 -29
  15. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/value.cpp +94 -39
  16. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_bytecode_compiler.cpp +10 -7
  17. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_control_flow.cpp +10 -3
  18. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_eval_context.cpp +2 -1
  19. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_expr_eval.cpp +3 -6
  20. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_openscad_parity.cpp +113 -0
  21. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/uv.lock +1 -1
  22. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/.github/workflows/ci.yml +0 -0
  23. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/.github/workflows/wheels.yml +0 -0
  24. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/.gitignore +0 -0
  25. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/.gitmodules +0 -0
  26. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/CLAUDE.md +0 -0
  27. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/LICENSE +0 -0
  29. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/README.md +0 -0
  30. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/bindings/CMakeLists.txt +0 -0
  31. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/bindings/ast_to_py.cpp +0 -0
  32. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/bindings/module.cpp +0 -0
  33. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/cmake/embed_font.cmake +0 -0
  34. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/cmake/patch_manifold.cmake +0 -0
  35. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/examples/CMakeLists.txt +0 -0
  36. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/examples/eval_perf_benchmark.cpp +0 -0
  37. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/examples/manifold_cache_reuse.cpp +0 -0
  38. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/examples/minimal_debugger.cpp +0 -0
  39. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  40. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/.gitignore +0 -0
  41. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  42. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  43. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/README.md +0 -0
  44. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  45. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  46. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  47. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  48. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  49. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  50. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  51. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  52. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  53. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  54. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  55. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  56. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  57. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  58. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  59. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  60. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  61. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  62. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  63. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  64. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  65. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  66. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  67. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  68. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  69. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  70. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  71. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  72. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  73. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  74. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  75. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  76. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  77. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  78. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  79. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  80. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  81. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  82. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  83. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  84. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  85. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  86. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  87. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  88. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  89. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  90. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  91. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  92. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  93. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  94. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  95. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  96. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  97. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  98. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  99. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  100. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
  101. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  102. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  103. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  104. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  105. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  106. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  107. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  108. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  109. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  110. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  111. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  112. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  113. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  114. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  115. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  116. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  117. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  118. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  119. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  120. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  121. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  122. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  123. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  124. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  125. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  126. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  127. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  128. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  129. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  130. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  131. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  132. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  133. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  134. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  135. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  136. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  137. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  138. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  139. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  140. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  141. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  142. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  143. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  144. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  145. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/resources/fonts/LICENSE +0 -0
  146. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  147. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  148. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  149. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  150. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/CMakeLists.txt +0 -0
  151. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/booleans.cpp +0 -0
  152. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/builtins.hpp +0 -0
  153. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/call_args.cpp +0 -0
  154. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/color.cpp +0 -0
  155. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/dxf_dim.cpp +0 -0
  156. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/extrude.cpp +0 -0
  157. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/import.cpp +0 -0
  158. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/modifiers.cpp +0 -0
  159. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/primitives_2d.cpp +0 -0
  160. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/primitives_3d.cpp +0 -0
  161. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/profile_time.cpp +0 -0
  162. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/registry.cpp +0 -0
  163. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/roof.cpp +0 -0
  164. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/segments.cpp +0 -0
  165. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/surface.cpp +0 -0
  166. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/text.cpp +0 -0
  167. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/topology.cpp +0 -0
  168. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/builtins/transforms.cpp +0 -0
  169. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/colored_body.cpp +0 -0
  170. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/coverage.cpp +0 -0
  171. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/csg_generate.cpp +0 -0
  172. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/csg_resolve.cpp +0 -0
  173. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/css_colors.cpp +0 -0
  174. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/debug/debug_repl.cpp +0 -0
  175. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/debug_profile.cpp +0 -0
  176. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/eval_error.cpp +0 -0
  177. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/eval_use.cpp +0 -0
  178. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/export.cpp +0 -0
  179. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/format_closure.cpp +0 -0
  180. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/import/dxf_import.cpp +0 -0
  181. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/import/mesh_import.cpp +0 -0
  182. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/import/scene_import.cpp +0 -0
  183. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/import/surface_load.cpp +0 -0
  184. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/import/svg_import.cpp +0 -0
  185. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/manifold_cache.cpp +0 -0
  186. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/measure_geometry.cpp +0 -0
  187. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/mesh_check.cpp +0 -0
  188. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/text/font_match.cpp +0 -0
  189. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/text/text_metrics.cpp +0 -0
  190. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/utf8.cpp +0 -0
  191. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/xkcd_colors.cpp +0 -0
  192. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/xkcd_colors.hpp +0 -0
  193. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/src/zip_stored.cpp +0 -0
  194. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/CMakeLists.txt +0 -0
  195. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_booleans.cpp +0 -0
  196. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_children_separate.cpp +0 -0
  197. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_cli.cpp +0 -0
  198. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_coverage.cpp +0 -0
  199. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_csg_tree.cpp +0 -0
  200. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_debug_hooks.cpp +0 -0
  201. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_discretizer.cpp +0 -0
  202. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_dxf_svg_import.cpp +0 -0
  203. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_export.cpp +0 -0
  204. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_primitives.cpp +0 -0
  214. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_profile_time.cpp +0 -0
  215. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_profiling.cpp +0 -0
  216. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_python_bindings.py +0 -0
  217. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_render_expr.cpp +0 -0
  218. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_scope_trail.cpp +0 -0
  219. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_scoping.cpp +0 -0
  220. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_surface.cpp +0 -0
  221. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_tail_calls.cpp +0 -0
  222. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_text.cpp +0 -0
  223. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_transforms.cpp +0 -0
  224. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_undef_args.cpp +0 -0
  225. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_unexpected_args.cpp +0 -0
  226. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_use.cpp +0 -0
  227. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_value.cpp +0 -0
  228. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_viewport_params.cpp +0 -0
  229. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tests/test_zip_stored.cpp +0 -0
  230. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tools/cli/CMakeLists.txt +0 -0
  231. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tools/cli/cli_lib.cpp +0 -0
  232. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tools/cli/cli_lib.hpp +0 -0
  233. {openscad_cpp_evaluator-1.41.0 → openscad_cpp_evaluator-1.42.0}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.41.0
3
+ Version: 1.42.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -43,6 +43,7 @@ enum class Op {
43
43
  StoreLocal, // a = slot index (pops)
44
44
  LoadDyn, // a = name pool index ($-prefixed)
45
45
  StoreDyn, // a = name pool index (pops; also marks dynExplicit)
46
+ WarnDuplicateLet, // a = name pool index (pops; warns, binds nothing -- see Evaluator::warnDuplicateLet)
46
47
  LoadFree, // a = name pool index -- Evaluator::evalIdentifier fallback (warnIfUndef=true)
47
48
  // Same as LoadFree, but Evaluator::evalIdentifier's warnIfUndef=false --
48
49
  // only ever emitted for a PrimaryCall's own callee probe (bytecode_
@@ -240,20 +241,6 @@ enum class Op {
240
241
  // there's no branch to emit here).
241
242
  Echo,
242
243
 
243
- // Only ever emitted for a non-empty-argument AssertOp (see
244
- // bytecode_compiler.cpp's own AssertOp case -- a zero-argument
245
- // assert() is unconditionally true and compiles straight through with
246
- // no check at all). Reached only on the condition-false path (guarded
247
- // by a JumpIfTrue the compiler emits around it, mirroring LogicalOrOp's
248
- // own jump-then-patch shape) -- always throws via Evaluator::error(),
249
- // i.e. [[noreturn]] at runtime. a = 1 if a message Value was compiled
250
- // and pushed (pop it), else 0. b = constant-pool index of condText
251
- // (the condition expression's own source text, interned ONCE at
252
- // compile time via its toString() -- cheaper than the interpreter,
253
- // which recomputes this on every failing call). pos/node: same
254
- // Evaluator::error() TRACE-walk need CheckIterLimit already has.
255
- AssertFail,
256
-
257
244
  // -- Module-body compilation (Stage 2) --------------------------------
258
245
  // A call to a user module resolved to a specific ModuleDeclaration AT
259
246
  // COMPILE TIME (see CompiledChunk::ModuleCallSite) -- the module-side
@@ -717,16 +704,10 @@ enum class Op {
717
704
  StoreLetVar,
718
705
 
719
706
  // a = index into CompiledChunk::assertSites. Pops site.argCount values
720
- // (all of this statement's own arguments, already compiled+pushed in
721
- // source order via compileExpr -- EVERY one, not just condition/
722
- // message, mirrors evalAssertStatement's own eager resolveArgs() over
723
- // AssertOp's lazy-message contract) and replicates
724
- // Evaluator::evalAssertStatement exactly: named "condition"/"message"
725
- // win over positional 0/1 (resolved at COMPILE time into
726
- // AssertSite::conditionArgIndex/messageArgIndex -- the argument SHAPE
727
- // is static, only values vary per call), throws via Evaluator::error()
728
- // on failure, otherwise runs any chained node.children natively (rare;
729
- // not worth its own compiled path -- see AssertSite's own doc comment).
707
+ // (every argument of an assert() statement or expression, pushed in
708
+ // source order) and hands them to Evaluator::checkAssert; a statement
709
+ // then runs any chained children natively (rare; not worth its own
710
+ // compiled path).
730
711
  AssertStatement,
731
712
  // a = nativeStatements index of the AST node whose arm this is. Emitted
732
713
  // only when the Evaluator was built with coverage on (compile-time
@@ -763,6 +744,10 @@ struct Instruction {
763
744
  // applyDefaults()'s existing sibling-isolated defaultCtx; empty if that
764
745
  // parameter has no default. `bodyCode` is the function's own expression body.
765
746
  struct CompiledChunk {
747
+ // String literals whose text holds an escape OpenSCAD calls undefined,
748
+ // with how many: the Evaluator warns for them once, when the chunk is
749
+ // compiled (it has the warning channel; the compiler does not).
750
+ std::vector<std::pair<const oscad::StringLiteral*, int>> undefinedEscapeLiterals;
766
751
  struct Param {
767
752
  std::string name;
768
753
  int slot = 0; // unused when isDyn is true -- $-params are never slot-addressed
@@ -1036,30 +1021,13 @@ struct CompiledChunk {
1036
1021
  const oscad::ASTNode* node = nullptr;
1037
1022
  };
1038
1023
 
1039
- // One Op::AssertStatement site -- see that op's own doc comment for
1040
- // the full contract. `conditionArgIndex`/`messageArgIndex` are indices
1041
- // into the site's own argCount-sized popped-argument array (source
1042
- // order), resolved at COMPILE time via the same "named wins over
1043
- // positional 0/1" priority Evaluator::getArg applies at runtime --
1044
- // nullopt means "no argument supplies this logical parameter" (mirrors
1045
- // getArg's own defaultValue fallback: absent condition reads as
1046
- // `Value{true}`, absent message means no ": ..." suffix).
1047
- // `condTextConstIdx`: constants[] index of the condition argument's own
1048
- // source text (or "false" when no condition argument exists, matching
1049
- // evalAssertStatement's `node.arguments.empty() ? "false" : ...`),
1050
- // interned once at compile time. `node`: for both Evaluator::error()'s
1051
- // TRACE walk and the (rare) chained-children fallback below.
1052
- //
1053
- // ponytail: a script with TWO named args of the SAME name (e.g.
1054
- // `assert(condition=true, condition=false)`) would resolve to the
1055
- // LAST one here, not getArg's own first-match; not worth the extra
1056
- // bookkeeping to fix for input that's already nonsensical.
1024
+ // One Op::AssertStatement site. `statement` is null for the expression
1025
+ // form, which has no children.
1057
1026
  struct AssertSite {
1058
1027
  int argCount = 0;
1059
- std::optional<int> conditionArgIndex;
1060
- std::optional<int> messageArgIndex;
1061
- int condTextConstIdx = -1;
1062
- const oscad::ModularAssert* node = nullptr;
1028
+ const std::vector<std::unique_ptr<oscad::Argument>>* arguments = nullptr;
1029
+ const oscad::ASTNode* node = nullptr;
1030
+ const oscad::ModularAssert* statement = nullptr;
1063
1031
  };
1064
1032
 
1065
1033
  // Every distinct chunk this SPECIFIC CompiledChunk owns is either a
@@ -2,6 +2,8 @@
2
2
 
3
3
  #include "openscad_cpp_evaluator/bound_args.hpp"
4
4
  #include "openscad_cpp_evaluator/bytecode.hpp"
5
+
6
+ #include <unordered_set>
5
7
  #include "openscad_cpp_evaluator/bytecode_vm.hpp"
6
8
  #include "openscad_cpp_evaluator/call_args.hpp"
7
9
  #include "openscad_cpp_evaluator/csg_node.hpp"
@@ -557,6 +559,30 @@ public:
557
559
  // Public: builtins/import.cpp's not-manifold warning is emitted from a
558
560
  // free function, same reasoning as tagGenerated()/builtinChildren().
559
561
  void warn(const std::string& message, const oscad::Position* position);
562
+ // OpenSCAD's lexer warns "Undefined escape sequence" once per bad escape
563
+ // in the source. This warns for a literal the first time it is
564
+ // evaluated or compiled, and never again for that literal.
565
+ void warnUndefinedEscapes(const oscad::StringLiteral& lit, int count);
566
+ // let() keeps the FIRST binding of a repeated name: OpenSCAD evaluates
567
+ // the later right-hand side, warns with its value, and discards it.
568
+ // OpenSCAD's Assert::performAssert, given every argument's value in
569
+ // source order -- it evaluates them all, pass or fail, so a passing
570
+ // `assert(true, nosuch)` still warns. Warns about unexpected arguments;
571
+ // throws via error() when the condition is false or missing (`assert()`
572
+ // fails). The message prints as echo() would print it.
573
+ void checkAssert(const std::vector<std::unique_ptr<oscad::Argument>>& arguments, const std::vector<Value>& values,
574
+ const oscad::ASTNode& node);
575
+ void warnDuplicateLet(const std::string& name, const Value& v, const oscad::Position* position);
576
+ template <typename Assignments>
577
+ static bool repeatsEarlierLetName(const Assignments& assignments, size_t i) {
578
+ for (size_t j = 0; j < i; ++j)
579
+ if (assignments[j]->name->name == assignments[i]->name->name) return true;
580
+ return false;
581
+ }
582
+ std::unordered_set<const oscad::StringLiteral*> undefinedEscapesWarned_;
583
+ void warnUndefinedEscapes(const CompiledChunk& chunk) {
584
+ for (const auto& [lit, n] : chunk.undefinedEscapeLiterals) warnUndefinedEscapes(*lit, n);
585
+ }
560
586
  // Hands one already-formatted warning line to echoFn_, recording it in
561
587
  // warnCapture first when one is active. Every warning path goes through
562
588
  // here, including a ManifoldCache hit replaying a cached line.
@@ -306,7 +306,9 @@ std::string formatNumber(double v);
306
306
  //
307
307
  // A trailing lone backslash is kept as itself. It can only occur in an
308
308
  // unterminated literal, which is a parse error long before this runs.
309
- std::string unescapeStringLiteral(const std::string& raw);
309
+ // `undefinedEscapes`, when given, counts the escapes OpenSCAD's lexer calls
310
+ // undefined (it warns once for each).
311
+ std::string unescapeStringLiteral(const std::string& raw, int* undefinedEscapes = nullptr);
310
312
 
311
313
  // echo()/str()/assert-message display format: "undef" | "true"/"false" |
312
314
  // "[start : step : end]" (range) | formatNumber() (number) | "[e1, e2, ...]"
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.41.0"
7
+ version = "1.42.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -118,6 +118,7 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
118
118
  // needs `i` visible in `j`'s own range expression).
119
119
  std::function<void(size_t, EvalContext&)> recurse = [&](size_t depth, EvalContext& parentCtx) {
120
120
  if (depth == node.assignments.size()) {
121
+ if (depth == 0) return; // for() with no loop variables iterates zero times in OpenSCAD, not once.
121
122
  // One body-entry marker per full cartesian-product iteration
122
123
  // and nothing per individual variable binding (unlike
123
124
  // evalFor) -- mirrors _resolve_intersection_for's single
@@ -246,23 +246,6 @@ Value builtinPow(double a, double b) {
246
246
 
247
247
  // -- cross/rands/search/lookup --------------------------------------------
248
248
 
249
- Value builtinCross(const Value& aArg, const Value& bArg) {
250
- const auto a = allNumericList(aArg);
251
- const auto b = allNumericList(bArg);
252
- if (!a || !b) return Value{};
253
- for (double x : *a) {
254
- if (!std::isfinite(x)) return Value{};
255
- }
256
- for (double x : *b) {
257
- if (!std::isfinite(x)) return Value{};
258
- }
259
- if (a->size() == 2 && b->size() == 2) return Value{(*a)[0] * (*b)[1] - (*a)[1] * (*b)[0]};
260
- if (a->size() == 3 && b->size() == 3) {
261
- return numList({(*a)[1] * (*b)[2] - (*a)[2] * (*b)[1], (*a)[2] * (*b)[0] - (*a)[0] * (*b)[2],
262
- (*a)[0] * (*b)[1] - (*a)[1] * (*b)[0]});
263
- }
264
- return Value{};
265
- }
266
249
 
267
250
  // -- linear_solve(A, b) --------------------------------------------------
268
251
  //
@@ -784,36 +767,49 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
784
767
  return Value{};
785
768
  }
786
769
 
787
- Value builtinLookup(const CallArgs& args) {
788
- // Guarded here rather than via scalarNumericArity() -- the second
789
- // argument is a table, so the all-arguments-are-numbers rule can't
790
- // apply. Without this, toDoubleLenient() turns a non-numeric key into
791
- // 0.0 and the lookup silently returns the table's first value.
770
+ // upstream builtin_lookup, line for line: a non-finite key warns; the
771
+ // first row must be exactly two numbers or the answer is undef; later rows
772
+ // that aren't are skipped; the low/high candidates are picked in table
773
+ // order (the first of equal keys wins), then interpolated. This used to
774
+ // sort the table, coerce malformed rows, and accept inf/nan keys silently.
775
+ Value builtinLookup(Evaluator& ev, const CallArgs& args, const oscad::Position* pos) {
792
776
  const Value keyArg = getArg(args, 0, "key", Value{});
793
- if (!std::holds_alternative<double>(keyArg)) return Value{};
794
- const double key = std::get<double>(keyArg);
795
- const Value tableArg = getArg(args, 1, "table", Value{});
796
- const ListPtr* tablePtr = std::get_if<ListPtr>(&tableArg);
797
- if (!tablePtr || !*tablePtr) return Value{};
798
-
799
- std::vector<std::pair<double, double>> pairs;
800
- for (const Value& row : (*tablePtr)->items) {
801
- const ListPtr* rowPtr = std::get_if<ListPtr>(&row);
802
- if (!rowPtr || !*rowPtr || (*rowPtr)->items.size() < 2) continue;
803
- pairs.emplace_back(toDoubleLenient((*rowPtr)->items[0]), toDoubleLenient((*rowPtr)->items[1]));
777
+ const double p = std::get<double>(keyArg); // checkBuiltinArgs: a number
778
+ if (!std::isfinite(p)) {
779
+ ev.warn("lookup(" + fmtValue(keyArg) + ", ...) first argument is not a number", pos);
780
+ return Value{};
804
781
  }
805
- if (pairs.empty()) return Value{};
806
- std::stable_sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
807
- if (key <= pairs.front().first) return Value{pairs.front().second};
808
- if (key >= pairs.back().first) return Value{pairs.back().second};
809
- for (size_t i = 0; i + 1 < pairs.size(); ++i) {
810
- const double k0 = pairs[i].first, k1 = pairs[i + 1].first;
811
- if (k0 <= key && key <= k1) {
812
- const double t = (k1 == k0) ? 0.0 : (key - k0) / (k1 - k0);
813
- return Value{pairs[i].second + t * (pairs[i + 1].second - pairs[i].second)};
782
+ const auto& vec = std::get<ListPtr>(getArg(args, 1, "table", Value{}))->items; // checkBuiltinArgs: a vector
783
+ const auto vec2 = [](const Value& v, double& a, double& b) {
784
+ const ListPtr* l = std::get_if<ListPtr>(&v);
785
+ if (!l || !*l || (*l)->items.size() != 2) return false;
786
+ const double* x = std::get_if<double>(&(*l)->items[0]);
787
+ const double* y = std::get_if<double>(&(*l)->items[1]);
788
+ if (!x || !y) return false;
789
+ a = *x;
790
+ b = *y;
791
+ return true;
792
+ };
793
+ double lowP, lowV, highP, highV;
794
+ if (vec.empty() || !vec2(vec[0], lowP, lowV)) return Value{};
795
+ highP = lowP;
796
+ highV = lowV;
797
+ for (size_t k = 1; k < vec.size(); ++k) {
798
+ double thisP, thisV;
799
+ if (!vec2(vec[k], thisP, thisV)) continue;
800
+ if (thisP <= p && (thisP > lowP || lowP > p)) {
801
+ lowP = thisP;
802
+ lowV = thisV;
803
+ }
804
+ if (thisP >= p && (thisP < highP || highP < p)) {
805
+ highP = thisP;
806
+ highV = thisV;
814
807
  }
815
808
  }
816
- return Value{0.0};
809
+ if (p <= lowP) return Value{highV};
810
+ if (p >= highP) return Value{lowV};
811
+ const double f = (p - lowP) / (highP - lowP);
812
+ return Value{highV * f + lowV * (1 - f)};
817
813
  }
818
814
 
819
815
  std::string asStringOr(const Value& v, const std::string& fallback) {
@@ -1155,12 +1151,14 @@ const std::unordered_map<int, BuiltinCheck>& builtinChecks() {
1155
1151
  std::unordered_map<int, BuiltinCheck> m;
1156
1152
  const auto add = [&m](BuiltinFnId id, BuiltinCheck c) { m.emplace(static_cast<int>(id), std::move(c)); };
1157
1153
  for (BuiltinFnId id : {BuiltinFnId::Abs, BuiltinFnId::Sign, BuiltinFnId::Ceil, BuiltinFnId::Floor,
1158
- BuiltinFnId::Round, BuiltinFnId::Sqrt, BuiltinFnId::Ln, BuiltinFnId::Log,
1154
+ BuiltinFnId::Round, BuiltinFnId::Sqrt, BuiltinFnId::Ln,
1159
1155
  BuiltinFnId::Exp, BuiltinFnId::Sin, BuiltinFnId::Cos, BuiltinFnId::Tan,
1160
1156
  BuiltinFnId::Asin, BuiltinFnId::Acos, BuiltinFnId::Atan}) {
1161
1157
  add(id, {1, 1, "1", {kNum}});
1162
1158
  }
1163
1159
  add(BuiltinFnId::Atan2, {2, 2, "2", {kNum, kNum}});
1160
+ // log(x) or log(base, x); upstream's count warning names 2.
1161
+ add(BuiltinFnId::Log, {1, 2, "2", {kNum, kNum}});
1164
1162
  add(BuiltinFnId::Pow, {2, 2, "2", {kNum, kNum}});
1165
1163
  add(BuiltinFnId::Cross, {2, 2, "2", {kVec, kVec}});
1166
1164
  add(BuiltinFnId::Lookup, {2, 2, "2", {kNum, kVec}});
@@ -1275,6 +1273,9 @@ BuiltinFnId builtinFnIdFor(const std::string& name) {
1275
1273
  return it == ids.end() ? BuiltinFnId::None : it->second;
1276
1274
  }
1277
1275
 
1276
+ Value evalBuiltinFunctionInOrder(Evaluator& ev, BuiltinFnId id, const std::string& name, const CallArgs& args,
1277
+ const oscad::ASTNode& node);
1278
+
1278
1279
  Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs& args, const oscad::ASTNode& node) {
1279
1280
  return evalBuiltinFunctionResolved(ev, builtinFnIdFor(name), builtinParamNames(name), name, args, node);
1280
1281
  }
@@ -1297,6 +1298,36 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1297
1298
  }
1298
1299
  }
1299
1300
 
1301
+ // Every other builtin function reads its arguments IN ORDER and ignores
1302
+ // their names, exactly as upstream's builtin_* functions index
1303
+ // `arguments[i]`: sin(a=90) is sin(90), pow(y=3, x=2) is 3^2, and
1304
+ // rands(0, 1, 2, seed_value=5) is seeded. Names used to bind by this
1305
+ // port's own spelling and drop the rest silently -- sin(a=90) was 0,
1306
+ // concat(a=[1], b=[2]) was [], rands(seed_value=) went unseeded.
1307
+ // (textmetrics/fontmetrics -- with declaredParams -- and dxf_dim/
1308
+ // dxf_cross parse names upstream, through Parameters::parse.)
1309
+ const bool byName = declaredParams || id == BuiltinFnId::DxfDim || id == BuiltinFnId::DxfCross;
1310
+ CallArgs ordered;
1311
+ if (!byName && node.kind() == oscad::NodeKind::PrimaryCall && !args.named.empty()) {
1312
+ int k = 0, out = 0;
1313
+ for (const auto& a : static_cast<const oscad::PrimaryCall&>(node).arguments) {
1314
+ const Value* v = nullptr;
1315
+ if (a->kind() == oscad::NodeKind::PositionalArgument) {
1316
+ v = args.findPositional(k++);
1317
+ } else {
1318
+ const std::string& n = static_cast<const oscad::NamedArgument&>(*a).name->name;
1319
+ if (isConfigVariable(n)) continue; // a $-override, not an argument
1320
+ v = args.findNamed(n);
1321
+ }
1322
+ ordered.setPositional(out++, v ? *v : Value{});
1323
+ }
1324
+ }
1325
+ const CallArgs& argsInOrder = (!byName && !args.named.empty()) ? ordered : args;
1326
+ return evalBuiltinFunctionInOrder(ev, id, name, argsInOrder, node);
1327
+ }
1328
+
1329
+ Value evalBuiltinFunctionInOrder(Evaluator& ev, BuiltinFnId id, const std::string& name, const CallArgs& args,
1330
+ const oscad::ASTNode& node) {
1300
1331
  // Arity and argument types, with the reference's own two diagnostics.
1301
1332
  // This replaced a silent version of the same gate (scalarNumericArity /
1302
1333
  // numericOnlyNames), which returned undef without ever saying why.
@@ -1321,8 +1352,10 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1321
1352
  }
1322
1353
  case BuiltinFnId::Round: {
1323
1354
  const double x = toDoubleLenient(getArg(args, 0, "x", Value{}));
1324
- if (std::isnan(x) || std::isinf(x)) return Value{x};
1325
- return Value{x >= 0 ? std::floor(x + 0.5) : std::ceil(x - 0.5)};
1355
+ // std::round, as upstream: halves away from zero, exactly. The
1356
+ // floor(x + 0.5) this was rounded 0.49999999999999994 up to 1
1357
+ // (the sum rounds to 1.0) and broke integers above 2^52.
1358
+ return Value{std::round(x)};
1326
1359
  }
1327
1360
  case BuiltinFnId::Sqrt: {
1328
1361
  const double x = toDoubleLenient(getArg(args, 0, "x", Value{}));
@@ -1334,9 +1367,13 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1334
1367
  return Value{x < 0 ? std::numeric_limits<double>::quiet_NaN() : std::log(x)};
1335
1368
  }
1336
1369
  case BuiltinFnId::Log: {
1337
- const double x = toDoubleLenient(getArg(args, 0, "x", Value{}));
1338
- if (x == 0) return Value{-std::numeric_limits<double>::infinity()};
1339
- return Value{x < 0 ? std::numeric_limits<double>::quiet_NaN() : std::log10(x)};
1370
+ // upstream builtin_log: log(y) / log(base), base 10 by default --
1371
+ // that formula, not log10(), so results agree to the last bit.
1372
+ // The two-argument form was missing (log(2, 8) was undef).
1373
+ const bool twoArgs = positionalCount(args) == 2;
1374
+ const double base = twoArgs ? toDoubleLenient(getArg(args, 0, "base", Value{})) : 10.0;
1375
+ const double y = toDoubleLenient(getArg(args, twoArgs ? 1 : 0, "x", Value{}));
1376
+ return Value{std::log(y) / std::log(base)};
1340
1377
  }
1341
1378
  case BuiltinFnId::Exp: return Value{std::exp(toDoubleLenient(getArg(args, 0, "x", Value{})))};
1342
1379
  case BuiltinFnId::Sin: return Value{sinDegrees(toDoubleLenient(getArg(args, 0, "x", Value{})))};
@@ -1371,25 +1408,42 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1371
1408
  return Value{std::sqrt(sum)};
1372
1409
  }
1373
1410
  case BuiltinFnId::Cross: {
1411
+ // upstream builtin_cross, check for check: two 2-vectors are
1412
+ // multiplied as they are (a non-number counts as 0, inf and nan
1413
+ // propagate -- no checks at all); otherwise both must be
1414
+ // 3-vectors, and each element pair is checked in order for a
1415
+ // non-number, then NaN, then infinity, each with its own warning.
1374
1416
  const Value a = getArg(args, 0, "a", Value{});
1375
1417
  const Value b = getArg(args, 1, "b", Value{});
1376
- // Two distinct messages, and the size check runs first: a
1377
- // 4-element operand is a size complaint even when it also holds
1378
- // a string.
1379
- const auto sizeOf = [](const Value& v) {
1380
- const ListPtr* l = std::get_if<ListPtr>(&v);
1381
- return (l && *l) ? (*l)->items.size() : size_t{0};
1418
+ const auto& va = std::get<ListPtr>(a)->items;
1419
+ const auto& vb = std::get<ListPtr>(b)->items;
1420
+ const auto num = [](const Value& v) {
1421
+ const double* d = std::get_if<double>(&v);
1422
+ return d ? *d : 0.0;
1382
1423
  };
1383
- const size_t na = sizeOf(a), nb = sizeOf(b);
1384
- if (na != nb || (na != 2 && na != 3)) {
1424
+ if (va.size() == 2 && vb.size() == 2) return Value{num(va[0]) * num(vb[1]) - num(va[1]) * num(vb[0])};
1425
+ if (va.size() != 3 || vb.size() != 3) {
1385
1426
  ev.warn("Invalid vector size of parameter for cross()", &node.position());
1386
1427
  return Value{};
1387
1428
  }
1388
- if (!allNumericList(a) || !allNumericList(b)) {
1389
- ev.warn("Invalid value in parameter vector for cross()", &node.position());
1390
- return Value{};
1429
+ for (size_t k = 0; k < 3; ++k) {
1430
+ if (!std::holds_alternative<double>(va[k]) || !std::holds_alternative<double>(vb[k])) {
1431
+ ev.warn("Invalid value in parameter vector for cross()", &node.position());
1432
+ return Value{};
1433
+ }
1434
+ const double d0 = std::get<double>(va[k]), d1 = std::get<double>(vb[k]);
1435
+ if (std::isnan(d0) || std::isnan(d1)) {
1436
+ ev.warn("Invalid value (NaN) in parameter vector for cross()", &node.position());
1437
+ return Value{};
1438
+ }
1439
+ if (std::isinf(d0) || std::isinf(d1)) {
1440
+ ev.warn("Invalid value (INF) in parameter vector for cross()", &node.position());
1441
+ return Value{};
1442
+ }
1391
1443
  }
1392
- return builtinCross(a, b);
1444
+ return numList({num(va[1]) * num(vb[2]) - num(va[2]) * num(vb[1]),
1445
+ num(va[2]) * num(vb[0]) - num(va[0]) * num(vb[2]),
1446
+ num(va[0]) * num(vb[1]) - num(va[1]) * num(vb[0])});
1393
1447
  }
1394
1448
  case BuiltinFnId::Rands: {
1395
1449
  ev.noteRandsCall();
@@ -1493,7 +1547,7 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1493
1547
  return Value{std::holds_alternative<ObjectPtr>(x) && std::get<ObjectPtr>(x) != nullptr};
1494
1548
  }
1495
1549
  case BuiltinFnId::Search: return builtinSearch(args, ev, &node.position());
1496
- case BuiltinFnId::Lookup: return builtinLookup(args);
1550
+ case BuiltinFnId::Lookup: return builtinLookup(ev, args, &node.position());
1497
1551
  case BuiltinFnId::HasKey: {
1498
1552
  const Value objArg = getArg(args, 0, "object", Value{});
1499
1553
  const ObjectPtr* obj = std::get_if<ObjectPtr>(&objArg);
@@ -404,13 +404,14 @@ public:
404
404
  case NodeKind::BooleanLiteral:
405
405
  out.push_back({Op::PushBool, static_cast<const oscad::BooleanLiteral&>(node).val ? 1 : 0, 0, nullptr});
406
406
  return;
407
- case NodeKind::StringLiteral:
408
- out.push_back({Op::PushConst,
409
- internConst(Value{unescapeStringLiteral(
410
- static_cast<const oscad::StringLiteral&>(node).val)}),
411
- 0,
412
- nullptr});
407
+ case NodeKind::StringLiteral: {
408
+ const auto& lit = static_cast<const oscad::StringLiteral&>(node);
409
+ int undefinedEscapes = 0;
410
+ Value v{unescapeStringLiteral(lit.val, &undefinedEscapes)};
411
+ if (undefinedEscapes) chunk_.undefinedEscapeLiterals.emplace_back(&lit, undefinedEscapes);
412
+ out.push_back({Op::PushConst, internConst(std::move(v)), 0, nullptr});
413
413
  return;
414
+ }
414
415
  case NodeKind::UndefinedLiteral:
415
416
  out.push_back({Op::PushConst, internConst(Value{}), 0, nullptr});
416
417
  return;
@@ -797,7 +798,7 @@ public:
797
798
  for (size_t i = 0; i < n.assignments.size(); ++i) {
798
799
  const auto& assign = n.assignments[i];
799
800
  const std::string& name = assign->name->name;
800
- if (name.empty() || name[0] == '$') continue;
801
+ if (name.empty() || name[0] == '$' || Evaluator::repeatsEarlierLetName(n.assignments, i)) continue;
801
802
  if (auto candidates = collectLetrecCandidateLiterals(*assign->expr)) {
802
803
  letrecSlot[i] = declareLocal(scope, name);
803
804
  letrecCandidates[i] = std::move(*candidates);
@@ -860,7 +861,9 @@ public:
860
861
  }
861
862
  }
862
863
  }
863
- if (!name.empty() && name[0] == '$') {
864
+ if (Evaluator::repeatsEarlierLetName(n.assignments, i)) {
865
+ out.push_back({Op::WarnDuplicateLet, internName(name), 0, &n.position()});
866
+ } else if (!name.empty() && name[0] == '$') {
864
867
  out.push_back({Op::StoreDyn, internName(name), 0, &assign->position()});
865
868
  } else {
866
869
  int slot = selfBinding ? preDeclaredSlot : declareLocal(scope, name);
@@ -910,37 +913,17 @@ public:
910
913
  }
911
914
 
912
915
  case NodeKind::AssertOp: {
916
+ // Every argument is evaluated, pass or fail, as OpenSCAD
917
+ // does -- see Evaluator::checkAssert.
913
918
  auto& n = static_cast<const oscad::AssertOp&>(node);
914
- if (n.arguments.empty()) {
915
- // Mirrors evalAssertExpr's own `raw.empty() || ...`
916
- // short-circuit exactly: a zero-argument assert() is
917
- // unconditionally true, so the check can never fail --
918
- // skip it entirely rather than compiling a check that
919
- // can never trigger.
920
- compileExpr(*n.body, out, scope, tail);
921
- return;
922
- }
923
- compileExpr(*argExpr(*n.arguments[0]), out, scope); // condition, never tail
924
- size_t jumpPassed = out.size();
925
- out.push_back({Op::JumpIfTrue, 0, 0, nullptr});
926
- const bool hasMessage = n.arguments.size() > 1;
927
- // Lazily compiled: only reachable on the condition-false
928
- // path (guarded by the JumpIfTrue above) -- mirrors
929
- // evalAssertExpr's own lazy evaluation of the message
930
- // argument exactly (only evaluated when the assertion
931
- // actually fails). Slot resolution is unaffected by which
932
- // runtime path reaches this code -- CompileScope is purely
933
- // a compile-time name/index table, same as every other
934
- // conditionally-executed branch this compiler already
935
- // handles (Ternary, LogicalAnd/Or, ListCompIf).
936
- if (hasMessage) compileExpr(*argExpr(*n.arguments[1]), out, scope); // message, never tail
937
- // Precomputed ONCE at compile time (cheaper than the
938
- // interpreter, which recomputes this on every failing
939
- // call) -- toString() is a pure, ctx-free AST-to-text
940
- // operation.
941
- int condTextIdx = internConst(Value{argExpr(*n.arguments[0])->toString()});
942
- out.push_back({Op::AssertFail, hasMessage ? 1 : 0, condTextIdx, &n.position(), &n});
943
- out[jumpPassed].a = static_cast<int>(out.size());
919
+ CompiledChunk::AssertSite site;
920
+ site.node = &n;
921
+ site.arguments = &n.arguments;
922
+ site.argCount = static_cast<int>(n.arguments.size());
923
+ for (const auto& arg : n.arguments) compileExpr(*argExpr(*arg), out, scope); // never tail
924
+ int siteIdx = static_cast<int>(chunk_.assertSites.size());
925
+ chunk_.assertSites.push_back(std::move(site));
926
+ out.push_back({Op::AssertStatement, siteIdx, 0, &n.position()});
944
927
  compileExpr(*n.body, out, scope, tail);
945
928
  return;
946
929
  }
@@ -1202,10 +1185,13 @@ public:
1202
1185
  size_t placeholderIdx = out.size();
1203
1186
  out.push_back({Op::OpenLocalScope, 0, 0, nullptr});
1204
1187
  int slotStart = nextSlot_;
1205
- for (const auto& assign : n.assignments) {
1188
+ for (size_t i = 0; i < n.assignments.size(); ++i) {
1189
+ const auto& assign = n.assignments[i];
1206
1190
  compileExpr(*assign->expr, out, scope);
1207
1191
  const std::string& name = assign->name->name;
1208
- if (!name.empty() && name[0] == '$') {
1192
+ if (Evaluator::repeatsEarlierLetName(n.assignments, i)) {
1193
+ out.push_back({Op::WarnDuplicateLet, internName(name), 0, &n.position()});
1194
+ } else if (!name.empty() && name[0] == '$') {
1209
1195
  out.push_back({Op::StoreDyn, internName(name), 0, &assign->position()});
1210
1196
  } else {
1211
1197
  int slot = declareLocal(scope, name);
@@ -1488,6 +1474,7 @@ public:
1488
1474
  const size_t numDims = n.assignments.size();
1489
1475
  std::function<void(size_t)> emitDim = [&](size_t d) {
1490
1476
  if (d == numDims) {
1477
+ if (d == 0) return; // for() with no loop variables iterates zero times in OpenSCAD, not once.
1491
1478
  if (!n.body.empty()) {
1492
1479
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(n.body.front().get()), 0, nullptr});
1493
1480
  }
@@ -1612,50 +1599,16 @@ public:
1612
1599
  return;
1613
1600
  }
1614
1601
  case NodeKind::ModularAssert: {
1615
- // Genuinely different contract from AssertOp's own compiled
1616
- // form (Op::AssertFail): the statement form supports named
1617
- // arguments AND evaluates every argument EAGERLY (mirrors
1618
- // Evaluator::evalAssertStatement's own resolveArgs() call
1619
- // exactly -- unlike AssertOp's lazily-compiled message,
1620
- // guarded by a runtime JumpIfTrue), so every argument is
1621
- // compiled+pushed here unconditionally, in source order.
1602
+ // Like AssertOp's compiled form, plus chained children.
1622
1603
  auto& n = static_cast<const oscad::ModularAssert&>(stmt);
1623
1604
  out.push_back({Op::CheckDebugStatement, internNativeStatement(&stmt), 0, nullptr});
1624
1605
  CompiledChunk::AssertSite site;
1625
1606
  site.node = &n;
1607
+ site.statement = &n;
1608
+ site.arguments = &n.arguments;
1626
1609
  site.argCount = static_cast<int>(n.arguments.size());
1627
1610
  CompileScope exprScope;
1628
- for (size_t i = 0; i < n.arguments.size(); ++i) {
1629
- compileIsolatedExpr(*argExpr(*n.arguments[i]), out, exprScope);
1630
- if (n.arguments[i]->kind() == NodeKind::NamedArgument) {
1631
- auto& na = static_cast<const oscad::NamedArgument&>(*n.arguments[i]);
1632
- if (na.name->name == "condition") site.conditionArgIndex = static_cast<int>(i);
1633
- else if (na.name->name == "message") site.messageArgIndex = static_cast<int>(i);
1634
- }
1635
- }
1636
- // Second pass: positional 0/1 fill whichever logical
1637
- // parameter a named arg didn't already claim -- run AFTER
1638
- // the named pass above (not interleaved) so a named arg
1639
- // wins regardless of its position relative to its
1640
- // positional counterpart in source, matching
1641
- // Evaluator::getArg's own "named first" priority exactly.
1642
- int posCounter = 0;
1643
- for (size_t i = 0; i < n.arguments.size(); ++i) {
1644
- if (n.arguments[i]->kind() == NodeKind::NamedArgument) continue;
1645
- if (posCounter == 0 && !site.conditionArgIndex) site.conditionArgIndex = static_cast<int>(i);
1646
- else if (posCounter == 1 && !site.messageArgIndex) site.messageArgIndex = static_cast<int>(i);
1647
- ++posCounter;
1648
- }
1649
- // Precomputed once, like AssertFail's own condText --
1650
- // mirrors evalAssertStatement's `node.arguments.empty() ?
1651
- // "false" : argExpr(*node.arguments[0])->toString()`
1652
- // exactly (a MISSING condition arg -- e.g. a bare
1653
- // `assert(message="x");` -- reads the same as an EMPTY
1654
- // arg list here: no arg supplies "condition" either way).
1655
- site.condTextConstIdx = internConst(Value{
1656
- site.conditionArgIndex
1657
- ? argExpr(*n.arguments[static_cast<size_t>(*site.conditionArgIndex)])->toString()
1658
- : std::string("false")});
1611
+ for (const auto& arg : n.arguments) compileIsolatedExpr(*argExpr(*arg), out, exprScope);
1659
1612
  int siteIdx = static_cast<int>(chunk_.assertSites.size());
1660
1613
  chunk_.assertSites.push_back(std::move(site));
1661
1614
  out.push_back({Op::AssertStatement, siteIdx, 0, &n.position()});
@@ -1683,10 +1636,14 @@ public:
1683
1636
  auto& n = static_cast<const oscad::ModularLet&>(stmt);
1684
1637
  CompileScope exprScope;
1685
1638
  out.push_back({Op::OpenLetScope, 0, 0, nullptr});
1686
- for (const auto& assign : n.assignments) {
1639
+ for (size_t i = 0; i < n.assignments.size(); ++i) {
1640
+ const auto& assign = n.assignments[i];
1687
1641
  out.push_back({Op::CheckDebugStatement, internNativeStatement(assign.get()), 0, nullptr});
1688
1642
  compileIsolatedExpr(*assign->expr, out, exprScope);
1689
- out.push_back({Op::StoreLetVar, internName(assign->name->name), 0, &assign->position()});
1643
+ if (Evaluator::repeatsEarlierLetName(n.assignments, i))
1644
+ out.push_back({Op::WarnDuplicateLet, internName(assign->name->name), 0, &n.position()});
1645
+ else
1646
+ out.push_back({Op::StoreLetVar, internName(assign->name->name), 0, &assign->position()});
1690
1647
  }
1691
1648
  compileStatementList(n.children, out);
1692
1649
  out.push_back({Op::CloseExprScope, 0, 0, nullptr});
@@ -1897,6 +1854,7 @@ public:
1897
1854
  const size_t numDims = n.assignments.size();
1898
1855
  std::function<void(size_t)> emitDim = [&](size_t d) {
1899
1856
  if (d == numDims) {
1857
+ if (d == 0) return; // for() with no loop variables iterates zero times in OpenSCAD, not once.
1900
1858
  const oscad::ASTNode* marker = n.body.empty() ? static_cast<const oscad::ASTNode*>(&n) : n.body.front().get();
1901
1859
  out.push_back({Op::NativeCheckDebugExprLevel, internNativeStatement(marker), 0, nullptr});
1902
1860
  compileStatementList(n.body, out);