openscad-cpp-evaluator 1.37.2__tar.gz → 1.38.1__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 (232) hide show
  1. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/CLAUDE.md +30 -0
  2. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +1 -0
  4. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +14 -0
  5. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +1 -0
  6. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/expression.cpp +13 -0
  7. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +5 -0
  8. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +1 -0
  9. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/grammar/lexer.l +3 -0
  10. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/grammar/parser.y +12 -1
  11. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +6 -0
  12. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/pretty_print.cpp +3 -0
  13. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +11 -0
  14. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
  15. openscad_cpp_evaluator-1.38.1/external/openscad_cpp_parser/tests/test_profile_time.cpp +86 -0
  16. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/csg_node.hpp +7 -0
  17. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/evaluator.hpp +17 -0
  18. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/pyproject.toml +1 -1
  19. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/CMakeLists.txt +1 -0
  20. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/builtins.hpp +6 -0
  21. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/extrude.cpp +100 -12
  22. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/function_builtins.cpp +1 -0
  23. openscad_cpp_evaluator-1.38.1/src/builtins/profile_time.cpp +63 -0
  24. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/registry.cpp +2 -0
  25. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/coverage.cpp +6 -0
  26. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/csg_generate.cpp +11 -0
  27. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/csg_resolve.cpp +19 -0
  28. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/expr_eval.cpp +15 -0
  29. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/format_closure.cpp +4 -0
  30. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/CMakeLists.txt +1 -0
  31. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_extrude_roof.cpp +129 -0
  32. openscad_cpp_evaluator-1.38.1/tests/test_profile_time.cpp +139 -0
  33. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/uv.lock +1 -1
  34. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/.github/workflows/ci.yml +0 -0
  35. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/.github/workflows/wheels.yml +0 -0
  36. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/.gitignore +0 -0
  37. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/.gitmodules +0 -0
  38. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/CMakeLists.txt +0 -0
  39. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/LICENSE +0 -0
  40. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/README.md +0 -0
  41. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/bindings/CMakeLists.txt +0 -0
  42. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/bindings/ast_to_py.cpp +0 -0
  43. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/bindings/module.cpp +0 -0
  44. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/cmake/embed_font.cmake +0 -0
  45. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/cmake/patch_manifold.cmake +0 -0
  46. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/examples/CMakeLists.txt +0 -0
  47. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/examples/eval_perf_benchmark.cpp +0 -0
  48. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/examples/manifold_cache_reuse.cpp +0 -0
  49. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/examples/minimal_debugger.cpp +0 -0
  50. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  51. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/.gitignore +0 -0
  52. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  53. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  54. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/README.md +0 -0
  55. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  56. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  57. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  58. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  59. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  60. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  61. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  62. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  63. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  64. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  65. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  66. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  67. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  68. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  69. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  70. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  71. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
  72. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  73. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  74. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  75. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  76. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  77. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  78. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  79. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  80. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  81. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
  82. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  83. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  84. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  85. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  86. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  87. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  88. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  89. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  90. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  91. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  92. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  93. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  94. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  95. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  96. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  97. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  98. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  99. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  100. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  101. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  102. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  103. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  104. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  105. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  106. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  107. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  108. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  109. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  110. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  111. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  112. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  113. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  114. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  115. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  116. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  117. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  118. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  119. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  120. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  121. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  122. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  123. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  124. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
  125. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  126. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  127. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  128. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  129. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  130. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  131. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  132. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  133. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  134. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  135. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  136. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  137. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  138. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  139. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  140. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  141. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
  142. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  143. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
  144. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/resources/fonts/LICENSE +0 -0
  145. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  146. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  147. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  148. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  149. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/booleans.cpp +0 -0
  150. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/call_args.cpp +0 -0
  151. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/color.cpp +0 -0
  152. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/control.cpp +0 -0
  153. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/dxf_dim.cpp +0 -0
  154. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/import.cpp +0 -0
  155. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/modifiers.cpp +0 -0
  156. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/primitives_2d.cpp +0 -0
  157. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/primitives_3d.cpp +0 -0
  158. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/roof.cpp +0 -0
  159. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/segments.cpp +0 -0
  160. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/surface.cpp +0 -0
  161. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/text.cpp +0 -0
  162. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/topology.cpp +0 -0
  163. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/builtins/transforms.cpp +0 -0
  164. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/bytecode_compiler.cpp +0 -0
  165. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/bytecode_vm.cpp +0 -0
  166. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/colored_body.cpp +0 -0
  167. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/css_colors.cpp +0 -0
  168. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/debug/debug_repl.cpp +0 -0
  169. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/debug_profile.cpp +0 -0
  170. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/eval_context.cpp +0 -0
  171. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/eval_error.cpp +0 -0
  172. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/eval_use.cpp +0 -0
  173. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/export.cpp +0 -0
  174. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/import/dxf_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/import/mesh_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/import/scene_import.cpp +0 -0
  177. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/import/surface_load.cpp +0 -0
  178. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/import/svg_import.cpp +0 -0
  179. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/manifold_cache.cpp +0 -0
  180. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/measure_geometry.cpp +0 -0
  181. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/mesh_check.cpp +0 -0
  182. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/stmt_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/text/font_match.cpp +0 -0
  184. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/text/freetype_font_provider.cpp +0 -0
  185. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/text/text_metrics.cpp +0 -0
  186. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/user_calls.cpp +0 -0
  187. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/utf8.cpp +0 -0
  188. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/value.cpp +0 -0
  189. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/xkcd_colors.cpp +0 -0
  190. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/xkcd_colors.hpp +0 -0
  191. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/src/zip_stored.cpp +0 -0
  192. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_booleans.cpp +0 -0
  193. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_bytecode_compiler.cpp +0 -0
  194. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_children_separate.cpp +0 -0
  195. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_cli.cpp +0 -0
  196. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_control_flow.cpp +0 -0
  197. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_coverage.cpp +0 -0
  198. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_csg_tree.cpp +0 -0
  199. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_debug_hooks.cpp +0 -0
  200. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_discretizer.cpp +0 -0
  201. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_dxf_svg_import.cpp +0 -0
  202. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_eval_context.cpp +0 -0
  203. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_export.cpp +0 -0
  204. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_expr_eval.cpp +0 -0
  205. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_primitives.cpp +0 -0
  214. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_profiling.cpp +0 -0
  215. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_python_bindings.py +0 -0
  216. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_render_expr.cpp +0 -0
  217. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_scope_trail.cpp +0 -0
  218. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_scoping.cpp +0 -0
  219. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_surface.cpp +0 -0
  220. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_tail_calls.cpp +0 -0
  221. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_text.cpp +0 -0
  222. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_transforms.cpp +0 -0
  223. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_undef_args.cpp +0 -0
  224. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_unexpected_args.cpp +0 -0
  225. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_use.cpp +0 -0
  226. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_value.cpp +0 -0
  227. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_viewport_params.cpp +0 -0
  228. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tests/test_zip_stored.cpp +0 -0
  229. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tools/cli/CMakeLists.txt +0 -0
  230. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tools/cli/cli_lib.cpp +0 -0
  231. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tools/cli/cli_lib.hpp +0 -0
  232. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.1}/tools/cli/main.cpp +0 -0
@@ -1866,3 +1866,33 @@ A compiled function keeps its parameters in frame slots, which no `EvalContext`
1866
1866
  can see, and the children are statement opcodes that resolve names through the
1867
1867
  context — without this, `function f(w) = render() { cube(w); }.volume;` finds no
1868
1868
  `w` and silently measures nothing.
1869
+
1870
+ ## `profile_time()`
1871
+
1872
+ `profile_time([label]) { ... }` prints `PROFILE: label: T ms (script S ms, geometry G ms)`;
1873
+ `x = profile_time([label]) expr;` prints `PROFILE: label: T ms` and evaluates to `expr`'s value.
1874
+ `supported_feature("profile-time")` is 1. The label is optional (default `line N of file`), and
1875
+ is evaluated before the clock starts. Times under 1 ms print in microseconds (`fmtMs`), so a fast part never reads `0.00 ms`.
1876
+
1877
+ ### Implementation notes
1878
+
1879
+ - **Parser:** `profile_time` is a reserved keyword (`KW_PROFILE_TIME`) for the same reason as
1880
+ `render`. The statement form is a plain `ModularCall` named `profile_time`; the expression form
1881
+ is a `ProfileTimeOp` node shaped like `EchoOp`, with a required body.
1882
+ - **Script time** is taken around the resolve function's call in `Evaluator::evalModularCall`
1883
+ (csg_resolve.cpp), not inside `resolveProfileTime`, so argument evaluation and every child
1884
+ count. It is stored on `CSGNode::profileScriptMs`, which is deliberately not in `cacheKey()` --
1885
+ a timing in the key would make every profiled subtree a permanent miss.
1886
+ - **Geometry time** is taken in `generateTreeImpl`, from *before* the cache lookup, and printed
1887
+ when the node finishes, so a hit reports `geometry cached` rather than vanishing. A hit on an
1888
+ **ancestor** never visits the profile_time() node at all; `reportCachedProfiles` walks the hit
1889
+ subtree for them, but only when the run resolved any (`profileNodeCount_`), so a model without
1890
+ profile_time() pays nothing on its hits.
1891
+ - **Resolve-only runs** (`evaluate(..., generate=false)`) print `script S ms (no geometry built)`
1892
+ at resolve time instead (`generateRequested_`).
1893
+ - **Expression form** is interpreter-only: the bytecode compiler has no case for
1894
+ `ProfileTimeOp`, so its `default:` throws `NotCompilable` and the containing function falls back
1895
+ to the interpreter. It is also deliberately absent from `simplifyTailStep` -- the clock must stop
1896
+ after the body, which a tail step would not wait for.
1897
+ - `PROFILE:` lines go straight to `echoFn_`, not through `emitWarning`, so the ManifoldCache never
1898
+ stores or replays them (a replay would print a stale time as if it were current).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.37.2
3
+ Version: 1.38.1
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -34,6 +34,7 @@ enum class NodeKind {
34
34
  LetOp,
35
35
  EchoOp,
36
36
  AssertOp,
37
+ ProfileTimeOp,
37
38
  FunctionLiteral,
38
39
  // Operators
39
40
  UnaryMinusOp,
@@ -217,6 +217,20 @@ public:
217
217
  void buildScope(Scope& parentScope) override;
218
218
  };
219
219
 
220
+ // `profile_time(arguments...) body` -- same shape as EchoOp; times the body.
221
+ // Unlike echo/assert the body is required: timing nothing is meaningless.
222
+ class ProfileTimeOp : public Expression {
223
+ public:
224
+ ProfileTimeOp(Position position, std::vector<std::unique_ptr<Argument>> arguments, std::unique_ptr<Expression> body)
225
+ : Expression(NodeKind::ProfileTimeOp, std::move(position)), arguments(std::move(arguments)), body(std::move(body)) {}
226
+
227
+ std::vector<std::unique_ptr<Argument>> arguments;
228
+ std::unique_ptr<Expression> body;
229
+
230
+ std::string toString() const override;
231
+ void buildScope(Scope& parentScope) override;
232
+ };
233
+
220
234
  // `assert(arguments...) body` -- same shape as EchoOp.
221
235
  class AssertOp : public Expression {
222
236
  public:
@@ -21,6 +21,7 @@ const char* nodeKindName(NodeKind kind) {
21
21
  case NodeKind::LetOp: return "LetOp";
22
22
  case NodeKind::EchoOp: return "EchoOp";
23
23
  case NodeKind::AssertOp: return "AssertOp";
24
+ case NodeKind::ProfileTimeOp: return "ProfileTimeOp";
24
25
  case NodeKind::FunctionLiteral: return "FunctionLiteral";
25
26
  case NodeKind::UnaryMinusOp: return "UnaryMinusOp";
26
27
  case NodeKind::AdditionOp: return "AdditionOp";
@@ -134,6 +134,18 @@ void EchoOp::buildScope(Scope& parentScope) {
134
134
  body->buildScope(parentScope);
135
135
  }
136
136
 
137
+ std::string ProfileTimeOp::toString() const {
138
+ return "profile_time(" + joinToString(arguments, ", ") + ") " + body->toString();
139
+ }
140
+
141
+ void ProfileTimeOp::buildScope(Scope& parentScope) {
142
+ setScope(parentScope);
143
+ for (auto& a : arguments) {
144
+ a->buildScope(parentScope);
145
+ }
146
+ body->buildScope(parentScope);
147
+ }
148
+
137
149
  std::string AssertOp::toString() const {
138
150
  return "assert(" + joinToString(arguments, ", ") + ") " + body->toString();
139
151
  }
@@ -219,6 +231,7 @@ int operatorPrecedence(NodeKind kind) {
219
231
  case NodeKind::LetOp:
220
232
  case NodeKind::EchoOp:
221
233
  case NodeKind::AssertOp:
234
+ case NodeKind::ProfileTimeOp:
222
235
  case NodeKind::FunctionLiteral:
223
236
  return 0;
224
237
  case NodeKind::LogicalOrOp:
@@ -88,6 +88,11 @@ NodePtr makeEchoOp(ParserDriver& driver, const OscadLocation& loc, NodeList argu
88
88
  nodeCast<Expression>(std::move(body)));
89
89
  }
90
90
 
91
+ NodePtr makeProfileTimeOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body) {
92
+ return std::make_unique<ProfileTimeOp>(driver.toPosition(loc), nodeListCast<Argument>(std::move(arguments)),
93
+ nodeCast<Expression>(std::move(body)));
94
+ }
95
+
91
96
  NodePtr makeAssertOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body) {
92
97
  return std::make_unique<AssertOp>(driver.toPosition(loc), nodeListCast<Argument>(std::move(arguments)),
93
98
  nodeCast<Expression>(std::move(body)));
@@ -104,6 +104,7 @@ NodePtr makeAssignment(ParserDriver& driver, const OscadLocation& loc, const Osc
104
104
 
105
105
  NodePtr makeLetOp(ParserDriver& driver, const OscadLocation& loc, NodeList assignments, NodePtr body);
106
106
  NodePtr makeEchoOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body);
107
+ NodePtr makeProfileTimeOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body);
107
108
  NodePtr makeAssertOp(ParserDriver& driver, const OscadLocation& loc, NodeList arguments, NodePtr body);
108
109
  NodePtr makeFunctionLiteral(ParserDriver& driver, const OscadLocation& loc, NodeList parameters, NodePtr body);
109
110
 
@@ -52,6 +52,9 @@ const std::unordered_map<std::string, KeywordFactory>& keywordTable() {
52
52
  // apart from a function call. Note `$render` still lexes as NAME --
53
53
  // this table is keyed on the full IDENT text, including the `$`.
54
54
  {"render", [](const OscadLocation& l) { return yy::parser::make_KW_RENDER(l); }},
55
+ // Reserved for the same reason as `render`: its expression form
56
+ // (`x = profile_time("label") expr;`) starts like a function call.
57
+ {"profile_time", [](const OscadLocation& l) { return yy::parser::make_KW_PROFILE_TIME(l); }},
55
58
  {"undef", [](const OscadLocation& l) { return yy::parser::make_KW_UNDEF(l); }},
56
59
  {"true", [](const OscadLocation& l) { return yy::parser::make_KW_TRUE(l); }},
57
60
  {"false", [](const OscadLocation& l) { return yy::parser::make_KW_FALSE(l); }},
@@ -93,6 +93,7 @@
93
93
  KW_INTERSECTION_FOR "intersection_for"
94
94
  KW_EACH "each"
95
95
  KW_RENDER "render"
96
+ KW_PROFILE_TIME "profile_time"
96
97
  KW_UNDEF "undef"
97
98
  KW_TRUE "true"
98
99
  KW_FALSE "false"
@@ -158,7 +159,7 @@
158
159
  %type <NodePtr> modifier_show_only modifier_highlight modifier_background modifier_disable
159
160
  %type <NodePtr> if_statement ifelse_statement
160
161
  %type <NodePtr> modular_for modular_intersection_for modular_let modular_assert modular_echo modular_call
161
- %type <NodePtr> render_stmt render_expr
162
+ %type <NodePtr> render_stmt render_expr profile_time_stmt
162
163
  %type <NodePtr> expr opchain postfix primary
163
164
  %type <NodePtr> range_expr vector_expr vector_element
164
165
  %type <NodePtr> listcomp_elements listcomp_paren_expr listcomp_let listcomp_each
@@ -330,6 +331,7 @@ single_module_instantiation:
330
331
  | modular_echo { $$ = std::move($1); }
331
332
  | modular_call { $$ = std::move($1); }
332
333
  | render_stmt { $$ = std::move($1); }
334
+ | profile_time_stmt { $$ = std::move($1); }
333
335
  ;
334
336
 
335
337
  modular_for:
@@ -378,6 +380,14 @@ render_stmt:
378
380
  }
379
381
  ;
380
382
 
383
+ // Same arrangement as render_stmt: the keyword exists only for the
384
+ // expression form, and the statement form is an ordinary builtin call.
385
+ profile_time_stmt:
386
+ "profile_time" "(" arguments ")" child_statement {
387
+ $$ = makeModularCall(driver, @$, @1, "profile_time", std::move($3), std::move($5));
388
+ }
389
+ ;
390
+
381
391
  // -- Expressions ----------------------------------------------------------
382
392
  //
383
393
  // `expr` covers let/assert/echo/funclit_def/ternary plus the operator
@@ -393,6 +403,7 @@ expr:
393
403
  | "assert" "(" arguments ")" { $$ = makeAssertOp(driver, @$, std::move($3), makeUndefinedLiteral(driver, @$)); }
394
404
  | "echo" "(" arguments ")" expr { $$ = makeEchoOp(driver, @$, std::move($3), std::move($5)); }
395
405
  | "echo" "(" arguments ")" { $$ = makeEchoOp(driver, @$, std::move($3), makeUndefinedLiteral(driver, @$)); }
406
+ | "profile_time" "(" arguments ")" expr { $$ = makeProfileTimeOp(driver, @$, std::move($3), std::move($5)); }
396
407
  | "function" "(" parameters ")" expr { $$ = makeFunctionLiteral(driver, @$, std::move($3), std::move($5)); }
397
408
  | opchain "?" expr ":" expr { $$ = makeTernaryOp(driver, @$, std::move($1), std::move($3), std::move($5)); }
398
409
  | opchain { $$ = std::move($1); }
@@ -180,6 +180,12 @@ void classifyNode(ASTNode& node, std::vector<ExprSlot>& exprFields, std::vector<
180
180
  addExpr(n.body, exprFields);
181
181
  break;
182
182
  }
183
+ case NodeKind::ProfileTimeOp: {
184
+ auto& n = static_cast<ProfileTimeOp&>(node);
185
+ addArgumentExprList(n.arguments, exprFields);
186
+ addExpr(n.body, exprFields);
187
+ break;
188
+ }
183
189
  case NodeKind::AssertOp: {
184
190
  auto& n = static_cast<AssertOp&>(node);
185
191
  addArgumentExprList(n.arguments, exprFields);
@@ -486,6 +486,9 @@ std::string fmtExpr(const ASTNode& exprNode, int indent, int w) {
486
486
  }
487
487
  return "echo(" + args + ")\n" + pad + fmtExpr(*e->body, indent, w);
488
488
  }
489
+ if (auto* p = dynamic_cast<const ProfileTimeOp*>(&exprNode)) {
490
+ return "profile_time(" + joinToString(p->arguments, ", ") + ")\n" + pad + fmtExpr(*p->body, indent, w);
491
+ }
489
492
  if (auto* l = dynamic_cast<const LetOp*>(&exprNode)) {
490
493
  return fmtLetOpExpr(*l, indent, w);
491
494
  }
@@ -150,6 +150,12 @@ json toJsonImpl(const ASTNode& node, bool includePos) {
150
150
  j["body"] = valueToJson(n.body.get(), includePos);
151
151
  break;
152
152
  }
153
+ case NodeKind::ProfileTimeOp: {
154
+ auto& n = static_cast<const ProfileTimeOp&>(node);
155
+ j["arguments"] = listToJson(n.arguments, includePos);
156
+ j["body"] = valueToJson(n.body.get(), includePos);
157
+ break;
158
+ }
153
159
  case NodeKind::AssertOp: {
154
160
  auto& n = static_cast<const AssertOp&>(node);
155
161
  j["arguments"] = listToJson(n.arguments, includePos);
@@ -446,6 +452,11 @@ const std::unordered_map<std::string, Builder>& registry() {
446
452
  return std::make_unique<EchoOp>(std::move(pos), listFromJson<Argument>(j, "arguments"),
447
453
  childFromJson<Expression>(j, "body"));
448
454
  }},
455
+ {"ProfileTimeOp",
456
+ [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
457
+ return std::make_unique<ProfileTimeOp>(std::move(pos), listFromJson<Argument>(j, "arguments"),
458
+ childFromJson<Expression>(j, "body"));
459
+ }},
449
460
  {"AssertOp",
450
461
  [](const json& j, Position pos) -> std::unique_ptr<ASTNode> {
451
462
  return std::make_unique<AssertOp>(std::move(pos), listFromJson<Argument>(j, "arguments"),
@@ -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_profile_time.cpp
29
30
  test_strict_commas.cpp
30
31
  test_concurrent_parse.cpp
31
32
  )
@@ -0,0 +1,86 @@
1
+ // `profile_time()`: the statement form times its children, the expression
2
+ // form (`x = profile_time("label") expr;`) its body. `profile_time` is a
3
+ // reserved keyword only because the expression form would otherwise be
4
+ // indistinguishable from a function call; the statement form is a plain
5
+ // ModularCall, so builtin dispatch downstream sees an ordinary module.
6
+
7
+ #include "openscad_cpp_parser/api.hpp"
8
+ #include "openscad_cpp_parser/pretty_print.hpp"
9
+ #include "openscad_cpp_parser/serialization.hpp"
10
+ #include "test_helpers.hpp"
11
+
12
+ #include <gtest/gtest.h>
13
+
14
+ using namespace oscad;
15
+
16
+ namespace {
17
+
18
+ const ProfileTimeOp* profileOf(const std::vector<std::unique_ptr<ASTNode>>& ast) {
19
+ auto* a = dynamic_cast<Assignment*>(ast[0].get());
20
+ return a ? dynamic_cast<const ProfileTimeOp*>(a->expr.get()) : nullptr;
21
+ }
22
+
23
+ } // namespace
24
+
25
+ TEST(ProfileTime, StatementFormIsAPlainModularCall) {
26
+ auto ast = parseSrc("profile_time(\"gears\") { cube(1); sphere(2); }");
27
+ ASSERT_EQ(ast.size(), 1u);
28
+ auto* call = dynamic_cast<ModularCall*>(ast[0].get());
29
+ ASSERT_NE(call, nullptr);
30
+ EXPECT_EQ(call->name->name, "profile_time");
31
+ EXPECT_EQ(call->arguments.size(), 1u);
32
+ EXPECT_EQ(call->children.size(), 2u);
33
+ }
34
+
35
+ TEST(ProfileTime, ExpressionFormWrapsItsBody) {
36
+ auto ast = parseSrc("x = profile_time(\"sum\") 1 + 2;");
37
+ const ProfileTimeOp* p = profileOf(ast);
38
+ ASSERT_NE(p, nullptr);
39
+ EXPECT_EQ(p->kind(), NodeKind::ProfileTimeOp);
40
+ EXPECT_EQ(p->arguments.size(), 1u);
41
+ EXPECT_EQ(p->body->kind(), NodeKind::AdditionOp);
42
+ }
43
+
44
+ TEST(ProfileTime, ExpressionFormAllowsNoLabel) {
45
+ const ProfileTimeOp* p = nullptr;
46
+ auto ast = parseSrc("x = profile_time() f(3);");
47
+ p = profileOf(ast);
48
+ ASSERT_NE(p, nullptr);
49
+ EXPECT_TRUE(p->arguments.empty());
50
+ }
51
+
52
+ TEST(ProfileTime, ExpressionFormNeedsABody) {
53
+ EXPECT_THROW(parseSrc("x = profile_time(\"a\");"), ParseError);
54
+ }
55
+
56
+ TEST(ProfileTime, IsReserved) {
57
+ EXPECT_THROW(parseSrc("profile_time = 1;"), ParseError);
58
+ EXPECT_THROW(parseSrc("function profile_time() = 1;"), ParseError);
59
+ EXPECT_THROW(parseSrc("module profile_time() {}"), ParseError);
60
+ // `$profile_time` is a different identifier and stays usable.
61
+ EXPECT_NO_THROW(parseSrc("$profile_time = 1;"));
62
+ }
63
+
64
+ TEST(ProfileTime, ToStringRoundTrips) {
65
+ auto ast = parseSrc("x = profile_time(\"a\") 1 + 2;");
66
+ EXPECT_EQ(ast[0]->toString(), "x = profile_time(\"a\") 1 + 2");
67
+ EXPECT_NE(profileOf(parseSrc(ast[0]->toString() + ";")), nullptr);
68
+ }
69
+
70
+ TEST(ProfileTime, JsonRoundTrips) {
71
+ auto ast = parseSrc("x = profile_time(\"a\") 1 + 2;");
72
+ auto back = astFromJsonString(astToJsonString(ast));
73
+ const ProfileTimeOp* p = profileOf(back);
74
+ ASSERT_NE(p, nullptr);
75
+ EXPECT_EQ(p->body->kind(), NodeKind::AdditionOp);
76
+ }
77
+
78
+ TEST(ProfileTime, PrettyPrintReparses) {
79
+ for (const char* src : {"x = profile_time(\"a\") 1 + 2;",
80
+ "profile_time(\"g\") { cube(1); }"}) {
81
+ auto ast = parseSrc(src);
82
+ std::string out = toOpenscad(ast);
83
+ EXPECT_NE(out.find("profile_time(\""), std::string::npos) << out;
84
+ EXPECT_NO_THROW(parseSrc(out)) << out;
85
+ }
86
+ }
@@ -71,6 +71,13 @@ struct CSGNode {
71
71
  // stack risk the moment this node is ever destroyed.
72
72
  int treeDepth = 1;
73
73
 
74
+ // profile_time() only: how long resolving this node (its arguments and
75
+ // its children's script code) took, in ms; < 0 for every other node.
76
+ // Measured fresh on every run, so -- like warnEntry -- deliberately NOT
77
+ // part of cacheKey(): putting a timing in the key would make every
78
+ // profiled subtree a permanent cache miss.
79
+ double profileScriptMs = -1.0;
80
+
74
81
  // Memoizes manifold_cache.cpp's cacheKey(*this) -- that function
75
82
  // recurses into every descendant to build its own key, so without this
76
83
  // memo a generate-time walk that calls cacheKey() at every node (not
@@ -422,6 +422,22 @@ public:
422
422
  // subtree, not just direct children, since hull() consumes all of it.
423
423
  bool insideHull = false;
424
424
 
425
+ // profile_time(). generateRequested_ is false only inside an
426
+ // evaluate(generate = false) run, where no geometry pass follows to
427
+ // report a profiled block, so resolve reports the script time alone.
428
+ // profileNodeCount_ counts this run's profile_time() nodes so a cache
429
+ // hit only walks its subtree for them when there are any (see
430
+ // reportCachedProfiles).
431
+ bool generateRequested_ = true;
432
+ std::size_t profileNodeCount_ = 0;
433
+ // Prints "PROFILE: <label>: ..." for a profile_time() node. geometryMs
434
+ // < 0 means no geometry pass; `cached` means its geometry came from the
435
+ // ManifoldCache rather than being built.
436
+ void reportProfile(const CSGNode& node, double geometryMs, bool cached);
437
+ // A cache hit at `node` skips its whole subtree, so a profile_time()
438
+ // inside it would otherwise print nothing at all.
439
+ void reportCachedProfiles(const CSGNode& node);
440
+
425
441
  ColoredBody tagGenerated(manifold::Manifold body, const oscad::ASTNode& node, const Value& colorValue);
426
442
 
427
443
  // tagGenerated()'s counterpart for a mesh Manifold refused to build --
@@ -837,6 +853,7 @@ private:
837
853
  Value evalLetExpr(const oscad::LetOp& node, EvalContext& ctx);
838
854
  Value evalEchoExpr(const oscad::EchoOp& node, EvalContext& ctx);
839
855
  Value evalAssertExpr(const oscad::AssertOp& node, EvalContext& ctx);
856
+ Value evalProfileTimeExpr(const oscad::ProfileTimeOp& node, EvalContext& ctx);
840
857
  void bindLetName(EvalContext& ctx, const std::string& name, const Value& v);
841
858
 
842
859
  // Bracketed public in place: Stage 2's Op::NativeStatement
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.37.2"
7
+ version = "1.38.1"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -43,6 +43,7 @@ add_library(openscad_cpp_evaluator STATIC
43
43
  builtins/booleans.cpp
44
44
  builtins/modifiers.cpp
45
45
  builtins/control.cpp
46
+ builtins/profile_time.cpp
46
47
  builtins/function_builtins.cpp
47
48
  builtins/import.cpp
48
49
  builtins/topology.cpp
@@ -146,6 +146,12 @@ std::vector<ColoredBody> generateChildren(Evaluator& ev, const CSGParams& params
146
146
  // generateTree()'s default child-concatenation, exactly reproducing
147
147
  // passthrough).
148
148
  CSGParams resolveRender(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
149
+ CSGParams resolveProfileTime(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx);
150
+ // profile_time()'s message pieces: "12.40 ms" (or "4.7 µs" under 1 ms), and the label it names --
151
+ // the label as written (a non-string formatted like echo() would), or
152
+ // "line N" (with the file when there is one) when none was given.
153
+ std::string fmtMs(double ms);
154
+ std::string profileLabel(const Value& label, const oscad::Position& pos);
149
155
 
150
156
  // breakpoint() -- no-op as of Phase 4 (no debugger until Phase 9); still
151
157
  // evaluates/validates its optional `condition` argument for parity.
@@ -118,34 +118,86 @@ manifold::Manifold extrudeTwisted(const manifold::Polygons& polys, double height
118
118
  BuiltinWrapParams computeLinearExtrudeParams(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
119
119
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
120
120
 
121
- // 100 when not given, as upstream (the old 1 was a guess), and `h` as
122
- // upstream's alias -- BOSL2's linear_extrude override forwards it.
123
- const double height = toDoubleLenient(getArgOrAlias(ev, &node.position(), args, 0, "height", "h", Value{100.0}));
124
- const bool center = truthy(getArg(args, std::nullopt, "center", Value{false}));
125
- const double twist = toDoubleLenient(getArg(args, std::nullopt, "twist", Value{0.0}));
121
+ // Positional order is upstream's: height, v, scale, center, twist,
122
+ // slices, segments. Only height used to be read positionally.
123
+ //
124
+ // Upstream's LinearExtrudeNode: the extrusion runs along a vector, (0,0,1)
125
+ // unless `v` gives one. Its length is `height` (`h` its alias, BOSL2's
126
+ // override forwards it) when given -- `v` then only sets the direction --
127
+ // else |v| when `v` is given, else 100. A vector pointing down (z <= 0)
128
+ // extrudes nothing. Bad values warn and fall back as upstream's do.
129
+ // `v` was ignored here, so an oblique extrusion came out straight.
130
+ double hv[3] = {0.0, 0.0, 1.0};
131
+ double length = 100.0;
132
+ const Value vArg = getArg(args, 1, "v", Value{});
133
+ if (!std::holds_alternative<std::monostate>(vArg)) {
134
+ const ListPtr* l = std::get_if<ListPtr>(&vArg);
135
+ bool ok = l && *l && (*l)->items.size() == 3;
136
+ double t[3] = {0, 0, 0};
137
+ for (int i = 0; ok && i < 3; ++i) {
138
+ const double* d = std::get_if<double>(&(*l)->items[i]);
139
+ ok = d && std::isfinite(*d);
140
+ if (ok) t[i] = *d;
141
+ }
142
+ if (ok) std::copy(t, t + 3, hv);
143
+ else ev.warn("v when specified should be a 3d vector", &node.position());
144
+ length = 1.0;
145
+ }
146
+ const Value heightArg = getArgOrAlias(ev, &node.position(), args, 0, "height", "h", Value{});
147
+ if (!std::holds_alternative<std::monostate>(heightArg)) {
148
+ const double* d = std::get_if<double>(&heightArg);
149
+ if (d && std::isfinite(*d)) {
150
+ length = *d;
151
+ } else {
152
+ ev.warn("height when specified should be a number", &node.position());
153
+ length = 100.0;
154
+ }
155
+ const double norm = std::sqrt(hv[0] * hv[0] + hv[1] * hv[1] + hv[2] * hv[2]);
156
+ for (double& c : hv) c /= norm;
157
+ }
158
+ for (double& c : hv) c *= length;
159
+ if (hv[2] <= 0) hv[2] = 0;
160
+ const double height = hv[2];
161
+ // Only a real boolean counts, as upstream: center="yes" is not true.
162
+ const Value centerArg = getArg(args, 3, "center", Value{false});
163
+ const bool* centerBool = std::get_if<bool>(&centerArg);
164
+ const bool center = centerBool && *centerBool;
165
+ const double twist = toDoubleLenient(getArg(args, 4, "twist", Value{0.0}));
126
166
  // Upstream's validate_integral: any finite number counts as given, and is
127
167
  // truncated and clamped (slices >= 1, segments >= 0). Not given, the
128
168
  // discretizer decides.
129
- const auto integral = [&](const char* name, double lo) -> std::optional<double> {
130
- const Value v = getArg(args, std::nullopt, name, Value{});
169
+ const auto integral = [&](int pos, const char* name, double lo) -> std::optional<double> {
170
+ const Value v = getArg(args, pos, name, Value{});
131
171
  const double* d = std::get_if<double>(&v);
132
172
  if (!d || !std::isfinite(*d)) return std::nullopt;
133
173
  return *d < lo ? lo : std::trunc(*d);
134
174
  };
135
- const std::optional<double> slices = integral("slices", 1.0);
136
- const std::optional<double> segments = integral("segments", 0.0);
137
- const Value scaleArg = getArg(args, std::nullopt, "scale", Value{});
175
+ const std::optional<double> slices = integral(5, "slices", 1.0);
176
+ const std::optional<double> segments = integral(6, "segments", 0.0);
177
+ const Value scaleArg = getArg(args, 2, "scale", Value{});
138
178
 
139
179
  double scaleX = 1.0, scaleY = 1.0;
180
+ bool scaleOk = true;
140
181
  if (const double* s = std::get_if<double>(&scaleArg)) {
141
182
  scaleX = scaleY = *s;
183
+ scaleOk = std::isfinite(*s);
142
184
  } else if (const ListPtr* l = std::get_if<ListPtr>(&scaleArg); l && *l && (*l)->items.size() >= 2) {
143
185
  scaleX = toDoubleLenient((*l)->items[0]);
144
186
  scaleY = toDoubleLenient((*l)->items[1]);
187
+ scaleOk = std::isfinite(scaleX) && std::isfinite(scaleY);
188
+ } else if (!std::holds_alternative<std::monostate>(scaleArg)) {
189
+ scaleOk = false;
190
+ }
191
+ if (!scaleOk) {
192
+ ev.warn("linear_extrude(..., scale=" + fmtValue(scaleArg) + ") could not be converted", &node.position());
193
+ scaleX = scaleY = 1.0;
145
194
  }
146
195
 
147
196
  CSGParams params;
148
197
  params["height"] = Value{height};
198
+ // How far the top slides sideways per unit of height (0 unless `v`).
199
+ params["shear_x"] = Value{height > 0 ? hv[0] / height : 0.0};
200
+ params["shear_y"] = Value{height > 0 ? hv[1] / height : 0.0};
149
201
  params["center"] = Value{center};
150
202
  params["twist"] = Value{twist};
151
203
  params["slices"] = slices ? Value{*slices} : Value{};
@@ -227,6 +279,14 @@ std::vector<ColoredBody> generateLinearExtrude(Evaluator& ev, const CSGParams& p
227
279
  ? extrudeTwisted(polys, height, slices, twist, scaleX, scaleY)
228
280
  : manifold::Manifold::Extrude(polys, height, slices - 1, -twist, manifold::vec2(scaleX, scaleY));
229
281
  if (std::get<bool>(params.at("center"))) body = body.Translate(manifold::vec3(0, 0, -height / 2));
282
+ // `v`: slide each point sideways in proportion to its height -- upstream
283
+ // places slice k at bottom + v*k/n, and centring subtracts v/2, so the
284
+ // same shear is right either way.
285
+ const double shearX = std::get<double>(params.at("shear_x"));
286
+ const double shearY = std::get<double>(params.at("shear_y"));
287
+ if (shearX != 0.0 || shearY != 0.0) {
288
+ body = body.Transform(manifold::mat3x4({1, 0, 0}, {0, 1, 0}, {shearX, shearY, 1}, {0, 0, 0}));
289
+ }
230
290
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
231
291
  }
232
292
 
@@ -241,11 +301,36 @@ std::vector<ColoredBody> generateLinearExtrude(Evaluator& ev, const CSGParams& p
241
301
  BuiltinWrapParams computeRotateExtrudeParams(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
242
302
  auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
243
303
 
244
- const double angle = toDoubleLenient(getArgOrAlias(ev, &node.position(), args, 0, "angle", "a", Value{360.0}));
304
+ // Positional order is upstream's: angle, start.
305
+ //
306
+ // Upstream's RotateExtrudeNode: the sweep runs from `start` through
307
+ // `start + angle`. With an angle, start defaults to 0 (the +X axis) and
308
+ // an angle outside (-360, 360] becomes a full turn; with none, the turn
309
+ // is 360 and starts at 180 (-X), which upstream says will change, and
310
+ // says so for an odd $fn, where the start shows. An explicit start
311
+ // always wins. It was ignored here: a quarter turn from 90 drew 0..90.
312
+ const Value angleArg = getArgOrAlias(ev, &node.position(), args, 0, "angle", "a", Value{});
313
+ const Value startArg = getArg(args, 1, "start", Value{});
314
+ const bool hasAngle = !std::holds_alternative<std::monostate>(angleArg) && std::isfinite(toDoubleLenient(angleArg));
315
+ const bool hasStart = !std::holds_alternative<std::monostate>(startArg) && std::isfinite(toDoubleLenient(startArg));
316
+ double angle = 360.0;
317
+ double start = 180.0;
318
+ if (hasAngle) {
319
+ angle = toDoubleLenient(angleArg);
320
+ start = 0.0;
321
+ if (angle <= -360.0 || angle > 360.0) angle = 360.0;
322
+ }
323
+ if (hasStart) start = toDoubleLenient(startArg);
324
+ const Discretizer disc = Discretizer::fromCtx(effCtx);
325
+ if (!hasAngle && !hasStart && (static_cast<int>(disc.fn) & 1)) {
326
+ ev.emitWarning("DEPRECATED: In future releases, rotational extrusion without \"angle\" will start at zero, "
327
+ "the +X axis. Set start=180 to explicitly start on the -X axis.");
328
+ }
245
329
 
246
330
  CSGParams params;
247
331
  params["angle"] = Value{angle};
248
- Discretizer::fromCtx(effCtx).store(params);
332
+ params["start"] = Value{start};
333
+ disc.store(params);
249
334
  params["color"] = colorToValue(effCtx.color);
250
335
  return BuiltinWrapParams{std::move(params), std::move(effCtx)};
251
336
  }
@@ -299,6 +384,9 @@ std::vector<ColoredBody> generateRotateExtrude(Evaluator& ev, const CSGParams& p
299
384
  v = manifold::vec3(v.x * std::cos(a), v.x * std::sin(a), sign * v.y);
300
385
  });
301
386
  }
387
+ // The arc above runs from +X; turn it to begin at `start`.
388
+ const double start = std::get<double>(params.at("start"));
389
+ if (start != 0.0) body = body.Rotate(0.0, 0.0, start);
302
390
  return {ev.tagGenerated(std::move(body), node, params.at("color"))};
303
391
  }
304
392
 
@@ -1018,6 +1018,7 @@ const std::unordered_map<std::string, double>& featureLevels() {
1018
1018
  {"roof-op", 1.0}, // roof(), method="voronoi" only
1019
1019
  {"svg-class", 1.0}, // import(svg, class=); id= is upstream's
1020
1020
  {"discretization-by-error", 1.0}, // $fe, always on (upstream: behind --enable)
1021
+ {"profile-time", 1.0}, // profile_time(label) { ... } and profile_time(label) expr
1021
1022
  };
1022
1023
  return levels;
1023
1024
  }