@hviana/sema 0.1.5 → 0.1.6

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 (160) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1687 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +17 -42
  51. package/dist/src/geometry.js +235 -266
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +749 -871
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +183 -208
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -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
- /** 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;
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
- /** 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;
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,46 +39,42 @@ 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 = (
43
- bytes: Uint8Array,
44
- variable: string,
45
- at: number,
46
- ) => number | null;
42
+ export type EvalExpr = (bytes: Uint8Array, variable: string, at: number) => number | null;
47
43
  /** The context a callback runs in: the means to call sibling ops by name, the
48
44
  * runtime knobs, pre-resolved resonance, and (when wired) an expression
49
45
  * evaluator. This is what makes derivations compose without import cycles
50
46
  * between kernel files. */
51
47
  export interface OpContext {
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;
48
+ /** Apply another registered op by canonical name. */
49
+ apply(name: string, args: Value[]): Value;
50
+ /** Whether an op is registered (lets a derivation pick a fast path when an
51
+ * optional sibling exists). */
52
+ has(name: string): boolean;
53
+ /** Resolve an operation-denoting VALUE to a canonical op name — the
54
+ * intelligent callback resolution the higher-order nd ops (map/reduce/filter/
55
+ * find) use for their FUNCTION ARGUMENT. An operation is named the SAME way
56
+ * any operation is recognised anywhere in the kernel, reusing the existing
57
+ * machinery rather than a bespoke table:
58
+ *
59
+ * 1. a literal SURFACE FORM — the op-value's bytes are a registered spelling
60
+ * ("+", "plus", "add", "*", "max", …) → the registry's own form index;
61
+ * 2. else its MEANING — {@link ResonanceSync.recogniseOp} over the same
62
+ * operation concepts, so a synonym or a multimodal gesture the bytes do
63
+ * not literally spell still resolves (pre-resolved by the host);
64
+ * 3. else a bare canonical NAME already (a symbol whose bytes equal an op
65
+ * name), or an int/bit treated as its decimal name — last resort.
66
+ *
67
+ * `arity` (when given) disambiguates a surface a unary and a binary op share
68
+ * (e.g. "-" is both negate and subtract): the claimant of that arity wins.
69
+ * Returns null when nothing resolves, so a higher-order op declines. */
70
+ resolveOp(op: Value, arity?: number): string | null;
71
+ /** The numerical runtime knobs. */
72
+ rt: OpRuntime;
73
+ /** Pre-resolved resonance for the polymorphic inverse and op recognition. */
74
+ resonance: ResonanceSync;
75
+ /** Evaluate a symbolic expression at a variable binding (see {@link
76
+ * EvalExpr}); undefined in a bare registry with no host evaluator. */
77
+ evalExpr?: EvalExpr;
82
78
  }
83
79
  /** The callback that executes an operation on resolved operands. */
84
80
  export type OpFn = (args: Value[], ctx: OpContext) => Value;
@@ -87,36 +83,36 @@ export type OpFn = (args: Value[], ctx: OpContext) => Value;
87
83
  * read off the registry rather than hardcoded in any parser. Higher
88
84
  * `precedence` binds tighter. */
89
85
  export interface InfixSyntax {
90
- precedence: number;
91
- rightAssoc?: boolean;
86
+ precedence: number;
87
+ rightAssoc?: boolean;
92
88
  }
93
89
  /** Optional syntactic/structure traits of an operation, declared where the op
94
90
  * is registered. Every parser-facing distinction the ALU makes is one of
95
91
  * these traits — there is no side table of names anywhere. */
96
92
  export interface OpTraits {
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;
93
+ /** Consumes an n-dimensional value WHOLE (exempt from broadcast). */
94
+ structural?: boolean;
95
+ /** Usable as an infix operator, with the given binding. */
96
+ infix?: InfixSyntax;
97
+ /** The FIRST operand is an EXPRESSION (a function's bytes) rather than a
98
+ * value; the remaining `arity − 1` operands are its points/bounds. The
99
+ * numerical layer's diff/integrate/solve/limit/optimize declare this. */
100
+ expression?: boolean;
105
101
  }
106
102
  /** An operation — primitive or derived, indistinguishable by type. */
107
103
  export interface Operation extends OpTraits {
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;
104
+ /** Canonical id, e.g. "add", "nand", "converge", "sin". */
105
+ name: string;
106
+ /** Operand count (fixed) or "variadic". */
107
+ arity: Arity;
108
+ /** True only for the kernel's irreducible roots. */
109
+ primitive: boolean;
110
+ /** Surface forms this op answers to — the spellings resonance maps to it,
111
+ * e.g. add ← ["+", "plus", "sum", "add"]. May be empty for an internal op
112
+ * that has no user-facing surface. */
113
+ forms: string[];
114
+ /** The callback. */
115
+ fn: OpFn;
120
116
  }
121
117
  /** A resonance that knows nothing — the default when no host is wired in, so
122
118
  * the pure kernel and its tests run with zero coupling. Op recognition then
@@ -128,61 +124,45 @@ export declare const NO_RESONANCE: ResonanceSync;
128
124
  * respect the dependency DAG loosely (a derived op may reference an op
129
125
  * registered later, since resolution is lazy). */
130
126
  export declare class OperationRegistry {
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;
127
+ private readonly ops;
128
+ /** Surface form → the canonical names that claim it. A form may be shared
129
+ * (e.g. "-" is both subtract and negate); the caller disambiguates by arity
130
+ * and position, so the index keeps every claimant. */
131
+ private readonly formIndex;
132
+ /** Register an operation record directly. */
133
+ register(op: Operation): void;
134
+ /** Register a PRIMITIVE op (hand-written callback), with optional {@link
135
+ * OpTraits} — structural (consumes an nd whole, exempt from broadcast),
136
+ * infix binding, expression-operand — declared here, next to the op. */
137
+ prim(name: string, arity: Arity, forms: string[], fn: OpFn, traits?: OpTraits): void;
138
+ /** Register a DERIVED op (callback composes other ops via ctx). `traits`
139
+ * as in {@link prim}. */
140
+ derive(name: string, arity: Arity, forms: string[], fn: OpFn, traits?: OpTraits): void;
141
+ /** The op with this canonical name, or undefined. */
142
+ get(name: string): Operation | undefined;
143
+ has(name: string): boolean;
144
+ /** The canonical names a surface form maps to (empty if none). Several ops
145
+ * can share one surface (unary vs binary "-"), so this returns all. */
146
+ lookupForm(form: string): readonly string[];
147
+ /** Every (surface form → canonical name) pair — the host enumerates these to
148
+ * seed its operator recogniser. */
149
+ formEntries(): Iterable<{
150
+ form: string;
151
+ name: string;
152
+ }>;
153
+ /** Every registered canonical name. */
154
+ names(): Iterable<string>;
155
+ /** Build a context bound to a resonance and runtime — the object derived
156
+ * callbacks call back into. `apply` validates arity-vs-presence and surfaces
157
+ * a clear error rather than letting an undefined op silently produce NaN.
158
+ * An optional expression evaluator lets the numerical layer act on functions
159
+ * (see {@link EvalExpr}). */
160
+ context(resonance: ResonanceSync, rt: OpRuntime, evalExpr?: EvalExpr): OpContext;
161
+ /** Resolve an operation-denoting value to a canonical op name — the shared
162
+ * machinery behind {@link OpContext.resolveOp}. Tries, in order: a literal
163
+ * surface form (the registry's own index, arity-disambiguated), the meaning
164
+ * via `resonance.recogniseOp`, then a bare canonical name / decimal reading.
165
+ * Returns null when nothing resolves. Static-shaped (takes the resonance
166
+ * explicitly) so both the context closure and callers can use it. */
167
+ resolveOp(op: Value, resonance: ResonanceSync, arity?: number): string | null;
188
168
  }
@@ -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
- opposite: () => null,
25
- recogniseOp: () => null,
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,174 +30,160 @@ 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
- 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);
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);
46
+ }
47
+ else
48
+ this.formIndex.set(f, [op.name]);
46
49
  }
47
- } else {
48
- this.formIndex.set(f, [op.name]);
49
- }
50
50
  }
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
- }
51
+ /** Register a PRIMITIVE op (hand-written callback), with optional {@link
52
+ * OpTraits} structural (consumes an nd whole, exempt from broadcast),
53
+ * infix binding, expression-operand declared here, next to the op. */
54
+ prim(name, arity, forms, fn, traits = {}) {
55
+ this.register({ name, arity, primitive: true, forms, fn, ...traits });
82
56
  }
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}"`);
105
- }
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;
124
- }
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
- );
132
- }
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);
141
- }
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
- );
146
- }
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);
57
+ /** Register a DERIVED op (callback composes other ops via ctx). `traits`
58
+ * as in {@link prim}. */
59
+ derive(name, arity, forms, fn, traits = {}) {
60
+ this.register({ name, arity, primitive: false, forms, fn, ...traits });
61
+ }
62
+ /** The op with this canonical name, or undefined. */
63
+ get(name) {
64
+ return this.ops.get(name);
171
65
  }
172
- if (text === null || text.length === 0) {
173
- return null;
66
+ has(name) {
67
+ return this.ops.has(name);
174
68
  }
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
- }
69
+ /** The canonical names a surface form maps to (empty if none). Several ops
70
+ * can share one surface (unary vs binary "-"), so this returns all. */
71
+ lookupForm(form) {
72
+ return this.formIndex.get(form) ?? [];
73
+ }
74
+ /** Every (surface form → canonical name) pair — the host enumerates these to
75
+ * seed its operator recogniser. */
76
+ *formEntries() {
77
+ for (const [form, names] of this.formIndex) {
78
+ for (const name of names)
79
+ yield { form, name };
185
80
  }
186
- }
187
- return claimants[0];
188
81
  }
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
- }
82
+ /** Every registered canonical name. */
83
+ names() {
84
+ return this.ops.keys();
85
+ }
86
+ /** Build a context bound to a resonance and runtime — the object derived
87
+ * callbacks call back into. `apply` validates arity-vs-presence and surfaces
88
+ * a clear error rather than letting an undefined op silently produce NaN.
89
+ * An optional expression evaluator lets the numerical layer act on functions
90
+ * (see {@link EvalExpr}). */
91
+ context(resonance, rt, evalExpr) {
92
+ const self = this;
93
+ const ctx = {
94
+ rt,
95
+ resonance,
96
+ evalExpr,
97
+ has: (name) => self.ops.has(name),
98
+ resolveOp: (op, arity) => self.resolveOp(op, resonance, arity),
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;
196
141
  }
197
- // (3) a bare canonical NAME already registered.
198
- if (this.ops.has(text)) {
199
- return text;
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]);
155
+ }
156
+ text = s.trim();
157
+ }
158
+ else if (op.domain === "int")
159
+ text = op.n.toString();
160
+ else if (op.domain === "bit")
161
+ text = String(op.b);
162
+ if (text === null || text.length === 0)
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
+ }
174
+ }
175
+ return claimants[0];
176
+ }
177
+ // (2) the MEANING, via pre-resolved resonance (synonym / multimodal gesture
178
+ // the bytes do not literally spell) — only a symbol carries meaning.
179
+ if (op.domain === "symbol") {
180
+ const byMeaning = resonance.recogniseOp(op.bytes);
181
+ if (byMeaning && this.ops.has(byMeaning))
182
+ return byMeaning;
183
+ }
184
+ // (3) a bare canonical NAME already registered.
185
+ if (this.ops.has(text))
186
+ return text;
187
+ return null;
200
188
  }
201
- return null;
202
- }
203
189
  }