@hviana/sema 0.1.0

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 (110) hide show
  1. package/AGENTS.md +470 -0
  2. package/AUTHORS.md +12 -0
  3. package/COMMERCIAL-LICENSE.md +19 -0
  4. package/CONTRIBUTING.md +15 -0
  5. package/HOW_IT_WORKS.md +4210 -0
  6. package/LICENSE.md +117 -0
  7. package/README.md +290 -0
  8. package/TRADEMARKS.md +19 -0
  9. package/example/demo.ts +46 -0
  10. package/example/train_base.ts +2675 -0
  11. package/index.html +893 -0
  12. package/package.json +25 -0
  13. package/src/alphabet.ts +34 -0
  14. package/src/alu/README.md +332 -0
  15. package/src/alu/src/alu.ts +541 -0
  16. package/src/alu/src/expr.ts +339 -0
  17. package/src/alu/src/index.ts +115 -0
  18. package/src/alu/src/kernel-arith.ts +377 -0
  19. package/src/alu/src/kernel-bits.ts +199 -0
  20. package/src/alu/src/kernel-logic.ts +102 -0
  21. package/src/alu/src/kernel-nd.ts +235 -0
  22. package/src/alu/src/kernel-numeric.ts +466 -0
  23. package/src/alu/src/operation.ts +344 -0
  24. package/src/alu/src/parser.ts +574 -0
  25. package/src/alu/src/resonance.ts +161 -0
  26. package/src/alu/src/text.ts +83 -0
  27. package/src/alu/src/value.ts +327 -0
  28. package/src/alu/test/alu.test.ts +1004 -0
  29. package/src/bytes.ts +62 -0
  30. package/src/config.ts +227 -0
  31. package/src/derive/README.md +295 -0
  32. package/src/derive/src/deduction.ts +263 -0
  33. package/src/derive/src/index.ts +24 -0
  34. package/src/derive/src/priority-queue.ts +70 -0
  35. package/src/derive/src/rewrite.ts +127 -0
  36. package/src/derive/src/trie.ts +242 -0
  37. package/src/derive/test/derive.test.ts +137 -0
  38. package/src/extension.ts +42 -0
  39. package/src/geometry.ts +511 -0
  40. package/src/index.ts +38 -0
  41. package/src/ingest-cache.ts +224 -0
  42. package/src/mind/articulation.ts +137 -0
  43. package/src/mind/attention.ts +1061 -0
  44. package/src/mind/canonical.ts +107 -0
  45. package/src/mind/graph-search.ts +1100 -0
  46. package/src/mind/index.ts +18 -0
  47. package/src/mind/junction.ts +347 -0
  48. package/src/mind/learning.ts +246 -0
  49. package/src/mind/match.ts +507 -0
  50. package/src/mind/mechanisms/alu.ts +33 -0
  51. package/src/mind/mechanisms/cast.ts +568 -0
  52. package/src/mind/mechanisms/confluence.ts +268 -0
  53. package/src/mind/mechanisms/cover.ts +248 -0
  54. package/src/mind/mechanisms/extraction.ts +422 -0
  55. package/src/mind/mechanisms/recall.ts +241 -0
  56. package/src/mind/mind.ts +483 -0
  57. package/src/mind/pipeline-mechanism.ts +326 -0
  58. package/src/mind/pipeline.ts +300 -0
  59. package/src/mind/primitives.ts +201 -0
  60. package/src/mind/rationale.ts +275 -0
  61. package/src/mind/reasoning.ts +198 -0
  62. package/src/mind/recognition.ts +234 -0
  63. package/src/mind/resonance.ts +0 -0
  64. package/src/mind/trace.ts +108 -0
  65. package/src/mind/traverse.ts +556 -0
  66. package/src/mind/types.ts +281 -0
  67. package/src/rabitq-hnsw/README.md +303 -0
  68. package/src/rabitq-hnsw/src/database.ts +492 -0
  69. package/src/rabitq-hnsw/src/heap.ts +90 -0
  70. package/src/rabitq-hnsw/src/hnsw.ts +514 -0
  71. package/src/rabitq-hnsw/src/index.ts +15 -0
  72. package/src/rabitq-hnsw/src/prng.ts +39 -0
  73. package/src/rabitq-hnsw/src/rabitq.ts +308 -0
  74. package/src/rabitq-hnsw/src/store.ts +994 -0
  75. package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
  76. package/src/store-sqlite.ts +928 -0
  77. package/src/store.ts +2148 -0
  78. package/src/vec.ts +124 -0
  79. package/test/00-extract.test.mjs +151 -0
  80. package/test/01-floor.test.mjs +20 -0
  81. package/test/02-roundtrip.test.mjs +83 -0
  82. package/test/03-recall.test.mjs +98 -0
  83. package/test/04-think.test.mjs +627 -0
  84. package/test/05-concepts.test.mjs +84 -0
  85. package/test/08-storage.test.mjs +948 -0
  86. package/test/09-edges.test.mjs +65 -0
  87. package/test/11-universality.test.mjs +180 -0
  88. package/test/13-conversation.test.mjs +228 -0
  89. package/test/14-scaling.test.mjs +503 -0
  90. package/test/15-decomposition-gap.test.mjs +0 -0
  91. package/test/16-bridge.test.mjs +250 -0
  92. package/test/17-intelligence.test.mjs +240 -0
  93. package/test/18-alu.test.mjs +209 -0
  94. package/test/19-nd.test.mjs +254 -0
  95. package/test/20-stability.test.mjs +489 -0
  96. package/test/21-partial.test.mjs +158 -0
  97. package/test/22-multihop.test.mjs +130 -0
  98. package/test/23-rationale.test.mjs +316 -0
  99. package/test/24-generalization.test.mjs +416 -0
  100. package/test/25-embedding.test.mjs +75 -0
  101. package/test/26-cooperative.test.mjs +89 -0
  102. package/test/27-saturation-drop.test.mjs +637 -0
  103. package/test/28-unknowable.test.mjs +88 -0
  104. package/test/29-counterfactual.test.mjs +476 -0
  105. package/test/30-conflict-resolution.test.mjs +166 -0
  106. package/test/31-audit.test.mjs +288 -0
  107. package/test/32-confluence.test.mjs +173 -0
  108. package/test/33-multi-candidate.test.mjs +222 -0
  109. package/test/34-cross-region.test.mjs +252 -0
  110. package/tsconfig.json +16 -0
@@ -0,0 +1,574 @@
1
+ // parser.ts — the ALU's query parser: recognise and evaluate every computation
2
+ // a raw byte query invokes.
3
+ //
4
+ // This is the intelligence layered over the Alu facade, and the ONLY module
5
+ // that reaches outside the kernel — through one narrow port, {@link AluHost}.
6
+ // The host (SEMA's Mind, or nothing) supplies exactly the two things bytes
7
+ // alone cannot answer:
8
+ //
9
+ // • MEANING (resonance) — which operation an opaque span names, and what the
10
+ // resonant opposite of a symbol is. Both are async (they hit the host's
11
+ // gist/halo index) and both are pre-resolved here, before any synchronous
12
+ // computation — the same hoisting discipline the host's search applies to
13
+ // concept hops and connectors.
14
+ // • GEOMETRY (segmentation) — where coherent runs begin and end. The parser
15
+ // never decides for itself whether the bytes between two tokens are "just a
16
+ // separator": it asks the host's geometric segmenter (the same one the
17
+ // perception tree uses), so what counts as spacing is a property of the
18
+ // learnt alphabet space, not a hardcoded character class. `reach` bounds
19
+ // how far an operator may look for its operand — the host's own grouping
20
+ // capacity, so "not … 4" across prose stays silent while "not 4" fires.
21
+ //
22
+ // Everything else — what a numeral is, which symbols are operators, how infix
23
+ // binds, which ops act on expressions — is read off the operation registry via
24
+ // the facade. The parser therefore has NO vocabulary of its own: extend the
25
+ // registry and the parser understands the new notation; teach the host's
26
+ // resonance space and it understands new spellings of the old one.
27
+ //
28
+ // The parser LEXES the query once into a single ascending TOKEN stream —
29
+ // numeric OPERANDS and symbolic OPERATORS from the facade's scanner, and the
30
+ // unclaimed runs between them as TERMS — and every recogniser reads that one
31
+ // stream. Tokens are disjoint and cover every non-spacing byte, so the gap
32
+ // between two consecutive tokens is pure spacing by construction. What it finds (independent of any
33
+ // content-defined chunking, so a number a chunker would split is read whole):
34
+ //
35
+ // 1. INFIX ARITHMETIC RUNS — maximal alternations of numeric operands and
36
+ // symbolic operators ("2+3*4", "3 + 3"), evaluated wholesale through the
37
+ // registry-derived expression grammar, so chaining and precedence are a
38
+ // recursive application of the same kernel.
39
+ // 2. OPERATIONS NAMED BY MEANING — a term (word, glyph, image/audio fragment)
40
+ // that names an operation literally or by resonance, applied to a
41
+ // following operand ("opposite large" → "small"), or, for the numerical
42
+ // layer, to a following EXPRESSION ("derivative of x^2 at 3").
43
+
44
+ import type { Alu } from "./alu.js";
45
+ import {
46
+ type AluResonance,
47
+ type ConceptAnchor,
48
+ prefetchOpposites,
49
+ prefetchResonance,
50
+ } from "./resonance.js";
51
+ import { NO_RESONANCE } from "./operation.js";
52
+ import { int, real, symbol, symbolSpans, type Value } from "./value.js";
53
+ import { nonSpaceRuns } from "./text.js";
54
+ import { bytesEqual, latin1 } from "../../bytes.js";
55
+
56
+ /** A half-open byte range. */
57
+ export interface Span {
58
+ i: number;
59
+ j: number;
60
+ }
61
+
62
+ /** A computation the parser recognised and evaluated: the query span [i, j) it
63
+ * is authoritative for, and the canonical result bytes. */
64
+ export interface ComputedSpan extends Span {
65
+ bytes: Uint8Array;
66
+ }
67
+
68
+ /** The port through which the parser reaches its host. This interface is the
69
+ * ENTIRE coupling surface between the ALU and any host: the ALU imports
70
+ * nothing from a mind or store, and every member is a GENERIC capability the
71
+ * host already has — nothing here mentions operations, vocabularies, or any
72
+ * ALU notion, so the host holds no ALU-specific logic either.
73
+ *
74
+ * MEANING (resonance):
75
+ * • meaningOf — which of the given labelled forms does a span MEAN? The ALU
76
+ * supplies the anchors (its own concept vocabulary); the host only answers
77
+ * nearness in its meaning space, above its own threshold, or null. This
78
+ * is what recognises an operation the bytes do not literally spell — an
79
+ * "∫" drawn, a "plus" spoken — in any modality.
80
+ * • continuation — the grounded form the host's corpus continues a form to
81
+ * (the same grounding its recall uses), or null. The ALU is the one that
82
+ * READS this as the resonant opposite: it asks for the continuation of an
83
+ * operand under an inverse, and a corpus that learnt "opposite of large →
84
+ * small" grounds exactly that. The host stays neutral — it only ever
85
+ * answers "where does this form lead?".
86
+ *
87
+ * GEOMETRY (perception):
88
+ * • segment — coherent runs by the host's own geometric perception. The
89
+ * parser uses this to ask "is this gap pure separator?" (one segment or
90
+ * none) — never a character class of its own.
91
+ * • reach — how far (in bytes) an operator may look for its operand: the
92
+ * host's own grouping capacity, so adjacency is judged by the same
93
+ * geometry that groups the host's perception. */
94
+ export interface AluHost {
95
+ meaningOf(
96
+ bytes: Uint8Array,
97
+ anchors: ReadonlyArray<ConceptAnchor>,
98
+ ): Promise<string | null>;
99
+ continuation(bytes: Uint8Array): Promise<Uint8Array | null>;
100
+ segment(bytes: Uint8Array): Span[];
101
+ reach: number;
102
+ }
103
+
104
+ /** A host that knows nothing beyond structure: no resonance, whitespace-run
105
+ * segmentation, unbounded reach. This is what "the ALU runs fully decoupled"
106
+ * means — the parser still reads literal notation, and only the meaning-based
107
+ * paths stay silent. */
108
+ export const STRUCTURAL_HOST: AluHost = {
109
+ meaningOf: async () => null,
110
+ continuation: async () => null,
111
+ segment: (bytes) => nonSpaceRuns(bytes),
112
+ reach: Number.POSITIVE_INFINITY,
113
+ };
114
+
115
+ export class QueryParser {
116
+ /** The host's generic capabilities, specialised once into the {@link
117
+ * AluResonance} the prefetch bridges consume — the ONE place the ALU gives
118
+ * the host's neutral answers their computational reading: op recognition is
119
+ * meaningOf over the ALU's own concept anchors, and the polymorphic INVERSE
120
+ * of a symbol is the corpus's grounded continuation of it (a learnt
121
+ * opposition relation leads from a form to its opposite; the host never
122
+ * needs to know that is what it grounded). */
123
+ private readonly resonance: AluResonance;
124
+
125
+ /** Session memo of a term's meaning-based op reading, keyed by its bytes.
126
+ * `meaningOf` is a full river fold of the span — measured at ~a third of
127
+ * a plain-English respond's latency, paid per WORD per query — and it is
128
+ * a pure function of the bytes (perception is pure; the concept anchors
129
+ * are fixed for the Alu's lifetime), so the reading never changes.
130
+ * Bounded: cleared wholesale when full (words recur; a rare clear only
131
+ * re-pays folds, never changes a reading). */
132
+ private readonly meaningMemo = new Map<string, readonly string[]>();
133
+ private static readonly MEANING_MEMO_MAX = 4096;
134
+
135
+ constructor(
136
+ private readonly alu: Alu,
137
+ private readonly host: AluHost = STRUCTURAL_HOST,
138
+ ) {
139
+ this.resonance = {
140
+ recogniseOp: (bytes) => host.meaningOf(bytes, alu.conceptAnchors()),
141
+ opposite: (bytes) => host.continuation(bytes),
142
+ };
143
+ }
144
+
145
+ /** Recognise and evaluate every computation `query` invokes. All async
146
+ * resonance is resolved in here, so the caller receives finished spans it
147
+ * can fold synchronously into its search. Results are deduplicated; a span
148
+ * that fails to compute is simply absent (the "rule does not fire"
149
+ * contract).
150
+ *
151
+ * The pipeline is one composition ladder, iterated to a FIXPOINT:
152
+ *
153
+ * 1. infix arithmetic RUNS are found and evaluated through the
154
+ * registry-derived grammar;
155
+ * 2. every computed span — a run, or a fired operation — is COLLAPSED
156
+ * into a single virtual operand, so the next round consumes it as one
157
+ * finished value: "sum 1*3 4" is add(3, 4), and "sqrt sum 9 16" is
158
+ * sqrt(25) — nesting by iteration, exactly as the grammar nests
159
+ * expressions, with no recursion machinery of its own;
160
+ * 3. rounds repeat while operations still fire (each round consumes at
161
+ * least one term, so the ladder is bounded by the term count).
162
+ *
163
+ * One AUTHORITY law then reconciles the readings: a span strictly
164
+ * contained in a larger computed span is that computation's MATERIAL (the
165
+ * "2-4" inside a solve's "x^2-4", the inner sum under a sqrt), not a rival
166
+ * result — the same rule by which the host's search lets a computed span
167
+ * override colliding learned facts. */
168
+ async parse(query: Uint8Array): Promise<ComputedSpan[]> {
169
+ const tokens = this.lex(query);
170
+ const runs = this.arithmeticRuns(query, tokens).map(([i, j]) => ({
171
+ i,
172
+ j,
173
+ bytes: this.evalRun(query.subarray(i, j)),
174
+ }));
175
+ const spans: ComputedSpan[] = runs.filter(
176
+ (r): r is ComputedSpan => r.bytes !== null,
177
+ );
178
+ let stream = compose(tokens, runs);
179
+ const readings = new Map<Token, readonly string[]>();
180
+ for (;;) {
181
+ const fired = await this.operations(query, stream, readings);
182
+ if (fired.length === 0) break;
183
+ spans.push(...fired);
184
+ stream = compose(stream, fired);
185
+ }
186
+ return authoritative(dedup(spans));
187
+ }
188
+
189
+ /** Apply an ALU operation to operand byte spans, with the host's resonance
190
+ * wired in — the entry point for computation over n-dimensional values.
191
+ *
192
+ * Unlike the in-query arithmetic rule (which hands the facade scalar operand
193
+ * bytes), this recognises each operand's STRUCTURE into a Value first and
194
+ * runs the kernel on the finished Values, with the full resonance snapshot
195
+ * pre-resolved — because an nd computation needs meaning in two places the
196
+ * bare facade cannot reach on its own:
197
+ *
198
+ * • the polymorphic INVERSE inside a broadcast — `inverse [large, 3, tall]`
199
+ * lifts element-wise (operation.ts), and each symbol element's opposite
200
+ * is a resonant lookup, grounded in the host's corpus exactly as a scalar
201
+ * inverse is;
202
+ * • the FUNCTION ARGUMENT of a higher-order op — `reduce(xs, ‹+›)`,
203
+ * `map(xs, ‹negate›)`: the operator value is resolved by
204
+ * {@link "./operation.js".OpContext.resolveOp}, which falls through to
205
+ * resonance when the bytes are not a literal surface form.
206
+ *
207
+ * Both are async, so every SYMBOL span reachable in the operands (recursing
208
+ * through nd nesting, see {@link "./value.js".symbolSpans}) is resolved ONCE
209
+ * up front into a synchronous snapshot, and the synchronous kernel computes
210
+ * against it. Returns null when the op is unknown or the computation
211
+ * declines — the "this rule does not fire" contract.
212
+ *
213
+ * `asSymbol(idx)` keeps an operand opaque (not read as structure or a
214
+ * number) — the numerical-layer convention where operand 0 is an
215
+ * expression's bytes; it defaults to "recognise everything", which is what
216
+ * an nd computation wants. */
217
+ async compute(
218
+ name: string,
219
+ operandBytes: Uint8Array[],
220
+ asSymbol: (idx: number) => boolean = () => false,
221
+ ): Promise<Uint8Array | null> {
222
+ const operands: Value[] = [];
223
+ const spans: Uint8Array[] = [];
224
+ for (let i = 0; i < operandBytes.length; i++) {
225
+ const v = asSymbol(i)
226
+ ? symbol(operandBytes[i])
227
+ : this.alu.recogniseValue(operandBytes[i]);
228
+ operands.push(v);
229
+ symbolSpans(v, spans);
230
+ }
231
+ const resonance = await prefetchResonance(this.resonance, spans);
232
+ return this.alu.applyValues(name, operands, resonance);
233
+ }
234
+
235
+ // ── the lex: one reading of the query every recogniser shares ────────────
236
+
237
+ /** Lex the query ONCE into a single ascending token stream: the facade's
238
+ * scanner claims numeric OPERANDS and symbolic OPERATORS, and every maximal
239
+ * unclaimed run between spacing bytes is a TERM — a word, glyph, or opaque
240
+ * fragment that may name an operation. Terms are deliberately bounded by
241
+ * the spacing floor, not the host's geometric segmentation: perception may
242
+ * cut mid-word (its segments are grouping capacity, not word boundaries),
243
+ * while an operation NAME is a notation-level token. Geometry still
244
+ * governs what happens BETWEEN tokens (gap bridging, operand reach).
245
+ *
246
+ * Tokens are disjoint, ascending, and cover every non-spacing byte — so
247
+ * the gap between consecutive tokens is pure spacing BY CONSTRUCTION, a
248
+ * structural fact the run recogniser leans on. */
249
+ private lex(query: Uint8Array): Token[] {
250
+ const { operands, operators } = this.alu.scan(query);
251
+ const claimed = [
252
+ ...operands.map((o): Token => ({ i: o.i, j: o.j, kind: "operand" })),
253
+ ...operators.map((o): Token => ({ i: o.i, j: o.j, kind: "operator" })),
254
+ ].sort((a, b) => a.i - b.i);
255
+ const tokens: Token[] = [];
256
+ let p = 0;
257
+ for (const c of [...claimed, END]) {
258
+ for (const t of nonSpaceRuns(query, p, Math.min(c.i, query.length))) {
259
+ tokens.push({ ...t, kind: "term" });
260
+ }
261
+ if (c !== END) tokens.push(c);
262
+ p = c.j;
263
+ }
264
+ return tokens;
265
+ }
266
+
267
+ // ── (1) infix arithmetic ──────────────────────────────────────────────────
268
+
269
+ /** The maximal infix-arithmetic runs in the query: an alternation of numeric
270
+ * operands and SYMBOLIC operators (e.g. "2+3*4"), returned as [start, end)
271
+ * ranges that begin and end on an operand.
272
+ *
273
+ * Consecutive tokens may be SEPARATED — "3 + 3" is the same run as "3+3".
274
+ * The gap between two tokens is a bridgeable separator exactly when the
275
+ * host's geometric segmenter reads it as at most one coherent run — the
276
+ * same judgement the perception tree makes about spacing — so no character
277
+ * is privileged as "the" separator. */
278
+ private arithmeticRuns(
279
+ query: Uint8Array,
280
+ tokens: Token[],
281
+ ): Array<[number, number]> {
282
+ const bridged = (from: number, to: number): boolean =>
283
+ from >= to || this.host.segment(query.subarray(from, to)).length <= 1;
284
+ const runs: Array<[number, number]> = [];
285
+ let k = 0;
286
+ while (k < tokens.length) {
287
+ if (tokens[k].kind !== "operand") {
288
+ k++;
289
+ continue;
290
+ }
291
+ let end = k;
292
+ while (
293
+ end + 1 < tokens.length &&
294
+ tokens[end + 1].kind !== "term" && // a term breaks the notation
295
+ tokens[end + 1].kind !== tokens[end].kind && // alternates
296
+ bridged(tokens[end].j, tokens[end + 1].i) // pure-spacing gap coheres
297
+ ) end++;
298
+ while (end > k && tokens[end].kind === "operator") end--; // end on operand
299
+ if (end > k) runs.push([tokens[k].i, tokens[end].j]);
300
+ k = end + 1;
301
+ }
302
+ return runs;
303
+ }
304
+
305
+ /** Evaluate an infix-arithmetic run to its canonical result bytes, through
306
+ * the kernel's recursive expression evaluator, or null if it does not
307
+ * evaluate. A whole result stays an exact int; otherwise the canonical
308
+ * rounded real — deterministic, so the search's chart memoises identical
309
+ * results identically. */
310
+ private evalRun(runBytes: Uint8Array): Uint8Array | null {
311
+ const x = this.alu.evalExpression(runBytes, "", 0);
312
+ if (x === null || !Number.isFinite(x)) return null;
313
+ return this.alu.codec.encode(
314
+ Number.isInteger(x) ? int(BigInt(x)) : real(x),
315
+ );
316
+ }
317
+
318
+ // ── (2) operations named by NAME or MEANING ───────────────────────────────
319
+
320
+ /** Recognise and apply the operations the query's TERMS name — the generic,
321
+ * multimodal path. A term may name an operation literally (its bytes are a
322
+ * registered surface form — no resonance cost) or by RESONANCE (its gist
323
+ * lands on an operation's concept, any modality).
324
+ *
325
+ * Recognition PROPOSES, application DISPOSES: a surface form may be shared
326
+ * by several operations ("zero" is both the constant and solve's
327
+ * root-finding), so every literal claimant is kept, in registration order,
328
+ * and the first whose application actually fires wins — disambiguation by
329
+ * what the query supplies, not by a precedence table. Recognition runs
330
+ * CONCURRENTLY — each term's reading is independent — and is memoised in
331
+ * `readings` across fixpoint rounds (a surviving term is the same token
332
+ * object), so each term resonates at most once per parse. Application is
333
+ * then ordered and deterministic: an EXPRESSION op takes the function that
334
+ * follows it ({@link applyToExpression}), any other op takes its arity's
335
+ * worth of operands from the stream ({@link applyToStream}). Both read
336
+ * the same composed stream — an expression's TEXT comes from the raw query
337
+ * bytes by position, so composition never disturbs it, while a composed
338
+ * operand serves as a finished point/bound or argument. */
339
+ private async operations(
340
+ query: Uint8Array,
341
+ tokens: Token[],
342
+ readings: Map<Token, readonly string[]>,
343
+ ): Promise<ComputedSpan[]> {
344
+ const terms = tokens.filter((t) => t.kind === "term");
345
+ const anchors = this.alu.conceptAnchors();
346
+ await Promise.all(terms.map(async (term) => {
347
+ if (readings.has(term)) return;
348
+ const span = query.subarray(term.i, term.j);
349
+ const key = latin1(span);
350
+ const literal = this.alu.lookupOperator(key);
351
+ if (literal.length > 0) {
352
+ readings.set(term, literal);
353
+ return;
354
+ }
355
+ const hit = this.meaningMemo.get(key);
356
+ if (hit !== undefined) {
357
+ readings.set(term, hit);
358
+ return;
359
+ }
360
+ const m = await this.host.meaningOf(span, anchors);
361
+ const meant: readonly string[] = m ? [m] : [];
362
+ if (this.meaningMemo.size >= QueryParser.MEANING_MEMO_MAX) {
363
+ this.meaningMemo.clear();
364
+ }
365
+ this.meaningMemo.set(key, meant);
366
+ readings.set(term, meant);
367
+ }));
368
+ const out: ComputedSpan[] = [];
369
+ for (const term of terms) {
370
+ for (const name of readings.get(term)!) {
371
+ const result = this.alu.isExpressionOp(name)
372
+ ? this.applyToExpression(query, name, term, tokens)
373
+ : await this.applyToStream(query, name, term, tokens);
374
+ if (result) {
375
+ out.push(result);
376
+ break; // the first claimant that fires owns the term
377
+ }
378
+ }
379
+ }
380
+ return out;
381
+ }
382
+
383
+ /** Apply a NUMERICAL-LAYER op — one whose first operand is an EXPRESSION (a
384
+ * function), declared by the op's own `expression` trait. The registry's
385
+ * arity says how many trailing numeric operands are its points/bounds
386
+ * (arity − 1); the bytes between the operator and those points — with
387
+ * non-math filler stripped by {@link cleanExprText} — are the expression,
388
+ * evaluated by a recursive application of the kernel. */
389
+ private applyToExpression(
390
+ query: Uint8Array,
391
+ name: string,
392
+ term: Span,
393
+ tokens: Token[],
394
+ ): ComputedSpan | null {
395
+ const operands = tokens.filter((t) =>
396
+ t.kind === "operand" && t.i >= term.j
397
+ );
398
+ const arity = this.alu.arityOf(name);
399
+ const points = arity - 1;
400
+ const exprEnd = operands.length > points
401
+ ? operands[operands.length - points].i
402
+ : operands[0]?.i ?? query.length;
403
+ const cleaned = this.cleanExprText(latin1(query.subarray(term.j, exprEnd)));
404
+ if (cleaned.length === 0) return null;
405
+ const args: Uint8Array[] = [UTF8.encode(cleaned)];
406
+ for (const o of operands.slice(-points)) {
407
+ args.push(o.value ?? query.subarray(o.i, o.j));
408
+ }
409
+ if (args.length !== arity) return null; // under-supplied → decline
410
+ const bytes = this.alu.applyBytesTyped(name, args, (idx) => idx === 0);
411
+ if (bytes === null) return null;
412
+ return { i: term.i, j: operands[operands.length - 1].j, bytes };
413
+ }
414
+
415
+ /** Apply any other op to the OPERAND STREAM after the operator: the numeric
416
+ * operands and symbol terms that follow it, merged nearest-first, each
417
+ * within the host's reach of the token before it — so "sqrt 144",
418
+ * "gcd 12 18", and "opposite large" are one rule, and the NEAREST token is
419
+ * the operand ("opposite large 5" inverts "large", not the 5).
420
+ *
421
+ * Two structural refinements make this read like notation rather than a
422
+ * special case:
423
+ *
424
+ * • WORD-INFIX — an under-supplied operator borrows the numeric operand
425
+ * immediately BEFORE it (within reach), so "7 minus 2" applies
426
+ * subtract(7, 2) exactly as "7 - 2" would: a synonym is notation too.
427
+ * • GROUNDED-ONLY SYMBOLS — when every operand is a symbol, a result that
428
+ * merely echoes an operand means resonance grounded nothing (an inverse
429
+ * with no learnt opposition), and the rule stays silent rather than
430
+ * invent meaning. Numeric identities ("max 3 7" → "7") are real
431
+ * results and pass.
432
+ *
433
+ * A NULLARY op never fires from a bare term: a computation must consume
434
+ * something, or any prose word that happens to name a constant would be
435
+ * rewritten. Symbol operands get their resonance (the opposite each may
436
+ * need) pre-resolved in one prefetch. */
437
+ private async applyToStream(
438
+ query: Uint8Array,
439
+ name: string,
440
+ term: Span,
441
+ tokens: Token[],
442
+ ): Promise<ComputedSpan | null> {
443
+ const arity = this.alu.arityOf(name);
444
+ if (arity === 0) return null; // a constant consumes nothing → not a rule
445
+
446
+ const picked: Token[] = [];
447
+ let from = term.j;
448
+ for (const tok of tokens) {
449
+ if (tok.i < term.j || tok.kind === "operator") continue;
450
+ if (picked.length === arity) break;
451
+ if (tok.i - from > this.host.reach) break; // next token too far
452
+ picked.push(tok);
453
+ from = tok.j;
454
+ }
455
+
456
+ // WORD-INFIX: borrow the numeric operand just before the operator.
457
+ let start = term.i;
458
+ if (picked.length === arity - 1) {
459
+ let before: Token | undefined;
460
+ for (const t of tokens) {
461
+ if (t.j > term.i) break;
462
+ if (t.kind === "operand") before = t;
463
+ }
464
+ if (before && term.i - before.j <= this.host.reach) {
465
+ picked.unshift(before);
466
+ start = before.i;
467
+ }
468
+ }
469
+ if (picked.length !== arity) return null; // under-supplied → decline
470
+
471
+ const args = picked.map((t) => t.value ?? query.subarray(t.i, t.j));
472
+ const symbols = picked.flatMap((t, k) =>
473
+ t.kind === "term" ? [args[k]] : []
474
+ );
475
+ const resonance = symbols.length > 0
476
+ ? await prefetchOpposites(this.resonance, symbols)
477
+ : NO_RESONANCE;
478
+ const bytes = this.alu.applyBytes(name, args, resonance);
479
+ if (bytes === null) return null;
480
+ if (
481
+ symbols.length === args.length && args.some((a) => bytesEqual(bytes, a))
482
+ ) {
483
+ return null; // an all-symbol result echoing an operand grounded nothing
484
+ }
485
+ return { i: start, j: picked[picked.length - 1].j, bytes };
486
+ }
487
+
488
+ /** Strip non-math filler tokens from a raw expression string — keep numbers,
489
+ * operators, parens, names the GRAMMAR resolves (unary functions,
490
+ * registered constants), and single-RUNE identifiers. The rune rule is the
491
+ * mirror of {@link "./alu.js".Alu.conceptAnchors}' compound rule: a compound
492
+ * name carries distributional meaning (it is either a resolvable operation
493
+ * or filler — "of", "at", "the"), while a bare atom carries none and can
494
+ * only be the expression's free variable. Dropping the filler leaves the
495
+ * evaluator clean notation. */
496
+ private cleanExprText(raw: string): string {
497
+ const g = this.alu.grammar;
498
+ const toks = g.tokenize(raw);
499
+ if (!toks) return ""; // not a tokenizable expression — stay silent
500
+ const keep: string[] = [];
501
+ for (const t of toks) {
502
+ if (
503
+ t.kind !== "name" ||
504
+ g.isFunction(t.text) || g.isConstant(t.text) || t.text.length === 1
505
+ ) {
506
+ keep.push(t.text);
507
+ }
508
+ }
509
+ return keep.join("");
510
+ }
511
+ }
512
+
513
+ const UTF8 = new TextEncoder();
514
+
515
+ /** One token of the query's single lex: a numeric OPERAND or symbolic
516
+ * OPERATOR (the scanner's reading) or an unclaimed TERM between them. The
517
+ * stream is ascending and disjoint and covers every non-spacing byte. A
518
+ * COMPOSED operand (a collapsed arithmetic run, see {@link compose}) carries
519
+ * its evaluated `value`; a plain token's value is its own bytes. */
520
+ interface Token extends Span {
521
+ kind: "operand" | "operator" | "term";
522
+ value?: Uint8Array;
523
+ }
524
+
525
+ /** Sentinel closing the lex walk (never emitted). */
526
+ const END: Token = { i: Infinity, j: Infinity, kind: "operator" };
527
+
528
+ /** Collapse each EVALUATED arithmetic run into one virtual OPERAND token
529
+ * carrying the run's result — the composed stream named operations consume,
530
+ * so an infix sub-expression is a single finished operand ("sum 1*3 4"
531
+ * applies add(3, 4)) exactly as the grammar nests expressions. A run that
532
+ * did not evaluate keeps its original tokens. One ascending merge walk. */
533
+ function compose(
534
+ tokens: Token[],
535
+ results: ReadonlyArray<{ i: number; j: number; bytes: Uint8Array | null }>,
536
+ ): Token[] {
537
+ if (results.length === 0) return tokens;
538
+ const sorted = [...results].sort((a, b) => a.i - b.i);
539
+ const out: Token[] = [];
540
+ let k = 0;
541
+ for (const r of sorted) {
542
+ while (k < tokens.length && tokens[k].i < r.i) out.push(tokens[k++]);
543
+ const inside: Token[] = [];
544
+ while (k < tokens.length && tokens[k].j <= r.j) inside.push(tokens[k++]);
545
+ if (r.bytes !== null) {
546
+ out.push({ i: r.i, j: r.j, kind: "operand", value: r.bytes });
547
+ } else out.push(...inside);
548
+ }
549
+ out.push(...tokens.slice(k));
550
+ return out;
551
+ }
552
+
553
+ /** The AUTHORITY law: a span strictly contained in a larger computed span is
554
+ * that computation's material — the outer computation consumed it — so only
555
+ * the outermost readings survive. Overlapping-but-not-nested spans are
556
+ * rival readings the host's search may still weigh against each other. */
557
+ function authoritative(spans: ComputedSpan[]): ComputedSpan[] {
558
+ return spans.filter((s) =>
559
+ !spans.some((o) =>
560
+ o !== s && o.i <= s.i && s.j <= o.j && o.j - o.i > s.j - s.i
561
+ )
562
+ );
563
+ }
564
+
565
+ /** Keep the first occurrence of each (span, bytes) result. */
566
+ function dedup(spans: ComputedSpan[]): ComputedSpan[] {
567
+ const seen = new Set<string>();
568
+ return spans.filter((s) => {
569
+ const key = s.i + "," + s.j + "," + latin1(s.bytes);
570
+ if (seen.has(key)) return false;
571
+ seen.add(key);
572
+ return true;
573
+ });
574
+ }