openscad-cpp-evaluator 0.4.3__tar.gz → 0.4.5__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 (194) hide show
  1. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/scope_trail.hpp +50 -2
  3. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/value.hpp +24 -1
  4. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/pyproject.toml +1 -1
  5. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/control.cpp +4 -1
  6. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/bytecode_vm.cpp +3 -1
  7. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/expr_eval.cpp +4 -1
  8. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/stmt_eval.cpp +4 -1
  9. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/value.cpp +17 -1
  10. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/CMakeLists.txt +1 -0
  11. openscad_cpp_evaluator-0.4.5/tests/test_scope_trail.cpp +79 -0
  12. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_value.cpp +63 -6
  13. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/.github/workflows/ci.yml +0 -0
  14. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/.github/workflows/wheels.yml +0 -0
  15. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/.gitignore +0 -0
  16. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/.gitmodules +0 -0
  17. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/CLAUDE.md +0 -0
  18. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/LICENSE +0 -0
  20. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/README.md +0 -0
  21. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/bindings/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/bindings/module.cpp +0 -0
  23. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/cmake/embed_font.cmake +0 -0
  24. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/examples/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/examples/eval_perf_benchmark.cpp +0 -0
  26. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/examples/manifold_cache_reuse.cpp +0 -0
  27. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/examples/minimal_debugger.cpp +0 -0
  28. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  29. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/.gitignore +0 -0
  30. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  31. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/README.md +0 -0
  33. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  34. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  35. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  36. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  37. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  38. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  39. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  40. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  41. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  42. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  43. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  44. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  45. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  46. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  73. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  74. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  75. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  76. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  77. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  78. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  79. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  80. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  81. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  82. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  83. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  84. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  85. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  86. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  87. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  88. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  89. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  90. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  91. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  92. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  93. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  94. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  95. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  96. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  97. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  98. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  100. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  101. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  102. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  103. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  104. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  105. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  106. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  107. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  108. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  109. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  110. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/export.hpp +0 -0
  111. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  113. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  114. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  115. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  116. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  117. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  118. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  119. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  121. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  122. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  123. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/python/openscad_cpp_evaluator/__init__.py +0 -0
  124. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/resources/fonts/LICENSE +0 -0
  125. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  126. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/CMakeLists.txt +0 -0
  127. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/booleans.cpp +0 -0
  128. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/builtins.hpp +0 -0
  129. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/call_args.cpp +0 -0
  130. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/color.cpp +0 -0
  131. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/extrude.cpp +0 -0
  132. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/function_builtins.cpp +0 -0
  133. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/import.cpp +0 -0
  134. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/modifiers.cpp +0 -0
  135. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/primitives_2d.cpp +0 -0
  136. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/primitives_3d.cpp +0 -0
  137. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/registry.cpp +0 -0
  138. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/roof.cpp +0 -0
  139. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/segments.cpp +0 -0
  140. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/surface.cpp +0 -0
  141. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/text.cpp +0 -0
  142. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/topology.cpp +0 -0
  143. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/builtins/transforms.cpp +0 -0
  144. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/bytecode_compiler.cpp +0 -0
  145. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/colored_body.cpp +0 -0
  146. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/csg_generate.cpp +0 -0
  147. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/csg_resolve.cpp +0 -0
  148. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/css_colors.cpp +0 -0
  149. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/debug/debug_repl.cpp +0 -0
  150. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/debug_profile.cpp +0 -0
  151. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/eval_context.cpp +0 -0
  152. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/eval_error.cpp +0 -0
  153. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/eval_use.cpp +0 -0
  154. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/export.cpp +0 -0
  155. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/import/dxf_import.cpp +0 -0
  156. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/import/mesh_import.cpp +0 -0
  157. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/import/surface_load.cpp +0 -0
  158. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/import/svg_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/manifold_cache.cpp +0 -0
  160. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/text/stb_font_provider.cpp +0 -0
  161. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/text/text_metrics.cpp +0 -0
  162. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/user_calls.cpp +0 -0
  163. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/src/zip_stored.cpp +0 -0
  164. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_booleans.cpp +0 -0
  165. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_bytecode_compiler.cpp +0 -0
  166. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_cli.cpp +0 -0
  167. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_control_flow.cpp +0 -0
  168. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_csg_tree.cpp +0 -0
  169. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_debug_hooks.cpp +0 -0
  170. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_dxf_svg_import.cpp +0 -0
  171. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_eval_context.cpp +0 -0
  172. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_export.cpp +0 -0
  173. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_expr_eval.cpp +0 -0
  174. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_extrude_roof.cpp +0 -0
  175. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_function_builtins.cpp +0 -0
  176. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_helpers.hpp +0 -0
  177. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_import_export.cpp +0 -0
  178. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_multi_color_merge.cpp +0 -0
  180. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_primitives.cpp +0 -0
  181. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_profiling.cpp +0 -0
  182. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_python_bindings.py +0 -0
  183. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_scoping.cpp +0 -0
  184. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_surface.cpp +0 -0
  185. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_tail_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_text.cpp +0 -0
  187. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_transforms.cpp +0 -0
  188. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_use.cpp +0 -0
  189. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_viewport_params.cpp +0 -0
  190. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tests/test_zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tools/cli/CMakeLists.txt +0 -0
  192. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tools/cli/cli_lib.cpp +0 -0
  193. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tools/cli/cli_lib.hpp +0 -0
  194. {openscad_cpp_evaluator-0.4.3 → openscad_cpp_evaluator-0.4.5}/tools/cli/main.cpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -55,7 +55,25 @@ public:
55
55
  }
56
56
 
57
57
  void set(const std::string& name, T value, int level) {
58
- stacks_[name].push_back(Entry{std::move(value), level});
58
+ std::vector<Entry>& vec = stacks_[name];
59
+ // A rebind of the SAME name at the level that's already current
60
+ // (a for-loop variable set every iteration without opening a new
61
+ // level each time, e.g. C-style for's init/incr -- see
62
+ // expr_eval.cpp's ListCompCFor case) overwrites the existing
63
+ // top-of-stack entry in place instead of pushing another one: a
64
+ // 999,999-iteration loop used to accumulate 999,999 Entry objects
65
+ // (and 999,999 duplicate name copies in dirty_[level]) that were
66
+ // never read again, all held until the level closed and popLevel()
67
+ // walked and popped every one of them in a single O(N) sweep.
68
+ // Nothing ever reads a stale same-level entry once a newer one at
69
+ // that same level exists (a level is one scope instantiation,
70
+ // never reopened), so this is a pure win, not just a memory
71
+ // optimization: it also collapses that O(N) unwind to O(1).
72
+ if (!vec.empty() && vec.back().level == level) {
73
+ vec.back().value = std::move(value);
74
+ return;
75
+ }
76
+ vec.push_back(Entry{std::move(value), level});
59
77
  dirty_[level].push_back(name);
60
78
  }
61
79
 
@@ -117,6 +135,17 @@ public:
117
135
 
118
136
  bool has(const std::string& name, int myLevel) const { return lookup(name, myLevel) != nullptr; }
119
137
 
138
+ // Test-only introspection: how many physical Entry objects exist for
139
+ // `name` (across every level, not just the current one) -- lets
140
+ // ScopeTrailStorageTest assert the same-level-overwrite optimization
141
+ // actually bounds growth (a for-loop variable set N times at one level
142
+ // should hold ~1 entry, not N), not just that lookup() still returns
143
+ // the right value either way.
144
+ size_t debugEntryCountForTesting(const std::string& name) const {
145
+ auto it = stacks_.find(name);
146
+ return it == stacks_.end() ? 0 : it->second.size();
147
+ }
148
+
120
149
  // Ancestry-filtered snapshot -- O(total distinct names ever bound so
121
150
  // far in the whole evaluation), not O(names visible now). Only used
122
151
  // by debug-REPL/children()-splice enumeration (never a hot path); see
@@ -258,7 +287,20 @@ public:
258
287
  void set(const std::string& name, T value, int level) {
259
288
  int id = intern_->idFor(name);
260
289
  ensureSize(id);
261
- stacks_[static_cast<size_t>(id)].push_back(Entry{std::move(value), level});
290
+ std::vector<Entry>& vec = stacks_[static_cast<size_t>(id)];
291
+ // Same same-level-overwrite optimization as ScopeTrailStorage::set()
292
+ // -- see that one's doc comment for the full rationale. Applies
293
+ // here too: e.g. `$fn = 1; $fn = 2;` as sibling top-level
294
+ // statements share one level (evalChildren's per-statement
295
+ // EvalContext only swaps `scope`, aliasing the same trail/level --
296
+ // see EvalContext::withScope's own doc comment), so repeated
297
+ // same-block reassignment of a $-variable used to accumulate
298
+ // entries here exactly like a for-loop's plain variable did.
299
+ if (!vec.empty() && vec.back().level == level) {
300
+ vec.back().value = std::move(value);
301
+ return;
302
+ }
303
+ vec.push_back(Entry{std::move(value), level});
262
304
  dirty_[level].push_back(id);
263
305
  }
264
306
 
@@ -299,6 +341,12 @@ public:
299
341
 
300
342
  bool has(const std::string& name, int myLevel) const { return lookup(name, myLevel) != nullptr; }
301
343
 
344
+ // See ScopeTrailStorage::debugEntryCountForTesting's own doc comment.
345
+ size_t debugEntryCountForTesting(const std::string& name) const {
346
+ int id = intern_->idFor(name);
347
+ return id >= 0 && static_cast<size_t>(id) < stacks_.size() ? stacks_[static_cast<size_t>(id)].size() : 0;
348
+ }
349
+
302
350
  std::vector<std::pair<std::string, T>> items(int myLevel) const {
303
351
  std::vector<std::pair<std::string, T>> result;
304
352
  for (size_t id = 0; id < stacks_.size(); ++id) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "openscad_cpp_evaluator/osc_range.hpp"
4
4
 
5
+ #include <functional>
5
6
  #include <memory>
6
7
  #include <optional>
7
8
  #include <string>
@@ -265,6 +266,13 @@ private:
265
266
  mutable std::optional<Cursor> cursor_;
266
267
  };
267
268
 
269
+ // Called (count = the range's own requested element count) when a range
270
+ // would be rejected for having too many elements -- see expandIterable's
271
+ // own doc comment. The caller's own warn(message, position) already knows
272
+ // how to format/emit a WARNING; this just hands back the number to put in
273
+ // it, keeping expandIterable() itself free of any Evaluator/echo coupling.
274
+ using RangeTooManyFn = std::function<void(size_t count)>;
275
+
268
276
  // Converts a for()/intersection_for() loop assignment's evaluated RHS into
269
277
  // the sequence of values to iterate: undef -> empty, range -> a LAZY
270
278
  // sequence (matching OscRange's own start/step/end iteration, half-open
@@ -275,7 +283,22 @@ private:
275
283
  // IterableValues), anything else (a bare scalar) -> a single-element
276
284
  // list. Mirrors the shared expansion logic duplicated across the
277
285
  // reference's _eval_for/_resolve_intersection_for.
278
- IterableValues expandIterable(const Value& v);
286
+ //
287
+ // A range whose own element count would be >= 1,000,000 is rejected
288
+ // entirely (onTooMany called, empty result returned) rather than iterated
289
+ // -- verified empirically against real OpenSCAD.app: a range producing
290
+ // exactly 999,999 elements iterates fine; exactly 1,000,000 emits
291
+ // "WARNING: Bad range parameter in for statement: too many elements (N)"
292
+ // and contributes zero iterations (not a truncation to 999,999, not an
293
+ // error like the C-style for's own _MAX_CFOR_ITERATIONS limit -- a
294
+ // different mechanism entirely, checked per-range, not against a
295
+ // cartesian-product total across a multi-variable for()). The Python
296
+ // reference (openscad_evaluator) has no equivalent check at all -- this
297
+ // was a real gap in both ports, closed here first since this port is the
298
+ // one with a live consumer (BelfrySCAD) that surfaced it. The count is
299
+ // computed in closed form (not IterableValues::size()'s O(n) walk) so a
300
+ // legitimate huge-but-under-the-limit range never pays an eager pass.
301
+ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany = nullptr);
279
302
 
280
303
  // `each <body>`'s own flatten-one-level rule, shared by the AST interpreter
281
304
  // (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.4.3"
7
+ version = "0.4.5"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -101,7 +101,10 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
101
101
  varSeqs.reserve(node.assignments.size());
102
102
  for (const auto& assign : node.assignments) {
103
103
  Value values = ev.evalExpr(*assign->expr, ctx);
104
- varSeqs.emplace_back(assign->name->name, expandIterable(values));
104
+ const oscad::Position* pos = &assign->position();
105
+ varSeqs.emplace_back(assign->name->name, expandIterable(values, [&](size_t count) {
106
+ ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
107
+ }));
105
108
  }
106
109
 
107
110
  std::vector<const oscad::ASTNode*> bodyNodes;
@@ -240,7 +240,9 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
240
240
  Value v = std::move(stack.back());
241
241
  stack.pop_back();
242
242
  IterList& il = iterLists[static_cast<size_t>(ins.a)];
243
- il.values = expandIterable(v);
243
+ il.values = expandIterable(v, [&](size_t count) {
244
+ ev.warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", ins.pos);
245
+ });
244
246
  il.index = 0;
245
247
  ++pc;
246
248
  break;
@@ -136,7 +136,10 @@ void Evaluator::evalListElement(const oscad::ASTNode& elem, EvalContext& ctx, st
136
136
  pairs.reserve(n.assignments.size());
137
137
  for (const auto& assign : n.assignments) {
138
138
  Value values = evalExpr(*assign->expr, ctx);
139
- pairs.push_back(Pair{assign->name->name, expandIterable(values)});
139
+ const oscad::Position* pos = &assign->position();
140
+ pairs.push_back(Pair{assign->name->name, expandIterable(values, [&](size_t count) {
141
+ warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
142
+ })});
140
143
  }
141
144
  const bool isNestedLc = (n.body->kind() == oscad::NodeKind::ListComprehension);
142
145
 
@@ -79,7 +79,10 @@ void Evaluator::evalFor(const oscad::ModularFor& node, EvalContext& ctx) {
79
79
  pairs.reserve(node.assignments.size());
80
80
  for (const auto& assign : node.assignments) {
81
81
  Value values = evalExpr(*assign->expr, ctx);
82
- pairs.push_back(AssignPair{assign->name->name, expandIterable(values)});
82
+ const oscad::Position* pos = &assign->position();
83
+ pairs.push_back(AssignPair{assign->name->name, expandIterable(values, [&](size_t count) {
84
+ warn("Bad range parameter in for statement: too many elements (" + std::to_string(count) + ")", pos);
85
+ })});
83
86
  }
84
87
 
85
88
  std::vector<const oscad::ASTNode*> bodyNodes;
@@ -328,9 +328,25 @@ std::string fmtValue(const Value& v) {
328
328
  return "<function-literal>"; // const FunctionLiteral* -- no meaningful textual form in the reference either
329
329
  }
330
330
 
331
- IterableValues expandIterable(const Value& v) {
331
+ // (Range::numValues()'s own count, closed-form) -- 1 + floor((end-start)/step),
332
+ // the same epsilon IterableValues::inRange uses so this agrees exactly with
333
+ // how many elements a lazy walk of the same range would actually produce.
334
+ // nullopt for a step of 0 or a direction that disagrees with start/end (a
335
+ // naturally-empty range -- 0 elements, never "too many").
336
+ std::optional<size_t> rangeElementCount(const OscRange& r) {
337
+ if (r.step == 0.0) return std::nullopt;
338
+ const double n = (r.end - r.start) / r.step;
339
+ if (n < -1e-10) return std::nullopt; // wrong direction -- naturally empty
340
+ return static_cast<size_t>(std::floor(n + 1e-10)) + 1;
341
+ }
342
+
343
+ IterableValues expandIterable(const Value& v, const RangeTooManyFn& onTooMany) {
332
344
  if (std::holds_alternative<std::monostate>(v)) return IterableValues{};
333
345
  if (const OscRange* r = std::get_if<OscRange>(&v)) {
346
+ if (const std::optional<size_t> count = rangeElementCount(*r); count && *count >= 1'000'000) {
347
+ if (onTooMany) onTooMany(*count);
348
+ return IterableValues{};
349
+ }
334
350
  // Lazy -- IterableValues itself reproduces this exact
335
351
  // termination condition (a zero step is naturally empty: neither
336
352
  // `x <= end` nor `x >= end` branch ever fires for it) without
@@ -10,6 +10,7 @@ FetchContent_MakeAvailable(googletest)
10
10
  add_executable(oscad_eval_tests
11
11
  test_value.cpp
12
12
  test_eval_context.cpp
13
+ test_scope_trail.cpp
13
14
  test_scoping.cpp
14
15
  test_bytecode_compiler.cpp
15
16
  test_expr_eval.cpp
@@ -0,0 +1,79 @@
1
+ #include "openscad_cpp_evaluator/scope_trail.hpp"
2
+
3
+ #include <gtest/gtest.h>
4
+
5
+ namespace oscadeval {
6
+ namespace {
7
+
8
+ TEST(ScopeTrailStorage, SetAtSameLevelOverwritesLatestValue) {
9
+ ScopeTrailStorage<int> storage;
10
+ const int level = storage.openLevel(0);
11
+ storage.set("i", 1, level);
12
+ storage.set("i", 2, level);
13
+ storage.set("i", 3, level);
14
+ ASSERT_NE(storage.lookup("i", level), nullptr);
15
+ EXPECT_EQ(*storage.lookup("i", level), 3);
16
+ }
17
+
18
+ TEST(ScopeTrailStorage, SetAtSameLevelDoesNotAccumulateEntries) {
19
+ // The actual optimization this session added: repeated set() calls at
20
+ // the SAME level (a for-loop variable rebound every iteration without
21
+ // opening a new level, or sibling same-block statements reassigning
22
+ // the same name) must overwrite in place, not grow the underlying
23
+ // stack -- verified via the test-only accessor, since correctness
24
+ // alone (SetAtSameLevelOverwritesLatestValue above) can't distinguish
25
+ // "overwrote in place" from "pushed and lookup() happens to find the
26
+ // newest one anyway."
27
+ ScopeTrailStorage<int> storage;
28
+ const int level = storage.openLevel(0);
29
+ for (int i = 0; i < 1000; ++i) storage.set("i", i, level);
30
+ EXPECT_EQ(storage.debugEntryCountForTesting("i"), 1u);
31
+ }
32
+
33
+ TEST(ScopeTrailStorage, SetAtANewLevelStillPushes) {
34
+ // The optimization must not collapse GENUINE nested bindings -- a
35
+ // child level's own set() for the same name is a real new binding
36
+ // (must be poppable back to the parent's value), not a same-level
37
+ // rebind.
38
+ ScopeTrailStorage<int> storage;
39
+ const int parentLevel = storage.openLevel(0);
40
+ storage.set("x", 1, parentLevel);
41
+ const int childLevel = storage.openLevel(parentLevel);
42
+ storage.set("x", 2, childLevel);
43
+ EXPECT_EQ(storage.debugEntryCountForTesting("x"), 2u);
44
+ ASSERT_NE(storage.lookup("x", childLevel), nullptr);
45
+ EXPECT_EQ(*storage.lookup("x", childLevel), 2);
46
+ storage.popLevel(childLevel);
47
+ ASSERT_NE(storage.lookup("x", parentLevel), nullptr);
48
+ EXPECT_EQ(*storage.lookup("x", parentLevel), 1);
49
+ }
50
+
51
+ TEST(ScopeTrailStorage, PopLevelAfterManySameLevelSetsRestoresParentValue) {
52
+ // The end-to-end correctness case a for-loop actually exercises: many
53
+ // same-level rebinds of the loop variable, then the level closes
54
+ // (loop exits) -- the parent's own binding for that name (if any) must
55
+ // resurface untouched, and the now-closed level's value must not leak.
56
+ ScopeTrailStorage<int> storage;
57
+ const int parentLevel = storage.openLevel(0);
58
+ storage.set("i", -1, parentLevel);
59
+ const int loopLevel = storage.openLevel(parentLevel);
60
+ for (int i = 0; i < 999; ++i) storage.set("i", i, loopLevel);
61
+ ASSERT_NE(storage.lookup("i", loopLevel), nullptr);
62
+ EXPECT_EQ(*storage.lookup("i", loopLevel), 998);
63
+ storage.popLevel(loopLevel);
64
+ ASSERT_NE(storage.lookup("i", parentLevel), nullptr);
65
+ EXPECT_EQ(*storage.lookup("i", parentLevel), -1);
66
+ }
67
+
68
+ TEST(IndexedScopeTrailStorage, SetAtSameLevelDoesNotAccumulateEntries) {
69
+ auto intern = std::make_shared<DynNameIntern>();
70
+ IndexedScopeTrailStorage<int> storage(intern);
71
+ const int level = storage.openLevel(0);
72
+ for (int i = 0; i < 1000; ++i) storage.set("$fn", i, level);
73
+ EXPECT_EQ(storage.debugEntryCountForTesting("$fn"), 1u);
74
+ ASSERT_NE(storage.lookup("$fn", level), nullptr);
75
+ EXPECT_EQ(*storage.lookup("$fn", level), 999);
76
+ }
77
+
78
+ } // namespace
79
+ } // namespace oscadeval
@@ -364,12 +364,14 @@ TEST(ExpandIterable, OutOfOrderIndexAccessStillReturnsCorrectValue) {
364
364
  }
365
365
 
366
366
  TEST(ExpandIterable, LargeRangePartialIterationStaysLazy) {
367
- // A huge range must not eagerly materialize a vector: constructing
368
- // expandIterable() and reading only the first few elements has to
369
- // stay instant regardless of the range's nominal size (a billion
370
- // elements here). If this regresses to eager expansion, this test
371
- // times out/OOMs instead of merely failing an assertion.
372
- auto items = expandIterable(Value{OscRange{0, 1, 1'000'000'000.0}});
367
+ // A huge (but under the 1,000,000-element "too many" limit -- see the
368
+ // TooManyElements tests below) range must not eagerly materialize a
369
+ // vector: constructing expandIterable() and reading only the first few
370
+ // elements has to stay instant regardless of the range's nominal size
371
+ // (999,999 elements here, the largest value that doesn't trigger
372
+ // rejection). If this regresses to eager expansion, this test times
373
+ // out/OOMs instead of merely failing an assertion.
374
+ auto items = expandIterable(Value{OscRange{0, 1, 999'998.0}});
373
375
  auto it = items.begin();
374
376
  EXPECT_DOUBLE_EQ(asNum(*it), 0.0);
375
377
  ++it;
@@ -377,3 +379,58 @@ TEST(ExpandIterable, LargeRangePartialIterationStaysLazy) {
377
379
  ++it;
378
380
  EXPECT_DOUBLE_EQ(asNum(*it), 2.0);
379
381
  }
382
+
383
+ TEST(ExpandIterable, RangeUnderOneMillionElementsIteratesNormally) {
384
+ // 999,999 elements (indices 0..999998) -- the largest range that must
385
+ // NOT be rejected. Verified against real OpenSCAD.app: this exact size
386
+ // iterates fine, one more element does not (see the next two tests).
387
+ bool warned = false;
388
+ auto items = expandIterable(Value{OscRange{0, 1, 999'998.0}}, [&](size_t) { warned = true; });
389
+ EXPECT_FALSE(warned);
390
+ EXPECT_EQ(items.size(), 999'999u);
391
+ }
392
+
393
+ TEST(ExpandIterable, RangeOfExactlyOneMillionElementsIsRejected) {
394
+ // 1,000,000 elements (indices 0..999999) -- real OpenSCAD.app rejects
395
+ // the whole range at exactly this size (WARNING + zero iterations, not
396
+ // a truncation to 999,999) -- a different mechanism from the C-style
397
+ // for loop's own _MAX_CFOR_ITERATIONS, which instead ALLOWS exactly
398
+ // 1,000,000 iterations and only errors past it. Verified empirically:
399
+ // these are genuinely different thresholds in real OpenSCAD, not a
400
+ // copy-paste of the same constant.
401
+ size_t warnedCount = 0;
402
+ auto items = expandIterable(Value{OscRange{0, 1, 999'999.0}}, [&](size_t count) { warnedCount = count; });
403
+ EXPECT_EQ(warnedCount, 1'000'000u);
404
+ EXPECT_EQ(items.size(), 0u);
405
+ }
406
+
407
+ TEST(ExpandIterable, HugeRangeIsRejectedInConstantTime) {
408
+ // The rejection check itself must be O(1) (closed-form), not a lazy
409
+ // walk to find out the count -- a billion-element range must reject
410
+ // instantly, not time out. This is exactly the case
411
+ // LargeRangePartialIterationStaysLazy used to (wrongly) exercise as
412
+ // "still iterable"; real OpenSCAD.app rejects it outright.
413
+ size_t warnedCount = 0;
414
+ auto items = expandIterable(Value{OscRange{0, 1, 1'000'000'000.0}}, [&](size_t count) { warnedCount = count; });
415
+ EXPECT_EQ(warnedCount, 1'000'000'001u);
416
+ EXPECT_EQ(items.size(), 0u);
417
+ }
418
+
419
+ TEST(ExpandIterable, NegativeStepRangeTooManyElementsIsRejectedToo) {
420
+ // Verified against real OpenSCAD.app: the too-many-elements check
421
+ // applies symmetrically to a descending range, with the correct count.
422
+ size_t warnedCount = 0;
423
+ auto items = expandIterable(Value{OscRange{1'099'999.0, -1, 0}}, [&](size_t count) { warnedCount = count; });
424
+ EXPECT_EQ(warnedCount, 1'100'000u);
425
+ EXPECT_EQ(items.size(), 0u);
426
+ }
427
+
428
+ TEST(ExpandIterable, ZeroStepRangeIsNaturallyEmptyNotTooMany) {
429
+ // A zero step never terminates by walking, but must not be
430
+ // misclassified as "too many" either -- it's naturally empty (0
431
+ // elements), no warning.
432
+ bool warned = false;
433
+ auto items = expandIterable(Value{OscRange{0, 0, 10}}, [&](size_t) { warned = true; });
434
+ EXPECT_FALSE(warned);
435
+ EXPECT_EQ(items.size(), 0u);
436
+ }