openscad-cpp-evaluator 1.7.2__tar.gz → 1.9.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 (215) hide show
  1. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/bindings/module.cpp +19 -5
  3. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +38 -0
  4. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/api.cpp +24 -4
  5. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +3 -0
  6. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/parser.y +24 -2
  7. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
  8. openscad_cpp_evaluator-1.9.0/external/openscad_cpp_parser/tests/test_strict_commas.cpp +117 -0
  9. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/export.hpp +16 -2
  10. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/pyproject.toml +1 -1
  11. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/python/openscad_cpp_evaluator/__init__.py +17 -5
  12. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/export.cpp +9 -5
  13. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_export.cpp +51 -1
  14. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_python_bindings.py +63 -0
  15. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tools/cli/cli_lib.cpp +9 -1
  16. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/CLAUDE.md +0 -0
  21. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/LICENSE +0 -0
  23. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/README.md +0 -0
  24. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/bindings/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/bindings/ast_to_py.cpp +0 -0
  26. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/cmake/embed_font.cmake +0 -0
  27. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/examples/CMakeLists.txt +0 -0
  28. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/examples/eval_perf_benchmark.cpp +0 -0
  29. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/examples/manifold_cache_reuse.cpp +0 -0
  30. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/examples/minimal_debugger.cpp +0 -0
  31. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  32. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/.gitignore +0 -0
  33. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  34. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  35. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/README.md +0 -0
  36. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  37. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  38. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  39. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  40. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  41. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  42. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  43. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  44. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  45. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  46. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  47. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  48. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  49. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  50. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  51. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  53. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  54. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  55. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  56. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  57. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  58. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  59. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  60. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  61. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  62. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  63. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  64. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  65. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  69. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  71. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  72. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  73. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  74. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  75. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  76. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  77. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  78. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  80. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  81. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  82. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  83. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  84. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  85. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  86. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  87. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  88. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  89. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  90. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  91. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  92. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  93. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  94. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  95. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  96. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  97. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  98. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  99. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  100. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  101. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  102. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  103. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  104. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  105. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  106. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  107. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  108. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  109. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  110. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  111. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  113. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  115. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  116. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  117. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  118. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  119. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  120. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  121. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  122. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  123. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  124. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  125. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  126. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  127. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  128. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/resources/fonts/LICENSE +0 -0
  129. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  130. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/CMakeLists.txt +0 -0
  131. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/booleans.cpp +0 -0
  132. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/builtins.hpp +0 -0
  133. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/call_args.cpp +0 -0
  134. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/color.cpp +0 -0
  135. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/control.cpp +0 -0
  136. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/dxf_dim.cpp +0 -0
  137. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/extrude.cpp +0 -0
  138. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/function_builtins.cpp +0 -0
  139. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/import.cpp +0 -0
  140. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/modifiers.cpp +0 -0
  141. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/primitives_2d.cpp +0 -0
  142. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/primitives_3d.cpp +0 -0
  143. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/builtins/transforms.cpp +0 -0
  150. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/bytecode_compiler.cpp +0 -0
  151. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/bytecode_vm.cpp +0 -0
  152. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/colored_body.cpp +0 -0
  153. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/csg_generate.cpp +0 -0
  154. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/csg_resolve.cpp +0 -0
  155. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/css_colors.cpp +0 -0
  156. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/debug/debug_repl.cpp +0 -0
  157. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/debug_profile.cpp +0 -0
  158. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/eval_context.cpp +0 -0
  159. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/eval_error.cpp +0 -0
  160. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/eval_use.cpp +0 -0
  161. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/expr_eval.cpp +0 -0
  162. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/format_closure.cpp +0 -0
  163. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/import/dxf_import.cpp +0 -0
  164. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/import/mesh_import.cpp +0 -0
  165. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/import/surface_load.cpp +0 -0
  166. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/import/svg_import.cpp +0 -0
  167. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/manifold_cache.cpp +0 -0
  168. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/measure_geometry.cpp +0 -0
  169. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/mesh_check.cpp +0 -0
  170. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/stmt_eval.cpp +0 -0
  171. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/text/font_match.cpp +0 -0
  172. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/text/freetype_font_provider.cpp +0 -0
  173. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/text/text_metrics.cpp +0 -0
  174. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/user_calls.cpp +0 -0
  175. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/utf8.cpp +0 -0
  176. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/value.cpp +0 -0
  177. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/src/zip_stored.cpp +0 -0
  178. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/CMakeLists.txt +0 -0
  179. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_booleans.cpp +0 -0
  180. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_bytecode_compiler.cpp +0 -0
  181. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_children_separate.cpp +0 -0
  182. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_cli.cpp +0 -0
  183. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_control_flow.cpp +0 -0
  184. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_csg_tree.cpp +0 -0
  185. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_debug_hooks.cpp +0 -0
  186. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_dxf_svg_import.cpp +0 -0
  187. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_eval_context.cpp +0 -0
  188. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_expr_eval.cpp +0 -0
  189. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_extrude_roof.cpp +0 -0
  190. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_feature_detection.cpp +0 -0
  191. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_function_builtins.cpp +0 -0
  192. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_helpers.hpp +0 -0
  193. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_import_export.cpp +0 -0
  194. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_include_cache.cpp +0 -0
  195. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_manifold_cache.cpp +0 -0
  196. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_mesh_check.cpp +0 -0
  197. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_multi_color_merge.cpp +0 -0
  198. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_primitives.cpp +0 -0
  199. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_profiling.cpp +0 -0
  200. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_render_expr.cpp +0 -0
  201. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_scope_trail.cpp +0 -0
  202. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_scoping.cpp +0 -0
  203. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_surface.cpp +0 -0
  204. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_tail_calls.cpp +0 -0
  205. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_text.cpp +0 -0
  206. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_transforms.cpp +0 -0
  207. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_unexpected_args.cpp +0 -0
  208. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_use.cpp +0 -0
  209. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_value.cpp +0 -0
  210. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_viewport_params.cpp +0 -0
  211. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tests/test_zip_stored.cpp +0 -0
  212. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tools/cli/CMakeLists.txt +0 -0
  213. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tools/cli/cli_lib.hpp +0 -0
  214. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/tools/cli/main.cpp +0 -0
  215. {openscad_cpp_evaluator-1.7.2 → openscad_cpp_evaluator-1.9.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.7.2
3
+ Version: 1.9.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -357,7 +357,7 @@ struct Geometry {
357
357
  // exportModel, with the path/format/warnings marshalling. Releases the GIL:
358
358
  // a large export is seconds of Manifold work with no Python involved.
359
359
  nb::list exportModelPy(const std::string& path, const Geometry& geom, const std::string& format, bool asciiStl,
360
- bool stripSlivers) {
360
+ bool stripSlivers, bool splitComponents) {
361
361
  std::vector<std::string> warnings;
362
362
  {
363
363
  nb::gil_scoped_release rel;
@@ -365,6 +365,7 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
365
365
  opts.format = format;
366
366
  opts.asciiStl = asciiStl;
367
367
  opts.stripSlivers = stripSlivers;
368
+ opts.splitComponents = splitComponents;
368
369
  warnings = oscadeval::exportModel(path, geom.bodies, opts);
369
370
  }
370
371
  nb::list out;
@@ -374,7 +375,7 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
374
375
 
375
376
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
376
377
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
377
- bool generate) {
378
+ bool generate, bool strictCommas) {
378
379
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
379
380
 
380
381
  std::vector<oscadeval::ColoredBody> bodies;
@@ -388,6 +389,12 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
388
389
  nb::gil_scoped_release rel;
389
390
  auto logFn = [&echoes](const std::string& m) { echoes.push_back(m); };
390
391
  try {
392
+ // Constructed on the parsing thread, which is the one whose
393
+ // thread-local mode StrictCommaScope sets -- a flag flipped from
394
+ // Python before the call would sit on the wrong thread the
395
+ // moment a host evaluates off the main one.
396
+ std::optional<oscad::StrictCommaScope> strict;
397
+ if (strictCommas) strict.emplace();
391
398
  oscad::ParsedProgram program = oscad::getProgramFromFile(path);
392
399
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
393
400
  oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
@@ -786,21 +793,28 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
786
793
 
787
794
  m.def("export_model", &exportModelPy, nb::arg("path"), nb::arg("geometry"), nb::arg("format") = std::string(),
788
795
  nb::arg("ascii_stl") = false, nb::arg("strip_slivers") = true,
796
+ nb::arg("split_components") = false,
789
797
  "Write `geometry` to `path`, format taken from the extension unless `format` says otherwise. "
790
798
  "Returns the warnings to surface (open shells, mesh problems, slivers removed) rather than "
791
799
  "logging them. Raises RuntimeError when there is no geometry, the format is unknown, or the "
792
- "file cannot be opened.");
800
+ "file cannot be opened.\n\n"
801
+ "`split_components` gives every disconnected piece its own object in the multi-object formats "
802
+ "(3MF, AMF, OBJ, PLY, VRML, X3D). Off by default, matching OpenSCAD, which writes one object "
803
+ "per colour however many pieces it is in.");
793
804
 
794
805
  m.def("export_extensions", []() { return oscadeval::exportExtensions(); },
795
806
  "The file extensions export_model understands, dot-prefixed. Read from the C++ writer table "
796
807
  "rather than restated here, so the Python side cannot drift from what can actually be written.");
797
808
 
798
809
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
799
- nb::arg("profile") = false, nb::arg("generate") = true,
810
+ nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
800
811
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).\n"
801
812
  "generate=False stops after the resolve pass: the script runs and reports everything "
802
813
  "it normally would, but no Manifold geometry is built, and neither bodies nor csg_tree "
803
- "are populated.");
814
+ "are populated.\n\n"
815
+ "strict_commas=True makes a trailing comma in a call argument list or a let/for "
816
+ "assignment list a syntax error, as OpenSCAD 2021.01 did. List literals and parameter "
817
+ "declarations keep theirs, which 2021.01 accepted too.");
804
818
  m.def("parse_decls", &parseDecls, nb::arg("path"),
805
819
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
806
820
  m.def("strip_slivers", &stripSliversPy, nb::arg("verts"), nb::arg("tris"),
@@ -60,6 +60,44 @@ std::unique_ptr<Scope> buildScopes(const std::vector<ASTNode*>& ast);
60
60
  std::unique_ptr<Scope> buildScopesInto(const std::vector<std::unique_ptr<ASTNode>>& ast, ScopeTable& table);
61
61
  std::unique_ptr<Scope> buildScopesInto(const std::vector<ASTNode*>& ast, ScopeTable& table);
62
62
 
63
+ // Makes a trailing comma in a CALL ARGUMENT LIST a syntax error for as long
64
+ // as it is in scope, matching OpenSCAD 2021.01:
65
+ //
66
+ // cube(1,); // error while this is in scope
67
+ // echo(let(x = 1, y = 2,) x) // error -- let() parses as a call
68
+ // a = [2, 4,]; // still fine: 2021.01 accepts it too
69
+ // module m(a, b,) {} // still fine, same reason
70
+ //
71
+ // One production (`arguments`) feeds every call form -- module
72
+ // instantiation, function calls, echo, assert, render and let -- so this is
73
+ // one rule, not a family of them. Parameter lists and list literals are
74
+ // deliberately untouched: 2021.01 accepts a trailing comma in both, and
75
+ // rejecting them would fail files it loads happily.
76
+ //
77
+ // A scope object rather than a parameter on all nine parse entry points:
78
+ // this is a parse-wide mode, the same shape ParseNumberingScope already
79
+ // uses, and threading a bool through getASTFromString/getASTFromFile/
80
+ // getASTFromLibraryFile/getProgramFromFile and their helpers would touch
81
+ // every one of them to say the same thing. Nests and restores on scope
82
+ // exit, exceptions included; thread-local, so one thread parsing strictly
83
+ // cannot change what another thread sees.
84
+ //
85
+ // getProgramFromFile()'s cache keys on this too -- a strict parse must not
86
+ // be handed a tree an earlier lenient parse of the same file left behind.
87
+ class StrictCommaScope {
88
+ public:
89
+ StrictCommaScope();
90
+ ~StrictCommaScope();
91
+ StrictCommaScope(const StrictCommaScope&) = delete;
92
+ StrictCommaScope& operator=(const StrictCommaScope&) = delete;
93
+
94
+ private:
95
+ bool previous_;
96
+ };
97
+
98
+ // Whether a StrictCommaScope is currently in effect on this thread.
99
+ bool strictCommasEnabled();
100
+
63
101
  // Parses `code`. Throws ParseError (with the full caret diagnostic) on a
64
102
  // syntax error.
65
103
  //
@@ -89,6 +89,17 @@ std::string formatSyntaxError(const ParserDriver& driver, const std::string& cod
89
89
 
90
90
  } // namespace
91
91
 
92
+ namespace {
93
+ // See StrictCommaScope (api.hpp). Thread-local so a strict parse on one
94
+ // thread cannot change what another thread is parsing.
95
+ thread_local bool g_strictCommas = false;
96
+ } // namespace
97
+
98
+ StrictCommaScope::StrictCommaScope() : previous_(g_strictCommas) { g_strictCommas = true; }
99
+ StrictCommaScope::~StrictCommaScope() { g_strictCommas = previous_; }
100
+
101
+ bool strictCommasEnabled() { return g_strictCommas; }
102
+
92
103
  std::vector<std::unique_ptr<ASTNode>> parseAst(const std::string& code, const std::string& origin, SourceMap* sourceMap) {
93
104
  // Stamps every node this parse builds with one treeId and a dense
94
105
  // slot, so a ScopeTable can address it without the node carrying a
@@ -96,6 +107,7 @@ std::vector<std::unique_ptr<ASTNode>> parseAst(const std::string& code, const st
96
107
  ParseNumberingScope numbering;
97
108
 
98
109
  ParserDriver driver(origin);
110
+ driver.strictCommas = g_strictCommas;
99
111
  lexerBeginString(code);
100
112
  yy::parser parser(driver);
101
113
  int rc = parser.parse();
@@ -258,7 +270,8 @@ namespace {
258
270
  using FileAst = std::vector<std::unique_ptr<ASTNode>>;
259
271
  using FileAstPtr = std::shared_ptr<const FileAst>;
260
272
 
261
- // One entry per (file, comments) -- keyed by PATH, with the content stamp
273
+ // One entry per (file, comments, strict-commas) -- keyed by PATH, with the
274
+ // content stamp
262
275
  // stored beside the tree rather than in the key. A stale stamp REPLACES the
263
276
  // entry instead of adding a second one: an editor re-renders on every save,
264
277
  // and a stamp-in-the-key cache would keep a full copy of every version the
@@ -266,11 +279,18 @@ using FileAstPtr = std::shared_ptr<const FileAst>;
266
279
  struct CacheKey {
267
280
  std::string path;
268
281
  bool comments;
269
- bool operator==(const CacheKey& o) const { return comments == o.comments && path == o.path; }
282
+ // Part of the key, not incidental: the same file parses differently
283
+ // under StrictCommaScope, so a strict parse must not be served a tree an
284
+ // earlier lenient parse of it left here (and vice versa).
285
+ bool strictCommas;
286
+ bool operator==(const CacheKey& o) const {
287
+ return comments == o.comments && strictCommas == o.strictCommas && path == o.path;
288
+ }
270
289
  };
271
290
  struct CacheKeyHash {
272
291
  size_t operator()(const CacheKey& k) const {
273
- return std::hash<std::string>{}(k.path) ^ (k.comments ? 0x5bf03635U : 0U);
292
+ return std::hash<std::string>{}(k.path) ^ (k.comments ? 0x5bf03635U : 0U)
293
+ ^ (k.strictCommas ? 0x9e3779b9U : 0U);
274
294
  }
275
295
  };
276
296
  struct CacheEntry {
@@ -290,7 +310,7 @@ FileAstPtr parseFileShared(const std::string& absPath, bool includeComments) {
290
310
  const auto written = fs::last_write_time(absPath, ec);
291
311
  const std::int64_t stampMtime = ec ? 0 : static_cast<std::int64_t>(written.time_since_epoch().count());
292
312
 
293
- const CacheKey key{absPath, includeComments};
313
+ const CacheKey key{absPath, includeComments, g_strictCommas};
294
314
  {
295
315
  std::lock_guard<std::mutex> lock(g_astCacheMutex);
296
316
  auto it = g_astCache.find(key);
@@ -39,6 +39,9 @@ public:
39
39
  std::string origin_;
40
40
  NodeList result;
41
41
  bool hadError = false;
42
+ // Copied from the ambient StrictCommaScope at parse start (api.cpp), so
43
+ // one parse's mode cannot change under it mid-run.
44
+ bool strictCommas = false;
42
45
  int errorLine = 0;
43
46
  int errorColumn = 0;
44
47
  int errorOffset = 0;
@@ -247,7 +247,18 @@ parameter:
247
247
  arguments:
248
248
  %empty { $$ = NodeList{}; }
249
249
  | argument_seq { $$ = std::move($1); }
250
- | argument_seq "," { $$ = std::move($1); }
250
+ // The trailing comma OpenSCAD 2021.01 rejected. Still parsed, then
251
+ // reported -- @2 is the comma itself, so the caret lands on it rather
252
+ // than on the whole argument list, which a %expect-style grammar split
253
+ // could not manage. This one production feeds every call form (module
254
+ // instantiation, function calls, echo, assert, render, and let), which
255
+ // is why the whole feature is one rule. See StrictCommaScope (api.hpp).
256
+ | argument_seq "," {
257
+ if (driver.strictCommas) {
258
+ driver.reportError(@2, "trailing comma in argument list");
259
+ }
260
+ $$ = std::move($1);
261
+ }
251
262
  ;
252
263
 
253
264
  argument_seq:
@@ -263,7 +274,18 @@ argument:
263
274
  assignments_expr:
264
275
  %empty { $$ = NodeList{}; }
265
276
  | assignment_expr_seq { $$ = std::move($1); }
266
- | assignment_expr_seq "," { $$ = std::move($1); }
277
+ // 2021.01 rejected this one too. `let` does NOT go through `arguments`
278
+ // in this grammar -- it has its own assignment list, shared with `for`
279
+ // and `intersection_for` -- so the feature is two productions, not the
280
+ // one it looks like. 2021.01 rejects a trailing comma in all four:
281
+ // let(x=1,), for(i=[0:2],), intersection_for(i=[0:1],) and the list
282
+ // comprehension forms of for. See StrictCommaScope (api.hpp).
283
+ | assignment_expr_seq "," {
284
+ if (driver.strictCommas) {
285
+ driver.reportError(@2, "trailing comma in assignment list");
286
+ }
287
+ $$ = std::move($1);
288
+ }
267
289
  ;
268
290
 
269
291
  assignment_expr_seq:
@@ -26,6 +26,7 @@ add_executable(oscad_tests
26
26
  test_ast_generation.cpp
27
27
  test_node_str.cpp
28
28
  test_render_expression.cpp
29
+ test_strict_commas.cpp
29
30
  )
30
31
  target_link_libraries(oscad_tests PRIVATE openscad_cpp_parser GTest::gtest_main)
31
32
 
@@ -0,0 +1,117 @@
1
+ // StrictCommaScope: a trailing comma in a call argument list is an error,
2
+ // matching OpenSCAD 2021.01 (BelfrySCAD issue #362).
3
+ //
4
+ // The scope is deliberately narrow. Measured against 2021.01 itself, it
5
+ // rejected a trailing comma in CALL ARGUMENTS and accepted one everywhere
6
+ // else -- list literals, list comprehensions, and module/function parameter
7
+ // declarations all take one, in 2021.01 as in every version since. Rejecting
8
+ // those would fail files 2021.01 loads happily, so they are left alone and
9
+ // this file says so case by case.
10
+ #include "test_helpers.hpp"
11
+
12
+ #include <gtest/gtest.h>
13
+
14
+ using namespace oscad;
15
+
16
+ namespace {
17
+
18
+ // Two productions carry it: `arguments` (every call form) and
19
+ // `assignments_expr` (let/for/intersection_for). `let` is NOT a call in this
20
+ // grammar -- it has its own assignment list -- which the first draft of this
21
+ // feature got wrong and this test caught.
22
+ const char* const kRejected[] = {
23
+ "cube(1,);", // module instantiation
24
+ "translate([0,0,0],) cube(1);", // ...with a child
25
+ "function g(x) = x; y = g(1,);", // user function call
26
+ "y = max(1, 2,);", // builtin function call
27
+ "y = str(\"a\", \"b\",);",
28
+ "echo(1, 2,);",
29
+ "assert(true, \"m\",);",
30
+ // assignments_expr, not arguments
31
+ "y = let(x = 1, y = 2,) x + y;",
32
+ "let(x = 1,) cube(x);",
33
+ "for (i = [0:2],) cube(i);",
34
+ "intersection_for (i = [0:1],) cube(1);",
35
+ "a = [for (i = [0:1],) i];",
36
+ };
37
+
38
+ // Accepted by 2021.01, so accepted here even under the scope.
39
+ const char* const kAccepted[] = {
40
+ "a = [2, 4,];", // list literal
41
+ "a = [for (i = [0:2]) i,];", // list comprehension
42
+ "a = [each [1, 2],];",
43
+ "module m(a, b,) {} m(1, 2);", // module parameter declaration
44
+ "function f(a, b,) = a + b;", // function parameter declaration
45
+ "cube(1);", // no trailing comma at all
46
+ "y = max(1, 2);",
47
+ };
48
+
49
+ } // namespace
50
+
51
+ TEST(StrictCommas, CallArgumentTrailingCommaIsAnErrorInScope) {
52
+ StrictCommaScope strict;
53
+ for (const char* src : kRejected) {
54
+ EXPECT_THROW(parseSrc(src), ParseError) << src;
55
+ }
56
+ }
57
+
58
+ TEST(StrictCommas, EverythingElseKeepsItsTrailingComma) {
59
+ StrictCommaScope strict;
60
+ for (const char* src : kAccepted) {
61
+ EXPECT_NO_THROW(parseSrc(src)) << src;
62
+ }
63
+ }
64
+
65
+ TEST(StrictCommas, OffByDefault) {
66
+ // The whole set parses without the scope -- this is opt-in, and every
67
+ // existing caller must be unaffected.
68
+ for (const char* src : kRejected) {
69
+ EXPECT_NO_THROW(parseSrc(src)) << src;
70
+ }
71
+ }
72
+
73
+ TEST(StrictCommas, ScopeNestsAndRestores) {
74
+ EXPECT_FALSE(strictCommasEnabled());
75
+ {
76
+ StrictCommaScope outer;
77
+ EXPECT_TRUE(strictCommasEnabled());
78
+ {
79
+ StrictCommaScope inner;
80
+ EXPECT_TRUE(strictCommasEnabled());
81
+ }
82
+ EXPECT_TRUE(strictCommasEnabled()) << "an inner scope must not switch it off";
83
+ }
84
+ EXPECT_FALSE(strictCommasEnabled());
85
+ }
86
+
87
+ TEST(StrictCommas, RestoresWhenAParseThrows) {
88
+ try {
89
+ StrictCommaScope strict;
90
+ parseSrc("cube(1,);");
91
+ FAIL() << "expected ParseError";
92
+ } catch (const ParseError&) {
93
+ }
94
+ EXPECT_FALSE(strictCommasEnabled()) << "the scope must unwind with the exception";
95
+ }
96
+
97
+ TEST(StrictCommas, TheErrorPointsAtTheComma) {
98
+ StrictCommaScope strict;
99
+ try {
100
+ parseSrc("cube(1,);");
101
+ FAIL() << "expected ParseError";
102
+ } catch (const ParseError& e) {
103
+ const std::string what = e.what();
104
+ EXPECT_NE(what.find("trailing comma in argument list"), std::string::npos) << what;
105
+ }
106
+ }
107
+
108
+ TEST(StrictCommas, AnAssignmentListSaysSo) {
109
+ StrictCommaScope strict;
110
+ try {
111
+ parseSrc("y = let(x = 1,) x;");
112
+ FAIL() << "expected ParseError";
113
+ } catch (const ParseError& e) {
114
+ const std::string what = e.what();
115
+ EXPECT_NE(what.find("trailing comma in assignment list"), std::string::npos) << what;
116
+ }
117
+ }
@@ -39,13 +39,21 @@ struct ExportObject {
39
39
  // `color("red") body(); color("blue") detail();` leaves the detail
40
40
  // whole. Only the invisible interior is affected: the visible surface
41
41
  // is identical either way.
42
- // 3. One object per connected component.
42
+ // 3. One object per connected component -- OPTIONAL, and off by default.
43
+ // Rules 1 and 2 are about correctness; this one is presentation, and
44
+ // it is the one that diverges from OpenSCAD, which writes a single
45
+ // object however many disjoint pieces the model has. Splitting turned
46
+ // `cube(10); translate([20,0,0]) cube(10);` into two 3MF objects and a
47
+ // multiboard tile into hundreds, which slicers list individually and
48
+ // Prusa Slicer complains about (issue #319). Ask for it when separate
49
+ // pieces are what you want; leave it off to match OpenSCAD.
43
50
  //
44
51
  // A body Manifold rejected (an open shell is not a solid) can take part in
45
52
  // none of that: it keeps its own triangles and its own object, and its
46
53
  // 1-based index is appended to `openParts` for the caller to warn about.
47
54
  std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& bodies,
48
- std::vector<int>* openParts = nullptr);
55
+ std::vector<int>* openParts = nullptr,
56
+ bool splitComponents = false);
49
57
 
50
58
  // Writes a binary STL: composes every body's mesh into one solid (bodies
51
59
  // with no `.body` -- 2D-only sections -- or an empty Manifold are
@@ -130,6 +138,12 @@ struct ExportOptions {
130
138
  // break no topology, but slicers commonly discard them and are then
131
139
  // left with the holes their removal opens.
132
140
  bool stripSlivers = true;
141
+ // Rule 3 of splitBodiesForExport: give every disconnected piece its own
142
+ // object in the multi-object formats (3MF, AMF, OBJ, PLY, VRML, X3D).
143
+ // Off matches OpenSCAD, which writes one object per colour however many
144
+ // pieces it is in; see that function's own comment for why the default
145
+ // moved here.
146
+ bool splitComponents = false;
133
147
  };
134
148
 
135
149
  // Writes `bodies` to `path`, choosing the writer from the extension (or
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.7.2"
7
+ version = "1.9.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -292,13 +292,13 @@ def strip_slivers(verts, tris):
292
292
 
293
293
 
294
294
  def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
295
- strip_slivers: bool = True) -> list:
295
+ strip_slivers: bool = True, split_components: bool = False) -> list:
296
296
  """Write `geometry` to `path`. Returns the warnings to surface.
297
297
 
298
298
  `geometry` is the handle an Evaluator stashes on itself as
299
299
  `.geometry` -- the evaluated bodies, still on the C++ side. Export has
300
300
  to do real CSG (the implicit top-level union, the per-colour volume
301
- claim, the connected-component split), and going through the flattened
301
+ claim), and going through the flattened
302
302
  arrays the renderer gets would mean rebuilding every Manifold first:
303
303
  ~146ms on a 224k-triangle model, and Manifold's own provenance lost
304
304
  along the way.
@@ -309,9 +309,13 @@ def export_model(path: str, geometry, format: str = "", ascii_stl: bool = False,
309
309
  here refuses to write: a deliberately open surface is a legitimate
310
310
  export. Raises EvalError when there is no geometry, the format is
311
311
  unknown, or the file cannot be opened.
312
+
313
+ `split_components` gives every disconnected piece its own object in the
314
+ multi-object formats. Off by default, which is what OpenSCAD writes.
312
315
  """
313
316
  try:
314
- return list(_ext.export_model(path, geometry, format, ascii_stl, strip_slivers))
317
+ return list(_ext.export_model(path, geometry, format, ascii_stl, strip_slivers,
318
+ split_components))
315
319
  except Exception as e:
316
320
  raise EvalError(str(e)) from e
317
321
 
@@ -493,7 +497,7 @@ class Evaluator:
493
497
  self.dyn_explicit = set()
494
498
 
495
499
  def evaluate(self, source_path: str, viewport_params: Optional[dict] = None,
496
- generate: bool = True):
500
+ generate: bool = True, strict_commas: bool = False):
497
501
  """Run `source_path` and return (bodies, id_to_node).
498
502
 
499
503
  `generate=False` stops after the resolve pass. The script still runs
@@ -504,6 +508,13 @@ class Evaluator:
504
508
  the geometry without skipping the language.
505
509
 
506
510
  Ignored on the debugger path, which has no geometry handle anyway.
511
+
512
+ `strict_commas=True` makes a trailing comma in a call argument list
513
+ or a let/for assignment list a syntax error, as OpenSCAD 2021.01 did
514
+ -- `cube(1,)` and `let(x = 1,)`, but NOT `[2, 4,]` or
515
+ `module m(a, b,)`, which 2021.01 accepted. For checking a script
516
+ against that version; off, and no divergence from current OpenSCAD,
517
+ otherwise. Also ignored on the debugger path.
507
518
  """
508
519
  vp = viewport_params or {}
509
520
  try:
@@ -524,7 +535,8 @@ class Evaluator:
524
535
  else:
525
536
  (body_dicts, echoes, id_spans, csg_tree, profile_result, dyn,
526
537
  dyn_explicit, geometry) = _ext.evaluate(
527
- source_path, vp, self._manifold_cache, self._profile, generate)
538
+ source_path, vp, self._manifold_cache, self._profile, generate,
539
+ strict_commas)
528
540
  # The evaluated bodies, still on the C++ side. Stashed like
529
541
  # csg_tree/profile_result rather than returned, so
530
542
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -418,7 +418,8 @@ std::vector<std::array<float, 4>> carryTriColors(const std::vector<std::array<fl
418
418
 
419
419
  } // namespace
420
420
 
421
- std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& bodies, std::vector<int>* openParts) {
421
+ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& bodies, std::vector<int>* openParts,
422
+ bool splitComponents) {
422
423
  struct Solid {
423
424
  manifold::Manifold man;
424
425
  std::optional<std::array<float, 4>> color;
@@ -545,9 +546,12 @@ std::vector<ExportObject> splitBodiesForExport(const std::vector<ColoredBody>& b
545
546
 
546
547
  std::vector<ExportObject> out;
547
548
  for (const Claimed& g : claimedGroups) {
548
- // Decompose() is the rule-3 split. A single-component solid comes
549
- // back as a one-element list, so there is no special case here.
550
- std::vector<manifold::Manifold> parts = g.man.Decompose();
549
+ // Decompose() is the rule-3 split, and it is opt-in: without it this
550
+ // colour group stays one object however many disjoint pieces it is
551
+ // in, which is what OpenSCAD writes. A single-component solid comes
552
+ // back as a one-element list either way, so there is no special case.
553
+ std::vector<manifold::Manifold> parts;
554
+ if (splitComponents) parts = g.man.Decompose();
551
555
  if (parts.empty()) parts.push_back(g.man);
552
556
  for (const manifold::Manifold& part : parts) {
553
557
  if (part.IsEmpty()) continue;
@@ -1022,7 +1026,7 @@ std::vector<std::string> exportModel(const std::string& path, const std::vector<
1022
1026
  // its own -- that is what the file contains.
1023
1027
  for (std::string& w : checkExportBodies(bodies)) warnings.push_back(std::move(w));
1024
1028
  std::vector<int> openParts;
1025
- const std::vector<ExportObject> objects = splitBodiesForExport(bodies, &openParts);
1029
+ const std::vector<ExportObject> objects = splitBodiesForExport(bodies, &openParts, opts.splitComponents);
1026
1030
  reportOpen(openParts);
1027
1031
  if (objects.empty()) throw std::runtime_error("No geometry to export");
1028
1032
  if (ext == ".3mf") {
@@ -163,10 +163,16 @@ TEST(ExportAmf, WritesOneObjectAndOneMaterialPerColour) {
163
163
  }
164
164
 
165
165
  TEST(ExportAmf, TwoObjectsSharingAColourShareOneMaterial) {
166
+ // splitComponents, because two objects is the premise: same-coloured
167
+ // disjoint pieces are ONE object by default (see the DisjointPieces
168
+ // tests below). What is under test here is that the two of them share a
169
+ // single material entry rather than each declaring their own.
166
170
  std::vector<ColoredBody> bodies =
167
171
  evalToBodies("color(\"red\") cube(2); translate([10,0,0]) color(\"red\") cube(2);");
168
172
  const std::string path = tempPath("shared.amf").string();
169
- exportModel(path, bodies, ExportOptions{});
173
+ ExportOptions opts;
174
+ opts.splitComponents = true;
175
+ exportModel(path, bodies, opts);
170
176
 
171
177
  const std::string xml = readFile(path);
172
178
  EXPECT_EQ(countOf(xml, "<object id="), 2u);
@@ -174,6 +180,50 @@ TEST(ExportAmf, TwoObjectsSharingAColourShareOneMaterial) {
174
180
  std::remove(path.c_str());
175
181
  }
176
182
 
183
+ // Issue #319: a model in several disconnected pieces was written as one
184
+ // object PER PIECE, so a slicer listed them individually -- hundreds of
185
+ // entries for a multiboard tile, and Prusa Slicer objecting. OpenSCAD writes
186
+ // one object, and so does this by default now; the split is still available
187
+ // for callers that want the pieces apart.
188
+ TEST(ExportSplit, DisjointPiecesAreOneObjectByDefault) {
189
+ std::vector<ColoredBody> bodies =
190
+ evalToBodies("cube(10); translate([20,0,0]) cube(10); translate([40,0,0]) cube(10);");
191
+ const std::string path = tempPath("joined.amf").string();
192
+ exportModel(path, bodies, ExportOptions{});
193
+
194
+ const std::string xml = readFile(path);
195
+ EXPECT_EQ(countOf(xml, "<object id="), 1u);
196
+ EXPECT_EQ(countOf(xml, "<volume"), 1u);
197
+ std::remove(path.c_str());
198
+ }
199
+
200
+ TEST(ExportSplit, DisjointPiecesSplitWhenAsked) {
201
+ std::vector<ColoredBody> bodies =
202
+ evalToBodies("cube(10); translate([20,0,0]) cube(10); translate([40,0,0]) cube(10);");
203
+ const std::string path = tempPath("split.amf").string();
204
+ ExportOptions opts;
205
+ opts.splitComponents = true;
206
+ exportModel(path, bodies, opts);
207
+
208
+ const std::string xml = readFile(path);
209
+ EXPECT_EQ(countOf(xml, "<object id="), 3u);
210
+ std::remove(path.c_str());
211
+ }
212
+
213
+ // Rule 2 is not what became optional: differently-coloured solids must stay
214
+ // separate objects either way, or a multi-material export loses its colours.
215
+ TEST(ExportSplit, ColoursStayApartWithTheSplitOff) {
216
+ std::vector<ColoredBody> bodies =
217
+ evalToBodies("color(\"red\") cube(10); translate([20,0,0]) color(\"blue\") cube(10);");
218
+ const std::string path = tempPath("colours.amf").string();
219
+ exportModel(path, bodies, ExportOptions{});
220
+
221
+ const std::string xml = readFile(path);
222
+ EXPECT_EQ(countOf(xml, "<object id="), 2u);
223
+ EXPECT_EQ(countOf(xml, "<material id="), 2u);
224
+ std::remove(path.c_str());
225
+ }
226
+
177
227
  TEST(ExportAmf, MaterialAndObjectIdsStartAtOne) {
178
228
  // id 0 is reserved by the spec; a reader may drop anything using it.
179
229
  std::vector<ColoredBody> bodies = evalToBodies("cube(2);");
@@ -633,3 +633,66 @@ def test_degenerate_ranges_do_not_overflow_the_element_count(tmp_path):
633
633
  # Silent for NaN; the unbounded pair warn, with the reference's number.
634
634
  assert len(warnings) == 2, warnings
635
635
  assert all("too many elements (4294967295)" in w for w in warnings), warnings
636
+
637
+
638
+ def test_export_model_facade_accepts_split_components(tmp_path):
639
+ """The Python facade's signature must track the nanobind binding's.
640
+
641
+ They are two hand-written parameter lists for one call, and adding
642
+ `split_components` to the binding alone left the facade dropping it --
643
+ a TypeError the C++ suite cannot see, because it never goes through
644
+ Python. Caught in practice, hence this.
645
+ """
646
+ from openscad_cpp_evaluator import Evaluator, export_model
647
+
648
+ src = tmp_path / "pieces.scad"
649
+ src.write_text("cube(10); translate([20,0,0]) cube(10); translate([40,0,0]) cube(10);")
650
+ ev = Evaluator()
651
+ ev.evaluate(str(src), {})
652
+
653
+ def objects_in(path, **kwargs):
654
+ export_model(str(path), ev.geometry, **kwargs)
655
+ import zipfile
656
+ with zipfile.ZipFile(path) as z:
657
+ return z.read("3D/3dmodel.model").decode().count("<object ")
658
+
659
+ # Default matches OpenSCAD: one object however many pieces.
660
+ assert objects_in(tmp_path / "joined.3mf") == 1
661
+ assert objects_in(tmp_path / "split.3mf", split_components=True) == 3
662
+
663
+
664
+ def test_strict_commas_reaches_the_parser(tmp_path):
665
+ """Evaluator.evaluate(strict_commas=True) must survive both hand-written
666
+ parameter lists -- the facade's and the nanobind binding's. The last
667
+ argument added this way was dropped by the facade and no C++ test could
668
+ see it (see test_export_model_facade_accepts_split_components)."""
669
+ import pytest
670
+ from openscad_cpp_evaluator import Evaluator, EvalError
671
+
672
+ rejected = tmp_path / "call.scad"
673
+ rejected.write_text("cube(1,);\n")
674
+ accepted = tmp_path / "list.scad"
675
+ accepted.write_text("a = [2, 4,];\ncube(a[0]);\n")
676
+
677
+ # Off by default: both parse, as they do in current OpenSCAD.
678
+ Evaluator(echo_fn=lambda _m: None).evaluate(str(rejected), {})
679
+ Evaluator(echo_fn=lambda _m: None).evaluate(str(accepted), {})
680
+
681
+ # On: a call's trailing comma is a syntax error, a list literal's is not
682
+ # -- 2021.01 accepted the list.
683
+ with pytest.raises(EvalError):
684
+ Evaluator(echo_fn=lambda _m: None).evaluate(str(rejected), {}, strict_commas=True)
685
+ Evaluator(echo_fn=lambda _m: None).evaluate(str(accepted), {}, strict_commas=True)
686
+
687
+
688
+ def test_strict_commas_does_not_leak_between_evaluations(tmp_path):
689
+ """The parser mode is a scope object; a strict parse must not leave it on
690
+ for the next caller."""
691
+ import pytest
692
+ from openscad_cpp_evaluator import Evaluator, EvalError
693
+
694
+ src = tmp_path / "call.scad"
695
+ src.write_text("cube(1,);\n")
696
+ with pytest.raises(EvalError):
697
+ Evaluator(echo_fn=lambda _m: None).evaluate(str(src), {}, strict_commas=True)
698
+ Evaluator(echo_fn=lambda _m: None).evaluate(str(src), {}) # must not raise