openscad-cpp-evaluator 1.18.0__tar.gz → 1.19.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 (222) hide show
  1. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/CLAUDE.md +33 -0
  2. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/PKG-INFO +1 -1
  3. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/bindings/module.cpp +56 -4
  4. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bytecode.hpp +6 -0
  5. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +5 -5
  6. openscad_cpp_evaluator-1.19.0/include/openscad_cpp_evaluator/coverage.hpp +116 -0
  7. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/evaluator.hpp +20 -1
  8. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/pyproject.toml +1 -1
  9. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/python/openscad_cpp_evaluator/__init__.py +13 -3
  10. openscad_cpp_evaluator-1.19.0/src/.DS_Store +0 -0
  11. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/CMakeLists.txt +1 -0
  12. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/bytecode_compiler.cpp +35 -16
  13. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/bytecode_vm.cpp +4 -0
  14. openscad_cpp_evaluator-1.19.0/src/coverage.cpp +397 -0
  15. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/csg_resolve.cpp +11 -0
  16. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/expr_eval.cpp +7 -2
  17. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/stmt_eval.cpp +9 -0
  18. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/user_calls.cpp +7 -5
  19. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/CMakeLists.txt +1 -0
  20. openscad_cpp_evaluator-1.19.0/tests/test_coverage.cpp +234 -0
  21. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_python_bindings.py +33 -0
  22. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/.github/workflows/ci.yml +0 -0
  23. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/.github/workflows/wheels.yml +0 -0
  24. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/.gitignore +0 -0
  25. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/.gitmodules +0 -0
  26. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/CMakeLists.txt +0 -0
  27. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/LICENSE +0 -0
  28. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/README.md +0 -0
  29. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/bindings/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/bindings/ast_to_py.cpp +0 -0
  31. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/cmake/embed_font.cmake +0 -0
  32. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/examples/CMakeLists.txt +0 -0
  33. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/examples/eval_perf_benchmark.cpp +0 -0
  34. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/examples/manifold_cache_reuse.cpp +0 -0
  35. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/examples/minimal_debugger.cpp +0 -0
  36. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  37. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/.gitignore +0 -0
  38. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  39. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  40. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/README.md +0 -0
  41. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  42. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  43. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  44. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  45. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  46. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  47. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  48. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  49. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  50. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  51. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  52. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  53. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  54. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  55. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  56. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  57. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  58. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  59. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  60. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  61. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  62. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  63. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  64. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  65. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  66. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  67. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  68. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  69. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  70. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  71. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  72. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  73. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  74. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  75. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  76. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  77. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  78. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  79. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  80. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  81. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  82. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  83. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  84. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  85. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  86. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  87. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  88. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  89. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  90. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  91. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  92. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  93. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  94. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  95. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  96. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  97. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  98. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  99. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  100. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
  101. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  102. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  103. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  104. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  105. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  106. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  107. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  108. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  109. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  110. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  111. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  112. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  113. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  114. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  115. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  116. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  117. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  118. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  119. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  120. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  121. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  122. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  123. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  124. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  125. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  126. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  127. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  128. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  129. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  130. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  131. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  132. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  133. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  134. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
  135. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  136. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  137. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LICENSE +0 -0
  138. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
  139. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
  140. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
  141. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  142. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/booleans.cpp +0 -0
  143. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/builtins.hpp +0 -0
  144. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/call_args.cpp +0 -0
  145. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/color.cpp +0 -0
  146. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/control.cpp +0 -0
  147. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/dxf_dim.cpp +0 -0
  148. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/extrude.cpp +0 -0
  149. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/function_builtins.cpp +0 -0
  150. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/import.cpp +0 -0
  151. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/modifiers.cpp +0 -0
  152. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/primitives_2d.cpp +0 -0
  153. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/primitives_3d.cpp +0 -0
  154. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/registry.cpp +0 -0
  155. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/roof.cpp +0 -0
  156. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/segments.cpp +0 -0
  157. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/surface.cpp +0 -0
  158. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/text.cpp +0 -0
  159. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/topology.cpp +0 -0
  160. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/builtins/transforms.cpp +0 -0
  161. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/colored_body.cpp +0 -0
  162. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/csg_generate.cpp +0 -0
  163. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/css_colors.cpp +0 -0
  164. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/debug/debug_repl.cpp +0 -0
  165. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/debug_profile.cpp +0 -0
  166. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/eval_context.cpp +0 -0
  167. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/eval_error.cpp +0 -0
  168. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/eval_use.cpp +0 -0
  169. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/export.cpp +0 -0
  170. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/format_closure.cpp +0 -0
  171. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/import/dxf_import.cpp +0 -0
  172. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/import/mesh_import.cpp +0 -0
  173. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/import/surface_load.cpp +0 -0
  174. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/import/svg_import.cpp +0 -0
  175. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/manifold_cache.cpp +0 -0
  176. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/measure_geometry.cpp +0 -0
  177. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/mesh_check.cpp +0 -0
  178. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/text/font_match.cpp +0 -0
  179. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/text/freetype_font_provider.cpp +0 -0
  180. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/text/text_metrics.cpp +0 -0
  181. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/utf8.cpp +0 -0
  182. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/value.cpp +0 -0
  183. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/src/zip_stored.cpp +0 -0
  184. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_booleans.cpp +0 -0
  185. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_bytecode_compiler.cpp +0 -0
  186. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_children_separate.cpp +0 -0
  187. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_cli.cpp +0 -0
  188. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_control_flow.cpp +0 -0
  189. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_csg_tree.cpp +0 -0
  190. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_debug_hooks.cpp +0 -0
  191. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_dxf_svg_import.cpp +0 -0
  192. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_eval_context.cpp +0 -0
  193. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_export.cpp +0 -0
  194. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_expr_eval.cpp +0 -0
  195. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_extrude_roof.cpp +0 -0
  196. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_feature_detection.cpp +0 -0
  197. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_function_builtins.cpp +0 -0
  198. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_helpers.hpp +0 -0
  199. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_import_export.cpp +0 -0
  200. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_include_cache.cpp +0 -0
  201. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_manifold_cache.cpp +0 -0
  202. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_mesh_check.cpp +0 -0
  203. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_multi_color_merge.cpp +0 -0
  204. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_primitives.cpp +0 -0
  205. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_profiling.cpp +0 -0
  206. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_render_expr.cpp +0 -0
  207. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_scope_trail.cpp +0 -0
  208. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_scoping.cpp +0 -0
  209. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_surface.cpp +0 -0
  210. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_tail_calls.cpp +0 -0
  211. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_text.cpp +0 -0
  212. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_transforms.cpp +0 -0
  213. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_unexpected_args.cpp +0 -0
  214. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_use.cpp +0 -0
  215. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_value.cpp +0 -0
  216. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_viewport_params.cpp +0 -0
  217. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tests/test_zip_stored.cpp +0 -0
  218. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/CMakeLists.txt +0 -0
  219. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/cli_lib.cpp +0 -0
  220. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/cli_lib.hpp +0 -0
  221. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/tools/cli/main.cpp +0 -0
  222. {openscad_cpp_evaluator-1.18.0 → openscad_cpp_evaluator-1.19.0}/uv.lock +0 -0
@@ -1480,6 +1480,39 @@ than the current pause point). Ported identically (same qualifier syntax, same e
1480
1480
  - `tools/cli/main.cpp` — a 3-line wrapper: builds `args` from `argv`, calls `runCli(args)` with the
1481
1481
  real `std::cin`/`std::cout`/`std::cerr`, returns its exit code.
1482
1482
 
1483
+ ## Coverage
1484
+
1485
+ `Evaluator(coverage=true)` records which parts of a run's source actually executed and
1486
+ fills `coverageResult`: `spans` (one per coverable node), `files` (one `CoverageFileSummary`
1487
+ per origin, sorted: statements/branches/bodies/spans, each with a hit count and a percent;
1488
+ `branches` counts Branch spans plus arm Statements, `percent` is over every span) and `total`
1489
+ (the same over every file). `summarizeCoverage()` is public so an aggregated span list can be
1490
+ re-summarised. Bindings: `Evaluator(coverage=True)` → `ev.coverage_result`, a dict
1491
+ `{spans, files, total}` of plain dicts. The vocabulary is fixed in one place,
1492
+ `coverage.hpp`/`coverage.cpp`, and used by both the recorder and the universe walk, so
1493
+ "covered" and "uncovered" never disagree about what counts:
1494
+
1495
+ - **statement** — every node in a statement list (top level, module/if/for/let bodies,
1496
+ `render()` children). A modifier (`%`/`#`/`!`/`*`) is the statement; the call it wraps is
1497
+ not one of its own. The first statement of an if/else arm carries `arm=true`.
1498
+ - **branch** — a ternary arm, a comprehension if/else arm, the right operand of `&&`/`||`.
1499
+ - **body** — a function, module or function-literal declaration: was it ever entered?
1500
+
1501
+ Hits are recorded at deliberate points, once per execution: `evalChildren`'s statement loop
1502
+ and `Op::Cover` (emitted per statement by `compileOneStatement`/`tryCompileAssignmentBlock`
1503
+ and at every arm, only when the Evaluator was built with coverage — chunks are per-Evaluator)
1504
+ on the two paths; `enterUserCall` for bodies; the arm sites in `evalExpr`, the tail
1505
+ trampoline (`simplifyTailStep`, which bypasses `evalExpr`'s own TernaryOp case) and the
1506
+ comprehension if/else cases. **Not** from `checkDebug`: the debugger checkpoints an if-arm's
1507
+ first statement twice and the `if` itself never, so piggybacking on it gave counts that
1508
+ differed between the VM and the interpreter. `Coverage.VmAndInterpreterAgree` compares the
1509
+ covered set under both and is the test that keeps them honest. The VM stays on under
1510
+ coverage (unlike a debug hook). The hit table is indexed by the node's `(treeId, slot)`
1511
+ numbering, so a hit is an array increment. Used-file globals are in the universe via
1512
+ `usedFileGlobals_`; everything else comes from the processed node list with each node's own
1513
+ `origin`, so includes and `use`d files report under their own paths. Let-bindings and plain
1514
+ comprehension elements are not in the vocabulary; hits on them are simply not reported.
1515
+
1483
1516
  ## `render()` in expression position
1484
1517
 
1485
1518
  `render()` has two jobs, decided by where it is written:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.18.0
3
+ Version: 1.19.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -26,6 +26,7 @@
26
26
  #include "openscad_cpp_evaluator/freetype_font_provider.hpp"
27
27
  #include "openscad_cpp_evaluator/mesh_check.hpp"
28
28
  #include "openscad_cpp_evaluator/manifold_cache.hpp"
29
+ #include "openscad_cpp_evaluator/coverage.hpp"
29
30
  #include "openscad_cpp_evaluator/profile.hpp"
30
31
  #include "openscad_cpp_evaluator/value.hpp"
31
32
 
@@ -432,9 +433,51 @@ nb::list exportModelPy(const std::string& path, const Geometry& geom, const std:
432
433
  return out;
433
434
  }
434
435
 
436
+ nb::dict coverageSummaryToPy(const oscadeval::CoverageFileSummary& f) {
437
+ nb::dict d;
438
+ d["origin"] = f.origin;
439
+ d["statements"] = f.statements;
440
+ d["statements_hit"] = f.statements_hit;
441
+ d["branches"] = f.branches;
442
+ d["branches_hit"] = f.branches_hit;
443
+ d["bodies"] = f.bodies;
444
+ d["bodies_hit"] = f.bodies_hit;
445
+ d["spans"] = f.spans;
446
+ d["spans_hit"] = f.spans_hit;
447
+ d["percent"] = f.percent();
448
+ d["statement_percent"] = f.statement_percent();
449
+ d["branch_percent"] = f.branch_percent();
450
+ d["body_percent"] = f.body_percent();
451
+ return d;
452
+ }
453
+
454
+ nb::object coverageResultToPy(const std::optional<oscadeval::CoverageResult>& cr) {
455
+ if (!cr) return nb::none();
456
+ nb::list files;
457
+ for (const oscadeval::CoverageFileSummary& f : cr->files) files.append(coverageSummaryToPy(f));
458
+ nb::list out;
459
+ for (const oscadeval::CoverageSpan& s : cr->spans) {
460
+ nb::dict d;
461
+ d["origin"] = s.origin;
462
+ d["line"] = s.line;
463
+ d["column"] = s.column;
464
+ d["start"] = s.start_offset;
465
+ d["end"] = s.end_offset;
466
+ d["kind"] = oscadeval::coverageKindName(s.kind);
467
+ d["arm"] = s.arm;
468
+ d["hits"] = s.hits;
469
+ out.append(d);
470
+ }
471
+ nb::dict result;
472
+ result["spans"] = out;
473
+ result["files"] = files;
474
+ result["total"] = coverageSummaryToPy(cr->total);
475
+ return result;
476
+ }
477
+
435
478
  nb::object evaluate(const std::string& path, nb::dict viewportParams,
436
479
  std::shared_ptr<oscadeval::ManifoldCache> manifoldCache, bool profile,
437
- bool generate, bool strictCommas) {
480
+ bool generate, bool strictCommas, bool coverage) {
438
481
  std::unordered_map<std::string, oscadeval::Value> vp = toViewportParams(viewportParams);
439
482
 
440
483
  std::vector<oscadeval::ColoredBody> bodies;
@@ -442,6 +485,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
442
485
  std::vector<IdSpan> idSpans;
443
486
  std::vector<std::unique_ptr<oscadeval::CSGNode>> csgTree;
444
487
  std::optional<oscadeval::ProfileResult> profileResult;
488
+ std::optional<oscadeval::CoverageResult> coverageResult;
445
489
  nb::dict dyn;
446
490
  nb::object dynExplicit;
447
491
  {
@@ -456,7 +500,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
456
500
  if (strictCommas) strict.emplace();
457
501
  oscad::ParsedProgram program = oscad::getProgramFromFile(path);
458
502
  oscadeval::ResolvedUseScopes used = oscadeval::resolveUseScopes(program.nodes, path, logFn);
459
- oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile);
503
+ oscadeval::Evaluator ev(logFn, nullptr, manifoldCache, oscadeval::DebugHooks{}, profile, coverage);
460
504
  ev.setUsedFileGlobals(used.usedFileGlobals);
461
505
  oscadeval::EvalContext ctx = oscadeval::EvalContext::makeRoot(used.rootScope.get());
462
506
  bodies = oscadeval::toRenderableBodies(ev.evaluate(used.processedNodes, ctx, vp, generate));
@@ -466,6 +510,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
466
510
  // which a resolve-only caller has no use for.
467
511
  if (generate) csgTree = std::move(ev.csgTree);
468
512
  profileResult = std::move(ev.profileResult);
513
+ coverageResult = std::move(ev.coverageResult);
469
514
  {
470
515
  nb::gil_scoped_acquire g; // building Python objects needs the GIL back
471
516
  std::tie(dyn, dynExplicit) = dynStateToPy(ctx);
@@ -495,7 +540,7 @@ nb::object evaluate(const std::string& path, nb::dict viewportParams,
495
540
  auto geom = std::make_shared<Geometry>();
496
541
  geom->bodies = std::move(bodies);
497
542
  return nb::make_tuple(bodiesToList(geom->bodies), echoList, idSpansToDict(idSpans), csgTreeToPy(csgTree),
498
- profileResultToPy(profileResult), dyn, dynExplicit, geom);
543
+ profileResultToPy(profileResult), dyn, dynExplicit, geom, coverageResultToPy(coverageResult));
499
544
  }
500
545
 
501
546
  // ------------------------------------------------------------------------
@@ -910,7 +955,14 @@ NB_MODULE(_openscad_cpp_evaluator, m) {
910
955
 
911
956
  m.def("evaluate", &evaluate, nb::arg("path"), nb::arg("viewport_params"), nb::arg("manifold_cache") = nullptr,
912
957
  nb::arg("profile") = false, nb::arg("generate") = true, nb::arg("strict_commas") = false,
913
- "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit).\n"
958
+ nb::arg("coverage") = false,
959
+ "Evaluate a .scad file; return (bodies, echoes, id_to_node, csg_tree, profile_result, dyn, dyn_explicit, "
960
+ "geometry, coverage_result).\n"
961
+ "coverage=True records which statements, branch arms and bodies ran: coverage_result is a dict "
962
+ "{spans, files, total}: spans is a list of dicts (origin, line, column, start, end, kind, arm, hits), "
963
+ "one per coverable node of every file the run touched; files is one summary per origin, sorted "
964
+ "(statements/statements_hit, branches/branches_hit, bodies/bodies_hit, spans/spans_hit and the four "
965
+ "percent fields); total is the same over every file. None when off.\n"
914
966
  "generate=False stops after the resolve pass: the script runs and reports everything "
915
967
  "it normally would, but no Manifold geometry is built, and neither bodies nor csg_tree "
916
968
  "are populated.\n\n"
@@ -728,6 +728,12 @@ enum class Op {
728
728
  // on failure, otherwise runs any chained node.children natively (rare;
729
729
  // not worth its own compiled path -- see AssertSite's own doc comment).
730
730
  AssertStatement,
731
+ // a = nativeStatements index of the AST node whose arm this is. Emitted
732
+ // only when the Evaluator was built with coverage on (compile-time
733
+ // flag, and chunks are per-Evaluator): Evaluator::coverHit(node), nothing
734
+ // else. Marks the arms the debugger never checkpoints in compiled code
735
+ // -- ternary arms, comprehension if/else arms, `&&`/`||` right operands.
736
+ Cover,
731
737
  };
732
738
 
733
739
  struct Instruction {
@@ -21,7 +21,7 @@ namespace oscadeval {
21
21
  // whole function, unconditionally correct since nothing about this
22
22
  // function's behavior changes based on whether it happened to compile.
23
23
  std::optional<CompiledChunk> tryCompileFunction(const oscad::FunctionDeclaration& decl,
24
- const oscad::ScopeTable* scopeTable);
24
+ const oscad::ScopeTable* scopeTable, bool coverage = false);
25
25
 
26
26
  // Compiles a bare STATEMENT-context expression -- an assignment's RHS, an
27
27
  // if/for condition, a module-call or echo()/assert() argument -- rather
@@ -50,7 +50,7 @@ std::optional<CompiledChunk> tryCompileFunction(const oscad::FunctionDeclaration
50
50
  // any callee inside it statically, the same way a function body's call
51
51
  // sites are.
52
52
  std::optional<CompiledChunk> tryCompileStatementExpr(const oscad::Expression& expr, const oscad::Scope* scope,
53
- const oscad::ScopeTable* scopeTable);
53
+ const oscad::ScopeTable* scopeTable, bool coverage = false);
54
54
 
55
55
  // Compiles a run of SIBLING assignment statements sharing one scope --
56
56
  // exactly Evaluator::evalChildren's own `assignments` sub-list (stmt_eval.
@@ -85,7 +85,7 @@ std::optional<CompiledChunk> tryCompileStatementExpr(const oscad::Expression& ex
85
85
  // instead of just for that one assignment's own RHS.
86
86
  std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const oscad::Assignment*>& assigns,
87
87
  const oscad::ScopeTable* scopeTable,
88
- const oscad::Scope* scope);
88
+ const oscad::Scope* scope, bool coverage = false);
89
89
 
90
90
  // Attempts to compile `decl`'s parameter defaults + STATEMENT-list body
91
91
  // (decl.children) to bytecode (Stage 2) -- the module-side analog of
@@ -101,7 +101,7 @@ std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const o
101
101
  // EXPRESSION (inside an if-condition/for-range/argument) can still bail
102
102
  // (via tryCompileStatementExpr's own NotCompilable, propagated up).
103
103
  std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration& decl,
104
- const oscad::ScopeTable* scopeTable);
104
+ const oscad::ScopeTable* scopeTable, bool coverage = false);
105
105
 
106
106
  // Same compilation (assignment/if/for/resolved-module-call get real
107
107
  // bytecode, everything else a native passthrough), but for an ARBITRARY
@@ -126,6 +126,6 @@ std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration
126
126
  // scope()`, mirroring tryRunCompiledAssignmentBlock's own convention.
127
127
  std::optional<CompiledChunk> tryCompileChildrenList(const std::vector<const oscad::ASTNode*>& children,
128
128
  const oscad::ScopeTable* scopeTable,
129
- const oscad::Scope* scope);
129
+ const oscad::Scope* scope, bool coverage = false);
130
130
 
131
131
  } // namespace oscadeval
@@ -0,0 +1,116 @@
1
+ #pragma once
2
+
3
+ // Coverage: which parts of a script (and of the libraries it pulls in) ran.
4
+ //
5
+ // The unit of coverage is the checkpoint set the debugger already uses --
6
+ // Evaluator::checkDebug fires for every statement, every function/module
7
+ // body entered, every if/else arm's first statement, every ternary arm and
8
+ // list-comprehension if/else arm -- plus two arms the debugger never needed
9
+ // to stop at, the right operands of `&&`/`||`. Recording a hit is an array
10
+ // increment indexed by the node's (treeId, slot), the dense per-parse
11
+ // numbering ScopeTable relies on, so the bytecode VM stays on and a hit
12
+ // costs no hashing and no string.
13
+ //
14
+ // What COULD have run -- the universe the report counts against -- comes
15
+ // from a walk over the same trees (collectCoverable) using one predicate
16
+ // for both sides, so "covered" and "uncovered" are one vocabulary. A hit
17
+ // on a node outside that vocabulary (a let-binding, a plain comprehension
18
+ // element) is simply not reported.
19
+
20
+ #include "openscad_cpp_parser/ast/ast_node.hpp"
21
+
22
+ #include <cstdint>
23
+ #include <string>
24
+ #include <vector>
25
+
26
+ namespace oscadeval {
27
+
28
+ enum class CoverageKind {
29
+ Statement, // a statement in a module body, an if/for/let body, or at top level
30
+ Branch, // a ternary arm, a comprehension if/else arm, a `&&`/`||` right operand
31
+ Body, // a function, module or function-literal body: was it ever entered?
32
+ };
33
+
34
+ const char* coverageKindName(CoverageKind kind);
35
+
36
+ struct CoverageSpan {
37
+ std::string origin;
38
+ int line = 0;
39
+ int column = 0;
40
+ int start_offset = 0;
41
+ int end_offset = 0;
42
+ CoverageKind kind = CoverageKind::Statement;
43
+ // A Statement that is the first of an if/else arm. Branch coverage counts
44
+ // these alongside the Branch kind; they stay Statements so a statement
45
+ // count is still a statement count.
46
+ bool arm = false;
47
+ std::uint32_t hits = 0;
48
+ };
49
+
50
+ // Per-file totals. "branches" counts Branch spans plus arm Statements (an
51
+ // if/else arm is a branch too); those arm statements are ALSO in
52
+ // "statements", since a statement count should be a statement count.
53
+ // "percent" is over every span in the file, whatever its kind.
54
+ struct CoverageFileSummary {
55
+ std::string origin;
56
+ std::uint32_t statements = 0, statements_hit = 0;
57
+ std::uint32_t branches = 0, branches_hit = 0;
58
+ std::uint32_t bodies = 0, bodies_hit = 0;
59
+ std::uint32_t spans = 0, spans_hit = 0;
60
+ double percent() const { return spans ? 100.0 * spans_hit / spans : 100.0; }
61
+ double statement_percent() const { return statements ? 100.0 * statements_hit / statements : 100.0; }
62
+ double branch_percent() const { return branches ? 100.0 * branches_hit / branches : 100.0; }
63
+ double body_percent() const { return bodies ? 100.0 * bodies_hit / bodies : 100.0; }
64
+ };
65
+
66
+ struct CoverageResult {
67
+ std::vector<CoverageSpan> spans; // one per coverable node, source order per tree
68
+ std::vector<CoverageFileSummary> files; // one per origin, sorted by origin
69
+ CoverageFileSummary total; // every file together (origin empty)
70
+ };
71
+
72
+ // The per-file and total summaries for `spans`; buildCoverageResult calls
73
+ // this, and a merged/aggregated span list (BelfrySCAD's --test --coverage)
74
+ // can call it again.
75
+ void summarizeCoverage(CoverageResult& result);
76
+
77
+ class CoverageRecorder {
78
+ public:
79
+ void clear() { hits_.clear(); }
80
+ void hit(const oscad::ASTNode& node) {
81
+ const std::uint32_t tree = node.treeId(), slot = node.slot();
82
+ if (tree >= hits_.size()) hits_.resize(tree + 1);
83
+ std::vector<std::uint32_t>& slots = hits_[tree];
84
+ if (slot >= slots.size()) slots.resize(slot + 1, 0);
85
+ ++slots[slot];
86
+ }
87
+ std::uint32_t hitsFor(const oscad::ASTNode& node) const {
88
+ const std::uint32_t tree = node.treeId(), slot = node.slot();
89
+ if (tree >= hits_.size()) return 0;
90
+ const std::vector<std::uint32_t>& slots = hits_[tree];
91
+ return slot < slots.size() ? slots[slot] : 0;
92
+ }
93
+
94
+ private:
95
+ std::vector<std::vector<std::uint32_t>> hits_;
96
+ };
97
+
98
+ struct Coverable {
99
+ const oscad::ASTNode* node = nullptr;
100
+ CoverageKind kind = CoverageKind::Statement;
101
+ bool arm = false;
102
+ };
103
+
104
+ // Every coverable node under `roots` (a top-level statement list: the main
105
+ // file with its includes spliced in and any use-injected declarations) plus
106
+ // `extraStatements` (a used file's own top-level assignments, which `use`
107
+ // never splices anywhere -- see Evaluator::setUsedFileGlobals). Source
108
+ // order within each tree; a node reachable twice is listed once.
109
+ std::vector<Coverable> collectCoverable(const std::vector<const oscad::ASTNode*>& roots,
110
+ const std::vector<const oscad::ASTNode*>& extraStatements);
111
+
112
+ CoverageResult buildCoverageResult(const std::vector<const oscad::ASTNode*>& roots,
113
+ const std::vector<const oscad::ASTNode*>& extraStatements,
114
+ const CoverageRecorder& recorder);
115
+
116
+ } // namespace oscadeval
@@ -8,6 +8,7 @@
8
8
  #include "openscad_cpp_evaluator/debug_hooks.hpp"
9
9
  #include "openscad_cpp_evaluator/eval_context.hpp"
10
10
  #include "openscad_cpp_evaluator/eval_use.hpp"
11
+ #include "openscad_cpp_evaluator/coverage.hpp"
11
12
  #include "openscad_cpp_evaluator/eval_error.hpp"
12
13
  #include "openscad_cpp_evaluator/font_provider.hpp"
13
14
  #include "openscad_cpp_evaluator/manifold_cache.hpp"
@@ -84,7 +85,7 @@ public:
84
85
  // work at all.
85
86
  explicit Evaluator(EchoFn echoFn = {}, std::shared_ptr<FontProvider> fontProvider = nullptr,
86
87
  std::shared_ptr<ManifoldCache> manifoldCache = nullptr, DebugHooks debugHooks = {},
87
- bool profiling = false);
88
+ bool profiling = false, bool coverage = false);
88
89
 
89
90
  // Text/font builtins' shared access to the resolved FontProvider
90
91
  // (lazily constructing the built-in default on first call if none was
@@ -659,6 +660,22 @@ public:
659
660
  // resolve+generate bracket evaluate() itself runs). Mirrors the
660
661
  // reference's Evaluator.profile_result.
661
662
  std::optional<ProfileResult> profileResult;
663
+ // Filled by evaluate() when constructed with coverage=true: one span per
664
+ // coverable node of every tree the run touched, with its hit count.
665
+ // See coverage.hpp for the vocabulary.
666
+ std::optional<CoverageResult> coverageResult;
667
+
668
+ bool coverageEnabled() const { return coverage_; }
669
+ // Called once per statement execution (evalChildren on the interpreter
670
+ // side, Op::Cover on the compiled side), once per body entry
671
+ // (enterUserCall), and once per branch arm taken (ternary, comprehension
672
+ // if/else, `&&`/`||` right operand). Deliberately NOT from checkDebug:
673
+ // the debugger's checkpoints double up on an if-arm's first statement
674
+ // and never land on the `if` itself. Free when coverage is off: one
675
+ // predictable branch.
676
+ void coverHit(const oscad::ASTNode& node) {
677
+ if (coverage_) coverageRecorder_.hit(node);
678
+ }
662
679
 
663
680
  // The resolved (and, after evaluate() runs, generated) CSG tree from the
664
681
  // most recent evaluate() call -- moved here from evaluateImpl's local
@@ -1984,6 +2001,8 @@ private:
1984
2001
  // -- Profiling (Phase 9) ----------------------------------------------
1985
2002
 
1986
2003
  bool profiling_ = false;
2004
+ bool coverage_ = false;
2005
+ CoverageRecorder coverageRecorder_;
1987
2006
  std::map<ProfileSiteKey, CallSiteProfile> profileSites_;
1988
2007
  std::set<ProfileSiteKey> profileActive_; // site keys with a call currently on callStack_
1989
2008
  std::vector<double> profileChildTime_; // parallel aux stack to callStack_
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.18.0"
7
+ version = "1.19.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -532,7 +532,7 @@ class Evaluator:
532
532
  """
533
533
 
534
534
  def __init__(self, echo_fn=None, debug_hook=None, error_break_fn=None, return_hook=None,
535
- manifold_cache=None, profile=False, fast_continue_signal=None):
535
+ manifold_cache=None, profile=False, fast_continue_signal=None, coverage=False):
536
536
  self._echo_fn = echo_fn
537
537
  self._debug_hook = debug_hook
538
538
  self._error_break_fn = error_break_fn
@@ -540,8 +540,17 @@ class Evaluator:
540
540
  self._manifold_cache = manifold_cache
541
541
  self._profile = profile
542
542
  self._fast_continue_signal = fast_continue_signal
543
+ self._coverage = coverage
543
544
  self.csg_tree = []
544
545
  self.profile_result = None
546
+ # coverage=True: after evaluate(), {"spans": [...], "files": [...],
547
+ # "total": {...}}. spans: one dict (origin, line, column, start, end,
548
+ # kind, arm, hits) per coverable node -- statement, branch arm, or
549
+ # function/module body -- of every file the run touched. files: one
550
+ # summary per origin, sorted, with counts and percentages per kind
551
+ # and overall; total: the same over every file. None when off. See
552
+ # coverage.hpp.
553
+ self.coverage_result = None
545
554
  self.dyn = {}
546
555
  self.dyn_explicit = set()
547
556
 
@@ -583,9 +592,9 @@ class Evaluator:
583
592
  self.geometry = None
584
593
  else:
585
594
  (body_dicts, echoes, id_spans, csg_tree, profile_result, dyn,
586
- dyn_explicit, geometry) = _ext.evaluate(
595
+ dyn_explicit, geometry, coverage_result) = _ext.evaluate(
587
596
  source_path, vp, self._manifold_cache, self._profile, generate,
588
- strict_commas)
597
+ strict_commas, self._coverage)
589
598
  # The evaluated bodies, still on the C++ side. Stashed like
590
599
  # csg_tree/profile_result rather than returned, so
591
600
  # evaluate()'s own 2-tuple result is unchanged -- callers
@@ -598,6 +607,7 @@ class Evaluator:
598
607
  self._echo_fn(line)
599
608
  self.csg_tree = csg_tree
600
609
  self.profile_result = profile_result
610
+ self.coverage_result = coverage_result
601
611
  self.dyn = dyn
602
612
  self.dyn_explicit = dyn_explicit
603
613
  except EvalError:
@@ -12,6 +12,7 @@ add_library(openscad_cpp_evaluator STATIC
12
12
  colored_body.cpp
13
13
  css_colors.cpp
14
14
  csg_resolve.cpp
15
+ coverage.cpp
15
16
  csg_generate.cpp
16
17
  measure_geometry.cpp
17
18
  manifold_cache.cpp
@@ -271,7 +271,7 @@ class Compiler;
271
271
  bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope, const oscad::ASTNode* selfDecl,
272
272
  const oscad::ScopeTable* scopeTable, std::vector<EnclosingLevel> enclosing,
273
273
  const std::vector<std::unique_ptr<oscad::ParameterDeclaration>>& params,
274
- const oscad::Expression& bodyExpr);
274
+ const oscad::Expression& bodyExpr, bool coverage = false);
275
275
 
276
276
  class Compiler {
277
277
  public:
@@ -291,9 +291,9 @@ public:
291
291
  // when compiling a FunctionLiteral nested inside another compile --
292
292
  // see the FunctionLiteral case in compileExpr).
293
293
  Compiler(CompiledChunk& chunk, const oscad::Scope* scope, const oscad::ASTNode* selfDecl,
294
- const oscad::ScopeTable* scopeTable, std::vector<EnclosingLevel> enclosing)
294
+ const oscad::ScopeTable* scopeTable, std::vector<EnclosingLevel> enclosing, bool coverage = false)
295
295
  : chunk_(chunk), scope_(scope), selfDecl_(selfDecl), scopeTable_(scopeTable),
296
- enclosing_(std::move(enclosing)) {}
296
+ enclosing_(std::move(enclosing)), coverage_(coverage) {}
297
297
 
298
298
  // A node's own lexical scope, or null. Replaces ASTNode::scope(),
299
299
  // which is gone: one parsed tree is shared between evaluations, so a
@@ -470,7 +470,7 @@ public:
470
470
  childEnclosing.push_back({selfDecl_, &scope});
471
471
  CompiledChunk literalChunk;
472
472
  if (!compileFunctionLike(literalChunk, scopeOf(n), &n, scopeTable_, std::move(childEnclosing), n.parameters,
473
- *n.body)) {
473
+ *n.body, coverage_)) {
474
474
  throw NotCompilable{};
475
475
  }
476
476
  // Effective capture set: `n`'s own direct free-variable
@@ -555,10 +555,12 @@ public:
555
555
  compileExpr(*n.condition, out, scope); // condition is never tail
556
556
  size_t jumpToFalse = out.size();
557
557
  out.push_back({Op::JumpIfFalse, 0, 0, nullptr});
558
+ cover(out, n.trueExpr.get());
558
559
  compileExpr(*n.trueExpr, out, scope, tail); // both branches inherit
559
560
  size_t jumpToEnd = out.size();
560
561
  out.push_back({Op::Jump, 0, 0, nullptr});
561
562
  out[jumpToFalse].a = static_cast<int>(out.size());
563
+ cover(out, n.falseExpr.get());
562
564
  compileExpr(*n.falseExpr, out, scope, tail);
563
565
  out[jumpToEnd].a = static_cast<int>(out.size());
564
566
  return;
@@ -568,6 +570,7 @@ public:
568
570
  compileExpr(*n.left, out, scope);
569
571
  size_t j1 = out.size();
570
572
  out.push_back({Op::JumpIfFalse, 0, 0, nullptr});
573
+ cover(out, n.right.get());
571
574
  compileExpr(*n.right, out, scope);
572
575
  size_t j2 = out.size();
573
576
  out.push_back({Op::JumpIfFalse, 0, 0, nullptr});
@@ -586,6 +589,7 @@ public:
586
589
  compileExpr(*n.left, out, scope);
587
590
  size_t j1 = out.size();
588
591
  out.push_back({Op::JumpIfTrue, 0, 0, nullptr});
592
+ cover(out, n.right.get());
589
593
  compileExpr(*n.right, out, scope);
590
594
  size_t j2 = out.size();
591
595
  out.push_back({Op::JumpIfTrue, 0, 0, nullptr});
@@ -1172,6 +1176,7 @@ public:
1172
1176
  compileExpr(*n.condition, out, scope);
1173
1177
  size_t j = out.size();
1174
1178
  out.push_back({Op::JumpIfFalse, 0, 0, nullptr});
1179
+ cover(out, n.trueExpr.get());
1175
1180
  compileListCompBody(*n.trueExpr, out, scope);
1176
1181
  out[j].a = static_cast<int>(out.size());
1177
1182
  return;
@@ -1181,10 +1186,12 @@ public:
1181
1186
  compileExpr(*n.condition, out, scope);
1182
1187
  size_t j = out.size();
1183
1188
  out.push_back({Op::JumpIfFalse, 0, 0, nullptr});
1189
+ cover(out, n.trueExpr.get());
1184
1190
  compileListCompBody(*n.trueExpr, out, scope);
1185
1191
  size_t jend = out.size();
1186
1192
  out.push_back({Op::Jump, 0, 0, nullptr});
1187
1193
  out[j].a = static_cast<int>(out.size());
1194
+ cover(out, n.falseExpr.get());
1188
1195
  compileListCompBody(*n.falseExpr, out, scope);
1189
1196
  out[jend].a = static_cast<int>(out.size());
1190
1197
  return;
@@ -1511,6 +1518,11 @@ public:
1511
1518
  void compileOneStatement(const oscad::ASTNode& stmt, std::vector<Instruction>& out) {
1512
1519
  using oscad::NodeKind;
1513
1520
  trackSpan(stmt);
1521
+ // One coverage hit per statement execution, whichever way it is
1522
+ // compiled below (inline or Op::NativeStatement -- the native path
1523
+ // calls evalStatement directly, not evalChildren, so it does not
1524
+ // count itself). Mirrors evalChildren's own coverHit.
1525
+ if (stmt.kind() != NodeKind::ModuleDeclaration && stmt.kind() != NodeKind::FunctionDeclaration) cover(out, &stmt);
1514
1526
  // A statement's own .scope() can differ from this whole chunk's
1515
1527
  // fixed scope_ member (e.g. a `use` statement earlier in the SAME
1516
1528
  // script/module body changes what later statements can see) --
@@ -1916,14 +1928,20 @@ private:
1916
1928
  std::vector<EnclosingLevel> enclosing_;
1917
1929
  int nextSlot_ = 0;
1918
1930
  int nextIterList_ = 0;
1931
+ bool coverage_ = false; // emit Op::Cover for every statement and every arm
1932
+
1933
+ public:
1934
+ void cover(std::vector<Instruction>& out, const oscad::ASTNode* arm) {
1935
+ if (coverage_) out.push_back({Op::Cover, internNativeStatement(arm), 0, nullptr});
1936
+ }
1919
1937
  };
1920
1938
 
1921
1939
  bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope, const oscad::ASTNode* selfDecl,
1922
1940
  const oscad::ScopeTable* scopeTable, std::vector<EnclosingLevel> enclosing,
1923
1941
  const std::vector<std::unique_ptr<oscad::ParameterDeclaration>>& params,
1924
- const oscad::Expression& bodyExpr) {
1942
+ const oscad::Expression& bodyExpr, bool coverage) {
1925
1943
  chunk.selfDecl = selfDecl;
1926
- Compiler compiler(chunk, staticScope, selfDecl, scopeTable, std::move(enclosing));
1944
+ Compiler compiler(chunk, staticScope, selfDecl, scopeTable, std::move(enclosing), coverage);
1927
1945
  CompileScope bodyScope;
1928
1946
  bodyScope.push();
1929
1947
  for (const auto& p : params) {
@@ -1960,19 +1978,19 @@ bool compileFunctionLike(CompiledChunk& chunk, const oscad::Scope* staticScope,
1960
1978
  } // namespace
1961
1979
 
1962
1980
  std::optional<CompiledChunk> tryCompileFunction(const oscad::FunctionDeclaration& decl,
1963
- const oscad::ScopeTable* scopeTable) {
1981
+ const oscad::ScopeTable* scopeTable, bool coverage) {
1964
1982
  CompiledChunk chunk;
1965
1983
  if (!compileFunctionLike(chunk, scopeTable ? scopeTable->get(decl) : nullptr, &decl, scopeTable, {},
1966
- decl.parameters, *decl.expr))
1984
+ decl.parameters, *decl.expr, coverage))
1967
1985
  return std::nullopt;
1968
1986
  return chunk;
1969
1987
  }
1970
1988
 
1971
1989
  std::optional<CompiledChunk> tryCompileStatementExpr(const oscad::Expression& expr, const oscad::Scope* scope,
1972
- const oscad::ScopeTable* scopeTable) {
1990
+ const oscad::ScopeTable* scopeTable, bool coverage) {
1973
1991
  static const std::vector<std::unique_ptr<oscad::ParameterDeclaration>> kNoParams;
1974
1992
  CompiledChunk chunk;
1975
- if (!compileFunctionLike(chunk, scope, nullptr, scopeTable, {}, kNoParams, expr)) return std::nullopt;
1993
+ if (!compileFunctionLike(chunk, scope, nullptr, scopeTable, {}, kNoParams, expr, coverage)) return std::nullopt;
1976
1994
  // See this function's own doc comment (bytecode_compiler.hpp) for why a
1977
1995
  // captures-having nested closure can't be supported by this bare
1978
1996
  // wrapper -- selfDecl is nullptr and enclosing is empty above, so its
@@ -1986,7 +2004,7 @@ std::optional<CompiledChunk> tryCompileStatementExpr(const oscad::Expression& ex
1986
2004
 
1987
2005
  std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const oscad::Assignment*>& assigns,
1988
2006
  const oscad::ScopeTable* scopeTable,
1989
- const oscad::Scope* scope) {
2007
+ const oscad::Scope* scope, bool coverage) {
1990
2008
  // Reassignment-warning fidelity (see this function's own doc comment,
1991
2009
  // bytecode_compiler.hpp) -- cheap, one-time scan before touching the
1992
2010
  // compiler at all.
@@ -2002,11 +2020,12 @@ std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const o
2002
2020
  }
2003
2021
 
2004
2022
  CompiledChunk chunk;
2005
- Compiler compiler(chunk, scope, nullptr, scopeTable, {});
2023
+ Compiler compiler(chunk, scope, nullptr, scopeTable, {}, coverage);
2006
2024
  CompileScope compileScope;
2007
2025
  compileScope.push();
2008
2026
  try {
2009
2027
  for (const oscad::Assignment* a : assigns) {
2028
+ compiler.cover(chunk.bodyCode, a); // one statement, one hit, same as evalChildren
2010
2029
  compiler.compileExpr(*a->expr, chunk.bodyCode, compileScope); // RHS is never tail
2011
2030
  const std::string& name = a->name->name;
2012
2031
  if (!name.empty() && name[0] == '$') {
@@ -2041,7 +2060,7 @@ std::optional<CompiledChunk> tryCompileAssignmentBlock(const std::vector<const o
2041
2060
  }
2042
2061
 
2043
2062
  std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration& decl,
2044
- const oscad::ScopeTable* scopeTable) {
2063
+ const oscad::ScopeTable* scopeTable, bool coverage) {
2045
2064
  CompiledChunk chunk;
2046
2065
  chunk.isModule = true;
2047
2066
  chunk.selfDecl = &decl;
@@ -2055,7 +2074,7 @@ std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration
2055
2074
  // compile path, just scoped to that one statement's own sub-
2056
2075
  // expression -- see this file's own module-chunk doc comment
2057
2076
  // (bytecode.hpp) for the full reasoning.
2058
- Compiler compiler(chunk, scopeTable ? scopeTable->get(decl) : nullptr, nullptr, scopeTable, {});
2077
+ Compiler compiler(chunk, scopeTable ? scopeTable->get(decl) : nullptr, nullptr, scopeTable, {}, coverage);
2059
2078
  try {
2060
2079
  compiler.compileStatementList(decl.children, chunk.bodyCode);
2061
2080
  } catch (const NotCompilable&) {
@@ -2068,7 +2087,7 @@ std::optional<CompiledChunk> tryCompileModuleBody(const oscad::ModuleDeclaration
2068
2087
 
2069
2088
  std::optional<CompiledChunk> tryCompileChildrenList(const std::vector<const oscad::ASTNode*>& children,
2070
2089
  const oscad::ScopeTable* scopeTable,
2071
- const oscad::Scope* scope) {
2090
+ const oscad::Scope* scope, bool coverage) {
2072
2091
  CompiledChunk chunk;
2073
2092
  // Same completion semantics as a module chunk (no return value, its
2074
2093
  // whole effect is the side effect of what lands in treeStack_) --
@@ -2079,7 +2098,7 @@ std::optional<CompiledChunk> tryCompileChildrenList(const std::vector<const osca
2079
2098
  // ever resolved against it either way; module bodies don't create
2080
2099
  // escaping closures).
2081
2100
  chunk.isModule = true;
2082
- Compiler compiler(chunk, scope, nullptr, scopeTable, {});
2101
+ Compiler compiler(chunk, scope, nullptr, scopeTable, {}, coverage);
2083
2102
  try {
2084
2103
  compiler.compileStatementList(children, chunk.bodyCode);
2085
2104
  } catch (const NotCompilable&) {