@hviana/sema 0.1.3 → 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.
Files changed (123) hide show
  1. package/dist/example/demo.js +25 -25
  2. package/dist/example/train_base.d.ts +21 -19
  3. package/dist/example/train_base.js +1952 -1684
  4. package/dist/src/alphabet.d.ts +3 -3
  5. package/dist/src/alphabet.js +27 -24
  6. package/dist/src/alu/src/alu.d.ts +194 -169
  7. package/dist/src/alu/src/alu.js +414 -374
  8. package/dist/src/alu/src/expr.d.ts +43 -36
  9. package/dist/src/alu/src/expr.js +278 -252
  10. package/dist/src/alu/src/index.d.ts +98 -9
  11. package/dist/src/alu/src/index.js +63 -9
  12. package/dist/src/alu/src/kernel-arith.d.ts +5 -1
  13. package/dist/src/alu/src/kernel-arith.js +285 -195
  14. package/dist/src/alu/src/kernel-bits.d.ts +5 -1
  15. package/dist/src/alu/src/kernel-bits.js +115 -81
  16. package/dist/src/alu/src/kernel-logic.js +63 -33
  17. package/dist/src/alu/src/kernel-nd.js +195 -155
  18. package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
  19. package/dist/src/alu/src/kernel-numeric.js +331 -259
  20. package/dist/src/alu/src/operation.d.ts +132 -112
  21. package/dist/src/alu/src/operation.js +163 -149
  22. package/dist/src/alu/src/parser.d.ts +175 -168
  23. package/dist/src/alu/src/parser.js +421 -382
  24. package/dist/src/alu/src/resonance.d.ts +25 -16
  25. package/dist/src/alu/src/resonance.js +56 -49
  26. package/dist/src/alu/src/text.d.ts +7 -3
  27. package/dist/src/alu/src/text.js +37 -31
  28. package/dist/src/alu/src/value.d.ts +14 -14
  29. package/dist/src/alu/src/value.js +160 -150
  30. package/dist/src/alu/test/alu.test.js +656 -543
  31. package/dist/src/bytes.d.ts +5 -1
  32. package/dist/src/bytes.js +40 -33
  33. package/dist/src/config.d.ts +102 -102
  34. package/dist/src/config.js +83 -83
  35. package/dist/src/derive/src/deduction.d.ts +62 -60
  36. package/dist/src/derive/src/deduction.js +109 -103
  37. package/dist/src/derive/src/index.d.ts +7 -1
  38. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  39. package/dist/src/derive/src/priority-queue.js +61 -57
  40. package/dist/src/derive/src/rewrite.d.ts +18 -15
  41. package/dist/src/derive/src/rewrite.js +67 -59
  42. package/dist/src/derive/src/trie.d.ts +56 -56
  43. package/dist/src/derive/src/trie.js +179 -175
  44. package/dist/src/derive/test/derive.test.js +100 -96
  45. package/dist/src/extension.d.ts +16 -13
  46. package/dist/src/geometry.d.ts +42 -17
  47. package/dist/src/geometry.js +266 -235
  48. package/dist/src/index.d.ts +17 -2
  49. package/dist/src/index.js +7 -1
  50. package/dist/src/ingest-cache.d.ts +30 -25
  51. package/dist/src/ingest-cache.js +127 -108
  52. package/dist/src/mind/articulation.d.ts +5 -1
  53. package/dist/src/mind/articulation.js +112 -77
  54. package/dist/src/mind/attention.d.ts +108 -40
  55. package/dist/src/mind/attention.js +871 -749
  56. package/dist/src/mind/canonical.d.ts +19 -4
  57. package/dist/src/mind/canonical.js +31 -27
  58. package/dist/src/mind/graph-search.d.ts +225 -201
  59. package/dist/src/mind/graph-search.js +821 -742
  60. package/dist/src/mind/index.d.ts +10 -2
  61. package/dist/src/mind/junction.d.ts +58 -31
  62. package/dist/src/mind/junction.js +237 -172
  63. package/dist/src/mind/learning.d.ts +52 -16
  64. package/dist/src/mind/learning.js +165 -143
  65. package/dist/src/mind/match.d.ts +69 -20
  66. package/dist/src/mind/match.js +318 -259
  67. package/dist/src/mind/mechanisms/alu.js +16 -16
  68. package/dist/src/mind/mechanisms/cast.d.ts +13 -9
  69. package/dist/src/mind/mechanisms/cast.js +456 -363
  70. package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
  71. package/dist/src/mind/mechanisms/confluence.js +183 -152
  72. package/dist/src/mind/mechanisms/cover.d.ts +8 -2
  73. package/dist/src/mind/mechanisms/cover.js +210 -148
  74. package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
  75. package/dist/src/mind/mechanisms/extraction.js +288 -234
  76. package/dist/src/mind/mechanisms/recall.d.ts +10 -6
  77. package/dist/src/mind/mechanisms/recall.js +185 -126
  78. package/dist/src/mind/mind.d.ts +154 -129
  79. package/dist/src/mind/mind.js +288 -258
  80. package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
  81. package/dist/src/mind/pipeline-mechanism.js +172 -161
  82. package/dist/src/mind/pipeline.d.ts +14 -4
  83. package/dist/src/mind/pipeline.js +189 -125
  84. package/dist/src/mind/primitives.d.ts +32 -8
  85. package/dist/src/mind/primitives.js +117 -99
  86. package/dist/src/mind/rationale.d.ts +98 -86
  87. package/dist/src/mind/rationale.js +121 -113
  88. package/dist/src/mind/reasoning.d.ts +13 -2
  89. package/dist/src/mind/reasoning.js +166 -129
  90. package/dist/src/mind/recognition.d.ts +4 -1
  91. package/dist/src/mind/recognition.js +208 -183
  92. package/dist/src/mind/resonance.d.ts +22 -5
  93. package/dist/src/mind/resonance.js +0 -0
  94. package/dist/src/mind/trace.d.ts +25 -6
  95. package/dist/src/mind/trace.js +58 -50
  96. package/dist/src/mind/traverse.d.ts +58 -16
  97. package/dist/src/mind/traverse.js +357 -304
  98. package/dist/src/mind/types.d.ts +127 -120
  99. package/dist/src/mind/types.js +69 -60
  100. package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
  101. package/dist/src/rabitq-hnsw/src/database.js +359 -342
  102. package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
  103. package/dist/src/rabitq-hnsw/src/heap.js +80 -75
  104. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
  105. package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
  106. package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
  107. package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
  108. package/dist/src/rabitq-hnsw/src/prng.js +27 -25
  109. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
  110. package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
  111. package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
  112. package/dist/src/rabitq-hnsw/src/store.js +843 -752
  113. package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
  114. package/dist/src/sema.d.ts +35 -0
  115. package/dist/src/sema.js +77 -0
  116. package/dist/src/store-sqlite.d.ts +166 -144
  117. package/dist/src/store-sqlite.js +660 -578
  118. package/dist/src/store.d.ts +630 -593
  119. package/dist/src/store.js +1581 -1440
  120. package/dist/src/vec.d.ts +9 -5
  121. package/dist/src/vec.js +73 -61
  122. package/package.json +2 -2
  123. package/src/sema.ts +102 -0
@@ -26,7 +26,8 @@ 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) => (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.charCodeAt(0) > 127;
29
+ const isLetter = (c) =>
30
+ (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c.charCodeAt(0) > 127;
30
31
  const isSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\r";
31
32
  /** Tokenize an expression string: numerals (the irreducible floor), operator
32
33
  * tokens (matched longest-first against `operators` — the registry's own
@@ -35,107 +36,114 @@ const isSpace = (c) => c === " " || c === "\t" || c === "\n" || c === "\r";
35
36
  * extraction). Returns null when a character fits none of these — the span is
36
37
  * not a clean expression. */
37
38
  export function tokenize(s, operators) {
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;
45
- }
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;
77
- }
78
- if (c === "(") {
79
- toks.push({ kind: "lparen", text: c });
80
- i++;
81
- continue;
82
- }
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;
39
+ const toks = [];
40
+ let i = 0;
41
+ while (i < s.length) {
42
+ const c = s[i];
43
+ if (isSpace(c)) {
44
+ i++;
45
+ continue;
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;
96
58
  }
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;
59
+ }
60
+ // optional exponent
61
+ if (j < s.length && (s[j] === "e" || s[j] === "E")) {
62
+ let k = j + 1;
63
+ if (k < s.length && (s[k] === "+" || s[k] === "-")) {
64
+ k++;
104
65
  }
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;
66
+ if (k < s.length && isDigit(s[k])) {
67
+ while (k < s.length && isDigit(s[k])) {
68
+ k++;
69
+ }
70
+ j = k;
111
71
  }
112
- return null; // an unexpected character → not a clean expression
72
+ }
73
+ const text = s.slice(i, j);
74
+ const num = Number(text);
75
+ if (!Number.isFinite(num)) {
76
+ return null;
77
+ }
78
+ toks.push({ kind: "num", text, value: num });
79
+ i = j;
80
+ continue;
81
+ }
82
+ if (c === "(") {
83
+ toks.push({ kind: "lparen", text: c });
84
+ i++;
85
+ continue;
113
86
  }
114
- return toks;
87
+ if (c === ")") {
88
+ toks.push({ kind: "rparen", text: c });
89
+ i++;
90
+ continue;
91
+ }
92
+ // A registered symbolic operator form, longest first — claimed before the
93
+ // identifier path so an operator GLYPH (·, ×, ≤, …) is an operator, not a
94
+ // name.
95
+ const op = operators?.find((f) => s.startsWith(f, i));
96
+ if (op !== undefined) {
97
+ toks.push({ kind: "op", text: op });
98
+ i += op.length;
99
+ continue;
100
+ }
101
+ if (isLetter(c)) {
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;
116
+ }
117
+ return null; // an unexpected character → not a clean expression
118
+ }
119
+ return toks;
115
120
  }
116
121
  /** The identifiers in `s` that are NOT known unary functions and NOT registered
117
122
  * constants — the free-variable candidates. Used to auto-detect the variable
118
123
  * when the caller did not name one. */
119
124
  export function freeVariables(s, isUnaryFn, isConstant = () => false) {
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
- }
125
+ const toks = tokenize(s);
126
+ if (!toks) {
127
+ return [];
128
+ }
129
+ const out = [];
130
+ const seen = new Set();
131
+ for (let k = 0; k < toks.length; k++) {
132
+ const t = toks[k];
133
+ if (t.kind !== "name") {
134
+ continue;
137
135
  }
138
- return out;
136
+ // A name immediately followed by "(" is a function call, not a variable.
137
+ const isCall = k + 1 < toks.length && toks[k + 1].kind === "lparen";
138
+ if (isCall || isUnaryFn(t.text) || isConstant(t.text)) {
139
+ continue;
140
+ }
141
+ if (!seen.has(t.text)) {
142
+ seen.add(t.text);
143
+ out.push(t.text);
144
+ }
145
+ }
146
+ return out;
139
147
  }
140
148
  /** The expression grammar, read off an operation registry once and reused for
141
149
  * every evaluation. It resolves each token through the registry's own form
@@ -143,176 +151,194 @@ export function freeVariables(s, isUnaryFn, isConstant = () => false) {
143
151
  * arity-1 claimant of a shared symbol, functions as arity-1 ops, constants as
144
152
  * arity-0 ops — so the grammar and the kernel can never drift apart. */
145
153
  export class ExprGrammar {
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;
178
- }
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;
154
+ registry;
155
+ applyScalar;
156
+ /** Symbolic operator tokens, longest first (so "<=" wins over "<"). */
157
+ operatorTokens;
158
+ /** token → the infix op it names and its declared binding. */
159
+ infixTable = new Map();
160
+ /** token → the arity-1 (prefix) op it names. */
161
+ prefixTable = new Map();
162
+ /** The tightest infix tier — a prefix operator binds its operand up through
163
+ * it, so "-x^2" reads -(x^2) without a hardcoded rule. */
164
+ maxPrecedence;
165
+ constructor(registry, applyScalar) {
166
+ this.registry = registry;
167
+ this.applyScalar = applyScalar;
168
+ const tokens = new Set();
169
+ let maxPrec = 0;
170
+ for (const { form, name } of registry.formEntries()) {
171
+ const op = registry.get(name);
172
+ const symbolic = form.length > 0 && !/[A-Za-z0-9]/.test(form);
173
+ if (!symbolic) {
174
+ continue;
175
+ }
176
+ tokens.add(form);
177
+ if (op.infix && !this.infixTable.has(form)) {
178
+ this.infixTable.set(form, { name, syntax: op.infix });
179
+ maxPrec = Math.max(maxPrec, op.infix.precedence);
180
+ }
181
+ if (op.arity === 1 && !this.prefixTable.has(form)) {
182
+ this.prefixTable.set(form, name);
183
+ }
188
184
  }
189
- /** Whether an identifier names a unary function. */
190
- isFunction(name) {
191
- return this.resolveName(name, 1) !== null;
185
+ this.operatorTokens = [...tokens].sort((a, b) => b.length - a.length);
186
+ this.maxPrecedence = maxPrec;
187
+ }
188
+ /** Resolve an identifier to a registered op of the given arity: a surface
189
+ * form claimant of that arity first, else the canonical name itself. */
190
+ resolveName(name, arity) {
191
+ for (const n of this.registry.lookupForm(name)) {
192
+ if (this.registry.get(n).arity === arity) {
193
+ return n;
194
+ }
192
195
  }
193
- /** Whether an identifier names a nullary constant (an arity-0 op). */
194
- isConstant(name) {
195
- return this.resolveName(name, 0) !== null;
196
+ const own = this.registry.get(name);
197
+ return own && own.arity === arity ? name : null;
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;
196
230
  }
197
- /** Tokenize with this grammar's operator vocabulary. */
198
- tokenize(s) {
199
- return tokenize(s, this.operatorTokens);
231
+ let v = variable;
232
+ if (v === "") {
233
+ const fv = this.freeVariables(s);
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.
200
240
  }
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));
204
- }
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.
241
+ let pos = 0;
242
+ const peek = () => toks[pos];
243
+ const next = () => toks[pos++];
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;
224
256
  }
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;
257
+ const infix = grammar.infixTable.get(t.text);
258
+ if (!infix || infix.syntax.precedence < minPrec) {
259
+ break;
251
260
  }
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();
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;
269
+ }
270
+ // An operand: a prefix operator (the arity-1 claimant of its symbol,
271
+ // binding up through the tightest infix tier), or an atom.
272
+ function parseOperand() {
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();
264
278
  }
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
- }
279
+ next();
280
+ return grammar.applyScalar(prefix, [parseExpr(grammar.maxPrecedence)]);
281
+ }
282
+ return parseAtom();
283
+ }
284
+ function parseAtom() {
285
+ const t = peek();
286
+ if (!t) {
287
+ fail();
288
+ }
289
+ if (t.kind === "num") {
290
+ next();
291
+ return t.value;
292
+ }
293
+ if (t.kind === "lparen") {
294
+ next();
295
+ const inner = parseExpr(0);
296
+ if (!peek() || peek().kind !== "rparen") {
297
+ fail();
298
+ }
299
+ next();
300
+ return inner;
301
+ }
302
+ if (t.kind === "name") {
303
+ next();
304
+ // A function call: name "(" expr ")".
305
+ if (peek() && peek().kind === "lparen") {
306
+ const fn = grammar.resolveName(t.text, 1);
307
+ if (fn === null) {
304
308
  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]);
305
317
  }
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;
318
+ // Otherwise an identifier: the free variable, or a nullary constant
319
+ // resolved through the registry like any other operation.
320
+ if (t.text === v) {
321
+ return at;
311
322
  }
312
- catch (err) {
313
- if (err === FAIL)
314
- return null;
315
- return null; // a kernel apply threw (e.g. ÷0 produced ∞) → decline
323
+ const konst = grammar.resolveName(t.text, 0);
324
+ if (konst !== null) {
325
+ return grammar.applyScalar(konst, []);
316
326
  }
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
317
342
  }
343
+ }
318
344
  }