sutra-dev 0.9.2__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.2/sutra_dev.egg-info → sutra_dev-0.9.4}/PKG-INFO +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/pyproject.toml +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/__init__.py +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/__main__.py +35 -9
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/codegen.py +13 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/codegen_base.py +273 -2
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/codegen_pytorch.py +147 -13
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/embedding.py +10 -2
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/fv.py +24 -0
- sutra_dev-0.9.4/sutra_compiler/fv_loop_convergence.py +177 -0
- sutra_dev-0.9.4/sutra_compiler/fv_sampler_convergence.py +199 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/parser.py +31 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/repl.py +132 -6
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/logic.su +12 -8
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/strings.su +6 -0
- sutra_dev-0.9.4/sutra_compiler/symbol_table.py +550 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/validator.py +250 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4/sutra_dev.egg-info}/PKG-INFO +1 -1
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_dev.egg-info/SOURCES.txt +23 -1
- sutra_dev-0.9.4/tests/test_arity_diagnostic.py +82 -0
- sutra_dev-0.9.4/tests/test_cast_codegen.py +183 -0
- sutra_dev-0.9.4/tests/test_fv_loop_convergence.py +164 -0
- sutra_dev-0.9.4/tests/test_fv_sampler_convergence.py +121 -0
- sutra_dev-0.9.4/tests/test_int_to_string.py +84 -0
- sutra_dev-0.9.4/tests/test_interp_string_codegen.py +86 -0
- sutra_dev-0.9.4/tests/test_le_ge_ties.py +59 -0
- sutra_dev-0.9.4/tests/test_make_string_idempotent.py +62 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_no_host_readout.py +3 -1
- sutra_dev-0.9.4/tests/test_ntm_ram.py +492 -0
- sutra_dev-0.9.4/tests/test_num_eq_indicator.py +96 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_parser.py +25 -0
- sutra_dev-0.9.4/tests/test_python_builtin_escape.py +66 -0
- sutra_dev-0.9.4/tests/test_repl.py +132 -0
- sutra_dev-0.9.4/tests/test_run_error_diagnostics.py +53 -0
- sutra_dev-0.9.4/tests/test_string_plus_concat.py +102 -0
- sutra_dev-0.9.4/tests/test_symbol_table.py +250 -0
- sutra_dev-0.9.4/tests/test_type_inference.py +105 -0
- sutra_dev-0.9.4/tests/test_unknown_function_diagnostic.py +100 -0
- sutra_dev-0.9.4/tests/test_unknown_type_diagnostic.py +89 -0
- sutra_dev-0.9.4/tests/test_unknown_variable_diagnostic.py +114 -0
- sutra_dev-0.9.4/tests/test_unsafe_override_codegen.py +42 -0
- sutra_dev-0.9.4/tests/test_wrong_arg_type_diagnostic.py +81 -0
- sutra_dev-0.9.2/tests/test_ntm_ram.py +0 -263
- sutra_dev-0.9.2/tests/test_repl.py +0 -73
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/LICENSE +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/README.md +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/setup.cfg +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/ast_nodes.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/axon_keys.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/cached_compile.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/codegen_thrml.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/diagnostics.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/fv_key_soundness.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/fv_obligation_checker.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/fv_poly_bound.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/inliner.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/lexer.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/loop_capture.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/loop_desugar.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/multi_process.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/preeval.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/promise_desugar.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/review.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/simplify.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/simplify_egglog.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/axons.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/bigint.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/bitwise.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/embed.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/javascript_object.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/javascript_primitives.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/math.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/memory.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/modulus.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/numbers.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/promises.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/rotation.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/similarity.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/tensor.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib/vectors.su +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/stdlib_loader.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/sutradb_embedded.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/tabulate.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/trace.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_compiler/workspace.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_dev.egg-info/dependency_links.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_dev.egg-info/entry_points.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_dev.egg-info/requires.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/sutra_dev.egg-info/top_level.txt +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_arrow_functions.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_await_midfunction.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_await_substrate_pure.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_axon_build.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_axon_device_coherence.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_axon_item_call_result.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_axon_keys.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_axon_project.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_branchless_loop.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_buffer_transcendentals.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_cached_compile.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_class_fields_runtime.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_codegen.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_codegen_pytorch.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_codegen_thrml.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_corpus.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_dimension_audit.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_dimension_audit_warning.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_embedding_provider.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_equality_autograd.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fused_nn.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_general_checker.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_key_soundness.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_kleene_grid_exactness.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_poly_obligation_checker.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_public_api.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_termination.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_fv_worked_example.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_higher_order_functions.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_implicit_loop_desugar.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_inliner.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_int_dict.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_jvm_core.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_lexer.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_list_ops.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_load_matrix.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_loop_capture.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_loop_function_decl.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_matrix_literal.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_measurement_claim_sweep.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_mini_wasm_machine.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_multi_process_runtime.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_native_recursion.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_optional_llm_model.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_preeval.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_process_pool_runtime.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_ram_direct_tensor.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_rotation_prewarm.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_simplify.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_simplify_egglog.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_simplify_egglog_cse.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_snap_diagnostic.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_stdlib_loader.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_string_equality.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_substrate_leak_sweep.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_sutradb_embedded.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_tabulate.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_terminal_string_decode.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_torch_compile_wrap.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_trainable_matrix.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_transcendentals.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_type_test_gap.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_vector_literal.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_wasm_core.py +0 -0
- {sutra_dev-0.9.2 → sutra_dev-0.9.4}/tests/test_workspace.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
|
|
@@ -325,11 +325,24 @@ def _run_execute(path: str, *, runtime_dim: int, runtime_seed: int,
|
|
|
325
325
|
return 1
|
|
326
326
|
mod = types.ModuleType("_sutra_run")
|
|
327
327
|
mod.__file__ = f"<generated from {path}>"
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
328
|
+
try:
|
|
329
|
+
exec(compile(py_src, mod.__file__, "exec"), mod.__dict__)
|
|
330
|
+
if hasattr(mod, "main") and callable(mod.main):
|
|
331
|
+
result = mod.main()
|
|
332
|
+
if result is not None:
|
|
333
|
+
print(_decode_terminal_result(mod, result))
|
|
334
|
+
else:
|
|
335
|
+
# The substrate program is the body of main(); a file without one
|
|
336
|
+
# has nothing to run. Say so rather than exiting silently with no
|
|
337
|
+
# output (a newcomer otherwise can't tell why nothing happened).
|
|
338
|
+
print(f"{path}: no main() found - nothing to run", file=sys.stderr)
|
|
339
|
+
except Exception as exc:
|
|
340
|
+
# A runtime error in the generated module — e.g. a type mismatch the v0.1
|
|
341
|
+
# validator can't catch yet — should read like a Sutra diagnostic, not an
|
|
342
|
+
# uncaught Python traceback. KeyboardInterrupt / SystemExit are not caught
|
|
343
|
+
# (they subclass BaseException, not Exception), so Ctrl-C still works.
|
|
344
|
+
print(f"{path}: runtime error: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
345
|
+
return 1
|
|
333
346
|
return 0
|
|
334
347
|
|
|
335
348
|
|
|
@@ -387,7 +400,7 @@ def _run_viz(path: str, *, runtime_dim: int, runtime_seed: int,
|
|
|
387
400
|
# Inject tracing shim: after the _VSA = _TorchVSA(...) line,
|
|
388
401
|
# wrap every method with a tracing version.
|
|
389
402
|
shim = '''
|
|
390
|
-
#
|
|
403
|
+
# -- Tracing shim (injected by --run-viz) --
|
|
391
404
|
_orig_embed = _VSA.embed
|
|
392
405
|
_orig_bind = _VSA.bind
|
|
393
406
|
_orig_unbind = _VSA.unbind
|
|
@@ -417,7 +430,7 @@ _VSA.embed = _traced_embed
|
|
|
417
430
|
_VSA.bind = _traced_bind
|
|
418
431
|
_VSA.unbind = _traced_unbind
|
|
419
432
|
_VSA.bundle = _traced_bundle
|
|
420
|
-
#
|
|
433
|
+
# -- End tracing shim --
|
|
421
434
|
'''
|
|
422
435
|
# Find the _VSA = _TorchVSA(...) line and inject after it
|
|
423
436
|
lines = py_src.split('\n')
|
|
@@ -514,6 +527,19 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
514
527
|
parser = argparse.ArgumentParser(
|
|
515
528
|
prog="sutrac",
|
|
516
529
|
description="Validate Sutra (.su) source files.",
|
|
530
|
+
epilog=(
|
|
531
|
+
"interactive REPL:\n"
|
|
532
|
+
" sutrac repl Launch the interactive evaluator (takes no\n"
|
|
533
|
+
" file). Type an expression to see its value;\n"
|
|
534
|
+
" declarations accumulate as session state.\n"
|
|
535
|
+
"\n"
|
|
536
|
+
"examples:\n"
|
|
537
|
+
" sutrac file.su Validate a source file.\n"
|
|
538
|
+
" sutrac --run file.su Compile and execute (needs torch).\n"
|
|
539
|
+
" sutrac --emit file.su Print the generated PyTorch module.\n"
|
|
540
|
+
" sutrac repl Explore interactively."
|
|
541
|
+
),
|
|
542
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
517
543
|
)
|
|
518
544
|
parser.add_argument(
|
|
519
545
|
"paths",
|
|
@@ -541,7 +567,7 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
541
567
|
help=(
|
|
542
568
|
"Compile the first input file to self-contained torch Python and "
|
|
543
569
|
"print it to stdout. Picks CUDA at module init if available; "
|
|
544
|
-
"falls back to CPU otherwise. This is the one main codegen target
|
|
570
|
+
"falls back to CPU otherwise. This is the one main codegen target - "
|
|
545
571
|
"PyTorch is the runtime and the tensor-op library Sutra compiles "
|
|
546
572
|
"against."
|
|
547
573
|
),
|
|
@@ -564,7 +590,7 @@ def main(argv: List[str] | None = None) -> int:
|
|
|
564
590
|
"stdout. The default PyTorch path (--emit/--run) is untouched; this is "
|
|
565
591
|
"the Extropic thermodynamic-sampling compile target (queue.md approach "
|
|
566
592
|
"G). Requires jax + the thrml submodule. Lowering coverage is the "
|
|
567
|
-
"validated subset only
|
|
593
|
+
"validated subset only - see codegen_thrml.py."
|
|
568
594
|
),
|
|
569
595
|
)
|
|
570
596
|
parser.add_argument(
|
|
@@ -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})"
|
|
@@ -1700,7 +1712,7 @@ class Codegen(BaseCodegen):
|
|
|
1700
1712
|
self._emit("distinguish `'a'` (97 with flag) from `97` (97 without).")
|
|
1701
1713
|
self._emit("Arithmetic on chars works the same as on ints — both")
|
|
1702
1714
|
self._emit("live on the number axis. Downstream code that cares")
|
|
1703
|
-
self._emit("about the distinction can read the flag via `
|
|
1715
|
+
self._emit("about the distinction can read the flag via `is_string`.")
|
|
1704
1716
|
self._emit('"""')
|
|
1705
1717
|
self._emit("v = _np.zeros(self.dim, dtype=_np.float64)")
|
|
1706
1718
|
self._emit("v[self.semantic_dim + self.AXIS_REAL] = float(codepoint)")
|
|
@@ -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)
|
|
@@ -3021,6 +3045,34 @@ class BaseCodegen:
|
|
|
3021
3045
|
"char", "Character", "string", "String"}
|
|
3022
3046
|
)
|
|
3023
3047
|
|
|
3048
|
+
#: Types whose values are substrate Strings (text family).
|
|
3049
|
+
_TEXT_TYPES = frozenset({"string", "String", "char", "Character"})
|
|
3050
|
+
|
|
3051
|
+
def _is_text_expr(self, expr: ast.Expr) -> bool:
|
|
3052
|
+
"""True iff expr is provably a text (String) value at compile
|
|
3053
|
+
time. Conservative — unknown falls through to the numeric /
|
|
3054
|
+
element-wise `+` paths, same posture as _is_number_expr."""
|
|
3055
|
+
if isinstance(expr, (ast.StringLiteral, ast.CharLiteral,
|
|
3056
|
+
ast.InterpolatedString)):
|
|
3057
|
+
return True
|
|
3058
|
+
if isinstance(expr, ast.Identifier):
|
|
3059
|
+
return self._var_type.get(expr.name) in self._TEXT_TYPES
|
|
3060
|
+
if isinstance(expr, ast.Parenthesized):
|
|
3061
|
+
return self._is_text_expr(expr.inner)
|
|
3062
|
+
if isinstance(expr, (ast.CastExpr, ast.UnsafeCastExpr)):
|
|
3063
|
+
return (expr.target_type is not None
|
|
3064
|
+
and expr.target_type.name in self._TEXT_TYPES)
|
|
3065
|
+
if isinstance(expr, ast.BinaryOp) and expr.op == "+":
|
|
3066
|
+
return (self._is_text_expr(expr.left)
|
|
3067
|
+
and self._is_text_expr(expr.right))
|
|
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
|
|
3073
|
+
return self._resolved_return_type(expr.callee) in self._TEXT_TYPES
|
|
3074
|
+
return False
|
|
3075
|
+
|
|
3024
3076
|
def _is_synthetic_axis_expr(self, expr: ast.Expr) -> bool:
|
|
3025
3077
|
"""True iff expr is provably a synthetic-axis-encoded value at
|
|
3026
3078
|
compile time — int, float, complex, scalar, char, or string.
|
|
@@ -3380,6 +3432,21 @@ class BaseCodegen:
|
|
|
3380
3432
|
)
|
|
3381
3433
|
if user_op_name is not None:
|
|
3382
3434
|
return f"{user_op_name}({left}, {right})"
|
|
3435
|
+
# Text concatenation: `+` with BOTH operands provably text
|
|
3436
|
+
# dispatches to the substrate string_concat (round-17 audit
|
|
3437
|
+
# fix). Without this, lowercase-`string`-typed operands fell
|
|
3438
|
+
# through to ELEMENT-WISE VECTOR ADD — 'ab' + 'cd' silently
|
|
3439
|
+
# summed codepoint axes into garbage — and two string
|
|
3440
|
+
# literals crashed at runtime (host strs reached
|
|
3441
|
+
# string_concat). The String CLASS `operator +` still wins
|
|
3442
|
+
# above for class-typed operands. Literals re-translate with
|
|
3443
|
+
# dest_type="string" so they cross as substrate Strings.
|
|
3444
|
+
if (expr.op == "+" and self.supports_string_runtime
|
|
3445
|
+
and self._is_text_expr(expr.left)
|
|
3446
|
+
and self._is_text_expr(expr.right)):
|
|
3447
|
+
lt = self._translate_expr(expr.left, dest_type="string")
|
|
3448
|
+
rt = self._translate_expr(expr.right, dest_type="string")
|
|
3449
|
+
return f"_VSA.string_concat({lt}, {rt})"
|
|
3383
3450
|
# Logical operators dispatch through the substrate so they
|
|
3384
3451
|
# work uniformly on bool / fuzzy / trit / truth-axis-vector
|
|
3385
3452
|
# inputs. Zadeh fuzzy logic — min for AND, max for OR — on
|
|
@@ -3589,10 +3656,214 @@ class BaseCodegen:
|
|
|
3589
3656
|
return self._embed_expr_src(expr)
|
|
3590
3657
|
if isinstance(expr, ast.DefuzzyExpr):
|
|
3591
3658
|
return self._defuzzy_expr_src(expr)
|
|
3659
|
+
if isinstance(expr, ast.InterpolatedString):
|
|
3660
|
+
# `$"hello {s}!"` desugars to a make_string / string_concat
|
|
3661
|
+
# chain — every piece a substrate String, every join a
|
|
3662
|
+
# substrate op (strings.md). Only STRING-TYPED interpolants
|
|
3663
|
+
# are lowerable today: a number/fuzzy interpolant needs the
|
|
3664
|
+
# substrate number→string formatter, which is not built, so
|
|
3665
|
+
# those reject with a steer (same wall as the text casts).
|
|
3666
|
+
if not self.supports_string_runtime:
|
|
3667
|
+
raise CodegenNotSupported(
|
|
3668
|
+
expr,
|
|
3669
|
+
"interpolated strings need the substrate String "
|
|
3670
|
+
"runtime; use the PyTorch backend (codegen_pytorch)."
|
|
3671
|
+
)
|
|
3672
|
+
pieces: list[str] = []
|
|
3673
|
+
for part in expr.parts:
|
|
3674
|
+
if isinstance(part, str):
|
|
3675
|
+
if part:
|
|
3676
|
+
pieces.append(f"_VSA.make_string({part!r})")
|
|
3677
|
+
continue
|
|
3678
|
+
part_t = self._infer_cast_operand_type(part)
|
|
3679
|
+
if self._CAST_FAMILY.get(part_t) == "text":
|
|
3680
|
+
pieces.append(
|
|
3681
|
+
self._translate_expr(part, dest_type="string"))
|
|
3682
|
+
continue
|
|
3683
|
+
if part_t == "int":
|
|
3684
|
+
# strings.md § Integer formatting: the substrate
|
|
3685
|
+
# int→string formatter. int ONLY — a fractional
|
|
3686
|
+
# `number` rendering as its rounded integer would
|
|
3687
|
+
# silently lie, so it keeps rejecting below.
|
|
3688
|
+
pieces.append(
|
|
3689
|
+
f"_VSA.int_to_string("
|
|
3690
|
+
f"{self._translate_expr(part)})")
|
|
3691
|
+
continue
|
|
3692
|
+
if part_t is None:
|
|
3693
|
+
raise CodegenNotSupported(
|
|
3694
|
+
part,
|
|
3695
|
+
"interpolated expression must be string- or "
|
|
3696
|
+
"int-typed and statically inferable — assign it "
|
|
3697
|
+
"to a `string` (or `int`) variable first, or "
|
|
3698
|
+
"build the text with make_string / string_concat."
|
|
3699
|
+
)
|
|
3700
|
+
raise CodegenNotSupported(
|
|
3701
|
+
part,
|
|
3702
|
+
f"cannot interpolate a `{part_t}` value: only string- "
|
|
3703
|
+
"and int-typed interpolants lower (int_to_string is "
|
|
3704
|
+
"integer-shaped; fractional rendering is not built). "
|
|
3705
|
+
"For an integer value, declare it `int`; otherwise "
|
|
3706
|
+
"build the text with make_string / string_concat."
|
|
3707
|
+
)
|
|
3708
|
+
if not pieces:
|
|
3709
|
+
return "_VSA.make_string('')"
|
|
3710
|
+
src = pieces[0]
|
|
3711
|
+
for nxt in pieces[1:]:
|
|
3712
|
+
src = f"_VSA.string_concat({src}, {nxt})"
|
|
3713
|
+
return src
|
|
3714
|
+
if isinstance(expr, ast.UnsafeCastExpr):
|
|
3715
|
+
# types.md § "Casting — relabeling, not transformation":
|
|
3716
|
+
# unsafeCast<T>(x) is ALWAYS the pure relabel — the value
|
|
3717
|
+
# crosses unchanged and only the static type changes. It
|
|
3718
|
+
# never axis-moves; `(fuzzy) n` converts, `unsafeCast<fuzzy>(n)`
|
|
3719
|
+
# reinterprets (truth reading of a real-axis value is 0).
|
|
3720
|
+
return self._translate_expr(expr.expr)
|
|
3721
|
+
if isinstance(expr, ast.UnsafeOverrideExpr):
|
|
3722
|
+
# `unsafeOverride(v)` suppresses a type-check at the call
|
|
3723
|
+
# site and does not change the value — the runtime lowering
|
|
3724
|
+
# is the pure passthrough (same class as unsafeCast, minus
|
|
3725
|
+
# the type argument). Round-19 audit: was the last special
|
|
3726
|
+
# call form with no codegen.
|
|
3727
|
+
return self._translate_expr(expr.expr)
|
|
3728
|
+
if isinstance(expr, ast.CastExpr):
|
|
3729
|
+
return self._translate_cast(expr)
|
|
3592
3730
|
raise CodegenNotSupported(
|
|
3593
3731
|
expr, f"unsupported expression: {type(expr).__name__}"
|
|
3594
3732
|
)
|
|
3595
3733
|
|
|
3734
|
+
# ---- cast lowering (types.md § "Casting — relabeling, not
|
|
3735
|
+
# transformation") -------------------------------------------------
|
|
3736
|
+
#
|
|
3737
|
+
# The default cast is a NO-OP relabel: every Sutra value is already a
|
|
3738
|
+
# d-dim substrate vector (or a 0-d number at the entry boundary), and
|
|
3739
|
+
# downstream ops project the axes they need, so relabeling is free.
|
|
3740
|
+
# The ONE genuine axis-move pair is numeric↔truth: a number carries
|
|
3741
|
+
# its value on AXIS_REAL, a fuzzy/bool/trit on AXIS_TRUTH — a pure
|
|
3742
|
+
# relabel would strand the value on the wrong axis and every read
|
|
3743
|
+
# would see 0 (neutral). Text is walled off: number→string needs the
|
|
3744
|
+
# unbuilt substrate formatter; string→vector is the spec's "embedding
|
|
3745
|
+
# cast" whose real implementation is `embed()` at the entry boundary.
|
|
3746
|
+
|
|
3747
|
+
#: Sutra type name → cast family. Types not in the map (user classes,
|
|
3748
|
+
#: generics) relabel as "geometric" — the spec's free no-op default.
|
|
3749
|
+
_CAST_FAMILY = {
|
|
3750
|
+
"int": "number", "number": "number",
|
|
3751
|
+
"fuzzy": "truth", "bool": "truth", "trit": "truth",
|
|
3752
|
+
"complex": "geometric", "vector": "geometric", "role": "geometric",
|
|
3753
|
+
"matrix": "geometric",
|
|
3754
|
+
"string": "text", "String": "text",
|
|
3755
|
+
"char": "text", "Character": "text",
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
#: Whether this backend's runtime carries the numeric↔truth cast
|
|
3759
|
+
#: helpers (cast_number_to_truth / cast_truth_to_number / _cnum).
|
|
3760
|
+
#: The deprecated numpy backend does not; it keeps rejecting casts.
|
|
3761
|
+
supports_cast_lowering = False
|
|
3762
|
+
|
|
3763
|
+
#: Whether this backend's runtime carries the substrate String ops
|
|
3764
|
+
#: (make_string / string_concat). Gates InterpolatedString lowering;
|
|
3765
|
+
#: the deprecated numpy backend has no String runtime.
|
|
3766
|
+
supports_string_runtime = False
|
|
3767
|
+
|
|
3768
|
+
def _infer_cast_operand_type(self, expr: ast.Expr) -> str | None:
|
|
3769
|
+
"""Conservative static type of a cast operand: literals, typed
|
|
3770
|
+
locals/params (via _var_type), nested casts, parenthesized
|
|
3771
|
+
exprs. None when unknown — the caller decides whether the
|
|
3772
|
+
target makes the relabel/axis-move distinction load-bearing."""
|
|
3773
|
+
from .symbol_table import _LITERAL_TYPES
|
|
3774
|
+
kind = type(expr).__name__
|
|
3775
|
+
if kind in _LITERAL_TYPES:
|
|
3776
|
+
return _LITERAL_TYPES[kind]
|
|
3777
|
+
if isinstance(expr, ast.Identifier):
|
|
3778
|
+
return self._var_type.get(expr.name)
|
|
3779
|
+
if isinstance(expr, (ast.CastExpr, ast.UnsafeCastExpr)):
|
|
3780
|
+
return expr.target_type.name if expr.target_type else None
|
|
3781
|
+
if type(expr).__name__ == "Parenthesized" and hasattr(expr, "inner"):
|
|
3782
|
+
return self._infer_cast_operand_type(expr.inner)
|
|
3783
|
+
if isinstance(expr, ast.BinaryOp):
|
|
3784
|
+
if expr.op in ("==", "!=", "<", ">", "<=", ">=", "&&", "||"):
|
|
3785
|
+
return "bool"
|
|
3786
|
+
if expr.op in ("+", "-", "*", "/", "%"):
|
|
3787
|
+
lt = self._infer_cast_operand_type(expr.left)
|
|
3788
|
+
rt = self._infer_cast_operand_type(expr.right)
|
|
3789
|
+
if (self._CAST_FAMILY.get(lt) == "number"
|
|
3790
|
+
and self._CAST_FAMILY.get(rt) == "number"):
|
|
3791
|
+
return "number"
|
|
3792
|
+
return None
|
|
3793
|
+
|
|
3794
|
+
def _translate_cast(self, expr: ast.CastExpr) -> str:
|
|
3795
|
+
if not self.supports_cast_lowering:
|
|
3796
|
+
raise CodegenNotSupported(
|
|
3797
|
+
expr,
|
|
3798
|
+
"`(Type) expr` casts are not supported by this backend; "
|
|
3799
|
+
"use the PyTorch backend (codegen_pytorch)."
|
|
3800
|
+
)
|
|
3801
|
+
dst = expr.target_type.name if expr.target_type else None
|
|
3802
|
+
dst_family = self._CAST_FAMILY.get(dst, "geometric")
|
|
3803
|
+
src = self._infer_cast_operand_type(expr.expr)
|
|
3804
|
+
src_family = self._CAST_FAMILY.get(src, "geometric") if src else None
|
|
3805
|
+
|
|
3806
|
+
# --- text walls -------------------------------------------------
|
|
3807
|
+
if dst_family == "text" and src_family != "text":
|
|
3808
|
+
if src == "int":
|
|
3809
|
+
# strings.md § Integer formatting: `(string) n` on an
|
|
3810
|
+
# int is the integer formatter — the one number→text
|
|
3811
|
+
# cast with a real substrate implementation.
|
|
3812
|
+
return (f"_VSA.int_to_string("
|
|
3813
|
+
f"{self._translate_expr(expr.expr)})")
|
|
3814
|
+
raise CodegenNotSupported(
|
|
3815
|
+
expr,
|
|
3816
|
+
f"cannot cast to `{dst}`: only `int` values format "
|
|
3817
|
+
"(int_to_string — integer-shaped; fractional rendering is "
|
|
3818
|
+
"not built). For a literal, use `make_string(\"...\")`; "
|
|
3819
|
+
"join strings with `string_concat`."
|
|
3820
|
+
)
|
|
3821
|
+
if src_family == "text" and dst_family != "text":
|
|
3822
|
+
if dst_family == "geometric":
|
|
3823
|
+
raise CodegenNotSupported(
|
|
3824
|
+
expr,
|
|
3825
|
+
f"cannot cast a string to `{dst}`: that is the embedding "
|
|
3826
|
+
"cast, and its implementation is `embed(...)` at the "
|
|
3827
|
+
"program boundary — a String vector encodes codepoints, "
|
|
3828
|
+
"not semantics."
|
|
3829
|
+
)
|
|
3830
|
+
raise CodegenNotSupported(
|
|
3831
|
+
expr,
|
|
3832
|
+
f"cannot cast a string to `{dst}`: a String vector encodes "
|
|
3833
|
+
"packed codepoints, so its numeric axes are not a number. "
|
|
3834
|
+
"Use the string operations (string_length, char_at, ...) "
|
|
3835
|
+
"instead."
|
|
3836
|
+
)
|
|
3837
|
+
|
|
3838
|
+
inner = self._translate_expr(expr.expr)
|
|
3839
|
+
|
|
3840
|
+
# --- same family / text→text: the free relabel --------------------
|
|
3841
|
+
if src_family == dst_family:
|
|
3842
|
+
return inner
|
|
3843
|
+
|
|
3844
|
+
# --- the one genuine axis-move pair -------------------------------
|
|
3845
|
+
if src_family == "number" and dst_family == "truth":
|
|
3846
|
+
return f"_VSA.cast_number_to_truth({inner})"
|
|
3847
|
+
if src_family == "truth" and dst_family == "number":
|
|
3848
|
+
return f"_VSA.cast_truth_to_number({inner})"
|
|
3849
|
+
|
|
3850
|
+
# --- unknown source: only reject when the move/relabel choice
|
|
3851
|
+
# --- is load-bearing (truth/number targets read a single axis) ----
|
|
3852
|
+
if src_family is None and dst_family in ("truth", "number"):
|
|
3853
|
+
raise CodegenNotSupported(
|
|
3854
|
+
expr,
|
|
3855
|
+
f"cannot cast to `{dst}` here: the operand's static type "
|
|
3856
|
+
"can't be inferred, and truth/number casts need it to pick "
|
|
3857
|
+
"between relabel and axis-move. Assign the operand to a "
|
|
3858
|
+
"typed variable first."
|
|
3859
|
+
)
|
|
3860
|
+
|
|
3861
|
+
# --- every remaining pair relabels; _cnum canonicalizes a 0-d
|
|
3862
|
+
# --- number at the entry boundary and passes vectors through ------
|
|
3863
|
+
if src_family == "number" or src is None:
|
|
3864
|
+
return f"_VSA._cnum({inner})"
|
|
3865
|
+
return inner
|
|
3866
|
+
|
|
3596
3867
|
def _embed_expr_src(self, expr: ast.EmbedExpr) -> str:
|
|
3597
3868
|
"""Override point for per-backend `embed(<expr>)` lowering.
|
|
3598
3869
|
|