exprify 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exprify.js +1 -1
- package/dist/exprify.min.js +1 -1
- package/package.json +1 -1
- package/src/assets/capture.jpg +0 -0
- package/src/core/Exprify.js +369 -0
- package/src/core/context.js +30 -0
- package/src/function/executor.js +64 -0
- package/src/function/internal.js +612 -0
- package/src/function/registry.js +68 -0
- package/src/index.js +2 -0
- package/src/math/operations.js +38 -0
- package/src/parser/astBuild.js +531 -0
- package/src/parser/evaluator.js +460 -0
- package/src/parser/tokenizer.js +399 -0
- package/src/utils/globalUnits.js +217 -0
- package/src/utils/matrix.js +53 -0
- package/src/utils/store.js +178 -0
- package/src/variables/store.js +75 -0
- package/docs/README.md +0 -34
- package/docs/assets/css/style.scss +0 -4
- package/docs/tokenType.txt +0 -21
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { unwrapDenseMatrix, wrapDenseMatrix } from "../utils/matrix.js";
|
|
2
|
+
|
|
3
|
+
export function evaluateAST(node, context = {}) {
|
|
4
|
+
|
|
5
|
+
const vars = context.variables;
|
|
6
|
+
const fns = context.functions;
|
|
7
|
+
const units = context.units;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const isUnitObj = (v) =>
|
|
11
|
+
v && typeof v === "object" && "value" in v && "unit" in v;
|
|
12
|
+
|
|
13
|
+
const isComplex = (v) =>
|
|
14
|
+
v && typeof v === "object" && "re" in v && "im" in v;
|
|
15
|
+
|
|
16
|
+
const isSliceNode = (v) =>
|
|
17
|
+
v && typeof v === "object" && v.type === "SliceExpression";
|
|
18
|
+
|
|
19
|
+
const isMatrix = (v) =>
|
|
20
|
+
Array.isArray(v) && v.length > 0 && v.every(Array.isArray);
|
|
21
|
+
|
|
22
|
+
const cloneValue = (value) => {
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.map(cloneValue);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value && typeof value === "object") {
|
|
28
|
+
return { ...value };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const normalizeMatrix = (value) => {
|
|
35
|
+
value = unwrapDenseMatrix(value);
|
|
36
|
+
if (isMatrix(value)) return value.map((row) => [...row]);
|
|
37
|
+
if (Array.isArray(value)) return [value];
|
|
38
|
+
throw new Error("Expected matrix-compatible value");
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const toOneBasedIndex = (value) => {
|
|
42
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
43
|
+
throw new Error("Matrix indices must be positive integers");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return value - 1;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const resolveSelector = (selector, contextLength) => {
|
|
50
|
+
if (isSliceNode(selector)) {
|
|
51
|
+
const startValue = selector.start == null ? 1 : evaluateAST(selector.start, context);
|
|
52
|
+
const endValue = selector.end == null ? contextLength : evaluateAST(selector.end, context);
|
|
53
|
+
const start = toOneBasedIndex(startValue);
|
|
54
|
+
const end = toOneBasedIndex(endValue);
|
|
55
|
+
|
|
56
|
+
if (end < start) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = [];
|
|
61
|
+
for (let index = start; index <= end; index++) {
|
|
62
|
+
result.push(index);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [toOneBasedIndex(evaluateAST(selector, context))];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const indexMatrix = (matrix, selectors) => {
|
|
71
|
+
const target = normalizeMatrix(matrix);
|
|
72
|
+
|
|
73
|
+
if (selectors.length === 1) {
|
|
74
|
+
const rowIndexes = resolveSelector(selectors[0], target.length);
|
|
75
|
+
const rows = rowIndexes.map((rowIndex) => {
|
|
76
|
+
if (rowIndex >= target.length) {
|
|
77
|
+
throw new Error("Row index out of range");
|
|
78
|
+
}
|
|
79
|
+
return [...target[rowIndex]];
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return rows.length === 1 ? rows[0] : rows;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const rowIndexes = resolveSelector(selectors[0], target.length);
|
|
86
|
+
const colIndexes = resolveSelector(selectors[1], target[0]?.length || 0);
|
|
87
|
+
|
|
88
|
+
const values = rowIndexes.map((rowIndex) => {
|
|
89
|
+
if (rowIndex >= target.length) {
|
|
90
|
+
throw new Error("Row index out of range");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return colIndexes.map((colIndex) => {
|
|
94
|
+
if (colIndex >= target[rowIndex].length) {
|
|
95
|
+
throw new Error("Column index out of range");
|
|
96
|
+
}
|
|
97
|
+
return target[rowIndex][colIndex];
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (rowIndexes.length === 1 && colIndexes.length === 1) {
|
|
102
|
+
return values[0][0];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (rowIndexes.length === 1) {
|
|
106
|
+
return values[0];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (colIndexes.length === 1) {
|
|
110
|
+
return values.map((row) => [row[0]]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return values;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const assignMatrixIndex = (matrix, selectors, value) => {
|
|
117
|
+
const target = isMatrix(matrix)
|
|
118
|
+
? matrix.map((row) => [...row])
|
|
119
|
+
: Array.isArray(matrix)
|
|
120
|
+
? [matrix.slice()]
|
|
121
|
+
: [];
|
|
122
|
+
|
|
123
|
+
const rowSelector = selectors[0];
|
|
124
|
+
const colSelector = selectors[1];
|
|
125
|
+
|
|
126
|
+
if (!rowSelector) {
|
|
127
|
+
throw new Error("Matrix assignment requires at least one index");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const rowContextLength = Math.max(target.length, 1);
|
|
131
|
+
const rowIndexes = resolveSelector(rowSelector, rowContextLength);
|
|
132
|
+
|
|
133
|
+
if (selectors.length === 1) {
|
|
134
|
+
const rowsValue = isMatrix(value) ? value : normalizeMatrix(value);
|
|
135
|
+
|
|
136
|
+
if (rowsValue.length !== rowIndexes.length) {
|
|
137
|
+
throw new Error("Assigned row count does not match slice");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
rowIndexes.forEach((rowIndex, index) => {
|
|
141
|
+
target[rowIndex] = [...rowsValue[index]];
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
updatedMatrix: target,
|
|
146
|
+
selectionResult: rowIndexes.length === 1 ? [target[rowIndexes[0]]] : rowIndexes.map((rowIndex) => [target[rowIndex]])
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const maxCols = Math.max(...target.map((row) => row.length), 0, 1);
|
|
151
|
+
const colIndexes = resolveSelector(colSelector, maxCols);
|
|
152
|
+
const normalizedValue = normalizeMatrix(value);
|
|
153
|
+
|
|
154
|
+
if (normalizedValue.length !== rowIndexes.length) {
|
|
155
|
+
throw new Error("Assigned row count does not match matrix slice");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
normalizedValue.forEach((row, rowOffset) => {
|
|
159
|
+
if (row.length !== colIndexes.length) {
|
|
160
|
+
throw new Error("Assigned column count does not match matrix slice");
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
rowIndexes.forEach((rowIndex, rowOffset) => {
|
|
165
|
+
if (!target[rowIndex]) {
|
|
166
|
+
target[rowIndex] = [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
colIndexes.forEach((colIndex, colOffset) => {
|
|
170
|
+
target[rowIndex][colIndex] = normalizedValue[rowOffset][colOffset];
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
updatedMatrix: target,
|
|
176
|
+
selectionResult: rowIndexes.length === 1
|
|
177
|
+
? [colIndexes.map((colIndex) => target[rowIndexes[0]][colIndex])]
|
|
178
|
+
: rowIndexes.map((rowIndex) => colIndexes.map((colIndex) => target[rowIndex][colIndex]))
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const multiplyMatrices = (left, right) => {
|
|
183
|
+
const a = normalizeMatrix(left);
|
|
184
|
+
const b = normalizeMatrix(right);
|
|
185
|
+
|
|
186
|
+
if (a[0].length !== b.length) {
|
|
187
|
+
throw new Error("Matrix dimensions do not allow multiplication");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return a.map((row) =>
|
|
191
|
+
b[0].map((_, colIndex) =>
|
|
192
|
+
row.reduce((sum, value, rowIndex) => sum + (value * b[rowIndex][colIndex]), 0)
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const toComplex = (value) => {
|
|
198
|
+
if (isComplex(value)) return value;
|
|
199
|
+
if (typeof value === "number") return { re: value, im: 0 };
|
|
200
|
+
throw new Error("Complex arithmetic only supports numbers");
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const fromImaginary = (value) => ({ re: 0, im: value });
|
|
204
|
+
|
|
205
|
+
const simplifyComplex = (value) =>
|
|
206
|
+
value.im === 0 ? value.re : value;
|
|
207
|
+
|
|
208
|
+
const createFunctionScope = (params, args) => {
|
|
209
|
+
const scopedValues = {};
|
|
210
|
+
|
|
211
|
+
params.forEach((param, index) => {
|
|
212
|
+
scopedValues[param] = args[index];
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return scopedValues;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const evalComplexBinary = (operator, left, right) => {
|
|
219
|
+
const a = toComplex(left);
|
|
220
|
+
const b = toComplex(right);
|
|
221
|
+
|
|
222
|
+
switch (operator) {
|
|
223
|
+
case "+":
|
|
224
|
+
return simplifyComplex({ re: a.re + b.re, im: a.im + b.im });
|
|
225
|
+
case "-":
|
|
226
|
+
return simplifyComplex({ re: a.re - b.re, im: a.im - b.im });
|
|
227
|
+
case "*":
|
|
228
|
+
return simplifyComplex({
|
|
229
|
+
re: (a.re * b.re) - (a.im * b.im),
|
|
230
|
+
im: (a.re * b.im) + (a.im * b.re)
|
|
231
|
+
});
|
|
232
|
+
case "/": {
|
|
233
|
+
const denominator = (b.re ** 2) + (b.im ** 2);
|
|
234
|
+
|
|
235
|
+
if (denominator === 0) {
|
|
236
|
+
throw new Error("Division by zero");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return simplifyComplex({
|
|
240
|
+
re: ((a.re * b.re) + (a.im * b.im)) / denominator,
|
|
241
|
+
im: ((a.im * b.re) - (a.re * b.im)) / denominator
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
default:
|
|
245
|
+
throw new Error(`Operator ${operator} is not supported for complex numbers`);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/* ================= EVALUATOR ================= */
|
|
250
|
+
|
|
251
|
+
switch (node.type) {
|
|
252
|
+
|
|
253
|
+
/* ===== LITERAL ===== */
|
|
254
|
+
case "Literal":
|
|
255
|
+
return node.value;
|
|
256
|
+
|
|
257
|
+
case "ImaginaryLiteral":
|
|
258
|
+
return fromImaginary(node.value);
|
|
259
|
+
|
|
260
|
+
case "UnitLiteral":
|
|
261
|
+
return { value: node.value, unit: node.unit };
|
|
262
|
+
|
|
263
|
+
/* ===== VARIABLE ===== */
|
|
264
|
+
case "Identifier":
|
|
265
|
+
return vars.get(node.name);
|
|
266
|
+
|
|
267
|
+
/* ===== ASSIGNMENT ===== */
|
|
268
|
+
case "AssignmentExpression": {
|
|
269
|
+
const value = evaluateAST(node.right, context);
|
|
270
|
+
|
|
271
|
+
if (node.left.type === "Identifier") {
|
|
272
|
+
vars.set(node.left.name, value);
|
|
273
|
+
if (node.right.type === "ArrayExpression") {
|
|
274
|
+
return wrapDenseMatrix(unwrapDenseMatrix(value));
|
|
275
|
+
}
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (node.left.type === "IndexExpression" && node.left.object.type === "Identifier") {
|
|
280
|
+
const currentValue = vars.get(node.left.object.name);
|
|
281
|
+
const assigned = assignMatrixIndex(currentValue, node.left.selectors, value);
|
|
282
|
+
vars.set(node.left.object.name, assigned.updatedMatrix);
|
|
283
|
+
return assigned.selectionResult;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
throw new Error("Invalid assignment target");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
case "FunctionAssignmentExpression": {
|
|
290
|
+
if (node.operator !== "=") {
|
|
291
|
+
throw new Error(`Operator ${node.operator} is not supported for function definitions`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const fn = (...args) => {
|
|
295
|
+
const scopedContext = context.withScope(createFunctionScope(node.params, args));
|
|
296
|
+
return evaluateAST(node.right, scopedContext);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
fns.register(node.left.name, fn);
|
|
300
|
+
return fn;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* ===== UNARY ===== */
|
|
304
|
+
case "UnaryExpression": {
|
|
305
|
+
const val = evaluateAST(node.argument, context);
|
|
306
|
+
|
|
307
|
+
switch (node.operator) {
|
|
308
|
+
case "-":
|
|
309
|
+
return isComplex(val)
|
|
310
|
+
? simplifyComplex({ re: -val.re, im: -val.im })
|
|
311
|
+
: -val;
|
|
312
|
+
case "!": return !val;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
throw new Error(`Unknown unary operator ${node.operator}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* ===== BINARY ===== */
|
|
319
|
+
case "BinaryExpression": {
|
|
320
|
+
let left = evaluateAST(node.left, context);
|
|
321
|
+
let right = evaluateAST(node.right, context);
|
|
322
|
+
|
|
323
|
+
// UNIT handling
|
|
324
|
+
if (isUnitObj(left) || isUnitObj(right)) {
|
|
325
|
+
|
|
326
|
+
if (!units) throw new Error("Unit system not available");
|
|
327
|
+
|
|
328
|
+
return units.compute(node.operator, left, right);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (node.operator === "*" && (Array.isArray(left) || Array.isArray(right))) {
|
|
332
|
+
return multiplyMatrices(left, right);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (isComplex(left) || isComplex(right)) {
|
|
336
|
+
return evalComplexBinary(node.operator, left, right);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
switch (node.operator) {
|
|
340
|
+
case "+": return left + right;
|
|
341
|
+
case "-": return left - right;
|
|
342
|
+
case "*": return left * right;
|
|
343
|
+
case "/": return left / right;
|
|
344
|
+
case "%": return left % right;
|
|
345
|
+
case "^": return left ** right;
|
|
346
|
+
|
|
347
|
+
case ">": return left > right;
|
|
348
|
+
case "<": return left < right;
|
|
349
|
+
case ">=": return left >= right;
|
|
350
|
+
case "<=": return left <= right;
|
|
351
|
+
case "==": return left === right;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
throw new Error(`Unknown operator ${node.operator}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* ===== LOGICAL ===== */
|
|
358
|
+
case "LogicalExpression": {
|
|
359
|
+
const left = evaluateAST(node.left, context);
|
|
360
|
+
|
|
361
|
+
if (node.operator === "&&") {
|
|
362
|
+
return left && evaluateAST(node.right, context);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (node.operator === "||") {
|
|
366
|
+
return left || evaluateAST(node.right, context);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (node.operator === "??") {
|
|
370
|
+
return left ?? evaluateAST(node.right, context);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
throw new Error(`Unknown logical operator ${node.operator}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ===== FUNCTION CALL ===== */
|
|
377
|
+
case "CallExpression": {
|
|
378
|
+
const fnName = node.callee.name;
|
|
379
|
+
const fn = fns.get(fnName);
|
|
380
|
+
|
|
381
|
+
const args = node.arguments.map(arg =>
|
|
382
|
+
evaluateAST(arg, context)
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
return fn(...args);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* ===== PIPELINE ===== */
|
|
389
|
+
case "PipelineExpression": {
|
|
390
|
+
const leftVal = evaluateAST(node.left, context);
|
|
391
|
+
|
|
392
|
+
// right must be function
|
|
393
|
+
if (node.right.type === "CallExpression") {
|
|
394
|
+
const fnName = node.right.callee.name;
|
|
395
|
+
const fn = fns.get(fnName);
|
|
396
|
+
|
|
397
|
+
const args = [
|
|
398
|
+
leftVal,
|
|
399
|
+
...node.right.arguments.map(arg =>
|
|
400
|
+
evaluateAST(arg, context)
|
|
401
|
+
)
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
return fn(...args);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (node.right.type === "Identifier") {
|
|
408
|
+
const fn = fns.get(node.right.name);
|
|
409
|
+
return fn(leftVal);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
throw new Error("Invalid pipeline target");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* ===== UNIT CONVERSION ===== */
|
|
416
|
+
case "UnitConversion": {
|
|
417
|
+
const from = evaluateAST(node.from, context);
|
|
418
|
+
|
|
419
|
+
if (!isUnitObj(from)) {
|
|
420
|
+
throw new Error("Left side must be a unit value");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!units) {
|
|
424
|
+
throw new Error("Unit system not available");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return units.convert(from.value, from.unit, node.to);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* ===== ARRAY ===== */
|
|
431
|
+
case "ArrayExpression":
|
|
432
|
+
return node.elements.map(el => evaluateAST(el, context));
|
|
433
|
+
|
|
434
|
+
case "IndexExpression": {
|
|
435
|
+
const target = evaluateAST(node.object, context);
|
|
436
|
+
return indexMatrix(target, node.selectors);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* ===== OBJECT ===== */
|
|
440
|
+
case "ObjectExpression": {
|
|
441
|
+
const obj = {};
|
|
442
|
+
for (let p of node.properties) {
|
|
443
|
+
obj[p.key] = evaluateAST(p.value, context);
|
|
444
|
+
}
|
|
445
|
+
return obj;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* ===== MEMBER ===== */
|
|
449
|
+
case "MemberExpression": {
|
|
450
|
+
const obj = evaluateAST(node.object, context);
|
|
451
|
+
|
|
452
|
+
if (node.optional && obj == null) return undefined;
|
|
453
|
+
|
|
454
|
+
return obj[node.property.name];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
default:
|
|
458
|
+
throw new Error(`Unknown AST node type: ${node.type}`);
|
|
459
|
+
}
|
|
460
|
+
}
|