@hviana/sema 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -5
- package/CITATION.cff +49 -0
- package/HOW_IT_WORKS.md +11 -12
- package/README.md +7 -5
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +19 -21
- package/dist/example/train_base.js +1687 -1952
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +24 -27
- package/dist/src/alu/src/alu.d.ts +169 -194
- package/dist/src/alu/src/alu.js +374 -414
- package/dist/src/alu/src/expr.d.ts +36 -43
- package/dist/src/alu/src/expr.js +252 -278
- package/dist/src/alu/src/index.d.ts +9 -98
- package/dist/src/alu/src/index.js +10 -64
- package/dist/src/alu/src/kernel-arith.d.ts +1 -5
- package/dist/src/alu/src/kernel-arith.js +195 -285
- package/dist/src/alu/src/kernel-bits.d.ts +1 -5
- package/dist/src/alu/src/kernel-bits.js +81 -115
- package/dist/src/alu/src/kernel-logic.js +33 -63
- package/dist/src/alu/src/kernel-nd.js +155 -195
- package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
- package/dist/src/alu/src/kernel-numeric.js +259 -331
- package/dist/src/alu/src/operation.d.ts +112 -132
- package/dist/src/alu/src/operation.js +149 -163
- package/dist/src/alu/src/parser.d.ts +168 -175
- package/dist/src/alu/src/parser.js +382 -421
- package/dist/src/alu/src/resonance.d.ts +16 -25
- package/dist/src/alu/src/resonance.js +49 -56
- package/dist/src/alu/src/text.d.ts +3 -7
- package/dist/src/alu/src/text.js +31 -37
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +150 -160
- package/dist/src/alu/test/alu.test.js +543 -656
- package/dist/src/bytes.d.ts +1 -5
- package/dist/src/bytes.js +33 -40
- package/dist/src/config.d.ts +99 -102
- package/dist/src/config.js +78 -83
- package/dist/src/derive/src/deduction.d.ts +60 -62
- package/dist/src/derive/src/deduction.js +103 -109
- package/dist/src/derive/src/index.d.ts +1 -7
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +57 -61
- package/dist/src/derive/src/rewrite.d.ts +15 -18
- package/dist/src/derive/src/rewrite.js +59 -67
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +175 -179
- package/dist/src/derive/test/derive.test.js +96 -100
- package/dist/src/extension.d.ts +13 -16
- package/dist/src/geometry.d.ts +17 -42
- package/dist/src/geometry.js +235 -266
- package/dist/src/index.d.ts +2 -17
- package/dist/src/index.js +2 -12
- package/dist/src/ingest-cache.d.ts +25 -30
- package/dist/src/ingest-cache.js +108 -127
- package/dist/src/mind/articulation.d.ts +1 -5
- package/dist/src/mind/articulation.js +77 -112
- package/dist/src/mind/attention.d.ts +40 -108
- package/dist/src/mind/attention.js +749 -871
- package/dist/src/mind/canonical.d.ts +4 -19
- package/dist/src/mind/canonical.js +27 -31
- package/dist/src/mind/graph-search.d.ts +201 -225
- package/dist/src/mind/graph-search.js +742 -821
- package/dist/src/mind/index.d.ts +2 -10
- package/dist/src/mind/junction.d.ts +31 -58
- package/dist/src/mind/junction.js +172 -237
- package/dist/src/mind/learning.d.ts +16 -52
- package/dist/src/mind/learning.js +143 -165
- package/dist/src/mind/match.d.ts +20 -69
- package/dist/src/mind/match.js +259 -318
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +9 -13
- package/dist/src/mind/mechanisms/cast.js +363 -456
- package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
- package/dist/src/mind/mechanisms/confluence.js +152 -183
- package/dist/src/mind/mechanisms/cover.d.ts +2 -8
- package/dist/src/mind/mechanisms/cover.js +148 -210
- package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
- package/dist/src/mind/mechanisms/extraction.js +234 -288
- package/dist/src/mind/mechanisms/recall.d.ts +6 -10
- package/dist/src/mind/mechanisms/recall.js +126 -185
- package/dist/src/mind/mind.d.ts +129 -154
- package/dist/src/mind/mind.js +258 -288
- package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
- package/dist/src/mind/pipeline-mechanism.js +161 -172
- package/dist/src/mind/pipeline.d.ts +4 -14
- package/dist/src/mind/pipeline.js +125 -189
- package/dist/src/mind/primitives.d.ts +8 -32
- package/dist/src/mind/primitives.js +99 -117
- package/dist/src/mind/rationale.d.ts +86 -98
- package/dist/src/mind/rationale.js +113 -121
- package/dist/src/mind/reasoning.d.ts +2 -13
- package/dist/src/mind/reasoning.js +129 -166
- package/dist/src/mind/recognition.d.ts +1 -4
- package/dist/src/mind/recognition.js +183 -208
- package/dist/src/mind/resonance.d.ts +5 -22
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +6 -25
- package/dist/src/mind/trace.js +50 -58
- package/dist/src/mind/traverse.d.ts +16 -58
- package/dist/src/mind/traverse.js +304 -357
- package/dist/src/mind/types.d.ts +120 -127
- package/dist/src/mind/types.js +60 -69
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +9 -13
- package/dist/src/sema.js +26 -40
- package/dist/src/store-sqlite.d.ts +169 -166
- package/dist/src/store-sqlite.js +762 -662
- package/dist/src/store.d.ts +586 -630
- package/dist/src/store.js +1423 -1581
- package/dist/src/vec.d.ts +5 -9
- package/dist/src/vec.js +61 -73
- package/example/train_base.ts +13 -10
- package/package.json +1 -1
- package/src/alu/README.md +1 -1
- package/src/alu/src/index.ts +1 -1
- package/src/config.ts +19 -27
- package/src/index.ts +6 -11
- package/src/rabitq-ivf/README.md +56 -0
- package/src/rabitq-ivf/src/database.ts +276 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
- package/src/rabitq-ivf/src/ivf.ts +1330 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
- package/src/store-sqlite.ts +196 -9
- package/src/store.ts +8 -32
- package/test/08-storage.test.mjs +3 -3
- package/test/14-scaling.test.mjs +2 -2
- package/test/35-ivf.test.mjs +263 -0
- package/test/36-bloom.test.mjs +123 -0
- package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
- package/dist/src/rabitq-hnsw/src/database.js +0 -405
- package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
- package/dist/src/rabitq-hnsw/src/heap.js +0 -94
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
- package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
- package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
- package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
- package/dist/src/rabitq-hnsw/src/prng.js +0 -38
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
- package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
- package/dist/src/rabitq-hnsw/src/store.js +0 -916
- package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
- package/src/rabitq-hnsw/README.md +0 -303
- package/src/rabitq-hnsw/src/database.ts +0 -492
- package/src/rabitq-hnsw/src/heap.ts +0 -90
- package/src/rabitq-hnsw/src/hnsw.ts +0 -514
- package/src/rabitq-hnsw/src/store.ts +0 -994
- package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
- /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
package/dist/src/alu/src/alu.js
CHANGED
|
@@ -32,449 +32,409 @@
|
|
|
32
32
|
// glyph, or an operation the bytes do not spell at all (an integral, a limit) —
|
|
33
33
|
// are resolved only through the host port; the kernel itself stays a pure,
|
|
34
34
|
// deterministic machine over literal numerals and registered surface forms.
|
|
35
|
-
import { asReal, decimalCodec, nd, parseValue, real } from "./value.js";
|
|
36
|
-
import { NO_RESONANCE, OperationRegistry } from "./operation.js";
|
|
35
|
+
import { asReal, decimalCodec, nd, parseValue, real, } from "./value.js";
|
|
36
|
+
import { NO_RESONANCE, OperationRegistry, } from "./operation.js";
|
|
37
37
|
import { registerLogic } from "./kernel-logic.js";
|
|
38
38
|
import { registerBits } from "./kernel-bits.js";
|
|
39
39
|
import { registerArith } from "./kernel-arith.js";
|
|
40
40
|
import { registerNumeric } from "./kernel-numeric.js";
|
|
41
41
|
import { registerNd } from "./kernel-nd.js";
|
|
42
42
|
import { ExprGrammar } from "./expr.js";
|
|
43
|
-
import { QueryParser, STRUCTURAL_HOST } from "./parser.js";
|
|
44
|
-
import {
|
|
45
|
-
CLOSE,
|
|
46
|
-
DOT,
|
|
47
|
-
isDigitByte,
|
|
48
|
-
isSepByte,
|
|
49
|
-
matchBracket,
|
|
50
|
-
OPEN,
|
|
51
|
-
trimEnd,
|
|
52
|
-
trimStart,
|
|
53
|
-
} from "./text.js";
|
|
43
|
+
import { QueryParser, STRUCTURAL_HOST, } from "./parser.js";
|
|
44
|
+
import { CLOSE, DOT, isDigitByte, isSepByte, matchBracket, OPEN, trimEnd, trimStart, } from "./text.js";
|
|
54
45
|
import { latin1 } from "../../bytes.js";
|
|
55
46
|
export class Alu {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
/** Recognise and evaluate every computation `query` invokes — infix
|
|
89
|
-
* arithmetic runs, and operations named literally or by meaning (through the
|
|
90
|
-
* host port). All async resonance is resolved inside, so the caller
|
|
91
|
-
* receives finished spans it can fold synchronously into its search. See
|
|
92
|
-
* {@link "./parser.js".QueryParser.parse}. */
|
|
93
|
-
parse(query) {
|
|
94
|
-
return this.parser.parse(query);
|
|
95
|
-
}
|
|
96
|
-
/** Apply an operation to operand byte spans — operand STRUCTURE recognised
|
|
97
|
-
* into Values, every reachable symbol's resonance pre-resolved through the
|
|
98
|
-
* host port — and encode the result canonically; null when the computation
|
|
99
|
-
* declines. See {@link "./parser.js".QueryParser.compute}. */
|
|
100
|
-
compute(name, operandBytes, asSymbol = () => false) {
|
|
101
|
-
return this.parser.compute(name, operandBytes, asSymbol);
|
|
102
|
-
}
|
|
103
|
-
/** Infix arity of an op: its fixed arity, or 2 for a variadic op (an infix
|
|
104
|
-
* operator binds two operands; the registry's variadic fold still accepts
|
|
105
|
-
* the pair). */
|
|
106
|
-
infixArity(op) {
|
|
107
|
-
return op.arity === "variadic" ? 2 : op.arity;
|
|
108
|
-
}
|
|
109
|
-
/** Collect the symbolic surface forms (punctuation, no letters/digits) as
|
|
110
|
-
* UTF-8 byte patterns. Each maps to the binary claimant when one exists, so
|
|
111
|
-
* an infix scan resolves a shared symbol to its two-operand reading. */
|
|
112
|
-
buildSymbolicForms() {
|
|
113
|
-
const enc = new TextEncoder();
|
|
114
|
-
const isSymbolic = (s) => s.length > 0 && !/[A-Za-z0-9]/.test(s);
|
|
115
|
-
const byForm = new Map();
|
|
116
|
-
for (const { form, name } of this.registry.formEntries()) {
|
|
117
|
-
if (!isSymbolic(form)) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const a = byForm.get(form);
|
|
121
|
-
if (a) {
|
|
122
|
-
a.push(name);
|
|
123
|
-
} else {
|
|
124
|
-
byForm.set(form, [name]);
|
|
125
|
-
}
|
|
47
|
+
registry = new OperationRegistry();
|
|
48
|
+
codec;
|
|
49
|
+
rt;
|
|
50
|
+
/** Symbolic operator forms (no ASCII letter/digit), as byte patterns, sorted
|
|
51
|
+
* longest-first so the scanner matches "<=" before "<". */
|
|
52
|
+
symbolicForms;
|
|
53
|
+
_grammar;
|
|
54
|
+
_anchors;
|
|
55
|
+
/** How many {@link apply} calls ended in a caught throw this session, and
|
|
56
|
+
* the most recent caught error. A caught throw is USUALLY a routine
|
|
57
|
+
* decline (a symbol fed to arithmetic — the "rule does not fire"
|
|
58
|
+
* contract), but the same catch would also swallow a genuine kernel bug;
|
|
59
|
+
* these two fields make that observable instead of silent. Zero cost when
|
|
60
|
+
* nothing throws. */
|
|
61
|
+
applyCaught = 0;
|
|
62
|
+
lastApplyError = null;
|
|
63
|
+
decoder = new TextDecoder();
|
|
64
|
+
/** The query parser, wired to the host port — internal; hosts reach it only
|
|
65
|
+
* through {@link parse} and {@link compute}. */
|
|
66
|
+
parser;
|
|
67
|
+
constructor(opts = {}, host = STRUCTURAL_HOST) {
|
|
68
|
+
const precision = opts.precision ?? 6;
|
|
69
|
+
this.codec = decimalCodec(precision);
|
|
70
|
+
this.rt = { tol: opts.tol ?? 1e-10, maxIter: opts.maxIter ?? 1000 };
|
|
71
|
+
registerLogic(this.registry);
|
|
72
|
+
registerBits(this.registry);
|
|
73
|
+
registerArith(this.registry);
|
|
74
|
+
registerNumeric(this.registry);
|
|
75
|
+
registerNd(this.registry);
|
|
76
|
+
this.symbolicForms = this.buildSymbolicForms();
|
|
77
|
+
this.parser = new QueryParser(this, host);
|
|
126
78
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
79
|
+
/** Recognise and evaluate every computation `query` invokes — infix
|
|
80
|
+
* arithmetic runs, and operations named literally or by meaning (through the
|
|
81
|
+
* host port). All async resonance is resolved inside, so the caller
|
|
82
|
+
* receives finished spans it can fold synchronously into its search. See
|
|
83
|
+
* {@link "./parser.js".QueryParser.parse}. */
|
|
84
|
+
parse(query) {
|
|
85
|
+
return this.parser.parse(query);
|
|
86
|
+
}
|
|
87
|
+
/** Apply an operation to operand byte spans — operand STRUCTURE recognised
|
|
88
|
+
* into Values, every reachable symbol's resonance pre-resolved through the
|
|
89
|
+
* host port — and encode the result canonically; null when the computation
|
|
90
|
+
* declines. See {@link "./parser.js".QueryParser.compute}. */
|
|
91
|
+
compute(name, operandBytes, asSymbol = () => false) {
|
|
92
|
+
return this.parser.compute(name, operandBytes, asSymbol);
|
|
141
93
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
lookupOperator(form) {
|
|
148
|
-
return this.registry.lookupForm(form);
|
|
149
|
-
}
|
|
150
|
-
/** The operation CONCEPT anchors: the (canonical name, form bytes) pairs a
|
|
151
|
-
* span's MEANING is resonated against, so an operation the bytes do not
|
|
152
|
-
* literally spell (an integral, a derivative, a limit, …, in any modality)
|
|
153
|
-
* is recognised by gist nearness to one of these forms. This is what makes
|
|
154
|
-
* operation recognition generic and modality-agnostic rather than a fixed
|
|
155
|
-
* symbol table — and the ALU, not the host, decides which of its surface
|
|
156
|
-
* forms are anchors, using its own machinery:
|
|
157
|
-
*
|
|
158
|
-
* • a form the SCANNER already reads in full (a pure operator symbol like
|
|
159
|
-
* "<=", a numeral like "0") is excluded — the literal path owns it, and
|
|
160
|
-
* its meaning-space image would only add noise;
|
|
161
|
-
* • a form must be COMPOUND (≥ 2 runes): a single atom's gist is a
|
|
162
|
-
* coordinate of the alphabet, not a distributional meaning, so there is
|
|
163
|
-
* nothing for resonance to read.
|
|
164
|
-
*
|
|
165
|
-
* Cached once; the stable array identity lets the host memoise whatever
|
|
166
|
-
* representation (gists, indices) it derives from it. */
|
|
167
|
-
conceptAnchors() {
|
|
168
|
-
if (this._anchors) {
|
|
169
|
-
return this._anchors;
|
|
94
|
+
/** Infix arity of an op: its fixed arity, or 2 for a variadic op (an infix
|
|
95
|
+
* operator binds two operands; the registry's variadic fold still accepts
|
|
96
|
+
* the pair). */
|
|
97
|
+
infixArity(op) {
|
|
98
|
+
return op.arity === "variadic" ? 2 : op.arity;
|
|
170
99
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
100
|
+
/** Collect the symbolic surface forms (punctuation, no letters/digits) as
|
|
101
|
+
* UTF-8 byte patterns. Each maps to the binary claimant when one exists, so
|
|
102
|
+
* an infix scan resolves a shared symbol to its two-operand reading. */
|
|
103
|
+
buildSymbolicForms() {
|
|
104
|
+
const enc = new TextEncoder();
|
|
105
|
+
const isSymbolic = (s) => s.length > 0 && !/[A-Za-z0-9]/.test(s);
|
|
106
|
+
const byForm = new Map();
|
|
107
|
+
for (const { form, name } of this.registry.formEntries()) {
|
|
108
|
+
if (!isSymbolic(form))
|
|
109
|
+
continue;
|
|
110
|
+
const a = byForm.get(form);
|
|
111
|
+
if (a)
|
|
112
|
+
a.push(name);
|
|
113
|
+
else
|
|
114
|
+
byForm.set(form, [name]);
|
|
115
|
+
}
|
|
116
|
+
const out = [];
|
|
117
|
+
for (const [form, names] of byForm) {
|
|
118
|
+
// Prefer a binary/variadic claimant for infix matching.
|
|
119
|
+
let chosen = names[0];
|
|
120
|
+
let chosenArity = this.infixArity(this.registry.get(chosen));
|
|
121
|
+
for (const n of names) {
|
|
122
|
+
const a = this.infixArity(this.registry.get(n));
|
|
123
|
+
if (a === 2) {
|
|
124
|
+
chosen = n;
|
|
125
|
+
chosenArity = a;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
out.push({ bytes: enc.encode(form), name: chosen, arity: chosenArity });
|
|
130
|
+
}
|
|
131
|
+
// Longest first so a longer form wins over a prefix of it ("<=" before "<").
|
|
132
|
+
out.sort((a, b) => b.bytes.length - a.bytes.length);
|
|
133
|
+
return out;
|
|
188
134
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
* prefix/function/constant resolution all read off the registered ops, and
|
|
193
|
-
* every evaluation step routes back through this registry's own arithmetic —
|
|
194
|
-
* recursion all the way down (see expr.ts). */
|
|
195
|
-
get grammar() {
|
|
196
|
-
if (!this._grammar) {
|
|
197
|
-
const scalarCtx = this.registry.context(NO_RESONANCE, this.rt);
|
|
198
|
-
this._grammar = new ExprGrammar(
|
|
199
|
-
this.registry,
|
|
200
|
-
(name, args) => asReal(scalarCtx.apply(name, args.map(real))),
|
|
201
|
-
);
|
|
135
|
+
/** The canonical op name(s) a literal surface form names (exact lookup). */
|
|
136
|
+
lookupOperator(form) {
|
|
137
|
+
return this.registry.lookupForm(form);
|
|
202
138
|
}
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// A numeral: a run of digits, with at most one interior decimal point that
|
|
238
|
-
// is itself flanked by digits (so a trailing "." is left to the operators
|
|
239
|
-
// / skip path, not swallowed).
|
|
240
|
-
if (
|
|
241
|
-
isDigitByte(c) || (c === DOT && i + 1 < n && isDigitByte(bytes[i + 1]))
|
|
242
|
-
) {
|
|
243
|
-
let j = i;
|
|
244
|
-
let seenDot = false;
|
|
245
|
-
while (j < n) {
|
|
246
|
-
if (isDigitByte(bytes[j])) {
|
|
247
|
-
j++;
|
|
248
|
-
} else if (
|
|
249
|
-
bytes[j] === DOT && !seenDot && j + 1 < n &&
|
|
250
|
-
isDigitByte(bytes[j + 1])
|
|
251
|
-
) {
|
|
252
|
-
seenDot = true;
|
|
253
|
-
j++;
|
|
254
|
-
} else {
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
139
|
+
/** The operation CONCEPT anchors: the (canonical name, form bytes) pairs a
|
|
140
|
+
* span's MEANING is resonated against, so an operation the bytes do not
|
|
141
|
+
* literally spell (an integral, a derivative, a limit, …, in any modality)
|
|
142
|
+
* is recognised by gist nearness to one of these forms. This is what makes
|
|
143
|
+
* operation recognition generic and modality-agnostic rather than a fixed
|
|
144
|
+
* symbol table — and the ALU, not the host, decides which of its surface
|
|
145
|
+
* forms are anchors, using its own machinery:
|
|
146
|
+
*
|
|
147
|
+
* • a form the SCANNER already reads in full (a pure operator symbol like
|
|
148
|
+
* "<=", a numeral like "0") is excluded — the literal path owns it, and
|
|
149
|
+
* its meaning-space image would only add noise;
|
|
150
|
+
* • a form must be COMPOUND (≥ 2 runes): a single atom's gist is a
|
|
151
|
+
* coordinate of the alphabet, not a distributional meaning, so there is
|
|
152
|
+
* nothing for resonance to read.
|
|
153
|
+
*
|
|
154
|
+
* Cached once; the stable array identity lets the host memoise whatever
|
|
155
|
+
* representation (gists, indices) it derives from it. */
|
|
156
|
+
conceptAnchors() {
|
|
157
|
+
if (this._anchors)
|
|
158
|
+
return this._anchors;
|
|
159
|
+
const enc = new TextEncoder();
|
|
160
|
+
const out = [];
|
|
161
|
+
for (const { form, name } of this.registry.formEntries()) {
|
|
162
|
+
if ([...form].length < 2)
|
|
163
|
+
continue; // a lone atom carries no gist
|
|
164
|
+
const bytes = enc.encode(form);
|
|
165
|
+
// Fully claimed by the literal scanner → the literal path owns it.
|
|
166
|
+
const { operands, operators } = this.scan(bytes);
|
|
167
|
+
let claimed = 0;
|
|
168
|
+
for (const t of [...operands, ...operators])
|
|
169
|
+
claimed += t.j - t.i;
|
|
170
|
+
if (claimed === bytes.length)
|
|
171
|
+
continue;
|
|
172
|
+
out.push({ name, form: bytes });
|
|
257
173
|
}
|
|
258
|
-
|
|
259
|
-
i = j;
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
// The longest symbolic operator form starting here.
|
|
263
|
-
const op = this.matchSymbolic(bytes, i);
|
|
264
|
-
if (op) {
|
|
265
|
-
operators.push({
|
|
266
|
-
i,
|
|
267
|
-
j: i + op.bytes.length,
|
|
268
|
-
name: op.name,
|
|
269
|
-
arity: op.arity,
|
|
270
|
-
});
|
|
271
|
-
i += op.bytes.length;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
i++;
|
|
174
|
+
return (this._anchors = out);
|
|
275
175
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
let ok = true;
|
|
285
|
-
for (let k = 0; k < len; k++) {
|
|
286
|
-
if (bytes[at + k] !== f.bytes[k]) {
|
|
287
|
-
ok = false;
|
|
288
|
-
break;
|
|
176
|
+
/** The registry-derived expression grammar, built once: infix binding and
|
|
177
|
+
* prefix/function/constant resolution all read off the registered ops, and
|
|
178
|
+
* every evaluation step routes back through this registry's own arithmetic —
|
|
179
|
+
* recursion all the way down (see expr.ts). */
|
|
180
|
+
get grammar() {
|
|
181
|
+
if (!this._grammar) {
|
|
182
|
+
const scalarCtx = this.registry.context(NO_RESONANCE, this.rt);
|
|
183
|
+
this._grammar = new ExprGrammar(this.registry, (name, args) => asReal(scalarCtx.apply(name, args.map(real))));
|
|
289
184
|
}
|
|
290
|
-
|
|
291
|
-
if (ok) {
|
|
292
|
-
return f;
|
|
293
|
-
}
|
|
185
|
+
return this._grammar;
|
|
294
186
|
}
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
* the irreducible scalar floor (a numeral's digits → a quantity, via {@link
|
|
300
|
-
* parseValue}); recognising whether a span is one quantity or a LIST of them is
|
|
301
|
-
* layered here, over {@link scan}, so no caller re-implements it.
|
|
302
|
-
*
|
|
303
|
-
* A LIST is a run of element values joined by a CONSISTENT separator. No
|
|
304
|
-
* spelling is privileged: two complementary readings cover the forms a list
|
|
305
|
-
* takes —
|
|
306
|
-
*
|
|
307
|
-
* • CONTAINER — an explicit `[ … ]` group delimits a list whose elements may
|
|
308
|
-
* be anything (symbols, nested groups, mixed): split its interior at the TOP
|
|
309
|
-
* level (bracket-depth aware) on separator runs, each element recognised in
|
|
310
|
-
* turn (so nesting and heterogeneity recurse for free). This is also the
|
|
311
|
-
* codec's canonical OUTPUT spelling, so a computed list feeds straight back
|
|
312
|
-
* in as an operand.
|
|
313
|
-
* • SEQUENCE — a bare run of ≥2 numeric operands with a consistent connective
|
|
314
|
-
* between them (`1 2 3`, `1, 2, 3`, `1 and 2 and 3`): the operands {@link
|
|
315
|
-
* scan} finds are the elements, and whatever sits between them — a space, a
|
|
316
|
-
* comma, " and " — is the separator, accepted as long as it is the same
|
|
317
|
-
* throughout and the run has no leftover edges.
|
|
318
|
-
*
|
|
319
|
-
* Anything else is a SCALAR ({@link parseValue}): a numeral, or an opaque
|
|
320
|
-
* symbol of any modality (a learnt form, an operator name a higher-order op
|
|
321
|
-
* will resolve, a single word). */
|
|
322
|
-
recogniseValue(bytes) {
|
|
323
|
-
const s = bytes.subarray(trimStart(bytes), trimEnd(bytes));
|
|
324
|
-
// CONTAINER: a balanced [ … ] group spanning the whole (trimmed) span.
|
|
325
|
-
if (s.length >= 2 && s[0] === OPEN && matchBracket(s, 0) === s.length - 1) {
|
|
326
|
-
return nd(this.recogniseContainer(s.subarray(1, s.length - 1)));
|
|
187
|
+
/** Evaluate an expression's bytes at a variable binding, through this
|
|
188
|
+
* registry's own arithmetic (see {@link grammar}). */
|
|
189
|
+
evalExpression(bytes, variable, at) {
|
|
190
|
+
return this.grammar.eval(this.decoder.decode(bytes), variable, at);
|
|
327
191
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return nd(seq);
|
|
192
|
+
/** Whether a name is a registered operation. */
|
|
193
|
+
has(name) {
|
|
194
|
+
return this.registry.has(name);
|
|
332
195
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
* depth aware) spans divided by separator RUNS (maximal runs of {@link
|
|
338
|
-
* isSepByte}). The brackets are the explicit delimiter, so the divider's
|
|
339
|
-
* spelling is not privileged (`[1,2,3]`, `[1, 2, 3]`, `[1 2 3]` are the same
|
|
340
|
-
* list). Each element is recognised in turn, so a nested `[ … ]` element
|
|
341
|
-
* recurses and a symbol element stays opaque. An empty interior is `[]`. */
|
|
342
|
-
recogniseContainer(inner) {
|
|
343
|
-
if (trimEnd(inner) <= trimStart(inner)) {
|
|
344
|
-
return []; // empty / all space → []
|
|
196
|
+
/** The infix arity of a registered op (for the host to size a rule), or 0. */
|
|
197
|
+
arityOf(name) {
|
|
198
|
+
const op = this.registry.get(name);
|
|
199
|
+
return op ? this.infixArity(op) : 0;
|
|
345
200
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
201
|
+
/** Whether an op acts on an EXPRESSION (a function) rather than plain numbers
|
|
202
|
+
* — declared by the op itself at registration (the `expression` trait), so
|
|
203
|
+
* there is no side table of names here. */
|
|
204
|
+
isExpressionOp(name) {
|
|
205
|
+
return this.registry.get(name)?.expression === true;
|
|
206
|
+
}
|
|
207
|
+
/** Scan a byte span for operand (numeric) and operator (symbolic) spans. A
|
|
208
|
+
* pure, deterministic left-to-right lexer: at each position take a numeral if
|
|
209
|
+
* one starts there, else the longest symbolic operator form, else skip one
|
|
210
|
+
* byte. Numerals and operator symbols are disjoint character classes, so the
|
|
211
|
+
* two never overlap. */
|
|
212
|
+
scan(bytes) {
|
|
213
|
+
const operands = [];
|
|
214
|
+
const operators = [];
|
|
215
|
+
const n = bytes.length;
|
|
216
|
+
let i = 0;
|
|
217
|
+
while (i < n) {
|
|
218
|
+
const c = bytes[i];
|
|
219
|
+
// A numeral: a run of digits, with at most one interior decimal point that
|
|
220
|
+
// is itself flanked by digits (so a trailing "." is left to the operators
|
|
221
|
+
// / skip path, not swallowed).
|
|
222
|
+
if (isDigitByte(c) || (c === DOT && i + 1 < n && isDigitByte(bytes[i + 1]))) {
|
|
223
|
+
let j = i;
|
|
224
|
+
let seenDot = false;
|
|
225
|
+
while (j < n) {
|
|
226
|
+
if (isDigitByte(bytes[j])) {
|
|
227
|
+
j++;
|
|
228
|
+
}
|
|
229
|
+
else if (bytes[j] === DOT && !seenDot && j + 1 < n &&
|
|
230
|
+
isDigitByte(bytes[j + 1])) {
|
|
231
|
+
seenDot = true;
|
|
232
|
+
j++;
|
|
233
|
+
}
|
|
234
|
+
else
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
operands.push({ i, j, value: parseValue(bytes.subarray(i, j)) });
|
|
238
|
+
i = j;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
// The longest symbolic operator form starting here.
|
|
242
|
+
const op = this.matchSymbolic(bytes, i);
|
|
243
|
+
if (op) {
|
|
244
|
+
operators.push({
|
|
245
|
+
i,
|
|
246
|
+
j: i + op.bytes.length,
|
|
247
|
+
name: op.name,
|
|
248
|
+
arity: op.arity,
|
|
249
|
+
});
|
|
250
|
+
i += op.bytes.length;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
i++;
|
|
361
254
|
}
|
|
362
|
-
|
|
363
|
-
start = q;
|
|
364
|
-
p = q;
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
p++;
|
|
255
|
+
return { operands, operators };
|
|
368
256
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
257
|
+
matchSymbolic(bytes, at) {
|
|
258
|
+
for (const f of this.symbolicForms) {
|
|
259
|
+
const len = f.bytes.length;
|
|
260
|
+
if (at + len > bytes.length)
|
|
261
|
+
continue;
|
|
262
|
+
let ok = true;
|
|
263
|
+
for (let k = 0; k < len; k++) {
|
|
264
|
+
if (bytes[at + k] !== f.bytes[k]) {
|
|
265
|
+
ok = false;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (ok)
|
|
270
|
+
return f;
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
382
273
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
274
|
+
/** Read a byte span into a {@link Value} by recognising its STRUCTURE — the
|
|
275
|
+
* byte⇄Value boundary the host computes across. The kernel itself only knows
|
|
276
|
+
* the irreducible scalar floor (a numeral's digits → a quantity, via {@link
|
|
277
|
+
* parseValue}); recognising whether a span is one quantity or a LIST of them is
|
|
278
|
+
* layered here, over {@link scan}, so no caller re-implements it.
|
|
279
|
+
*
|
|
280
|
+
* A LIST is a run of element values joined by a CONSISTENT separator. No
|
|
281
|
+
* spelling is privileged: two complementary readings cover the forms a list
|
|
282
|
+
* takes —
|
|
283
|
+
*
|
|
284
|
+
* • CONTAINER — an explicit `[ … ]` group delimits a list whose elements may
|
|
285
|
+
* be anything (symbols, nested groups, mixed): split its interior at the TOP
|
|
286
|
+
* level (bracket-depth aware) on separator runs, each element recognised in
|
|
287
|
+
* turn (so nesting and heterogeneity recurse for free). This is also the
|
|
288
|
+
* codec's canonical OUTPUT spelling, so a computed list feeds straight back
|
|
289
|
+
* in as an operand.
|
|
290
|
+
* • SEQUENCE — a bare run of ≥2 numeric operands with a consistent connective
|
|
291
|
+
* between them (`1 2 3`, `1, 2, 3`, `1 and 2 and 3`): the operands {@link
|
|
292
|
+
* scan} finds are the elements, and whatever sits between them — a space, a
|
|
293
|
+
* comma, " and " — is the separator, accepted as long as it is the same
|
|
294
|
+
* throughout and the run has no leftover edges.
|
|
295
|
+
*
|
|
296
|
+
* Anything else is a SCALAR ({@link parseValue}): a numeral, or an opaque
|
|
297
|
+
* symbol of any modality (a learnt form, an operator name a higher-order op
|
|
298
|
+
* will resolve, a single word). */
|
|
299
|
+
recogniseValue(bytes) {
|
|
300
|
+
const s = bytes.subarray(trimStart(bytes), trimEnd(bytes));
|
|
301
|
+
// CONTAINER: a balanced [ … ] group spanning the whole (trimmed) span.
|
|
302
|
+
if (s.length >= 2 && s[0] === OPEN && matchBracket(s, 0) === s.length - 1) {
|
|
303
|
+
return nd(this.recogniseContainer(s.subarray(1, s.length - 1)));
|
|
304
|
+
}
|
|
305
|
+
// SEQUENCE: ≥2 numeric operands with a consistent connective between them.
|
|
306
|
+
const seq = this.recogniseSequence(s);
|
|
307
|
+
if (seq !== null)
|
|
308
|
+
return nd(seq);
|
|
309
|
+
// SCALAR floor.
|
|
310
|
+
return parseValue(s);
|
|
386
311
|
}
|
|
387
|
-
|
|
388
|
-
|
|
312
|
+
/** Split a CONTAINER's interior into its element values — top-level (bracket-
|
|
313
|
+
* depth aware) spans divided by separator RUNS (maximal runs of {@link
|
|
314
|
+
* isSepByte}). The brackets are the explicit delimiter, so the divider's
|
|
315
|
+
* spelling is not privileged (`[1,2,3]`, `[1, 2, 3]`, `[1 2 3]` are the same
|
|
316
|
+
* list). Each element is recognised in turn, so a nested `[ … ]` element
|
|
317
|
+
* recurses and a symbol element stays opaque. An empty interior is `[]`. */
|
|
318
|
+
recogniseContainer(inner) {
|
|
319
|
+
if (trimEnd(inner) <= trimStart(inner))
|
|
320
|
+
return []; // empty / all space → []
|
|
321
|
+
const elements = [];
|
|
322
|
+
let depth = 0;
|
|
323
|
+
let start = 0;
|
|
324
|
+
let p = 0;
|
|
325
|
+
while (p < inner.length) {
|
|
326
|
+
const c = inner[p];
|
|
327
|
+
if (c === OPEN)
|
|
328
|
+
depth++;
|
|
329
|
+
else if (c === CLOSE)
|
|
330
|
+
depth--;
|
|
331
|
+
if (depth === 0 && isSepByte(c)) {
|
|
332
|
+
let q = p;
|
|
333
|
+
while (q < inner.length && isSepByte(inner[q]))
|
|
334
|
+
q++;
|
|
335
|
+
elements.push(inner.subarray(start, p));
|
|
336
|
+
start = q;
|
|
337
|
+
p = q;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
p++;
|
|
341
|
+
}
|
|
342
|
+
elements.push(inner.subarray(start));
|
|
343
|
+
return elements.map((e) => this.recogniseValue(e));
|
|
389
344
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
345
|
+
/** Recognise a bare SEQUENCE — ≥2 numeric operands separated by a consistent
|
|
346
|
+
* connective, with no leftover material at the edges — or null when the span
|
|
347
|
+
* is not such a sequence. The operands are exactly the ones {@link scan}
|
|
348
|
+
* finds; the bytes between consecutive operands are the separator, which need
|
|
349
|
+
* only be the SAME throughout (a space, a comma, " and ", …) — its spelling is
|
|
350
|
+
* not constrained, so no separator is privileged. */
|
|
351
|
+
recogniseSequence(s) {
|
|
352
|
+
const { operands } = this.scan(s);
|
|
353
|
+
if (operands.length < 2)
|
|
354
|
+
return null;
|
|
355
|
+
// No leftover before the first operand or after the last (a clean sequence).
|
|
356
|
+
if (trimStart(s) !== operands[0].i)
|
|
357
|
+
return null;
|
|
358
|
+
if (trimEnd(s) !== operands[operands.length - 1].j)
|
|
359
|
+
return null;
|
|
360
|
+
let sep = null;
|
|
361
|
+
for (let k = 0; k + 1 < operands.length; k++) {
|
|
362
|
+
const gap = latin1(s.subarray(operands[k].j, operands[k + 1].i));
|
|
363
|
+
if (gap.length === 0)
|
|
364
|
+
return null; // operands must be separated
|
|
365
|
+
if (sep === null)
|
|
366
|
+
sep = gap;
|
|
367
|
+
else if (gap !== sep)
|
|
368
|
+
return null; // inconsistent → not one sequence
|
|
369
|
+
}
|
|
370
|
+
return operands.map((o) => this.recogniseValue(s.subarray(o.i, o.j)));
|
|
401
371
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
372
|
+
/** Apply an op to operand values, with the given pre-resolved resonance.
|
|
373
|
+
* Returns the result value, or null if the op is unknown or the computation
|
|
374
|
+
* throws (e.g. a symbol fed to arithmetic) — the caller treats null as "this
|
|
375
|
+
* rule does not fire", never as a wrong answer. */
|
|
376
|
+
apply(name, operands, resonance = NO_RESONANCE) {
|
|
377
|
+
if (!this.registry.has(name))
|
|
378
|
+
return null;
|
|
379
|
+
try {
|
|
380
|
+
const ctx = this.registry.context(resonance, this.rt, this.makeEvalExpr());
|
|
381
|
+
return ctx.apply(name, operands);
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
// The "rule does not fire" contract — but never invisibly: the counter
|
|
385
|
+
// and lastApplyError let a debugger distinguish a routine decline from
|
|
386
|
+
// a kernel bug that would otherwise read as "computation unknown".
|
|
387
|
+
this.applyCaught++;
|
|
388
|
+
this.lastApplyError = err;
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
411
391
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
this.
|
|
417
|
-
);
|
|
418
|
-
return ctx.apply(name, operands);
|
|
419
|
-
} catch (err) {
|
|
420
|
-
// The "rule does not fire" contract — but never invisibly: the counter
|
|
421
|
-
// and lastApplyError let a debugger distinguish a routine decline from
|
|
422
|
-
// a kernel bug that would otherwise read as "computation unknown".
|
|
423
|
-
this.applyCaught++;
|
|
424
|
-
this.lastApplyError = err;
|
|
425
|
-
return null;
|
|
392
|
+
/** The expression evaluator handed to op contexts, bound to the cached {@link
|
|
393
|
+
* grammar}: the numerical layer's integrand is evaluated by a recursive
|
|
394
|
+
* application of the same derived-from-nand arithmetic. */
|
|
395
|
+
makeEvalExpr() {
|
|
396
|
+
return (bytes, variable, at) => this.grammar.eval(this.decoder.decode(bytes), variable, at);
|
|
426
397
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
/** Decode operand byte spans, apply `name`, and encode the result — the pure
|
|
436
|
-
* computation the graph rule materialises into an output span. Returns null
|
|
437
|
-
* on any failure (unknown op, undecodable operand, thrown computation), so a
|
|
438
|
-
* rule that cannot compute simply does not fire. The result bytes are
|
|
439
|
-
* canonical (see {@link decimalCodec}), so two derivations of the same value
|
|
440
|
-
* agree byte-for-byte — required for the search's chart memoization. */
|
|
441
|
-
applyBytes(name, operandBytes, resonance = NO_RESONANCE) {
|
|
442
|
-
return this.applyBytesTyped(name, operandBytes, () => false, resonance);
|
|
443
|
-
}
|
|
444
|
-
/** Apply `name` to operand VALUES (a scalar, or an `nd` recognised by {@link
|
|
445
|
-
* recogniseValue}), and encode the result canonically. This is the entry
|
|
446
|
-
* point for computation over STRUCTURE: the scalar kernel and the nd kernel
|
|
447
|
-
* both consume Values, so once a span is recognised the computation is the
|
|
448
|
-
* same whether the operand came in as a number or a list. Returns null on any
|
|
449
|
-
* failure (unknown op, thrown computation), the same "this rule does not fire"
|
|
450
|
-
* contract as {@link applyBytes}. */
|
|
451
|
-
applyValues(name, operands, resonance = NO_RESONANCE) {
|
|
452
|
-
const result = this.apply(name, operands, resonance);
|
|
453
|
-
if (result === null) {
|
|
454
|
-
return null;
|
|
398
|
+
/** Decode operand byte spans, apply `name`, and encode the result — the pure
|
|
399
|
+
* computation the graph rule materialises into an output span. Returns null
|
|
400
|
+
* on any failure (unknown op, undecodable operand, thrown computation), so a
|
|
401
|
+
* rule that cannot compute simply does not fire. The result bytes are
|
|
402
|
+
* canonical (see {@link decimalCodec}), so two derivations of the same value
|
|
403
|
+
* agree byte-for-byte — required for the search's chart memoization. */
|
|
404
|
+
applyBytes(name, operandBytes, resonance = NO_RESONANCE) {
|
|
405
|
+
return this.applyBytesTyped(name, operandBytes, () => false, resonance);
|
|
455
406
|
}
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
: this.codec.decode(b);
|
|
469
|
-
if (v === null) {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
operands.push(v);
|
|
407
|
+
/** Apply `name` to operand VALUES (a scalar, or an `nd` recognised by {@link
|
|
408
|
+
* recogniseValue}), and encode the result canonically. This is the entry
|
|
409
|
+
* point for computation over STRUCTURE: the scalar kernel and the nd kernel
|
|
410
|
+
* both consume Values, so once a span is recognised the computation is the
|
|
411
|
+
* same whether the operand came in as a number or a list. Returns null on any
|
|
412
|
+
* failure (unknown op, thrown computation), the same "this rule does not fire"
|
|
413
|
+
* contract as {@link applyBytes}. */
|
|
414
|
+
applyValues(name, operands, resonance = NO_RESONANCE) {
|
|
415
|
+
const result = this.apply(name, operands, resonance);
|
|
416
|
+
if (result === null)
|
|
417
|
+
return null;
|
|
418
|
+
return this.codec.encode(result);
|
|
473
419
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
420
|
+
/** Like {@link applyBytes}, but `asSymbol(idx)` marks operand positions that
|
|
421
|
+
* must be kept as an opaque SYMBOL (not parsed as a number) — the convention
|
|
422
|
+
* the numerical layer needs, where operand 0 is an EXPRESSION (a function's
|
|
423
|
+
* bytes) the kernel samples via the expression evaluator, not a numeral. */
|
|
424
|
+
applyBytesTyped(name, operandBytes, asSymbol, resonance = NO_RESONANCE) {
|
|
425
|
+
const operands = [];
|
|
426
|
+
for (let i = 0; i < operandBytes.length; i++) {
|
|
427
|
+
const b = operandBytes[i];
|
|
428
|
+
const v = asSymbol(i)
|
|
429
|
+
? { domain: "symbol", bytes: b }
|
|
430
|
+
: this.codec.decode(b);
|
|
431
|
+
if (v === null)
|
|
432
|
+
return null;
|
|
433
|
+
operands.push(v);
|
|
434
|
+
}
|
|
435
|
+
const result = this.apply(name, operands, resonance);
|
|
436
|
+
if (result === null)
|
|
437
|
+
return null;
|
|
438
|
+
return this.codec.encode(result);
|
|
477
439
|
}
|
|
478
|
-
return this.codec.encode(result);
|
|
479
|
-
}
|
|
480
440
|
}
|