@hviana/sema 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +21 -19
- package/dist/example/train_base.js +1952 -1684
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +27 -24
- package/dist/src/alu/src/alu.d.ts +194 -169
- package/dist/src/alu/src/alu.js +414 -374
- package/dist/src/alu/src/expr.d.ts +43 -36
- package/dist/src/alu/src/expr.js +278 -252
- package/dist/src/alu/src/index.d.ts +98 -9
- package/dist/src/alu/src/index.js +63 -9
- package/dist/src/alu/src/kernel-arith.d.ts +5 -1
- package/dist/src/alu/src/kernel-arith.js +285 -195
- package/dist/src/alu/src/kernel-bits.d.ts +5 -1
- package/dist/src/alu/src/kernel-bits.js +115 -81
- package/dist/src/alu/src/kernel-logic.js +63 -33
- package/dist/src/alu/src/kernel-nd.js +195 -155
- package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
- package/dist/src/alu/src/kernel-numeric.js +331 -259
- package/dist/src/alu/src/operation.d.ts +132 -112
- package/dist/src/alu/src/operation.js +163 -149
- package/dist/src/alu/src/parser.d.ts +175 -168
- package/dist/src/alu/src/parser.js +421 -382
- package/dist/src/alu/src/resonance.d.ts +25 -16
- package/dist/src/alu/src/resonance.js +56 -49
- package/dist/src/alu/src/text.d.ts +7 -3
- package/dist/src/alu/src/text.js +37 -31
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +160 -150
- package/dist/src/alu/test/alu.test.js +656 -543
- package/dist/src/bytes.d.ts +5 -1
- package/dist/src/bytes.js +40 -33
- package/dist/src/config.d.ts +102 -102
- package/dist/src/config.js +83 -83
- package/dist/src/derive/src/deduction.d.ts +62 -60
- package/dist/src/derive/src/deduction.js +109 -103
- package/dist/src/derive/src/index.d.ts +7 -1
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +61 -57
- package/dist/src/derive/src/rewrite.d.ts +18 -15
- package/dist/src/derive/src/rewrite.js +67 -59
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +179 -175
- package/dist/src/derive/test/derive.test.js +100 -96
- package/dist/src/extension.d.ts +16 -13
- package/dist/src/geometry.d.ts +42 -17
- package/dist/src/geometry.js +266 -235
- package/dist/src/index.d.ts +17 -2
- package/dist/src/index.js +7 -1
- package/dist/src/ingest-cache.d.ts +30 -25
- package/dist/src/ingest-cache.js +127 -108
- package/dist/src/mind/articulation.d.ts +5 -1
- package/dist/src/mind/articulation.js +112 -77
- package/dist/src/mind/attention.d.ts +108 -40
- package/dist/src/mind/attention.js +871 -749
- package/dist/src/mind/canonical.d.ts +19 -4
- package/dist/src/mind/canonical.js +31 -27
- package/dist/src/mind/graph-search.d.ts +225 -201
- package/dist/src/mind/graph-search.js +821 -742
- package/dist/src/mind/index.d.ts +10 -2
- package/dist/src/mind/junction.d.ts +58 -31
- package/dist/src/mind/junction.js +237 -172
- package/dist/src/mind/learning.d.ts +52 -16
- package/dist/src/mind/learning.js +165 -143
- package/dist/src/mind/match.d.ts +69 -20
- package/dist/src/mind/match.js +318 -259
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +13 -9
- package/dist/src/mind/mechanisms/cast.js +456 -363
- package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
- package/dist/src/mind/mechanisms/confluence.js +183 -152
- package/dist/src/mind/mechanisms/cover.d.ts +8 -2
- package/dist/src/mind/mechanisms/cover.js +210 -148
- package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
- package/dist/src/mind/mechanisms/extraction.js +288 -234
- package/dist/src/mind/mechanisms/recall.d.ts +10 -6
- package/dist/src/mind/mechanisms/recall.js +185 -126
- package/dist/src/mind/mind.d.ts +154 -129
- package/dist/src/mind/mind.js +288 -258
- package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
- package/dist/src/mind/pipeline-mechanism.js +172 -161
- package/dist/src/mind/pipeline.d.ts +14 -4
- package/dist/src/mind/pipeline.js +189 -125
- package/dist/src/mind/primitives.d.ts +32 -8
- package/dist/src/mind/primitives.js +117 -99
- package/dist/src/mind/rationale.d.ts +98 -86
- package/dist/src/mind/rationale.js +121 -113
- package/dist/src/mind/reasoning.d.ts +13 -2
- package/dist/src/mind/reasoning.js +166 -129
- package/dist/src/mind/recognition.d.ts +4 -1
- package/dist/src/mind/recognition.js +208 -183
- package/dist/src/mind/resonance.d.ts +22 -5
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +25 -6
- package/dist/src/mind/trace.js +58 -50
- package/dist/src/mind/traverse.d.ts +58 -16
- package/dist/src/mind/traverse.js +357 -304
- package/dist/src/mind/types.d.ts +127 -120
- package/dist/src/mind/types.js +69 -60
- package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
- package/dist/src/rabitq-hnsw/src/database.js +359 -342
- package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
- package/dist/src/rabitq-hnsw/src/heap.js +80 -75
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
- package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
- package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
- package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
- package/dist/src/rabitq-hnsw/src/prng.js +27 -25
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
- package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
- package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
- package/dist/src/rabitq-hnsw/src/store.js +843 -752
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
- package/dist/src/sema.d.ts +13 -9
- package/dist/src/sema.js +40 -26
- package/dist/src/store-sqlite.d.ts +166 -144
- package/dist/src/store-sqlite.js +660 -578
- package/dist/src/store.d.ts +630 -593
- package/dist/src/store.js +1581 -1440
- package/dist/src/vec.d.ts +9 -5
- package/dist/src/vec.js +73 -61
- package/package.json +2 -2
|
@@ -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
|
|
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,375 +50,406 @@ 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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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);
|
|
85
126
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
return authoritative(dedup(spans));
|
|
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);
|
|
127
166
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
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);
|
|
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;
|
|
168
199
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
|
199
238
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
231
311
|
}
|
|
232
|
-
|
|
312
|
+
}
|
|
233
313
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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;
|
|
244
334
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
* CONCURRENTLY — each 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;
|
|
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));
|
|
304
338
|
}
|
|
305
|
-
|
|
306
|
-
|
|
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 };
|
|
339
|
+
if (args.length !== arity) {
|
|
340
|
+
return null; // under-supplied → decline
|
|
331
341
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
342
|
+
const bytes = this.alu.applyBytesTyped(name, args, (idx) => idx === 0);
|
|
343
|
+
if (bytes === null) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
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
|
|
374
|
+
}
|
|
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;
|
|
389
|
+
}
|
|
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;
|
|
384
397
|
}
|
|
385
|
-
if (
|
|
386
|
-
|
|
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
|
|
398
|
+
if (t.kind === "operand") {
|
|
399
|
+
before = t;
|
|
397
400
|
}
|
|
398
|
-
|
|
401
|
+
}
|
|
402
|
+
if (before && term.i - before.j <= this.host.reach) {
|
|
403
|
+
picked.unshift(before);
|
|
404
|
+
start = before.i;
|
|
405
|
+
}
|
|
399
406
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return keep.join("");
|
|
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
|
|
421
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
|
|
441
|
+
}
|
|
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
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return keep.join("");
|
|
452
|
+
}
|
|
422
453
|
}
|
|
423
454
|
const UTF8 = new TextEncoder();
|
|
424
455
|
/** Sentinel closing the lex walk (never emitted). */
|
|
@@ -429,41 +460,49 @@ const END = { i: Infinity, j: Infinity, kind: "operator" };
|
|
|
429
460
|
* applies add(3, 4)) exactly as the grammar nests expressions. A run that
|
|
430
461
|
* did not evaluate keeps its original tokens. One ascending merge walk. */
|
|
431
462
|
function compose(tokens, results) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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);
|
|
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++]);
|
|
448
472
|
}
|
|
449
|
-
|
|
450
|
-
|
|
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);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
out.push(...tokens.slice(k));
|
|
484
|
+
return out;
|
|
451
485
|
}
|
|
452
486
|
/** The AUTHORITY law: a span strictly contained in a larger computed span is
|
|
453
487
|
* that computation's material — the outer computation consumed it — so only
|
|
454
488
|
* the outermost readings survive. Overlapping-but-not-nested spans are
|
|
455
489
|
* rival readings the host's search may still weigh against each other. */
|
|
456
490
|
function authoritative(spans) {
|
|
457
|
-
|
|
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
|
+
);
|
|
458
496
|
}
|
|
459
497
|
/** Keep the first occurrence of each (span, bytes) result. */
|
|
460
498
|
function dedup(spans) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
+
});
|
|
469
508
|
}
|