sutra-dev 0.2.0__py3-none-any.whl
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_compiler/__init__.py +49 -0
- sutra_compiler/__main__.py +514 -0
- sutra_compiler/ast_nodes.py +553 -0
- sutra_compiler/codegen.py +1811 -0
- sutra_compiler/codegen_base.py +2436 -0
- sutra_compiler/codegen_pytorch.py +1472 -0
- sutra_compiler/diagnostics.py +145 -0
- sutra_compiler/inliner.py +581 -0
- sutra_compiler/lexer.py +821 -0
- sutra_compiler/parser.py +2112 -0
- sutra_compiler/review.py +322 -0
- sutra_compiler/simplify.py +1046 -0
- sutra_compiler/simplify_egglog.py +674 -0
- sutra_compiler/stdlib/axons.su +53 -0
- sutra_compiler/stdlib/embed.su +48 -0
- sutra_compiler/stdlib/javascript_object.su +18 -0
- sutra_compiler/stdlib/logic.su +202 -0
- sutra_compiler/stdlib/math.su +12 -0
- sutra_compiler/stdlib/memory.su +82 -0
- sutra_compiler/stdlib/numbers.su +99 -0
- sutra_compiler/stdlib/rotation.su +83 -0
- sutra_compiler/stdlib/similarity.su +97 -0
- sutra_compiler/stdlib/strings.su +56 -0
- sutra_compiler/stdlib/tensor.su +82 -0
- sutra_compiler/stdlib/vectors.su +119 -0
- sutra_compiler/stdlib_loader.py +219 -0
- sutra_compiler/sutradb_embedded.py +273 -0
- sutra_compiler/trace.py +135 -0
- sutra_compiler/validator.py +552 -0
- sutra_compiler/workspace.py +655 -0
- sutra_dev-0.2.0.dist-info/METADATA +80 -0
- sutra_dev-0.2.0.dist-info/RECORD +36 -0
- sutra_dev-0.2.0.dist-info/WHEEL +5 -0
- sutra_dev-0.2.0.dist-info/entry_points.txt +2 -0
- sutra_dev-0.2.0.dist-info/licenses/LICENSE +201 -0
- sutra_dev-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Canonical Sutra definitions for vector-similarity operations.
|
|
2
|
+
//
|
|
3
|
+
// Today these are hardcoded as runtime methods in codegen.py —
|
|
4
|
+
// see `def eq`, `def similarity`, and the vectorized `_argmax_cosine` /
|
|
5
|
+
// `_select_softmax` helpers emitted by _emit_prelude. This file is
|
|
6
|
+
// the target shape for the function-expansion pass to consume. See
|
|
7
|
+
// stdlib/README.md.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
// ==========================================================================
|
|
11
|
+
// Section 1 — implemented in pure Sutra
|
|
12
|
+
// ==========================================================================
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// neq — negation of cosine-similarity equality. Composed, not primitive.
|
|
16
|
+
// Once `eq` inlines, this collapses to `logical_not(eq(a, b))` which is
|
|
17
|
+
// one extra polynomial on top of the eq result.
|
|
18
|
+
function fuzzy neq(vector a, vector b) {
|
|
19
|
+
return !(a == b);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// ==========================================================================
|
|
24
|
+
// Section 2 — blocked on intrinsics
|
|
25
|
+
// ==========================================================================
|
|
26
|
+
//
|
|
27
|
+
// eq — cosine similarity, placed on the truth axis.
|
|
28
|
+
//
|
|
29
|
+
// function fuzzy eq(vector a, vector b) {
|
|
30
|
+
// scalar na = sqrt(dot(a, a));
|
|
31
|
+
// scalar nb = sqrt(dot(b, b));
|
|
32
|
+
// scalar cos = dot(a, b) / (na * nb + finfo_tiny);
|
|
33
|
+
// return make_truth(cos);
|
|
34
|
+
// }
|
|
35
|
+
//
|
|
36
|
+
// Blocked on: `dot`, `sqrt`, `make_truth`, `finfo_tiny`.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// similarity / Cosine — cosine similarity as a scalar (no truth-axis
|
|
40
|
+
// placement). Same dot/norm pipeline as eq, but returns the scalar
|
|
41
|
+
// cosine instead of wrapping it on the truth axis.
|
|
42
|
+
//
|
|
43
|
+
// function scalar similarity(vector a, vector b) {
|
|
44
|
+
// scalar na = sqrt(dot(a, a));
|
|
45
|
+
// scalar nb = sqrt(dot(b, b));
|
|
46
|
+
// return dot(a, b) / (na * nb + finfo_tiny);
|
|
47
|
+
// }
|
|
48
|
+
//
|
|
49
|
+
// Blocked on: `dot`, `sqrt`.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// argmax_cosine — highest-cosine match from a candidate list.
|
|
53
|
+
//
|
|
54
|
+
// function vector argmax_cosine(vector query, vector[] candidates) {
|
|
55
|
+
// matrix C = stack(candidates);
|
|
56
|
+
// vector scores = (C @ query) / (row_norms(C) * norm(query) + tiny);
|
|
57
|
+
// int best = argmax(scores);
|
|
58
|
+
// return candidates[best];
|
|
59
|
+
// }
|
|
60
|
+
//
|
|
61
|
+
// Blocked on: `stack`, `@` matmul, `row_norms`, `argmax`, array indexing
|
|
62
|
+
// with a runtime int.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// select — softmax-weighted superposition of option vectors.
|
|
66
|
+
//
|
|
67
|
+
// function vector select(scalar[] scores, vector[] options) {
|
|
68
|
+
// scalar[] s = scores - max(scores);
|
|
69
|
+
// scalar[] w = exp(s);
|
|
70
|
+
// w = w / sum(w);
|
|
71
|
+
// return sum(w[i] * options[i] for i);
|
|
72
|
+
// }
|
|
73
|
+
//
|
|
74
|
+
// Blocked on: `max`, `exp`, `sum` reductions, broadcast-multiply over
|
|
75
|
+
// an array, comprehension-style reduction.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// snap — cleanup to nearest stored pattern. The canonical "lookup"
|
|
79
|
+
// operation on a memory substrate. Not supported on the default CPU
|
|
80
|
+
// runtime — requires a substrate-level cleanup circuit. See
|
|
81
|
+
// `planning/sutra-spec/operations.md` for the semantic contract —
|
|
82
|
+
// cleanup is a substrate-level primitive, not composable from other
|
|
83
|
+
// Sutra ops.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
// ==========================================================================
|
|
87
|
+
// Section 3 — intrinsic declarations
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
//
|
|
90
|
+
// These are runtime-implemented. Calls compile to `_VSA.<name>(...)`.
|
|
91
|
+
// The stubs above show the target Sutra-level definitions once the
|
|
92
|
+
// underlying primitives (dot, sqrt, tanh) get first-class surfaces;
|
|
93
|
+
// for now the body lives in the runtime class and these declarations
|
|
94
|
+
// expose the signatures at the Sutra level.
|
|
95
|
+
|
|
96
|
+
intrinsic function fuzzy eq(vector a, vector b);
|
|
97
|
+
intrinsic function scalar similarity(vector a, vector b);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Canonical Sutra declaration for the String class and its
|
|
2
|
+
// 1-length subclass Character.
|
|
3
|
+
//
|
|
4
|
+
// Spec: planning/sutra-spec/strings.md.
|
|
5
|
+
//
|
|
6
|
+
// A String is a synthetic-axis-encoded array of codepoints:
|
|
7
|
+
// - AXIS_STRING_FLAG (synthetic[3]) = 1.0 marks the value as a String.
|
|
8
|
+
// - char[0] at AXIS_REAL (synthetic[0]).
|
|
9
|
+
// - char[1] at AXIS_IMAG (synthetic[1]).
|
|
10
|
+
// - char[k] for k >= 2 at synthetic[k + 3].
|
|
11
|
+
// - Trailing zero codepoints are treated as end-of-string.
|
|
12
|
+
// At the substrate level a String is "the same shape as a complex
|
|
13
|
+
// vector" — pairs of codepoints occupy the real/imag positions of
|
|
14
|
+
// each successive 2D slot.
|
|
15
|
+
//
|
|
16
|
+
// A Character is a String of length 1. Same flag, same encoding,
|
|
17
|
+
// just a typing refinement so the type system can say "this slot
|
|
18
|
+
// holds exactly one codepoint" where useful. Character is a
|
|
19
|
+
// subclass of String.
|
|
20
|
+
//
|
|
21
|
+
// Surface forms wired today:
|
|
22
|
+
// - `String.make_string("hello")` builds a String from a Sutra
|
|
23
|
+
// string literal.
|
|
24
|
+
// - `s.string_length()` returns the length as an int.
|
|
25
|
+
// - `s.string_char_at(i)` returns the codepoint at position i.
|
|
26
|
+
// - `String.is_string(v)` checks whether a vector is flag-set.
|
|
27
|
+
//
|
|
28
|
+
// Method-syntax-without-prefix (`s.length()`, `s[i]`, `s == t`) is
|
|
29
|
+
// the eventual user-facing target; today the prefixed runtime names
|
|
30
|
+
// are what dispatch.
|
|
31
|
+
//
|
|
32
|
+
// What is deferred:
|
|
33
|
+
// - String concatenation: `String.concat(a, b)` — would build a
|
|
34
|
+
// new String whose chars are the chars of a followed by the
|
|
35
|
+
// chars of b. Need to decide overflow semantics.
|
|
36
|
+
// - String equality: `String.equals(a, b)` — bit-equality on the
|
|
37
|
+
// codepoint axes, ignoring everything else.
|
|
38
|
+
// - String indexing as `s[i]` — sugar for string_char_at; needs
|
|
39
|
+
// the existing subscript dispatch to recognize String-typed
|
|
40
|
+
// locals.
|
|
41
|
+
// - Operator overloading for `+` (concat) and `==`. Sutra's
|
|
42
|
+
// operator-method support is partial; this is a class-system
|
|
43
|
+
// follow-up.
|
|
44
|
+
|
|
45
|
+
class String extends vector {
|
|
46
|
+
static intrinsic method String make_string(string s);
|
|
47
|
+
static intrinsic method int string_length(String v);
|
|
48
|
+
static intrinsic method int string_char_at(String v, int i);
|
|
49
|
+
static intrinsic method bool is_string(vector v);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A Character is a 1-length String. Same substrate encoding, same
|
|
53
|
+
// AXIS_STRING_FLAG, just narrower static type. Today the only thing
|
|
54
|
+
// that distinguishes them statically is the declared type; the
|
|
55
|
+
// runtime returns the same vector shape from make_char and make_string.
|
|
56
|
+
class Character extends String { }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Sutra's leaf VSA primitives — the bottom of the function-expansion
|
|
2
|
+
// chain.
|
|
3
|
+
//
|
|
4
|
+
// Higher-level operations like `bind`, `unbind`, `bundle`, and
|
|
5
|
+
// `similarity` are NOT magical substrate calls. They beta-reduce to
|
|
6
|
+
// the operations declared in this file:
|
|
7
|
+
//
|
|
8
|
+
// bind(role, filler) -> MatrixMul(RotationFor(role), filler)
|
|
9
|
+
// unbind(role, record) -> MatrixMul(Transpose(RotationFor(role)), record)
|
|
10
|
+
// bundle(a, b, c, ...) -> Normalize(a + b + c + ...)
|
|
11
|
+
// similarity(a, b) -> Dot(a, b) / (Norm(a) * Norm(b))
|
|
12
|
+
//
|
|
13
|
+
// The point of this file is **transparency**. When a programmer asks
|
|
14
|
+
// "what does `bind` actually do at the lowest level?" — the answer
|
|
15
|
+
// is `MatrixMul(RotationFor(role), filler)` and they can verify that
|
|
16
|
+
// by reading the source here. Same for `bundle` (vector add +
|
|
17
|
+
// normalize), `similarity` (dot / norm), and so on.
|
|
18
|
+
//
|
|
19
|
+
// These ARE the VSA primitives. Every other operation in the
|
|
20
|
+
// language reduces to a chain of calls to these. The bind /
|
|
21
|
+
// unbind / bundle / similarity operations are convenience names
|
|
22
|
+
// for common compositions; the actual computational substrate is
|
|
23
|
+
// matmul + tensor product + outer + dot + transpose.
|
|
24
|
+
//
|
|
25
|
+
// Both PascalCase and snake_case names are exposed:
|
|
26
|
+
//
|
|
27
|
+
// vector x = Tensor.MatrixMul(M, v); // preferred
|
|
28
|
+
// vector x = Tensor.matmul(M, v); // also works
|
|
29
|
+
//
|
|
30
|
+
// And the bare-name aliases via the stdlib_loader's dual
|
|
31
|
+
// registration:
|
|
32
|
+
//
|
|
33
|
+
// vector x = MatrixMul(M, v); // bare-name shortcut
|
|
34
|
+
|
|
35
|
+
class Tensor extends vector {
|
|
36
|
+
// Matrix multiplication. Works on 1-D, 2-D, or higher-rank
|
|
37
|
+
// arguments per numpy/torch matmul semantics:
|
|
38
|
+
// (n,) @ (n,) -> scalar (dot)
|
|
39
|
+
// (m, k) @ (k, n) -> (m, n)
|
|
40
|
+
// (m, k) @ (k,) -> (m,) — vector treated as column
|
|
41
|
+
// (k,) @ (k, n) -> (n,) — vector treated as row
|
|
42
|
+
static intrinsic method vector MatrixMul(vector a, vector b);
|
|
43
|
+
static intrinsic method vector matmul(vector a, vector b);
|
|
44
|
+
|
|
45
|
+
// Kronecker / tensor product. For two vectors of length n and m,
|
|
46
|
+
// produces a vector of length n*m: every pairwise product. For
|
|
47
|
+
// higher-rank inputs, produces the standard Kronecker block
|
|
48
|
+
// matrix.
|
|
49
|
+
static intrinsic method vector TensorProduct(vector a, vector b);
|
|
50
|
+
static intrinsic method vector tensor_product(vector a, vector b);
|
|
51
|
+
|
|
52
|
+
// Outer product. For two 1-D vectors a (length n) and b (length
|
|
53
|
+
// m), produces a 2-D matrix `M` where `M[i, j] = a[i] * b[j]`.
|
|
54
|
+
static intrinsic method vector Outer(vector a, vector b);
|
|
55
|
+
static intrinsic method vector outer(vector a, vector b);
|
|
56
|
+
|
|
57
|
+
// Inner / dot product. Scalar result.
|
|
58
|
+
static intrinsic method scalar Dot(vector a, vector b);
|
|
59
|
+
static intrinsic method scalar dot(vector a, vector b);
|
|
60
|
+
|
|
61
|
+
// Transpose. For 2-D input, swaps the two axes; for 1-D, returns
|
|
62
|
+
// the input unchanged (numpy convention — there's no "row" vs
|
|
63
|
+
// "column" distinction at rank-1).
|
|
64
|
+
static intrinsic method vector Transpose(vector m);
|
|
65
|
+
static intrinsic method vector transpose(vector m);
|
|
66
|
+
|
|
67
|
+
// L2 norm of a vector. Scalar result.
|
|
68
|
+
static intrinsic method scalar Norm(vector v);
|
|
69
|
+
static intrinsic method scalar norm(vector v);
|
|
70
|
+
|
|
71
|
+
// L2 normalization. Returns v / Norm(v) with an eps-guard to
|
|
72
|
+
// make the zero-norm case behave (returns zero, no NaN).
|
|
73
|
+
static intrinsic method vector Normalize(vector v);
|
|
74
|
+
static intrinsic method vector normalize(vector v);
|
|
75
|
+
|
|
76
|
+
// Cached rotation matrix for a role vector. The role-vector hash
|
|
77
|
+
// keys a Haar-random orthogonal rotation in the semantic block
|
|
78
|
+
// (identity in the synthetic block) — the substrate's binding
|
|
79
|
+
// operator. Same role always produces the same rotation.
|
|
80
|
+
static intrinsic method vector RotationFor(vector role);
|
|
81
|
+
static intrinsic method vector rotation_for(vector role);
|
|
82
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Canonical Sutra definitions for VSA vector operations — bind,
|
|
2
|
+
// unbind, bundle, permute. These are the core hypervector primitives
|
|
3
|
+
// the language is built around.
|
|
4
|
+
//
|
|
5
|
+
// Today they're hardcoded as runtime methods in codegen.py — see
|
|
6
|
+
// `def bind`, `def unbind`, `def bundle`, `def bundle_of_binds`,
|
|
7
|
+
// `def sign_flip`, `def make_sign_flip_key`. This file is the target
|
|
8
|
+
// shape for the inliner. See stdlib/README.md.
|
|
9
|
+
//
|
|
10
|
+
// Every VSA primitive below is currently blocked on intrinsics —
|
|
11
|
+
// they need matrix literals, matmul, variadic reductions, or
|
|
12
|
+
// seeded-random primitives that aren't Sutra-callable yet. The
|
|
13
|
+
// bodies below are pseudo-Sutra showing the target expansion.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// ==========================================================================
|
|
17
|
+
// Section 2 — blocked on intrinsics
|
|
18
|
+
// ==========================================================================
|
|
19
|
+
//
|
|
20
|
+
// bind — role-seeded Haar-random orthogonal rotation applied to filler.
|
|
21
|
+
// The rotation is block-diagonal: Haar in the semantic block, identity
|
|
22
|
+
// in the synthetic block, so bind acts only on semantic content and
|
|
23
|
+
// leaves the real / imag / truth / char-flag axes untouched.
|
|
24
|
+
//
|
|
25
|
+
// function vector bind(vector role, vector filler) {
|
|
26
|
+
// matrix Q = rotation_for_role(role); // cached Haar rotation
|
|
27
|
+
// return Q @ filler;
|
|
28
|
+
// }
|
|
29
|
+
//
|
|
30
|
+
// Blocked on: `rotation_for_role` primitive (role-hash → cached
|
|
31
|
+
// rotation matrix), `@` matmul. The caching is what makes repeat
|
|
32
|
+
// bind with the same role one lookup + one matmul.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// unbind — inverse of bind. Same block-diagonal rotation, transposed.
|
|
36
|
+
//
|
|
37
|
+
// function vector unbind(vector role, vector bound) {
|
|
38
|
+
// matrix Q = rotation_for_role(role);
|
|
39
|
+
// return transpose(Q) @ bound;
|
|
40
|
+
// }
|
|
41
|
+
//
|
|
42
|
+
// Blocked on: `rotation_for_role`, `transpose`, `@` matmul.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// bundle — normalized superposition. Variadic: takes any number of
|
|
46
|
+
// vectors, sums them, L2-normalizes the result.
|
|
47
|
+
//
|
|
48
|
+
// function vector bundle(vector... args) {
|
|
49
|
+
// vector s = zero_vector();
|
|
50
|
+
// foreach (v in args) {
|
|
51
|
+
// s = s + v;
|
|
52
|
+
// }
|
|
53
|
+
// return s / (norm(s) + finfo_tiny);
|
|
54
|
+
// }
|
|
55
|
+
//
|
|
56
|
+
// Blocked on: variadic parameter surface (`vector... args`), `norm`,
|
|
57
|
+
// `finfo_tiny`, scalar-divide-vector.
|
|
58
|
+
//
|
|
59
|
+
// The fused bundle-of-binds shape the codegen already emits
|
|
60
|
+
// (`bundle_of_binds((r1,f1), (r2,f2), ...)` → one einsum) is a
|
|
61
|
+
// compile-time peephole on top of this spec definition.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// basis_vector — alias for `embed`. Allocates a deterministic vector
|
|
65
|
+
// keyed by the string name. Kept as a separate spelling for clarity
|
|
66
|
+
// in VSA-style programs (role / filler allocation) vs pure embedding.
|
|
67
|
+
//
|
|
68
|
+
// function vector basis_vector(string name) {
|
|
69
|
+
// return embed(name);
|
|
70
|
+
// }
|
|
71
|
+
//
|
|
72
|
+
// This one is actually writable as Sutra today — `embed(x)` is the
|
|
73
|
+
// parsable primitive. Not listed as implemented above only because
|
|
74
|
+
// the inliner to consume it doesn't exist yet; when it does, this
|
|
75
|
+
// function becomes real.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// permute — apply a permutation key to a vector. The permutation
|
|
79
|
+
// scheme is a deterministic ±1 mask multiplied elementwise with
|
|
80
|
+
// the vector.
|
|
81
|
+
//
|
|
82
|
+
// function vector permute(vector v, permutation key) {
|
|
83
|
+
// return v * sign_flip_mask(key);
|
|
84
|
+
// }
|
|
85
|
+
//
|
|
86
|
+
// Blocked on: `sign_flip_mask` primitive (key → ±1 vector),
|
|
87
|
+
// element-wise multiply between a vector and a mask.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// permutation_key — allocate a deterministic permutation key from a
|
|
91
|
+
// string name.
|
|
92
|
+
//
|
|
93
|
+
// function permutation permutation_key(string name) {
|
|
94
|
+
// int seed = string_hash(name);
|
|
95
|
+
// return random_sign_mask(seed);
|
|
96
|
+
// }
|
|
97
|
+
//
|
|
98
|
+
// Blocked on: `string_hash`, `random_sign_mask` primitives.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
// identity_permutation — the no-op permutation. Applying it leaves
|
|
102
|
+
// a vector unchanged. Useful as a sentinel / default.
|
|
103
|
+
//
|
|
104
|
+
// function permutation identity_permutation() {
|
|
105
|
+
// return ones_mask();
|
|
106
|
+
// }
|
|
107
|
+
//
|
|
108
|
+
// Blocked on: `ones_mask` primitive (a ±1 mask that's all +1).
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
// compose — compose two permutations. With the ±1-mask
|
|
112
|
+
// representation, composition is element-wise multiply of the
|
|
113
|
+
// two masks.
|
|
114
|
+
//
|
|
115
|
+
// function permutation compose(permutation p, permutation q) {
|
|
116
|
+
// return p * q;
|
|
117
|
+
// }
|
|
118
|
+
//
|
|
119
|
+
// Blocked on: permutation type's Sutra-level operator semantics.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Loader for the Sutra standard library.
|
|
2
|
+
|
|
3
|
+
Walks `sutra_compiler/stdlib/*.su` at compiler init, parses each file,
|
|
4
|
+
and returns a symbol table mapping function names to their parsed
|
|
5
|
+
`FunctionDecl` AST nodes. The inliner pass consumes this table: when
|
|
6
|
+
it sees a `Call(Identifier(name), args)` whose name is in the table,
|
|
7
|
+
it beta-reduces the function body into the caller's AST.
|
|
8
|
+
|
|
9
|
+
This is the first stage in the stdlib lowering pipeline. Subsequent
|
|
10
|
+
stages (the inliner pass itself, unroll propagation, runtime-method
|
|
11
|
+
deletion, intrinsic mechanism, fusion pass) build on the symbol
|
|
12
|
+
table this module produces.
|
|
13
|
+
|
|
14
|
+
The loader is deliberately simple:
|
|
15
|
+
- No caching across processes. The parse is fast (7 files, ~600
|
|
16
|
+
lines total today) and runs once per compiler instantiation.
|
|
17
|
+
- No transitive resolution. If stdlib function A calls stdlib
|
|
18
|
+
function B, the inliner handles the nesting by inlining A first
|
|
19
|
+
and then running the pass again (or in one fixpoint pass) — this
|
|
20
|
+
module just returns the raw declarations.
|
|
21
|
+
- Duplicate names across files are a hard error. A function should
|
|
22
|
+
live in exactly one .su file; see stdlib/README.md's category
|
|
23
|
+
split.
|
|
24
|
+
- Parse diagnostics from stdlib files are fatal — the stdlib is the
|
|
25
|
+
compiler's own code, not user input. A broken stdlib is a compiler
|
|
26
|
+
bug.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
from typing import Dict, List, Optional
|
|
33
|
+
|
|
34
|
+
from . import ast_nodes as ast
|
|
35
|
+
from .lexer import Lexer
|
|
36
|
+
from .parser import Parser
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
STDLIB_DIR = os.path.join(os.path.dirname(__file__), "stdlib")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StdlibLoadError(Exception):
|
|
43
|
+
"""Raised when the stdlib fails to load. Always a compiler bug —
|
|
44
|
+
never a user error. Carries the file path and underlying reason
|
|
45
|
+
so the stacktrace points at the real problem."""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_stdlib(stdlib_dir: str = STDLIB_DIR) -> Dict[str, ast.FunctionDecl]:
|
|
49
|
+
"""Return `{function_name → FunctionDecl}` for every function
|
|
50
|
+
declaration in every `*.su` file under the stdlib directory.
|
|
51
|
+
|
|
52
|
+
Both shapes are picked up:
|
|
53
|
+
1. Top-level `function ...` and `intrinsic function ...;` —
|
|
54
|
+
the original stdlib form. Goes into the table by its bare
|
|
55
|
+
name (`log`, `bind`, etc.).
|
|
56
|
+
2. Class-body static methods on stdlib classes (`class Math {
|
|
57
|
+
static intrinsic method scalar log(scalar x); ... }`) — the
|
|
58
|
+
post-2026-05-01 namespaced form. Goes into the table under
|
|
59
|
+
BOTH the bare name (`log`) and the namespaced name
|
|
60
|
+
(`Math.log`). The bare-name entry preserves backward
|
|
61
|
+
compatibility for user code that still calls `log(x)`; the
|
|
62
|
+
namespaced entry supports the future call shape
|
|
63
|
+
`Math.log(x)`. Class-bodied static methods are repackaged
|
|
64
|
+
as `FunctionDecl` so callers don't have to know which
|
|
65
|
+
shape they came from.
|
|
66
|
+
|
|
67
|
+
Method declarations on instances (non-static), top-level
|
|
68
|
+
statements, and class bodies that aren't static-method bearing
|
|
69
|
+
are ignored — the stdlib is callable-namespace-only by design.
|
|
70
|
+
"""
|
|
71
|
+
table: Dict[str, ast.FunctionDecl] = {}
|
|
72
|
+
|
|
73
|
+
def _add(name: str, decl: ast.FunctionDecl, path: str) -> None:
|
|
74
|
+
if name in table:
|
|
75
|
+
existing = table[name]
|
|
76
|
+
raise StdlibLoadError(
|
|
77
|
+
f"duplicate stdlib function {name!r}: "
|
|
78
|
+
f"declared at {path}:{decl.span.start.line} and "
|
|
79
|
+
f"previously at {existing.span.start.line} in an "
|
|
80
|
+
f"earlier file. Each function should live in one "
|
|
81
|
+
f"stdlib file; see stdlib/README.md for the "
|
|
82
|
+
f"category split."
|
|
83
|
+
)
|
|
84
|
+
table[name] = decl
|
|
85
|
+
|
|
86
|
+
for fname in sorted(os.listdir(stdlib_dir)):
|
|
87
|
+
if not fname.endswith(".su"):
|
|
88
|
+
continue
|
|
89
|
+
path = os.path.join(stdlib_dir, fname)
|
|
90
|
+
module = _parse_stdlib_file(path)
|
|
91
|
+
for item in module.items:
|
|
92
|
+
if isinstance(item, ast.FunctionDecl):
|
|
93
|
+
_add(item.name, item, path)
|
|
94
|
+
elif isinstance(item, ast.ClassDecl):
|
|
95
|
+
for m in item.methods:
|
|
96
|
+
if (m.modifiers.is_static
|
|
97
|
+
and not m.is_operator
|
|
98
|
+
and not m.type_params):
|
|
99
|
+
# Repackage as a FunctionDecl so callers don't
|
|
100
|
+
# need to special-case class-bodied entries.
|
|
101
|
+
repackaged = ast.FunctionDecl(
|
|
102
|
+
modifiers=m.modifiers,
|
|
103
|
+
return_type=m.return_type,
|
|
104
|
+
name=m.name,
|
|
105
|
+
type_params=m.type_params,
|
|
106
|
+
params=m.params,
|
|
107
|
+
body=m.body,
|
|
108
|
+
is_operator=False,
|
|
109
|
+
is_intrinsic=m.is_intrinsic,
|
|
110
|
+
span=m.span,
|
|
111
|
+
)
|
|
112
|
+
# Bare-name entry (backward compat).
|
|
113
|
+
_add(m.name, repackaged, path)
|
|
114
|
+
# Namespaced entry (`Math.log`) — duplicates
|
|
115
|
+
# the AST node, which is fine because lookups
|
|
116
|
+
# don't mutate it.
|
|
117
|
+
_add(f"{item.name}.{m.name}", repackaged, path)
|
|
118
|
+
|
|
119
|
+
return table
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _parse_stdlib_file(path: str) -> ast.Module:
|
|
123
|
+
"""Lex + parse one stdlib .su file. Fatal on any diagnostic."""
|
|
124
|
+
with open(path, encoding="utf-8") as fp:
|
|
125
|
+
src = fp.read()
|
|
126
|
+
lexer = Lexer(src, file=path)
|
|
127
|
+
tokens = lexer.tokenize()
|
|
128
|
+
if lexer.diagnostics.has_errors():
|
|
129
|
+
raise StdlibLoadError(
|
|
130
|
+
f"{path}: stdlib lex errors — compiler bug:\n"
|
|
131
|
+
+ "\n".join(d.format() for d in lexer.diagnostics)
|
|
132
|
+
)
|
|
133
|
+
parser = Parser(tokens, file=path, diagnostics=lexer.diagnostics)
|
|
134
|
+
module = parser.parse_module()
|
|
135
|
+
if lexer.diagnostics.has_errors():
|
|
136
|
+
raise StdlibLoadError(
|
|
137
|
+
f"{path}: stdlib parse errors — compiler bug:\n"
|
|
138
|
+
+ "\n".join(d.format() for d in lexer.diagnostics)
|
|
139
|
+
)
|
|
140
|
+
return module
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def stdlib_function_names(stdlib_dir: str = STDLIB_DIR) -> List[str]:
|
|
144
|
+
"""Names of every function declaration the stdlib contributes.
|
|
145
|
+
|
|
146
|
+
Convenience wrapper around `load_stdlib` — returns just the names,
|
|
147
|
+
sorted, for diagnostics and documentation. A runtime-methods
|
|
148
|
+
removal pass can use this as the authoritative list of "stdlib
|
|
149
|
+
callers exist; this runtime method is a candidate for deletion
|
|
150
|
+
once the inliner is wired."
|
|
151
|
+
"""
|
|
152
|
+
return sorted(load_stdlib(stdlib_dir).keys())
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Module-level cache of the intrinsic names — names declared via
|
|
156
|
+
# `intrinsic function ...;` in any stdlib file. The codegen uses this
|
|
157
|
+
# to route `Call(Identifier(name), args)` to `_VSA.<name>(args)`
|
|
158
|
+
# when name is an intrinsic (the runtime class implements it).
|
|
159
|
+
# Populated lazily on first access; safe because stdlib source is
|
|
160
|
+
# frozen for a given process.
|
|
161
|
+
_INTRINSIC_NAMES_CACHE: Optional[frozenset] = None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def intrinsic_names(stdlib_dir: str = STDLIB_DIR) -> frozenset:
|
|
165
|
+
"""Return the frozenset of intrinsic function names declared in
|
|
166
|
+
the stdlib — the leaves the runtime must implement.
|
|
167
|
+
|
|
168
|
+
Includes both bare-name and namespaced (`Math.log`) entries since
|
|
169
|
+
the dual-registration pattern (load_stdlib) puts both into the
|
|
170
|
+
table. Callers that need only one form can filter with `.`."""
|
|
171
|
+
global _INTRINSIC_NAMES_CACHE
|
|
172
|
+
if _INTRINSIC_NAMES_CACHE is None:
|
|
173
|
+
table = load_stdlib(stdlib_dir)
|
|
174
|
+
_INTRINSIC_NAMES_CACHE = frozenset(
|
|
175
|
+
name for name, decl in table.items()
|
|
176
|
+
if getattr(decl, "is_intrinsic", False)
|
|
177
|
+
)
|
|
178
|
+
return _INTRINSIC_NAMES_CACHE
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# Per-class intrinsic-method registry, populated lazily. Maps
|
|
182
|
+
# `class_name -> frozenset of intrinsic method names`. Used by the
|
|
183
|
+
# codegen to dispatch `Tensor.MatrixMul(a, b)` to `_VSA.MatrixMul(a, b)`
|
|
184
|
+
# when the class is declared in stdlib (and therefore not in the
|
|
185
|
+
# user's module AST).
|
|
186
|
+
_STDLIB_CLASS_INTRINSICS_CACHE: Optional[Dict[str, frozenset]] = None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def stdlib_class_intrinsic_methods(
|
|
190
|
+
stdlib_dir: str = STDLIB_DIR,
|
|
191
|
+
) -> Dict[str, frozenset]:
|
|
192
|
+
"""Return `{class_name: frozenset(method_names)}` for every
|
|
193
|
+
static-intrinsic method declared inside a stdlib class body.
|
|
194
|
+
|
|
195
|
+
Lets the codegen dispatch namespaced stdlib calls
|
|
196
|
+
(`Tensor.MatrixMul(a, b)`) to the runtime alongside its existing
|
|
197
|
+
user-class dispatch path.
|
|
198
|
+
"""
|
|
199
|
+
global _STDLIB_CLASS_INTRINSICS_CACHE
|
|
200
|
+
if _STDLIB_CLASS_INTRINSICS_CACHE is None:
|
|
201
|
+
result: Dict[str, set] = {}
|
|
202
|
+
for fname in sorted(os.listdir(stdlib_dir)):
|
|
203
|
+
if not fname.endswith(".su"):
|
|
204
|
+
continue
|
|
205
|
+
path = os.path.join(stdlib_dir, fname)
|
|
206
|
+
module = _parse_stdlib_file(path)
|
|
207
|
+
for item in module.items:
|
|
208
|
+
if not isinstance(item, ast.ClassDecl):
|
|
209
|
+
continue
|
|
210
|
+
for m in item.methods:
|
|
211
|
+
if (m.is_intrinsic
|
|
212
|
+
and m.modifiers.is_static
|
|
213
|
+
and not m.is_operator
|
|
214
|
+
and not m.type_params):
|
|
215
|
+
result.setdefault(item.name, set()).add(m.name)
|
|
216
|
+
_STDLIB_CLASS_INTRINSICS_CACHE = {
|
|
217
|
+
cls: frozenset(names) for cls, names in result.items()
|
|
218
|
+
}
|
|
219
|
+
return _STDLIB_CLASS_INTRINSICS_CACHE
|