openscad-cpp-evaluator 1.34.0__tar.gz → 1.35.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 (227) hide show
  1. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/.github/workflows/ci.yml +14 -1
  2. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/.github/workflows/wheels.yml +9 -1
  3. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/CMakeLists.txt +9 -0
  4. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/PKG-INFO +1 -1
  5. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/bindings/module.cpp +4 -3
  6. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/value.hpp +63 -4
  7. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/pyproject.toml +1 -1
  8. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/booleans.cpp +1 -1
  9. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/function_builtins.cpp +15 -8
  10. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/primitives_3d.cpp +1 -1
  11. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/expr_eval.cpp +1 -1
  12. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/value.cpp +44 -10
  13. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/CMakeLists.txt +16 -0
  14. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_function_builtins.cpp +9 -9
  15. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_value.cpp +68 -0
  16. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/uv.lock +1 -1
  17. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/.gitignore +0 -0
  18. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/.gitmodules +0 -0
  19. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/CLAUDE.md +0 -0
  20. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/LICENSE +0 -0
  21. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/README.md +0 -0
  22. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/bindings/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/bindings/ast_to_py.cpp +0 -0
  24. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/cmake/embed_font.cmake +0 -0
  25. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/examples/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/examples/eval_perf_benchmark.cpp +0 -0
  27. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/examples/manifold_cache_reuse.cpp +0 -0
  28. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/examples/minimal_debugger.cpp +0 -0
  29. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  30. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/.gitignore +0 -0
  31. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  32. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/README.md +0 -0
  34. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  35. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  36. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  37. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  38. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  39. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  40. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  41. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  42. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  43. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  45. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  46. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  47. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  48. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  49. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  50. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  51. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  53. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  54. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  55. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  56. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  57. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  58. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  59. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  60. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  61. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  62. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  63. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  64. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  65. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  66. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  67. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  68. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  69. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  70. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  71. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  72. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  73. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  74. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  75. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  76. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  77. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  78. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  79. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  80. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  81. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  82. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  83. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  84. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  85. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  86. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  87. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  91. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  92. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  93. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  94. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  95. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  96. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  97. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  98. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  99. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  100. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  101. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  102. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  103. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  104. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  105. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  106. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  107. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  108. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  109. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  110. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  111. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  112. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  113. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  114. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  115. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  116. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  117. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  118. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  119. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  120. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  121. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  122. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  123. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  124. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  125. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  126. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  127. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  128. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  129. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  130. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  131. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  132. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  133. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  134. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  135. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  136. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/resources/fonts/LICENSE +0 -0
  137. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  138. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  139. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  140. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  141. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/CMakeLists.txt +0 -0
  142. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/builtins.hpp +0 -0
  143. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/call_args.cpp +0 -0
  144. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/color.cpp +0 -0
  145. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/control.cpp +0 -0
  146. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/dxf_dim.cpp +0 -0
  147. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/extrude.cpp +0 -0
  148. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/import.cpp +0 -0
  149. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/modifiers.cpp +0 -0
  150. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/primitives_2d.cpp +0 -0
  151. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/registry.cpp +0 -0
  152. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/roof.cpp +0 -0
  153. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/segments.cpp +0 -0
  154. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/surface.cpp +0 -0
  155. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/text.cpp +0 -0
  156. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/topology.cpp +0 -0
  157. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/builtins/transforms.cpp +0 -0
  158. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/bytecode_compiler.cpp +0 -0
  159. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/bytecode_vm.cpp +0 -0
  160. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/colored_body.cpp +0 -0
  161. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/coverage.cpp +0 -0
  162. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/csg_generate.cpp +0 -0
  163. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/csg_resolve.cpp +0 -0
  164. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/css_colors.cpp +0 -0
  165. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/debug/debug_repl.cpp +0 -0
  166. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/debug_profile.cpp +0 -0
  167. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/eval_context.cpp +0 -0
  168. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/eval_error.cpp +0 -0
  169. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/eval_use.cpp +0 -0
  170. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/export.cpp +0 -0
  171. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/format_closure.cpp +0 -0
  172. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/import/dxf_import.cpp +0 -0
  173. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/import/mesh_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/import/scene_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/import/surface_load.cpp +0 -0
  176. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/import/svg_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/manifold_cache.cpp +0 -0
  178. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/measure_geometry.cpp +0 -0
  179. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/mesh_check.cpp +0 -0
  180. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/stmt_eval.cpp +0 -0
  181. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/text/font_match.cpp +0 -0
  182. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/text/freetype_font_provider.cpp +0 -0
  183. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/text/text_metrics.cpp +0 -0
  184. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/user_calls.cpp +0 -0
  185. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/utf8.cpp +0 -0
  186. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/xkcd_colors.cpp +0 -0
  187. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/xkcd_colors.hpp +0 -0
  188. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/src/zip_stored.cpp +0 -0
  189. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_booleans.cpp +0 -0
  190. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_bytecode_compiler.cpp +0 -0
  191. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_children_separate.cpp +0 -0
  192. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_cli.cpp +0 -0
  193. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_control_flow.cpp +0 -0
  194. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_coverage.cpp +0 -0
  195. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_csg_tree.cpp +0 -0
  196. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_debug_hooks.cpp +0 -0
  197. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_discretizer.cpp +0 -0
  198. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_dxf_svg_import.cpp +0 -0
  199. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_eval_context.cpp +0 -0
  200. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_export.cpp +0 -0
  201. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_expr_eval.cpp +0 -0
  202. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_extrude_roof.cpp +0 -0
  203. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_feature_detection.cpp +0 -0
  204. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_helpers.hpp +0 -0
  205. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_import_export.cpp +0 -0
  206. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_include_cache.cpp +0 -0
  207. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_manifold_cache.cpp +0 -0
  208. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_mesh_check.cpp +0 -0
  209. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_multi_color_merge.cpp +0 -0
  210. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_primitives.cpp +0 -0
  211. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_profiling.cpp +0 -0
  212. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_python_bindings.py +0 -0
  213. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_render_expr.cpp +0 -0
  214. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_scope_trail.cpp +0 -0
  215. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_scoping.cpp +0 -0
  216. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_surface.cpp +0 -0
  217. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_tail_calls.cpp +0 -0
  218. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_text.cpp +0 -0
  219. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_transforms.cpp +0 -0
  220. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_unexpected_args.cpp +0 -0
  221. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_use.cpp +0 -0
  222. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_viewport_params.cpp +0 -0
  223. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tests/test_zip_stored.cpp +0 -0
  224. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tools/cli/CMakeLists.txt +0 -0
  225. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tools/cli/cli_lib.cpp +0 -0
  226. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tools/cli/cli_lib.hpp +0 -0
  227. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.35.0}/tools/cli/main.cpp +0 -0
@@ -50,8 +50,21 @@ jobs:
50
50
  # Linux/macOS use (CMAKE_BUILD_TYPE already picked the config), but
51
51
  # required on Windows's default multi-config Visual Studio
52
52
  # generator, which ignores CMAKE_BUILD_TYPE entirely.
53
+ # A bounded job count on the Unix runners. A bare `-j` becomes `make -j`,
54
+ # which starts EVERY compile at once; on macos-latest (3 vCPU, 7 GB) a
55
+ # pile of template-heavy translation units that size ran the build at
56
+ # 852-2000 s against Linux's ~300 s. Windows keeps the bare -j: MSBuild
57
+ # reads it as "use the cores", which is already bounded.
53
58
  - name: Build
54
- run: cmake --build build --config Release -j
59
+ shell: bash
60
+ run: |
61
+ case "$RUNNER_OS" in
62
+ macOS) JOBS=$(sysctl -n hw.ncpu) ;;
63
+ Linux) JOBS=$(nproc) ;;
64
+ *) JOBS= ;;
65
+ esac
66
+ echo "building with -j ${JOBS:-<unbounded: MSBuild>}"
67
+ cmake --build build --config Release -j $JOBS
55
68
 
56
69
  # Bytecode VM on (the default as of the Phase 5 rollout decision --
57
70
  # see evaluator.hpp's own bytecodeVmEnabled() comment) and off
@@ -38,8 +38,16 @@ jobs:
38
38
  name: macos-arm64
39
39
  - runner: macos-15-intel
40
40
  name: macos-x86_64
41
+ # Two Windows jobs, one per architecture, both on the x86_64 runner
42
+ # (ARM64 is cross-compiled; see CIBW_ARCHS_WINDOWS). As one job they
43
+ # built one after the other -- 7 min each, 14.5 min for the job,
44
+ # the slowest in the release -- when neither needs the other.
41
45
  - runner: windows-latest
42
46
  name: windows-x86_64
47
+ archs: AMD64
48
+ - runner: windows-latest
49
+ name: windows-arm64
50
+ archs: ARM64
43
51
  runs-on: ${{ matrix.runner }}
44
52
  steps:
45
53
  - uses: actions/checkout@v7
@@ -65,7 +73,7 @@ jobs:
65
73
  # ARM64 build at all, so building on an ARM runner would have to
66
74
  # emulate it. cibuildwheel skips CIBW_TEST_COMMAND for the
67
75
  # cross-compiled wheel, since an x86_64 host cannot run it.
68
- CIBW_ARCHS_WINDOWS: "AMD64 ARM64"
76
+ CIBW_ARCHS_WINDOWS: ${{ matrix.archs }}
69
77
  # delvewheel vendors the MSVC runtime into the wheel, but on an
70
78
  # x86_64 host it can only find the x86_64 copy -- repairing the
71
79
  # cross-built ARM64 wheel died with "Unable to find library:
@@ -79,6 +79,15 @@ if(MSVC)
79
79
  # unconditionally (a POSIX extension our code already relies on), so no
80
80
  # equivalent is needed there.
81
81
  add_compile_definitions(_USE_MATH_DEFINES)
82
+ # /MP: compile a target's source files in parallel. MSBuild's own /m (what
83
+ # `cmake --build -j` passes) runs PROJECTS in parallel but each project's
84
+ # files one at a time, and nearly everything here is one big library, so a
85
+ # Windows build was close to serial (CI's Build step ~665 s against Linux's
86
+ # ~250). Visual Studio generators only: under Ninja every compiler call is
87
+ # already one file, and Ninja does the parallelism itself.
88
+ if(CMAKE_GENERATOR MATCHES "Visual Studio")
89
+ add_compile_options(/MP)
90
+ endif()
82
91
  endif()
83
92
 
84
93
  # The nanobind Python extension (bindings/) links the static evaluator lib
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.34.0
3
+ Version: 1.35.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -73,9 +73,10 @@ oscadeval::Value pyToValue(nb::handle v) {
73
73
  if (nb::isinstance<nb::int_>(v) || nb::isinstance<nb::float_>(v)) return oscadeval::Value{nb::cast<double>(v)};
74
74
  if (nb::isinstance<nb::str>(v)) return oscadeval::Value{nb::cast<std::string>(v)};
75
75
  if (nb::isinstance<nb::list>(v) || nb::isinstance<nb::tuple>(v)) {
76
- oscadeval::ValueList vl;
77
- for (nb::handle item : nb::borrow<nb::sequence>(v)) vl.items.push_back(pyToValue(item));
78
- return oscadeval::Value{oscadeval::ListPtr(std::make_shared<const oscadeval::ValueList>(std::move(vl)))};
76
+ std::vector<oscadeval::Value> items;
77
+ for (nb::handle item : nb::borrow<nb::sequence>(v)) items.push_back(pyToValue(item));
78
+ return oscadeval::Value{oscadeval::ListPtr(
79
+ std::make_shared<const oscadeval::ValueList>(oscadeval::ValueList{std::move(items)}))};
79
80
  }
80
81
  return oscadeval::Value{}; // monostate = undef
81
82
  }
@@ -2,7 +2,9 @@
2
2
 
3
3
  #include "openscad_cpp_evaluator/osc_range.hpp"
4
4
 
5
+ #include <atomic>
5
6
  #include <functional>
7
+ #include <initializer_list>
6
8
  #include <memory>
7
9
  #include <optional>
8
10
  #include <string>
@@ -98,13 +100,70 @@ inline std::shared_ptr<TrailView<Value>> capturedLetTrail(const Closure& c) {
98
100
  return std::static_pointer_cast<TrailView<Value>>(c.capturedLet);
99
101
  }
100
102
 
101
- // Defined after Value so both can hold Value by value -- the standard
102
- // recursive-variant pattern (indirection through a forward-declared,
103
- // heap-allocated aggregate).
103
+ // A list's elements: a read-only view of the first `size()` elements of a
104
+ // buffer that other lists may share. Lists that are prefixes of one another
105
+ // share one buffer, which is what makes repeated appending cheap: `acc`
106
+ // and `concat(acc, [x])` are the same buffer, the second one element
107
+ // longer (listAppend). Defined after Value so both can hold Value by value
108
+ // -- the standard recursive-variant pattern.
109
+ //
110
+ // The buffer only ever grows within the capacity it was given, so element
111
+ // addresses never move while any view of it is alive, and an element, once
112
+ // written, is never changed. `used` is how far the longest view reaches;
113
+ // only a list ending exactly there may extend it, claiming the new slots
114
+ // with one compare-and-swap, so two lists sharing a prefix can never write
115
+ // the same slot (the loser copies).
116
+ struct ListBuffer {
117
+ std::vector<Value> v;
118
+ std::atomic<size_t> used;
119
+ // Set when this buffer came from appending: a list that was appended
120
+ // to once is likely to be again, so ITS copies grow geometrically,
121
+ // while a one-off concat's result is allocated at exactly its size.
122
+ bool fromAppend = false;
123
+ explicit ListBuffer(std::vector<Value>&& items) : v(std::move(items)), used(v.size()) {}
124
+ };
125
+
126
+ class ListItems {
127
+ public:
128
+ ListItems() = default;
129
+ ListItems(std::vector<Value>&& items) // NOLINT: implicit, as the old vector was
130
+ : buf_(std::make_shared<ListBuffer>(std::move(items))), n_(buf_->v.size()) {}
131
+ ListItems(const std::vector<Value>& items) : ListItems(std::vector<Value>(items)) {} // NOLINT
132
+ ListItems(std::initializer_list<Value> items) : ListItems(std::vector<Value>(items)) {}
133
+
134
+ size_t size() const { return n_; }
135
+ bool empty() const { return n_ == 0; }
136
+ const Value* data() const { return buf_ ? buf_->v.data() : nullptr; }
137
+ const Value* begin() const { return data(); }
138
+ const Value* end() const { return data() + n_; }
139
+ const Value& operator[](size_t i) const { return data()[i]; }
140
+ const Value& at(size_t i) const;
141
+ const Value& front() const { return data()[0]; }
142
+ const Value& back() const { return data()[n_ - 1]; }
143
+ // A copy as a plain vector, for the few callers that build on one.
144
+ // Explicit, so a copy is never made by accident binding a reference.
145
+ explicit operator std::vector<Value>() const { return std::vector<Value>(begin(), end()); }
146
+ std::vector<Value> toVector() const { return std::vector<Value>(begin(), end()); }
147
+
148
+ private:
149
+ ListItems(std::shared_ptr<ListBuffer> buf, size_t n) : buf_(std::move(buf)), n_(n) {}
150
+ std::shared_ptr<ListBuffer> buf_;
151
+ size_t n_ = 0;
152
+ friend ListPtr listAppend(const ListPtr& base, std::vector<Value>&& extra);
153
+ };
154
+
104
155
  struct ValueList {
105
- std::vector<Value> items;
156
+ ListItems items;
106
157
  };
107
158
 
159
+ // `base` followed by `extra`, sharing base's buffer whenever base is the
160
+ // longest list using it and the buffer has room: amortized O(|extra|)
161
+ // rather than O(|base| + |extra|), which is what turns the idiomatic
162
+ // tail-recursive accumulator, f(i, concat(acc, [x])), from quadratic into
163
+ // linear. Safe whatever else still holds `base`: base keeps seeing only
164
+ // its own elements.
165
+ ListPtr listAppend(const ListPtr& base, std::vector<Value>&& extra);
166
+
108
167
  // Insertion-ordered key/value pairs, not a map: object()'s iteration order
109
168
  // and `==` are order-sensitive (doc: openscad_evaluator/docs/evaluator.md,
110
169
  // the object() entry).
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.34.0"
7
+ version = "1.35.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -313,7 +313,7 @@ std::vector<ColoredBody> generateCsg(Evaluator& ev, const CSGParams& params, con
313
313
  const std::string& op = std::get<std::string>(params.at("op"));
314
314
  const auto& groupSizes = std::get<ListPtr>(params.at("group_sizes"))->items;
315
315
  const auto emptyIsAGroupIt = params.find("empty_is_a_group");
316
- const std::vector<Value>* emptyIsAGroup =
316
+ const ListItems* emptyIsAGroup =
317
317
  emptyIsAGroupIt == params.end()
318
318
  ? nullptr
319
319
  : &std::get<ListPtr>(emptyIsAGroupIt->second)->items;
@@ -616,11 +616,11 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
616
616
  const unsigned numReturns = static_cast<unsigned>(static_cast<long long>(nrRaw));
617
617
  const unsigned indexCol = static_cast<unsigned>(static_cast<long long>(icRaw));
618
618
 
619
- static const std::vector<Value> kNoItems;
619
+ static const ListItems kNoItems;
620
620
  const ListPtr* tablePtr = std::get_if<ListPtr>(&tableArg);
621
- const std::vector<Value>& table = (tablePtr && *tablePtr) ? (*tablePtr)->items : kNoItems;
621
+ const ListItems& table = (tablePtr && *tablePtr) ? (*tablePtr)->items : kNoItems;
622
622
 
623
- const auto itemsOf = [](const Value& v) -> const std::vector<Value>& {
623
+ const auto itemsOf = [](const Value& v) -> const ListItems& {
624
624
  const ListPtr* l = std::get_if<ListPtr>(&v);
625
625
  return (l && *l) ? (*l)->items : kNoItems;
626
626
  };
@@ -629,7 +629,7 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
629
629
  // only when the entry really is a long enough vector.
630
630
  const auto hits = [&](const Value& needle, const Value& entry) {
631
631
  if (indexCol == 0 && oscEqual(needle, entry)) return true;
632
- const std::vector<Value>& ev2 = itemsOf(entry);
632
+ const ListItems& ev2 = itemsOf(entry);
633
633
  return indexCol < ev2.size() && oscEqual(needle, ev2[indexCol]);
634
634
  };
635
635
  const auto num = [](size_t j) { return Value{static_cast<double>(j)}; };
@@ -682,7 +682,7 @@ Value builtinSearch(const CallArgs& args, Evaluator& ev, const oscad::Position*
682
682
  unsigned matchCount = 0;
683
683
  std::vector<Value> resultvec;
684
684
  for (size_t j = 0; j < table.size(); ++j) {
685
- const std::vector<Value>& entryVec = itemsOf(table[j]);
685
+ const ListItems& entryVec = itemsOf(table[j]);
686
686
  if (entryVec.size() <= indexCol) {
687
687
  ev.warn("Invalid entry in search vector at index " + std::to_string(j) +
688
688
  ", required number of values in the entry: " + std::to_string(indexCol + 1) +
@@ -1162,9 +1162,9 @@ bool checkMinMax(Evaluator& ev, const std::string& name, const CallArgs& args, c
1162
1162
  return false;
1163
1163
  }
1164
1164
  if (nPos == 1 && std::holds_alternative<ListPtr>(positionalAt(args, 0))) {
1165
- static const std::vector<Value> kEmptyItems;
1165
+ static const ListItems kEmptyItems;
1166
1166
  const ListPtr& l = std::get<ListPtr>(positionalAt(args, 0));
1167
- const std::vector<Value>& items = l ? l->items : kEmptyItems;
1167
+ const ListItems& items = l ? l->items : kEmptyItems;
1168
1168
  if (items.empty()) {
1169
1169
  warnArity(ev, name, "at least 1 vector element", 0, pos);
1170
1170
  return false;
@@ -1346,14 +1346,21 @@ Value evalBuiltinFunctionResolved(Evaluator& ev, BuiltinFnId id, const std::vect
1346
1346
  toDoubleLenient(getArg(args, 2, "value_count", Value{})), getArg(args, 3, "seed", Value{}));
1347
1347
  }
1348
1348
  case BuiltinFnId::Concat: {
1349
+ // Everything after the first argument, appended to the first
1350
+ // when it is a list: listAppend extends its buffer in place when
1351
+ // it can, so an accumulator loop is linear, not quadratic.
1352
+ const size_t count = positionalCount(args);
1353
+ const ListPtr* first = count ? std::get_if<ListPtr>(&positionalAt(args, 0)) : nullptr;
1349
1354
  std::vector<Value> out;
1350
- for (const Value& a : allPositional(args)) {
1355
+ for (size_t i = (first && *first) ? 1 : 0; i < count; ++i) {
1356
+ const Value& a = positionalAt(args, i);
1351
1357
  if (const ListPtr* l = std::get_if<ListPtr>(&a); l && *l) {
1352
1358
  out.insert(out.end(), (*l)->items.begin(), (*l)->items.end());
1353
1359
  } else {
1354
1360
  out.push_back(a);
1355
1361
  }
1356
1362
  }
1363
+ if (first && *first) return Value{listAppend(*first, std::move(out))};
1357
1364
  return listOf(std::move(out));
1358
1365
  }
1359
1366
  case BuiltinFnId::Len: {
@@ -133,7 +133,7 @@ CSGParams resolveCube(Evaluator& ev, const oscad::ModularCall& node, EvalContext
133
133
  } else if (l && *l && (*l)->items.size() == 3 &&
134
134
  std::all_of((*l)->items.begin(), (*l)->items.end(),
135
135
  [](const Value& v) { return std::holds_alternative<double>(v); })) {
136
- sizeVec = (*l)->items;
136
+ sizeVec = (*l)->items.toVector();
137
137
  } else if (!std::holds_alternative<std::monostate>(sizeArg)) {
138
138
  ev.warn("Unable to convert cube(size=" + fmtValue(sizeArg) +
139
139
  ", ...) parameter to a number or a vec3 of numbers",
@@ -42,7 +42,7 @@ std::optional<bool> valueLess(const Value& a, const Value& b) {
42
42
 
43
43
  const ListPtr& la = std::get<ListPtr>(a);
44
44
  const ListPtr& lb = std::get<ListPtr>(b);
45
- static const std::vector<Value> kEmpty;
45
+ static const ListItems kEmpty;
46
46
  const auto& ia = la ? la->items : kEmpty;
47
47
  const auto& ib = lb ? lb->items : kEmpty;
48
48
  const size_t n = std::min(ia.size(), ib.size());
@@ -8,6 +8,7 @@
8
8
  #include <limits>
9
9
  #include <optional>
10
10
  #include <sstream>
11
+ #include <stdexcept>
11
12
 
12
13
  namespace oscadeval {
13
14
 
@@ -205,8 +206,8 @@ namespace {
205
206
  // matrix*matrix branch of multiply_visitor -- including which check fires
206
207
  // first, since that decides which diagnostic a malformed operand produces.
207
208
  // `fail` records the message and returns undef.
208
- const std::vector<Value>& itemsOf(const Value& v) {
209
- static const std::vector<Value> kEmpty;
209
+ const ListItems& itemsOf(const Value& v) {
210
+ static const ListItems kEmpty;
210
211
  const ListPtr* l = std::get_if<ListPtr>(&v);
211
212
  return (l && *l) ? (*l)->items : kEmpty;
212
213
  }
@@ -219,7 +220,7 @@ Value fail(std::string* error, std::string message) {
219
220
  }
220
221
 
221
222
  // Vector dot product. Sizes are equal by the caller's own check.
222
- Value multVecVec(const std::vector<Value>& v1, const std::vector<Value>& v2, std::string* error) {
223
+ Value multVecVec(const ListItems& v1, const ListItems& v2, std::string* error) {
223
224
  double r = 0.0;
224
225
  for (size_t i = 0; i < v1.size(); ++i) {
225
226
  if (!isNum(v1[i]) || !isNum(v2[i])) {
@@ -230,11 +231,11 @@ Value multVecVec(const std::vector<Value>& v1, const std::vector<Value>& v2, std
230
231
  return Value{r};
231
232
  }
232
233
 
233
- Value multMatVec(const std::vector<Value>& mat, const std::vector<Value>& vec, std::string* error) {
234
+ Value multMatVec(const ListItems& mat, const ListItems& vec, std::string* error) {
234
235
  std::vector<Value> out;
235
236
  out.reserve(mat.size());
236
237
  for (size_t i = 0; i < mat.size(); ++i) {
237
- const std::vector<Value>& row = itemsOf(mat[i]);
238
+ const ListItems& row = itemsOf(mat[i]);
238
239
  if (!isVec(mat[i]) || row.size() != vec.size()) {
239
240
  return fail(error, "Matrix must be rectangular. Problem at row " + std::to_string(i));
240
241
  }
@@ -254,14 +255,14 @@ Value multMatVec(const std::vector<Value>& mat, const std::vector<Value>& vec, s
254
255
  return makeList(std::move(out));
255
256
  }
256
257
 
257
- Value multVecMat(const std::vector<Value>& vec, const std::vector<Value>& mat, std::string* error) {
258
+ Value multVecMat(const ListItems& vec, const ListItems& mat, std::string* error) {
258
259
  const size_t firstRowSize = itemsOf(mat[0]).size();
259
260
  std::vector<Value> out;
260
261
  out.reserve(firstRowSize);
261
262
  for (size_t i = 0; i < firstRowSize; ++i) {
262
263
  double re = 0.0;
263
264
  for (size_t j = 0; j < vec.size(); ++j) {
264
- const std::vector<Value>& row = itemsOf(mat[j]);
265
+ const ListItems& row = itemsOf(mat[j]);
265
266
  if (!isVec(mat[j]) || row.size() != firstRowSize) {
266
267
  return fail(error, "Matrix must be rectangular. Problem at row " + std::to_string(j));
267
268
  }
@@ -282,8 +283,8 @@ Value multVecMat(const std::vector<Value>& vec, const std::vector<Value>& mat, s
282
283
  } // namespace
283
284
 
284
285
  Value matmul(const Value& a, const Value& b, std::string* error) {
285
- const std::vector<Value>& al = itemsOf(a);
286
- const std::vector<Value>& bl = itemsOf(b);
286
+ const ListItems& al = itemsOf(a);
287
+ const ListItems& bl = itemsOf(b);
287
288
  // The reference checks emptiness before anything else, so `[] * [1,2]`
288
289
  // is this message rather than a length mismatch.
289
290
  if (al.empty() || bl.empty()) return fail(error, "Multiplication is undefined on empty vectors");
@@ -323,7 +324,7 @@ Value matmul(const Value& a, const Value& b, std::string* error) {
323
324
  std::vector<Value> rows;
324
325
  rows.reserve(al.size());
325
326
  for (size_t i = 0; i < al.size(); ++i) {
326
- const std::vector<Value>& srcRow = itemsOf(al[i]);
327
+ const ListItems& srcRow = itemsOf(al[i]);
327
328
  if (srcRow.size() != bl.size()) {
328
329
  return fail(error,
329
330
  "matrix*matrix left operand row length does not match right operand row count (" +
@@ -618,4 +619,37 @@ void appendEachInto(std::vector<Value>& out, const Value& v) {
618
619
  if (!std::holds_alternative<std::monostate>(v)) out.push_back(v);
619
620
  }
620
621
 
622
+ const Value& ListItems::at(size_t i) const {
623
+ if (i >= n_) throw std::out_of_range("list index out of range");
624
+ return data()[i];
625
+ }
626
+
627
+ ListPtr listAppend(const ListPtr& base, std::vector<Value>&& extra) {
628
+ if (!base) return std::make_shared<const ValueList>(ValueList{std::move(extra)});
629
+ if (extra.empty()) return base;
630
+ const ListItems& items = base->items;
631
+ const size_t n = items.n_, k = extra.size();
632
+ if (const auto& buf = items.buf_; buf && buf->v.capacity() - n >= k) {
633
+ size_t expected = n;
634
+ if (buf->used.compare_exchange_strong(expected, n + k)) {
635
+ // This list ended at the buffer's frontier and now owns the
636
+ // slots after it. Within capacity, push_back never reallocates,
637
+ // so every other view's elements stay where they are.
638
+ for (Value& x : extra) buf->v.push_back(std::move(x));
639
+ return std::make_shared<const ValueList>(ValueList{ListItems(buf, n + k)});
640
+ }
641
+ }
642
+ // Copy: a fresh buffer, given room to grow if this list has been
643
+ // appended to before (the accumulator case), exact otherwise.
644
+ const bool growing = items.buf_ && items.buf_->fromAppend;
645
+ std::vector<Value> out;
646
+ out.reserve(growing ? std::max<size_t>(2 * (n + k), 16) : n + k);
647
+ out.insert(out.end(), items.begin(), items.end());
648
+ for (Value& x : extra) out.push_back(std::move(x));
649
+ auto buf = std::make_shared<ListBuffer>(std::move(out));
650
+ buf->fromAppend = true;
651
+ const size_t len = buf->v.size();
652
+ return std::make_shared<const ValueList>(ValueList{ListItems(std::move(buf), len)});
653
+ }
654
+
621
655
  } // namespace oscadeval
@@ -48,6 +48,22 @@ add_executable(oscad_eval_tests
48
48
  # put the archive on the link line twice ("ld: warning: ignoring duplicate
49
49
  # libraries").
50
50
  target_link_libraries(oscad_eval_tests PRIVATE oscad_cli_lib GTest::gtest_main)
51
+ # Every test file includes gtest and nearly all include evaluator.hpp; parsing
52
+ # those again for each of 35 files was 40-52% of a test file's compile time.
53
+ # One precompiled header takes that off all of them -- the test executable is
54
+ # the last stage of every build and, on MSVC, the longest (~178 s of CI's
55
+ # Windows build).
56
+ target_precompile_headers(oscad_eval_tests PRIVATE
57
+ <gtest/gtest.h>
58
+ <openscad_cpp_evaluator/evaluator.hpp>
59
+ <openscad_cpp_parser/api.hpp>
60
+ <algorithm>
61
+ <cmath>
62
+ <filesystem>
63
+ <fstream>
64
+ <set>
65
+ <string>
66
+ <vector>)
51
67
  # stb_image_write.h, used only by this test binary to generate a PNG
52
68
  # fixture for ToRenderableBodies/surface() image-loading tests.
53
69
  target_include_directories(oscad_eval_tests PRIVATE ${stb_SOURCE_DIR})
@@ -23,7 +23,7 @@ double asNum(const Value& v) { return std::get<double>(v); }
23
23
  bool asBool(const Value& v) { return std::get<bool>(v); }
24
24
  bool isUndef(const Value& v) { return std::holds_alternative<std::monostate>(v); }
25
25
  std::string asStr(const Value& v) { return std::get<std::string>(v); }
26
- const std::vector<Value>& asList(const Value& v) { return std::get<ListPtr>(v)->items; }
26
+ const ListItems& asList(const Value& v) { return std::get<ListPtr>(v)->items; }
27
27
 
28
28
  } // namespace
29
29
 
@@ -742,7 +742,7 @@ TEST(LinearSolve, SolvesASquareSystem) {
742
742
  // asList() returns a reference INTO v, so v has to outlive it -- taking
743
743
  // asList(evalSrc(...)) directly dangles the moment the temporary dies.
744
744
  const Value v = evalSrc("linear_solve([[2,1],[1,3]], [5,10]).x", ev);
745
- const std::vector<Value>& x = asList(v);
745
+ const ListItems& x = asList(v);
746
746
  ASSERT_EQ(x.size(), 2u);
747
747
  EXPECT_NEAR(asNum(x[0]), 1.0, 1e-12);
748
748
  EXPECT_NEAR(asNum(x[1]), 3.0, 1e-12);
@@ -779,7 +779,7 @@ TEST(LinearSolve, SingularityIsRelativeToTheMatrixNotAbsolute) {
779
779
  Evaluator ev;
780
780
  EXPECT_FALSE(asBool(evalSrc("linear_solve([[2e-10,0],[0,2e-10]], [1e-10,1e-10]).singular", ev)));
781
781
  const Value v = evalSrc("linear_solve([[2e-10,0],[0,2e-10]], [1e-10,1e-10]).x", ev);
782
- const std::vector<Value>& x = asList(v);
782
+ const ListItems& x = asList(v);
783
783
  ASSERT_EQ(x.size(), 2u);
784
784
  EXPECT_NEAR(asNum(x[0]), 0.5, 1e-9);
785
785
  }
@@ -789,9 +789,9 @@ TEST(LinearSolve, AMatrixRightHandSideSolvesEveryColumn) {
789
789
  // Solving against the identity is the inverse; [[2,1],[1,3]] has
790
790
  // det 5, so the inverse is [[0.6,-0.2],[-0.2,0.4]].
791
791
  const Value v = evalSrc("linear_solve([[2,1],[1,3]], [[1,0],[0,1]]).x", ev);
792
- const std::vector<Value>& rows = asList(v);
792
+ const ListItems& rows = asList(v);
793
793
  ASSERT_EQ(rows.size(), 2u);
794
- const std::vector<Value>& r0 = asList(rows[0]);
794
+ const ListItems& r0 = asList(rows[0]);
795
795
  ASSERT_EQ(r0.size(), 2u);
796
796
  EXPECT_NEAR(asNum(r0[0]), 0.6, 1e-12);
797
797
  EXPECT_NEAR(asNum(r0[1]), -0.2, 1e-12);
@@ -814,7 +814,7 @@ TEST(LinearSolve, OverdeterminedGivesTheLeastSquaresFit) {
814
814
  // y = 1 + 2x, so the residual is zero and a=1, b=2.
815
815
  Evaluator ev;
816
816
  const Value v = evalSrc("linear_solve([[1,0],[1,1],[1,2],[1,3]], [1,3,5,7]).x", ev);
817
- const std::vector<Value>& x = asList(v);
817
+ const ListItems& x = asList(v);
818
818
  ASSERT_EQ(x.size(), 2u);
819
819
  EXPECT_NEAR(asNum(x[0]), 1.0, 1e-10);
820
820
  EXPECT_NEAR(asNum(x[1]), 2.0, 1e-10);
@@ -826,7 +826,7 @@ TEST(LinearSolve, OverdeterminedWithNoExactSolutionMinimisesTheResidual) {
826
826
  // read off this implementation.
827
827
  Evaluator ev;
828
828
  const Value v = evalSrc("linear_solve([[1,0],[1,1],[1,2],[1,3]], [0,1,2,10]).x", ev);
829
- const std::vector<Value>& x = asList(v);
829
+ const ListItems& x = asList(v);
830
830
  ASSERT_EQ(x.size(), 2u);
831
831
  EXPECT_NEAR(asNum(x[0]), -1.4, 1e-9);
832
832
  EXPECT_NEAR(asNum(x[1]), 3.1, 1e-9);
@@ -838,7 +838,7 @@ TEST(LinearSolve, UnderdeterminedGivesTheMinimUmNormSolution) {
838
838
  // asserts the NORM -- that is the whole claim of a minimum-norm solve.
839
839
  Evaluator ev;
840
840
  const Value v = evalSrc("linear_solve([[1,1]], [2]).x", ev);
841
- const std::vector<Value>& x = asList(v);
841
+ const ListItems& x = asList(v);
842
842
  ASSERT_EQ(x.size(), 2u);
843
843
  EXPECT_NEAR(asNum(x[0]), 1.0, 1e-12);
844
844
  EXPECT_NEAR(asNum(x[1]), 1.0, 1e-12);
@@ -848,7 +848,7 @@ TEST(LinearSolve, UnderdeterminedSatisfiesEveryEquation) {
848
848
  // 2 x 3: the plane_intersection shape BOSL2 relies on.
849
849
  Evaluator ev;
850
850
  const Value v = evalSrc("linear_solve([[1,0,1],[0,1,1]], [3,5]).x", ev);
851
- const std::vector<Value>& x = asList(v);
851
+ const ListItems& x = asList(v);
852
852
  ASSERT_EQ(x.size(), 3u);
853
853
  const double a = asNum(x[0]), b = asNum(x[1]), c = asNum(x[2]);
854
854
  EXPECT_NEAR(a + c, 3.0, 1e-12);
@@ -607,3 +607,71 @@ TEST(StringEscapes, ABackslashBeforeANewlineTakesTheWholeLineEnding) {
607
607
  EXPECT_EQ(unescapeStringLiteral("x\\\r\ny"), "xy"); // reference keeps the CR
608
608
  EXPECT_EQ(unescapeStringLiteral("x\\\ry"), "xy");
609
609
  }
610
+
611
+ // --- listAppend: lists that are prefixes of one another share a buffer ---
612
+
613
+ namespace {
614
+ ListPtr numsList(std::initializer_list<double> xs) {
615
+ std::vector<Value> v;
616
+ for (double x : xs) v.push_back(Value{x});
617
+ return std::make_shared<const ValueList>(ValueList{std::move(v)});
618
+ }
619
+ std::vector<double> nums(const ListPtr& l) {
620
+ std::vector<double> out;
621
+ for (const Value& v : l->items) out.push_back(std::get<double>(v));
622
+ return out;
623
+ }
624
+ std::vector<Value> one(double x) { return {Value{x}}; }
625
+ } // namespace
626
+
627
+ TEST(ListAppend, LeavesTheBaseListAsItWas) {
628
+ const ListPtr base = numsList({1, 2});
629
+ const ListPtr longer = listAppend(base, one(3));
630
+ EXPECT_EQ(nums(base), (std::vector<double>{1, 2}));
631
+ EXPECT_EQ(nums(longer), (std::vector<double>{1, 2, 3}));
632
+ }
633
+
634
+ TEST(ListAppend, AnAccumulatorCopiesOnlyLogarithmicallyOften) {
635
+ // Appends extend the same storage in place, copying only when it fills
636
+ // and then into double the room: 10,000 appends, a handful of copies --
637
+ // where copying every time (the old concat) made the loop quadratic.
638
+ ListPtr acc = numsList({0});
639
+ int copies = 0;
640
+ for (int i = 1; i < 10000; ++i) {
641
+ const ListPtr next = listAppend(acc, one(i));
642
+ if (next->items.data() != acc->items.data()) ++copies;
643
+ acc = next; // the previous list is still alive here, as in a call chain
644
+ }
645
+ EXPECT_LE(copies, 12);
646
+ ASSERT_EQ(acc->items.size(), 10000u);
647
+ for (size_t i = 0; i < 10000; ++i) EXPECT_EQ(std::get<double>(acc->items[i]), double(i));
648
+ }
649
+
650
+ TEST(ListAppend, TwoBranchesFromOnePrefixStayApart) {
651
+ // Only the list ending at the buffer's frontier may extend it; a second
652
+ // extension of the same prefix must copy rather than overwrite the
653
+ // first one's element.
654
+ const ListPtr base = listAppend(listAppend(numsList({0}), one(1)), one(2)); // has spare capacity
655
+ const ListPtr a = listAppend(base, one(10));
656
+ const ListPtr b = listAppend(base, one(20));
657
+ EXPECT_EQ(nums(a), (std::vector<double>{0, 1, 2, 10}));
658
+ EXPECT_EQ(nums(b), (std::vector<double>{0, 1, 2, 20}));
659
+ EXPECT_EQ(nums(base), (std::vector<double>{0, 1, 2}));
660
+ // ...and each branch can go on growing independently.
661
+ EXPECT_EQ(nums(listAppend(a, one(11))), (std::vector<double>{0, 1, 2, 10, 11}));
662
+ EXPECT_EQ(nums(listAppend(b, one(21))), (std::vector<double>{0, 1, 2, 20, 21}));
663
+ }
664
+
665
+ TEST(ListAppend, AOneOffConcatIsAllocatedExactly) {
666
+ // Only a list that has been appended to grows geometrically, so a plain
667
+ // concat(a, b) costs no spare memory.
668
+ const ListPtr r = listAppend(numsList({1, 2, 3}), one(4));
669
+ EXPECT_EQ(r->items.size(), 4u);
670
+ EXPECT_EQ(nums(r), (std::vector<double>{1, 2, 3, 4}));
671
+ }
672
+
673
+ TEST(ListAppend, EmptyExtraAndNullBase) {
674
+ const ListPtr base = numsList({1});
675
+ EXPECT_EQ(listAppend(base, {}), base);
676
+ EXPECT_EQ(nums(listAppend(nullptr, one(5))), (std::vector<double>{5}));
677
+ }
@@ -55,7 +55,7 @@ wheels = [
55
55
 
56
56
  [[package]]
57
57
  name = "openscad-cpp-evaluator"
58
- version = "1.34.0"
58
+ version = "1.35.0"
59
59
  source = { editable = "." }
60
60
  dependencies = [
61
61
  { name = "numpy" },