sutra-dev 0.9.3__tar.gz → 0.9.4__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.
- {sutra_dev-0.9.3/sutra_dev.egg-info → sutra_dev-0.9.4}/PKG-INFO +1 -1
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/pyproject.toml +1 -1
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/__init__.py +1 -1
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/codegen.py +12 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/codegen_base.py +30 -2
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/codegen_pytorch.py +40 -4
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/repl.py +45 -1
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/logic.su +12 -8
- {sutra_dev-0.9.3 → sutra_dev-0.9.4/sutra_dev.egg-info}/PKG-INFO +1 -1
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_dev.egg-info/SOURCES.txt +2 -0
- sutra_dev-0.9.4/tests/test_le_ge_ties.py +59 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_no_host_readout.py +3 -1
- sutra_dev-0.9.4/tests/test_num_eq_indicator.py +96 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_repl.py +16 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_string_plus_concat.py +18 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/LICENSE +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/README.md +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/setup.cfg +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/__main__.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/ast_nodes.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/axon_keys.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/cached_compile.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/codegen_thrml.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/diagnostics.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/embedding.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/fv.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/fv_key_soundness.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/fv_loop_convergence.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/fv_obligation_checker.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/fv_poly_bound.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/fv_sampler_convergence.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/inliner.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/lexer.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/loop_capture.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/loop_desugar.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/multi_process.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/parser.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/preeval.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/promise_desugar.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/review.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/simplify.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/simplify_egglog.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/axons.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/bigint.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/bitwise.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/embed.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/javascript_object.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/javascript_primitives.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/math.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/memory.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/modulus.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/numbers.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/promises.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/rotation.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/similarity.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/strings.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/tensor.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib/vectors.su +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/stdlib_loader.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/sutradb_embedded.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/symbol_table.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/tabulate.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/trace.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/validator.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_compiler/workspace.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_dev.egg-info/dependency_links.txt +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_dev.egg-info/entry_points.txt +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_dev.egg-info/requires.txt +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/sutra_dev.egg-info/top_level.txt +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_arity_diagnostic.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_arrow_functions.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_await_midfunction.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_await_substrate_pure.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_axon_build.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_axon_device_coherence.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_axon_item_call_result.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_axon_keys.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_axon_project.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_branchless_loop.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_buffer_transcendentals.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_cached_compile.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_cast_codegen.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_class_fields_runtime.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_codegen.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_codegen_pytorch.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_codegen_thrml.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_corpus.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_dimension_audit.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_dimension_audit_warning.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_embedding_provider.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_equality_autograd.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fused_nn.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_general_checker.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_key_soundness.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_kleene_grid_exactness.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_loop_convergence.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_poly_obligation_checker.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_public_api.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_sampler_convergence.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_termination.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_fv_worked_example.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_higher_order_functions.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_implicit_loop_desugar.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_inliner.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_int_dict.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_int_to_string.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_interp_string_codegen.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_jvm_core.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_lexer.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_list_ops.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_load_matrix.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_loop_capture.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_loop_function_decl.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_make_string_idempotent.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_matrix_literal.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_measurement_claim_sweep.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_mini_wasm_machine.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_multi_process_runtime.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_native_recursion.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_ntm_ram.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_optional_llm_model.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_parser.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_preeval.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_process_pool_runtime.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_python_builtin_escape.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_ram_direct_tensor.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_rotation_prewarm.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_run_error_diagnostics.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_simplify.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_simplify_egglog.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_simplify_egglog_cse.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_snap_diagnostic.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_stdlib_loader.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_string_equality.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_substrate_leak_sweep.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_sutradb_embedded.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_symbol_table.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_tabulate.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_terminal_string_decode.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_torch_compile_wrap.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_trainable_matrix.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_transcendentals.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_type_inference.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_type_test_gap.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_unknown_function_diagnostic.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_unknown_type_diagnostic.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_unknown_variable_diagnostic.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_unsafe_override_codegen.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_vector_literal.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_wasm_core.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_workspace.py +0 -0
- {sutra_dev-0.9.3 → sutra_dev-0.9.4}/tests/test_wrong_arg_type_diagnostic.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sutra-dev"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.4"
|
|
8
8
|
description = "Compiler for the Sutra programming language — tensor ops on frozen-LLM embedding vectors."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -28,7 +28,7 @@ The compiler is intentionally liberal where the spec is still open
|
|
|
28
28
|
forms and flags the clearly-forbidden ones.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
__version__ = "0.9.
|
|
31
|
+
__version__ = "0.9.4"
|
|
32
32
|
|
|
33
33
|
from .diagnostics import Diagnostic, DiagnosticLevel, DiagnosticBag
|
|
34
34
|
from .lexer import Lexer, Token, TokenKind
|
|
@@ -246,6 +246,18 @@ class Codegen(BaseCodegen):
|
|
|
246
246
|
meaningful signal there.
|
|
247
247
|
"""
|
|
248
248
|
assert op in ("eq", "neq")
|
|
249
|
+
# Number-family operands (int/number, incl. arithmetic results
|
|
250
|
+
# like `n % 3`) route through the exact relu indicator (Emma
|
|
251
|
+
# 2026-07-08): cosine is degenerate at the zero vector, so a
|
|
252
|
+
# runtime zero could never == anything — zero-testing was
|
|
253
|
+
# unreachable. Torch-runtime only (the deprecated numpy backend
|
|
254
|
+
# keeps its old routing).
|
|
255
|
+
if (getattr(self, "supports_cast_lowering", False)
|
|
256
|
+
and self._CAST_FAMILY.get(
|
|
257
|
+
self._infer_cast_operand_type(expr.left)) == "number"
|
|
258
|
+
and self._CAST_FAMILY.get(
|
|
259
|
+
self._infer_cast_operand_type(expr.right)) == "number"):
|
|
260
|
+
return f"_VSA.num_{op}({left_src}, {right_src})"
|
|
249
261
|
if (self._is_synthetic_axis_expr(expr.left)
|
|
250
262
|
and self._is_synthetic_axis_expr(expr.right)):
|
|
251
263
|
return f"_VSA.{op}_synthetic({left_src}, {right_src})"
|
|
@@ -702,6 +702,19 @@ class BaseCodegen:
|
|
|
702
702
|
def translate(self, module: ast.Module) -> str:
|
|
703
703
|
self._emit_prelude()
|
|
704
704
|
self._emit()
|
|
705
|
+
# Pre-pass 0: record every top-level user function's declared
|
|
706
|
+
# return type, so expression-type checks (e.g. the text-concat
|
|
707
|
+
# dispatch asking "does f(x) return a string?") can resolve
|
|
708
|
+
# FORWARD references — a call site is often translated before
|
|
709
|
+
# its callee. _resolved_return_type only knows class methods and
|
|
710
|
+
# the stdlib; without this map, `f(1) + " "` with a user
|
|
711
|
+
# `function string f` fell to the numeric path and crashed at
|
|
712
|
+
# runtime (round-23 audit).
|
|
713
|
+
self._user_fn_return_types: dict[str, str] = {}
|
|
714
|
+
for item in module.items:
|
|
715
|
+
if (isinstance(item, ast.FunctionDecl)
|
|
716
|
+
and item.return_type is not None):
|
|
717
|
+
self._user_fn_return_types[item.name] = item.return_type.name
|
|
705
718
|
# Pre-pass A: pull in stdlib class intrinsics (e.g.
|
|
706
719
|
# `Tensor.MatrixMul`, `Tensor.matmul`, etc.) so namespaced
|
|
707
720
|
# stdlib calls dispatch to `_VSA.<name>` even though the
|
|
@@ -2634,10 +2647,21 @@ class BaseCodegen:
|
|
|
2634
2647
|
f"unroll by hand.",
|
|
2635
2648
|
)
|
|
2636
2649
|
if isinstance(stmt, ast.IfStmt):
|
|
2650
|
+
# Design commitment, not a gap (control-flow.md § "`if` /
|
|
2651
|
+
# `else` is parsed but rejected at codegen"): Sutra
|
|
2652
|
+
# conditionals are weighted superpositions, not discrete
|
|
2653
|
+
# branches. The message must hand the newcomer the actual
|
|
2654
|
+
# rewrite, not just the philosophy (round-23 audit).
|
|
2637
2655
|
raise CodegenNotSupported(
|
|
2638
2656
|
stmt,
|
|
2639
|
-
"if
|
|
2640
|
-
"
|
|
2657
|
+
"`if`/`else` never compiles — by design, Sutra "
|
|
2658
|
+
"conditionals are weighted superpositions, not discrete "
|
|
2659
|
+
"branches. Write `select([score_a, score_b, ...], "
|
|
2660
|
+
"[result_a, result_b, ...])`: the highest score's result "
|
|
2661
|
+
"dominates the output. A boolean two-way branch is "
|
|
2662
|
+
"`select([is_true(cond), is_true(!cond)], [then_value, "
|
|
2663
|
+
"else_value])`. See the control-flow docs and "
|
|
2664
|
+
"examples/fuzzy_dispatch.su for the idiom.",
|
|
2641
2665
|
)
|
|
2642
2666
|
if isinstance(stmt, ast.TryStmt):
|
|
2643
2667
|
self._translate_try_catch(stmt)
|
|
@@ -3042,6 +3066,10 @@ class BaseCodegen:
|
|
|
3042
3066
|
return (self._is_text_expr(expr.left)
|
|
3043
3067
|
and self._is_text_expr(expr.right))
|
|
3044
3068
|
if isinstance(expr, ast.Call):
|
|
3069
|
+
if (isinstance(expr.callee, ast.Identifier)
|
|
3070
|
+
and getattr(self, "_user_fn_return_types", {}).get(
|
|
3071
|
+
expr.callee.name) in self._TEXT_TYPES):
|
|
3072
|
+
return True
|
|
3045
3073
|
return self._resolved_return_type(expr.callee) in self._TEXT_TYPES
|
|
3046
3074
|
return False
|
|
3047
3075
|
|
|
@@ -2910,12 +2910,17 @@ class PyTorchCodegen(Codegen):
|
|
|
2910
2910
|
self._emit('was a host `for k in range`, `.item()`, host `if`, host')
|
|
2911
2911
|
self._emit('`return k+1`). Gather the codepoint block, mark non-zero')
|
|
2912
2912
|
self._emit('positions, take the highest 1-based position that is')
|
|
2913
|
-
self._emit('non-zero: length = max((k+1) where cps[k]
|
|
2913
|
+
self._emit('non-zero: length = max((k+1) where cps[k] >= 0.5). All tensor')
|
|
2914
2914
|
self._emit('ops; 0-d tensor out. Trailing-zero-as-sentinel preserved')
|
|
2915
|
-
self._emit('(a 0 codepoint in the tail reads shorter, same as before).
|
|
2915
|
+
self._emit('(a 0 codepoint in the tail reads shorter, same as before).')
|
|
2916
|
+
self._emit('The 0.5 threshold (was `!= 0`) makes decode robust to')
|
|
2917
|
+
self._emit('superposition residue: a String coming out of select() carries')
|
|
2918
|
+
self._emit('~1e-9 leakage from losing branches on higher codepoint axes,')
|
|
2919
|
+
self._emit('which the exact-nonzero walk counted as characters (phantom')
|
|
2920
|
+
self._emit('NULs). Real codepoints are >= 32; residue is << 1."""')
|
|
2916
2921
|
self._emit("ax = self._str_axes()")
|
|
2917
2922
|
self._emit("cps = v.index_select(0, ax)")
|
|
2918
|
-
self._emit("nz = (cps
|
|
2923
|
+
self._emit("nz = (cps.abs() >= 0.5).to(self.dtype)")
|
|
2919
2924
|
self._emit("pos = _torch.arange(1, ax.shape[0] + 1, dtype=self.dtype, device=self.device)")
|
|
2920
2925
|
self._emit("return (pos * nz).max()")
|
|
2921
2926
|
self._indent -= 1
|
|
@@ -3334,7 +3339,10 @@ class PyTorchCodegen(Codegen):
|
|
|
3334
3339
|
self._emit("for i in range(n):")
|
|
3335
3340
|
self._indent += 1
|
|
3336
3341
|
self._emit("axis = self._string_axis(i)")
|
|
3337
|
-
self._emit("
|
|
3342
|
+
self._emit("# round(), not int(): a select() superposition leaves the")
|
|
3343
|
+
self._emit("# winner's codepoints at ~(1-eps)*cp — int() truncated 101.9997")
|
|
3344
|
+
self._emit("# to 101 and garbled every decoded character by one.")
|
|
3345
|
+
self._emit("chars.append(chr(round(v[self.semantic_dim + axis].item())))")
|
|
3338
3346
|
self._indent -= 1
|
|
3339
3347
|
self._emit('return "".join(chars)')
|
|
3340
3348
|
self._indent -= 1
|
|
@@ -3594,6 +3602,34 @@ class PyTorchCodegen(Codegen):
|
|
|
3594
3602
|
self._emit("return out")
|
|
3595
3603
|
self._indent -= 1
|
|
3596
3604
|
self._emit()
|
|
3605
|
+
# Number-family equality — the exact relu indicator (Emma
|
|
3606
|
+
# 2026-07-08). Cosine eq is DEGENERATE at the zero vector
|
|
3607
|
+
# (cos(0,v) is 0/0 → a runtime zero can never equal anything,
|
|
3608
|
+
# including zero), which made zero-testing unreachable — see
|
|
3609
|
+
# planning/findings/2026-07-08-zero-equality-reads-neutral-
|
|
3610
|
+
# cosine-degenerate.md.
|
|
3611
|
+
self._emit("def num_eq(self, a, b):")
|
|
3612
|
+
self._indent += 1
|
|
3613
|
+
self._emit('"""Number == number via the exact indicator:')
|
|
3614
|
+
self._emit('truth = 2*relu(1 - |x - y|) - 1 — +1 at equal, -1 at')
|
|
3615
|
+
self._emit('|diff| >= 1 (exact gap 2.0 at integer spacing, no residual;')
|
|
3616
|
+
self._emit('the neural-Unix keystone on the truth convention). Fractional')
|
|
3617
|
+
self._emit('nearness interpolates linearly (|d|=0.5 -> 0, neutral). The')
|
|
3618
|
+
self._emit('unit width is a trainable-surface candidate (a gain k on |d|),')
|
|
3619
|
+
self._emit('fixed at 1 today. All tensor ops; differentiable a.e."""')
|
|
3620
|
+
self._emit("d = _torch.abs(self._scalar(a) - self._scalar(b))")
|
|
3621
|
+
self._emit("truth = 2.0 * _torch.clamp(1.0 - d, min=0.0) - 1.0")
|
|
3622
|
+
self._emit("out = _torch.zeros(self.dim, dtype=self.dtype, device=self.device)")
|
|
3623
|
+
self._emit("out[self.semantic_dim + self.AXIS_TRUTH] = truth")
|
|
3624
|
+
self._emit("return out")
|
|
3625
|
+
self._indent -= 1
|
|
3626
|
+
self._emit()
|
|
3627
|
+
self._emit("def num_neq(self, a, b):")
|
|
3628
|
+
self._indent += 1
|
|
3629
|
+
self._emit('"""!= for number-family values — truth-axis negation of num_eq."""')
|
|
3630
|
+
self._emit("return -self.num_eq(a, b)")
|
|
3631
|
+
self._indent -= 1
|
|
3632
|
+
self._emit()
|
|
3597
3633
|
# Synthetic-axis equality — Euclidean distance + tanh
|
|
3598
3634
|
# (2026-05-08 directive). For int / float / complex / char /
|
|
3599
3635
|
# string operands; cosine doesn't distinguish well between
|
|
@@ -45,7 +45,7 @@ _EVAL_WRAP_TYPES = {"string"}
|
|
|
45
45
|
_BANNER = (
|
|
46
46
|
"Sutra REPL - type an expression to evaluate it; end a line with ';' or '}' "
|
|
47
47
|
"to add a declaration.\n"
|
|
48
|
-
"Commands: :help :decls :reset :quit\n"
|
|
48
|
+
"Commands: :help :ops :decls :reset :quit\n"
|
|
49
49
|
)
|
|
50
50
|
_HELP = (
|
|
51
51
|
"Sutra REPL help\n"
|
|
@@ -53,12 +53,54 @@ _HELP = (
|
|
|
53
53
|
" (number -> real value; concept -> nearest known string)\n"
|
|
54
54
|
" <stmt>; add a declaration/statement to the session (e.g. vector k = embed(\"king\");)\n"
|
|
55
55
|
" function ... { } add a function to the session\n"
|
|
56
|
+
" :ops list the callable operations (builtins, stdlib, special forms)\n"
|
|
56
57
|
" :decls print the accumulated session declarations\n"
|
|
57
58
|
" :reset clear all session declarations\n"
|
|
58
59
|
" :help this help\n"
|
|
59
60
|
" :quit / :q leave the REPL (Ctrl-D also works)\n"
|
|
60
61
|
)
|
|
61
62
|
|
|
63
|
+
|
|
64
|
+
def _ops_listing() -> str:
|
|
65
|
+
"""The `:ops` discovery surface: every operation callable from REPL
|
|
66
|
+
source, grouped. Built from the live dispatch tables (BUILTINS + the
|
|
67
|
+
stdlib intrinsic registry) so it cannot drift from what actually
|
|
68
|
+
resolves — a hand-written list here would rot."""
|
|
69
|
+
from .codegen_base import BUILTINS
|
|
70
|
+
try:
|
|
71
|
+
from .stdlib_loader import stdlib_class_intrinsic_methods
|
|
72
|
+
stdlib = stdlib_class_intrinsic_methods()
|
|
73
|
+
except Exception:
|
|
74
|
+
stdlib = {}
|
|
75
|
+
|
|
76
|
+
def _columns(names, indent=" ", width=76):
|
|
77
|
+
lines, cur = [], indent
|
|
78
|
+
for n in names:
|
|
79
|
+
if len(cur) + len(n) + 2 > width and cur.strip():
|
|
80
|
+
lines.append(cur.rstrip())
|
|
81
|
+
cur = indent
|
|
82
|
+
cur += n + " "
|
|
83
|
+
if cur.strip():
|
|
84
|
+
lines.append(cur.rstrip())
|
|
85
|
+
return "\n".join(lines)
|
|
86
|
+
|
|
87
|
+
parts = ["Operations callable from Sutra source in this REPL\n"]
|
|
88
|
+
parts.append(" builtins:\n" + _columns(sorted(BUILTINS.keys())) + "\n")
|
|
89
|
+
for cls in sorted(stdlib):
|
|
90
|
+
methods = sorted(stdlib[cls])
|
|
91
|
+
if not methods:
|
|
92
|
+
continue
|
|
93
|
+
parts.append(f" {cls} (call bare or as {cls}.<name>):\n"
|
|
94
|
+
+ _columns(methods) + "\n")
|
|
95
|
+
parts.append(
|
|
96
|
+
" special forms:\n"
|
|
97
|
+
" embed(\"...\") defuzzy(v) unsafeCast<T>(v) unsafeOverride(v)\n"
|
|
98
|
+
" (Type) expr casts $\"text {interpolant}\" interpolation\n"
|
|
99
|
+
)
|
|
100
|
+
parts.append(
|
|
101
|
+
" full reference: https://sutra.topazcomputing.com/capabilities/\n")
|
|
102
|
+
return "".join(parts)
|
|
103
|
+
|
|
62
104
|
_EVAL_FN = "__repl_eval__"
|
|
63
105
|
|
|
64
106
|
|
|
@@ -238,6 +280,8 @@ def run_repl(
|
|
|
238
280
|
break
|
|
239
281
|
if stripped == ":help":
|
|
240
282
|
out.write(_HELP); out.flush(); continue
|
|
283
|
+
if stripped == ":ops":
|
|
284
|
+
out.write(_ops_listing()); out.flush(); continue
|
|
241
285
|
if stripped == ":reset":
|
|
242
286
|
decls.clear(); out.write("(session cleared)\n"); out.flush(); continue
|
|
243
287
|
if stripped == ":decls":
|
|
@@ -129,16 +129,20 @@ function fuzzy lt(complex a, complex b) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
// ge / le —
|
|
133
|
-
// to
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
// ge / le — or(strict, ==) per Emma 2026-07-08 (the previous
|
|
133
|
+
// collapse-to-strict gave tanh(0) = 0 on exact ties, so the standard
|
|
134
|
+
// loop guard `i <= n` read NEUTRAL at the boundary — finding
|
|
135
|
+
// 2026-07-06-le-ge-tie-returns-neutral). The params are number-typed
|
|
136
|
+
// so the `==` component dispatches to the exact num_eq indicator:
|
|
137
|
+
// ties read +1, and the false directions stay exactly -1
|
|
138
|
+
// (max(-1, -1)) instead of softening through cosine. `||` is the
|
|
139
|
+
// truth-axis max.
|
|
140
|
+
function fuzzy ge(number a, number b) {
|
|
141
|
+
return (a > b) || (a == b);
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
function fuzzy le(
|
|
141
|
-
return a < b;
|
|
144
|
+
function fuzzy le(number a, number b) {
|
|
145
|
+
return (a < b) || (a == b);
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
|
|
@@ -99,6 +99,7 @@ tests/test_int_dict.py
|
|
|
99
99
|
tests/test_int_to_string.py
|
|
100
100
|
tests/test_interp_string_codegen.py
|
|
101
101
|
tests/test_jvm_core.py
|
|
102
|
+
tests/test_le_ge_ties.py
|
|
102
103
|
tests/test_lexer.py
|
|
103
104
|
tests/test_list_ops.py
|
|
104
105
|
tests/test_load_matrix.py
|
|
@@ -112,6 +113,7 @@ tests/test_multi_process_runtime.py
|
|
|
112
113
|
tests/test_native_recursion.py
|
|
113
114
|
tests/test_no_host_readout.py
|
|
114
115
|
tests/test_ntm_ram.py
|
|
116
|
+
tests/test_num_eq_indicator.py
|
|
115
117
|
tests/test_optional_llm_model.py
|
|
116
118
|
tests/test_parser.py
|
|
117
119
|
tests/test_preeval.py
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""`<=` / `>=` at exact ties (Emma decided 2026-07-08: adopt or(<, ==)).
|
|
2
|
+
|
|
3
|
+
Before: stdlib le/ge collapsed to the strict ops — `2 <= 2` read
|
|
4
|
+
tanh(0) = 0 (NEUTRAL), so the standard loop guard `i <= n` misbehaved
|
|
5
|
+
at the boundary. Now `le = or(<, ==)` / `ge = or(>, ==)` with the ==
|
|
6
|
+
component on the exact num_eq indicator: ties read +1, and both
|
|
7
|
+
false directions stay exactly -1 (max(-1, -1)).
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
from sutra_compiler.lexer import Lexer
|
|
14
|
+
from sutra_compiler.parser import Parser
|
|
15
|
+
from sutra_compiler.codegen_pytorch import translate_module
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _truth(expr: str, dim: int = 16) -> float:
|
|
19
|
+
src = f"function fuzzy main(){{ int x = 2; int y = 2; return {expr}; }}"
|
|
20
|
+
lx = Lexer(src, file="<t>")
|
|
21
|
+
ast = Parser(lx.tokenize(), file="<t>", diagnostics=lx.diagnostics).parse_module()
|
|
22
|
+
assert not lx.diagnostics.has_errors(), list(lx.diagnostics)
|
|
23
|
+
ns: dict = {}
|
|
24
|
+
exec(translate_module(ast, llm_model="none", runtime_dim=dim), ns)
|
|
25
|
+
r = ns["main"]()
|
|
26
|
+
v = ns["_VSA"]
|
|
27
|
+
if not hasattr(r, "ndim") or r.ndim == 0:
|
|
28
|
+
return float(r)
|
|
29
|
+
return float(r[v.semantic_dim + v.AXIS_TRUTH])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestTies(unittest.TestCase):
|
|
33
|
+
def test_le_tie_true(self):
|
|
34
|
+
self.assertAlmostEqual(_truth("2 <= 2"), 1.0, places=4)
|
|
35
|
+
|
|
36
|
+
def test_ge_tie_true(self):
|
|
37
|
+
self.assertAlmostEqual(_truth("2 >= 2"), 1.0, places=4)
|
|
38
|
+
|
|
39
|
+
def test_var_tie(self):
|
|
40
|
+
self.assertAlmostEqual(_truth("x <= y"), 1.0, places=4)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestStrictUnchanged(unittest.TestCase):
|
|
44
|
+
def test_le_true_direction(self):
|
|
45
|
+
self.assertAlmostEqual(_truth("2 <= 3"), 1.0, places=4)
|
|
46
|
+
|
|
47
|
+
def test_le_false_direction_stays_crisp(self):
|
|
48
|
+
self.assertAlmostEqual(_truth("3 <= 2"), -1.0, places=4)
|
|
49
|
+
|
|
50
|
+
def test_ge_false_direction_stays_crisp(self):
|
|
51
|
+
self.assertAlmostEqual(_truth("2 >= 3"), -1.0, places=4)
|
|
52
|
+
|
|
53
|
+
def test_strict_lt_tie_still_neutral(self):
|
|
54
|
+
# strict < at a tie is tanh(0) = 0 — deliberately unchanged.
|
|
55
|
+
self.assertAlmostEqual(_truth("2 < 2"), 0.0, places=4)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
unittest.main()
|
|
@@ -32,7 +32,9 @@ import pytest
|
|
|
32
32
|
# - _js_str_cmp — JS-interop carve-out (host string comparison).
|
|
33
33
|
# GOAL: 0 for genuine introspection; the above are sanctioned I/O/JS/control
|
|
34
34
|
# boundaries. Only ever lower this.
|
|
35
|
-
|
|
35
|
+
# 18 -> 17 on 2026-07-08: retiring the dead is_char alias removed its
|
|
36
|
+
# .item() flag-read (round-19). Progress toward 0.
|
|
37
|
+
BASELINE_ITEM_READOUTS = 17
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
def _generated_runtime_source() -> str:
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Number-family `==` via the exact relu indicator (Emma 2026-07-08).
|
|
2
|
+
|
|
3
|
+
Cosine `==` is degenerate at the zero vector — `(15 % 3) == 0` read
|
|
4
|
+
NEUTRAL, making zero-testing (divisibility, emptiness, termination)
|
|
5
|
+
unreachable (finding 2026-07-08-zero-equality-reads-neutral...).
|
|
6
|
+
Emma's pick: number-family operands route through
|
|
7
|
+
`truth = 2·relu(1 − |x−y|) − 1` — +1 at equal, −1 at |diff| ≥ 1,
|
|
8
|
+
exact gap 2.0 at integer spacing, all tensor ops, differentiable a.e.
|
|
9
|
+
Cosine `==` stays for vector/semantic operands; the Euclidean
|
|
10
|
+
`eq_synthetic` stays for string/char/complex.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
from sutra_compiler.lexer import Lexer
|
|
17
|
+
from sutra_compiler.parser import Parser
|
|
18
|
+
from sutra_compiler.codegen_pytorch import translate_module
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _truth(src: str, dim: int = 16) -> float:
|
|
22
|
+
lx = Lexer(src, file="<t>")
|
|
23
|
+
ast = Parser(lx.tokenize(), file="<t>", diagnostics=lx.diagnostics).parse_module()
|
|
24
|
+
assert not lx.diagnostics.has_errors(), list(lx.diagnostics)
|
|
25
|
+
ns: dict = {}
|
|
26
|
+
exec(translate_module(ast, llm_model="none", runtime_dim=dim), ns)
|
|
27
|
+
r = ns["main"]()
|
|
28
|
+
v = ns["_VSA"]
|
|
29
|
+
if not hasattr(r, "ndim") or r.ndim == 0:
|
|
30
|
+
return float(r)
|
|
31
|
+
return float(r[v.semantic_dim + v.AXIS_TRUTH])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestZeroEquality(unittest.TestCase):
|
|
35
|
+
def test_mod_zero_is_true(self):
|
|
36
|
+
# THE finding case: was 0.0 (neutral), must be +1.
|
|
37
|
+
self.assertAlmostEqual(_truth(
|
|
38
|
+
"function fuzzy main(){ return (15 % 3) == 0; }"), 1.0, places=4)
|
|
39
|
+
|
|
40
|
+
def test_mod_nonzero_is_false(self):
|
|
41
|
+
self.assertAlmostEqual(_truth(
|
|
42
|
+
"function fuzzy main(){ return (16 % 3) == 0; }"), -1.0, places=4)
|
|
43
|
+
|
|
44
|
+
def test_runtime_zero_vars(self):
|
|
45
|
+
self.assertAlmostEqual(_truth(
|
|
46
|
+
"function fuzzy main(){ number x = make_real(0.0); "
|
|
47
|
+
"return x == 0; }"), 1.0, places=4)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestIntegerEquality(unittest.TestCase):
|
|
51
|
+
def test_equal_ints(self):
|
|
52
|
+
self.assertAlmostEqual(_truth(
|
|
53
|
+
"function fuzzy main(){ int a = 2; int b = 2; return a == b; }"),
|
|
54
|
+
1.0, places=4)
|
|
55
|
+
|
|
56
|
+
def test_unequal_ints(self):
|
|
57
|
+
self.assertAlmostEqual(_truth(
|
|
58
|
+
"function fuzzy main(){ int a = 2; int b = 3; return a == b; }"),
|
|
59
|
+
-1.0, places=4)
|
|
60
|
+
|
|
61
|
+
def test_neq(self):
|
|
62
|
+
self.assertAlmostEqual(_truth(
|
|
63
|
+
"function fuzzy main(){ int a = 2; int b = 3; return a != b; }"),
|
|
64
|
+
1.0, places=4)
|
|
65
|
+
self.assertAlmostEqual(_truth(
|
|
66
|
+
"function fuzzy main(){ int a = 2; int b = 2; return a != b; }"),
|
|
67
|
+
-1.0, places=4)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestFizzBuzzEndToEnd(unittest.TestCase):
|
|
71
|
+
def test_fizzbuzz_via_select(self):
|
|
72
|
+
src = '''
|
|
73
|
+
function string fizzbuzz(int n) {
|
|
74
|
+
number s3 = (number) ((n % 3) == 0);
|
|
75
|
+
number s5 = (number) ((n % 5) == 0);
|
|
76
|
+
return select(
|
|
77
|
+
[10 * (s3 + s5), 10 * (s3 - s5), 10 * (s5 - s3), 10 * (0 - s3 - s5)],
|
|
78
|
+
[make_string("fizzbuzz"), make_string("fizz"),
|
|
79
|
+
make_string("buzz"), int_to_string(n)]);
|
|
80
|
+
}
|
|
81
|
+
function string main() {
|
|
82
|
+
return fizzbuzz(15) + " " + fizzbuzz(3) + " " + fizzbuzz(5) + " " + fizzbuzz(7);
|
|
83
|
+
}
|
|
84
|
+
'''
|
|
85
|
+
lx = Lexer(src, file="<t>")
|
|
86
|
+
ast = Parser(lx.tokenize(), file="<t>",
|
|
87
|
+
diagnostics=lx.diagnostics).parse_module()
|
|
88
|
+
assert not lx.diagnostics.has_errors()
|
|
89
|
+
ns: dict = {}
|
|
90
|
+
exec(translate_module(ast, llm_model="none", runtime_dim=64), ns)
|
|
91
|
+
got = ns["_VSA"].string_to_python(ns["main"]())
|
|
92
|
+
self.assertEqual(got, "fizzbuzz fizz buzz 7")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
unittest.main()
|
|
@@ -114,3 +114,19 @@ def test_scalar_tensor_result_shows_clean_number_not_repr():
|
|
|
114
114
|
out = _decode_result(_Mod(), torch.tensor(0.6812))
|
|
115
115
|
assert "tensor(" not in out and "device=" not in out
|
|
116
116
|
assert out == "= 0.6812"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_ops_lists_builtins_stdlib_and_special_forms():
|
|
120
|
+
# :ops is built from the LIVE dispatch tables, so it must show a
|
|
121
|
+
# builtin, a String intrinsic (incl. the new int_to_string), and
|
|
122
|
+
# the special forms — the REPL's stdlib-discovery surface
|
|
123
|
+
# (usability round 22).
|
|
124
|
+
out = _drive([":ops", ":quit"])
|
|
125
|
+
for needle in ("bundle", "argmax_cosine", "string_concat",
|
|
126
|
+
"int_to_string", "embed(", "unsafeCast", "capabilities"):
|
|
127
|
+
assert needle in out, f"missing {needle!r} in :ops output"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_help_mentions_ops():
|
|
131
|
+
out = _drive([":help", ":quit"])
|
|
132
|
+
assert ":ops" in out
|
|
@@ -80,5 +80,23 @@ class TestNumericPlusUntouched(unittest.TestCase):
|
|
|
80
80
|
3.5, places=4)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
class TestUserFunctionReturnConcat(unittest.TestCase):
|
|
84
|
+
def test_user_string_function_plus_literal(self):
|
|
85
|
+
# Round-23 regression: `f(x) + " "` with a user-declared
|
|
86
|
+
# `function string f` crashed at runtime (Tensor + str) — the
|
|
87
|
+
# text dispatch only knew stdlib/class return types, so the
|
|
88
|
+
# literal stayed a host str on the numeric path.
|
|
89
|
+
self.assertEqual(_run_text(
|
|
90
|
+
'function string shout(string s){ return s + "!"; }\n'
|
|
91
|
+
'function string main(){ return shout("hi") + " there"; }'),
|
|
92
|
+
"hi! there")
|
|
93
|
+
|
|
94
|
+
def test_chained_user_calls(self):
|
|
95
|
+
self.assertEqual(_run_text(
|
|
96
|
+
'function string tag(int n){ return int_to_string(n); }\n'
|
|
97
|
+
'function string main(){ return tag(1) + "-" + tag(2); }'),
|
|
98
|
+
"1-2")
|
|
99
|
+
|
|
100
|
+
|
|
83
101
|
if __name__ == "__main__":
|
|
84
102
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|