openscad-cpp-evaluator 1.4.0__tar.gz → 1.5.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.0 → openscad_cpp_evaluator-1.5.0}/PKG-INFO +1 -1
  2. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +12 -0
  3. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/scope.cpp +16 -6
  4. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/utf8.hpp +6 -0
  5. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/function_builtins.cpp +2 -23
  7. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/eval_use.cpp +32 -7
  8. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/utf8.cpp +20 -0
  9. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/value.cpp +84 -7
  10. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_include_cache.cpp +29 -0
  11. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_value.cpp +106 -0
  12. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/.github/workflows/ci.yml +0 -0
  13. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/.github/workflows/wheels.yml +0 -0
  14. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/.gitignore +0 -0
  15. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/.gitmodules +0 -0
  16. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/CLAUDE.md +0 -0
  17. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/CMakeLists.txt +0 -0
  18. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/LICENSE +0 -0
  19. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/README.md +0 -0
  20. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/bindings/CMakeLists.txt +0 -0
  21. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/bindings/ast_to_py.cpp +0 -0
  22. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/bindings/module.cpp +0 -0
  23. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/cmake/embed_font.cmake +0 -0
  24. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/examples/CMakeLists.txt +0 -0
  25. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/examples/eval_perf_benchmark.cpp +0 -0
  26. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/examples/manifold_cache_reuse.cpp +0 -0
  27. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/examples/minimal_debugger.cpp +0 -0
  28. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  29. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/.gitignore +0 -0
  30. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  31. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  32. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/README.md +0 -0
  33. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  34. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  35. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  36. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  37. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  38. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  39. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  40. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  41. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  42. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  43. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  44. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  45. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
  46. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  47. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  48. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  49. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  50. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  51. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  52. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  53. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  54. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  55. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  56. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  57. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  58. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  59. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  60. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  61. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  62. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  63. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  64. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  65. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  66. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  67. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  68. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  69. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  70. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  71. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  72. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  73. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  74. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  75. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  76. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  77. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  78. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  79. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  80. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  81. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  82. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  83. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  84. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  85. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  86. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  87. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  88. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  89. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  90. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  91. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  92. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  93. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  94. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  95. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  96. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  97. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  98. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  99. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  100. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  101. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  102. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  103. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  104. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  105. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  106. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  107. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  108. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  109. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
  110. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  111. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  112. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  113. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
  114. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  116. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  117. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  118. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  119. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  120. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  121. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  122. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  123. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  124. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  125. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  126. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  127. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  128. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  129. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/resources/fonts/LICENSE +0 -0
  130. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  131. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/CMakeLists.txt +0 -0
  132. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/booleans.cpp +0 -0
  133. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/builtins.hpp +0 -0
  134. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/call_args.cpp +0 -0
  135. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/color.cpp +0 -0
  136. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/control.cpp +0 -0
  137. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/dxf_dim.cpp +0 -0
  138. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/extrude.cpp +0 -0
  139. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/import.cpp +0 -0
  140. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/modifiers.cpp +0 -0
  141. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/primitives_2d.cpp +0 -0
  142. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/primitives_3d.cpp +0 -0
  143. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/builtins/transforms.cpp +0 -0
  150. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/bytecode_compiler.cpp +0 -0
  151. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/bytecode_vm.cpp +0 -0
  152. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/colored_body.cpp +0 -0
  153. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/csg_generate.cpp +0 -0
  154. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/csg_resolve.cpp +0 -0
  155. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/css_colors.cpp +0 -0
  156. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/debug/debug_repl.cpp +0 -0
  157. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/debug_profile.cpp +0 -0
  158. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/eval_context.cpp +0 -0
  159. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/eval_error.cpp +0 -0
  160. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/export.cpp +0 -0
  161. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/expr_eval.cpp +0 -0
  162. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/format_closure.cpp +0 -0
  163. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/import/dxf_import.cpp +0 -0
  164. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/import/mesh_import.cpp +0 -0
  165. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/import/surface_load.cpp +0 -0
  166. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/import/svg_import.cpp +0 -0
  167. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/manifold_cache.cpp +0 -0
  168. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/measure_geometry.cpp +0 -0
  169. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/mesh_check.cpp +0 -0
  170. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/stmt_eval.cpp +0 -0
  171. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/text/font_match.cpp +0 -0
  172. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/text/freetype_font_provider.cpp +0 -0
  173. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/text/text_metrics.cpp +0 -0
  174. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/user_calls.cpp +0 -0
  175. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/src/zip_stored.cpp +0 -0
  176. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/CMakeLists.txt +0 -0
  177. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_booleans.cpp +0 -0
  178. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_bytecode_compiler.cpp +0 -0
  179. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_children_separate.cpp +0 -0
  180. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_cli.cpp +0 -0
  181. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_control_flow.cpp +0 -0
  182. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_csg_tree.cpp +0 -0
  183. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_debug_hooks.cpp +0 -0
  184. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_dxf_svg_import.cpp +0 -0
  185. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_eval_context.cpp +0 -0
  186. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_export.cpp +0 -0
  187. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_expr_eval.cpp +0 -0
  188. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_extrude_roof.cpp +0 -0
  189. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_feature_detection.cpp +0 -0
  190. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_function_builtins.cpp +0 -0
  191. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_helpers.hpp +0 -0
  192. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_import_export.cpp +0 -0
  193. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_manifold_cache.cpp +0 -0
  194. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_mesh_check.cpp +0 -0
  195. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_multi_color_merge.cpp +0 -0
  196. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_primitives.cpp +0 -0
  197. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_profiling.cpp +0 -0
  198. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_python_bindings.py +0 -0
  199. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_render_expr.cpp +0 -0
  200. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_scope_trail.cpp +0 -0
  201. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_scoping.cpp +0 -0
  202. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_surface.cpp +0 -0
  203. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_tail_calls.cpp +0 -0
  204. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_text.cpp +0 -0
  205. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_transforms.cpp +0 -0
  206. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_unexpected_args.cpp +0 -0
  207. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_use.cpp +0 -0
  208. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_viewport_params.cpp +0 -0
  209. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tests/test_zip_stored.cpp +0 -0
  210. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tools/cli/CMakeLists.txt +0 -0
  211. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tools/cli/cli_lib.cpp +0 -0
  212. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tools/cli/cli_lib.hpp +0 -0
  213. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.0}/tools/cli/main.cpp +0 -0
  214. {openscad_cpp_evaluator-1.4.0 → openscad_cpp_evaluator-1.5.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.0
3
+ Version: 1.5.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -48,6 +48,18 @@ std::unique_ptr<Scope> buildScopes(const std::vector<std::unique_ptr<ASTNode>>&
48
48
  // why this exists. Never takes ownership.
49
49
  std::unique_ptr<Scope> buildScopes(const std::vector<ASTNode*>& ast);
50
50
 
51
+ // Same, but recording into a table the CALLER owns, and not attaching it to
52
+ // the returned root.
53
+ //
54
+ // For a resolution that spans several files: `use <file>` builds a separate
55
+ // root Scope per used file, but all of them are read back through one
56
+ // evaluation, so their nodes' scopes have to land in ONE table. A per-root
57
+ // table would leave the outer evaluation unable to see anything the nested
58
+ // resolutions recorded -- every node of a used file would read back as
59
+ // having no scope.
60
+ std::unique_ptr<Scope> buildScopesInto(const std::vector<std::unique_ptr<ASTNode>>& ast, ScopeTable& table);
61
+ std::unique_ptr<Scope> buildScopesInto(const std::vector<ASTNode*>& ast, ScopeTable& table);
62
+
51
63
  // Parses `code`. Throws ParseError (with the full caret diagnostic) on a
52
64
  // syntax error.
53
65
  //
@@ -8,26 +8,36 @@
8
8
 
9
9
  namespace oscad {
10
10
 
11
- std::unique_ptr<Scope> buildScopes(const std::vector<std::unique_ptr<ASTNode>>& ast) {
12
- auto owned = std::make_unique<ScopeTable>();
13
- ScopeTableScope recording(*owned);
11
+ std::unique_ptr<Scope> buildScopesInto(const std::vector<std::unique_ptr<ASTNode>>& ast, ScopeTable& table) {
12
+ ScopeTableScope recording(table);
14
13
  auto root = std::make_unique<Scope>();
15
14
  collectHoistedDeclarations(ast, *root);
16
15
  for (auto& node : ast) {
17
16
  node->buildScope(*root);
18
17
  }
19
- root->adoptTable(std::move(owned));
20
18
  return root;
21
19
  }
22
20
 
23
- std::unique_ptr<Scope> buildScopes(const std::vector<ASTNode*>& ast) {
21
+ std::unique_ptr<Scope> buildScopes(const std::vector<std::unique_ptr<ASTNode>>& ast) {
24
22
  auto owned = std::make_unique<ScopeTable>();
25
- ScopeTableScope recording(*owned);
23
+ auto root = buildScopesInto(ast, *owned);
24
+ root->adoptTable(std::move(owned));
25
+ return root;
26
+ }
27
+
28
+ std::unique_ptr<Scope> buildScopesInto(const std::vector<ASTNode*>& ast, ScopeTable& table) {
29
+ ScopeTableScope recording(table);
26
30
  auto root = std::make_unique<Scope>();
27
31
  collectHoistedDeclarations(ast, *root);
28
32
  for (ASTNode* node : ast) {
29
33
  node->buildScope(*root);
30
34
  }
35
+ return root;
36
+ }
37
+
38
+ std::unique_ptr<Scope> buildScopes(const std::vector<ASTNode*>& ast) {
39
+ auto owned = std::make_unique<ScopeTable>();
40
+ auto root = buildScopesInto(ast, *owned);
31
41
  root->adoptTable(std::move(owned));
32
42
  return root;
33
43
  }
@@ -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.0"
7
+ version = "1.5.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -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]); };
@@ -17,8 +17,19 @@ ResolvedUseScopes resolveUseScopes(const std::vector<std::unique_ptr<oscad::ASTN
17
17
  return resolveUseScopes(borrowed, currentFile, logFn);
18
18
  }
19
19
 
20
- ResolvedUseScopes resolveUseScopes(const std::vector<const oscad::ASTNode*>& ownNodes,
21
- const std::string& currentFile, const std::function<void(const std::string&)>& logFn) {
20
+ namespace {
21
+
22
+ // The recursion. `table` is threaded through every level rather than each
23
+ // level building its own: `use <file>` gives each used file its own root
24
+ // Scope, but one evaluation reads all of them back, so every node's scope
25
+ // has to land in ONE table. A table per level meant the outer evaluation
26
+ // could not see anything a nested resolution recorded -- every node of a
27
+ // used file read back as having no scope, and `use` silently resolved to
28
+ // undef. BelfrySCAD's own suite caught that; this repo's did not.
29
+ ResolvedUseScopes resolveUseScopesInto(const std::vector<const oscad::ASTNode*>& ownNodes,
30
+ const std::string& currentFile,
31
+ const std::function<void(const std::string&)>& logFn,
32
+ oscad::ScopeTable& table) {
22
33
  ResolvedUseScopes result;
23
34
 
24
35
  std::vector<const oscad::ASTNode*> injected;
@@ -56,7 +67,10 @@ ResolvedUseScopes resolveUseScopes(const std::vector<const oscad::ASTNode*>& own
56
67
  result.usedFileAsts.push_back(std::move(lib.ast));
57
68
  const std::vector<std::unique_ptr<oscad::ASTNode>>& libAst = result.usedFileAsts.back();
58
69
 
59
- ResolvedUseScopes nested = resolveUseScopes(libAst, lib.resolvedPath, logFn);
70
+ std::vector<const oscad::ASTNode*> libBorrowed;
71
+ libBorrowed.reserve(libAst.size());
72
+ for (const auto& n : libAst) libBorrowed.push_back(n.get());
73
+ ResolvedUseScopes nested = resolveUseScopesInto(libBorrowed, lib.resolvedPath, logFn, table);
60
74
 
61
75
  std::vector<const oscad::ASTNode*> libInjected;
62
76
  for (const oscad::ASTNode* n : nested.ownNodesFiltered) {
@@ -92,12 +106,11 @@ ResolvedUseScopes resolveUseScopes(const std::vector<const oscad::ASTNode*>& own
92
106
  std::vector<oscad::ASTNode*> mutableProcessed;
93
107
  mutableProcessed.reserve(result.processedNodes.size());
94
108
  for (const oscad::ASTNode* n : result.processedNodes) mutableProcessed.push_back(const_cast<oscad::ASTNode*>(n));
95
- result.rootScope = oscad::buildScopes(mutableProcessed);
109
+ result.rootScope = oscad::buildScopesInto(mutableProcessed, table);
96
110
 
97
111
  {
98
- // Re-anchoring writes scopes too, into the same table buildScopes()
99
- // just filled -- which the root scope now owns.
100
- oscad::ScopeTableScope recording(*const_cast<oscad::ScopeTable*>(result.rootScope->table()));
112
+ // Re-anchoring writes scopes too, into the same shared table.
113
+ oscad::ScopeTableScope recording(table);
101
114
  for (const auto& [libInjected, libRootScope] : reanchor) {
102
115
  for (const oscad::ASTNode* n : libInjected) const_cast<oscad::ASTNode*>(n)->buildScope(*libRootScope);
103
116
  }
@@ -106,4 +119,16 @@ ResolvedUseScopes resolveUseScopes(const std::vector<const oscad::ASTNode*>& own
106
119
  return result;
107
120
  }
108
121
 
122
+ } // namespace
123
+
124
+ ResolvedUseScopes resolveUseScopes(const std::vector<const oscad::ASTNode*>& ownNodes, const std::string& currentFile,
125
+ const std::function<void(const std::string&)>& logFn) {
126
+ // One table for the whole resolution, handed to the root scope at the
127
+ // end so it lives exactly as long as the scopes it points into.
128
+ auto table = std::make_unique<oscad::ScopeTable>();
129
+ ResolvedUseScopes result = resolveUseScopesInto(ownNodes, currentFile, logFn, *table);
130
+ if (result.rootScope) result.rootScope->adoptTable(std::move(table));
131
+ return result;
132
+ }
133
+
109
134
  } // namespace oscadeval
@@ -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;
@@ -374,17 +374,60 @@ Value matmul(const Value& a, const Value& b, std::string* error) {
374
374
  " and " + oscTypeName(e2));
375
375
  }
376
376
 
377
+ namespace {
378
+
379
+ // `count` hex digits starting at `at`, into `out`. False (leaving `out`
380
+ // untouched) if any of them is not a hex digit.
381
+ bool hexDigits(const std::string& s, size_t at, size_t count, std::uint32_t& out) {
382
+ std::uint32_t v = 0;
383
+ for (size_t k = 0; k < count; ++k) {
384
+ const char c = s[at + k];
385
+ std::uint32_t d;
386
+ if (c >= '0' && c <= '9') d = static_cast<std::uint32_t>(c - '0');
387
+ else if (c >= 'a' && c <= 'f') d = static_cast<std::uint32_t>(c - 'a' + 10);
388
+ else if (c >= 'A' && c <= 'F') d = static_cast<std::uint32_t>(c - 'A' + 10);
389
+ else return false;
390
+ v = (v << 4) | d;
391
+ }
392
+ out = v;
393
+ return true;
394
+ }
395
+
396
+ // What a well-formed \x/\u escape contributes. A code point that cannot
397
+ // be encoded -- zero, or a lone surrogate -- becomes a SPACE rather than
398
+ // nothing or invalid UTF-8. That is the reference's behaviour, checked by
399
+ // equality since the bytes are hard to see: "\u0000" == " ", "\ud83d" ==
400
+ // " ", and "\x00" == "\u0000", all true on 2026.02.01. Note chr() does
401
+ // NOT agree -- chr(55357) is the empty string -- so the two paths differ
402
+ // deliberately.
403
+ std::string encodeEscapedCodePoint(std::uint32_t cp) {
404
+ if (cp == 0 || (cp >= 0xD800u && cp <= 0xDFFFu) || cp > 0x10FFFFu) return " ";
405
+ return utf8Encode(cp);
406
+ }
407
+
408
+ } // namespace
409
+
377
410
  std::string unescapeStringLiteral(const std::string& raw) {
378
- // Most strings carry no escape at all, and this runs on every
379
- // evaluation of a literal on the tree-walking path -- so don't build a
380
- // second copy of the string unless there is something to change.
381
- const size_t first = raw.find('\\');
411
+ // Most strings need no work at all, and this runs on every evaluation
412
+ // of a literal on the tree-walking path -- so don't build a second copy
413
+ // unless there is something to change. A bare newline counts as
414
+ // something to change (see the loop), so it has to open the scan too.
415
+ const size_t first = raw.find_first_of("\\\n");
382
416
  if (first == std::string::npos) return raw;
383
417
 
384
418
  std::string out;
385
419
  out.reserve(raw.size());
386
420
  out.append(raw, 0, first);
387
421
  for (size_t i = first; i < raw.size(); ++i) {
422
+ // A raw LF inside a string literal contributes NOTHING -- writing a
423
+ // string across two source lines joins them, keeping the second
424
+ // line's indentation:
425
+ // s = "abcd
426
+ // efgh"; // -> "abcd efgh", 12 characters
427
+ // A raw CR is not special and stands for itself, so a CRLF file
428
+ // leaves the CR behind (len 3 for "x<CR><LF>y"). Both verified on
429
+ // 2026.02.01.
430
+ if (raw[i] == '\n') continue;
388
431
  if (raw[i] != '\\' || i + 1 >= raw.size()) {
389
432
  out.push_back(raw[i]); // a trailing lone backslash stands for itself
390
433
  continue;
@@ -394,10 +437,44 @@ std::string unescapeStringLiteral(const std::string& raw) {
394
437
  case 'n': out.push_back('\n'); ++i; break;
395
438
  case 't': out.push_back('\t'); ++i; break;
396
439
  case 'r': out.push_back('\r'); ++i; break;
397
- case '\n': ++i; break; // line continuation: contributes nothing
440
+ case 'x': {
441
+ // \xNN, exactly two hex digits, and ASCII ONLY. The
442
+ // reference rejects anything above 0x7F -- a raw high byte
443
+ // would not be valid UTF-8, and the rest of the string is
444
+ // -- leaving the text to stand for itself: "\xff" is the
445
+ // three characters x, f, f. Verified against 2026.02.01,
446
+ // which takes \x00..\x7f and refuses \x80..\xff.
447
+ std::uint32_t cp = 0;
448
+ if (i + 3 < raw.size() && hexDigits(raw, i + 2, 2, cp) && cp <= 0x7Fu) {
449
+ out += encodeEscapedCodePoint(cp);
450
+ i += 3;
451
+ break;
452
+ }
453
+ out.push_back(next);
454
+ ++i;
455
+ break;
456
+ }
457
+ case 'u': {
458
+ // \uXXXX, exactly four hex digits. No \u{...} form: the
459
+ // reference calls that an undefined escape.
460
+ std::uint32_t cp = 0;
461
+ if (i + 5 < raw.size() && hexDigits(raw, i + 2, 4, cp)) {
462
+ out += encodeEscapedCodePoint(cp);
463
+ i += 5;
464
+ break;
465
+ }
466
+ out.push_back(next);
467
+ ++i;
468
+ break;
469
+ }
470
+ case '\n': ++i; break; // backslash + LF: both go
398
471
  case '\r':
399
- // Only a CRLF pair is a line continuation; a lone CR is an
400
- // ordinary escaped character like any other.
472
+ // A backslash before CRLF takes the whole line ending.
473
+ // Deliberately unlike the reference, which treats the
474
+ // backslash as an undefined escape and keeps the CR --
475
+ // leaving a stray control character in any string wrapped
476
+ // in a file written on Windows. See this suite's
477
+ // BackslashNewlineContributesNothing.
401
478
  if (i + 2 < raw.size() && raw[i + 2] == '\n') { i += 2; break; }
402
479
  out.push_back('\r');
403
480
  ++i;
@@ -130,3 +130,32 @@ TEST(IncludeCache, ADerivedContextKeepsTheScopeTable) {
130
130
  ev.evaluate(used.processedNodes, ctx, {}, /*generate=*/false);
131
131
  for (const std::string& m : logs) EXPECT_EQ(m.find("Recursion"), std::string::npos) << m;
132
132
  }
133
+
134
+
135
+ TEST(IncludeCache, NestedUseKeepsItsScopesInTheSharedTable) {
136
+ // `use <file>` gives each used file its own root Scope, and a used file
137
+ // may itself `use` another. All of them are read back by ONE
138
+ // evaluation, so every node's scope has to land in ONE ScopeTable.
139
+ //
140
+ // The version of this that built a table per recursion level threw the
141
+ // nested ones away: the outer evaluation then saw no scope at all for a
142
+ // used file's nodes, and combo() below resolved to undef instead of
143
+ // 107. Every C++ test here passed anyway -- BelfrySCAD's suite is what
144
+ // caught it -- so this is that case, kept where the code lives.
145
+ writeFile("inner.scad", "inner_val = 100;\nfunction get_inner() = inner_val;\n");
146
+ writeFile("lib2.scad",
147
+ "use <oscad_inccache_inner.scad>\n"
148
+ "lib2_val = 7;\n"
149
+ "function combo() = get_inner() + lib2_val;\n");
150
+ const auto main2 = writeFile("main2.scad",
151
+ "use <oscad_inccache_lib2.scad>\n"
152
+ "echo(combo());\n"
153
+ "echo(is_undef(inner_val));\n");
154
+
155
+ const std::vector<std::string> echoes = echoesOf(main2);
156
+ ASSERT_EQ(echoes.size(), 2u);
157
+ // combo() reaches through lib2's own nested use into inner.scad.
158
+ EXPECT_EQ(echoes[0], "ECHO: 107");
159
+ // ...while inner.scad's own declarations stay invisible to main2.
160
+ EXPECT_EQ(echoes[1], "ECHO: true");
161
+ }
@@ -496,3 +496,109 @@ TEST(StringEscapes, BothEvaluationPathsCookTheLiteral) {
496
496
  ASSERT_EQ(echoed.size(), 1u);
497
497
  EXPECT_EQ(echoed[0], "ECHO: 3") << "line continuation, compiled path";
498
498
  }
499
+
500
+ // Every expectation below was read off OpenSCAD 2026.02.01 rather than a
501
+ // spec, because the rules are narrow and not guessable:
502
+ //
503
+ // \xNN two hex digits, ASCII ONLY -- \x80..\xff are REFUSED, since a
504
+ // raw high byte is not valid UTF-8 and the rest of the string is
505
+ // \uXXXX exactly four hex digits; there is no \u{...} form
506
+ // an unencodable code point (zero, or a lone surrogate) becomes a SPACE
507
+ // anything else after a backslash is an "undefined escape": the
508
+ // backslash is dropped and the rest stands for itself
509
+ //
510
+ // The reference also emits "WARNING: Undefined escape sequence" for that
511
+ // last case, at PARSE time (once per literal, not once per evaluation --
512
+ // checked with a literal inside a for loop). This port has no parse-time
513
+ // warning channel, so it produces the same STRING without the warning.
514
+ TEST(StringEscapes, DecodesFourDigitUnicodeEscapes) {
515
+ EXPECT_EQ(unescapeStringLiteral("\\u03a9"), "Ω"); // len() of this is 1, not 5
516
+ EXPECT_EQ(unescapeStringLiteral("a\\u03a9b"), "aΩb");
517
+ EXPECT_EQ(unescapeStringLiteral("\\u0041"), "A");
518
+ EXPECT_EQ(unescapeStringLiteral("\\u00a9"), "©");
519
+ EXPECT_EQ(unescapeStringLiteral("\\uffff"), "￿");
520
+ EXPECT_EQ(unescapeStringLiteral("\\uFFFF"), "￿"); // hex digits are case-insensitive
521
+ }
522
+
523
+ TEST(StringEscapes, DecodesAsciiHexEscapesOnly) {
524
+ EXPECT_EQ(unescapeStringLiteral("\\x41"), "A");
525
+ EXPECT_EQ(unescapeStringLiteral("\\x7e"), "~");
526
+ EXPECT_EQ(unescapeStringLiteral("\\x7f"), "\x7f");
527
+ // Above 0x7F the reference refuses and the text stands for itself.
528
+ EXPECT_EQ(unescapeStringLiteral("\\x80"), "x80");
529
+ EXPECT_EQ(unescapeStringLiteral("\\xa9"), "xa9");
530
+ EXPECT_EQ(unescapeStringLiteral("\\xff"), "xff");
531
+ }
532
+
533
+ TEST(StringEscapes, AnUnencodableCodePointBecomesASpace) {
534
+ // Confirmed by equality on the reference, since the bytes are hard to
535
+ // read back: "" == " ", "\ud83d" == " " and "\x00" == ""
536
+ // are all true there. chr() does NOT agree -- chr(55357) is the empty
537
+ // string -- so the two paths differ deliberately.
538
+ EXPECT_EQ(unescapeStringLiteral("\\u0000"), " ");
539
+ EXPECT_EQ(unescapeStringLiteral("\\x00"), " ");
540
+ EXPECT_EQ(unescapeStringLiteral("\\ud83d"), " "); // lone high surrogate
541
+ EXPECT_EQ(unescapeStringLiteral("\\udfff"), " "); // lone low surrogate
542
+ }
543
+
544
+ TEST(StringEscapes, AnUndefinedEscapeDropsOnlyItsBackslash) {
545
+ EXPECT_EQ(unescapeStringLiteral("\\q"), "q");
546
+ EXPECT_EQ(unescapeStringLiteral("\\u12"), "u12"); // too few digits
547
+ EXPECT_EQ(unescapeStringLiteral("\\uZZZZ"), "uZZZZ"); // not hex
548
+ EXPECT_EQ(unescapeStringLiteral("\\u{41}"), "u{41}"); // no brace form
549
+ EXPECT_EQ(unescapeStringLiteral("\\x4"), "x4");
550
+ EXPECT_EQ(unescapeStringLiteral("\\xg1"), "xg1");
551
+ }
552
+
553
+ TEST(StringEscapes, LeavesTheAlreadyHandledEscapesAlone) {
554
+ EXPECT_EQ(unescapeStringLiteral("\\n"), "\n");
555
+ EXPECT_EQ(unescapeStringLiteral("\\t"), "\t");
556
+ EXPECT_EQ(unescapeStringLiteral("\\r"), "\r");
557
+ EXPECT_EQ(unescapeStringLiteral("\\\\"), "\\");
558
+ EXPECT_EQ(unescapeStringLiteral("\\\""), "\"");
559
+ EXPECT_EQ(unescapeStringLiteral("plain"), "plain");
560
+ }
561
+
562
+ TEST(StringEscapes, ATruncatedEscapeDoesNotReadPastTheEnd) {
563
+ // \u and \x look ahead four and two characters; one cut short at the
564
+ // end of the literal must stop rather than run off the buffer.
565
+ EXPECT_EQ(unescapeStringLiteral("\\u"), "u");
566
+ EXPECT_EQ(unescapeStringLiteral("\\u0"), "u0");
567
+ EXPECT_EQ(unescapeStringLiteral("\\u03a"), "u03a");
568
+ EXPECT_EQ(unescapeStringLiteral("\\x"), "x");
569
+ EXPECT_EQ(unescapeStringLiteral("\\x4"), "x4");
570
+ EXPECT_EQ(unescapeStringLiteral("\\"), "\\");
571
+ }
572
+
573
+ TEST(StringEscapes, ARawNewlineInsideALiteralContributesNothing) {
574
+ // Writing a string across two source lines joins them, keeping the
575
+ // second line's indentation -- this is how OpenSCAD lets a long string
576
+ // be wrapped, and it is NOT a backslash continuation.
577
+ //
578
+ // s = "abcd
579
+ // efgh"; // -> "abcd efgh", 12 characters
580
+ EXPECT_EQ(unescapeStringLiteral("abcd\n efgh"), "abcd efgh");
581
+ EXPECT_EQ(unescapeStringLiteral("x\ny"), "xy");
582
+ EXPECT_EQ(unescapeStringLiteral("x\n\ny"), "xy");
583
+ EXPECT_EQ(unescapeStringLiteral("x\n \n y"), "x y");
584
+ }
585
+
586
+ TEST(StringEscapes, ARawCarriageReturnStandsForItself) {
587
+ // Only the LF is special. A CRLF file therefore leaves the CR in the
588
+ // string: "x<CR><LF>y" is three characters, not two.
589
+ EXPECT_EQ(unescapeStringLiteral("x\ry"), "x\ry");
590
+ EXPECT_EQ(unescapeStringLiteral("x\r\ny"), "x\ry");
591
+ }
592
+
593
+ TEST(StringEscapes, ABackslashBeforeANewlineTakesTheWholeLineEnding) {
594
+ // The reference has no line-continuation escape at all: it calls the
595
+ // backslash an undefined escape, drops only it, then applies the
596
+ // ordinary newline rules -- so "x\\<CR><LF>y" is 3 characters there,
597
+ // the CR surviving. This port takes the whole line ending instead, on
598
+ // purpose, so a string wrapped in a Windows-line-ending file does not
599
+ // pick up a stray CR. Same reasoning as
600
+ // BackslashNewlineContributesNothing above.
601
+ EXPECT_EQ(unescapeStringLiteral("x\\\ny"), "xy");
602
+ EXPECT_EQ(unescapeStringLiteral("x\\\r\ny"), "xy"); // reference keeps the CR
603
+ EXPECT_EQ(unescapeStringLiteral("x\\\ry"), "x\ry");
604
+ }