openscad-cpp-evaluator 1.37.2__tar.gz → 1.38.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 (232) hide show
  1. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/CLAUDE.md +30 -0
  2. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/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.0}/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.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +1 -0
  6. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/expression.cpp +13 -0
  7. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +5 -0
  8. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +1 -0
  9. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/lexer.l +3 -0
  10. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/parser.y +12 -1
  11. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +6 -0
  12. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/pretty_print.cpp +3 -0
  13. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +11 -0
  14. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +1 -0
  15. openscad_cpp_evaluator-1.38.0/external/openscad_cpp_parser/tests/test_profile_time.cpp +86 -0
  16. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/csg_node.hpp +7 -0
  17. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/evaluator.hpp +17 -0
  18. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/pyproject.toml +1 -1
  19. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/CMakeLists.txt +1 -0
  20. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/builtins.hpp +6 -0
  21. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/function_builtins.cpp +1 -0
  22. openscad_cpp_evaluator-1.38.0/src/builtins/profile_time.cpp +63 -0
  23. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/registry.cpp +2 -0
  24. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/coverage.cpp +6 -0
  25. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/csg_generate.cpp +11 -0
  26. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/csg_resolve.cpp +19 -0
  27. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/expr_eval.cpp +15 -0
  28. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/format_closure.cpp +4 -0
  29. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/CMakeLists.txt +1 -0
  30. openscad_cpp_evaluator-1.38.0/tests/test_profile_time.cpp +139 -0
  31. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/uv.lock +1 -1
  32. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/.github/workflows/ci.yml +0 -0
  33. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/.github/workflows/wheels.yml +0 -0
  34. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/.gitignore +0 -0
  35. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/.gitmodules +0 -0
  36. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/CMakeLists.txt +0 -0
  37. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/LICENSE +0 -0
  38. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/README.md +0 -0
  39. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/bindings/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/bindings/ast_to_py.cpp +0 -0
  41. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/bindings/module.cpp +0 -0
  42. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/cmake/embed_font.cmake +0 -0
  43. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/cmake/patch_manifold.cmake +0 -0
  44. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/examples/CMakeLists.txt +0 -0
  45. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/examples/eval_perf_benchmark.cpp +0 -0
  46. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/examples/manifold_cache_reuse.cpp +0 -0
  47. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/examples/minimal_debugger.cpp +0 -0
  48. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  49. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/.gitignore +0 -0
  50. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  51. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  52. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/README.md +0 -0
  53. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  54. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  55. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  56. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  57. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  58. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  59. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  60. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  61. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  62. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  63. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  64. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  65. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  66. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  67. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
  68. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  69. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  70. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  71. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  72. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  73. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  74. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  75. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  76. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  77. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  78. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  79. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  80. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  81. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  82. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  83. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  84. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  85. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  86. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
  87. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  88. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  89. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  90. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  91. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  92. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  93. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  94. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  95. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  96. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  97. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  98. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  99. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  100. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  101. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  102. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  103. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  104. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  105. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  106. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  107. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  108. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  109. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  110. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  111. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  112. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  113. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
  114. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  115. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  116. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  117. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  118. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  119. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  120. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  121. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  122. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  123. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  124. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  125. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  126. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  127. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  128. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  129. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  130. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  131. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  132. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  133. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  134. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  135. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  136. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  137. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  138. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  139. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  140. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  141. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  142. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LICENSE +0 -0
  143. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  144. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  145. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  146. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  147. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/booleans.cpp +0 -0
  148. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/call_args.cpp +0 -0
  149. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/color.cpp +0 -0
  150. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/control.cpp +0 -0
  151. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/dxf_dim.cpp +0 -0
  152. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/extrude.cpp +0 -0
  153. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/import.cpp +0 -0
  154. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/modifiers.cpp +0 -0
  155. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/primitives_2d.cpp +0 -0
  156. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/primitives_3d.cpp +0 -0
  157. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/roof.cpp +0 -0
  158. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/segments.cpp +0 -0
  159. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/surface.cpp +0 -0
  160. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/text.cpp +0 -0
  161. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/topology.cpp +0 -0
  162. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/builtins/transforms.cpp +0 -0
  163. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/bytecode_compiler.cpp +0 -0
  164. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/bytecode_vm.cpp +0 -0
  165. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/colored_body.cpp +0 -0
  166. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/css_colors.cpp +0 -0
  167. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/debug/debug_repl.cpp +0 -0
  168. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/debug_profile.cpp +0 -0
  169. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/eval_context.cpp +0 -0
  170. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/eval_error.cpp +0 -0
  171. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/eval_use.cpp +0 -0
  172. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/export.cpp +0 -0
  173. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/import/dxf_import.cpp +0 -0
  174. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/import/mesh_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/import/scene_import.cpp +0 -0
  176. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/import/surface_load.cpp +0 -0
  177. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/import/svg_import.cpp +0 -0
  178. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/manifold_cache.cpp +0 -0
  179. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/measure_geometry.cpp +0 -0
  180. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/mesh_check.cpp +0 -0
  181. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/stmt_eval.cpp +0 -0
  182. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/text/font_match.cpp +0 -0
  183. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/text/freetype_font_provider.cpp +0 -0
  184. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/text/text_metrics.cpp +0 -0
  185. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/user_calls.cpp +0 -0
  186. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/utf8.cpp +0 -0
  187. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/value.cpp +0 -0
  188. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/xkcd_colors.cpp +0 -0
  189. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/xkcd_colors.hpp +0 -0
  190. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/src/zip_stored.cpp +0 -0
  191. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_booleans.cpp +0 -0
  192. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_bytecode_compiler.cpp +0 -0
  193. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_children_separate.cpp +0 -0
  194. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_cli.cpp +0 -0
  195. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_control_flow.cpp +0 -0
  196. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_coverage.cpp +0 -0
  197. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_csg_tree.cpp +0 -0
  198. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_debug_hooks.cpp +0 -0
  199. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_discretizer.cpp +0 -0
  200. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_dxf_svg_import.cpp +0 -0
  201. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_eval_context.cpp +0 -0
  202. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_export.cpp +0 -0
  203. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_expr_eval.cpp +0 -0
  204. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_extrude_roof.cpp +0 -0
  205. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_feature_detection.cpp +0 -0
  206. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_function_builtins.cpp +0 -0
  207. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_helpers.hpp +0 -0
  208. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_import_export.cpp +0 -0
  209. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_include_cache.cpp +0 -0
  210. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_manifold_cache.cpp +0 -0
  211. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_mesh_check.cpp +0 -0
  212. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_multi_color_merge.cpp +0 -0
  213. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_primitives.cpp +0 -0
  214. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_profiling.cpp +0 -0
  215. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_python_bindings.py +0 -0
  216. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_render_expr.cpp +0 -0
  217. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_scope_trail.cpp +0 -0
  218. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_scoping.cpp +0 -0
  219. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_surface.cpp +0 -0
  220. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_tail_calls.cpp +0 -0
  221. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_text.cpp +0 -0
  222. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_transforms.cpp +0 -0
  223. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_undef_args.cpp +0 -0
  224. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_unexpected_args.cpp +0 -0
  225. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_use.cpp +0 -0
  226. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_value.cpp +0 -0
  227. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_viewport_params.cpp +0 -0
  228. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tests/test_zip_stored.cpp +0 -0
  229. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tools/cli/CMakeLists.txt +0 -0
  230. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tools/cli/cli_lib.cpp +0 -0
  231. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/tools/cli/cli_lib.hpp +0 -0
  232. {openscad_cpp_evaluator-1.37.2 → openscad_cpp_evaluator-1.38.0}/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.0
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.0"
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.
@@ -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
  }
@@ -0,0 +1,63 @@
1
+ #include "builtins.hpp"
2
+
3
+ #include "openscad_cpp_evaluator/call_args.hpp"
4
+ #include "openscad_cpp_evaluator/evaluator.hpp"
5
+
6
+ #include <cstdio>
7
+
8
+ namespace oscadeval {
9
+
10
+ // Microseconds below a millisecond, where "0.00 ms" would hide the answer.
11
+ std::string fmtMs(double ms) {
12
+ char buf[32];
13
+ if (ms < 1.0) std::snprintf(buf, sizeof buf, "%.1f \u00b5s", ms * 1000.0);
14
+ else std::snprintf(buf, sizeof buf, "%.2f ms", ms);
15
+ return buf;
16
+ }
17
+
18
+ std::string profileLabel(const Value& label, const oscad::Position& pos) {
19
+ if (const std::string* s = std::get_if<std::string>(&label)) return *s;
20
+ if (!std::holds_alternative<std::monostate>(label)) return fmtValue(label);
21
+ std::string where = "line " + std::to_string(pos.line);
22
+ if (!pos.origin.empty()) where += " of " + pos.origin.substr(pos.origin.find_last_of("/\\") + 1);
23
+ return where;
24
+ }
25
+
26
+ // profile_time([label]) { ... } -- resolves its children like render() does.
27
+ // The script-time clock is NOT here but around this call in
28
+ // Evaluator::evalModularCall, so it covers argument evaluation and every
29
+ // child alike; the geometry time is taken in generateTreeImpl. The label
30
+ // goes into params (it is constant, so it costs the cache nothing).
31
+ CSGParams resolveProfileTime(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
32
+ auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
33
+ CSGParams params;
34
+ params["label"] = getArg(args, 0, "label");
35
+ EvalContext blockCtx = ev.blockScope(effCtx);
36
+ ev.evalChildren(node.children, blockCtx);
37
+ return params;
38
+ }
39
+
40
+ void Evaluator::reportProfile(const CSGNode& node, double geometryMs, bool cached) {
41
+ if (!echoFn_ || node.node == nullptr) return;
42
+ auto it = node.params.find("label");
43
+ const std::string label = profileLabel(it == node.params.end() ? Value{} : it->second, node.node->position());
44
+ const double scriptMs = node.profileScriptMs < 0 ? 0.0 : node.profileScriptMs;
45
+ std::string msg = "PROFILE: " + label + ": ";
46
+ if (geometryMs < 0) {
47
+ msg += "script " + fmtMs(scriptMs) + " (no geometry built)";
48
+ } else if (cached) {
49
+ msg += fmtMs(scriptMs) + " (script " + fmtMs(scriptMs) + ", geometry cached)";
50
+ } else {
51
+ msg += fmtMs(scriptMs + geometryMs) + " (script " + fmtMs(scriptMs) + ", geometry " + fmtMs(geometryMs) + ")";
52
+ }
53
+ echoFn_(msg);
54
+ }
55
+
56
+ void Evaluator::reportCachedProfiles(const CSGNode& node) {
57
+ for (const std::unique_ptr<CSGNode>& c : node.children) {
58
+ if (c->isBuiltin && c->kind == "profile_time") reportProfile(*c, 0.0, true);
59
+ reportCachedProfiles(*c);
60
+ }
61
+ }
62
+
63
+ } // namespace oscadeval
@@ -26,6 +26,7 @@ const std::unordered_map<std::string_view, ResolveFn>& resolveDispatch() {
26
26
  {"intersection", &resolveCsg},
27
27
  {"children", &resolveChildren},
28
28
  {"render", &resolveRender},
29
+ {"profile_time", &resolveProfileTime},
29
30
  {"breakpoint", &resolveBreakpoint},
30
31
  {"import", &resolveImport},
31
32
  {"hull", &resolveHull},
@@ -118,6 +119,7 @@ const std::vector<std::string>* builtinParamNames(const std::string& name) {
118
119
  {"minkowski", {"convexity"}},
119
120
  {"children", {"index", "separate"}},
120
121
  {"render", {"convexity"}},
122
+ {"profile_time", {"label"}},
121
123
  // "repair" and "class" are this port's own additions, not upstream
122
124
  // parameters. ("id" IS upstream's, and was declared here long before
123
125
  // anything read it -- so a script filtering by id was accepted in
@@ -263,6 +263,12 @@ private:
263
263
  expr(*o.body);
264
264
  break;
265
265
  }
266
+ case NodeKind::ProfileTimeOp: {
267
+ auto& o = static_cast<const oscad::ProfileTimeOp&>(e);
268
+ args(o.arguments);
269
+ expr(*o.body);
270
+ break;
271
+ }
266
272
  case NodeKind::FunctionLiteral: {
267
273
  auto& f = static_cast<const oscad::FunctionLiteral&>(e);
268
274
  emit(&e, CoverageKind::Body);
@@ -36,6 +36,10 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
36
36
  for (CSGNode* nodePtr : tree) {
37
37
  CSGNode& node = *nodePtr;
38
38
  checkCancel();
39
+ // profile_time(): timed from before the cache lookup, so a hit
40
+ // still reports (as cached) rather than vanishing.
41
+ const bool profiled = node.isBuiltin && node.kind == "profile_time";
42
+ const auto profileStart = profiled ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
39
43
 
40
44
  // ManifoldCache lookup: node.uncacheable (rands() taint, set
41
45
  // during resolve -- see csg_resolve.cpp) always forces a miss,
@@ -69,6 +73,8 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
69
73
  // one (BelfrySCAD #521). Goes through emitWarning so an enclosing
70
74
  // capture records them too, exactly as a fresh generate would.
71
75
  for (const std::string& w : cached->warnings) emitWarning(w);
76
+ // A hit skips the subtree, profile_time() calls and all.
77
+ if (profileNodeCount_ > 0) reportCachedProfiles(node);
72
78
  // Skipped entirely while measuring: restampCachedIds writes
73
79
  // idToNode/idToColor, and geometry that is about to be discarded
74
80
  // has no click-to-source identity worth recording. See
@@ -171,6 +177,11 @@ std::vector<ColoredBody> Evaluator::generateTreeImpl(const std::vector<CSGNode*>
171
177
  // capacity, and 35K nodes' worth of 592-byte slots was 230MB.
172
178
  for (const std::unique_ptr<CSGNode>& c : node.children) c->bodies = std::vector<ColoredBody>();
173
179
  }
180
+ if (profiled) {
181
+ reportProfile(node,
182
+ std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - profileStart).count(),
183
+ cached.has_value());
184
+ }
174
185
  for (const ColoredBody& b : node.bodies) topLevelBodies.push_back(b);
175
186
  }
176
187
  return topLevelBodies;
@@ -93,10 +93,17 @@ void Evaluator::evalModularCall(const oscad::ModularCall& node, EvalContext& ctx
93
93
  treeStack_.emplace_back();
94
94
  const std::uint64_t randsBefore = randsCallCount_;
95
95
  CSGParams params;
96
+ const bool profiled = hasResolveFn && name == "profile_time";
97
+ double profileScriptMs = -1.0;
96
98
  try {
97
99
  if (hasResolveFn) {
98
100
  warnUnexpectedBuiltinArgs(*this, node);
101
+ const auto profileStart = profiled ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
99
102
  params = it->second(*this, node, ctx);
103
+ if (profiled) {
104
+ profileScriptMs =
105
+ std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - profileStart).count();
106
+ }
100
107
  } else if (!isBuiltin) {
101
108
  evalUserModule(static_cast<const oscad::ModuleDeclaration&>(*userModuleDecl), node, ctx);
102
109
  } else {
@@ -144,7 +151,13 @@ void Evaluator::evalModularCall(const oscad::ModularCall& node, EvalContext& ctx
144
151
  treeNode->uncacheable = uncacheable;
145
152
  treeNode->children = std::move(children);
146
153
  treeNode->params = std::move(params);
154
+ treeNode->profileScriptMs = profileScriptMs;
147
155
  setTreeDepthOrThrow(*treeNode, node);
156
+ if (profiled) {
157
+ ++profileNodeCount_;
158
+ // No geometry pass will follow to report it, so report it now.
159
+ if (!generateRequested_) reportProfile(*treeNode, -1.0, false);
160
+ }
148
161
  treeStack_.back().push_back(std::move(treeNode));
149
162
  }
150
163
 
@@ -271,6 +284,12 @@ std::vector<ColoredBody> Evaluator::evaluateImpl(const NodeList& nodes, EvalCont
271
284
 
272
285
  using Clock = std::chrono::steady_clock;
273
286
  const Clock::time_point resolveStart = profiling_ ? Clock::now() : Clock::time_point{};
287
+ profileNodeCount_ = 0;
288
+ generateRequested_ = generate;
289
+ struct GenerateRequestedGuard {
290
+ bool& flag;
291
+ ~GenerateRequestedGuard() { flag = true; }
292
+ } generateRequestedGuard{generateRequested_};
274
293
  std::vector<std::unique_ptr<CSGNode>> tree = resolveTreeImpl(nodes, ctx);
275
294
  rerootAtShowOnly(tree);
276
295
  const Clock::time_point resolveEnd = profiling_ ? Clock::now() : Clock::time_point{};
@@ -2,6 +2,7 @@
2
2
  #include "openscad_cpp_evaluator/utf8.hpp"
3
3
 
4
4
  #include "openscad_cpp_evaluator/call_args.hpp"
5
+ #include "builtins/builtins.hpp"
5
6
  #include "openscad_cpp_evaluator/eval_error.hpp"
6
7
  #include "openscad_cpp_evaluator/freetype_font_provider.hpp"
7
8
 
@@ -444,6 +445,18 @@ Value Evaluator::evalEchoExpr(const oscad::EchoOp& node, EvalContext& ctx) {
444
445
  return evalExpr(*node.body, ctx);
445
446
  }
446
447
 
448
+ // `x = profile_time("label") expr;` -- times evaluating `expr` and prints
449
+ // it. The label is evaluated before the clock starts, so it isn't counted.
450
+ Value Evaluator::evalProfileTimeExpr(const oscad::ProfileTimeOp& node, EvalContext& ctx) {
451
+ checkDebug(node, ctx);
452
+ const Value label = getArg(resolveArgs(*this, node.arguments, ctx), 0, "label");
453
+ const auto start = std::chrono::steady_clock::now();
454
+ Value result = evalExpr(*node.body, ctx);
455
+ const double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
456
+ if (echoFn_) echoFn_("PROFILE: " + profileLabel(label, node.position()) + ": " + fmtMs(ms));
457
+ return result;
458
+ }
459
+
447
460
  Value Evaluator::evalAssertExpr(const oscad::AssertOp& node, EvalContext& ctx) {
448
461
  // Unlike the statement form (evalAssertStatement), which supports named
449
462
  // arguments via getArg()/CallArgs, the expression form indexes raw
@@ -518,6 +531,8 @@ Value Evaluator::evalExpr(const oscad::Expression& node, EvalContext& ctx) {
518
531
  return evalEchoExpr(static_cast<const oscad::EchoOp&>(node), ctx);
519
532
  case NodeKind::AssertOp:
520
533
  return evalAssertExpr(static_cast<const oscad::AssertOp&>(node), ctx);
534
+ case NodeKind::ProfileTimeOp:
535
+ return evalProfileTimeExpr(static_cast<const oscad::ProfileTimeOp&>(node), ctx);
521
536
 
522
537
  case NodeKind::PrimaryIndex: {
523
538
  auto& n = static_cast<const oscad::PrimaryIndex&>(node);