@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.
- 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 +1700 -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 +29 -43
- package/dist/src/geometry.js +324 -268
- 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 +754 -872
- 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 +141 -185
- package/dist/src/mind/mind.d.ts +194 -155
- package/dist/src/mind/mind.js +392 -289
- 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 +20 -33
- package/dist/src/mind/primitives.js +124 -120
- 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 +198 -210
- 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 +29 -58
- package/dist/src/mind/traverse.js +344 -356
- package/dist/src/mind/types.d.ts +122 -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 +171 -166
- package/dist/src/store-sqlite.js +768 -662
- package/dist/src/store.d.ts +597 -630
- package/dist/src/store.js +1434 -1581
- package/dist/src/vec.d.ts +5 -9
- package/dist/src/vec.js +61 -73
- package/example/train_base.ts +33 -15
- package/index.html +65 -0
- 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/geometry.ts +93 -1
- package/src/index.ts +6 -11
- package/src/mind/attention.ts +11 -6
- package/src/mind/mechanisms/recall.ts +29 -13
- package/src/mind/mind.ts +250 -19
- package/src/mind/primitives.ts +43 -6
- package/src/mind/recognition.ts +26 -8
- package/src/mind/traverse.ts +47 -0
- package/src/mind/types.ts +20 -21
- 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 +204 -9
- package/src/store.ts +27 -34
- package/test/08-storage.test.mjs +3 -3
- package/test/13-conversation.test.mjs +190 -20
- 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/expr.js
CHANGED
|
@@ -26,8 +26,7 @@ const isDigit = (c) => c >= "0" && c <= "9";
|
|
|
26
26
|
// so a variable or function name may be written in any script (multimodal /
|
|
27
27
|
// multilingual), not just A–Z. Operator glyphs (registered symbolic forms)
|
|
28
28
|
// are claimed BEFORE identifiers, so "·" and "≤" never read as names.
|
|
29
|
-
const isLetter = (c) =>
|
|
30
|
-
(c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.charCodeAt(0) > 127;
|
|
29
|
+
const isLetter = (c) => (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.charCodeAt(0) > 127;
|
|
31
30
|
const isSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\r";
|
|
32
31
|
/** Tokenize an expression string: numerals (the irreducible floor), operator
|
|
33
32
|
* tokens (matched longest-first against `operators` — the registry's own
|
|
@@ -36,114 +35,107 @@ const isSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\r";
|
|
|
36
35
|
* extraction). Returns null when a character fits none of these — the span is
|
|
37
36
|
* not a clean expression. */
|
|
38
37
|
export function tokenize(s, operators) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
if (isDigit(c) || (c === "." && i + 1 < s.length && isDigit(s[i + 1]))) {
|
|
48
|
-
let j = i;
|
|
49
|
-
let seenDot = false;
|
|
50
|
-
while (j < s.length) {
|
|
51
|
-
if (isDigit(s[j])) {
|
|
52
|
-
j++;
|
|
53
|
-
} else if (s[j] === "." && !seenDot) {
|
|
54
|
-
seenDot = true;
|
|
55
|
-
j++;
|
|
56
|
-
} else {
|
|
57
|
-
break;
|
|
38
|
+
const toks = [];
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (i < s.length) {
|
|
41
|
+
const c = s[i];
|
|
42
|
+
if (isSpace(c)) {
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
58
45
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
if (isDigit(c) || (c === "." && i + 1 < s.length && isDigit(s[i + 1]))) {
|
|
47
|
+
let j = i;
|
|
48
|
+
let seenDot = false;
|
|
49
|
+
while (j < s.length) {
|
|
50
|
+
if (isDigit(s[j]))
|
|
51
|
+
j++;
|
|
52
|
+
else if (s[j] === "." && !seenDot) {
|
|
53
|
+
seenDot = true;
|
|
54
|
+
j++;
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
// optional exponent
|
|
60
|
+
if (j < s.length && (s[j] === "e" || s[j] === "E")) {
|
|
61
|
+
let k = j + 1;
|
|
62
|
+
if (k < s.length && (s[k] === "+" || s[k] === "-"))
|
|
63
|
+
k++;
|
|
64
|
+
if (k < s.length && isDigit(s[k])) {
|
|
65
|
+
while (k < s.length && isDigit(s[k]))
|
|
66
|
+
k++;
|
|
67
|
+
j = k;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const text = s.slice(i, j);
|
|
71
|
+
const num = Number(text);
|
|
72
|
+
if (!Number.isFinite(num))
|
|
73
|
+
return null;
|
|
74
|
+
toks.push({ kind: "num", text, value: num });
|
|
75
|
+
i = j;
|
|
76
|
+
continue;
|
|
65
77
|
}
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
j = k;
|
|
78
|
+
if (c === "(") {
|
|
79
|
+
toks.push({ kind: "lparen", text: c });
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
71
82
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let j = i;
|
|
103
|
-
while (j < s.length && (isLetter(s[j]) || isDigit(s[j]))) {
|
|
104
|
-
j++;
|
|
105
|
-
}
|
|
106
|
-
toks.push({ kind: "name", text: s.slice(i, j) });
|
|
107
|
-
i = j;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
// Fallback for a symbolic character outside any vocabulary: its own token,
|
|
111
|
-
// so name extraction (freeVariables) still splits "a*b" without a grammar.
|
|
112
|
-
if (operators === undefined) {
|
|
113
|
-
toks.push({ kind: "op", text: c });
|
|
114
|
-
i++;
|
|
115
|
-
continue;
|
|
83
|
+
if (c === ")") {
|
|
84
|
+
toks.push({ kind: "rparen", text: c });
|
|
85
|
+
i++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// A registered symbolic operator form, longest first — claimed before the
|
|
89
|
+
// identifier path so an operator GLYPH (·, ×, ≤, …) is an operator, not a
|
|
90
|
+
// name.
|
|
91
|
+
const op = operators?.find((f) => s.startsWith(f, i));
|
|
92
|
+
if (op !== undefined) {
|
|
93
|
+
toks.push({ kind: "op", text: op });
|
|
94
|
+
i += op.length;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (isLetter(c)) {
|
|
98
|
+
let j = i;
|
|
99
|
+
while (j < s.length && (isLetter(s[j]) || isDigit(s[j])))
|
|
100
|
+
j++;
|
|
101
|
+
toks.push({ kind: "name", text: s.slice(i, j) });
|
|
102
|
+
i = j;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Fallback for a symbolic character outside any vocabulary: its own token,
|
|
106
|
+
// so name extraction (freeVariables) still splits "a*b" without a grammar.
|
|
107
|
+
if (operators === undefined) {
|
|
108
|
+
toks.push({ kind: "op", text: c });
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
return null; // an unexpected character → not a clean expression
|
|
116
113
|
}
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
return toks;
|
|
114
|
+
return toks;
|
|
120
115
|
}
|
|
121
116
|
/** The identifiers in `s` that are NOT known unary functions and NOT registered
|
|
122
117
|
* constants — the free-variable candidates. Used to auto-detect the variable
|
|
123
118
|
* when the caller did not name one. */
|
|
124
119
|
export function freeVariables(s, isUnaryFn, isConstant = () => false) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
seen.add(t.text);
|
|
143
|
-
out.push(t.text);
|
|
120
|
+
const toks = tokenize(s);
|
|
121
|
+
if (!toks)
|
|
122
|
+
return [];
|
|
123
|
+
const out = [];
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (let k = 0; k < toks.length; k++) {
|
|
126
|
+
const t = toks[k];
|
|
127
|
+
if (t.kind !== "name")
|
|
128
|
+
continue;
|
|
129
|
+
// A name immediately followed by "(" is a function call, not a variable.
|
|
130
|
+
const isCall = k + 1 < toks.length && toks[k + 1].kind === "lparen";
|
|
131
|
+
if (isCall || isUnaryFn(t.text) || isConstant(t.text))
|
|
132
|
+
continue;
|
|
133
|
+
if (!seen.has(t.text)) {
|
|
134
|
+
seen.add(t.text);
|
|
135
|
+
out.push(t.text);
|
|
136
|
+
}
|
|
144
137
|
}
|
|
145
|
-
|
|
146
|
-
return out;
|
|
138
|
+
return out;
|
|
147
139
|
}
|
|
148
140
|
/** The expression grammar, read off an operation registry once and reused for
|
|
149
141
|
* every evaluation. It resolves each token through the registry's own form
|
|
@@ -151,194 +143,176 @@ export function freeVariables(s, isUnaryFn, isConstant = () => false) {
|
|
|
151
143
|
* arity-1 claimant of a shared symbol, functions as arity-1 ops, constants as
|
|
152
144
|
* arity-0 ops — so the grammar and the kernel can never drift apart. */
|
|
153
145
|
export class ExprGrammar {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
146
|
+
registry;
|
|
147
|
+
applyScalar;
|
|
148
|
+
/** Symbolic operator tokens, longest first (so "<=" wins over "<"). */
|
|
149
|
+
operatorTokens;
|
|
150
|
+
/** token → the infix op it names and its declared binding. */
|
|
151
|
+
infixTable = new Map();
|
|
152
|
+
/** token → the arity-1 (prefix) op it names. */
|
|
153
|
+
prefixTable = new Map();
|
|
154
|
+
/** The tightest infix tier — a prefix operator binds its operand up through
|
|
155
|
+
* it, so "-x^2" reads -(x^2) without a hardcoded rule. */
|
|
156
|
+
maxPrecedence;
|
|
157
|
+
constructor(registry, applyScalar) {
|
|
158
|
+
this.registry = registry;
|
|
159
|
+
this.applyScalar = applyScalar;
|
|
160
|
+
const tokens = new Set();
|
|
161
|
+
let maxPrec = 0;
|
|
162
|
+
for (const { form, name } of registry.formEntries()) {
|
|
163
|
+
const op = registry.get(name);
|
|
164
|
+
const symbolic = form.length > 0 && !/[A-Za-z0-9]/.test(form);
|
|
165
|
+
if (!symbolic)
|
|
166
|
+
continue;
|
|
167
|
+
tokens.add(form);
|
|
168
|
+
if (op.infix && !this.infixTable.has(form)) {
|
|
169
|
+
this.infixTable.set(form, { name, syntax: op.infix });
|
|
170
|
+
maxPrec = Math.max(maxPrec, op.infix.precedence);
|
|
171
|
+
}
|
|
172
|
+
if (op.arity === 1 && !this.prefixTable.has(form)) {
|
|
173
|
+
this.prefixTable.set(form, name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.operatorTokens = [...tokens].sort((a, b) => b.length - a.length);
|
|
177
|
+
this.maxPrecedence = maxPrec;
|
|
184
178
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return
|
|
194
|
-
}
|
|
179
|
+
/** Resolve an identifier to a registered op of the given arity: a surface
|
|
180
|
+
* form claimant of that arity first, else the canonical name itself. */
|
|
181
|
+
resolveName(name, arity) {
|
|
182
|
+
for (const n of this.registry.lookupForm(name)) {
|
|
183
|
+
if (this.registry.get(n).arity === arity)
|
|
184
|
+
return n;
|
|
185
|
+
}
|
|
186
|
+
const own = this.registry.get(name);
|
|
187
|
+
return own && own.arity === arity ? name : null;
|
|
195
188
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
/** Whether an identifier names a unary function. */
|
|
200
|
-
isFunction(name) {
|
|
201
|
-
return this.resolveName(name, 1) !== null;
|
|
202
|
-
}
|
|
203
|
-
/** Whether an identifier names a nullary constant (an arity-0 op). */
|
|
204
|
-
isConstant(name) {
|
|
205
|
-
return this.resolveName(name, 0) !== null;
|
|
206
|
-
}
|
|
207
|
-
/** Tokenize with this grammar's operator vocabulary. */
|
|
208
|
-
tokenize(s) {
|
|
209
|
-
return tokenize(s, this.operatorTokens);
|
|
210
|
-
}
|
|
211
|
-
/** The free-variable candidates of `s` under this grammar. */
|
|
212
|
-
freeVariables(s) {
|
|
213
|
-
return freeVariables(
|
|
214
|
-
s,
|
|
215
|
-
(n) => this.isFunction(n),
|
|
216
|
-
(n) => this.isConstant(n),
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
/** Evaluate expression `s` with `variable` bound to `at`. Returns a number,
|
|
220
|
-
* or null if `s` is not a well-formed expression. Every binary/function/
|
|
221
|
-
* constant step is applied through the kernel, so the arithmetic is the
|
|
222
|
-
* kernel's own. When `variable` is empty, the single free variable is
|
|
223
|
-
* auto-detected; if there is more than one and none was named, evaluation
|
|
224
|
-
* declines (null) — a genuinely multivariate expression needs an explicit
|
|
225
|
-
* binding. */
|
|
226
|
-
eval(s, variable, at) {
|
|
227
|
-
const toks = this.tokenize(s);
|
|
228
|
-
if (!toks || toks.length === 0) {
|
|
229
|
-
return null;
|
|
189
|
+
/** Whether an identifier names a unary function. */
|
|
190
|
+
isFunction(name) {
|
|
191
|
+
return this.resolveName(name, 1) !== null;
|
|
230
192
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (fv.length === 1) {
|
|
235
|
-
v = fv[0];
|
|
236
|
-
} else if (fv.length > 1) {
|
|
237
|
-
return null; // multivariate, no binding named
|
|
238
|
-
}
|
|
239
|
-
// fv.length === 0: a constant expression is fine.
|
|
193
|
+
/** Whether an identifier names a nullary constant (an arity-0 op). */
|
|
194
|
+
isConstant(name) {
|
|
195
|
+
return this.resolveName(name, 0) !== null;
|
|
240
196
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const FAIL = Symbol("expr-fail");
|
|
245
|
-
const fail = () => {
|
|
246
|
-
throw FAIL;
|
|
247
|
-
};
|
|
248
|
-
const grammar = this;
|
|
249
|
-
// Precedence climbing over the registry-declared infix tiers.
|
|
250
|
-
function parseExpr(minPrec) {
|
|
251
|
-
let lhs = parseOperand();
|
|
252
|
-
for (;;) {
|
|
253
|
-
const t = peek();
|
|
254
|
-
if (!t || t.kind !== "op") {
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
const infix = grammar.infixTable.get(t.text);
|
|
258
|
-
if (!infix || infix.syntax.precedence < minPrec) {
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
next();
|
|
262
|
-
const nextMin = infix.syntax.rightAssoc
|
|
263
|
-
? infix.syntax.precedence
|
|
264
|
-
: infix.syntax.precedence + 1;
|
|
265
|
-
const rhs = parseExpr(nextMin);
|
|
266
|
-
lhs = grammar.applyScalar(infix.name, [lhs, rhs]);
|
|
267
|
-
}
|
|
268
|
-
return lhs;
|
|
197
|
+
/** Tokenize with this grammar's operator vocabulary. */
|
|
198
|
+
tokenize(s) {
|
|
199
|
+
return tokenize(s, this.operatorTokens);
|
|
269
200
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const t = peek();
|
|
274
|
-
if (t && t.kind === "op") {
|
|
275
|
-
const prefix = grammar.prefixTable.get(t.text);
|
|
276
|
-
if (prefix === undefined) {
|
|
277
|
-
return fail();
|
|
278
|
-
}
|
|
279
|
-
next();
|
|
280
|
-
return grammar.applyScalar(prefix, [parseExpr(grammar.maxPrecedence)]);
|
|
281
|
-
}
|
|
282
|
-
return parseAtom();
|
|
201
|
+
/** The free-variable candidates of `s` under this grammar. */
|
|
202
|
+
freeVariables(s) {
|
|
203
|
+
return freeVariables(s, (n) => this.isFunction(n), (n) => this.isConstant(n));
|
|
283
204
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (
|
|
297
|
-
|
|
205
|
+
/** Evaluate expression `s` with `variable` bound to `at`. Returns a number,
|
|
206
|
+
* or null if `s` is not a well-formed expression. Every binary/function/
|
|
207
|
+
* constant step is applied through the kernel, so the arithmetic is the
|
|
208
|
+
* kernel's own. When `variable` is empty, the single free variable is
|
|
209
|
+
* auto-detected; if there is more than one and none was named, evaluation
|
|
210
|
+
* declines (null) — a genuinely multivariate expression needs an explicit
|
|
211
|
+
* binding. */
|
|
212
|
+
eval(s, variable, at) {
|
|
213
|
+
const toks = this.tokenize(s);
|
|
214
|
+
if (!toks || toks.length === 0)
|
|
215
|
+
return null;
|
|
216
|
+
let v = variable;
|
|
217
|
+
if (v === "") {
|
|
218
|
+
const fv = this.freeVariables(s);
|
|
219
|
+
if (fv.length === 1)
|
|
220
|
+
v = fv[0];
|
|
221
|
+
else if (fv.length > 1)
|
|
222
|
+
return null; // multivariate, no binding named
|
|
223
|
+
// fv.length === 0: a constant expression is fine.
|
|
224
|
+
}
|
|
225
|
+
let pos = 0;
|
|
226
|
+
const peek = () => toks[pos];
|
|
227
|
+
const next = () => toks[pos++];
|
|
228
|
+
const FAIL = Symbol("expr-fail");
|
|
229
|
+
const fail = () => {
|
|
230
|
+
throw FAIL;
|
|
231
|
+
};
|
|
232
|
+
const grammar = this;
|
|
233
|
+
// Precedence climbing over the registry-declared infix tiers.
|
|
234
|
+
function parseExpr(minPrec) {
|
|
235
|
+
let lhs = parseOperand();
|
|
236
|
+
for (;;) {
|
|
237
|
+
const t = peek();
|
|
238
|
+
if (!t || t.kind !== "op")
|
|
239
|
+
break;
|
|
240
|
+
const infix = grammar.infixTable.get(t.text);
|
|
241
|
+
if (!infix || infix.syntax.precedence < minPrec)
|
|
242
|
+
break;
|
|
243
|
+
next();
|
|
244
|
+
const nextMin = infix.syntax.rightAssoc
|
|
245
|
+
? infix.syntax.precedence
|
|
246
|
+
: infix.syntax.precedence + 1;
|
|
247
|
+
const rhs = parseExpr(nextMin);
|
|
248
|
+
lhs = grammar.applyScalar(infix.name, [lhs, rhs]);
|
|
249
|
+
}
|
|
250
|
+
return lhs;
|
|
298
251
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
252
|
+
// An operand: a prefix operator (the arity-1 claimant of its symbol,
|
|
253
|
+
// binding up through the tightest infix tier), or an atom.
|
|
254
|
+
function parseOperand() {
|
|
255
|
+
const t = peek();
|
|
256
|
+
if (t && t.kind === "op") {
|
|
257
|
+
const prefix = grammar.prefixTable.get(t.text);
|
|
258
|
+
if (prefix === undefined)
|
|
259
|
+
return fail();
|
|
260
|
+
next();
|
|
261
|
+
return grammar.applyScalar(prefix, [parseExpr(grammar.maxPrecedence)]);
|
|
262
|
+
}
|
|
263
|
+
return parseAtom();
|
|
264
|
+
}
|
|
265
|
+
function parseAtom() {
|
|
266
|
+
const t = peek();
|
|
267
|
+
if (!t)
|
|
268
|
+
fail();
|
|
269
|
+
if (t.kind === "num") {
|
|
270
|
+
next();
|
|
271
|
+
return t.value;
|
|
272
|
+
}
|
|
273
|
+
if (t.kind === "lparen") {
|
|
274
|
+
next();
|
|
275
|
+
const inner = parseExpr(0);
|
|
276
|
+
if (!peek() || peek().kind !== "rparen")
|
|
277
|
+
fail();
|
|
278
|
+
next();
|
|
279
|
+
return inner;
|
|
280
|
+
}
|
|
281
|
+
if (t.kind === "name") {
|
|
282
|
+
next();
|
|
283
|
+
// A function call: name "(" expr ")".
|
|
284
|
+
if (peek() && peek().kind === "lparen") {
|
|
285
|
+
const fn = grammar.resolveName(t.text, 1);
|
|
286
|
+
if (fn === null)
|
|
287
|
+
return fail();
|
|
288
|
+
next(); // (
|
|
289
|
+
const arg = parseExpr(0);
|
|
290
|
+
if (!peek() || peek().kind !== "rparen")
|
|
291
|
+
fail();
|
|
292
|
+
next(); // )
|
|
293
|
+
return grammar.applyScalar(fn, [arg]);
|
|
294
|
+
}
|
|
295
|
+
// Otherwise an identifier: the free variable, or a nullary constant
|
|
296
|
+
// resolved through the registry like any other operation.
|
|
297
|
+
if (t.text === v)
|
|
298
|
+
return at;
|
|
299
|
+
const konst = grammar.resolveName(t.text, 0);
|
|
300
|
+
if (konst !== null)
|
|
301
|
+
return grammar.applyScalar(konst, []);
|
|
302
|
+
return fail();
|
|
303
|
+
}
|
|
308
304
|
return fail();
|
|
309
|
-
}
|
|
310
|
-
next(); // (
|
|
311
|
-
const arg = parseExpr(0);
|
|
312
|
-
if (!peek() || peek().kind !== "rparen") {
|
|
313
|
-
fail();
|
|
314
|
-
}
|
|
315
|
-
next(); // )
|
|
316
|
-
return grammar.applyScalar(fn, [arg]);
|
|
317
305
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
306
|
+
try {
|
|
307
|
+
const result = parseExpr(0);
|
|
308
|
+
if (pos !== toks.length)
|
|
309
|
+
return null; // trailing tokens → malformed
|
|
310
|
+
return Number.isFinite(result) ? result : null;
|
|
322
311
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
312
|
+
catch (err) {
|
|
313
|
+
if (err === FAIL)
|
|
314
|
+
return null;
|
|
315
|
+
return null; // a kernel apply threw (e.g. ÷0 produced ∞) → decline
|
|
326
316
|
}
|
|
327
|
-
return fail();
|
|
328
|
-
}
|
|
329
|
-
return fail();
|
|
330
|
-
}
|
|
331
|
-
try {
|
|
332
|
-
const result = parseExpr(0);
|
|
333
|
-
if (pos !== toks.length) {
|
|
334
|
-
return null; // trailing tokens → malformed
|
|
335
|
-
}
|
|
336
|
-
return Number.isFinite(result) ? result : null;
|
|
337
|
-
} catch (err) {
|
|
338
|
-
if (err === FAIL) {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
return null; // a kernel apply threw (e.g. ÷0 produced ∞) → decline
|
|
342
317
|
}
|
|
343
|
-
}
|
|
344
318
|
}
|