@hviana/sema 0.1.4 → 0.1.5
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.
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +21 -19
- package/dist/example/train_base.js +1952 -1684
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +27 -24
- package/dist/src/alu/src/alu.d.ts +194 -169
- package/dist/src/alu/src/alu.js +414 -374
- package/dist/src/alu/src/expr.d.ts +43 -36
- package/dist/src/alu/src/expr.js +278 -252
- package/dist/src/alu/src/index.d.ts +98 -9
- package/dist/src/alu/src/index.js +63 -9
- package/dist/src/alu/src/kernel-arith.d.ts +5 -1
- package/dist/src/alu/src/kernel-arith.js +285 -195
- package/dist/src/alu/src/kernel-bits.d.ts +5 -1
- package/dist/src/alu/src/kernel-bits.js +115 -81
- package/dist/src/alu/src/kernel-logic.js +63 -33
- package/dist/src/alu/src/kernel-nd.js +195 -155
- package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
- package/dist/src/alu/src/kernel-numeric.js +331 -259
- package/dist/src/alu/src/operation.d.ts +132 -112
- package/dist/src/alu/src/operation.js +163 -149
- package/dist/src/alu/src/parser.d.ts +175 -168
- package/dist/src/alu/src/parser.js +421 -382
- package/dist/src/alu/src/resonance.d.ts +25 -16
- package/dist/src/alu/src/resonance.js +56 -49
- package/dist/src/alu/src/text.d.ts +7 -3
- package/dist/src/alu/src/text.js +37 -31
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +160 -150
- package/dist/src/alu/test/alu.test.js +656 -543
- package/dist/src/bytes.d.ts +5 -1
- package/dist/src/bytes.js +40 -33
- package/dist/src/config.d.ts +102 -102
- package/dist/src/config.js +83 -83
- package/dist/src/derive/src/deduction.d.ts +62 -60
- package/dist/src/derive/src/deduction.js +109 -103
- package/dist/src/derive/src/index.d.ts +7 -1
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +61 -57
- package/dist/src/derive/src/rewrite.d.ts +18 -15
- package/dist/src/derive/src/rewrite.js +67 -59
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +179 -175
- package/dist/src/derive/test/derive.test.js +100 -96
- package/dist/src/extension.d.ts +16 -13
- package/dist/src/geometry.d.ts +42 -17
- package/dist/src/geometry.js +266 -235
- package/dist/src/index.d.ts +17 -2
- package/dist/src/index.js +7 -1
- package/dist/src/ingest-cache.d.ts +30 -25
- package/dist/src/ingest-cache.js +127 -108
- package/dist/src/mind/articulation.d.ts +5 -1
- package/dist/src/mind/articulation.js +112 -77
- package/dist/src/mind/attention.d.ts +108 -40
- package/dist/src/mind/attention.js +871 -749
- package/dist/src/mind/canonical.d.ts +19 -4
- package/dist/src/mind/canonical.js +31 -27
- package/dist/src/mind/graph-search.d.ts +225 -201
- package/dist/src/mind/graph-search.js +821 -742
- package/dist/src/mind/index.d.ts +10 -2
- package/dist/src/mind/junction.d.ts +58 -31
- package/dist/src/mind/junction.js +237 -172
- package/dist/src/mind/learning.d.ts +52 -16
- package/dist/src/mind/learning.js +165 -143
- package/dist/src/mind/match.d.ts +69 -20
- package/dist/src/mind/match.js +318 -259
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +13 -9
- package/dist/src/mind/mechanisms/cast.js +456 -363
- package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
- package/dist/src/mind/mechanisms/confluence.js +183 -152
- package/dist/src/mind/mechanisms/cover.d.ts +8 -2
- package/dist/src/mind/mechanisms/cover.js +210 -148
- package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
- package/dist/src/mind/mechanisms/extraction.js +288 -234
- package/dist/src/mind/mechanisms/recall.d.ts +10 -6
- package/dist/src/mind/mechanisms/recall.js +185 -126
- package/dist/src/mind/mind.d.ts +154 -129
- package/dist/src/mind/mind.js +288 -258
- package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
- package/dist/src/mind/pipeline-mechanism.js +172 -161
- package/dist/src/mind/pipeline.d.ts +14 -4
- package/dist/src/mind/pipeline.js +189 -125
- package/dist/src/mind/primitives.d.ts +32 -8
- package/dist/src/mind/primitives.js +117 -99
- package/dist/src/mind/rationale.d.ts +98 -86
- package/dist/src/mind/rationale.js +121 -113
- package/dist/src/mind/reasoning.d.ts +13 -2
- package/dist/src/mind/reasoning.js +166 -129
- package/dist/src/mind/recognition.d.ts +4 -1
- package/dist/src/mind/recognition.js +208 -183
- package/dist/src/mind/resonance.d.ts +22 -5
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +25 -6
- package/dist/src/mind/trace.js +58 -50
- package/dist/src/mind/traverse.d.ts +58 -16
- package/dist/src/mind/traverse.js +357 -304
- package/dist/src/mind/types.d.ts +127 -120
- package/dist/src/mind/types.js +69 -60
- package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
- package/dist/src/rabitq-hnsw/src/database.js +359 -342
- package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
- package/dist/src/rabitq-hnsw/src/heap.js +80 -75
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
- package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
- package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
- package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
- package/dist/src/rabitq-hnsw/src/prng.js +27 -25
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
- package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
- package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
- package/dist/src/rabitq-hnsw/src/store.js +843 -752
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
- package/dist/src/sema.d.ts +13 -9
- package/dist/src/sema.js +40 -26
- package/dist/src/store-sqlite.d.ts +166 -144
- package/dist/src/store-sqlite.js +660 -578
- package/dist/src/store.d.ts +630 -593
- package/dist/src/store.js +1581 -1440
- package/dist/src/vec.d.ts +9 -5
- package/dist/src/vec.js +73 -61
- package/package.json +2 -2
|
@@ -6,29 +6,29 @@ export type Arity = number | "variadic";
|
|
|
6
6
|
* This is the only path by which a computation reads MEANING from a symbol's
|
|
7
7
|
* opaque bytes; numbers never touch it. */
|
|
8
8
|
export interface ResonanceSync {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
/** The resonant opposite of a symbol's bytes — its antonym / inverse in the
|
|
10
|
+
* resonance space, of whatever modality — or null if none is known. Found
|
|
11
|
+
* by the host's halo resonance and handed in pre-resolved (see
|
|
12
|
+
* resonance.ts). */
|
|
13
|
+
opposite(bytes: Uint8Array): Uint8Array | null;
|
|
14
|
+
/** The canonical operation a symbol's bytes MEAN, found by resonance over the
|
|
15
|
+
* operation concepts — the GENERIC, modality-agnostic recognition path, the
|
|
16
|
+
* same one the host uses to recognise an operator a query does not literally
|
|
17
|
+
* spell. Pre-resolved by the host; null when nothing resonates. This is how
|
|
18
|
+
* the FUNCTION ARGUMENT of a higher-order nd op (the "+" of a reduce, the
|
|
19
|
+
* predicate of a filter) is resolved when it is not a literal surface form —
|
|
20
|
+
* see {@link OpContext.resolveOp}. */
|
|
21
|
+
recogniseOp(bytes: Uint8Array): string | null;
|
|
22
22
|
}
|
|
23
23
|
/** Runtime knobs the numerical kernel reads (the limit layer's tolerance and
|
|
24
24
|
* iteration ceiling). Carried on the context so a derived op never closes
|
|
25
25
|
* over global state. */
|
|
26
26
|
export interface OpRuntime {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
/** Convergence tolerance ε: a refinement stops when successive results agree
|
|
28
|
+
* within this. */
|
|
29
|
+
tol: number;
|
|
30
|
+
/** Hard iteration ceiling, so a non-converging step still terminates. */
|
|
31
|
+
maxIter: number;
|
|
32
32
|
}
|
|
33
33
|
/** Evaluate a symbolic EXPRESSION at a binding of its free variable, returning a
|
|
34
34
|
* plain number, or null when the expression cannot be evaluated (unparseable,
|
|
@@ -39,42 +39,46 @@ export interface OpRuntime {
|
|
|
39
39
|
* application of the same ALU. Injected by the host (the {@link
|
|
40
40
|
* "./alu.js".Alu} facade); absent in a bare registry, where expression-ops
|
|
41
41
|
* simply decline. */
|
|
42
|
-
export type EvalExpr = (
|
|
42
|
+
export type EvalExpr = (
|
|
43
|
+
bytes: Uint8Array,
|
|
44
|
+
variable: string,
|
|
45
|
+
at: number,
|
|
46
|
+
) => number | null;
|
|
43
47
|
/** The context a callback runs in: the means to call sibling ops by name, the
|
|
44
48
|
* runtime knobs, pre-resolved resonance, and (when wired) an expression
|
|
45
49
|
* evaluator. This is what makes derivations compose without import cycles
|
|
46
50
|
* between kernel files. */
|
|
47
51
|
export interface OpContext {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
/** Apply another registered op by canonical name. */
|
|
53
|
+
apply(name: string, args: Value[]): Value;
|
|
54
|
+
/** Whether an op is registered (lets a derivation pick a fast path when an
|
|
55
|
+
* optional sibling exists). */
|
|
56
|
+
has(name: string): boolean;
|
|
57
|
+
/** Resolve an operation-denoting VALUE to a canonical op name — the
|
|
58
|
+
* intelligent callback resolution the higher-order nd ops (map/reduce/filter/
|
|
59
|
+
* find) use for their FUNCTION ARGUMENT. An operation is named the SAME way
|
|
60
|
+
* any operation is recognised anywhere in the kernel, reusing the existing
|
|
61
|
+
* machinery rather than a bespoke table:
|
|
62
|
+
*
|
|
63
|
+
* 1. a literal SURFACE FORM — the op-value's bytes are a registered spelling
|
|
64
|
+
* ("+", "plus", "add", "*", "max", …) → the registry's own form index;
|
|
65
|
+
* 2. else its MEANING — {@link ResonanceSync.recogniseOp} over the same
|
|
66
|
+
* operation concepts, so a synonym or a multimodal gesture the bytes do
|
|
67
|
+
* not literally spell still resolves (pre-resolved by the host);
|
|
68
|
+
* 3. else a bare canonical NAME already (a symbol whose bytes equal an op
|
|
69
|
+
* name), or an int/bit treated as its decimal name — last resort.
|
|
70
|
+
*
|
|
71
|
+
* `arity` (when given) disambiguates a surface a unary and a binary op share
|
|
72
|
+
* (e.g. "-" is both negate and subtract): the claimant of that arity wins.
|
|
73
|
+
* Returns null when nothing resolves, so a higher-order op declines. */
|
|
74
|
+
resolveOp(op: Value, arity?: number): string | null;
|
|
75
|
+
/** The numerical runtime knobs. */
|
|
76
|
+
rt: OpRuntime;
|
|
77
|
+
/** Pre-resolved resonance for the polymorphic inverse and op recognition. */
|
|
78
|
+
resonance: ResonanceSync;
|
|
79
|
+
/** Evaluate a symbolic expression at a variable binding (see {@link
|
|
80
|
+
* EvalExpr}); undefined in a bare registry with no host evaluator. */
|
|
81
|
+
evalExpr?: EvalExpr;
|
|
78
82
|
}
|
|
79
83
|
/** The callback that executes an operation on resolved operands. */
|
|
80
84
|
export type OpFn = (args: Value[], ctx: OpContext) => Value;
|
|
@@ -83,36 +87,36 @@ export type OpFn = (args: Value[], ctx: OpContext) => Value;
|
|
|
83
87
|
* read off the registry rather than hardcoded in any parser. Higher
|
|
84
88
|
* `precedence` binds tighter. */
|
|
85
89
|
export interface InfixSyntax {
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
precedence: number;
|
|
91
|
+
rightAssoc?: boolean;
|
|
88
92
|
}
|
|
89
93
|
/** Optional syntactic/structure traits of an operation, declared where the op
|
|
90
94
|
* is registered. Every parser-facing distinction the ALU makes is one of
|
|
91
95
|
* these traits — there is no side table of names anywhere. */
|
|
92
96
|
export interface OpTraits {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
/** Consumes an n-dimensional value WHOLE (exempt from broadcast). */
|
|
98
|
+
structural?: boolean;
|
|
99
|
+
/** Usable as an infix operator, with the given binding. */
|
|
100
|
+
infix?: InfixSyntax;
|
|
101
|
+
/** The FIRST operand is an EXPRESSION (a function's bytes) rather than a
|
|
102
|
+
* value; the remaining `arity − 1` operands are its points/bounds. The
|
|
103
|
+
* numerical layer's diff/integrate/solve/limit/optimize declare this. */
|
|
104
|
+
expression?: boolean;
|
|
101
105
|
}
|
|
102
106
|
/** An operation — primitive or derived, indistinguishable by type. */
|
|
103
107
|
export interface Operation extends OpTraits {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
/** Canonical id, e.g. "add", "nand", "converge", "sin". */
|
|
109
|
+
name: string;
|
|
110
|
+
/** Operand count (fixed) or "variadic". */
|
|
111
|
+
arity: Arity;
|
|
112
|
+
/** True only for the kernel's irreducible roots. */
|
|
113
|
+
primitive: boolean;
|
|
114
|
+
/** Surface forms this op answers to — the spellings resonance maps to it,
|
|
115
|
+
* e.g. add ← ["+", "plus", "sum", "add"]. May be empty for an internal op
|
|
116
|
+
* that has no user-facing surface. */
|
|
117
|
+
forms: string[];
|
|
118
|
+
/** The callback. */
|
|
119
|
+
fn: OpFn;
|
|
116
120
|
}
|
|
117
121
|
/** A resonance that knows nothing — the default when no host is wired in, so
|
|
118
122
|
* the pure kernel and its tests run with zero coupling. Op recognition then
|
|
@@ -124,45 +128,61 @@ export declare const NO_RESONANCE: ResonanceSync;
|
|
|
124
128
|
* respect the dependency DAG loosely (a derived op may reference an op
|
|
125
129
|
* registered later, since resolution is lazy). */
|
|
126
130
|
export declare class OperationRegistry {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
131
|
+
private readonly ops;
|
|
132
|
+
/** Surface form → the canonical names that claim it. A form may be shared
|
|
133
|
+
* (e.g. "-" is both subtract and negate); the caller disambiguates by arity
|
|
134
|
+
* and position, so the index keeps every claimant. */
|
|
135
|
+
private readonly formIndex;
|
|
136
|
+
/** Register an operation record directly. */
|
|
137
|
+
register(op: Operation): void;
|
|
138
|
+
/** Register a PRIMITIVE op (hand-written callback), with optional {@link
|
|
139
|
+
* OpTraits} — structural (consumes an nd whole, exempt from broadcast),
|
|
140
|
+
* infix binding, expression-operand — declared here, next to the op. */
|
|
141
|
+
prim(
|
|
142
|
+
name: string,
|
|
143
|
+
arity: Arity,
|
|
144
|
+
forms: string[],
|
|
145
|
+
fn: OpFn,
|
|
146
|
+
traits?: OpTraits,
|
|
147
|
+
): void;
|
|
148
|
+
/** Register a DERIVED op (callback composes other ops via ctx). `traits`
|
|
149
|
+
* as in {@link prim}. */
|
|
150
|
+
derive(
|
|
151
|
+
name: string,
|
|
152
|
+
arity: Arity,
|
|
153
|
+
forms: string[],
|
|
154
|
+
fn: OpFn,
|
|
155
|
+
traits?: OpTraits,
|
|
156
|
+
): void;
|
|
157
|
+
/** The op with this canonical name, or undefined. */
|
|
158
|
+
get(name: string): Operation | undefined;
|
|
159
|
+
has(name: string): boolean;
|
|
160
|
+
/** The canonical names a surface form maps to (empty if none). Several ops
|
|
161
|
+
* can share one surface (unary vs binary "-"), so this returns all. */
|
|
162
|
+
lookupForm(form: string): readonly string[];
|
|
163
|
+
/** Every (surface form → canonical name) pair — the host enumerates these to
|
|
164
|
+
* seed its operator recogniser. */
|
|
165
|
+
formEntries(): Iterable<{
|
|
166
|
+
form: string;
|
|
167
|
+
name: string;
|
|
168
|
+
}>;
|
|
169
|
+
/** Every registered canonical name. */
|
|
170
|
+
names(): Iterable<string>;
|
|
171
|
+
/** Build a context bound to a resonance and runtime — the object derived
|
|
172
|
+
* callbacks call back into. `apply` validates arity-vs-presence and surfaces
|
|
173
|
+
* a clear error rather than letting an undefined op silently produce NaN.
|
|
174
|
+
* An optional expression evaluator lets the numerical layer act on functions
|
|
175
|
+
* (see {@link EvalExpr}). */
|
|
176
|
+
context(
|
|
177
|
+
resonance: ResonanceSync,
|
|
178
|
+
rt: OpRuntime,
|
|
179
|
+
evalExpr?: EvalExpr,
|
|
180
|
+
): OpContext;
|
|
181
|
+
/** Resolve an operation-denoting value to a canonical op name — the shared
|
|
182
|
+
* machinery behind {@link OpContext.resolveOp}. Tries, in order: a literal
|
|
183
|
+
* surface form (the registry's own index, arity-disambiguated), the meaning
|
|
184
|
+
* via `resonance.recogniseOp`, then a bare canonical name / decimal reading.
|
|
185
|
+
* Returns null when nothing resolves. Static-shaped (takes the resonance
|
|
186
|
+
* explicitly) so both the context closure and callers can use it. */
|
|
187
|
+
resolveOp(op: Value, resonance: ResonanceSync, arity?: number): string | null;
|
|
168
188
|
}
|
|
@@ -21,8 +21,8 @@ import { isNd, nd } from "./value.js";
|
|
|
21
21
|
* the pure kernel and its tests run with zero coupling. Op recognition then
|
|
22
22
|
* falls back to literal surface forms / canonical names only. */
|
|
23
23
|
export const NO_RESONANCE = {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
opposite: () => null,
|
|
25
|
+
recogniseOp: () => null,
|
|
26
26
|
};
|
|
27
27
|
/** The collection of operations, indexed by canonical name and by surface form.
|
|
28
28
|
* Building a kernel is a sequence of {@link prim}/{@link derive} calls; the
|
|
@@ -30,160 +30,174 @@ export const NO_RESONANCE = {
|
|
|
30
30
|
* respect the dependency DAG loosely (a derived op may reference an op
|
|
31
31
|
* registered later, since resolution is lazy). */
|
|
32
32
|
export class OperationRegistry {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
else
|
|
48
|
-
this.formIndex.set(f, [op.name]);
|
|
33
|
+
ops = new Map();
|
|
34
|
+
/** Surface form → the canonical names that claim it. A form may be shared
|
|
35
|
+
* (e.g. "-" is both subtract and negate); the caller disambiguates by arity
|
|
36
|
+
* and position, so the index keeps every claimant. */
|
|
37
|
+
formIndex = new Map();
|
|
38
|
+
/** Register an operation record directly. */
|
|
39
|
+
register(op) {
|
|
40
|
+
this.ops.set(op.name, op);
|
|
41
|
+
for (const f of op.forms) {
|
|
42
|
+
const a = this.formIndex.get(f);
|
|
43
|
+
if (a) {
|
|
44
|
+
if (!a.includes(op.name)) {
|
|
45
|
+
a.push(op.name);
|
|
49
46
|
}
|
|
47
|
+
} else {
|
|
48
|
+
this.formIndex.set(f, [op.name]);
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
51
|
+
}
|
|
52
|
+
/** Register a PRIMITIVE op (hand-written callback), with optional {@link
|
|
53
|
+
* OpTraits} — structural (consumes an nd whole, exempt from broadcast),
|
|
54
|
+
* infix binding, expression-operand — declared here, next to the op. */
|
|
55
|
+
prim(name, arity, forms, fn, traits = {}) {
|
|
56
|
+
this.register({ name, arity, primitive: true, forms, fn, ...traits });
|
|
57
|
+
}
|
|
58
|
+
/** Register a DERIVED op (callback composes other ops via ctx). `traits`
|
|
59
|
+
* as in {@link prim}. */
|
|
60
|
+
derive(name, arity, forms, fn, traits = {}) {
|
|
61
|
+
this.register({ name, arity, primitive: false, forms, fn, ...traits });
|
|
62
|
+
}
|
|
63
|
+
/** The op with this canonical name, or undefined. */
|
|
64
|
+
get(name) {
|
|
65
|
+
return this.ops.get(name);
|
|
66
|
+
}
|
|
67
|
+
has(name) {
|
|
68
|
+
return this.ops.has(name);
|
|
69
|
+
}
|
|
70
|
+
/** The canonical names a surface form maps to (empty if none). Several ops
|
|
71
|
+
* can share one surface (unary vs binary "-"), so this returns all. */
|
|
72
|
+
lookupForm(form) {
|
|
73
|
+
return this.formIndex.get(form) ?? [];
|
|
74
|
+
}
|
|
75
|
+
/** Every (surface form → canonical name) pair — the host enumerates these to
|
|
76
|
+
* seed its operator recogniser. */
|
|
77
|
+
*formEntries() {
|
|
78
|
+
for (const [form, names] of this.formIndex) {
|
|
79
|
+
for (const name of names) {
|
|
80
|
+
yield { form, name };
|
|
81
|
+
}
|
|
68
82
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
}
|
|
84
|
+
/** Every registered canonical name. */
|
|
85
|
+
names() {
|
|
86
|
+
return this.ops.keys();
|
|
87
|
+
}
|
|
88
|
+
/** Build a context bound to a resonance and runtime — the object derived
|
|
89
|
+
* callbacks call back into. `apply` validates arity-vs-presence and surfaces
|
|
90
|
+
* a clear error rather than letting an undefined op silently produce NaN.
|
|
91
|
+
* An optional expression evaluator lets the numerical layer act on functions
|
|
92
|
+
* (see {@link EvalExpr}). */
|
|
93
|
+
context(resonance, rt, evalExpr) {
|
|
94
|
+
const self = this;
|
|
95
|
+
const ctx = {
|
|
96
|
+
rt,
|
|
97
|
+
resonance,
|
|
98
|
+
evalExpr,
|
|
99
|
+
has: (name) => self.ops.has(name),
|
|
100
|
+
resolveOp: (op, arity) => self.resolveOp(op, resonance, arity),
|
|
101
|
+
apply(name, args) {
|
|
102
|
+
const op = self.ops.get(name);
|
|
103
|
+
if (!op) {
|
|
104
|
+
throw new Error(`ALU: unknown operation "${name}"`);
|
|
80
105
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
apply(name, args) {
|
|
100
|
-
const op = self.ops.get(name);
|
|
101
|
-
if (!op)
|
|
102
|
-
throw new Error(`ALU: unknown operation "${name}"`);
|
|
103
|
-
// ── ELEMENT-WISE BROADCAST ────────────────────────────────────────
|
|
104
|
-
// A SCALAR op (non-structural) applied to an n-dimensional argument
|
|
105
|
-
// lifts over it: the op runs on each element and the results re-pack
|
|
106
|
-
// into an nd of the same shape. This is the ONE place "every operation
|
|
107
|
-
// supports nd" is implemented — add, sin, nand, the polymorphic inverse
|
|
108
|
-
// all broadcast for free, and because each element re-enters `apply`,
|
|
109
|
-
// NESTING recurses (an nd of nd lifts twice) with no extra code.
|
|
110
|
-
//
|
|
111
|
-
// • several nd args ZIP position-wise (their top-level lengths must
|
|
112
|
-
// agree); a scalar arg is held constant against the list — so
|
|
113
|
-
// add([1,2,3],[4,5,6]) = [5,7,9] and add([1,2,3], 10) = [11,12,13].
|
|
114
|
-
// • a structural op is exempt: it wants the whole list (a reduce
|
|
115
|
-
// cannot be lifted across the very elements it folds).
|
|
116
|
-
if (!op.structural && args.some(isNd)) {
|
|
117
|
-
let len = -1;
|
|
118
|
-
for (const a of args) {
|
|
119
|
-
if (!isNd(a))
|
|
120
|
-
continue;
|
|
121
|
-
if (len === -1)
|
|
122
|
-
len = a.items.length;
|
|
123
|
-
else if (a.items.length !== len) {
|
|
124
|
-
throw new Error(`ALU: cannot broadcast "${name}" over lists of unequal length ` +
|
|
125
|
-
`(${len} vs ${a.items.length})`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const out = [];
|
|
129
|
-
for (let i = 0; i < len; i++) {
|
|
130
|
-
out.push(ctx.apply(name, args.map((a) => isNd(a) ? a.items[i] : a)));
|
|
131
|
-
}
|
|
132
|
-
return nd(out);
|
|
133
|
-
}
|
|
134
|
-
if (op.arity !== "variadic" && args.length !== op.arity) {
|
|
135
|
-
throw new Error(`ALU: "${name}" expects ${op.arity} operand(s), got ${args.length}`);
|
|
136
|
-
}
|
|
137
|
-
return op.fn(args, ctx);
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
return ctx;
|
|
141
|
-
}
|
|
142
|
-
/** Resolve an operation-denoting value to a canonical op name — the shared
|
|
143
|
-
* machinery behind {@link OpContext.resolveOp}. Tries, in order: a literal
|
|
144
|
-
* surface form (the registry's own index, arity-disambiguated), the meaning
|
|
145
|
-
* via `resonance.recogniseOp`, then a bare canonical name / decimal reading.
|
|
146
|
-
* Returns null when nothing resolves. Static-shaped (takes the resonance
|
|
147
|
-
* explicitly) so both the context closure and callers can use it. */
|
|
148
|
-
resolveOp(op, resonance, arity) {
|
|
149
|
-
// The op-value's surface text, if it has a byte reading.
|
|
150
|
-
let text = null;
|
|
151
|
-
if (op.domain === "symbol") {
|
|
152
|
-
let s = "";
|
|
153
|
-
for (let i = 0; i < op.bytes.length; i++) {
|
|
154
|
-
s += String.fromCharCode(op.bytes[i]);
|
|
106
|
+
// ── ELEMENT-WISE BROADCAST ────────────────────────────────────────
|
|
107
|
+
// A SCALAR op (non-structural) applied to an n-dimensional argument
|
|
108
|
+
// lifts over it: the op runs on each element and the results re-pack
|
|
109
|
+
// into an nd of the same shape. This is the ONE place "every operation
|
|
110
|
+
// supports nd" is implemented — add, sin, nand, the polymorphic inverse
|
|
111
|
+
// all broadcast for free, and because each element re-enters `apply`,
|
|
112
|
+
// NESTING recurses (an nd of nd lifts twice) with no extra code.
|
|
113
|
+
//
|
|
114
|
+
// • several nd args ZIP position-wise (their top-level lengths must
|
|
115
|
+
// agree); a scalar arg is held constant against the list — so
|
|
116
|
+
// add([1,2,3],[4,5,6]) = [5,7,9] and add([1,2,3], 10) = [11,12,13].
|
|
117
|
+
// • a structural op is exempt: it wants the whole list (a reduce
|
|
118
|
+
// cannot be lifted across the very elements it folds).
|
|
119
|
+
if (!op.structural && args.some(isNd)) {
|
|
120
|
+
let len = -1;
|
|
121
|
+
for (const a of args) {
|
|
122
|
+
if (!isNd(a)) {
|
|
123
|
+
continue;
|
|
155
124
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return null;
|
|
164
|
-
// (1) a literal SURFACE FORM, arity-disambiguated when asked.
|
|
165
|
-
const claimants = this.formIndex.get(text);
|
|
166
|
-
if (claimants && claimants.length > 0) {
|
|
167
|
-
if (arity !== undefined) {
|
|
168
|
-
for (const n of claimants) {
|
|
169
|
-
const o = this.ops.get(n);
|
|
170
|
-
const a = o && (o.arity === "variadic" ? 2 : o.arity);
|
|
171
|
-
if (a === arity)
|
|
172
|
-
return n;
|
|
173
|
-
}
|
|
125
|
+
if (len === -1) {
|
|
126
|
+
len = a.items.length;
|
|
127
|
+
} else if (a.items.length !== len) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`ALU: cannot broadcast "${name}" over lists of unequal length ` +
|
|
130
|
+
`(${len} vs ${a.items.length})`,
|
|
131
|
+
);
|
|
174
132
|
}
|
|
175
|
-
|
|
133
|
+
}
|
|
134
|
+
const out = [];
|
|
135
|
+
for (let i = 0; i < len; i++) {
|
|
136
|
+
out.push(
|
|
137
|
+
ctx.apply(name, args.map((a) => isNd(a) ? a.items[i] : a)),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return nd(out);
|
|
176
141
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (byMeaning && this.ops.has(byMeaning))
|
|
182
|
-
return byMeaning;
|
|
142
|
+
if (op.arity !== "variadic" && args.length !== op.arity) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`ALU: "${name}" expects ${op.arity} operand(s), got ${args.length}`,
|
|
145
|
+
);
|
|
183
146
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
147
|
+
return op.fn(args, ctx);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
return ctx;
|
|
151
|
+
}
|
|
152
|
+
/** Resolve an operation-denoting value to a canonical op name — the shared
|
|
153
|
+
* machinery behind {@link OpContext.resolveOp}. Tries, in order: a literal
|
|
154
|
+
* surface form (the registry's own index, arity-disambiguated), the meaning
|
|
155
|
+
* via `resonance.recogniseOp`, then a bare canonical name / decimal reading.
|
|
156
|
+
* Returns null when nothing resolves. Static-shaped (takes the resonance
|
|
157
|
+
* explicitly) so both the context closure and callers can use it. */
|
|
158
|
+
resolveOp(op, resonance, arity) {
|
|
159
|
+
// The op-value's surface text, if it has a byte reading.
|
|
160
|
+
let text = null;
|
|
161
|
+
if (op.domain === "symbol") {
|
|
162
|
+
let s = "";
|
|
163
|
+
for (let i = 0; i < op.bytes.length; i++) {
|
|
164
|
+
s += String.fromCharCode(op.bytes[i]);
|
|
165
|
+
}
|
|
166
|
+
text = s.trim();
|
|
167
|
+
} else if (op.domain === "int") {
|
|
168
|
+
text = op.n.toString();
|
|
169
|
+
} else if (op.domain === "bit") {
|
|
170
|
+
text = String(op.b);
|
|
171
|
+
}
|
|
172
|
+
if (text === null || text.length === 0) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
// (1) a literal SURFACE FORM, arity-disambiguated when asked.
|
|
176
|
+
const claimants = this.formIndex.get(text);
|
|
177
|
+
if (claimants && claimants.length > 0) {
|
|
178
|
+
if (arity !== undefined) {
|
|
179
|
+
for (const n of claimants) {
|
|
180
|
+
const o = this.ops.get(n);
|
|
181
|
+
const a = o && (o.arity === "variadic" ? 2 : o.arity);
|
|
182
|
+
if (a === arity) {
|
|
183
|
+
return n;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return claimants[0];
|
|
188
|
+
}
|
|
189
|
+
// (2) the MEANING, via pre-resolved resonance (synonym / multimodal gesture
|
|
190
|
+
// the bytes do not literally spell) — only a symbol carries meaning.
|
|
191
|
+
if (op.domain === "symbol") {
|
|
192
|
+
const byMeaning = resonance.recogniseOp(op.bytes);
|
|
193
|
+
if (byMeaning && this.ops.has(byMeaning)) {
|
|
194
|
+
return byMeaning;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// (3) a bare canonical NAME already registered.
|
|
198
|
+
if (this.ops.has(text)) {
|
|
199
|
+
return text;
|
|
188
200
|
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
189
203
|
}
|