sutra-dev 0.9.1__tar.gz → 0.9.3__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 (151) hide show
  1. {sutra_dev-0.9.1/sutra_dev.egg-info → sutra_dev-0.9.3}/PKG-INFO +4 -1
  2. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/pyproject.toml +11 -1
  3. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/__init__.py +1 -1
  4. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/__main__.py +73 -19
  5. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/codegen.py +34 -16
  6. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/codegen_base.py +254 -39
  7. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/codegen_pytorch.py +121 -19
  8. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/codegen_thrml.py +7 -5
  9. sutra_dev-0.9.3/sutra_compiler/embedding.py +150 -0
  10. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/fv.py +24 -0
  11. sutra_dev-0.9.3/sutra_compiler/fv_loop_convergence.py +177 -0
  12. sutra_dev-0.9.3/sutra_compiler/fv_sampler_convergence.py +199 -0
  13. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/lexer.py +10 -11
  14. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/multi_process.py +1 -1
  15. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/parser.py +38 -8
  16. sutra_dev-0.9.3/sutra_compiler/repl.py +303 -0
  17. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/simplify.py +11 -26
  18. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/axons.su +3 -3
  19. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/bitwise.su +1 -1
  20. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/embed.su +3 -6
  21. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/logic.su +2 -3
  22. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/numbers.su +2 -2
  23. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/rotation.su +2 -2
  24. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/similarity.su +35 -15
  25. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/strings.su +6 -0
  26. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/vectors.su +0 -14
  27. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib_loader.py +1 -1
  28. sutra_dev-0.9.3/sutra_compiler/symbol_table.py +550 -0
  29. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/validator.py +300 -4
  30. {sutra_dev-0.9.1 → sutra_dev-0.9.3/sutra_dev.egg-info}/PKG-INFO +4 -1
  31. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_dev.egg-info/SOURCES.txt +27 -1
  32. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_dev.egg-info/requires.txt +4 -0
  33. sutra_dev-0.9.3/tests/test_arity_diagnostic.py +82 -0
  34. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_axon_keys.py +4 -4
  35. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_axon_project.py +3 -3
  36. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_branchless_loop.py +1 -1
  37. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_cached_compile.py +3 -3
  38. sutra_dev-0.9.3/tests/test_cast_codegen.py +183 -0
  39. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_codegen.py +85 -85
  40. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_codegen_pytorch.py +19 -18
  41. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_dimension_audit.py +2 -2
  42. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_dimension_audit_warning.py +1 -1
  43. sutra_dev-0.9.3/tests/test_embedding_provider.py +109 -0
  44. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_key_soundness.py +2 -2
  45. sutra_dev-0.9.3/tests/test_fv_loop_convergence.py +164 -0
  46. sutra_dev-0.9.3/tests/test_fv_sampler_convergence.py +121 -0
  47. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_termination.py +1 -1
  48. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_inliner.py +2 -2
  49. sutra_dev-0.9.3/tests/test_int_to_string.py +84 -0
  50. sutra_dev-0.9.3/tests/test_interp_string_codegen.py +86 -0
  51. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_list_ops.py +14 -0
  52. sutra_dev-0.9.3/tests/test_make_string_idempotent.py +62 -0
  53. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_mini_wasm_machine.py +1 -1
  54. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_multi_process_runtime.py +2 -2
  55. sutra_dev-0.9.3/tests/test_ntm_ram.py +492 -0
  56. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_optional_llm_model.py +2 -2
  57. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_parser.py +30 -5
  58. sutra_dev-0.9.3/tests/test_python_builtin_escape.py +66 -0
  59. sutra_dev-0.9.3/tests/test_repl.py +116 -0
  60. sutra_dev-0.9.3/tests/test_run_error_diagnostics.py +53 -0
  61. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_simplify.py +56 -56
  62. sutra_dev-0.9.3/tests/test_snap_diagnostic.py +143 -0
  63. sutra_dev-0.9.3/tests/test_string_plus_concat.py +84 -0
  64. sutra_dev-0.9.3/tests/test_symbol_table.py +250 -0
  65. sutra_dev-0.9.3/tests/test_terminal_string_decode.py +53 -0
  66. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_transcendentals.py +14 -63
  67. sutra_dev-0.9.3/tests/test_type_inference.py +105 -0
  68. sutra_dev-0.9.3/tests/test_unknown_function_diagnostic.py +100 -0
  69. sutra_dev-0.9.3/tests/test_unknown_type_diagnostic.py +89 -0
  70. sutra_dev-0.9.3/tests/test_unknown_variable_diagnostic.py +114 -0
  71. sutra_dev-0.9.3/tests/test_unsafe_override_codegen.py +42 -0
  72. sutra_dev-0.9.3/tests/test_wrong_arg_type_diagnostic.py +81 -0
  73. sutra_dev-0.9.1/tests/test_ntm_ram.py +0 -263
  74. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/LICENSE +0 -0
  75. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/README.md +0 -0
  76. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/setup.cfg +0 -0
  77. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/ast_nodes.py +0 -0
  78. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/axon_keys.py +0 -0
  79. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/cached_compile.py +0 -0
  80. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/diagnostics.py +0 -0
  81. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/fv_key_soundness.py +0 -0
  82. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/fv_obligation_checker.py +0 -0
  83. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/fv_poly_bound.py +0 -0
  84. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/inliner.py +0 -0
  85. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/loop_capture.py +0 -0
  86. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/loop_desugar.py +0 -0
  87. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/preeval.py +0 -0
  88. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/promise_desugar.py +0 -0
  89. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/review.py +0 -0
  90. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/simplify_egglog.py +0 -0
  91. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/bigint.su +0 -0
  92. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/javascript_object.su +0 -0
  93. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/javascript_primitives.su +0 -0
  94. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/math.su +0 -0
  95. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/memory.su +0 -0
  96. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/modulus.su +0 -0
  97. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/promises.su +0 -0
  98. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/stdlib/tensor.su +0 -0
  99. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/sutradb_embedded.py +0 -0
  100. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/tabulate.py +0 -0
  101. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/trace.py +0 -0
  102. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_compiler/workspace.py +0 -0
  103. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_dev.egg-info/dependency_links.txt +0 -0
  104. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_dev.egg-info/entry_points.txt +0 -0
  105. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/sutra_dev.egg-info/top_level.txt +0 -0
  106. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_arrow_functions.py +0 -0
  107. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_await_midfunction.py +0 -0
  108. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_await_substrate_pure.py +0 -0
  109. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_axon_build.py +0 -0
  110. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_axon_device_coherence.py +0 -0
  111. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_axon_item_call_result.py +0 -0
  112. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_buffer_transcendentals.py +0 -0
  113. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_class_fields_runtime.py +0 -0
  114. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_codegen_thrml.py +0 -0
  115. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_corpus.py +0 -0
  116. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_equality_autograd.py +0 -0
  117. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fused_nn.py +0 -0
  118. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_general_checker.py +0 -0
  119. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_kleene_grid_exactness.py +0 -0
  120. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_poly_obligation_checker.py +0 -0
  121. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_public_api.py +0 -0
  122. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_fv_worked_example.py +0 -0
  123. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_higher_order_functions.py +0 -0
  124. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_implicit_loop_desugar.py +0 -0
  125. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_int_dict.py +0 -0
  126. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_jvm_core.py +0 -0
  127. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_lexer.py +0 -0
  128. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_load_matrix.py +0 -0
  129. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_loop_capture.py +0 -0
  130. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_loop_function_decl.py +0 -0
  131. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_matrix_literal.py +0 -0
  132. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_measurement_claim_sweep.py +0 -0
  133. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_native_recursion.py +0 -0
  134. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_no_host_readout.py +0 -0
  135. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_preeval.py +0 -0
  136. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_process_pool_runtime.py +0 -0
  137. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_ram_direct_tensor.py +0 -0
  138. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_rotation_prewarm.py +0 -0
  139. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_simplify_egglog.py +0 -0
  140. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_simplify_egglog_cse.py +0 -0
  141. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_stdlib_loader.py +0 -0
  142. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_string_equality.py +0 -0
  143. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_substrate_leak_sweep.py +0 -0
  144. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_sutradb_embedded.py +0 -0
  145. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_tabulate.py +0 -0
  146. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_torch_compile_wrap.py +0 -0
  147. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_trainable_matrix.py +0 -0
  148. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_type_test_gap.py +0 -0
  149. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_vector_literal.py +0 -0
  150. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_wasm_core.py +0 -0
  151. {sutra_dev-0.9.1 → sutra_dev-0.9.3}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sutra-dev
3
- Version: 0.9.1
3
+ Version: 0.9.3
4
4
  Summary: Compiler for the Sutra programming language — tensor ops on frozen-LLM embedding vectors.
5
5
  Author: Emma Leonhart
6
6
  License-Expression: AGPL-3.0-only
@@ -23,6 +23,9 @@ License-File: LICENSE
23
23
  Requires-Dist: numpy>=1.26
24
24
  Provides-Extra: runtime
25
25
  Requires-Dist: torch>=2.1; extra == "runtime"
26
+ Provides-Extra: embed
27
+ Requires-Dist: sentence-transformers>=3.0; extra == "embed"
28
+ Requires-Dist: einops>=0.7; extra == "embed"
26
29
  Provides-Extra: ts
27
30
  Requires-Dist: sutra-from-ts>=0.1.0; extra == "ts"
28
31
  Provides-Extra: fv
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sutra-dev"
7
- version = "0.9.1"
7
+ version = "0.9.3"
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"
@@ -44,6 +44,16 @@ dependencies = [
44
44
  runtime = [
45
45
  "torch>=2.1",
46
46
  ]
47
+ # In-process embedding backend. sentence-transformers loads the SAME frozen
48
+ # nomic-embed-text model HuggingFace serves, so semantic Sutra programs run with
49
+ # NO Ollama daemon — this is the default backend (sutra_compiler/embedding.py).
50
+ # einops is needed by nomic-bert's custom modeling code. Pure make_real / matrix
51
+ # / arithmetic programs embed nothing and need none of this.
52
+ # Install with: pip install sutra-dev[runtime,embed]
53
+ embed = [
54
+ "sentence-transformers>=3.0",
55
+ "einops>=0.7",
56
+ ]
47
57
  # TypeScript→Sutra transpiler. `pip install sutra-dev[ts]` pulls in
48
58
  # the working ts2su CLI from the sister package, so the recommended
49
59
  # way to get a complete Sutra dev environment for browser-layer work
@@ -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.1"
31
+ __version__ = "0.9.3"
32
32
 
33
33
  from .diagnostics import Diagnostic, DiagnosticLevel, DiagnosticBag
34
34
  from .lexer import Lexer, Token, TokenKind
@@ -23,6 +23,7 @@ from typing import List
23
23
 
24
24
  from . import __version__
25
25
  from . import ast_nodes as ast
26
+ from .codegen_base import CodegenNotSupported
26
27
  from .codegen_pytorch import translate_module as translate_pytorch
27
28
  from .diagnostics import Diagnostic, DiagnosticLevel
28
29
  from .lexer import Lexer
@@ -292,10 +293,20 @@ def _compile_to_python(path: str, *, runtime_dim: int,
292
293
  tabulate_module(module)
293
294
  if loop_T is None:
294
295
  loop_T = _read_atman_loop_T(path) or 50
295
- return translate_pytorch(
296
- module, runtime_dim=runtime_dim, runtime_seed=runtime_seed,
297
- loop_max_iterations=loop_T,
298
- )
296
+ try:
297
+ return translate_pytorch(
298
+ module, runtime_dim=runtime_dim, runtime_seed=runtime_seed,
299
+ loop_max_iterations=loop_T,
300
+ )
301
+ except CodegenNotSupported as exc:
302
+ # The backend can't lower this construct (an unsupported builtin like
303
+ # `snap`, an unsupported node, etc.). `exc` already formats as
304
+ # `line:col: codegen: <message>`; prepend the file path so it reads
305
+ # like every other Sutra diagnostic (`file:line:col: …`) instead of
306
+ # surfacing to the user as an uncaught Python traceback. Single choke
307
+ # point: --run / --emit / runtime-viz all route through here.
308
+ print(f"{path}:{exc}", file=sys.stderr)
309
+ return None
299
310
 
300
311
 
301
312
  def _run_execute(path: str, *, runtime_dim: int, runtime_seed: int,
@@ -314,26 +325,48 @@ def _run_execute(path: str, *, runtime_dim: int, runtime_seed: int,
314
325
  return 1
315
326
  mod = types.ModuleType("_sutra_run")
316
327
  mod.__file__ = f"<generated from {path}>"
317
- exec(compile(py_src, mod.__file__, "exec"), mod.__dict__)
318
- if hasattr(mod, "main") and callable(mod.main):
319
- result = mod.main()
320
- if result is not None:
321
- print(_decode_terminal_result(mod, result))
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
322
346
  return 0
323
347
 
324
348
 
325
349
  def _decode_terminal_result(mod, result):
326
- """Terminal/output boundary: decode a number-vector `main()` result to its
327
- real-axis value for display. The language itself has no scalar-readout
328
- accessor (`real()` was removed — substrate purity); the host reading the
329
- FINAL result for display is the one external terminal boundary, the same as
330
- printing a returned value in any runtime. A number-vector is a 1-D tensor of
331
- length `_VSA.dim`; anything else (string, int, already-decoded) prints as-is."""
350
+ """Terminal/output boundary: decode a `main()` result to a host value for
351
+ display. The language has no scalar-readout accessor (`real()` was removed —
352
+ substrate purity); the host reading the FINAL result for display is the one
353
+ external terminal boundary, the same as printing a returned value in any
354
+ runtime.
355
+
356
+ Both a String and a number-vector are 1-D tensors of length `_VSA.dim`; the
357
+ AXIS_STRING_FLAG tells them apart. The string check MUST come first: a String
358
+ stores its codepoints on synthetic axes and the real axis coincides with the
359
+ first codepoint, so a String read as a number prints that char's codepoint
360
+ (e.g. `return "hello world"` would print 104.0 = 'h'). A String decodes to its
361
+ Python text via the sanctioned `string_to_python` terminal decode; a
362
+ number-vector decodes to its real-axis value; anything else prints as-is."""
332
363
  try:
333
364
  import torch as _t
334
365
  vsa = getattr(mod, "_VSA", None)
335
366
  if (vsa is not None and isinstance(result, _t.Tensor)
336
367
  and result.ndim == 1 and result.shape[0] == vsa.dim):
368
+ if vsa.is_string(result):
369
+ return vsa.string_to_python(result)
337
370
  return float(result[vsa.semantic_dim + vsa.AXIS_REAL])
338
371
  except Exception:
339
372
  pass
@@ -367,7 +400,7 @@ def _run_viz(path: str, *, runtime_dim: int, runtime_seed: int,
367
400
  # Inject tracing shim: after the _VSA = _TorchVSA(...) line,
368
401
  # wrap every method with a tracing version.
369
402
  shim = '''
370
- # ── Tracing shim (injected by --run-viz) ──
403
+ # -- Tracing shim (injected by --run-viz) --
371
404
  _orig_embed = _VSA.embed
372
405
  _orig_bind = _VSA.bind
373
406
  _orig_unbind = _VSA.unbind
@@ -397,7 +430,7 @@ _VSA.embed = _traced_embed
397
430
  _VSA.bind = _traced_bind
398
431
  _VSA.unbind = _traced_unbind
399
432
  _VSA.bundle = _traced_bundle
400
- # ── End tracing shim ──
433
+ # -- End tracing shim --
401
434
  '''
402
435
  # Find the _VSA = _TorchVSA(...) line and inject after it
403
436
  lines = py_src.split('\n')
@@ -483,9 +516,30 @@ def _emit_thrml(path: str) -> int:
483
516
 
484
517
 
485
518
  def main(argv: List[str] | None = None) -> int:
519
+ # `sutrac repl` (or `--repl`) launches the interactive evaluator. Intercepted
520
+ # before argparse because the validator requires a `paths` positional and the
521
+ # REPL takes no file.
522
+ _args = argv if argv is not None else sys.argv[1:]
523
+ if _args and _args[0] in ("repl", "--repl"):
524
+ from .repl import main_repl
525
+ return main_repl()
526
+
486
527
  parser = argparse.ArgumentParser(
487
528
  prog="sutrac",
488
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,
489
543
  )
490
544
  parser.add_argument(
491
545
  "paths",
@@ -513,7 +567,7 @@ def main(argv: List[str] | None = None) -> int:
513
567
  help=(
514
568
  "Compile the first input file to self-contained torch Python and "
515
569
  "print it to stdout. Picks CUDA at module init if available; "
516
- "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 - "
517
571
  "PyTorch is the runtime and the tensor-op library Sutra compiles "
518
572
  "against."
519
573
  ),
@@ -536,7 +590,7 @@ def main(argv: List[str] | None = None) -> int:
536
590
  "stdout. The default PyTorch path (--emit/--run) is untouched; this is "
537
591
  "the Extropic thermodynamic-sampling compile target (queue.md approach "
538
592
  "G). Requires jax + the thrml submodule. Lowering coverage is the "
539
- "validated subset only see codegen_thrml.py."
593
+ "validated subset only - see codegen_thrml.py."
540
594
  ),
541
595
  )
542
596
  parser.add_argument(
@@ -89,7 +89,7 @@ class Codegen(BaseCodegen):
89
89
  self._semantic_dim = runtime_dim
90
90
  self._synthetic_dim = (synthetic_dim if synthetic_dim is not None
91
91
  else self.DEFAULT_SYNTHETIC_DIM)
92
- # List of strings that appear in `basis_vector("...")` calls,
92
+ # List of strings that appear in `embed("...")` calls,
93
93
  # populated by translate_module() between simplify and codegen.
94
94
  # The codegen emits a batched Ollama pre-fetch at module init
95
95
  # to replace N sequential HTTP round-trips with one call.
@@ -119,6 +119,18 @@ class Codegen(BaseCodegen):
119
119
  "array_filter",
120
120
  })
121
121
 
122
+ # Steering hints attached to the CodegenNotSupported error for
123
+ # spec'd-but-unimplemented builtins. `snap` is the cleanup primitive
124
+ # backed by an attractor circuit the substrate doesn't have yet;
125
+ # argmax_cosine against an explicit codebook is the implemented form
126
+ # the demos use today (tutorial 03). Keeps the rejection message from
127
+ # being a dead end.
128
+ _UNSUPPORTED_BUILTIN_HINTS = {
129
+ "snap": "use `argmax_cosine(query, [a, b, c])` to clean a vector up "
130
+ "against an explicit codebook; `snap` (attractor-circuit "
131
+ "cleanup) is a future substrate primitive",
132
+ }
133
+
122
134
  def _char_literal_src(self, expr: ast.CharLiteral) -> str:
123
135
  """Lower `'a'` to a runtime make_char call with the code point."""
124
136
  return f"_VSA.make_char({int(expr.value)})"
@@ -444,10 +456,12 @@ class Codegen(BaseCodegen):
444
456
  callee = call.callee
445
457
  if isinstance(callee, ast.Identifier):
446
458
  if callee.name in self._UNSUPPORTED_BUILTINS:
459
+ hint = self._UNSUPPORTED_BUILTIN_HINTS.get(callee.name)
460
+ detail = f" — {hint}" if hint else ""
447
461
  raise CodegenNotSupported(
448
462
  call,
449
- f"`{callee.name}` is not supported on the pure-numpy "
450
- f"substrate (no cleanup circuit at runtime)",
463
+ f"`{callee.name}` is not yet supported on the substrate "
464
+ f"(no runtime lowering on this backend){detail}",
451
465
  )
452
466
  if (isinstance(callee, ast.MemberAccess)
453
467
  and callee.member in self._REMOVED_SCALAR_ACCESSORS):
@@ -512,7 +526,7 @@ class Codegen(BaseCodegen):
512
526
  self._emit("# repeated bind/unbind with the same role O(d^2) lookup + matmul.")
513
527
  self._emit("self._rot_cache = {}")
514
528
  self._emit("# On-disk embedding cache. Second-and-later runs load every")
515
- self._emit("# previously-seen basis_vector(...) string from disk instead of")
529
+ self._emit("# previously-seen embed(...) string from disk instead of")
516
530
  self._emit("# hitting Ollama. Cache is keyed by (model, dim) so changing")
517
531
  self._emit("# either invalidates cleanly (different cache file).")
518
532
  self._emit("import os as _os")
@@ -524,9 +538,13 @@ class Codegen(BaseCodegen):
524
538
  self._emit("_os.makedirs(self._cache_dir, exist_ok=True)")
525
539
  self._emit("# Sanitize model name for use as filename.")
526
540
  self._emit("_safe_model = llm_model.replace('/', '_').replace(':', '_')")
541
+ self._emit("# Backend-aware: in-process (transformers) and ollama realize the")
542
+ self._emit("# same model with slightly different geometry, so they must not")
543
+ self._emit("# share a cache file or one backend reads the other's vectors.")
544
+ self._emit("_emb_backend = _os.environ.get('SUTRA_EMBED_BACKEND', 'auto').strip().lower() or 'auto'")
527
545
  self._emit("self._cache_path = _os.path.join(")
528
546
  self._indent += 1
529
- self._emit("self._cache_dir, f'{_safe_model}-d{self.dim}.npz')")
547
+ self._emit("self._cache_dir, f'{_safe_model}-d{self.dim}-{_emb_backend}.npz')")
530
548
  self._indent -= 1
531
549
  self._emit("self._load_disk_cache()")
532
550
  self._emit("# Transcendental lookup tables — substrate-pure interpolation per")
@@ -641,9 +659,9 @@ class Codegen(BaseCodegen):
641
659
  self._emit('starts at zero."""')
642
660
  self._emit("if name not in self._codebook:")
643
661
  self._indent += 1
644
- self._emit("import ollama")
645
- self._emit("r = ollama.embed(model=self.llm_model, input=name)")
646
- self._emit("v = _np.array(r['embeddings'][0], dtype=_np.float64)")
662
+ self._emit("from sutra_compiler.embedding import embed_texts as _embed_texts")
663
+ self._emit("r = _embed_texts([name], self.llm_model)")
664
+ self._emit("v = _np.array(r[0], dtype=_np.float64)")
647
665
  self._emit("# Mean-center. Raw LLM embeddings cluster in a cone (all-")
648
666
  self._emit("# positive-ish); centering keeps rotation/bind algebra")
649
667
  self._emit("# well-behaved.")
@@ -685,11 +703,11 @@ class Codegen(BaseCodegen):
685
703
  self._indent += 1
686
704
  self._emit("return")
687
705
  self._indent -= 1
688
- self._emit("import ollama")
689
- self._emit("r = ollama.embed(model=self.llm_model, input=missing)")
706
+ self._emit("from sutra_compiler.embedding import embed_texts as _embed_texts")
707
+ self._emit("r = _embed_texts(missing, self.llm_model)")
690
708
  self._emit("for i, name in enumerate(missing):")
691
709
  self._indent += 1
692
- self._emit("v = _np.array(r['embeddings'][i], dtype=_np.float64)")
710
+ self._emit("v = _np.array(r[i], dtype=_np.float64)")
693
711
  self._emit("v = v - _np.mean(v)")
694
712
  self._emit("n = _np.linalg.norm(v)")
695
713
  self._emit("if n > 0: v = v / n")
@@ -1682,7 +1700,7 @@ class Codegen(BaseCodegen):
1682
1700
  self._emit("distinguish `'a'` (97 with flag) from `97` (97 without).")
1683
1701
  self._emit("Arithmetic on chars works the same as on ints — both")
1684
1702
  self._emit("live on the number axis. Downstream code that cares")
1685
- self._emit("about the distinction can read the flag via `is_char`.")
1703
+ self._emit("about the distinction can read the flag via `is_string`.")
1686
1704
  self._emit('"""')
1687
1705
  self._emit("v = _np.zeros(self.dim, dtype=_np.float64)")
1688
1706
  self._emit("v[self.semantic_dim + self.AXIS_REAL] = float(codepoint)")
@@ -2146,7 +2164,7 @@ class Codegen(BaseCodegen):
2146
2164
  f"seed={self.runtime_seed}, "
2147
2165
  f"llm_model={self._llm_model!r})"
2148
2166
  )
2149
- # Batched pre-fetch of every basis_vector("...") string argument
2167
+ # Batched pre-fetch of every embed("...") string argument
2150
2168
  # the program uses. One Ollama round-trip instead of N sequential
2151
2169
  # ones. Collected by the simplify pass (see translate_module).
2152
2170
  if self._prefetch_strings:
@@ -2233,11 +2251,11 @@ def translate_module(module: ast.Module, **kwargs) -> str:
2233
2251
  Runs the simplification pass over the AST before handing to the
2234
2252
  codegen so identity rewrites (bundle(v) -> v, bundle flattening)
2235
2253
  happen in source-to-source form rather than in the emitted
2236
- Python. Also collects every `basis_vector("...")` string literal
2254
+ Python. Also collects every `embed("...")` string literal
2237
2255
  so the codegen can emit a batched Ollama pre-fetch at module init
2238
2256
  (N HTTP round-trips collapse into one batched embed call).
2239
2257
  """
2240
- from .simplify import simplify_module, collect_basis_vector_strings
2258
+ from .simplify import simplify_module, collect_embedded_strings
2241
2259
  from .inliner import inline_stdlib_calls
2242
2260
  from .promise_desugar import desugar_promises
2243
2261
  from .loop_desugar import desugar_implicit_loops
@@ -2263,7 +2281,7 @@ def translate_module(module: ast.Module, **kwargs) -> str:
2263
2281
  # absorption, which can fold parts of the inlined form.
2264
2282
  inline_stdlib_calls(module)
2265
2283
  simplify_module(module)
2266
- strings = collect_basis_vector_strings(module)
2284
+ strings = collect_embedded_strings(module)
2267
2285
  cg = Codegen(**kwargs)
2268
2286
  cg._prefetch_strings = strings
2269
2287
  cg._axon_keys_bound = bound_keys