openscad-cpp-evaluator 0.43.0__tar.gz → 0.44.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 (208) hide show
  1. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/CLAUDE.md +15 -0
  2. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/CMakeLists.txt +13 -1
  3. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/PKG-INFO +1 -1
  4. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/evaluator.hpp +7 -0
  5. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/pyproject.toml +1 -1
  6. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/CMakeLists.txt +8 -0
  7. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/function_builtins.cpp +43 -2
  8. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/bytecode_compiler.cpp +18 -9
  9. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/eval_context.cpp +28 -0
  10. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/expr_eval.cpp +10 -0
  11. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/user_calls.cpp +10 -0
  12. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/CMakeLists.txt +1 -0
  13. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_bytecode_compiler.cpp +7 -7
  14. openscad_cpp_evaluator-0.44.0/tests/test_feature_detection.cpp +161 -0
  15. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_use.cpp +6 -3
  16. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/.github/workflows/ci.yml +0 -0
  17. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/.github/workflows/wheels.yml +0 -0
  18. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/.gitignore +0 -0
  19. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/.gitmodules +0 -0
  20. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/LICENSE +0 -0
  21. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/README.md +0 -0
  22. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/bindings/CMakeLists.txt +0 -0
  23. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/bindings/ast_to_py.cpp +0 -0
  24. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/bindings/module.cpp +0 -0
  25. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/cmake/embed_font.cmake +0 -0
  26. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/examples/CMakeLists.txt +0 -0
  27. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/examples/eval_perf_benchmark.cpp +0 -0
  28. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/examples/manifold_cache_reuse.cpp +0 -0
  29. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/examples/minimal_debugger.cpp +0 -0
  30. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
  31. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/.gitignore +0 -0
  32. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
  33. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
  34. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/README.md +0 -0
  35. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
  36. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
  37. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
  38. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
  39. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
  40. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
  41. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
  42. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
  43. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
  44. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
  45. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
  46. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
  47. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
  48. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
  49. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
  50. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
  51. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
  52. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
  53. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
  54. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
  55. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
  56. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
  57. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
  58. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
  59. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
  60. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
  61. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
  62. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
  63. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
  64. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
  65. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
  66. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
  67. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
  68. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
  69. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
  70. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
  71. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
  72. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
  73. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
  74. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
  75. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
  76. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
  77. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
  78. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
  79. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
  80. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
  81. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
  82. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
  83. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
  84. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
  85. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
  86. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
  87. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
  88. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
  89. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
  90. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
  91. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
  92. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
  93. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
  94. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
  95. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
  96. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
  97. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
  98. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
  99. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
  100. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
  101. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
  102. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
  103. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
  104. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
  105. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
  106. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
  107. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
  108. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
  109. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
  110. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
  111. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
  112. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
  113. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
  114. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
  115. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
  116. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
  117. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
  118. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
  119. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
  120. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
  121. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
  122. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
  123. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
  124. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
  125. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
  126. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
  127. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
  128. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
  129. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
  130. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/resources/fonts/LICENSE +0 -0
  131. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
  132. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/booleans.cpp +0 -0
  133. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/builtins.hpp +0 -0
  134. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/call_args.cpp +0 -0
  135. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/color.cpp +0 -0
  136. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/control.cpp +0 -0
  137. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/dxf_dim.cpp +0 -0
  138. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/extrude.cpp +0 -0
  139. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/import.cpp +0 -0
  140. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/modifiers.cpp +0 -0
  141. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/primitives_2d.cpp +0 -0
  142. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/primitives_3d.cpp +0 -0
  143. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/registry.cpp +0 -0
  144. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/roof.cpp +0 -0
  145. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/segments.cpp +0 -0
  146. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/surface.cpp +0 -0
  147. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/text.cpp +0 -0
  148. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/topology.cpp +0 -0
  149. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/builtins/transforms.cpp +0 -0
  150. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/bytecode_vm.cpp +0 -0
  151. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/colored_body.cpp +0 -0
  152. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/csg_generate.cpp +0 -0
  153. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/csg_resolve.cpp +0 -0
  154. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/css_colors.cpp +0 -0
  155. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/debug/debug_repl.cpp +0 -0
  156. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/debug_profile.cpp +0 -0
  157. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/eval_error.cpp +0 -0
  158. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/eval_use.cpp +0 -0
  159. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/export.cpp +0 -0
  160. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/import/dxf_import.cpp +0 -0
  161. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/import/mesh_import.cpp +0 -0
  162. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/import/surface_load.cpp +0 -0
  163. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/import/svg_import.cpp +0 -0
  164. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/manifold_cache.cpp +0 -0
  165. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/measure_geometry.cpp +0 -0
  166. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/mesh_check.cpp +0 -0
  167. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/stmt_eval.cpp +0 -0
  168. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/text/font_match.cpp +0 -0
  169. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/text/freetype_font_provider.cpp +0 -0
  170. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/text/text_metrics.cpp +0 -0
  171. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/value.cpp +0 -0
  172. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/src/zip_stored.cpp +0 -0
  173. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_booleans.cpp +0 -0
  174. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_children_separate.cpp +0 -0
  175. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_cli.cpp +0 -0
  176. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_control_flow.cpp +0 -0
  177. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_csg_tree.cpp +0 -0
  178. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_debug_hooks.cpp +0 -0
  179. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_dxf_svg_import.cpp +0 -0
  180. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_eval_context.cpp +0 -0
  181. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_export.cpp +0 -0
  182. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_expr_eval.cpp +0 -0
  183. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_extrude_roof.cpp +0 -0
  184. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_function_builtins.cpp +0 -0
  185. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_helpers.hpp +0 -0
  186. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_import_export.cpp +0 -0
  187. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_manifold_cache.cpp +0 -0
  188. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_mesh_check.cpp +0 -0
  189. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_multi_color_merge.cpp +0 -0
  190. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_primitives.cpp +0 -0
  191. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_profiling.cpp +0 -0
  192. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_python_bindings.py +0 -0
  193. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_render_expr.cpp +0 -0
  194. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_scope_trail.cpp +0 -0
  195. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_scoping.cpp +0 -0
  196. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_surface.cpp +0 -0
  197. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_tail_calls.cpp +0 -0
  198. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_text.cpp +0 -0
  199. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_transforms.cpp +0 -0
  200. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_unexpected_args.cpp +0 -0
  201. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_value.cpp +0 -0
  202. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_viewport_params.cpp +0 -0
  203. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tests/test_zip_stored.cpp +0 -0
  204. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tools/cli/CMakeLists.txt +0 -0
  205. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tools/cli/cli_lib.cpp +0 -0
  206. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tools/cli/cli_lib.hpp +0 -0
  207. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/tools/cli/main.cpp +0 -0
  208. {openscad_cpp_evaluator-0.43.0 → openscad_cpp_evaluator-0.44.0}/uv.lock +0 -0
@@ -441,6 +441,21 @@ handling in arithmetic, the closure-detection rule for `call_ctx` vs `child_ctx`
441
441
  verbatim port, not an independent design. Deliberate divergences are called out in code comments —
442
442
  grep for `ponytail:`.
443
443
 
444
+ - **Feature detection** — `$_SUPPORTED_FEATURE` (just `true`; names the CAPABILITY rather than the
445
+ vendor, so any evaluator adding `supported_feature()` is meant to set it, and checking it first
446
+ solves the bootstrapping problem of calling a function you don't know exists. Written
447
+ `!is_undef($_SUPPORTED_FEATURE) && supported_feature(...)` the guard is **silent** in OpenSCAD --
448
+ `is_undef()` is the one way to read an unknown variable there without a warning, and `&&`
449
+ short-circuits past the unknown call; verified against the reference), `$_BELFRYSCAD` (seeded in `EvalContext::makeRoot`, `src/eval_context.cpp`, as
450
+ `[major, minor, patch]` from `OSCAD_EVAL_VERSION_*` compile definitions that the top-level
451
+ `CMakeLists.txt` parses out of `pyproject.toml`, so wheel and local builds cannot disagree and a
452
+ bump needs one edit) and `supported_feature("name")` (`featureLevels()` in
453
+ `src/builtins/function_builtins.cpp`, returning a LEVEL so a feature whose semantics change can
454
+ be told from its earlier self; 0 for unknown names and non-string arguments, deliberately
455
+ silent so probing for a future feature is safe). Both exist because OpenSCAD **silently ignores**
456
+ unknown arguments -- `children(separate=true)` renders the wrong shape there with no warning --
457
+ and both read as `undef` in OpenSCAD, so a guard written against them works in both.
458
+
444
459
  - `include/openscad_cpp_evaluator/value.hpp`, `src/value.cpp` — the `Value` variant (OpenSCAD's
445
460
  dynamic value type) and its free-function arithmetic/comparison helpers. See the plan's §1 for
446
461
  the type-mapping table and why there's no numpy-equivalent library.
@@ -1,5 +1,17 @@
1
1
  cmake_minimum_required(VERSION 3.24)
2
- project(openscad_cpp_evaluator CXX)
2
+
3
+ # Read the version straight out of pyproject.toml rather than repeating it
4
+ # here. It reaches C++ as OSCAD_EVAL_VERSION_{MAJOR,MINOR,PATCH} (see
5
+ # src/CMakeLists.txt), which is what seeds $_BELFRYSCAD -- so a released
6
+ # wheel and a local `cmake --build` always report the same numbers, and a
7
+ # version bump needs one edit, not three.
8
+ file(READ "${CMAKE_CURRENT_SOURCE_DIR}/pyproject.toml" _oscad_pyproject)
9
+ if(NOT _oscad_pyproject MATCHES "\nversion = \"([0-9]+)\.([0-9]+)\.([0-9]+)\"")
10
+ message(FATAL_ERROR "could not read version = \"X.Y.Z\" from pyproject.toml")
11
+ endif()
12
+ set(OSCAD_EVAL_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
13
+
14
+ project(openscad_cpp_evaluator VERSION ${OSCAD_EVAL_VERSION} LANGUAGES CXX)
3
15
 
4
16
  # On Apple Silicon, warn (don't fail -- a universal or intentionally-x86_64
5
17
  # build is legitimate) if the effective target architecture is x86_64 while
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_cpp_evaluator
3
- Version: 0.43.0
3
+ Version: 0.44.0
4
4
  Summary: C++ OpenSCAD evaluator with Python bindings
5
5
  Author: NeuSCAD Contributors
6
6
  License-Expression: MIT
@@ -372,6 +372,13 @@ public:
372
372
  // One synthetic `children(index)` statement, owned by syntheticNodes_.
373
373
  const oscad::ASTNode* makeChildrenIndexCall(const oscad::ModularCall& origin, size_t index);
374
374
 
375
+ // The sole argument of `is_undef(x)` when x is a bare identifier, else
376
+ // nullptr. That call is a PROBE -- asking whether a name is defined must
377
+ // not itself complain that it isn't -- so the name is read with
378
+ // warnIfUndef=false. Static and syntactic so the bytecode compiler can
379
+ // ask at compile time and emit LoadFreeNoWarn.
380
+ static const oscad::Identifier* undefProbeTarget(const oscad::PrimaryCall& call);
381
+
375
382
  // Syntactic gate: is this statement a `children(..., separate=true)`?
376
383
  // Cheap and side-effect-free, so the bytecode compiler can ask it at
377
384
  // COMPILE time to decide which opcode to emit, long before the flag's
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_cpp_evaluator"
7
- version = "0.43.0"
7
+ version = "0.44.0"
8
8
  description = "C++ OpenSCAD evaluator with Python bindings"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -46,6 +46,14 @@ add_library(openscad_cpp_evaluator STATIC
46
46
  builtins/surface.cpp
47
47
  )
48
48
 
49
+ # Seeds $_BELFRYSCAD (eval_context.cpp). Parsed from pyproject.toml by the
50
+ # top-level CMakeLists so there is one source of truth for the version.
51
+ target_compile_definitions(openscad_cpp_evaluator PRIVATE
52
+ OSCAD_EVAL_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
53
+ OSCAD_EVAL_VERSION_MINOR=${PROJECT_VERSION_MINOR}
54
+ OSCAD_EVAL_VERSION_PATCH=${PROJECT_VERSION_PATCH}
55
+ )
56
+
49
57
  target_include_directories(openscad_cpp_evaluator PUBLIC
50
58
  $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
51
59
  )
@@ -449,7 +449,7 @@ bool isBuiltinFunctionName(const std::string& name) {
449
449
  "acos", "atan", "atan2", "max", "min", "pow", "norm", "cross", "rands", "concat", "len", "str",
450
450
  "chr", "ord", "is_undef", "is_num", "is_bool", "is_string", "is_list", "is_function", "is_object",
451
451
  "search", "lookup", "has_key", "version", "version_num", "parent_module",
452
- "object", "textmetrics", "fontmetrics", "dxf_dim", "dxf_cross",
452
+ "object", "textmetrics", "fontmetrics", "dxf_dim", "dxf_cross", "supported_feature",
453
453
  };
454
454
  return names.count(name) > 0;
455
455
  }
@@ -591,9 +591,37 @@ enum class BuiltinFnId {
591
591
  TextMetrics, FontMetrics, Abs, Sign, Ceil, Floor, Round, Sqrt, Ln, Log, Exp, Sin, Cos, Tan,
592
592
  Asin, Acos, Atan, Atan2, Max, Min, Pow, Norm, Cross, Rands, Concat, Len, Str, Chr, Ord,
593
593
  IsUndef, IsNum, IsBool, IsString, IsList, IsFunction, IsObject, Search, Lookup, HasKey,
594
- Version, VersionNum, ParentModule, DxfDim, DxfCross,
594
+ Version, VersionNum, ParentModule, DxfDim, DxfCross, SupportedFeature,
595
595
  };
596
596
 
597
+ // supported_feature("name") -> the level at which this build implements that
598
+ // feature, or 0 for one it does not implement (and for a name it has never
599
+ // heard of, which is deliberate: probing for a feature from a future release
600
+ // is supposed to be safe).
601
+ //
602
+ // A LEVEL rather than a boolean so a feature whose semantics change later
603
+ // can be told apart from its earlier self. Everything here is 1 today;
604
+ // nothing has changed since this function existed, and a script cannot
605
+ // observe what a build without supported_feature() did anyway.
606
+ //
607
+ // This is the answer to a real hazard: OpenSCAD does not reject arguments or
608
+ // names it doesn't know -- children(separate=true) is silently ignored there
609
+ // -- so a script using an extension runs and quietly renders something else.
610
+ // Guarding on supported_feature() is how a script says so out loud.
611
+ const std::unordered_map<std::string, double>& featureLevels() {
612
+ static const std::unordered_map<std::string, double> levels = {
613
+ {"separate-children", 1.0}, // children(..., separate=true)
614
+ {"minkowski-diff", 1.0}, // minkowski_difference()
615
+ {"sphere-styles", 1.0}, // sphere(style=)
616
+ {"export-name", 1.0}, // $export_name
617
+ {"simplify-op", 1.0}, // simplify()
618
+ {"expr-import", 1.0}, // import() in expression position
619
+ {"object-function", 1.0}, // object(), unconditional here
620
+ {"roof-op", 1.0}, // roof(), method="voronoi" only
621
+ };
622
+ return levels;
623
+ }
624
+
597
625
  const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
598
626
  static const std::unordered_map<std::string, BuiltinFnId> ids = {
599
627
  {"textmetrics", BuiltinFnId::TextMetrics}, {"fontmetrics", BuiltinFnId::FontMetrics},
@@ -614,6 +642,7 @@ const std::unordered_map<std::string, BuiltinFnId>& builtinFnIds() {
614
642
  {"version", BuiltinFnId::Version}, {"version_num", BuiltinFnId::VersionNum},
615
643
  {"parent_module", BuiltinFnId::ParentModule},
616
644
  {"dxf_dim", BuiltinFnId::DxfDim}, {"dxf_cross", BuiltinFnId::DxfCross},
645
+ {"supported_feature", BuiltinFnId::SupportedFeature},
617
646
  };
618
647
  return ids;
619
648
  }
@@ -694,6 +723,7 @@ const std::unordered_map<int, BuiltinCheck>& builtinChecks() {
694
723
  // warning, so only the too-many case is an arity error.
695
724
  add(BuiltinFnId::ParentModule, {0, 1, "1", {kNum}});
696
725
  add(BuiltinFnId::HasKey, {-1, -1, nullptr, {{TObj, "object"}, {TStr, "string"}}});
726
+ add(BuiltinFnId::SupportedFeature, {1, 1, "1", {}});
697
727
  for (BuiltinFnId id : {BuiltinFnId::IsUndef, BuiltinFnId::IsNum, BuiltinFnId::IsBool,
698
728
  BuiltinFnId::IsString, BuiltinFnId::IsList, BuiltinFnId::IsFunction,
699
729
  BuiltinFnId::IsObject}) {
@@ -1008,6 +1038,17 @@ Value evalBuiltinFunction(Evaluator& ev, const std::string& name, const CallArgs
1008
1038
  // The OpenSCAD release we track. version_num() is that same
1009
1039
  // year/month/day folded as y * 10000 + m * 100 + d, exactly like the
1010
1040
  // reference's own builtin_version_num (builtin_functions.cc).
1041
+ case BuiltinFnId::SupportedFeature: {
1042
+ const Value name = getArg(args, 0, "feature", Value{});
1043
+ const std::string* s = std::get_if<std::string>(&name);
1044
+ // A non-string is 0 rather than an error, same as an unknown
1045
+ // name: the whole point of this function is that asking is
1046
+ // always safe.
1047
+ if (!s) return Value{0.0};
1048
+ const auto& levels = featureLevels();
1049
+ const auto it = levels.find(*s);
1050
+ return Value{it == levels.end() ? 0.0 : it->second};
1051
+ }
1011
1052
  case BuiltinFnId::Version: return numList({2026.0, 1.0, 1.0});
1012
1053
  case BuiltinFnId::VersionNum: {
1013
1054
  // The optional vector argument the reference also accepts: with
@@ -620,15 +620,24 @@ public:
620
620
  }
621
621
  }
622
622
 
623
- for (const auto& argPtr : n.arguments) {
624
- if (argPtr->kind() == NodeKind::NamedArgument) {
625
- auto& a = static_cast<const oscad::NamedArgument&>(*argPtr);
626
- compileExpr(*a.expr, out, scope); // an argument is never tail
627
- site.argNames.push_back(a.name->name);
628
- } else {
629
- auto& a = static_cast<const oscad::PositionalArgument&>(*argPtr);
630
- compileExpr(*a.expr, out, scope);
631
- site.argNames.push_back(std::nullopt);
623
+ // is_undef(name) probes rather than reads -- the same quiet
624
+ // load the interpreter uses (Evaluator::undefProbeTarget),
625
+ // so the two engines agree on which reads warn.
626
+ if (const oscad::Identifier* undefProbe = Evaluator::undefProbeTarget(n)) {
627
+ compileIdentifierLoad(undefProbe->name, undefProbe->position(), out, scope,
628
+ /*warnIfUndef=*/false);
629
+ site.argNames.push_back(std::nullopt);
630
+ } else {
631
+ for (const auto& argPtr : n.arguments) {
632
+ if (argPtr->kind() == NodeKind::NamedArgument) {
633
+ auto& a = static_cast<const oscad::NamedArgument&>(*argPtr);
634
+ compileExpr(*a.expr, out, scope); // an argument is never tail
635
+ site.argNames.push_back(a.name->name);
636
+ } else {
637
+ auto& a = static_cast<const oscad::PositionalArgument&>(*argPtr);
638
+ compileExpr(*a.expr, out, scope);
639
+ site.argNames.push_back(std::nullopt);
640
+ }
632
641
  }
633
642
  }
634
643
  int siteIdx = static_cast<int>(chunk_.callSites.size());
@@ -21,6 +21,34 @@ EvalContext EvalContext::makeRoot(const oscad::Scope* rootScope) {
21
21
  // see undef, take the falsy branch by accident rather than by rule, and
22
22
  // warn about an unknown variable while doing it.
23
23
  ctx.dyn->set("$preview", Value{false});
24
+ // $_BELFRYSCAD -- present only here, so a script can tell which
25
+ // evaluator it is running under. OpenSCAD leaves it undef, which is the
26
+ // whole point: `if ($_BELFRYSCAD == undef)` is the portable test, and it
27
+ // works because an unknown $-variable is undef rather than an error.
28
+ //
29
+ // Shaped like OpenSCAD's own version() -- a [major, minor, patch] list
30
+ // rather than a string -- so comparisons need no string splitting.
31
+ // Which FEATURES exist is a separate question with a better answer:
32
+ // supported_feature() (function_builtins.cpp).
33
+ // $_SUPPORTED_FEATURE -- "supported_feature() is callable here". Set by
34
+ // any implementation that provides the function, not just this one, so a
35
+ // script can check for the FUNCTION rather than for a vendor:
36
+ //
37
+ // if (!is_undef($_SUPPORTED_FEATURE) && supported_feature("sep..."))
38
+ //
39
+ // Solves the bootstrapping problem -- you cannot safely call
40
+ // supported_feature() without first knowing it exists -- and in that
41
+ // exact form it is SILENT in OpenSCAD: is_undef() is the one way to
42
+ // read an unknown variable there without a warning, and && short-
43
+ // circuits so the unknown function is never reached. Verified against
44
+ // the reference binary: no warning, correct branch.
45
+ // If OpenSCAD ever adds supported_feature(), it sets this too and the
46
+ // same script starts working there with no edit.
47
+ ctx.dyn->set("$_SUPPORTED_FEATURE", Value{true});
48
+ ctx.dyn->set("$_BELFRYSCAD",
49
+ Value{std::make_shared<const ValueList>(ValueList{{Value{double{OSCAD_EVAL_VERSION_MAJOR}},
50
+ Value{double{OSCAD_EVAL_VERSION_MINOR}},
51
+ Value{double{OSCAD_EVAL_VERSION_PATCH}}}})});
24
52
  ctx.let_ = TrailView<Value>::makeRoot();
25
53
  ctx.dynPositions = TrailView<const oscad::Position*>::makeRoot();
26
54
  ctx.childrenNodes = std::make_shared<const ChildrenNodeList>();
@@ -107,6 +107,16 @@ void Evaluator::warn(const std::string& message, const oscad::Position* position
107
107
  echoFn_(formatWarning(message, position, callStack_));
108
108
  }
109
109
 
110
+ const oscad::Identifier* Evaluator::undefProbeTarget(const oscad::PrimaryCall& call) {
111
+ if (call.left->kind() != oscad::NodeKind::Identifier) return nullptr;
112
+ if (static_cast<const oscad::Identifier&>(*call.left).name != "is_undef") return nullptr;
113
+ if (call.arguments.size() != 1) return nullptr;
114
+ if (call.arguments[0]->kind() != oscad::NodeKind::PositionalArgument) return nullptr;
115
+ const auto& arg = static_cast<const oscad::PositionalArgument&>(*call.arguments[0]);
116
+ if (arg.expr->kind() != oscad::NodeKind::Identifier) return nullptr;
117
+ return static_cast<const oscad::Identifier*>(arg.expr.get());
118
+ }
119
+
110
120
  Value Evaluator::evalIdentifier(const std::string& name, const oscad::Position* position, EvalContext& ctx,
111
121
  bool warnIfUndef) {
112
122
  if (const Value* v = ctx.let_->find(name)) return *v;
@@ -882,6 +882,16 @@ Value Evaluator::evalFunctionCall(const oscad::PrimaryCall& node, EvalContext& c
882
882
  // exactly (name in _BUILTIN_FN_NAMES gates which branch runs at
883
883
  // all).
884
884
  if (isBuiltinFunctionName(leftId->name)) {
885
+ // is_undef(name) is a probe: asking whether a name is defined
886
+ // must not itself warn that it isn't. The $-branch of
887
+ // evalIdentifier is already silent for any name, but a PLAIN
888
+ // identifier warned -- so `is_undef(some_flag)` was noisy here
889
+ // and silent in the reference. Verified against OpenSCAD:
890
+ // no warning for either spelling inside is_undef().
891
+ if (const oscad::Identifier* probe = undefProbeTarget(node)) {
892
+ return Value{std::holds_alternative<std::monostate>(
893
+ evalIdentifier(probe->name, &probe->position(), ctx, /*warnIfUndef=*/false))};
894
+ }
885
895
  // object() needs the raw argument list, not a pre-resolved
886
896
  // CallArgs, to merge positional/named arguments in their exact
887
897
  // call-site interleaved order (see builtinObject's own
@@ -21,6 +21,7 @@ add_executable(oscad_eval_tests
21
21
  test_render_expr.cpp
22
22
  test_booleans.cpp
23
23
  test_children_separate.cpp
24
+ test_feature_detection.cpp
24
25
  test_control_flow.cpp
25
26
  test_tail_calls.cpp
26
27
  test_function_builtins.cpp
@@ -162,13 +162,13 @@ TEST(BytecodeCompiler, SelfReferentialParameterDefaultResolvesToUndef) {
162
162
  ScopedVm vm(true);
163
163
  // Referencing "x" inside its own default falls all the way through to
164
164
  // Scope::lookupVariable finding nothing usable there (a
165
- // ParameterDeclaration), producing the same "Ignoring unknown
166
- // variable" WARNING the plain interpreter does today (verified against
167
- // the unmodified CLI directly, not assumed) -- this test's own point is
168
- // that the compiled path reproduces that exactly, warning included, not
169
- // that the warning is otherwise desirable.
170
- EXPECT_EQ(runCapturingEcho("function foo(x = is_undef(x) ? 5 : x) = x;\necho(foo());"),
171
- "WARNING: Ignoring unknown variable 'x' in file <string>, line 1\nECHO: 5");
165
+ // ParameterDeclaration) -- undef, so the ternary takes the 5. Silent:
166
+ // is_undef() is a probe and does not warn about the name it asks about,
167
+ // matching the reference, which is silent for this exact script. This
168
+ // test's own point is that the compiled path reproduces the interpreter
169
+ // exactly; it used to assert a warning both engines emitted and the
170
+ // reference did not.
171
+ EXPECT_EQ(runCapturingEcho("function foo(x = is_undef(x) ? 5 : x) = x;\necho(foo());"), "ECHO: 5");
172
172
  // Caller-supplied value: default expression never runs at all, so no
173
173
  // warning either.
174
174
  EXPECT_EQ(runCapturingEcho("function foo(x = is_undef(x) ? 5 : x) = x;\necho(foo(10));"), "ECHO: 10");
@@ -0,0 +1,161 @@
1
+ // $_BELFRYSCAD and supported_feature() -- how a script asks which evaluator
2
+ // it is running under, and what that evaluator can actually do.
3
+ //
4
+ // The hazard they exist for: OpenSCAD does not reject arguments or names it
5
+ // has never heard of. `children(separate=true)` is silently ignored there,
6
+ // so a script using an extension runs and quietly renders something else.
7
+ // Both of these are readable from OpenSCAD too -- an unknown $-variable and
8
+ // an unknown function are undef (with a warning), not errors -- so a guard
9
+ // written against them works in both.
10
+
11
+ #include "openscad_cpp_evaluator/evaluator.hpp"
12
+
13
+ #include "test_helpers.hpp"
14
+
15
+ #include <gtest/gtest.h>
16
+
17
+ using namespace oscadeval;
18
+ using namespace oscadeval::test;
19
+
20
+ namespace {
21
+
22
+ class ScopedVm {
23
+ public:
24
+ explicit ScopedVm(bool enabled) { Evaluator::setBytecodeVmEnabledForTesting(enabled); }
25
+ ~ScopedVm() { Evaluator::setBytecodeVmEnabledForTesting(std::nullopt); }
26
+ };
27
+
28
+ std::vector<std::string> echoesFrom(const std::string& src) {
29
+ std::vector<std::string> out;
30
+ evalSrc(src, [&](const std::string& m) { out.push_back(m); });
31
+ return out;
32
+ }
33
+
34
+ // Every feature name the docs promise. A typo in either direction -- here or
35
+ // in featureLevels() -- shows up as a 0.
36
+ const char* kFeatures[] = {"separate-children", "minkowski-diff", "sphere-styles", "export-name",
37
+ "simplify-op", "expr-import", "object-function", "roof-op"};
38
+
39
+ } // namespace
40
+
41
+ TEST(FeatureDetection, BelfryscadVariableIsTheEvaluatorVersion) {
42
+ for (bool vm : {false, true}) {
43
+ ScopedVm guard(vm);
44
+ // Shaped like OpenSCAD's own version(): [major, minor, patch], so a
45
+ // comparison needs no string splitting. Read from pyproject.toml at
46
+ // configure time, so this cannot drift from the released version.
47
+ const std::vector<std::string> e = echoesFrom("echo(is_list($_BELFRYSCAD), len($_BELFRYSCAD));");
48
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
49
+ EXPECT_NE(e[0].find("true"), std::string::npos) << "vm=" << vm << ", got " << e[0];
50
+ EXPECT_NE(e[0].find("3"), std::string::npos) << "vm=" << vm << ", got " << e[0];
51
+ }
52
+ }
53
+
54
+ TEST(FeatureDetection, BelfryscadVariableHoldsThreeNumbers) {
55
+ for (bool vm : {false, true}) {
56
+ ScopedVm guard(vm);
57
+ const std::vector<std::string> e = echoesFrom(
58
+ "echo(is_num($_BELFRYSCAD[0]), is_num($_BELFRYSCAD[1]), is_num($_BELFRYSCAD[2]));");
59
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
60
+ EXPECT_EQ(e[0].find("false"), std::string::npos) << "vm=" << vm << ", got " << e[0];
61
+ }
62
+ }
63
+
64
+ TEST(FeatureDetection, EveryDocumentedFeatureReportsALevel) {
65
+ for (bool vm : {false, true}) {
66
+ ScopedVm guard(vm);
67
+ for (const char* f : kFeatures) {
68
+ const std::vector<std::string> e =
69
+ echoesFrom(std::string("echo(supported_feature(\"") + f + "\"));");
70
+ ASSERT_EQ(e.size(), 1u) << f << ", vm=" << vm;
71
+ EXPECT_NE(e[0].find("1"), std::string::npos) << f << ", vm=" << vm << ", got " << e[0];
72
+ EXPECT_EQ(e[0].find("0"), std::string::npos) << f << " reported unsupported, vm=" << vm;
73
+ }
74
+ }
75
+ }
76
+
77
+ TEST(FeatureDetection, AnUnknownFeatureIsZeroAndSilent) {
78
+ // Probing for a feature from a future release has to be safe -- that is
79
+ // the whole point of asking. No warning, no error, just 0.
80
+ for (bool vm : {false, true}) {
81
+ ScopedVm guard(vm);
82
+ const std::vector<std::string> e = echoesFrom("echo(supported_feature(\"time-travel\"));");
83
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm << " (unexpected warning?)";
84
+ EXPECT_NE(e[0].find("0"), std::string::npos) << "vm=" << vm << ", got " << e[0];
85
+ }
86
+ }
87
+
88
+ TEST(FeatureDetection, ANonStringArgumentIsZeroRatherThanAnError) {
89
+ for (bool vm : {false, true}) {
90
+ ScopedVm guard(vm);
91
+ for (const char* arg : {"42", "undef", "[1,2]"}) {
92
+ const std::vector<std::string> e =
93
+ echoesFrom(std::string("echo(supported_feature(") + arg + "));");
94
+ ASSERT_EQ(e.size(), 1u) << arg << ", vm=" << vm;
95
+ EXPECT_NE(e[0].find("0"), std::string::npos) << arg << ", vm=" << vm << ", got " << e[0];
96
+ }
97
+ }
98
+ }
99
+
100
+ TEST(FeatureDetection, SupportedFeatureMarkerIsSet) {
101
+ // Vendor-neutral: it says "supported_feature() is callable", not "this is
102
+ // BelfrySCAD". Any implementation adding the function is meant to set it,
103
+ // so a script guards on the capability rather than on who is running it.
104
+ for (bool vm : {false, true}) {
105
+ ScopedVm guard(vm);
106
+ const std::vector<std::string> e = echoesFrom("echo($_SUPPORTED_FEATURE);");
107
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
108
+ EXPECT_NE(e[0].find("true"), std::string::npos) << "vm=" << vm << ", got " << e[0];
109
+ }
110
+ }
111
+
112
+ TEST(FeatureDetection, TheMarkerGuardsTheCallItself) {
113
+ // The bootstrapping shape: you cannot safely CALL supported_feature()
114
+ // without first knowing it exists. Checking the marker first solves that,
115
+ // and because && short-circuits, an evaluator lacking both warns once
116
+ // rather than twice -- verified against the reference binary, which
117
+ // reports exactly one warning for this source and takes the else branch.
118
+ for (bool vm : {false, true}) {
119
+ ScopedVm guard(vm);
120
+ const std::vector<std::string> e = echoesFrom(
121
+ "if (!is_undef($_SUPPORTED_FEATURE) && supported_feature(\"separate-children\"))\n"
122
+ " echo(\"extended\");\n"
123
+ "else echo(\"portable\");\n");
124
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
125
+ EXPECT_NE(e[0].find("extended"), std::string::npos) << "vm=" << vm << ", got " << e[0];
126
+ }
127
+ }
128
+
129
+ TEST(FeatureDetection, IsUndefDoesNotWarnAboutTheNameItProbes) {
130
+ // Asking whether a name is defined must not itself complain that it
131
+ // isn't -- otherwise the portable guard is noisy here while being
132
+ // silent in OpenSCAD. Both spellings, since only the $-prefixed one
133
+ // used to be quiet.
134
+ for (bool vm : {false, true}) {
135
+ ScopedVm guard(vm);
136
+ for (const char* name : {"$_NO_SUCH_DOLLAR", "no_such_plain"}) {
137
+ const std::vector<std::string> e = echoesFrom(std::string("echo(is_undef(") + name + "));");
138
+ ASSERT_EQ(e.size(), 1u) << name << ", vm=" << vm << " (warned?)";
139
+ EXPECT_NE(e[0].find("true"), std::string::npos) << name << ", vm=" << vm << ", got " << e[0];
140
+ }
141
+ // A plain read of an unknown name still warns -- only the probe is
142
+ // exempt, and only for its own argument.
143
+ const std::vector<std::string> e = echoesFrom("echo(no_such_plain);");
144
+ ASSERT_EQ(e.size(), 2u) << "vm=" << vm;
145
+ EXPECT_NE(e[0].find("Ignoring unknown variable"), std::string::npos) << "vm=" << vm;
146
+ }
147
+ }
148
+
149
+ TEST(FeatureDetection, TheGuardIdiomReadsTrueHere) {
150
+ // What a script actually writes. In OpenSCAD $_BELFRYSCAD is undef and
151
+ // supported_feature() is an unknown function returning undef -- both
152
+ // falsy -- so the same source takes the other branch there.
153
+ for (bool vm : {false, true}) {
154
+ ScopedVm guard(vm);
155
+ const std::vector<std::string> e = echoesFrom(
156
+ "if ($_BELFRYSCAD == undef) echo(\"portable\");\n"
157
+ "else if (supported_feature(\"separate-children\")) echo(\"extended\");\n");
158
+ ASSERT_EQ(e.size(), 1u) << "vm=" << vm;
159
+ EXPECT_NE(e[0].find("extended"), std::string::npos) << "vm=" << vm << ", got " << e[0];
160
+ }
161
+ }
@@ -52,11 +52,14 @@ TEST(UseStatement, InjectsFunctionAndModuleButNotVariables) {
52
52
  std::vector<std::string> echoed;
53
53
  UseEvaluated e = evalFile(main, [&](const std::string& m) { echoed.push_back(m); });
54
54
 
55
- ASSERT_EQ(echoed.size(), 3u);
55
+ // Two, not three: is_undef() is a probe and does not warn about the name
56
+ // it is asking about -- matching the reference, which is silent for this
57
+ // exact script. `ECHO: true` is what proves lib_var wasn't injected; the
58
+ // warning this used to also assert was our own divergence, not the point.
59
+ ASSERT_EQ(echoed.size(), 2u);
56
60
  EXPECT_EQ(echoed[0], "ECHO: 103"); // 1 + 2 + lib_var(100) -- proves lib_add's body resolves
57
61
  // lib_var from *its own* file's scope (reanchoring)
58
- EXPECT_NE(echoed[1].find("WARNING: Ignoring unknown variable 'lib_var'"), std::string::npos); // NOT injected
59
- EXPECT_EQ(echoed[2], "ECHO: true"); // is_undef(lib_var) in main's own scope
62
+ EXPECT_EQ(echoed[1], "ECHO: true"); // is_undef(lib_var) in main's own scope -- NOT injected
60
63
  EXPECT_EQ(e.bodies.size(), 1u); // lib_cube(5) still produced geometry
61
64
  std::filesystem::remove(lib);
62
65
  std::filesystem::remove(main);