exprify 1.0.4 → 1.0.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/HISTORY.md +49 -0
- package/README.md +109 -182
- package/SECURITY.md +18 -0
- package/bin/cli.mjs +234 -0
- package/dist/exprify.cjs.cjs +3558 -1220
- package/dist/exprify.cjs.cjs.map +1 -1
- package/dist/exprify.esm.js +3558 -1220
- package/dist/exprify.esm.js.map +1 -1
- package/dist/exprify.js +3560 -1222
- package/dist/exprify.js.map +1 -1
- package/dist/exprify.min.js +2 -2
- package/dist/exprify.min.js.map +1 -1
- package/package.json +44 -17
- package/src/core/context.js +35 -27
- package/src/core/exprify.js +880 -0
- package/src/function/executor.js +29 -20
- package/src/function/internal.js +1150 -153
- package/src/function/registry.js +23 -16
- package/src/index.js +1 -1
- package/src/math/bignumber.js +31 -0
- package/src/math/fraction.js +112 -0
- package/src/math/operations.js +38 -24
- package/src/parser/astBuild.js +276 -214
- package/src/parser/evaluator.js +431 -171
- package/src/parser/tokenizer.js +179 -146
- package/src/utils/decimal.js +264 -0
- package/src/utils/globalUnits.js +43 -35
- package/src/utils/matrix.js +14 -14
- package/src/utils/store.js +69 -47
- package/src/variables/store.js +18 -15
- package/src/core/Exprify.js +0 -369
package/src/parser/evaluator.js
CHANGED
|
@@ -1,55 +1,67 @@
|
|
|
1
|
-
import { unwrapDenseMatrix, wrapDenseMatrix } from
|
|
2
|
-
|
|
1
|
+
import { unwrapDenseMatrix, wrapDenseMatrix, isDenseMatrixWrapper } from '../utils/matrix.js';
|
|
2
|
+
import {
|
|
3
|
+
isFraction,
|
|
4
|
+
fraction as createFrac,
|
|
5
|
+
addFrac,
|
|
6
|
+
subFrac,
|
|
7
|
+
mulFrac,
|
|
8
|
+
divFrac,
|
|
9
|
+
powFrac,
|
|
10
|
+
} from '../math/fraction.js';
|
|
11
|
+
import { isBigNumber, bigNumber as createBN } from '../math/bignumber.js';
|
|
12
|
+
|
|
13
|
+
/** @param {any } node*/
|
|
3
14
|
export function evaluateAST(node, context = {}) {
|
|
4
|
-
|
|
5
15
|
const vars = context.variables;
|
|
6
16
|
const fns = context.functions;
|
|
7
17
|
const units = context.units;
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const isSliceNode = (v) =>
|
|
17
|
-
v && typeof v === "object" && v.type === "SliceExpression";
|
|
18
|
-
|
|
19
|
-
const isMatrix = (v) =>
|
|
19
|
+
const isUnitObj = (/** @type {any} */ v) =>
|
|
20
|
+
v && typeof v === 'object' && 'value' in v && 'unit' in v;
|
|
21
|
+
const isComplex = (/** @type {any} */ v) => v && typeof v === 'object' && 're' in v && 'im' in v;
|
|
22
|
+
const isSliceNode = (/** @type {any} */ v) =>
|
|
23
|
+
v && typeof v === 'object' && v.type === 'SliceExpression';
|
|
24
|
+
const isMatrix = (/** @type {any[]} */ v) =>
|
|
20
25
|
Array.isArray(v) && v.length > 0 && v.every(Array.isArray);
|
|
26
|
+
const isMatrixLike = (/** @type {any} */ v) => isMatrix(v) || isDenseMatrixWrapper(v);
|
|
21
27
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
const normalizeMatrix = (/** @type {any[]} */ value) => {
|
|
29
|
+
value = unwrapDenseMatrix(value);
|
|
30
|
+
if (isMatrix(value)) {
|
|
31
|
+
return value.map((/** @type {any} */ row) => [...row]);
|
|
25
32
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return { ...value };
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return [value];
|
|
29
35
|
}
|
|
30
|
-
|
|
31
|
-
return value;
|
|
36
|
+
throw new Error('Expected matrix-compatible value');
|
|
32
37
|
};
|
|
33
38
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (Array.isArray(value)) return [value];
|
|
38
|
-
throw new Error("Expected matrix-compatible value");
|
|
39
|
+
const nodeError = (/** @type {string} */ msg) => {
|
|
40
|
+
const pos = node && node.pos !== undefined ? ` at position ${node.pos}` : '';
|
|
41
|
+
return new Error(`${msg}${pos}`);
|
|
39
42
|
};
|
|
40
43
|
|
|
41
|
-
const toOneBasedIndex = (value) => {
|
|
42
|
-
if (typeof value !==
|
|
43
|
-
throw new Error(
|
|
44
|
+
const toOneBasedIndex = (/** @type {unknown} */ value) => {
|
|
45
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
46
|
+
throw new Error('Matrix indices must be positive integers');
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
return value - 1;
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
const resolveSelector = (
|
|
52
|
+
const resolveSelector = (
|
|
53
|
+
/** @type {{ start: null | undefined; end: null | undefined; }} */ selector,
|
|
54
|
+
/** @type {number} */ contextLength
|
|
55
|
+
) => {
|
|
50
56
|
if (isSliceNode(selector)) {
|
|
51
|
-
const startValue =
|
|
52
|
-
|
|
57
|
+
const startValue =
|
|
58
|
+
selector.start === null || selector.start === undefined
|
|
59
|
+
? 1
|
|
60
|
+
: evaluateAST(selector.start, context);
|
|
61
|
+
const endValue =
|
|
62
|
+
selector.end === null || selector.end === undefined
|
|
63
|
+
? contextLength
|
|
64
|
+
: evaluateAST(selector.end, context);
|
|
53
65
|
const start = toOneBasedIndex(startValue);
|
|
54
66
|
const end = toOneBasedIndex(endValue);
|
|
55
67
|
|
|
@@ -67,14 +79,14 @@ export function evaluateAST(node, context = {}) {
|
|
|
67
79
|
return [toOneBasedIndex(evaluateAST(selector, context))];
|
|
68
80
|
};
|
|
69
81
|
|
|
70
|
-
const indexMatrix = (matrix, selectors) => {
|
|
82
|
+
const indexMatrix = (/** @type {any} */ matrix, /** @type {string | any[]} */ selectors) => {
|
|
71
83
|
const target = normalizeMatrix(matrix);
|
|
72
84
|
|
|
73
85
|
if (selectors.length === 1) {
|
|
74
86
|
const rowIndexes = resolveSelector(selectors[0], target.length);
|
|
75
|
-
const rows = rowIndexes.map((rowIndex) => {
|
|
87
|
+
const rows = rowIndexes.map((/** @type {number} */ rowIndex) => {
|
|
76
88
|
if (rowIndex >= target.length) {
|
|
77
|
-
throw new Error(
|
|
89
|
+
throw new Error('Row index out of range');
|
|
78
90
|
}
|
|
79
91
|
return [...target[rowIndex]];
|
|
80
92
|
});
|
|
@@ -85,14 +97,14 @@ export function evaluateAST(node, context = {}) {
|
|
|
85
97
|
const rowIndexes = resolveSelector(selectors[0], target.length);
|
|
86
98
|
const colIndexes = resolveSelector(selectors[1], target[0]?.length || 0);
|
|
87
99
|
|
|
88
|
-
const values = rowIndexes.map((rowIndex) => {
|
|
100
|
+
const values = rowIndexes.map((/** @type {number} */ rowIndex) => {
|
|
89
101
|
if (rowIndex >= target.length) {
|
|
90
|
-
throw new Error(
|
|
102
|
+
throw new Error('Row index out of range');
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
return colIndexes.map((colIndex) => {
|
|
105
|
+
return colIndexes.map((/** @type {number} */ colIndex) => {
|
|
94
106
|
if (colIndex >= target[rowIndex].length) {
|
|
95
|
-
throw new Error(
|
|
107
|
+
throw new Error('Column index out of range');
|
|
96
108
|
}
|
|
97
109
|
return target[rowIndex][colIndex];
|
|
98
110
|
});
|
|
@@ -107,15 +119,19 @@ export function evaluateAST(node, context = {}) {
|
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
if (colIndexes.length === 1) {
|
|
110
|
-
return values.map((row) => [row[0]]);
|
|
122
|
+
return values.map((/** @type {any[]} */ row) => [row[0]]);
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
return values;
|
|
114
126
|
};
|
|
115
127
|
|
|
116
|
-
const assignMatrixIndex = (
|
|
128
|
+
const assignMatrixIndex = (
|
|
129
|
+
/** @type {any[]} */ matrix,
|
|
130
|
+
/** @type {string | any[]} */ selectors,
|
|
131
|
+
/** @type {any} */ value
|
|
132
|
+
) => {
|
|
117
133
|
const target = isMatrix(matrix)
|
|
118
|
-
? matrix.map((row) => [...row])
|
|
134
|
+
? matrix.map((/** @type {any} */ row) => [...row])
|
|
119
135
|
: Array.isArray(matrix)
|
|
120
136
|
? [matrix.slice()]
|
|
121
137
|
: [];
|
|
@@ -124,7 +140,7 @@ export function evaluateAST(node, context = {}) {
|
|
|
124
140
|
const colSelector = selectors[1];
|
|
125
141
|
|
|
126
142
|
if (!rowSelector) {
|
|
127
|
-
throw new Error(
|
|
143
|
+
throw new Error('Matrix assignment requires at least one index');
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
const rowContextLength = Math.max(target.length, 1);
|
|
@@ -134,111 +150,205 @@ export function evaluateAST(node, context = {}) {
|
|
|
134
150
|
const rowsValue = isMatrix(value) ? value : normalizeMatrix(value);
|
|
135
151
|
|
|
136
152
|
if (rowsValue.length !== rowIndexes.length) {
|
|
137
|
-
throw new Error(
|
|
153
|
+
throw new Error('Assigned row count does not match slice');
|
|
138
154
|
}
|
|
139
155
|
|
|
140
|
-
rowIndexes.forEach(
|
|
141
|
-
|
|
142
|
-
|
|
156
|
+
rowIndexes.forEach(
|
|
157
|
+
(/** @type {string | number} */ rowIndex, /** @type {string | number} */ index) => {
|
|
158
|
+
target[rowIndex] = [...rowsValue[index]];
|
|
159
|
+
}
|
|
160
|
+
);
|
|
143
161
|
|
|
144
162
|
return {
|
|
145
163
|
updatedMatrix: target,
|
|
146
|
-
selectionResult:
|
|
164
|
+
selectionResult:
|
|
165
|
+
rowIndexes.length === 1
|
|
166
|
+
? [target[rowIndexes[0]]]
|
|
167
|
+
: rowIndexes.map((/** @type {string | number} */ rowIndex) => [target[rowIndex]]),
|
|
147
168
|
};
|
|
148
169
|
}
|
|
149
170
|
|
|
150
|
-
const maxCols = Math.max(
|
|
171
|
+
const maxCols = Math.max(
|
|
172
|
+
...target.map((/** @type {string | any[]} */ row) => row.length),
|
|
173
|
+
0,
|
|
174
|
+
1
|
|
175
|
+
);
|
|
151
176
|
const colIndexes = resolveSelector(colSelector, maxCols);
|
|
152
177
|
const normalizedValue = normalizeMatrix(value);
|
|
153
178
|
|
|
154
179
|
if (normalizedValue.length !== rowIndexes.length) {
|
|
155
|
-
throw new Error(
|
|
180
|
+
throw new Error('Assigned row count does not match matrix slice');
|
|
156
181
|
}
|
|
157
182
|
|
|
158
|
-
normalizedValue.forEach((row,
|
|
183
|
+
normalizedValue.forEach((/** @type {string | any[]} */ row, /** @type {any} */ _rowOffset) => {
|
|
159
184
|
if (row.length !== colIndexes.length) {
|
|
160
|
-
throw new Error(
|
|
185
|
+
throw new Error('Assigned column count does not match matrix slice');
|
|
161
186
|
}
|
|
162
187
|
});
|
|
163
188
|
|
|
164
|
-
rowIndexes.forEach(
|
|
165
|
-
|
|
166
|
-
target[rowIndex]
|
|
167
|
-
|
|
189
|
+
rowIndexes.forEach(
|
|
190
|
+
(/** @type {string | number} */ rowIndex, /** @type {string | number} */ rowOffset) => {
|
|
191
|
+
if (!target[rowIndex]) {
|
|
192
|
+
target[rowIndex] = [];
|
|
193
|
+
}
|
|
168
194
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
195
|
+
colIndexes.forEach(
|
|
196
|
+
(/** @type {string | number} */ colIndex, /** @type {string | number} */ colOffset) => {
|
|
197
|
+
target[rowIndex][colIndex] = normalizedValue[rowOffset][colOffset];
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
);
|
|
173
202
|
|
|
174
203
|
return {
|
|
175
204
|
updatedMatrix: target,
|
|
176
|
-
selectionResult:
|
|
177
|
-
|
|
178
|
-
|
|
205
|
+
selectionResult:
|
|
206
|
+
rowIndexes.length === 1
|
|
207
|
+
? [
|
|
208
|
+
colIndexes.map(
|
|
209
|
+
(/** @type {string | number} */ colIndex) => target[rowIndexes[0]][colIndex]
|
|
210
|
+
),
|
|
211
|
+
]
|
|
212
|
+
: rowIndexes.map((/** @type {string | number} */ rowIndex) =>
|
|
213
|
+
colIndexes.map(
|
|
214
|
+
(/** @type {string | number} */ colIndex) => target[rowIndex][colIndex]
|
|
215
|
+
)
|
|
216
|
+
),
|
|
179
217
|
};
|
|
180
218
|
};
|
|
181
219
|
|
|
182
|
-
const
|
|
220
|
+
const isScalar = (/** @type {any} */ v) => typeof v === 'number' || typeof v === 'bigint';
|
|
221
|
+
|
|
222
|
+
const multiplyMatrices = (/** @type {any} */ left, /** @type {any} */ right) => {
|
|
223
|
+
if (isScalar(left)) {
|
|
224
|
+
const b = normalizeMatrix(right);
|
|
225
|
+
return b.map((/** @type {any[]} */ row) =>
|
|
226
|
+
row.map((/** @type {number} */ v) => Number(left) * v)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (isScalar(right)) {
|
|
230
|
+
const a = normalizeMatrix(left);
|
|
231
|
+
return a.map((/** @type {any[]} */ row) =>
|
|
232
|
+
row.map((/** @type {number} */ v) => v * Number(right))
|
|
233
|
+
);
|
|
234
|
+
}
|
|
183
235
|
const a = normalizeMatrix(left);
|
|
184
236
|
const b = normalizeMatrix(right);
|
|
185
237
|
|
|
186
238
|
if (a[0].length !== b.length) {
|
|
187
|
-
throw new Error(
|
|
239
|
+
throw new Error('Matrix dimensions do not allow multiplication');
|
|
188
240
|
}
|
|
189
241
|
|
|
190
|
-
return a.map((row) =>
|
|
191
|
-
b[0].map((_, colIndex) =>
|
|
192
|
-
row.reduce(
|
|
242
|
+
return a.map((/** @type {any[]} */ row) =>
|
|
243
|
+
b[0].map((/** @type {any} */ _, /** @type {string | number} */ colIndex) =>
|
|
244
|
+
row.reduce(
|
|
245
|
+
(
|
|
246
|
+
/** @type {number} */ sum,
|
|
247
|
+
/** @type {number} */ value,
|
|
248
|
+
/** @type {string | number} */ rowIndex
|
|
249
|
+
) => sum + value * b[rowIndex][colIndex],
|
|
250
|
+
0
|
|
251
|
+
)
|
|
193
252
|
)
|
|
194
253
|
);
|
|
195
254
|
};
|
|
196
255
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
256
|
+
const addMatrices = (/** @type {any} */ left, /** @type {any} */ right) => {
|
|
257
|
+
const a = normalizeMatrix(left);
|
|
258
|
+
const b = normalizeMatrix(right);
|
|
259
|
+
if (a.length !== b.length || a[0].length !== b[0].length) {
|
|
260
|
+
throw new Error('Matrix dimensions must match for addition');
|
|
261
|
+
}
|
|
262
|
+
return a.map((/** @type {any[]} */ row, /** @type {string | number} */ i) =>
|
|
263
|
+
row.map((/** @type {any} */ v, /** @type {string | number} */ j) => v + b[i][j])
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const subtractMatrices = (/** @type {any} */ left, /** @type {any} */ right) => {
|
|
268
|
+
const a = normalizeMatrix(left);
|
|
269
|
+
const b = normalizeMatrix(right);
|
|
270
|
+
if (a.length !== b.length || a[0].length !== b[0].length) {
|
|
271
|
+
throw new Error('Matrix dimensions must match for subtraction');
|
|
272
|
+
}
|
|
273
|
+
return a.map((/** @type {any[]} */ row, /** @type {string | number} */ i) =>
|
|
274
|
+
row.map((/** @type {number} */ v, /** @type {string | number} */ j) => v - b[i][j])
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const identityMatrix = (/** @type {any} */ n) =>
|
|
279
|
+
Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)));
|
|
280
|
+
|
|
281
|
+
const powerMatrix = (/** @type {any} */ left, /** @type {any} */ right) => {
|
|
282
|
+
const a = normalizeMatrix(left);
|
|
283
|
+
if (a.length !== a[0].length) {
|
|
284
|
+
throw new Error('Matrix power requires a square matrix');
|
|
285
|
+
}
|
|
286
|
+
if (!Number.isInteger(right) || right < 0) {
|
|
287
|
+
throw new Error('Matrix power requires a non-negative integer exponent');
|
|
288
|
+
}
|
|
289
|
+
if (right === 0) {
|
|
290
|
+
return identityMatrix(a.length);
|
|
291
|
+
}
|
|
292
|
+
let result = a;
|
|
293
|
+
for (let i = 1; i < right; i++) {
|
|
294
|
+
result = multiplyMatrices(result, a);
|
|
295
|
+
}
|
|
296
|
+
return result;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const toComplex = (/** @type {any} */ value) => {
|
|
300
|
+
if (isComplex(value)) {
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
if (typeof value === 'number') {
|
|
304
|
+
return { re: value, im: 0 };
|
|
305
|
+
}
|
|
306
|
+
throw new Error('Complex arithmetic only supports numbers');
|
|
201
307
|
};
|
|
202
308
|
|
|
203
|
-
const fromImaginary = (value) => ({ re: 0, im: value });
|
|
309
|
+
const fromImaginary = (/** @type {any} */ value) => ({ re: 0, im: value });
|
|
204
310
|
|
|
205
|
-
const simplifyComplex = (value) =>
|
|
311
|
+
const simplifyComplex = (/** @type {{ re: any; im: any; }} */ value) =>
|
|
206
312
|
value.im === 0 ? value.re : value;
|
|
207
313
|
|
|
208
|
-
const createFunctionScope = (params, args) => {
|
|
314
|
+
const createFunctionScope = (/** @type {any[]} */ params, /** @type {any[]} */ args) => {
|
|
209
315
|
const scopedValues = {};
|
|
210
316
|
|
|
211
|
-
params.forEach((param, index) => {
|
|
317
|
+
params.forEach((/** @type {string | number} */ param, /** @type {string | number} */ index) => {
|
|
212
318
|
scopedValues[param] = args[index];
|
|
213
319
|
});
|
|
214
320
|
|
|
215
321
|
return scopedValues;
|
|
216
322
|
};
|
|
217
323
|
|
|
218
|
-
const evalComplexBinary = (
|
|
324
|
+
const evalComplexBinary = (
|
|
325
|
+
/** @type {any} */ operator,
|
|
326
|
+
/** @type {any} */ left,
|
|
327
|
+
/** @type {any} */ right
|
|
328
|
+
) => {
|
|
219
329
|
const a = toComplex(left);
|
|
220
330
|
const b = toComplex(right);
|
|
221
331
|
|
|
222
332
|
switch (operator) {
|
|
223
|
-
case
|
|
333
|
+
case '+':
|
|
224
334
|
return simplifyComplex({ re: a.re + b.re, im: a.im + b.im });
|
|
225
|
-
case
|
|
335
|
+
case '-':
|
|
226
336
|
return simplifyComplex({ re: a.re - b.re, im: a.im - b.im });
|
|
227
|
-
case
|
|
337
|
+
case '*':
|
|
228
338
|
return simplifyComplex({
|
|
229
|
-
re:
|
|
230
|
-
im:
|
|
339
|
+
re: a.re * b.re - a.im * b.im,
|
|
340
|
+
im: a.re * b.im + a.im * b.re,
|
|
231
341
|
});
|
|
232
|
-
case
|
|
233
|
-
const denominator =
|
|
342
|
+
case '/': {
|
|
343
|
+
const denominator = b.re ** 2 + b.im ** 2;
|
|
234
344
|
|
|
235
345
|
if (denominator === 0) {
|
|
236
|
-
throw new Error(
|
|
346
|
+
throw new Error('Division by zero');
|
|
237
347
|
}
|
|
238
348
|
|
|
239
349
|
return simplifyComplex({
|
|
240
|
-
re: (
|
|
241
|
-
im: (
|
|
350
|
+
re: (a.re * b.re + a.im * b.im) / denominator,
|
|
351
|
+
im: (a.im * b.re - a.re * b.im) / denominator,
|
|
242
352
|
});
|
|
243
353
|
}
|
|
244
354
|
default:
|
|
@@ -246,52 +356,76 @@ export function evaluateAST(node, context = {}) {
|
|
|
246
356
|
}
|
|
247
357
|
};
|
|
248
358
|
|
|
249
|
-
|
|
250
|
-
|
|
359
|
+
// EVALUATOR
|
|
251
360
|
switch (node.type) {
|
|
252
|
-
|
|
253
|
-
/* ===== LITERAL ===== */
|
|
254
|
-
case "Literal":
|
|
361
|
+
case 'Literal':
|
|
255
362
|
return node.value;
|
|
256
363
|
|
|
257
|
-
case
|
|
364
|
+
case 'ImaginaryLiteral':
|
|
258
365
|
return fromImaginary(node.value);
|
|
259
366
|
|
|
260
|
-
case
|
|
367
|
+
case 'UnitLiteral':
|
|
261
368
|
return { value: node.value, unit: node.unit };
|
|
262
369
|
|
|
263
|
-
|
|
264
|
-
case
|
|
370
|
+
// VARIABLE
|
|
371
|
+
case 'Identifier':
|
|
265
372
|
return vars.get(node.name);
|
|
266
373
|
|
|
267
|
-
|
|
268
|
-
case
|
|
269
|
-
|
|
374
|
+
// Assignment with optional compound operator (+=, -=, *=, /=): read current, apply, write
|
|
375
|
+
case 'AssignmentExpression': {
|
|
376
|
+
let value;
|
|
377
|
+
if (node.operator !== '=') {
|
|
378
|
+
const current = vars.get(node.left.name);
|
|
379
|
+
const right = evaluateAST(node.right, context);
|
|
380
|
+
const op = node.operator.slice(0, -1);
|
|
381
|
+
switch (op) {
|
|
382
|
+
case '+':
|
|
383
|
+
value = current + right;
|
|
384
|
+
break;
|
|
385
|
+
case '-':
|
|
386
|
+
value = current - right;
|
|
387
|
+
break;
|
|
388
|
+
case '*':
|
|
389
|
+
value = current * right;
|
|
390
|
+
break;
|
|
391
|
+
case '/':
|
|
392
|
+
value = current / right;
|
|
393
|
+
break;
|
|
394
|
+
case '%':
|
|
395
|
+
value = current % right;
|
|
396
|
+
break;
|
|
397
|
+
default:
|
|
398
|
+
throw nodeError(`Unknown compound operator ${node.operator}`);
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
value = evaluateAST(node.right, context);
|
|
402
|
+
}
|
|
270
403
|
|
|
271
|
-
if (node.left.type ===
|
|
404
|
+
if (node.left.type === 'Identifier') {
|
|
272
405
|
vars.set(node.left.name, value);
|
|
273
|
-
if (node.right.type ===
|
|
406
|
+
if (node.right.type === 'ArrayExpression') {
|
|
274
407
|
return wrapDenseMatrix(unwrapDenseMatrix(value));
|
|
275
408
|
}
|
|
276
409
|
return value;
|
|
277
410
|
}
|
|
278
411
|
|
|
279
|
-
if (node.left.type ===
|
|
412
|
+
if (node.left.type === 'IndexExpression' && node.left.object.type === 'Identifier') {
|
|
280
413
|
const currentValue = vars.get(node.left.object.name);
|
|
281
414
|
const assigned = assignMatrixIndex(currentValue, node.left.selectors, value);
|
|
282
415
|
vars.set(node.left.object.name, assigned.updatedMatrix);
|
|
283
416
|
return assigned.selectionResult;
|
|
284
417
|
}
|
|
285
418
|
|
|
286
|
-
throw
|
|
419
|
+
throw nodeError('Invalid assignment target');
|
|
287
420
|
}
|
|
288
421
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
422
|
+
// User-defined function via f(a,b)=expr: closure evaluates body in a new scope with params bound
|
|
423
|
+
case 'FunctionAssignmentExpression': {
|
|
424
|
+
if (node.operator !== '=') {
|
|
425
|
+
throw nodeError(`Operator ${node.operator} is not supported for function definitions`);
|
|
292
426
|
}
|
|
293
427
|
|
|
294
|
-
const fn = (...args) => {
|
|
428
|
+
const fn = (/** @type {any} */ ...args) => {
|
|
295
429
|
const scopedContext = context.withScope(createFunctionScope(node.params, args));
|
|
296
430
|
return evaluateAST(node.right, scopedContext);
|
|
297
431
|
};
|
|
@@ -300,36 +434,112 @@ export function evaluateAST(node, context = {}) {
|
|
|
300
434
|
return fn;
|
|
301
435
|
}
|
|
302
436
|
|
|
303
|
-
|
|
304
|
-
case
|
|
437
|
+
// UNARY
|
|
438
|
+
case 'UnaryExpression': {
|
|
305
439
|
const val = evaluateAST(node.argument, context);
|
|
306
440
|
|
|
307
441
|
switch (node.operator) {
|
|
308
|
-
case
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
442
|
+
case '-':
|
|
443
|
+
if (isBigNumber(val)) {
|
|
444
|
+
return val.negated();
|
|
445
|
+
}
|
|
446
|
+
if (isComplex(val)) {
|
|
447
|
+
return simplifyComplex({ re: -val.re, im: -val.im });
|
|
448
|
+
}
|
|
449
|
+
return -val;
|
|
450
|
+
case '!':
|
|
451
|
+
return !val;
|
|
313
452
|
}
|
|
314
453
|
|
|
315
|
-
throw
|
|
454
|
+
throw nodeError(`Unknown unary operator ${node.operator}`);
|
|
316
455
|
}
|
|
317
456
|
|
|
318
|
-
|
|
319
|
-
case
|
|
320
|
-
|
|
321
|
-
|
|
457
|
+
// Dispatch order: unit arithmetic -> matrix arithmetic -> complex arithmetic -> scalar arithmetic
|
|
458
|
+
case 'BinaryExpression': {
|
|
459
|
+
const left = evaluateAST(node.left, context);
|
|
460
|
+
const right = evaluateAST(node.right, context);
|
|
322
461
|
|
|
323
462
|
// UNIT handling
|
|
324
463
|
if (isUnitObj(left) || isUnitObj(right)) {
|
|
325
|
-
|
|
326
|
-
|
|
464
|
+
if (!units) {
|
|
465
|
+
throw new Error('Unit system not available');
|
|
466
|
+
}
|
|
327
467
|
|
|
328
468
|
return units.compute(node.operator, left, right);
|
|
329
469
|
}
|
|
330
470
|
|
|
331
|
-
if (
|
|
332
|
-
|
|
471
|
+
if (
|
|
472
|
+
isMatrixLike(left) ||
|
|
473
|
+
isMatrixLike(right) ||
|
|
474
|
+
(node.operator === '*' && (Array.isArray(left) || Array.isArray(right)))
|
|
475
|
+
) {
|
|
476
|
+
switch (node.operator) {
|
|
477
|
+
case '+':
|
|
478
|
+
return addMatrices(left, right);
|
|
479
|
+
case '-':
|
|
480
|
+
return subtractMatrices(left, right);
|
|
481
|
+
case '*':
|
|
482
|
+
return multiplyMatrices(left, right);
|
|
483
|
+
case '^':
|
|
484
|
+
return powerMatrix(left, right);
|
|
485
|
+
default:
|
|
486
|
+
throw nodeError(`Operator ${node.operator} not supported for matrices`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (isFraction(left) || isFraction(right)) {
|
|
491
|
+
const a = isFraction(left) ? left : createFrac(left, 1);
|
|
492
|
+
const b = isFraction(right) ? right : createFrac(right, 1);
|
|
493
|
+
switch (node.operator) {
|
|
494
|
+
case '+':
|
|
495
|
+
return addFrac(a, b);
|
|
496
|
+
case '-':
|
|
497
|
+
return subFrac(a, b);
|
|
498
|
+
case '*':
|
|
499
|
+
return mulFrac(a, b);
|
|
500
|
+
case '/':
|
|
501
|
+
return divFrac(a, b);
|
|
502
|
+
case '^': {
|
|
503
|
+
const p = powFrac(a, right);
|
|
504
|
+
if (p) {
|
|
505
|
+
return p;
|
|
506
|
+
}
|
|
507
|
+
throw nodeError('Fraction power requires non-negative integer exponent');
|
|
508
|
+
}
|
|
509
|
+
default:
|
|
510
|
+
throw nodeError(`Operator ${node.operator} not supported for fractions`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (isBigNumber(left) || isBigNumber(right)) {
|
|
515
|
+
const a = isBigNumber(left) ? left : createBN(left);
|
|
516
|
+
const b = isBigNumber(right) ? right : createBN(right);
|
|
517
|
+
switch (node.operator) {
|
|
518
|
+
case '+':
|
|
519
|
+
return a.plus(b);
|
|
520
|
+
case '-':
|
|
521
|
+
return a.minus(b);
|
|
522
|
+
case '*':
|
|
523
|
+
return a.times(b);
|
|
524
|
+
case '/':
|
|
525
|
+
return a.div(b);
|
|
526
|
+
case '%':
|
|
527
|
+
return a.mod(b);
|
|
528
|
+
case '^':
|
|
529
|
+
return a.pow(b);
|
|
530
|
+
case '>':
|
|
531
|
+
return a.gt(b);
|
|
532
|
+
case '<':
|
|
533
|
+
return a.lt(b);
|
|
534
|
+
case '>=':
|
|
535
|
+
return a.gte(b);
|
|
536
|
+
case '<=':
|
|
537
|
+
return a.lte(b);
|
|
538
|
+
case '==':
|
|
539
|
+
return a.eq(b);
|
|
540
|
+
default:
|
|
541
|
+
throw nodeError(`Operator ${node.operator} not supported for BigNumber`);
|
|
542
|
+
}
|
|
333
543
|
}
|
|
334
544
|
|
|
335
545
|
if (isComplex(left) || isComplex(right)) {
|
|
@@ -337,124 +547,174 @@ export function evaluateAST(node, context = {}) {
|
|
|
337
547
|
}
|
|
338
548
|
|
|
339
549
|
switch (node.operator) {
|
|
340
|
-
case
|
|
341
|
-
|
|
342
|
-
case
|
|
343
|
-
|
|
344
|
-
case
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
case
|
|
349
|
-
|
|
350
|
-
case
|
|
351
|
-
|
|
550
|
+
case '+':
|
|
551
|
+
return left + right;
|
|
552
|
+
case '-':
|
|
553
|
+
return left - right;
|
|
554
|
+
case '*':
|
|
555
|
+
return left * right;
|
|
556
|
+
case '/':
|
|
557
|
+
return left / right;
|
|
558
|
+
case '%':
|
|
559
|
+
return left % right;
|
|
560
|
+
case '^':
|
|
561
|
+
return left ** right;
|
|
562
|
+
|
|
563
|
+
case '>':
|
|
564
|
+
return left > right;
|
|
565
|
+
case '<':
|
|
566
|
+
return left < right;
|
|
567
|
+
case '>=':
|
|
568
|
+
return left >= right;
|
|
569
|
+
case '<=':
|
|
570
|
+
return left <= right;
|
|
571
|
+
case '==':
|
|
572
|
+
return left === right;
|
|
352
573
|
}
|
|
353
574
|
|
|
354
|
-
throw
|
|
575
|
+
throw nodeError(`Unknown operator ${node.operator}`);
|
|
355
576
|
}
|
|
356
577
|
|
|
357
|
-
|
|
358
|
-
case
|
|
578
|
+
// Short-circuit: && returns first falsy, || returns first truthy, ?? returns first non-nullish
|
|
579
|
+
case 'LogicalExpression': {
|
|
359
580
|
const left = evaluateAST(node.left, context);
|
|
360
581
|
|
|
361
|
-
if (node.operator ===
|
|
582
|
+
if (node.operator === '&&') {
|
|
362
583
|
return left && evaluateAST(node.right, context);
|
|
363
584
|
}
|
|
364
585
|
|
|
365
|
-
if (node.operator ===
|
|
586
|
+
if (node.operator === '||') {
|
|
366
587
|
return left || evaluateAST(node.right, context);
|
|
367
588
|
}
|
|
368
589
|
|
|
369
|
-
if (node.operator ===
|
|
590
|
+
if (node.operator === '??') {
|
|
370
591
|
return left ?? evaluateAST(node.right, context);
|
|
371
592
|
}
|
|
372
593
|
|
|
373
|
-
throw
|
|
594
|
+
throw nodeError(`Unknown logical operator ${node.operator}`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Range [start..end] inclusive: returns array of integers from floor(start) to floor(end)
|
|
598
|
+
case 'RangeExpression': {
|
|
599
|
+
const start = evaluateAST(node.start, context);
|
|
600
|
+
const end = evaluateAST(node.end, context);
|
|
601
|
+
if (typeof start !== 'number' || typeof end !== 'number') {
|
|
602
|
+
throw nodeError('Range requires numeric bounds');
|
|
603
|
+
}
|
|
604
|
+
const result = [];
|
|
605
|
+
for (let i = Math.floor(start); i <= Math.floor(end); i++) {
|
|
606
|
+
result.push(i);
|
|
607
|
+
}
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Lambda: return a callable function evaluating the body with params bound in a new scope
|
|
612
|
+
case 'ArrowFunctionExpression': {
|
|
613
|
+
const fn = (/** @type {any[]} */ ...args) => {
|
|
614
|
+
const scopedContext = context.withScope(createFunctionScope(node.params, args));
|
|
615
|
+
return evaluateAST(node.body, scopedContext);
|
|
616
|
+
};
|
|
617
|
+
return fn;
|
|
374
618
|
}
|
|
375
619
|
|
|
376
|
-
|
|
377
|
-
case
|
|
620
|
+
// Function call: flatten spread (...array) arguments, then invoke
|
|
621
|
+
case 'CallExpression': {
|
|
378
622
|
const fnName = node.callee.name;
|
|
379
623
|
const fn = fns.get(fnName);
|
|
380
624
|
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
625
|
+
const rawArgs = node.arguments.map((/** @type {{ type: string; argument: any; }} */ arg) => {
|
|
626
|
+
if (arg.type === 'SpreadElement') {
|
|
627
|
+
const val = evaluateAST(arg.argument, context);
|
|
628
|
+
if (!Array.isArray(val)) {
|
|
629
|
+
throw new Error('Spread operator requires an array');
|
|
630
|
+
}
|
|
631
|
+
return { spread: true, values: val };
|
|
632
|
+
}
|
|
633
|
+
return { spread: false, value: evaluateAST(arg, context) };
|
|
634
|
+
});
|
|
635
|
+
const args = [];
|
|
636
|
+
for (const arg of rawArgs) {
|
|
637
|
+
if (arg.spread) {
|
|
638
|
+
args.push(...arg.values);
|
|
639
|
+
} else {
|
|
640
|
+
args.push(arg.value);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
384
643
|
|
|
385
644
|
return fn(...args);
|
|
386
645
|
}
|
|
387
646
|
|
|
388
|
-
|
|
389
|
-
case
|
|
647
|
+
// Pipeline: left value is passed as first argument to the right function/expression
|
|
648
|
+
case 'PipelineExpression': {
|
|
390
649
|
const leftVal = evaluateAST(node.left, context);
|
|
391
650
|
|
|
392
651
|
// right must be function
|
|
393
|
-
if (node.right.type ===
|
|
652
|
+
if (node.right.type === 'CallExpression') {
|
|
394
653
|
const fnName = node.right.callee.name;
|
|
395
654
|
const fn = fns.get(fnName);
|
|
396
655
|
|
|
397
656
|
const args = [
|
|
398
657
|
leftVal,
|
|
399
|
-
...node.right.arguments.map(arg =>
|
|
400
|
-
evaluateAST(arg, context)
|
|
401
|
-
)
|
|
658
|
+
...node.right.arguments.map((/** @type {any} */ arg) => evaluateAST(arg, context)),
|
|
402
659
|
];
|
|
403
660
|
|
|
404
661
|
return fn(...args);
|
|
405
662
|
}
|
|
406
663
|
|
|
407
|
-
if (node.right.type ===
|
|
664
|
+
if (node.right.type === 'Identifier') {
|
|
408
665
|
const fn = fns.get(node.right.name);
|
|
409
666
|
return fn(leftVal);
|
|
410
667
|
}
|
|
411
668
|
|
|
412
|
-
throw
|
|
669
|
+
throw nodeError('Invalid pipeline target');
|
|
413
670
|
}
|
|
414
671
|
|
|
415
|
-
|
|
416
|
-
case
|
|
672
|
+
// Unit conversion: value fromUnit -> toUnit
|
|
673
|
+
case 'UnitConversion': {
|
|
417
674
|
const from = evaluateAST(node.from, context);
|
|
418
675
|
|
|
419
676
|
if (!isUnitObj(from)) {
|
|
420
|
-
throw
|
|
677
|
+
throw nodeError('Left side must be a unit value');
|
|
421
678
|
}
|
|
422
679
|
|
|
423
680
|
if (!units) {
|
|
424
|
-
throw
|
|
681
|
+
throw nodeError('Unit system not available');
|
|
425
682
|
}
|
|
426
683
|
|
|
427
684
|
return units.convert(from.value, from.unit, node.to);
|
|
428
685
|
}
|
|
429
686
|
|
|
430
|
-
|
|
431
|
-
case
|
|
432
|
-
return node.elements.map(el => evaluateAST(el, context));
|
|
687
|
+
// ARRAY
|
|
688
|
+
case 'ArrayExpression':
|
|
689
|
+
return node.elements.map((/** @type {any} */ el) => evaluateAST(el, context));
|
|
433
690
|
|
|
434
|
-
|
|
691
|
+
// Matrix/array indexing: target[selector1, selector2] with 1-based and slice support
|
|
692
|
+
case 'IndexExpression': {
|
|
435
693
|
const target = evaluateAST(node.object, context);
|
|
436
694
|
return indexMatrix(target, node.selectors);
|
|
437
695
|
}
|
|
438
696
|
|
|
439
|
-
|
|
440
|
-
case
|
|
697
|
+
// OBJECT
|
|
698
|
+
case 'ObjectExpression': {
|
|
441
699
|
const obj = {};
|
|
442
|
-
for (
|
|
700
|
+
for (const p of node.properties) {
|
|
443
701
|
obj[p.key] = evaluateAST(p.value, context);
|
|
444
702
|
}
|
|
445
703
|
return obj;
|
|
446
704
|
}
|
|
447
705
|
|
|
448
|
-
|
|
449
|
-
case
|
|
706
|
+
// Property access: obj.prop; optional chaining (?.) returns undefined if obj is null/undefined
|
|
707
|
+
case 'MemberExpression': {
|
|
450
708
|
const obj = evaluateAST(node.object, context);
|
|
451
709
|
|
|
452
|
-
if (node.optional && obj
|
|
710
|
+
if (node.optional && (obj === null || obj === undefined)) {
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
453
713
|
|
|
454
714
|
return obj[node.property.name];
|
|
455
715
|
}
|
|
456
716
|
|
|
457
717
|
default:
|
|
458
|
-
throw
|
|
718
|
+
throw nodeError(`Unknown AST node type: ${node.type}`);
|
|
459
719
|
}
|
|
460
720
|
}
|