@hviana/sema 0.1.5 → 0.1.7

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