openscad-cpp-evaluator 1.34.0__tar.gz → 1.36.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 (228) hide show
  1. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/.github/workflows/ci.yml +14 -1
  2. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/.github/workflows/wheels.yml +9 -1
  3. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/CLAUDE.md +8 -1
  4. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/CMakeLists.txt +12 -0
  5. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/PKG-INFO +1 -1
  6. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/bindings/module.cpp +4 -3
  7. openscad_cpp_evaluator-1.36.0/cmake/patch_manifold.cmake +31 -0
  8. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +1 -1
  9. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/value.hpp +89 -4
  10. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/pyproject.toml +1 -1
  11. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/booleans.cpp +3 -3
  12. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/call_args.cpp +2 -2
  13. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/control.cpp +1 -1
  14. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/dxf_dim.cpp +1 -1
  15. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/function_builtins.cpp +16 -9
  16. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/import.cpp +11 -12
  17. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/primitives_2d.cpp +4 -4
  18. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/primitives_3d.cpp +8 -8
  19. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/surface.cpp +2 -2
  20. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/text.cpp +2 -3
  21. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/bytecode_vm.cpp +9 -10
  22. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/colored_body.cpp +1 -1
  23. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/eval_context.cpp +2 -2
  24. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/expr_eval.cpp +16 -5
  25. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/measure_geometry.cpp +1 -1
  26. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/value.cpp +94 -19
  27. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/CMakeLists.txt +16 -0
  28. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_control_flow.cpp +91 -24
  29. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_expr_eval.cpp +26 -13
  30. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_function_builtins.cpp +9 -9
  31. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_value.cpp +98 -15
  32. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_viewport_params.cpp +2 -2
  33. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/uv.lock +1 -1
  34. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/.gitignore +0 -0
  35. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/.gitmodules +0 -0
  36. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/LICENSE +0 -0
  37. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/README.md +0 -0
  38. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/bindings/CMakeLists.txt +0 -0
  39. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/bindings/ast_to_py.cpp +0 -0
  40. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/cmake/embed_font.cmake +0 -0
  41. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/examples/CMakeLists.txt +0 -0
  42. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/examples/eval_perf_benchmark.cpp +0 -0
  43. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/examples/manifold_cache_reuse.cpp +0 -0
  44. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/examples/minimal_debugger.cpp +0 -0
  45. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  46. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/.gitignore +0 -0
  47. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  48. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/README.md +0 -0
  50. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  51. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  52. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  53. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  54. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  55. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  56. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  57. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  58. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  59. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  60. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  61. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  62. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  63. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  64. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  65. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  66. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  67. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  68. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  69. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  70. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  71. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  72. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  73. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  74. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  75. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  76. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  77. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  78. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  79. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  80. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  81. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  82. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  83. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  84. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  85. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  86. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  87. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  88. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  89. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  90. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  91. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  92. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  93. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  94. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  95. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  96. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  97. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  98. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  99. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  100. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  101. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  102. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  103. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  104. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  105. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  106. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  107. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  108. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  109. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  110. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  111. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  112. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  113. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  114. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  115. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  116. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  117. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  118. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  119. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  120. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  121. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  122. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  123. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  124. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  125. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  126. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  127. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  128. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  129. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  130. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  131. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  132. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  133. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  134. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  135. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  136. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  137. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  138. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  139. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  140. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  141. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  142. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  143. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  144. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  145. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  146. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  147. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  148. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  149. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  150. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  151. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/resources/fonts/LICENSE +0 -0
  152. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  153. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  154. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  155. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  156. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/CMakeLists.txt +0 -0
  157. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/builtins.hpp +0 -0
  158. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/color.cpp +0 -0
  159. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/extrude.cpp +0 -0
  160. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/modifiers.cpp +0 -0
  161. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/registry.cpp +0 -0
  162. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/roof.cpp +0 -0
  163. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/segments.cpp +0 -0
  164. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/topology.cpp +0 -0
  165. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/builtins/transforms.cpp +0 -0
  166. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/bytecode_compiler.cpp +0 -0
  167. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/coverage.cpp +0 -0
  168. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/csg_generate.cpp +0 -0
  169. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/csg_resolve.cpp +0 -0
  170. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/css_colors.cpp +0 -0
  171. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/debug/debug_repl.cpp +0 -0
  172. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/debug_profile.cpp +0 -0
  173. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/eval_error.cpp +0 -0
  174. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/eval_use.cpp +0 -0
  175. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/export.cpp +0 -0
  176. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/format_closure.cpp +0 -0
  177. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/import/dxf_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/import/mesh_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/import/scene_import.cpp +0 -0
  180. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/import/surface_load.cpp +0 -0
  181. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/import/svg_import.cpp +0 -0
  182. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/manifold_cache.cpp +0 -0
  183. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/mesh_check.cpp +0 -0
  184. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/stmt_eval.cpp +0 -0
  185. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/text/font_match.cpp +0 -0
  186. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/text/freetype_font_provider.cpp +0 -0
  187. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/text/text_metrics.cpp +0 -0
  188. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/user_calls.cpp +0 -0
  189. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/utf8.cpp +0 -0
  190. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/xkcd_colors.cpp +0 -0
  191. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/xkcd_colors.hpp +0 -0
  192. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/src/zip_stored.cpp +0 -0
  193. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_booleans.cpp +0 -0
  194. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_bytecode_compiler.cpp +0 -0
  195. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_children_separate.cpp +0 -0
  196. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_cli.cpp +0 -0
  197. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_coverage.cpp +0 -0
  198. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_csg_tree.cpp +0 -0
  199. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_debug_hooks.cpp +0 -0
  200. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_discretizer.cpp +0 -0
  201. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_dxf_svg_import.cpp +0 -0
  202. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_eval_context.cpp +0 -0
  203. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_export.cpp +0 -0
  204. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_helpers.hpp +0 -0
  207. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_import_export.cpp +0 -0
  208. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_include_cache.cpp +0 -0
  209. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_manifold_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_mesh_check.cpp +0 -0
  211. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_multi_color_merge.cpp +0 -0
  212. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_primitives.cpp +0 -0
  213. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_profiling.cpp +0 -0
  214. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_python_bindings.py +0 -0
  215. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_render_expr.cpp +0 -0
  216. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_scope_trail.cpp +0 -0
  217. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_scoping.cpp +0 -0
  218. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_surface.cpp +0 -0
  219. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_tail_calls.cpp +0 -0
  220. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_text.cpp +0 -0
  221. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_transforms.cpp +0 -0
  222. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_unexpected_args.cpp +0 -0
  223. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_use.cpp +0 -0
  224. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tests/test_zip_stored.cpp +0 -0
  225. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tools/cli/CMakeLists.txt +0 -0
  226. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tools/cli/cli_lib.cpp +0 -0
  227. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.0}/tools/cli/cli_lib.hpp +0 -0
  228. {openscad_cpp_evaluator-1.34.0 → openscad_cpp_evaluator-1.36.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:
@@ -9,7 +9,14 @@ OpenSCAD AST and produces Manifold CSG geometry. This port builds on two depende
9
9
 
10
10
  - `openscad_cpp_parser` (git submodule, `external/openscad_cpp_parser`) for lexing/parsing/AST/scope
11
11
  - Manifold (https://github.com/elalish/manifold) v3.5.2, pulled in via CMake `FetchContent` (pinned
12
- tag, not vendored) — wired in as of Phase 2
12
+ tag, not vendored) — wired in as of Phase 2. **Patched at fetch time** by
13
+ `cmake/patch_manifold.cmake`: it deletes the `combined.RemoveDegenerates()` call in
14
+ `CsgLeafNode::Compose`, which on some inputs leaves the face data inconsistent so the sort after
15
+ it reads past an array (heap-buffer-overflow under ASan, reached by
16
+ `MinkowskiDifference.ShrinksANonConvexBodyOnEverySide`). Bisected with a Manifold-only repro to
17
+ upstream #1789 (969b1417), which fixes it by deleting the same call; unreleased as of 3.5.4.
18
+ The script fails the configure if the source around the call has changed, so **drop the patch
19
+ when bumping past #1789** rather than letting it rot
13
20
  - Boost.Polygon (`boostorg/polygon` + `boostorg/config`, fetched individually — not the full Boost
14
21
  superproject) for `roof()`'s Voronoi-diagram construction — wired in as of Phase 6, see below
15
22
  - `nothings/stb` (`stb_image.h`; header-only, no build step) for `surface()`'s PNG loader — wired
@@ -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
@@ -128,6 +137,9 @@ FetchContent_Declare(
128
137
  GIT_REPOSITORY https://github.com/elalish/manifold.git
129
138
  GIT_TAG v3.5.2
130
139
  GIT_SHALLOW TRUE
140
+ # Backports the one line of upstream #1789 that fixes a heap overflow in
141
+ # CsgLeafNode::Compose -- see cmake/patch_manifold.cmake.
142
+ PATCH_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_manifold.cmake
131
143
  )
132
144
  FetchContent_MakeAvailable(manifold)
133
145
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.34.0
3
+ Version: 1.36.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
+ oscadeval::makeList(std::move(items)))};
79
80
  }
80
81
  return oscadeval::Value{}; // monostate = undef
81
82
  }
@@ -0,0 +1,31 @@
1
+ # Patches the fetched Manifold v3.5.2 source; run as its FetchContent
2
+ # PATCH_COMMAND, in the source directory.
3
+ #
4
+ # CsgLeafNode::Compose calls RemoveDegenerates() on the combined mesh and
5
+ # then sorts it, and on some inputs the cleanup leaves the face data
6
+ # inconsistent: SortGeometry -> GatherFaces then reads past the end of an
7
+ # array (a heap-buffer-overflow under AddressSanitizer). Our
8
+ # MinkowskiDifference.ShrinksANonConvexBodyOnEverySide test reaches it;
9
+ # reproduced with Manifold alone, and bisected upstream to elalish/manifold
10
+ # #1789 ("Improved decimation", 969b1417), which fixes it by deleting this
11
+ # same call -- the only part of that commit the fix needs. Not in any
12
+ # release yet; drop this patch when Manifold is bumped past it.
13
+ #
14
+ # Idempotent: a re-run on already-patched source changes nothing. Anything
15
+ # else -- the call or its surroundings changed -- is an error, so a version
16
+ # bump can never silently skip the patch.
17
+ set(file src/csg_tree.cpp)
18
+ file(READ ${file} src)
19
+ set(call " // required to remove parts that are smaller than the tolerance\n combined.RemoveDegenerates();\n")
20
+ string(FIND "${src}" "${call}" at)
21
+ if(at EQUAL -1)
22
+ string(FIND "${src}" "combined.RemoveDegenerates();" any)
23
+ if(NOT any EQUAL -1)
24
+ message(FATAL_ERROR "patch_manifold.cmake: ${file} changed around RemoveDegenerates(); update the patch")
25
+ endif()
26
+ message(STATUS "patch_manifold.cmake: already applied")
27
+ return()
28
+ endif()
29
+ string(REPLACE "${call}" "" src "${src}")
30
+ file(WRITE ${file} "${src}")
31
+ message(STATUS "patch_manifold.cmake: removed RemoveDegenerates() from CsgLeafNode::Compose")
@@ -170,7 +170,7 @@ struct VmFrame {
170
170
  std::vector<Value> slots;
171
171
  std::vector<Value> stack;
172
172
  std::vector<bool> bound;
173
- std::vector<std::vector<Value>> accumStack;
173
+ std::vector<ListBuilder> accumStack;
174
174
  std::vector<IterList> iterLists;
175
175
  // std::deque, NOT vector: Evaluator::enterUserCall stores
176
176
  // CallStackFrame::bodyCtx as a pointer to ctxChain.back() (see
@@ -2,7 +2,10 @@
2
2
 
3
3
  #include "openscad_cpp_evaluator/osc_range.hpp"
4
4
 
5
+ #include <atomic>
6
+ #include <cstdint>
5
7
  #include <functional>
8
+ #include <initializer_list>
6
9
  #include <memory>
7
10
  #include <optional>
8
11
  #include <string>
@@ -98,11 +101,93 @@ inline std::shared_ptr<TrailView<Value>> capturedLetTrail(const Closure& c) {
98
101
  return std::static_pointer_cast<TrailView<Value>>(c.capturedLet);
99
102
  }
100
103
 
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).
104
+ // A list's elements: a read-only view of the first `size()` elements of a
105
+ // buffer that other lists may share. Lists that are prefixes of one another
106
+ // share one buffer, which is what makes repeated appending cheap: `acc`
107
+ // and `concat(acc, [x])` are the same buffer, the second one element
108
+ // longer (listAppend). Defined after Value so both can hold Value by value
109
+ // -- the standard recursive-variant pattern.
110
+ //
111
+ // The buffer only ever grows within the capacity it was given, so element
112
+ // addresses never move while any view of it is alive, and an element, once
113
+ // written, is never changed. `used` is how far the longest view reaches;
114
+ // only a list ending exactly there may extend it, claiming the new slots
115
+ // with one compare-and-swap, so two lists sharing a prefix can never write
116
+ // the same slot (the loser copies).
117
+ struct ListBuffer {
118
+ std::vector<Value> v; // never reallocated once a list views it
119
+ std::atomic<uint32_t> used{0}; // with fromAppend, packs into one word
120
+ // Set when this buffer came from appending: a list that was appended
121
+ // to once is likely to be again, so ITS copies grow geometrically,
122
+ // while a one-off concat's result is allocated at exactly its size.
123
+ bool fromAppend = false;
124
+ };
125
+
126
+ // A list's elements: the first `size()` of a buffer. A list made by
127
+ // makeList keeps its buffer in its own allocation (keep_ empty); one made by
128
+ // appending in place views the buffer of the list it extends, and keep_
129
+ // holds that list's allocation alive. The element pointer is cached, so a
130
+ // read is one hop, as with a plain vector. Neither copyable nor movable:
131
+ // an own-buffer view must never outlive the allocation it sits in.
132
+ class ListItems {
133
+ public:
134
+ ListItems() = default;
135
+ ListItems(const ListItems&) = delete;
136
+ ListItems& operator=(const ListItems&) = delete;
137
+
138
+ size_t size() const { return n_; }
139
+ bool empty() const { return n_ == 0; }
140
+ const Value* data() const { return data_; }
141
+ const Value* begin() const { return data_; }
142
+ const Value* end() const { return data_ + n_; }
143
+ const Value& operator[](size_t i) const { return data_[i]; }
144
+ const Value& at(size_t i) const;
145
+ const Value& front() const { return data_[0]; }
146
+ const Value& back() const { return data_[n_ - 1]; }
147
+ // A copy as a plain vector, for the few callers that build on one.
148
+ // Explicit, so a copy is never made by accident binding a reference.
149
+ explicit operator std::vector<Value>() const { return std::vector<Value>(begin(), end()); }
150
+ std::vector<Value> toVector() const { return std::vector<Value>(begin(), end()); }
151
+
152
+ private:
153
+ std::shared_ptr<const void> keep_;
154
+ ListBuffer* buf_ = nullptr;
155
+ const Value* data_ = nullptr;
156
+ size_t n_ = 0;
157
+ friend ListPtr makeList(std::vector<Value> items);
158
+ friend ListPtr listAppend(const ListPtr& base, std::vector<Value>&& extra);
159
+ };
160
+
104
161
  struct ValueList {
105
- std::vector<Value> items;
162
+ ListItems items;
163
+ };
164
+
165
+ // A new list holding `items` (adopted, not copied): one allocation for the
166
+ // list and its buffer together, plus the elements' own -- as many as a list
167
+ // that was a plain vector cost.
168
+ ListPtr makeList(std::vector<Value> items);
169
+ inline ListPtr makeList() { return makeList(std::vector<Value>{}); }
170
+
171
+
172
+ // `base` followed by `extra`, sharing base's buffer whenever base is the
173
+ // longest list using it and the buffer has room: amortized O(|extra|)
174
+ // rather than O(|base| + |extra|), which is what turns the idiomatic
175
+ // tail-recursive accumulator, f(i, concat(acc, [x])), from quadratic into
176
+ // linear. Safe whatever else still holds `base`: base keeps seeing only
177
+ // its own elements.
178
+ ListPtr listAppend(const ListPtr& base, std::vector<Value>&& extra);
179
+
180
+ // Builds a list literal or comprehension result from its contributions. When
181
+ // the first contribution is `each <a list>`, the result starts AS that list
182
+ // and everything after it is appended with listAppend -- so the other
183
+ // accumulator idiom, f(i, [each acc, x]), extends acc in place just as
184
+ // concat(acc, [x]) does, instead of copying it.
185
+ struct ListBuilder {
186
+ ListPtr seed; // the list an `each` started from, if any
187
+ std::vector<Value> rest; // everything contributed after it
188
+ void push(Value v) { rest.push_back(std::move(v)); }
189
+ void appendEach(const Value& v); // as appendEachInto
190
+ ListPtr finish();
106
191
  };
107
192
 
108
193
  // Insertion-ordered key/value pairs, not a map: object()'s iteration order
@@ -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.36.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -143,9 +143,9 @@ CSGParams resolveCsg(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
143
143
 
144
144
  CSGParams params;
145
145
  params["op"] = Value{op};
146
- params["group_sizes"] = Value{std::make_shared<const ValueList>(ValueList{std::move(groupSizes)})};
146
+ params["group_sizes"] = Value{makeList(std::move(groupSizes))};
147
147
  params["empty_is_a_group"] =
148
- Value{std::make_shared<const ValueList>(ValueList{std::move(emptyIsAGroup)})};
148
+ Value{makeList(std::move(emptyIsAGroup))};
149
149
  return params;
150
150
  }
151
151
 
@@ -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;
@@ -93,10 +93,10 @@ Value callArgsToValue(const CallArgs& args) {
93
93
  std::vector<std::pair<std::string, Value>> namedVec(args.named.begin(), args.named.end());
94
94
 
95
95
  std::vector<Value> outer = {
96
- Value{std::make_shared<const ValueList>(ValueList{std::move(posVec)})},
96
+ Value{makeList(std::move(posVec))},
97
97
  Value{std::make_shared<const ValueObject>(ValueObject{std::move(namedVec)})},
98
98
  };
99
- return Value{std::make_shared<const ValueList>(ValueList{std::move(outer)})};
99
+ return Value{makeList(std::move(outer))};
100
100
  }
101
101
 
102
102
  const oscad::Expression* argExpr(const oscad::Argument& arg) {
@@ -141,7 +141,7 @@ CSGParams resolveIntersectionFor(Evaluator& ev, const oscad::ModularIntersection
141
141
  recurse(0, ctx);
142
142
 
143
143
  CSGParams params;
144
- params["group_sizes"] = Value{std::make_shared<const ValueList>(ValueList{std::move(groupSizes)})};
144
+ params["group_sizes"] = Value{makeList(std::move(groupSizes))};
145
145
  return params;
146
146
  }
147
147
 
@@ -234,7 +234,7 @@ Value builtinDxfCross(Evaluator& ev, const CallArgs& args, const oscad::ASTNode&
234
234
  if (dem == 0.0) break; // parallel: no cross, same as the reference
235
235
  const double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / dem;
236
236
  std::vector<Value> xy{Value{x1 + ua * (x2 - x1)}, Value{y1 + ua * (y2 - y1)}};
237
- return Value{std::make_shared<const ValueList>(ValueList{std::move(xy)})};
237
+ return Value{makeList(std::move(xy))};
238
238
  }
239
239
  }
240
240
  ev.warn("Can't find cross in '" + c.rawFile + "', layer '" + c.layer + "'!", &node.position());
@@ -48,7 +48,7 @@ std::optional<std::vector<double>> allNumericList(const Value& v) {
48
48
  return out;
49
49
  }
50
50
 
51
- Value listOf(std::vector<Value> items) { return Value{std::make_shared<const ValueList>(ValueList{std::move(items)})}; }
51
+ Value listOf(std::vector<Value> items) { return Value{makeList(std::move(items))}; }
52
52
  Value numList(const std::vector<double>& xs) {
53
53
  std::vector<Value> items;
54
54
  items.reserve(xs.size());
@@ -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: {
@@ -66,7 +66,7 @@ Value jsonToValue(const nlohmann::ordered_json& j) {
66
66
  std::vector<Value> items;
67
67
  items.reserve(j.size());
68
68
  for (const auto& el : j) items.push_back(jsonToValue(el));
69
- return Value{std::make_shared<const ValueList>(ValueList{std::move(items)})};
69
+ return Value{makeList(std::move(items))};
70
70
  }
71
71
  if (j.is_object()) {
72
72
  std::vector<std::pair<std::string, Value>> items;
@@ -102,20 +102,19 @@ Value meshToVnf(const LoadedMesh& mesh) {
102
102
  if (it == vertMap.end()) {
103
103
  idx = static_cast<int>(vertsOut.size());
104
104
  vertMap.emplace(v, idx);
105
- vertsOut.push_back(Value{std::make_shared<const ValueList>(
106
- ValueList{{Value{v[0]}, Value{v[1]}, Value{v[2]}}})});
105
+ vertsOut.push_back(Value{makeList({Value{v[0]}, Value{v[1]}, Value{v[2]}})});
107
106
  } else {
108
107
  idx = it->second;
109
108
  }
110
109
  face.push_back(Value{static_cast<double>(idx)});
111
110
  }
112
- facesOut.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(face)})});
111
+ facesOut.push_back(Value{makeList(std::move(face))});
113
112
  }
114
113
  std::vector<Value> outer = {
115
- Value{std::make_shared<const ValueList>(ValueList{std::move(vertsOut)})},
116
- Value{std::make_shared<const ValueList>(ValueList{std::move(facesOut)})},
114
+ Value{makeList(std::move(vertsOut))},
115
+ Value{makeList(std::move(facesOut))},
117
116
  };
118
- return Value{std::make_shared<const ValueList>(ValueList{std::move(outer)})};
117
+ return Value{makeList(std::move(outer))};
119
118
  }
120
119
 
121
120
  } // namespace
@@ -152,10 +151,10 @@ Value contoursToValue(const std::vector<Contour2d>& contours) {
152
151
  for (const auto& c : contours) {
153
152
  std::vector<Value> pts;
154
153
  pts.reserve(c.size());
155
- for (const auto& p : c) pts.push_back(Value{std::make_shared<const ValueList>(ValueList{{Value{p[0]}, Value{p[1]}}})});
156
- outer.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(pts)})});
154
+ for (const auto& p : c) pts.push_back(Value{makeList({Value{p[0]}, Value{p[1]}})});
155
+ outer.push_back(Value{makeList(std::move(pts))});
157
156
  }
158
- return Value{std::make_shared<const ValueList>(ValueList{std::move(outer)})};
157
+ return Value{makeList(std::move(outer))};
159
158
  }
160
159
 
161
160
  std::vector<Contour2d> valueToContours(const Value& v) {
@@ -284,8 +283,8 @@ CSGParams resolveImport(Evaluator& ev, const oscad::ModularCall& node, EvalConte
284
283
  params["kind"] = Value{std::string("mesh")};
285
284
  params["repair"] = Value{repair};
286
285
  params["tolerance"] = toleranceArg;
287
- params["verts"] = Value{std::make_shared<const ValueList>(ValueList{std::move(vertsFlat)})};
288
- params["tris"] = Value{std::make_shared<const ValueList>(ValueList{std::move(trisFlat)})};
286
+ params["verts"] = Value{makeList(std::move(vertsFlat))};
287
+ params["tris"] = Value{makeList(std::move(trisFlat))};
289
288
  return params;
290
289
  }
291
290
  if (ext == ".json") {
@@ -79,9 +79,9 @@ CSGParams resolve2d(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
79
79
  const double x = (pl && *pl && !(*pl)->items.empty()) ? toDoubleLenient((*pl)->items[0]) : 0.0;
80
80
  const double y = (pl && *pl && (*pl)->items.size() > 1) ? toDoubleLenient((*pl)->items[1]) : 0.0;
81
81
  std::vector<Value> xy = {Value{x}, Value{y}};
82
- pts.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(xy)})});
82
+ pts.push_back(Value{makeList(std::move(xy))});
83
83
  }
84
- params["pts"] = Value{std::make_shared<const ValueList>(ValueList{std::move(pts)})};
84
+ params["pts"] = Value{makeList(std::move(pts))};
85
85
 
86
86
  if (const ListPtr* pathsList = std::get_if<ListPtr>(&pathsArg); pathsList && *pathsList) {
87
87
  std::vector<Value> paths;
@@ -91,9 +91,9 @@ CSGParams resolve2d(Evaluator& ev, const oscad::ModularCall& node, EvalContext&
91
91
  if (pathIdxList && *pathIdxList) {
92
92
  for (const Value& idx : (*pathIdxList)->items) idxVals.push_back(Value{toDoubleLenient(idx)});
93
93
  }
94
- paths.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(idxVals)})});
94
+ paths.push_back(Value{makeList(std::move(idxVals))});
95
95
  }
96
- params["paths"] = Value{std::make_shared<const ValueList>(ValueList{std::move(paths)})};
96
+ params["paths"] = Value{makeList(std::move(paths))};
97
97
  } else {
98
98
  params["paths"] = Value{}; // undef == "no paths given" -- pts is one single contour
99
99
  }
@@ -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",
@@ -141,7 +141,7 @@ CSGParams resolveCube(Evaluator& ev, const oscad::ModularCall& node, EvalContext
141
141
  }
142
142
 
143
143
  CSGParams params;
144
- params["size"] = Value{std::make_shared<const ValueList>(ValueList{std::move(sizeVec)})};
144
+ params["size"] = Value{makeList(std::move(sizeVec))};
145
145
  params["center"] = Value{center};
146
146
  params["color"] = colorToValue(effCtx.color);
147
147
  return params;
@@ -402,8 +402,8 @@ CSGParams resolveSphere(Evaluator& ev, const oscad::ModularCall& node, EvalConte
402
402
  // Manifold::Sphere IS a subdivided octahedron (Shape::Octahedron,
403
403
  // then Subdivide), so this needs no mesh of our own -- generate
404
404
  // calls it directly.
405
- params["verts"] = Value{std::make_shared<const ValueList>(ValueList{})};
406
- params["tris"] = Value{std::make_shared<const ValueList>(ValueList{})};
405
+ params["verts"] = Value{makeList()};
406
+ params["tris"] = Value{makeList()};
407
407
  return params;
408
408
  }
409
409
 
@@ -418,8 +418,8 @@ CSGParams resolveSphere(Evaluator& ev, const oscad::ModularCall& node, EvalConte
418
418
  std::vector<Value> trisValues;
419
419
  trisValues.reserve(m.tris.size());
420
420
  for (int t : m.tris) trisValues.push_back(Value{static_cast<double>(t)});
421
- params["verts"] = Value{std::make_shared<const ValueList>(ValueList{std::move(vertsValues)})};
422
- params["tris"] = Value{std::make_shared<const ValueList>(ValueList{std::move(trisValues)})};
421
+ params["verts"] = Value{makeList(std::move(vertsValues))};
422
+ params["tris"] = Value{makeList(std::move(trisValues))};
423
423
  return params;
424
424
  }
425
425
 
@@ -832,8 +832,8 @@ CSGParams resolvePolyhedron(Evaluator& ev, const oscad::ModularCall& node, EvalC
832
832
  for (uint32_t t : tris) trisValues.push_back(Value{static_cast<double>(t)});
833
833
 
834
834
  CSGParams params;
835
- params["verts"] = Value{std::make_shared<const ValueList>(ValueList{std::move(vertsValues)})};
836
- params["tris"] = Value{std::make_shared<const ValueList>(ValueList{std::move(trisValues)})};
835
+ params["verts"] = Value{makeList(std::move(vertsValues))};
836
+ params["tris"] = Value{makeList(std::move(trisValues))};
837
837
  params["color"] = colorToValue(effCtx.color);
838
838
  return params;
839
839
  }
@@ -44,9 +44,9 @@ CSGParams resolveSurface(Evaluator& ev, const oscad::ModularCall& node, EvalCont
44
44
  std::vector<Value> rowVals;
45
45
  rowVals.reserve(row.size());
46
46
  for (double v : row) rowVals.push_back(Value{v});
47
- rows.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(rowVals)})});
47
+ rows.push_back(Value{makeList(std::move(rowVals))});
48
48
  }
49
- params["heights"] = Value{std::make_shared<const ValueList>(ValueList{std::move(rows)})};
49
+ params["heights"] = Value{makeList(std::move(rows))};
50
50
  return params;
51
51
  }
52
52
 
@@ -69,8 +69,7 @@ CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext
69
69
  std::vector<Value> glyphs;
70
70
  glyphs.reserve(m.glyphs.size());
71
71
  for (const TextMeasurement::Placed& g : m.glyphs) {
72
- glyphs.push_back(Value{std::make_shared<const ValueList>(
73
- ValueList{{Value{static_cast<double>(g.glyph)}, Value{g.x}, Value{g.y}}})});
72
+ glyphs.push_back(Value{makeList({Value{static_cast<double>(g.glyph)}, Value{g.x}, Value{g.y}})});
74
73
  }
75
74
 
76
75
  CSGParams params;
@@ -89,7 +88,7 @@ CSGParams resolveText(Evaluator& ev, const oscad::ModularCall& node, EvalContext
89
88
  params["segs"] = Value{static_cast<double>(segs)};
90
89
  params["offset_x"] = Value{offsetX};
91
90
  params["offset_y"] = Value{offsetY};
92
- params["glyphs"] = Value{std::make_shared<const ValueList>(ValueList{std::move(glyphs)})};
91
+ params["glyphs"] = Value{makeList(std::move(glyphs))};
93
92
  params["color"] = colorToValue(effCtx.color);
94
93
  return params;
95
94
  }
@@ -792,7 +792,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
792
792
  items[count - 1 - i] = std::move(f.stack.back());
793
793
  f.stack.pop_back();
794
794
  }
795
- f.stack.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(items)})});
795
+ f.stack.push_back(Value{makeList(std::move(items))});
796
796
  ++f.pc;
797
797
  break;
798
798
  }
@@ -803,28 +803,28 @@ Value driveVm(Evaluator& ev, size_t floor) {
803
803
  case Op::AccumAppendOne: {
804
804
  Value v = std::move(f.stack.back());
805
805
  f.stack.pop_back();
806
- f.accumStack.back().push_back(std::move(v));
806
+ f.accumStack.back().push(std::move(v));
807
807
  ++f.pc;
808
808
  break;
809
809
  }
810
810
  case Op::AccumAppendEach: {
811
811
  Value v = std::move(f.stack.back());
812
812
  f.stack.pop_back();
813
- appendEachInto(f.accumStack.back(), v);
813
+ f.accumStack.back().appendEach(v);
814
814
  ++f.pc;
815
815
  break;
816
816
  }
817
817
  case Op::AccumClose: {
818
- std::vector<Value> items = std::move(f.accumStack.back());
818
+ ListPtr list = f.accumStack.back().finish();
819
819
  f.accumStack.pop_back();
820
- f.stack.push_back(Value{std::make_shared<const ValueList>(ValueList{std::move(items)})});
820
+ f.stack.push_back(Value{std::move(list)});
821
821
  ++f.pc;
822
822
  break;
823
823
  }
824
824
  case Op::AccumMergeEach: {
825
- std::vector<Value> inner = std::move(f.accumStack.back());
825
+ const ListPtr inner = f.accumStack.back().finish();
826
826
  f.accumStack.pop_back();
827
- for (const Value& item : inner) appendEachInto(f.accumStack.back(), item);
827
+ for (const Value& item : inner->items) f.accumStack.back().appendEach(item);
828
828
  ++f.pc;
829
829
  break;
830
830
  }
@@ -1492,9 +1492,8 @@ Value driveVm(Evaluator& ev, size_t floor) {
1492
1492
  CSGParams params;
1493
1493
  if (site.includeOpParam) params["op"] = Value{pending.op};
1494
1494
  params["group_sizes"] =
1495
- Value{std::make_shared<const ValueList>(ValueList{std::move(pending.groupSizes)})};
1496
- params["empty_is_a_group"] = Value{std::make_shared<const ValueList>(
1497
- ValueList{std::move(pending.emptyIsAGroup)})};
1495
+ Value{makeList(std::move(pending.groupSizes))};
1496
+ params["empty_is_a_group"] = Value{makeList(std::move(pending.emptyIsAGroup))};
1498
1497
  // Mirrors Evaluator::buildTreeNode's own post-
1499
1498
  // resolveBody() half exactly (csg_resolve.cpp).
1500
1499
  const bool uncacheable =