openscad-cpp-evaluator 0.17.0__tar.gz → 0.22.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 (198) hide show
  1. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/bindings/module.cpp +27 -3
  3. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +21 -0
  4. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/grammar/lexer.l +5 -1
  5. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +73 -0
  6. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/eval_context.hpp +15 -0
  7. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/evaluator.hpp +41 -2
  8. openscad_cpp_evaluator-0.22.0/include/openscad_cpp_evaluator/profile.hpp +81 -0
  9. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/pyproject.toml +1 -1
  10. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/python/openscad_cpp_evaluator/__init__.py +20 -2
  11. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/csg_resolve.cpp +3 -1
  12. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/debug_profile.cpp +116 -3
  13. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/eval_context.cpp +1 -0
  14. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/user_calls.cpp +14 -1
  15. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_cli.cpp +1 -1
  16. openscad_cpp_evaluator-0.22.0/tests/test_profiling.cpp +409 -0
  17. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tools/cli/cli_lib.cpp +8 -5
  18. openscad_cpp_evaluator-0.17.0/include/openscad_cpp_evaluator/profile.hpp +0 -43
  19. openscad_cpp_evaluator-0.17.0/tests/test_profiling.cpp +0 -127
  20. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/.github/workflows/ci.yml +0 -0
  21. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/.github/workflows/wheels.yml +0 -0
  22. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/.gitignore +0 -0
  23. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/.gitmodules +0 -0
  24. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/CLAUDE.md +0 -0
  25. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/LICENSE +0 -0
  27. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/README.md +0 -0
  28. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/bindings/CMakeLists.txt +0 -0
  29. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/bindings/ast_to_py.cpp +0 -0
  30. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/cmake/embed_font.cmake +0 -0
  31. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/examples/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/examples/eval_perf_benchmark.cpp +0 -0
  33. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/examples/manifold_cache_reuse.cpp +0 -0
  34. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/examples/minimal_debugger.cpp +0 -0
  35. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  36. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/.gitignore +0 -0
  37. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  38. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  39. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/README.md +0 -0
  40. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  41. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  42. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  43. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  44. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  45. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  46. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  47. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  48. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  49. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  50. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  51. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  52. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  53. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  54. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  55. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  56. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  57. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  58. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  59. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  60. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  61. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  62. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  63. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  64. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  65. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  66. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  67. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  68. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  69. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  70. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  71. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  72. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  73. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  74. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  75. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  76. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  77. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  78. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  79. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  80. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  81. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  82. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  83. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  84. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  85. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  86. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  87. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  88. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  89. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  90. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  91. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  92. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  93. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  94. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  95. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  96. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  97. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  98. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  99. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  100. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  101. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  102. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  103. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  104. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  105. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  106. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  107. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  108. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  109. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  110. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  111. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  112. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  113. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  114. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  115. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  116. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  117. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  118. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  119. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  120. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  121. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
  122. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  123. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  124. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  125. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  126. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/resources/fonts/LICENSE +0 -0
  127. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  128. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/CMakeLists.txt +0 -0
  129. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/booleans.cpp +0 -0
  130. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/builtins.hpp +0 -0
  131. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/call_args.cpp +0 -0
  132. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/color.cpp +0 -0
  133. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/control.cpp +0 -0
  134. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/extrude.cpp +0 -0
  135. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/function_builtins.cpp +0 -0
  136. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/import.cpp +0 -0
  137. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/modifiers.cpp +0 -0
  138. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/primitives_2d.cpp +0 -0
  139. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/primitives_3d.cpp +0 -0
  140. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/registry.cpp +0 -0
  141. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/roof.cpp +0 -0
  142. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/segments.cpp +0 -0
  143. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/surface.cpp +0 -0
  144. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/text.cpp +0 -0
  145. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/topology.cpp +0 -0
  146. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/builtins/transforms.cpp +0 -0
  147. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/bytecode_compiler.cpp +0 -0
  148. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/bytecode_vm.cpp +0 -0
  149. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/colored_body.cpp +0 -0
  150. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/csg_generate.cpp +0 -0
  151. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/css_colors.cpp +0 -0
  152. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/debug/debug_repl.cpp +0 -0
  153. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/eval_error.cpp +0 -0
  154. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/eval_use.cpp +0 -0
  155. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/export.cpp +0 -0
  156. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/expr_eval.cpp +0 -0
  157. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/import/dxf_import.cpp +0 -0
  158. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/import/mesh_import.cpp +0 -0
  159. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/import/surface_load.cpp +0 -0
  160. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/import/svg_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/manifold_cache.cpp +0 -0
  162. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/stmt_eval.cpp +0 -0
  163. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/text/stb_font_provider.cpp +0 -0
  164. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/text/text_metrics.cpp +0 -0
  165. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/value.cpp +0 -0
  166. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/src/zip_stored.cpp +0 -0
  167. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/CMakeLists.txt +0 -0
  168. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_booleans.cpp +0 -0
  169. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_bytecode_compiler.cpp +0 -0
  170. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_control_flow.cpp +0 -0
  171. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_csg_tree.cpp +0 -0
  172. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_debug_hooks.cpp +0 -0
  173. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_dxf_svg_import.cpp +0 -0
  174. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_eval_context.cpp +0 -0
  175. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_export.cpp +0 -0
  176. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_expr_eval.cpp +0 -0
  177. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_extrude_roof.cpp +0 -0
  178. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_function_builtins.cpp +0 -0
  179. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_helpers.hpp +0 -0
  180. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_import_export.cpp +0 -0
  181. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_manifold_cache.cpp +0 -0
  182. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_multi_color_merge.cpp +0 -0
  183. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_primitives.cpp +0 -0
  184. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_python_bindings.py +0 -0
  185. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_scope_trail.cpp +0 -0
  186. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_scoping.cpp +0 -0
  187. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_surface.cpp +0 -0
  188. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_tail_calls.cpp +0 -0
  189. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_text.cpp +0 -0
  190. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_transforms.cpp +0 -0
  191. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_use.cpp +0 -0
  192. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_value.cpp +0 -0
  193. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_viewport_params.cpp +0 -0
  194. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tests/test_zip_stored.cpp +0 -0
  195. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tools/cli/CMakeLists.txt +0 -0
  196. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tools/cli/cli_lib.hpp +0 -0
  197. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/tools/cli/main.cpp +0 -0
  198. {openscad_cpp_evaluator-0.17.0 → openscad_cpp_evaluator-0.22.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.17.0
3
+ Version: 0.22.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -246,10 +246,34 @@ nb::object profileResultToPy(const std::optional<oscadeval::ProfileResult>& pr)
246
246
  if (!pr) return nb::none();
247
247
  nb::list sites;
248
248
  for (const oscadeval::CallSiteProfile& s : pr->callSites) {
249
- sites.append(facadeAttr("CallSiteProfile")(s.kind, s.name, s.callerName, s.callOrigin, s.callLine, s.declOrigin,
250
- s.declLine, s.callCount, s.selfTime, s.cumulativeTime));
249
+ sites.append(facadeAttr("CallSiteProfile")(s.kind, s.name, s.callerName, s.callOrigin, s.callLine, s.callColumn,
250
+ s.declOrigin, s.declLine, s.callCount, s.selfTime, s.cumulativeTime));
251
251
  }
252
- return facadeAttr("ProfileResult")(sites, pr->resolveTime, pr->generateTime, pr->totalTime, pr->unattributedTime);
252
+ // The calling-context tree, as plain dicts. Parent/child INDICES, not
253
+ // nested objects: the C++ side is already a flat vector keyed that way,
254
+ // and a self-referential nesting would have to be rebuilt here for no
255
+ // gain -- a consumer walks it by index just as C++ does.
256
+ nb::list paths;
257
+ for (const oscadeval::ProfilePathNode& n : pr->paths) {
258
+ nb::dict d;
259
+ d["parent"] = n.parent;
260
+ nb::list kids;
261
+ for (int c : n.children) kids.append(c);
262
+ d["children"] = kids;
263
+ d["kind"] = n.kind;
264
+ d["name"] = n.name;
265
+ d["call_origin"] = n.callOrigin;
266
+ d["call_line"] = n.callLine;
267
+ d["call_column"] = n.callColumn;
268
+ d["decl_origin"] = n.declOrigin;
269
+ d["decl_line"] = n.declLine;
270
+ d["call_count"] = n.callCount;
271
+ d["self_time"] = n.selfTime;
272
+ d["cumulative_time"] = n.cumulativeTime;
273
+ paths.append(d);
274
+ }
275
+ return facadeAttr("ProfileResult")(sites, pr->resolveTime, pr->generateTime, pr->totalTime, pr->unattributedTime,
276
+ paths);
253
277
  }
254
278
 
255
279
  // ctx.dyn / ctx.dynExplicit -> (dict[str, Any], set[str]) -- every currently-
@@ -49,6 +49,18 @@ public:
49
49
  LexPos tokenEnd;
50
50
  std::string stringBuffer; // accumulates a STRING token's content while in the %x STR lexer state
51
51
 
52
+ // Where the opening quote of the STRING currently being lexed began.
53
+ //
54
+ // Needed because YY_USER_ACTION resets tokenStart on EVERY rule match,
55
+ // and a string is matched by several rules (opening quote, content
56
+ // runs, escapes, closing quote). currentTokenLoc() at the closing
57
+ // quote therefore describes just that one character, which made every
58
+ // StringLiteral's source span a single `"` -- and dragged the span of
59
+ // anything wrapping it (a PositionalArgument, a vector element) along
60
+ // with it. Recorded when the opening quote is matched and used by
61
+ // stringTokenLoc() below.
62
+ LexPos stringStart;
63
+
52
64
  Position toPosition(const OscadLocation& loc) const {
53
65
  return Position{origin_, loc.first_line, loc.first_column, loc.first_offset, loc.last_offset};
54
66
  }
@@ -58,6 +70,15 @@ public:
58
70
  tokenEnd.column, tokenStart.offset, tokenEnd.offset};
59
71
  }
60
72
 
73
+ // As currentTokenLoc(), but spanning from the string's OPENING quote
74
+ // (stringStart) to the current token's end -- i.e. the whole literal
75
+ // including both quotes, which is what a caller slicing the source by
76
+ // this span expects to get back.
77
+ OscadLocation stringTokenLoc() const {
78
+ return OscadLocation{stringStart.line, stringStart.column, tokenEnd.line,
79
+ tokenEnd.column, stringStart.offset, tokenEnd.offset};
80
+ }
81
+
61
82
  void reportError(const OscadLocation& loc, const std::string& message);
62
83
  };
63
84
 
@@ -93,13 +93,17 @@ NUMLIT 0[xX][0-9A-Fa-f]+|[0-9]+([.][0-9]*)?([eE][+-]?[0-9]+)?|[.][0-9]+([eE][+-
93
93
 
94
94
  \" {
95
95
  driver.stringBuffer.clear();
96
+ // YY_USER_ACTION has just set tokenStart to this opening quote; keep
97
+ // it, because the rules that follow will overwrite it (see
98
+ // ParserDriver::stringStart).
99
+ driver.stringStart = driver.tokenStart;
96
100
  BEGIN(STR);
97
101
  }
98
102
  <STR>\\. { driver.stringBuffer.append(yytext, yyleng); }
99
103
  <STR>[^"\\]+ { driver.stringBuffer.append(yytext, yyleng); }
100
104
  <STR>\" {
101
105
  BEGIN(INITIAL);
102
- return yy::parser::make_STRING(driver.stringBuffer, driver.currentTokenLoc());
106
+ return yy::parser::make_STRING(driver.stringBuffer, driver.stringTokenLoc());
103
107
  }
104
108
 
105
109
  <INCFILE>[ \t\r\n]+ { /* skip whitespace before < */ }
@@ -363,3 +363,76 @@ TEST(LexicalIdentifiers, IdentifierStr) {
363
363
  std::vector<std::unique_ptr<ASTNode>> ast;
364
364
  EXPECT_EQ(exprSrc("foo", ast)->toString(), "foo");
365
365
  }
366
+
367
+ // -- Source spans ---------------------------------------------------------
368
+
369
+ namespace {
370
+ // The source text a node's own position spans -- what a consumer slicing
371
+ // by start/end_offset actually gets back.
372
+ std::string spanned(const std::string& src, const oscad::ASTNode& n) {
373
+ const auto& p = n.position();
374
+ return src.substr(static_cast<size_t>(p.start_offset),
375
+ static_cast<size_t>(p.end_offset - p.start_offset));
376
+ }
377
+ } // namespace
378
+
379
+ // A string is matched by SEVERAL lexer rules (opening quote, content runs,
380
+ // escapes, closing quote), and YY_USER_ACTION resets tokenStart on every
381
+ // one of them -- so building the token's location from currentTokenLoc() at
382
+ // the closing quote described just that one character. Every StringLiteral
383
+ // spanned a bare `"`, and so did anything wrapping it, which silently
384
+ // corrupted a downstream consumer that sliced source by those offsets.
385
+ TEST(SourceSpans, StringLiteralSpansTheWholeLiteral) {
386
+ struct Case { std::string src; std::string want; };
387
+ const Case cases[] = {
388
+ {"a = \"txt\";", "\"txt\""},
389
+ {"a = \"\";", "\"\""}, // empty
390
+ {"a = \"with \\\"quote\\\" in it\";", "\"with \\\"quote\\\" in it\""},
391
+ {"a = \"esc \\n seq\";", "\"esc \\n seq\""},
392
+ {"a = \"comma,inside\";", "\"comma,inside\""},
393
+ };
394
+ for (const Case& c : cases) {
395
+ auto ast = oscad::parseSrc(c.src);
396
+ ASSERT_EQ(ast.size(), 1u) << c.src;
397
+ auto* assign = dynamic_cast<oscad::Assignment*>(ast[0].get());
398
+ ASSERT_NE(assign, nullptr) << c.src;
399
+ EXPECT_EQ(spanned(c.src, *assign->expr), c.want) << c.src;
400
+ }
401
+ }
402
+
403
+ // The enclosing node's span has to be right too -- that is how the bug
404
+ // actually did damage, by dragging an argument's end_offset into the
405
+ // middle of the string.
406
+ TEST(SourceSpans, NodesWrappingAStringSpanCorrectly) {
407
+ const std::string src = "f(\"x,y\", b);";
408
+ auto ast = oscad::parseSrc(src);
409
+ auto* call = dynamic_cast<oscad::ModularCall*>(ast[0].get());
410
+ ASSERT_NE(call, nullptr);
411
+ ASSERT_EQ(call->arguments.size(), 2u);
412
+ EXPECT_EQ(spanned(src, *call->arguments[0]), "\"x,y\"");
413
+ EXPECT_EQ(spanned(src, *call->arguments[1]), "b");
414
+ }
415
+
416
+ TEST(SourceSpans, StringsInAVectorSpanCorrectly) {
417
+ const std::string src = "x = [\"a,b\",\"c\"];";
418
+ auto ast = oscad::parseSrc(src);
419
+ auto* assign = dynamic_cast<oscad::Assignment*>(ast[0].get());
420
+ ASSERT_NE(assign, nullptr);
421
+ auto* vec = dynamic_cast<oscad::ListComprehension*>(assign->expr.get());
422
+ ASSERT_NE(vec, nullptr);
423
+ ASSERT_EQ(vec->elements.size(), 2u);
424
+ EXPECT_EQ(spanned(src, *vec->elements[0]), "\"a,b\"");
425
+ EXPECT_EQ(spanned(src, *vec->elements[1]), "\"c\"");
426
+ }
427
+
428
+ // Multi-line strings must still report the OPENING quote's line/column, not
429
+ // the closing one's.
430
+ TEST(SourceSpans, MultiLineStringReportsItsStartingLine) {
431
+ const std::string src = "a = 1;\nb = \"one\ntwo\";\n";
432
+ auto ast = oscad::parseSrc(src);
433
+ ASSERT_EQ(ast.size(), 2u);
434
+ auto* assign = dynamic_cast<oscad::Assignment*>(ast[1].get());
435
+ ASSERT_NE(assign, nullptr);
436
+ EXPECT_EQ(assign->expr->position().line, 2);
437
+ EXPECT_EQ(spanned(src, *assign->expr), "\"one\ntwo\"");
438
+ }
@@ -59,6 +59,21 @@ struct EvalContext {
59
59
  std::shared_ptr<const ChildrenNodeList> childrenNodes;
60
60
  const EvalContext* childrenCallerCtx = nullptr;
61
61
 
62
+ // True while evaluating statements that a children() forwarded, so a
63
+ // module call reached this way profiles as kind "child" rather than
64
+ // "module". `foo() foo();` and `module foo() { foo(); }` both produce a
65
+ // foo->foo edge, but only the second is recursion, and a profile that
66
+ // cannot tell them apart is misleading.
67
+ //
68
+ // Propagates through withScope/childCtx (a forwarded `translate()
69
+ // foo()` is still child-passing) but NOT through callCtx, which resets
70
+ // it by omission -- entering a body ends the forwarding. The one
71
+ // deliberate exception is buildModuleChildCtx, which copies the
72
+ // caller's flag onto the body ctx so enterUserCall can read it there;
73
+ // enterUserCall then clears it, so the call it describes is the only
74
+ // one that sees it.
75
+ bool viaChildren = false;
76
+
62
77
  // The one genuinely fresh construction: seeds `dyn` with OpenSCAD's
63
78
  // built-in $-variable defaults ($fn=0, $fa=12, $fs=2, $t=0,
64
79
  // $parent_modules=0). Every other EvalContext in a run is derived
@@ -1234,12 +1234,16 @@ private:
1234
1234
 
1235
1235
  // -- Profiling (Phase 9) --------------------------------------------
1236
1236
 
1237
- // (kind, name, callOrigin, callLine) -- the exact call-site identity a
1237
+ // (kind, name, callOrigin, callLine, callColumn) -- the exact call-site
1238
+ // identity a
1238
1239
  // CallSiteProfile aggregates by. std::map (not unordered_map): a plain
1239
1240
  // tuple of comparable fields already gets a free operator< from the
1240
1241
  // standard library, so this needs no custom hash function for a table
1241
1242
  // that's never more than a few thousand entries even on a large script.
1242
- using ProfileSiteKey = std::tuple<std::string, std::string, std::string, int>;
1243
+ // Column is part of the key, not decoration: `foo() foo();` and
1244
+ // `f(g(x))` put two distinct call sites on one line, and without it
1245
+ // they aggregate into one entry whose times belong to neither.
1246
+ using ProfileSiteKey = std::tuple<std::string, std::string, std::string, int, int>;
1243
1247
 
1244
1248
  // Pushes profiling state for a user module/function call about to
1245
1249
  // start -- shared by evalUserModule/evalUserFunction/
@@ -1252,6 +1256,13 @@ private:
1252
1256
  ProfileSiteKey key;
1253
1257
  bool recursiveReentry;
1254
1258
  std::chrono::steady_clock::time_point start;
1259
+ // Node this call occupies in the calling-context tree, and the one
1260
+ // to restore on exit. `pathRecursive` is the per-PATH equivalent of
1261
+ // recursiveReentry: true when this entry folded back onto a node
1262
+ // already on the path, so only the outermost entry adds cumulative
1263
+ // time -- the same rule callSites uses, applied per node.
1264
+ int pathNode = -1;
1265
+ int pathPrev = -1;
1255
1266
  };
1256
1267
  std::optional<ProfileHandle> profileEnter(const std::string& kind, const std::string& name,
1257
1268
  const oscad::Position* callPos, const oscad::Position* declPos);
@@ -1712,6 +1723,34 @@ private:
1712
1723
  std::map<ProfileSiteKey, CallSiteProfile> profileSites_;
1713
1724
  std::set<ProfileSiteKey> profileActive_; // site keys with a call currently on callStack_
1714
1725
  std::vector<double> profileChildTime_; // parallel aux stack to callStack_
1726
+
1727
+ // Calling-context tree (see ProfilePathNode). profilePaths_[0] is the
1728
+ // <toplevel> root, created lazily on the first profiled call;
1729
+ // profileCurrentPath_ is the node the running call belongs to, so a
1730
+ // profileEnter knows which parent to hang its node off.
1731
+ std::vector<ProfilePathNode> profilePaths_;
1732
+ int profileCurrentPath_ = -1;
1733
+
1734
+ // Safety valve. The tree is bounded by distinct ACYCLIC paths (see
1735
+ // ProfilePathNode on recursion folding), which is finite but can still
1736
+ // be large for a deeply-layered library. Past this, no new nodes are
1737
+ // created and further calls fold onto their parent -- the report stays
1738
+ // truthful about totals and simply stops subdividing, rather than the
1739
+ // profiler becoming the thing that runs out of memory.
1740
+ static constexpr size_t kMaxProfilePathNodes = 200000;
1741
+
1742
+ // Finds-or-creates the child of profileCurrentPath_ for this call site
1743
+ // and returns its index. Sets `folded` when the returned node was
1744
+ // already on the current path (recursion, or the node cap) rather than
1745
+ // a fresh or sibling child -- the caller must then skip cumulative
1746
+ // time for this entry, exactly as recursiveReentry does for callSites.
1747
+ int profilePathEnter(const std::string& kind, const std::string& name, const std::string& callOrigin,
1748
+ int callLine, int callColumn, const oscad::Position* declPos, bool& folded);
1749
+
1750
+ // Fills in every node's cumulativeTime as selfTime + the cumulative of
1751
+ // its children, bottom-up. See profileExit for why this is derived
1752
+ // rather than measured.
1753
+ void finalizeProfilePaths();
1715
1754
  };
1716
1755
 
1717
1756
  } // namespace oscadeval
@@ -0,0 +1,81 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <vector>
5
+
6
+ namespace oscadeval {
7
+
8
+ // Aggregated profiling data for one *call site* -- a specific source
9
+ // location that calls a specific user module/function, not one
10
+ // declaration. Two different calls to the same function get separate
11
+ // entries; the same call expression re-executed many times (a loop body,
12
+ // recursion) aggregates into one entry with callCount > 1, since the AST
13
+ // node (and thus its position) is identical across those invocations.
14
+ // Mirrors the reference's CallSiteProfile.
15
+ struct CallSiteProfile {
16
+ std::string kind; // "module" | "child" (forwarded via children()) | "function"
17
+ std::string name;
18
+ std::string callerName; // enclosing module/function's own name, or "<toplevel>"
19
+ std::string callOrigin;
20
+ int callLine = 0;
21
+ int callColumn = 0; // distinguishes two calls sharing one line
22
+ std::string declOrigin;
23
+ int declLine = 0;
24
+ int callCount = 0;
25
+ double selfTime = 0.0; // seconds, own code only, never double-counted
26
+ double cumulativeTime = 0.0; // seconds, includes children; recursion-guarded
27
+ };
28
+
29
+ // One node of the calling-context tree: a call site reached by ONE
30
+ // specific path from <toplevel>. Where CallSiteProfile aggregates a site
31
+ // over every path that reached it, this keeps them separate -- so
32
+ // `cuboid` called from `bracket` and `cuboid` called from `rail` are two
33
+ // nodes with their own times, and a consumer can show what a particular
34
+ // path actually cost rather than a total across all callers.
35
+ //
36
+ // Flat vector with parent/child indices rather than pointers: it survives
37
+ // the vector reallocating as nodes are appended, and crosses the Python
38
+ // binding as plain data with no ownership question.
39
+ //
40
+ // Recursion is folded rather than unrolled. Re-entering a call site
41
+ // already on the current path reuses that node instead of appending a new
42
+ // child, so `fib` calling itself 400 deep is one node with callCount 400,
43
+ // not a 400-node chain. That bounds the tree by distinct ACYCLIC paths and
44
+ // keeps the recursion-guarded cumulative-time rule (only the outermost
45
+ // entry contributes) meaningful per node.
46
+ struct ProfilePathNode {
47
+ int parent = -1; // index into ProfileResult::paths; -1 for the root
48
+ std::vector<int> children; // indices, in first-call order
49
+ std::string kind; // "module" | "child" | "function"; empty for the root
50
+ std::string name; // callee; "<toplevel>" for the root
51
+ std::string callOrigin;
52
+ int callLine = 0;
53
+ int callColumn = 0; // distinguishes two calls sharing one line
54
+ std::string declOrigin;
55
+ int declLine = 0;
56
+ int callCount = 0;
57
+ double selfTime = 0.0; // seconds, this node's own code on this path
58
+ double cumulativeTime = 0.0; // seconds, this node and everything under it
59
+ };
60
+
61
+ // Whole-evaluate() profiling summary, built when Evaluator is constructed
62
+ // with profiling=true. unattributedTime covers top-level script code and
63
+ // anything else not inside a user module/function call (native builtins'
64
+ // own resolve work, mostly) -- resolveTime always equals
65
+ // sum(s.selfTime for callSites) + unattributedTime, so a UI can show
66
+ // percentages that honestly add to 100%. Mirrors the reference's
67
+ // ProfileResult.
68
+ struct ProfileResult {
69
+ std::vector<CallSiteProfile> callSites;
70
+ // The calling-context tree; paths[0] is always the <toplevel> root.
71
+ // Empty only if profiling was off. callSites stays exactly as it was --
72
+ // the flat per-site view is still the right answer to "what is
73
+ // expensive overall", and is cheaper to scan than walking this.
74
+ std::vector<ProfilePathNode> paths;
75
+ double resolveTime = 0.0;
76
+ double generateTime = 0.0;
77
+ double totalTime = 0.0;
78
+ double unattributedTime = 0.0;
79
+ };
80
+
81
+ } // namespace oscadeval
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.17.0"
7
+ version = "0.22.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -9,7 +9,7 @@ like a manifold3d body (`.is_empty()` / `.to_mesh()`), so the renderer reads
9
9
  `cb.body.to_mesh().vert_properties/tri_verts/run_original_id/run_index`
10
10
  exactly as before -- no cross-module Manifold objects are ever exchanged.
11
11
  """
12
- from dataclasses import dataclass
12
+ from dataclasses import dataclass, field
13
13
  from typing import Optional
14
14
 
15
15
  from . import _openscad_cpp_evaluator as _ext
@@ -142,11 +142,12 @@ class _CSGNode:
142
142
  class CallSiteProfile:
143
143
  """Aggregated profiling data for one call site. Mirrors the reference's
144
144
  CallSiteProfile -- see ProfileResult's own docstring."""
145
- kind: str # "module" | "function"
145
+ kind: str # "module" | "child" | "function"
146
146
  name: str
147
147
  caller_name: str
148
148
  call_origin: str
149
149
  call_line: int
150
+ call_column: int
150
151
  decl_origin: str
151
152
  decl_line: int
152
153
  call_count: int = 0
@@ -165,6 +166,23 @@ class ProfileResult:
165
166
  generate_time: float
166
167
  total_time: float
167
168
  unattributed_time: float
169
+ # Calling-context tree: a flat list of dicts, paths[0] the <toplevel>
170
+ # root, linked by `parent`/`children` INDICES into this same list.
171
+ #
172
+ # Where call_sites aggregates a site over every path that reached it,
173
+ # each node here is one call site on ONE path -- so `cuboid` called
174
+ # from `bracket` and from `rail` are separate nodes with their own
175
+ # times, and a report can say what a particular path cost rather than
176
+ # only what a name cost in total.
177
+ #
178
+ # Keys: parent, children, kind, name, call_origin, call_line,
179
+ # decl_origin, decl_line, call_count, self_time, cumulative_time.
180
+ #
181
+ # Recursion is folded: re-entering a site already on the path reuses
182
+ # that node (call_count rises) instead of unrolling a node per level.
183
+ # Defaults to empty so a ProfileResult built by older code still
184
+ # constructs.
185
+ paths: list = field(default_factory=list)
168
186
 
169
187
 
170
188
  def _summarize_param(value, max_items: int = 6, max_len: int = 40) -> str:
@@ -269,8 +269,10 @@ std::vector<ColoredBody> Evaluator::evaluateImpl(const NodeList& nodes, EvalCont
269
269
  selfSum += site.selfTime;
270
270
  sites.push_back(site);
271
271
  }
272
+ finalizeProfilePaths();
272
273
  profileResult = ProfileResult{
273
- std::move(sites), resolveTime, generateTime, resolveTime + generateTime, std::max(0.0, resolveTime - selfSum),
274
+ std::move(sites), profilePaths_, resolveTime, generateTime, resolveTime + generateTime,
275
+ std::max(0.0, resolveTime - selfSum),
274
276
  };
275
277
  }
276
278
 
@@ -116,12 +116,87 @@ void Evaluator::checkDebug(const oscad::ASTNode& node, EvalContext& ctx, bool fo
116
116
  if (action.stop) throw EvalError(kDebuggingStoppedMessage);
117
117
  }
118
118
 
119
+ int Evaluator::profilePathEnter(const std::string& kind, const std::string& name, const std::string& callOrigin,
120
+ int callLine, int callColumn, const oscad::Position* declPos, bool& folded) {
121
+ folded = false;
122
+ if (profilePaths_.empty()) {
123
+ ProfilePathNode root;
124
+ root.name = "<toplevel>";
125
+ profilePaths_.push_back(std::move(root));
126
+ profileCurrentPath_ = 0;
127
+ }
128
+ const int parent = profileCurrentPath_ < 0 ? 0 : profileCurrentPath_;
129
+
130
+ // Immediate self-recursion folds onto the parent rather than growing a
131
+ // chain per level (see ProfilePathNode).
132
+ //
133
+ // Only the parent, never a deeper ancestor. Folding across intervening
134
+ // nodes credits the whole re-entered subtree to the ancestor, and every
135
+ // node between it and here loses that time -- cumulative is derived
136
+ // bottom-up, so it can only count what is actually below. A BOSL2
137
+ // attachment chain showed restore() at 226ms in the flat view and 2.5ms
138
+ // in the tree for the same call site, because its callees re-entered a
139
+ // multmatrix already open further up and took 223ms with them.
140
+ //
141
+ // A cycle through other call sites therefore gets a real node. That is
142
+ // what makes the tree's numbers agree with the flat view's; the cost is
143
+ // more nodes on mutually-recursive chains, which kMaxProfilePathNodes
144
+ // still bounds.
145
+ if (parent > 0) {
146
+ const ProfilePathNode& n = profilePaths_[static_cast<size_t>(parent)];
147
+ if (n.name == name && n.callOrigin == callOrigin && n.callLine == callLine &&
148
+ n.callColumn == callColumn && n.kind == kind) {
149
+ folded = true; // direct recursion: this site called itself
150
+ return parent;
151
+ }
152
+ }
153
+ for (int childIdx : profilePaths_[static_cast<size_t>(parent)].children) {
154
+ const ProfilePathNode& n = profilePaths_[static_cast<size_t>(childIdx)];
155
+ if (n.name == name && n.callOrigin == callOrigin && n.callLine == callLine &&
156
+ n.callColumn == callColumn && n.kind == kind) {
157
+ return childIdx;
158
+ }
159
+ }
160
+ if (profilePaths_.size() >= kMaxProfilePathNodes) {
161
+ folded = true; // cap reached: stop subdividing, keep totals honest
162
+ return parent;
163
+ }
164
+
165
+ ProfilePathNode node;
166
+ node.parent = parent;
167
+ node.kind = kind;
168
+ node.name = name;
169
+ node.callOrigin = callOrigin;
170
+ node.callLine = callLine;
171
+ node.callColumn = callColumn;
172
+ node.declOrigin = declPos ? declPos->origin : "";
173
+ node.declLine = declPos ? declPos->line : 0;
174
+ const int idx = static_cast<int>(profilePaths_.size());
175
+ profilePaths_.push_back(std::move(node));
176
+ profilePaths_[static_cast<size_t>(parent)].children.push_back(idx);
177
+ return idx;
178
+ }
179
+
180
+ void Evaluator::finalizeProfilePaths() {
181
+ // Children always have a higher index than their parent (a node is
182
+ // appended when first reached, and folding never creates an edge back
183
+ // to an ancestor), so one reverse sweep resolves the whole tree with
184
+ // no recursion and no risk of an unbounded native stack.
185
+ for (size_t i = profilePaths_.size(); i-- > 0;) {
186
+ ProfilePathNode& n = profilePaths_[i];
187
+ double total = n.selfTime;
188
+ for (int c : n.children) total += profilePaths_[static_cast<size_t>(c)].cumulativeTime;
189
+ n.cumulativeTime = total;
190
+ }
191
+ }
192
+
119
193
  std::optional<Evaluator::ProfileHandle> Evaluator::profileEnter(const std::string& kind, const std::string& name,
120
194
  const oscad::Position* callPos, const oscad::Position* declPos) {
121
195
  if (!profiling_) return std::nullopt;
122
196
  const std::string callOrigin = callPos ? callPos->origin : "";
123
197
  const int callLine = callPos ? callPos->line : 0;
124
- const ProfileSiteKey key{kind, name, callOrigin, callLine};
198
+ const int callColumn = callPos ? callPos->column : 0;
199
+ const ProfileSiteKey key{kind, name, callOrigin, callLine, callColumn};
125
200
 
126
201
  auto it = profileSites_.find(key);
127
202
  if (it == profileSites_.end()) {
@@ -137,6 +212,7 @@ std::optional<Evaluator::ProfileHandle> Evaluator::profileEnter(const std::strin
137
212
  site.callerName = callStack_.empty() ? "<toplevel>" : callStack_.back().name;
138
213
  site.callOrigin = callOrigin;
139
214
  site.callLine = callLine;
215
+ site.callColumn = callColumn;
140
216
  site.declOrigin = declPos ? declPos->origin : "";
141
217
  site.declLine = declPos ? declPos->line : 0;
142
218
  it = profileSites_.emplace(key, std::move(site)).first;
@@ -146,7 +222,17 @@ std::optional<Evaluator::ProfileHandle> Evaluator::profileEnter(const std::strin
146
222
  const bool recursiveReentry = profileActive_.count(key) > 0;
147
223
  if (!recursiveReentry) profileActive_.insert(key);
148
224
  profileChildTime_.push_back(0.0);
149
- return ProfileHandle{key, recursiveReentry, std::chrono::steady_clock::now()};
225
+
226
+ const int prevPath = profileCurrentPath_;
227
+ bool folded = false; // only affects node reuse now, not accounting
228
+ const int pathNode = profilePathEnter(kind, name, callOrigin, callLine, callColumn, declPos, folded);
229
+ profilePaths_[static_cast<size_t>(pathNode)].callCount += 1;
230
+ profileCurrentPath_ = pathNode;
231
+
232
+ ProfileHandle handle{key, recursiveReentry, std::chrono::steady_clock::now()};
233
+ handle.pathNode = pathNode;
234
+ handle.pathPrev = prevPath;
235
+ return handle;
150
236
  }
151
237
 
152
238
  void Evaluator::profileRecordTailHop(const std::string& kind, const std::string& name, const oscad::Position* callPos,
@@ -154,7 +240,8 @@ void Evaluator::profileRecordTailHop(const std::string& kind, const std::string&
154
240
  if (!profiling_) return;
155
241
  const std::string callOrigin = callPos ? callPos->origin : "";
156
242
  const int callLine = callPos ? callPos->line : 0;
157
- const ProfileSiteKey key{kind, name, callOrigin, callLine};
243
+ const int callColumn = callPos ? callPos->column : 0;
244
+ const ProfileSiteKey key{kind, name, callOrigin, callLine, callColumn};
158
245
 
159
246
  auto it = profileSites_.find(key);
160
247
  if (it == profileSites_.end()) {
@@ -174,6 +261,7 @@ void Evaluator::profileRecordTailHop(const std::string& kind, const std::string&
174
261
  site.callerName = callStack_.empty() ? "<toplevel>" : callStack_.back().name;
175
262
  site.callOrigin = callOrigin;
176
263
  site.callLine = callLine;
264
+ site.callColumn = callColumn;
177
265
  site.declOrigin = declPos ? declPos->origin : "";
178
266
  site.declLine = declPos ? declPos->line : 0;
179
267
  it = profileSites_.emplace(key, std::move(site)).first;
@@ -199,6 +287,31 @@ void Evaluator::profileExit(const ProfileHandle& handle) {
199
287
  site.cumulativeTime += elapsed;
200
288
  profileActive_.erase(handle.key);
201
289
  }
290
+
291
+ // Same accounting, but attributed to this call's own node in the
292
+ // calling-context tree rather than to the site aggregated over every
293
+ // path that reached it.
294
+ if (handle.pathNode >= 0 && static_cast<size_t>(handle.pathNode) < profilePaths_.size()) {
295
+ // Self time only. Cumulative is DERIVED from the subtree once the
296
+ // run finishes (finalizeProfilePaths), not measured here.
297
+ //
298
+ // Measuring it per entry is wrong at a fold point: when a site
299
+ // recurses, every level lands on the same node, but only the
300
+ // outermost entry may add its elapsed (or nested time double-
301
+ // counts) -- while the calls made from every level still attach as
302
+ // that node's children. The node then reads smaller than the
303
+ // children under it. Found on a real model: a `_translate` entered
304
+ // 7 times via recursion showed 22.85ms with 90.09ms of children.
305
+ //
306
+ // Self time has no such problem: it is disjoint by construction
307
+ // (the child-time stack subtracts nested work), so summing it
308
+ // across every entry is exactly this node's own cost on this path,
309
+ // and cumulative built from it can never contradict the subtree.
310
+ ProfilePathNode& node = profilePaths_[static_cast<size_t>(handle.pathNode)];
311
+ node.selfTime += elapsed - childTime;
312
+ profileCurrentPath_ = handle.pathPrev;
313
+ }
314
+
202
315
  if (!profileChildTime_.empty()) profileChildTime_.back() += elapsed;
203
316
  }
204
317
 
@@ -41,6 +41,7 @@ EvalContext EvalContext::childCtx(const oscad::Scope* newScope, std::optional<st
41
41
  result.color = newColor.has_value() ? newColor : color;
42
42
  result.childrenNodes = newChildrenNodes ? newChildrenNodes : childrenNodes;
43
43
  result.childrenCallerCtx = newChildrenCallerCtx ? newChildrenCallerCtx : childrenCallerCtx;
44
+ result.viaChildren = viaChildren;
44
45
  return result;
45
46
  }
46
47
 
@@ -337,6 +337,9 @@ EvalContext Evaluator::buildModuleChildCtx(const oscad::ModuleDeclaration& decl,
337
337
  for (const auto& c : call.children) childrenNodes->push_back(c.get());
338
338
 
339
339
  EvalContext childCtx = callCtxFor(decl, ctx, childScope, childrenNodes, &ctx);
340
+ // callCtx reset this to false; carry the CALLER's value across so
341
+ // enterUserCall can tell a forwarded child from a recursive call.
342
+ childCtx.viaChildren = ctx.viaChildren;
340
343
 
341
344
  // $children counts module-instantiation child *statements* passed in
342
345
  // `{}`, not the number of geometries they produce -- e.g. children()
@@ -712,7 +715,12 @@ Evaluator::UserCallHandle Evaluator::enterUserCall(const std::string& name, cons
712
715
  ++nativeUserCallDepth_;
713
716
  h.countedTowardNativeDepth = true;
714
717
  }
715
- h.prof = profileEnter(isModule ? "module" : "function", name, callPos, &declNode.position());
718
+ // Consumed, not just read: the body's own statements run in this same
719
+ // ctx, and a plain call among them is not itself a forwarded child.
720
+ const bool viaChildren = childCtx.viaChildren;
721
+ childCtx.viaChildren = false;
722
+ h.prof = profileEnter(isModule ? (viaChildren ? "child" : "module") : "function", name, callPos,
723
+ &declNode.position());
716
724
  callStack_.push_back(CallStackFrame{kind, name, callPos, &declNode.position(), &declNode, nullptr, upvalueParent});
717
725
  callStack_.back().bodyCtx = &childCtx; // per-frame locals for the debugger
718
726
  if (isModule) ++moduleCallDepth_;
@@ -920,6 +928,11 @@ std::optional<Evaluator::ChildrenForward> Evaluator::prepareChildrenForward(cons
920
928
  // A children() forwarding chain's own dyn/let_/etc. must alias the
921
929
  // *caller's* (not this ctx's) -- see EvalContext::withScope's rationale.
922
930
  EvalContext evalCtx = callerCtx->childCtx(nullptr, std::nullopt, callerCtx->childrenNodes, callerCtx->childrenCallerCtx);
931
+ // Everything evaluated from this context is a forwarded child, so a
932
+ // module call reached through it profiles as "child" not "module".
933
+ // Set here rather than at the returns: there are two of them (bare
934
+ // children() and indexed children(i)).
935
+ evalCtx.viaChildren = true;
923
936
  // `ctx` here is the post-resolveCallArgs effCtx, deliberately: a
924
937
  // `children($fn=12)`-style named-$ override lives only at effCtx's own
925
938
  // trail level, and TrailView::items() is ancestry-visible, so reading
@@ -222,7 +222,7 @@ TEST(CliProfile, CsvFormatWritesHeaderAndCommaSeparatedRows) {
222
222
  0);
223
223
  const std::string text = readFile(report);
224
224
  EXPECT_NE(text.find("# total_time,"), std::string::npos);
225
- EXPECT_NE(text.find("kind,name,caller,call_origin,call_line,call_count,self_time,cumulative_time\n"), std::string::npos);
225
+ EXPECT_NE(text.find("kind,name,caller,call_origin,call_line,call_column,call_count,self_time,cumulative_time\n"), std::string::npos);
226
226
  EXPECT_NE(text.find("function,fib,"), std::string::npos);
227
227
  std::filesystem::remove(src);
228
228
  std::filesystem::remove(out);