openscad-cpp-evaluator 1.8.0__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.8.0 → openscad_cpp_evaluator-1.9.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/bindings/module.cpp +12 -3
  3. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +38 -0
  4. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/api.cpp +24 -4
  5. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +3 -0
  6. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/parser.y +24 -2
  7. {openscad_cpp_evaluator-1.8.0 → 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.8.0 → openscad_cpp_evaluator-1.9.0}/pyproject.toml +1 -1
  10. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/python/openscad_cpp_evaluator/__init__.py +10 -2
  11. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_python_bindings.py +37 -0
  12. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tools/cli/cli_lib.cpp +9 -1
  13. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/.github/workflows/ci.yml +0 -0
  14. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/.github/workflows/wheels.yml +0 -0
  15. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/.gitignore +0 -0
  16. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/.gitmodules +0 -0
  17. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/CLAUDE.md +0 -0
  18. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/CMakeLists.txt +0 -0
  19. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/LICENSE +0 -0
  20. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/README.md +0 -0
  21. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/bindings/CMakeLists.txt +0 -0
  22. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/bindings/ast_to_py.cpp +0 -0
  23. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/cmake/embed_font.cmake +0 -0
  24. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/examples/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/examples/eval_perf_benchmark.cpp +0 -0
  26. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/examples/manifold_cache_reuse.cpp +0 -0
  27. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/examples/minimal_debugger.cpp +0 -0
  28. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  29. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/.gitignore +0 -0
  30. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  31. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/README.md +0 -0
  33. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  46. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  50. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  51. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  52. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  53. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  54. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  55. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  56. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  57. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  58. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  59. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  60. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  61. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  62. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  63. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  64. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  65. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  66. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  67. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  68. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  69. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  70. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  71. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  72. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  73. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  74. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  75. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  76. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  77. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  78. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  79. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  80. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  81. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  82. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  83. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  84. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  85. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  86. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  87. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  88. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  89. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  90. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  91. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  92. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  93. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  94. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  95. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  96. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  97. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  98. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  99. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  100. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  101. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  102. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  103. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  104. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  105. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  106. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  107. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  108. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  109. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  110. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  111. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  112. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  113. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  114. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  115. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  116. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  117. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  118. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  119. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  120. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  121. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  122. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  123. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  124. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  125. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  126. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/resources/fonts/LICENSE +0 -0
  127. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  128. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/CMakeLists.txt +0 -0
  129. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/booleans.cpp +0 -0
  130. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/builtins.hpp +0 -0
  131. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/call_args.cpp +0 -0
  132. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/color.cpp +0 -0
  133. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/control.cpp +0 -0
  134. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/dxf_dim.cpp +0 -0
  135. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/extrude.cpp +0 -0
  136. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/function_builtins.cpp +0 -0
  137. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/import.cpp +0 -0
  138. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/modifiers.cpp +0 -0
  139. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/primitives_2d.cpp +0 -0
  140. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/primitives_3d.cpp +0 -0
  141. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/registry.cpp +0 -0
  142. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/roof.cpp +0 -0
  143. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/segments.cpp +0 -0
  144. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/surface.cpp +0 -0
  145. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/text.cpp +0 -0
  146. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/topology.cpp +0 -0
  147. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/builtins/transforms.cpp +0 -0
  148. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/bytecode_compiler.cpp +0 -0
  149. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/bytecode_vm.cpp +0 -0
  150. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/colored_body.cpp +0 -0
  151. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/csg_generate.cpp +0 -0
  152. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/csg_resolve.cpp +0 -0
  153. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/css_colors.cpp +0 -0
  154. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/debug/debug_repl.cpp +0 -0
  155. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/debug_profile.cpp +0 -0
  156. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/eval_context.cpp +0 -0
  157. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/eval_error.cpp +0 -0
  158. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/eval_use.cpp +0 -0
  159. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/export.cpp +0 -0
  160. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/expr_eval.cpp +0 -0
  161. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/format_closure.cpp +0 -0
  162. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/import/dxf_import.cpp +0 -0
  163. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/import/mesh_import.cpp +0 -0
  164. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/import/surface_load.cpp +0 -0
  165. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/import/svg_import.cpp +0 -0
  166. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/manifold_cache.cpp +0 -0
  167. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/measure_geometry.cpp +0 -0
  168. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/mesh_check.cpp +0 -0
  169. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/stmt_eval.cpp +0 -0
  170. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/text/font_match.cpp +0 -0
  171. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/text/freetype_font_provider.cpp +0 -0
  172. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/text/text_metrics.cpp +0 -0
  173. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/user_calls.cpp +0 -0
  174. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/utf8.cpp +0 -0
  175. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/value.cpp +0 -0
  176. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/src/zip_stored.cpp +0 -0
  177. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/CMakeLists.txt +0 -0
  178. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_booleans.cpp +0 -0
  179. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_bytecode_compiler.cpp +0 -0
  180. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_children_separate.cpp +0 -0
  181. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_cli.cpp +0 -0
  182. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_control_flow.cpp +0 -0
  183. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_csg_tree.cpp +0 -0
  184. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_debug_hooks.cpp +0 -0
  185. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_dxf_svg_import.cpp +0 -0
  186. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_eval_context.cpp +0 -0
  187. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_export.cpp +0 -0
  188. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_expr_eval.cpp +0 -0
  189. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_extrude_roof.cpp +0 -0
  190. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_feature_detection.cpp +0 -0
  191. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_function_builtins.cpp +0 -0
  192. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_helpers.hpp +0 -0
  193. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_import_export.cpp +0 -0
  194. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_include_cache.cpp +0 -0
  195. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_manifold_cache.cpp +0 -0
  196. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_mesh_check.cpp +0 -0
  197. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_multi_color_merge.cpp +0 -0
  198. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_primitives.cpp +0 -0
  199. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_profiling.cpp +0 -0
  200. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_render_expr.cpp +0 -0
  201. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_scope_trail.cpp +0 -0
  202. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_scoping.cpp +0 -0
  203. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_surface.cpp +0 -0
  204. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_tail_calls.cpp +0 -0
  205. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_text.cpp +0 -0
  206. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_transforms.cpp +0 -0
  207. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_unexpected_args.cpp +0 -0
  208. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_use.cpp +0 -0
  209. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_value.cpp +0 -0
  210. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_viewport_params.cpp +0 -0
  211. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tests/test_zip_stored.cpp +0 -0
  212. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tools/cli/CMakeLists.txt +0 -0
  213. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tools/cli/cli_lib.hpp +0 -0
  214. {openscad_cpp_evaluator-1.8.0 → openscad_cpp_evaluator-1.9.0}/tools/cli/main.cpp +0 -0
  215. {openscad_cpp_evaluator-1.8.0 → 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.8.0
3
+ Version: 1.9.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -375,7 +375,7 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
375
375
 
376
376
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
377
377
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
378
- bool generate) {
378
+ bool generate, bool strictCommas) {
379
379
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
380
380
 
381
381
  std::vector<oscadeval::ColoredBody> bodies;
@@ -389,6 +389,12 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
389
389
  nb::gil_scoped_release rel;
390
390
  auto logFn = [&echoes](const std::string& m) { echoes.push_back(m); };
391
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();
392
398
  oscad::ParsedProgram program = oscad::getProgramFromFile(path);
393
399
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
394
400
  oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
@@ -801,11 +807,14 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
801
807
  "rather than restated here, so the Python side cannot drift from what can actually be written.");
802
808
 
803
809
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
804
- nb::arg("profile") = false, nb::arg("generate") = true,
810
+ nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
805
811
  "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).\n"
806
812
  "generate=False stops after the resolve pass: the script runs and reports everything "
807
813
  "it normally would, but no Manifold geometry is built, and neither bodies nor csg_tree "
808
- "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.");
809
818
  m.def("parse_decls", &parseDecls, nb::arg("path"),
810
819
  "Parse a .scad file; return top-level declaration (namespace, name, start, end, line, column, origin) tuples.");
811
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
+ }
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.8.0"
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"
@@ -497,7 +497,7 @@ class Evaluator:
497
497
  self.dyn_explicit = set()
498
498
 
499
499
  def evaluate(self, source_path: str, viewport_params: Optional[dict] = None,
500
- generate: bool = True):
500
+ generate: bool = True, strict_commas: bool = False):
501
501
  """Run `source_path` and return (bodies, id_to_node).
502
502
 
503
503
  `generate=False` stops after the resolve pass. The script still runs
@@ -508,6 +508,13 @@ class Evaluator:
508
508
  the geometry without skipping the language.
509
509
 
510
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.
511
518
  """
512
519
  vp = viewport_params or {}
513
520
  try:
@@ -528,7 +535,8 @@ class Evaluator:
528
535
  else:
529
536
  (body_dicts, echoes, id_spans, csg_tree, profile_result, dyn,
530
537
  dyn_explicit, geometry) = _ext.evaluate(
531
- source_path, vp, self._manifold_cache, self._profile, generate)
538
+ source_path, vp, self._manifold_cache, self._profile, generate,
539
+ strict_commas)
532
540
  # The evaluated bodies, still on the C++ side. Stashed like
533
541
  # csg_tree/profile_result rather than returned, so
534
542
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -659,3 +659,40 @@ def test_export_model_facade_accepts_split_components(tmp_path):
659
659
  # Default matches OpenSCAD: one object however many pieces.
660
660
  assert objects_in(tmp_path / "joined.3mf") == 1
661
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
@@ -222,6 +222,7 @@ int runCli(const std::vector<std::string>& args, std::istream& in, std::ostream&
222
222
  std::string profileMinSelfStr;
223
223
  std::string profileMinCallsStr;
224
224
  bool debug = false;
225
+ bool strictCommas = false;
225
226
  for (size_t i = 0; i < args.size(); ++i) {
226
227
  const std::string& arg = args[i];
227
228
  if (arg == "-o" && i + 1 < args.size()) {
@@ -242,6 +243,8 @@ int runCli(const std::vector<std::string>& args, std::istream& in, std::ostream&
242
243
  profileMinCallsStr = args[++i];
243
244
  } else if (arg == "--debug") {
244
245
  debug = true;
246
+ } else if (arg == "--strict-commas") {
247
+ strictCommas = true;
245
248
  } else if (inputPath.empty()) {
246
249
  inputPath = arg;
247
250
  }
@@ -249,7 +252,7 @@ int runCli(const std::vector<std::string>& args, std::istream& in, std::ostream&
249
252
  if (inputPath.empty() || outputPath.empty()) {
250
253
  err << "usage: openscad-cpp-evaluator <input.scad> -o <output.{stl,obj,off,3mf,ply,wrl,x3d}> [--format stl|obj|off|3mf|ply|wrl|x3d] [--ascii-stl] "
251
254
  "[--profile FILENAME [--profile-format text|csv] [--profile-sort self|cumulative|calls|name] "
252
- "[--profile-min-self SECONDS] [--profile-min-calls N]] [--debug]\n";
255
+ "[--profile-min-self SECONDS] [--profile-min-calls N]] [--debug] [--strict-commas]\n";
253
256
  return 1;
254
257
  }
255
258
  const std::string fmt = formatForPath(format, outputPath);
@@ -293,6 +296,11 @@ int runCli(const std::vector<std::string>& args, std::istream& in, std::ostream&
293
296
  }
294
297
 
295
298
  try {
299
+ // Scoped to the parse: a trailing comma in a call argument list or a
300
+ // let/for assignment list is a syntax error while it is in effect,
301
+ // as OpenSCAD 2021.01 had it. See oscad::StrictCommaScope.
302
+ std::optional<oscad::StrictCommaScope> strict;
303
+ if (strictCommas) strict.emplace();
296
304
  std::vector<std::unique_ptr<oscad::ASTNode>> ast = oscad::getASTFromFile(inputPath);
297
305
  ResolvedUseScopes used = resolveUseScopes(ast, inputPath, [&out](const std::string& msg) { out << msg << "\n"; });
298
306