openscad-cpp-evaluator 1.4.1__tar.gz → 1.6.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 (214) hide show
  1. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +24 -1
  3. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/call_args.hpp +10 -0
  4. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/evaluator.hpp +20 -1
  5. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/scope_trail.hpp +21 -11
  6. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/utf8.hpp +6 -0
  7. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/pyproject.toml +1 -1
  8. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/call_args.cpp +12 -0
  9. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/function_builtins.cpp +15 -35
  10. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/bytecode_vm.cpp +46 -43
  11. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/user_calls.cpp +11 -5
  12. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/utf8.cpp +20 -0
  13. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/value.cpp +84 -7
  14. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_bytecode_compiler.cpp +35 -0
  15. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_scope_trail.cpp +24 -0
  16. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_value.cpp +106 -0
  17. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/.github/workflows/ci.yml +0 -0
  18. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/.github/workflows/wheels.yml +0 -0
  19. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/.gitignore +0 -0
  20. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/.gitmodules +0 -0
  21. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/CLAUDE.md +0 -0
  22. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/LICENSE +0 -0
  24. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/README.md +0 -0
  25. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/bindings/CMakeLists.txt +0 -0
  26. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/bindings/ast_to_py.cpp +0 -0
  27. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/bindings/module.cpp +0 -0
  28. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/cmake/embed_font.cmake +0 -0
  29. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/examples/CMakeLists.txt +0 -0
  30. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/examples/eval_perf_benchmark.cpp +0 -0
  31. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/examples/manifold_cache_reuse.cpp +0 -0
  32. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/examples/minimal_debugger.cpp +0 -0
  33. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  34. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/.gitignore +0 -0
  35. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  36. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  37. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/README.md +0 -0
  38. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  39. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  40. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  41. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  42. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  43. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  44. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  45. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  46. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  47. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  48. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  49. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  50. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  51. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  52. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  53. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  54. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  55. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  56. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  57. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  58. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  59. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  60. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  61. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  62. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  63. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  64. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  65. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  66. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  67. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  68. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  69. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  70. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  71. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  72. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  73. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  74. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  75. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  76. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  77. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  78. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  79. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  80. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  81. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  82. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  83. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  84. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  85. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  86. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  87. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  88. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  89. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  90. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  91. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  92. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  93. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  94. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  95. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  96. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  97. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  98. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  99. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  100. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  101. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  102. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  103. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  104. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  105. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  106. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  107. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  108. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  109. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  110. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  111. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  112. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  113. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  114. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  115. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  116. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  117. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  118. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  119. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  120. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  121. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  122. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  123. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  124. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  125. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  126. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  127. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  128. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  129. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  130. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  131. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  132. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/resources/fonts/LICENSE +0 -0
  133. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  134. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/CMakeLists.txt +0 -0
  135. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/booleans.cpp +0 -0
  136. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/builtins.hpp +0 -0
  137. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/color.cpp +0 -0
  138. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/control.cpp +0 -0
  139. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/dxf_dim.cpp +0 -0
  140. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/extrude.cpp +0 -0
  141. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/import.cpp +0 -0
  142. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/modifiers.cpp +0 -0
  143. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/primitives_2d.cpp +0 -0
  144. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/primitives_3d.cpp +0 -0
  145. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/registry.cpp +0 -0
  146. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/roof.cpp +0 -0
  147. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/segments.cpp +0 -0
  148. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/surface.cpp +0 -0
  149. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/text.cpp +0 -0
  150. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/topology.cpp +0 -0
  151. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/builtins/transforms.cpp +0 -0
  152. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/bytecode_compiler.cpp +0 -0
  153. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/colored_body.cpp +0 -0
  154. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/csg_generate.cpp +0 -0
  155. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/csg_resolve.cpp +0 -0
  156. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/css_colors.cpp +0 -0
  157. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/debug/debug_repl.cpp +0 -0
  158. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/debug_profile.cpp +0 -0
  159. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/eval_context.cpp +0 -0
  160. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/eval_error.cpp +0 -0
  161. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/eval_use.cpp +0 -0
  162. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/export.cpp +0 -0
  163. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/expr_eval.cpp +0 -0
  164. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/format_closure.cpp +0 -0
  165. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/import/dxf_import.cpp +0 -0
  166. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/import/mesh_import.cpp +0 -0
  167. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/import/surface_load.cpp +0 -0
  168. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/import/svg_import.cpp +0 -0
  169. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/manifold_cache.cpp +0 -0
  170. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/measure_geometry.cpp +0 -0
  171. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/mesh_check.cpp +0 -0
  172. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/stmt_eval.cpp +0 -0
  173. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/text/font_match.cpp +0 -0
  174. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/text/freetype_font_provider.cpp +0 -0
  175. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/text/text_metrics.cpp +0 -0
  176. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/src/zip_stored.cpp +0 -0
  177. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/CMakeLists.txt +0 -0
  178. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_booleans.cpp +0 -0
  179. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_children_separate.cpp +0 -0
  180. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_cli.cpp +0 -0
  181. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_control_flow.cpp +0 -0
  182. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_csg_tree.cpp +0 -0
  183. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_debug_hooks.cpp +0 -0
  184. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_dxf_svg_import.cpp +0 -0
  185. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_eval_context.cpp +0 -0
  186. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_export.cpp +0 -0
  187. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_expr_eval.cpp +0 -0
  188. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_extrude_roof.cpp +0 -0
  189. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_feature_detection.cpp +0 -0
  190. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_function_builtins.cpp +0 -0
  191. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_helpers.hpp +0 -0
  192. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_import_export.cpp +0 -0
  193. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_include_cache.cpp +0 -0
  194. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_manifold_cache.cpp +0 -0
  195. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_mesh_check.cpp +0 -0
  196. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_multi_color_merge.cpp +0 -0
  197. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_primitives.cpp +0 -0
  198. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_profiling.cpp +0 -0
  199. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_python_bindings.py +0 -0
  200. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_render_expr.cpp +0 -0
  201. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_scoping.cpp +0 -0
  202. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_surface.cpp +0 -0
  203. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_tail_calls.cpp +0 -0
  204. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_text.cpp +0 -0
  205. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_transforms.cpp +0 -0
  206. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_unexpected_args.cpp +0 -0
  207. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_use.cpp +0 -0
  208. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_viewport_params.cpp +0 -0
  209. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tests/test_zip_stored.cpp +0 -0
  210. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tools/cli/CMakeLists.txt +0 -0
  211. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tools/cli/cli_lib.cpp +0 -0
  212. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tools/cli/cli_lib.hpp +0 -0
  213. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/tools/cli/main.cpp +0 -0
  214. {openscad_cpp_evaluator-1.4.1 → openscad_cpp_evaluator-1.6.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 1.4.1
3
+ Version: 1.6.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -152,7 +152,20 @@ struct IterList {
152
152
  // (pre-redesign) behavior exactly.
153
153
  struct VmFrame {
154
154
  const CompiledChunk* chunk = nullptr;
155
- const std::vector<Instruction>* code = nullptr;
155
+ // This frame's instruction stream, pre-decomposed into data()/size().
156
+ // driveVm's dispatch loop reads both once per INSTRUCTION; when this was a
157
+ // `const std::vector<Instruction>*`, that cost a pointer chase plus an
158
+ // end-minus-begin divided by sizeof(Instruction) (a multiply by a magic
159
+ // constant) on every opcode. ~11% of a whole Anklet.scad render sat in that
160
+ // loop preamble. A compiled chunk's code vector is immutable for as long as
161
+ // any frame runs it, so caching these is free -- but they must only ever be
162
+ // set together, hence setCode() rather than two public fields to forget.
163
+ const Instruction* code = nullptr;
164
+ size_t codeSize = 0;
165
+ void setCode(const std::vector<Instruction>& c) {
166
+ code = c.data();
167
+ codeSize = c.size();
168
+ }
156
169
  size_t pc = 0;
157
170
  std::vector<Value> slots;
158
171
  std::vector<Value> stack;
@@ -189,6 +202,16 @@ struct VmFrame {
189
202
  // brackets first", normally via matched Push/Pop, or via
190
203
  // teardownVmCallStackDownTo on the exception path) already covers it.
191
204
  std::vector<PendingCsgWrap> csgWrapStack;
205
+ // Scratch CallArgs for this frame's builtin/import call sites, reused
206
+ // across calls so its two vectors keep their capacity: building a fresh
207
+ // one per call was 1.99M of one Anklet.scad render's 10M allocations,
208
+ // second only to the argument vector this frame's operand stack now
209
+ // stands in for. One per FRAME rather than one per Evaluator because a
210
+ // builtin can re-enter the VM -- but only by pushing a new frame, which
211
+ // brings its own scratch, and a frame never has two call opcodes in
212
+ // flight at once. Frames themselves are pooled (acquireVmFrame), so the
213
+ // capacity survives the call that grew it.
214
+ CallArgs argScratch;
192
215
  // The ORIGINAL callee name at push time, used by
193
216
  // Evaluator::exitUserCallSuccess's own returnHook call when this frame
194
217
  // carries a bracket -- deliberately NOT updated by a later tail hop
@@ -97,4 +97,14 @@ const oscad::Expression* argExpr(const oscad::Argument& arg);
97
97
  // positional argument, not a fixed named slot.
98
98
  std::vector<Value> allPositional(const CallArgs& args);
99
99
 
100
+ // allPositional()'s size and element access, without building the vector.
101
+ // The argument CHECKERS (function_builtins.cpp) only ever ask how many
102
+ // positional slots there are and look at a couple of them, so they used to
103
+ // pay a heap allocation plus a Value copy per slot on every builtin call
104
+ // that has a check entry -- 1.97M of the 16.6M allocations in one
105
+ // Anklet.scad render. positionalAt() answers undef for a gap, exactly as
106
+ // allPositional() pads one.
107
+ size_t positionalCount(const CallArgs& args);
108
+ const Value& positionalAt(const CallArgs& args, size_t pos);
109
+
100
110
  } // namespace oscadeval
@@ -1812,7 +1812,26 @@ private:
1812
1812
  // its (possibly many) active occurrences is checked, checking each
1813
1813
  // DISTINCT X once is exactly equivalent to checking every occurrence
1814
1814
  // -- not an approximation.
1815
- std::unordered_map<const oscad::ASTNode*, int> activeDeclRefcount_;
1815
+ // A flat vector, not a map, for the reason the paragraph above already
1816
+ // establishes: the number of DISTINCT active declarations stays small and
1817
+ // roughly constant, so a linear scan beats hashing -- and an
1818
+ // unordered_map cost one node allocation per user call (595K in one
1819
+ // Anklet.scad render) where this costs none after warmup, while the
1820
+ // every-call containment scan above walks contiguous memory instead of a
1821
+ // hash table's buckets. Same flat-vector-over-small-map trade CallArgs and
1822
+ // BoundArgs already make. Entry order is not meaningful (the scan visits
1823
+ // all of them), so removal swaps with the back.
1824
+ std::vector<std::pair<const oscad::ASTNode*, int>> activeDeclRefcount_;
1825
+ // ++count for `decl`, appending an entry if it has none yet.
1826
+ void noteActiveDeclEnter(const oscad::ASTNode* declNode) {
1827
+ for (auto& entry : activeDeclRefcount_) {
1828
+ if (entry.first == declNode) {
1829
+ ++entry.second;
1830
+ return;
1831
+ }
1832
+ }
1833
+ activeDeclRefcount_.emplace_back(declNode, 1);
1834
+ }
1816
1835
 
1817
1836
  // Shared cap on callStack_'s own size, checked by both
1818
1837
  // evalUserFunctionCore (below) and evalUserModule (user_calls.cpp) --
@@ -50,7 +50,7 @@ public:
50
50
  // ancestry chain here (an isolating derivation -- see openChild).
51
51
  int openLevel(int parentLevel) {
52
52
  int level = ++nextLevel_;
53
- parent_[level] = parentLevel;
53
+ parent_.push_back(parentLevel); // index == level; see parent_'s own comment
54
54
  return level;
55
55
  }
56
56
 
@@ -122,7 +122,7 @@ public:
122
122
  }
123
123
  dirty_.erase(it);
124
124
  }
125
- parent_.erase(level);
125
+ parent_[static_cast<size_t>(level)] = 0; // dead: an ancestry walk stops here
126
126
  }
127
127
 
128
128
  // nullptr if `name` has no binding on `myLevel`'s own ancestry chain.
@@ -154,8 +154,7 @@ public:
154
154
  } else if (entryLevel == ancestor) {
155
155
  return &vec[static_cast<size_t>(idx)].value;
156
156
  } else {
157
- auto pit = parent_.find(ancestor);
158
- ancestor = pit != parent_.end() ? pit->second : 0;
157
+ ancestor = parent_[static_cast<size_t>(ancestor)];
159
158
  }
160
159
  }
161
160
  return nullptr;
@@ -194,7 +193,19 @@ private:
194
193
  };
195
194
  std::unordered_map<std::string, std::vector<Entry>> stacks_;
196
195
  std::unordered_map<int, std::vector<std::string>> dirty_;
197
- std::unordered_map<int, int> parent_; // level -> parent level (0 = chain terminates here)
196
+ // level -> parent level (0 = chain terminates here, which is also what a
197
+ // popped level reads back as). Levels come from `++nextLevel_`, so they are
198
+ // dense and monotonic and this is a plain vector indexed BY level, with
199
+ // element 0 the unused stand-in for "no parent" -- push_back in openLevel()
200
+ // keeps index == level. It used to be an unordered_map<int,int>, which cost
201
+ // one hash-node allocation per scope derivation (3 of the 10 allocations a
202
+ // user function call makes, ~2M of them in one Anklet.scad render) and made
203
+ // lookup()'s ancestry walk -- run on every variable read -- a hash probe per
204
+ // hop instead of an array index. It never shrinks: 4 bytes per level ever
205
+ // opened (~2.6MB for Anklet.scad), traded knowingly for the allocation.
206
+ // Levels are NEVER renumbered or reused -- lookup()'s sorted-merge walk
207
+ // depends on level numbers being globally monotonic.
208
+ std::vector<int> parent_{0};
198
209
  int nextLevel_ = 0;
199
210
  };
200
211
 
@@ -249,7 +260,7 @@ public:
249
260
  // call returns; ~TrailView() pops ITS bindings from ScopeTrailStorage
250
261
  // (erasing the very data the still-alive child's ancestry walk needs)
251
262
  // even though the walk can still numerically reach that ancestor level
252
- // via ScopeTrailStorage::parent_ (that map entry is untouched -- only
263
+ // via ScopeTrailStorage::parent_ (that entry is untouched -- only
253
264
  // the DATA is gone). Caught by a closure created inside a let()
254
265
  // nested one level inside its own enclosing call reading undef for a
255
266
  // variable bound at the call's own (now-popped) level, not the let()'s
@@ -344,7 +355,7 @@ public:
344
355
 
345
356
  int openLevel(int parentLevel) {
346
357
  int level = ++nextLevel_;
347
- parent_[level] = parentLevel;
358
+ parent_.push_back(parentLevel); // index == level; see parent_'s own comment
348
359
  return level;
349
360
  }
350
361
 
@@ -395,7 +406,7 @@ public:
395
406
  }
396
407
  dirty_.erase(it);
397
408
  }
398
- parent_.erase(level);
409
+ parent_[static_cast<size_t>(level)] = 0; // dead: an ancestry walk stops here
399
410
  }
400
411
 
401
412
  // Same sorted-merge ancestry walk as ScopeTrailStorage::lookup -- see
@@ -414,8 +425,7 @@ public:
414
425
  } else if (entryLevel == ancestor) {
415
426
  return &vec[static_cast<size_t>(idx)].value;
416
427
  } else {
417
- auto pit = parent_.find(ancestor);
418
- ancestor = pit != parent_.end() ? pit->second : 0;
428
+ ancestor = parent_[static_cast<size_t>(ancestor)];
419
429
  }
420
430
  }
421
431
  return nullptr;
@@ -450,7 +460,7 @@ private:
450
460
  std::shared_ptr<DynNameIntern> intern_;
451
461
  std::vector<std::vector<Entry>> stacks_;
452
462
  std::unordered_map<int, std::vector<int>> dirty_;
453
- std::unordered_map<int, int> parent_;
463
+ std::vector<int> parent_{0}; // see ScopeTrailStorage::parent_
454
464
  int nextLevel_ = 0;
455
465
  };
456
466
 
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <cstdint>
3
4
  #include <string>
4
5
  #include <vector>
5
6
 
@@ -17,6 +18,11 @@ namespace oscadeval {
17
18
  // string that actually holds non-ASCII pays, and those are short in
18
19
  // practice.
19
20
 
21
+ // One code point as UTF-8. The caller decides which code points are
22
+ // legal -- chr() drops an invalid one, a \uXXXX escape substitutes a space
23
+ // -- so this encodes whatever it is given.
24
+ std::string utf8Encode(uint32_t cp);
25
+
20
26
  // True when every byte is < 0x80, so byte offsets and character offsets are
21
27
  // the same thing.
22
28
  bool utf8IsAscii(const std::string& s);
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "1.4.1"
7
+ version = "1.6.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -106,6 +106,18 @@ const oscad::Expression* argExpr(const oscad::Argument& arg) {
106
106
  return static_cast<const oscad::PositionalArgument&>(arg).expr.get();
107
107
  }
108
108
 
109
+ size_t positionalCount(const CallArgs& args) {
110
+ int maxPos = -1;
111
+ for (const auto& [idx, v] : args.positional) maxPos = std::max(maxPos, idx);
112
+ return static_cast<size_t>(maxPos + 1);
113
+ }
114
+
115
+ const Value& positionalAt(const CallArgs& args, size_t pos) {
116
+ static const Value kUndef;
117
+ const Value* v = args.findPositional(static_cast<int>(pos));
118
+ return v ? *v : kUndef;
119
+ }
120
+
109
121
  std::vector<Value> allPositional(const CallArgs& args) {
110
122
  int maxPos = -1;
111
123
  for (const auto& [idx, v] : args.positional) maxPos = std::max(maxPos, idx);
@@ -193,29 +193,8 @@ double atan2Degrees(double y, double x) {
193
193
  return std::fabs(degs - whole) < 3.0E-14 ? whole : degs;
194
194
  }
195
195
 
196
- // UTF-8 encode/decode for chr()/ord() -- OpenSCAD strings are unicode text,
197
- // not byte arrays, so a codepoint above U+007F needs real multi-byte
198
- // handling, not a raw byte cast.
199
- std::string utf8Encode(uint32_t cp) {
200
- std::string out;
201
- if (cp <= 0x7F) {
202
- out.push_back(static_cast<char>(cp));
203
- } else if (cp <= 0x7FF) {
204
- out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
205
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
206
- } else if (cp <= 0xFFFF) {
207
- out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
208
- out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
209
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
210
- } else {
211
- out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
212
- out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
213
- out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
214
- out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
215
- }
216
- return out;
217
- }
218
-
196
+ // utf8Encode lives in utf8.cpp -- the string-literal escape decoder
197
+ // (\uXXXX) needs it too.
219
198
  uint32_t utf8DecodeFirst(const std::string& s) {
220
199
  if (s.empty()) return 0;
221
200
  const auto b = [&](size_t i) { return static_cast<unsigned char>(s[i]); };
@@ -1200,15 +1179,15 @@ void warnConversion(Evaluator& ev, const std::string& name, const std::string& w
1200
1179
  // numbers, with a distinct "at least 1 vector element" arity text for the
1201
1180
  // empty-vector case.
1202
1181
  bool checkMinMax(Evaluator& ev, const std::string& name, const CallArgs& args, const oscad::Position* pos) {
1203
- const std::vector<Value> positional = allPositional(args);
1204
- const size_t count = positional.size() + args.named.size();
1182
+ const size_t nPos = positionalCount(args);
1183
+ const size_t count = nPos + args.named.size();
1205
1184
  if (count < 1) {
1206
1185
  warnArity(ev, name, "at least 1", count, pos);
1207
1186
  return false;
1208
1187
  }
1209
- if (positional.size() == 1 && std::holds_alternative<ListPtr>(positional[0])) {
1188
+ if (nPos == 1 && std::holds_alternative<ListPtr>(positionalAt(args, 0))) {
1210
1189
  static const std::vector<Value> kEmptyItems;
1211
- const ListPtr& l = std::get<ListPtr>(positional[0]);
1190
+ const ListPtr& l = std::get<ListPtr>(positionalAt(args, 0));
1212
1191
  const std::vector<Value>& items = l ? l->items : kEmptyItems;
1213
1192
  if (items.empty()) {
1214
1193
  warnArity(ev, name, "at least 1 vector element", 0, pos);
@@ -1222,9 +1201,9 @@ bool checkMinMax(Evaluator& ev, const std::string& name, const CallArgs& args, c
1222
1201
  }
1223
1202
  return true;
1224
1203
  }
1225
- for (size_t i = 0; i < positional.size(); ++i) {
1226
- if (!std::holds_alternative<double>(positional[i])) {
1227
- warnConversion(ev, name, "argument " + std::to_string(i), "number", positional[i], pos);
1204
+ for (size_t i = 0; i < nPos; ++i) {
1205
+ if (!std::holds_alternative<double>(positionalAt(args, i))) {
1206
+ warnConversion(ev, name, "argument " + std::to_string(i), "number", positionalAt(args, i), pos);
1228
1207
  return false;
1229
1208
  }
1230
1209
  }
@@ -1240,8 +1219,8 @@ bool checkBuiltinArgs(Evaluator& ev, const std::string& name, BuiltinFnId id, co
1240
1219
  if (it == checks.end()) return true;
1241
1220
  const BuiltinCheck& c = it->second;
1242
1221
 
1243
- const std::vector<Value> positional = allPositional(args);
1244
- const size_t count = positional.size() + args.named.size();
1222
+ const size_t nPos = positionalCount(args);
1223
+ const size_t count = nPos + args.named.size();
1245
1224
  if (c.arityText && (static_cast<int>(count) < c.minArgs || (c.maxArgs >= 0 && static_cast<int>(count) > c.maxArgs))) {
1246
1225
  warnArity(ev, name, c.arityText, count, pos);
1247
1226
  return false;
@@ -1250,9 +1229,10 @@ bool checkBuiltinArgs(Evaluator& ev, const std::string& name, BuiltinFnId id, co
1250
1229
  // flat argument list can't be reconstructed from our split
1251
1230
  // positional/named form, and warning on the wrong index would be worse
1252
1231
  // than staying quiet on a spelling nobody uses for these builtins.
1253
- for (size_t i = 0; i < c.types.size() && i < positional.size(); ++i) {
1254
- if (!(typeBit(positional[i]) & c.types[i].allowed)) {
1255
- warnConversion(ev, name, "argument " + std::to_string(i), c.types[i].expected, positional[i], pos);
1232
+ for (size_t i = 0; i < c.types.size() && i < nPos; ++i) {
1233
+ const Value& v = positionalAt(args, i);
1234
+ if (!(typeBit(v) & c.types[i].allowed)) {
1235
+ warnConversion(ev, name, "argument " + std::to_string(i), c.types[i].expected, v, pos);
1256
1236
  return false;
1257
1237
  }
1258
1238
  }
@@ -20,10 +20,18 @@ namespace {
20
20
 
21
21
  // Shared by Op::CallFn's isBuiltin (evalBuiltinFunction) and isImport
22
22
  // (importAsValue) branches -- both take the same pre-resolved CallArgs
23
- // shape resolveArgs would build for the interpreter path. `args` is
24
- // consumed (each element moved out).
25
- CallArgs buildCallArgs(const CompiledChunk::CallSite& site, std::vector<Value>& args, size_t argCount) {
26
- CallArgs callArgs;
23
+ // shape resolveArgs would build for the interpreter path. `args` points
24
+ // AT THE CALLER FRAME'S OPERAND STACK, not at a copy: the arguments are
25
+ // already sitting there contiguously and in order, so every Op::Call*
26
+ // hands over `f.stack.data() + argBase` and truncates the stack once the
27
+ // arguments have been moved out. Building a `std::vector<Value>` for them
28
+ // instead was the single largest allocation site in the whole evaluator --
29
+ // 2.57M of one Anklet.scad render's 12.7M allocations, one per function
30
+ // call, builtins included. `args` is consumed (each element moved out), and
31
+ // nothing may push to that stack until it has been.
32
+ CallArgs& buildCallArgs(const CompiledChunk::CallSite& site, Value* args, size_t argCount, CallArgs& callArgs) {
33
+ callArgs.positional.clear();
34
+ callArgs.named.clear();
27
35
  int positionalIdx = 0;
28
36
  for (size_t i = 0; i < argCount; ++i) {
29
37
  if (site.argNames[i]) {
@@ -41,7 +49,7 @@ CallArgs buildCallArgs(const CompiledChunk::CallSite& site, std::vector<Value>&
41
49
  // LIST is `paramNames` (in declared order -- a FunctionDeclaration's
42
50
  // parameters or a FunctionLiteral's). Shared by CallFn/CallFnTail (a
43
51
  // site.decl callee) and CallDynamic/CallDynamicTail (a closure callee).
44
- BoundArgs buildBoundArgs(Evaluator& ev, const CompiledChunk::CallSite& site, std::vector<Value>& args, size_t argCount,
52
+ BoundArgs buildBoundArgs(Evaluator& ev, const CompiledChunk::CallSite& site, Value* args, size_t argCount,
45
53
  const std::vector<std::unique_ptr<oscad::ParameterDeclaration>>& paramNames) {
46
54
  BoundArgs bound;
47
55
  bound.reserve(argCount);
@@ -176,7 +184,7 @@ void applyCompiledDefaultsToFrame(Evaluator& ev, const CompiledChunk& chunk, VmF
176
184
  if (!chunk.defaultCode[i].empty()) {
177
185
  auto defaultFrame = ev.acquireVmFrame();
178
186
  defaultFrame->chunk = &chunk;
179
- defaultFrame->code = &chunk.defaultCode[i];
187
+ defaultFrame->setCode(chunk.defaultCode[i]);
180
188
  defaultFrame->pc = 0;
181
189
  defaultFrame->slots = frame.slots; // defaults may read earlier SIBLING slots? no -- compiled with an
182
190
  // isolated (zero-frame) scope, see bytecode_compiler.cpp's
@@ -213,7 +221,7 @@ void pushBareFrame(Evaluator& ev, const CompiledChunk& chunk, const std::vector<
213
221
  EvalContext ctx) {
214
222
  auto frame = ev.acquireVmFrame();
215
223
  frame->chunk = &chunk;
216
- frame->code = &code;
224
+ frame->setCode(code);
217
225
  frame->pc = 0;
218
226
  frame->slots.assign(static_cast<size_t>(chunk.numSlots), Value{});
219
227
  frame->stack.clear();
@@ -254,7 +262,7 @@ void pushBracketedCallFrame(Evaluator& ev, const CompiledChunk& chunk, const osc
254
262
 
255
263
  auto frame = ev.acquireVmFrame();
256
264
  frame->chunk = &chunk;
257
- frame->code = &chunk.bodyCode;
265
+ frame->setCode(chunk.bodyCode);
258
266
  frame->pc = 0;
259
267
  frame->slots.assign(static_cast<size_t>(chunk.numSlots), Value{});
260
268
  frame->stack.clear();
@@ -314,7 +322,7 @@ void pushBracketedModuleFrame(Evaluator& ev, const CompiledChunk& chunk, const o
314
322
  }
315
323
  auto frame = ev.acquireVmFrame();
316
324
  frame->chunk = &chunk;
317
- frame->code = &chunk.bodyCode;
325
+ frame->setCode(chunk.bodyCode);
318
326
  frame->pc = 0;
319
327
  // See runCompiledModuleBody's own doc comment (below) for why this is
320
328
  // .assign(numSlots) now, not .clear() -- a nested let-expression
@@ -369,7 +377,7 @@ void pushChildrenForwardFrame(Evaluator& ev, const CompiledChunk& chunk, EvalCon
369
377
  }
370
378
  auto frame = ev.acquireVmFrame();
371
379
  frame->chunk = &chunk;
372
- frame->code = &chunk.bodyCode;
380
+ frame->setCode(chunk.bodyCode);
373
381
  frame->pc = 0;
374
382
  frame->slots.assign(static_cast<size_t>(chunk.numSlots), Value{});
375
383
  frame->stack.clear();
@@ -491,7 +499,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
491
499
  }
492
500
  while (ev.vmCallStack_.size() > floor) {
493
501
  VmFrame& f = *ev.vmCallStack_.back();
494
- if (f.pc >= f.code->size()) {
502
+ if (f.pc >= f.codeSize) {
495
503
  // A module chunk produces nothing on the stack at all --
496
504
  // its whole effect already landed in treeStack_ as a side
497
505
  // effect of running its own body (Op::CallModule/
@@ -528,7 +536,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
528
536
  continue;
529
537
  }
530
538
 
531
- const Instruction& ins = (*f.code)[f.pc];
539
+ const Instruction& ins = f.code[f.pc];
532
540
  EvalContext& ctx = f.ctxChain.back();
533
541
  switch (ins.op) {
534
542
  case Op::PushConst:
@@ -787,27 +795,28 @@ Value driveVm(Evaluator& ev, size_t floor) {
787
795
  case Op::CallFn: {
788
796
  const CompiledChunk::CallSite& site = f.chunk->callSites[static_cast<size_t>(ins.a)];
789
797
  const size_t argCount = static_cast<size_t>(ins.b);
790
- std::vector<Value> args(argCount);
791
- for (size_t i = 0; i < argCount; ++i) {
792
- args[argCount - 1 - i] = std::move(f.stack.back());
793
- f.stack.pop_back();
794
- }
798
+ const size_t argBase = f.stack.size() - argCount;
799
+ Value* args = f.stack.data() + argBase; // into f.stack; truncated below once consumed
795
800
  if (site.isImport) {
796
- CallArgs callArgs = buildCallArgs(site, args, argCount);
801
+ CallArgs& callArgs = buildCallArgs(site, args, argCount, f.argScratch);
802
+ f.stack.resize(argBase);
797
803
  f.stack.push_back(importAsValue(ev, callArgs, *site.callNode));
798
804
  ++f.pc;
799
805
  } else if (site.isBuiltin && site.calleeName == "object") {
800
806
  std::vector<std::pair<std::optional<std::string>, Value>> pairs;
801
807
  pairs.reserve(argCount);
802
808
  for (size_t i = 0; i < argCount; ++i) pairs.emplace_back(site.argNames[i], std::move(args[i]));
809
+ f.stack.resize(argBase);
803
810
  f.stack.push_back(mergeObjectArgs(ev, pairs, &site.callNode->position()));
804
811
  ++f.pc;
805
812
  } else if (site.isBuiltin) {
806
- CallArgs callArgs = buildCallArgs(site, args, argCount);
813
+ CallArgs& callArgs = buildCallArgs(site, args, argCount, f.argScratch);
814
+ f.stack.resize(argBase);
807
815
  f.stack.push_back(evalBuiltinFunction(ev, site.calleeName, callArgs, *site.callNode));
808
816
  ++f.pc;
809
817
  } else {
810
818
  BoundArgs bound = buildBoundArgs(ev, site, args, argCount, site.decl->parameters);
819
+ f.stack.resize(argBase);
811
820
  const CompiledChunk* calleeChunk = ev.useBytecodeVm() ? ev.lookupOrCompileChunk(*site.decl) : nullptr;
812
821
  if (calleeChunk) {
813
822
  const oscad::Scope* fnScope = ctx.scopeOf(*site.decl) ? ctx.scopeOf(*site.decl) : ctx.scope;
@@ -828,17 +837,14 @@ Value driveVm(Evaluator& ev, size_t floor) {
828
837
  case Op::CallDynamic: {
829
838
  const CompiledChunk::CallSite& site = f.chunk->callSites[static_cast<size_t>(ins.a)];
830
839
  const size_t argCount = static_cast<size_t>(ins.b);
831
- std::vector<Value> args(argCount);
832
- for (size_t i = 0; i < argCount; ++i) {
833
- args[argCount - 1 - i] = std::move(f.stack.back());
834
- f.stack.pop_back();
835
- }
836
- Value callee = std::move(f.stack.back());
837
- f.stack.pop_back();
840
+ const size_t argBase = f.stack.size() - argCount;
841
+ Value* args = f.stack.data() + argBase; // into f.stack; truncated below once consumed
842
+ Value callee = std::move(f.stack[argBase - 1]);
838
843
  if (const auto* closurePtr = std::get_if<ClosurePtr>(&callee); closurePtr && *closurePtr) {
839
844
  const Closure& closure = **closurePtr;
840
845
  const oscad::FunctionLiteral& funcNode = *closure.node;
841
846
  BoundArgs bound = buildBoundArgs(ev, site, args, argCount, funcNode.parameters);
847
+ f.stack.resize(argBase - 1); // also drops the callee
842
848
  const CompiledChunk* calleeChunk = ev.useBytecodeVm() ? ev.lookupCompiledLiteralChunk(funcNode) : nullptr;
843
849
  if (calleeChunk) {
844
850
  const oscad::Scope* fnScope = ctx.scopeOf(funcNode) ? ctx.scopeOf(funcNode) : ctx.scope;
@@ -851,6 +857,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
851
857
  }
852
858
  } else {
853
859
  if (!site.calleeName.empty()) ev.warn("Ignoring unknown function '" + site.calleeName + "'", ins.pos);
860
+ f.stack.resize(argBase - 1); // arguments and callee both go
854
861
  f.stack.push_back(Value{});
855
862
  ++f.pc;
856
863
  }
@@ -859,12 +866,10 @@ Value driveVm(Evaluator& ev, size_t floor) {
859
866
  case Op::CallFnTail: {
860
867
  const CompiledChunk::CallSite& site = f.chunk->callSites[static_cast<size_t>(ins.a)];
861
868
  const size_t argCount = static_cast<size_t>(ins.b);
862
- std::vector<Value> args(argCount);
863
- for (size_t i = 0; i < argCount; ++i) {
864
- args[argCount - 1 - i] = std::move(f.stack.back());
865
- f.stack.pop_back();
866
- }
869
+ const size_t argBase = f.stack.size() - argCount;
870
+ Value* args = f.stack.data() + argBase; // into f.stack; truncated below once consumed
867
871
  BoundArgs bound = buildBoundArgs(ev, site, args, argCount, site.decl->parameters);
872
+ f.stack.resize(argBase);
868
873
  // Tail-hop-in-place requires f.hopEligible -- a frame
869
874
  // that's call-boundary-free (statement expression,
870
875
  // assignment block, parameter default) has no
@@ -890,7 +895,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
890
895
  if (calleeChunk) {
891
896
  ev.recordTailCallHop(site.calleeName, *site.decl, &site.callNode->position(), f.tailHopGuard);
892
897
  f.chunk = calleeChunk;
893
- f.code = &calleeChunk->bodyCode;
898
+ f.setCode(calleeChunk->bodyCode);
894
899
  f.pc = 0;
895
900
  f.slots.assign(static_cast<size_t>(calleeChunk->numSlots), Value{});
896
901
  f.stack.clear();
@@ -921,17 +926,14 @@ Value driveVm(Evaluator& ev, size_t floor) {
921
926
  case Op::CallDynamicTail: {
922
927
  const CompiledChunk::CallSite& site = f.chunk->callSites[static_cast<size_t>(ins.a)];
923
928
  const size_t argCount = static_cast<size_t>(ins.b);
924
- std::vector<Value> args(argCount);
925
- for (size_t i = 0; i < argCount; ++i) {
926
- args[argCount - 1 - i] = std::move(f.stack.back());
927
- f.stack.pop_back();
928
- }
929
- Value callee = std::move(f.stack.back());
930
- f.stack.pop_back();
929
+ const size_t argBase = f.stack.size() - argCount;
930
+ Value* args = f.stack.data() + argBase; // into f.stack; truncated below once consumed
931
+ Value callee = std::move(f.stack[argBase - 1]);
931
932
  if (const auto* closurePtr = std::get_if<ClosurePtr>(&callee); closurePtr && *closurePtr) {
932
933
  const Closure& closure = **closurePtr;
933
934
  const oscad::FunctionLiteral& funcNode = *closure.node;
934
935
  BoundArgs bound = buildBoundArgs(ev, site, args, argCount, funcNode.parameters);
936
+ f.stack.resize(argBase - 1); // also drops the callee
935
937
  const bool thisFrameBracketed = f.hopEligible;
936
938
  std::optional<EvalContext> hopCtx = thisFrameBracketed
937
939
  ? ev.isolatedCallCtxFor(funcNode, ctx, capturedLetTrail(closure))
@@ -942,7 +944,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
942
944
  if (calleeChunk) {
943
945
  ev.recordTailCallHop("<function literal>", funcNode, ins.pos, f.tailHopGuard);
944
946
  f.chunk = calleeChunk;
945
- f.code = &calleeChunk->bodyCode;
947
+ f.setCode(calleeChunk->bodyCode);
946
948
  f.pc = 0;
947
949
  f.slots.assign(static_cast<size_t>(calleeChunk->numSlots), Value{});
948
950
  f.stack.clear();
@@ -967,6 +969,7 @@ Value driveVm(Evaluator& ev, size_t floor) {
967
969
  }
968
970
  } else {
969
971
  if (!site.calleeName.empty()) ev.warn("Ignoring unknown function '" + site.calleeName + "'", ins.pos);
972
+ f.stack.resize(argBase - 1); // arguments and callee both go
970
973
  f.stack.push_back(Value{});
971
974
  ++f.pc;
972
975
  }
@@ -1559,7 +1562,7 @@ Value runCompiledFunction(Evaluator& ev, const CompiledChunk& chunk,
1559
1562
  EvalContext& childCtx) {
1560
1563
  auto frame = ev.acquireVmFrame();
1561
1564
  frame->chunk = &chunk;
1562
- frame->code = &chunk.bodyCode;
1565
+ frame->setCode(chunk.bodyCode);
1563
1566
  frame->pc = 0;
1564
1567
  frame->slots.assign(static_cast<size_t>(chunk.numSlots), Value{});
1565
1568
  frame->stack.clear();
@@ -1585,7 +1588,7 @@ Value runCompiledFunctionFromBound(Evaluator& ev, const CompiledChunk& chunk, co
1585
1588
  EvalContext& childCtx) {
1586
1589
  auto frame = ev.acquireVmFrame();
1587
1590
  frame->chunk = &chunk;
1588
- frame->code = &chunk.bodyCode;
1591
+ frame->setCode(chunk.bodyCode);
1589
1592
  frame->pc = 0;
1590
1593
  frame->slots.assign(static_cast<size_t>(chunk.numSlots), Value{});
1591
1594
  frame->stack.clear();
@@ -1644,7 +1647,7 @@ void runCompiledModuleBody(Evaluator& ev, const CompiledChunk& chunk, EvalContex
1644
1647
  // exactly (VmFrame::ownsModuleSplice's own doc comment, bytecode_vm.hpp).
1645
1648
  auto frame = ev.acquireVmFrame();
1646
1649
  frame->chunk = &chunk;
1647
- frame->code = &chunk.bodyCode;
1650
+ frame->setCode(chunk.bodyCode);
1648
1651
  frame->pc = 0;
1649
1652
  // .assign, not .clear() -- a module chunk's own bodyCode has no
1650
1653
  // PARAMETER slots (module params are always bound natively, see this
@@ -685,7 +685,7 @@ void Evaluator::recordTailCallHop(const std::string& calleeName, const oscad::AS
685
685
  // here too, or it would count the OLD declaration as active forever
686
686
  // and never see the NEW one at all.
687
687
  noteActiveDeclExit(frame.declNode);
688
- ++activeDeclRefcount_[&calleeDecl];
688
+ noteActiveDeclEnter(&calleeDecl);
689
689
  frame.name = calleeName;
690
690
  frame.declNode = &calleeDecl;
691
691
  frame.declPosition = &calleeDecl.position();
@@ -736,16 +736,22 @@ Evaluator::UserCallHandle Evaluator::enterUserCall(const std::string& name, cons
736
736
  callStack_.push_back(CallStackFrame{kind, name, callPos, &declNode.position(), &declNode, nullptr, upvalueParent});
737
737
  callStack_.back().bodyCtx = &childCtx; // per-frame locals for the debugger
738
738
  if (isModule) ++moduleCallDepth_;
739
- ++activeDeclRefcount_[&declNode];
739
+ noteActiveDeclEnter(&declNode);
740
740
  if (bodyExpr) checkDebug(*bodyExpr, childCtx);
741
741
  lastCtx_ = &childCtx;
742
742
  return h;
743
743
  }
744
744
 
745
745
  void Evaluator::noteActiveDeclExit(const oscad::ASTNode* declNode) {
746
- auto it = activeDeclRefcount_.find(declNode);
747
- if (it == activeDeclRefcount_.end()) return; // defensive; should always be found
748
- if (--it->second <= 0) activeDeclRefcount_.erase(it);
746
+ for (auto it = activeDeclRefcount_.begin(); it != activeDeclRefcount_.end(); ++it) {
747
+ if (it->first != declNode) continue;
748
+ if (--it->second <= 0) {
749
+ *it = activeDeclRefcount_.back(); // order is not meaningful
750
+ activeDeclRefcount_.pop_back();
751
+ }
752
+ return;
753
+ }
754
+ // not found: defensive; should always be present
749
755
  }
750
756
 
751
757
  void Evaluator::exitUserCallSuccess(const std::string& name, const UserCallHandle& handle, const Value& result,
@@ -17,6 +17,26 @@ size_t nextCharStart(const std::string& s, size_t i) {
17
17
 
18
18
  } // namespace
19
19
 
20
+ std::string utf8Encode(uint32_t cp) {
21
+ std::string out;
22
+ if (cp <= 0x7F) {
23
+ out.push_back(static_cast<char>(cp));
24
+ } else if (cp <= 0x7FF) {
25
+ out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
26
+ out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
27
+ } else if (cp <= 0xFFFF) {
28
+ out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
29
+ out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
30
+ out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
31
+ } else {
32
+ out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
33
+ out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
34
+ out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
35
+ out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
36
+ }
37
+ return out;
38
+ }
39
+
20
40
  bool utf8IsAscii(const std::string& s) {
21
41
  for (char c : s) {
22
42
  if (static_cast<unsigned char>(c) >= 0x80) return false;