@bilig/formula 0.1.0
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/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/addressing.d.ts +66 -0
- package/dist/addressing.js +179 -0
- package/dist/addressing.js.map +1 -0
- package/dist/ast.d.ts +74 -0
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -0
- package/dist/binder.d.ts +13 -0
- package/dist/binder.js +1700 -0
- package/dist/binder.js.map +1 -0
- package/dist/builtin-capabilities.d.ts +19 -0
- package/dist/builtin-capabilities.js +861 -0
- package/dist/builtin-capabilities.js.map +1 -0
- package/dist/builtins/complex.d.ts +10 -0
- package/dist/builtins/complex.js +407 -0
- package/dist/builtins/complex.js.map +1 -0
- package/dist/builtins/convert.d.ts +3 -0
- package/dist/builtins/convert.js +362 -0
- package/dist/builtins/convert.js.map +1 -0
- package/dist/builtins/datetime.d.ts +23 -0
- package/dist/builtins/datetime.js +1096 -0
- package/dist/builtins/datetime.js.map +1 -0
- package/dist/builtins/distribution-builtins.d.ts +16 -0
- package/dist/builtins/distribution-builtins.js +517 -0
- package/dist/builtins/distribution-builtins.js.map +1 -0
- package/dist/builtins/distributions.d.ts +34 -0
- package/dist/builtins/distributions.js +722 -0
- package/dist/builtins/distributions.js.map +1 -0
- package/dist/builtins/financial-builtins.d.ts +16 -0
- package/dist/builtins/financial-builtins.js +324 -0
- package/dist/builtins/financial-builtins.js.map +1 -0
- package/dist/builtins/financial.d.ts +11 -0
- package/dist/builtins/financial.js +241 -0
- package/dist/builtins/financial.js.map +1 -0
- package/dist/builtins/fixed-income-builtins.d.ts +14 -0
- package/dist/builtins/fixed-income-builtins.js +598 -0
- package/dist/builtins/fixed-income-builtins.js.map +1 -0
- package/dist/builtins/fixed-income.d.ts +42 -0
- package/dist/builtins/fixed-income.js +668 -0
- package/dist/builtins/fixed-income.js.map +1 -0
- package/dist/builtins/formatting.d.ts +8 -0
- package/dist/builtins/formatting.js +53 -0
- package/dist/builtins/formatting.js.map +1 -0
- package/dist/builtins/logical.d.ts +4 -0
- package/dist/builtins/logical.js +258 -0
- package/dist/builtins/logical.js.map +1 -0
- package/dist/builtins/lookup-array-shape-builtins.d.ts +21 -0
- package/dist/builtins/lookup-array-shape-builtins.js +517 -0
- package/dist/builtins/lookup-array-shape-builtins.js.map +1 -0
- package/dist/builtins/lookup-criteria-builtins.d.ts +16 -0
- package/dist/builtins/lookup-criteria-builtins.js +216 -0
- package/dist/builtins/lookup-criteria-builtins.js.map +1 -0
- package/dist/builtins/lookup-database-builtins.d.ts +17 -0
- package/dist/builtins/lookup-database-builtins.js +294 -0
- package/dist/builtins/lookup-database-builtins.js.map +1 -0
- package/dist/builtins/lookup-financial-builtins.d.ts +11 -0
- package/dist/builtins/lookup-financial-builtins.js +291 -0
- package/dist/builtins/lookup-financial-builtins.js.map +1 -0
- package/dist/builtins/lookup-hypothesis-builtins.d.ts +11 -0
- package/dist/builtins/lookup-hypothesis-builtins.js +57 -0
- package/dist/builtins/lookup-hypothesis-builtins.js.map +1 -0
- package/dist/builtins/lookup-matrix-builtins.d.ts +17 -0
- package/dist/builtins/lookup-matrix-builtins.js +218 -0
- package/dist/builtins/lookup-matrix-builtins.js.map +1 -0
- package/dist/builtins/lookup-order-statistics-builtins.d.ts +18 -0
- package/dist/builtins/lookup-order-statistics-builtins.js +575 -0
- package/dist/builtins/lookup-order-statistics-builtins.js.map +1 -0
- package/dist/builtins/lookup-reference-builtins.d.ts +18 -0
- package/dist/builtins/lookup-reference-builtins.js +300 -0
- package/dist/builtins/lookup-reference-builtins.js.map +1 -0
- package/dist/builtins/lookup-regression-builtins.d.ts +12 -0
- package/dist/builtins/lookup-regression-builtins.js +511 -0
- package/dist/builtins/lookup-regression-builtins.js.map +1 -0
- package/dist/builtins/lookup-sort-filter-builtins.d.ts +20 -0
- package/dist/builtins/lookup-sort-filter-builtins.js +382 -0
- package/dist/builtins/lookup-sort-filter-builtins.js.map +1 -0
- package/dist/builtins/lookup.d.ts +13 -0
- package/dist/builtins/lookup.js +867 -0
- package/dist/builtins/lookup.js.map +1 -0
- package/dist/builtins/math-builtins.d.ts +31 -0
- package/dist/builtins/math-builtins.js +420 -0
- package/dist/builtins/math-builtins.js.map +1 -0
- package/dist/builtins/numeric.d.ts +30 -0
- package/dist/builtins/numeric.js +150 -0
- package/dist/builtins/numeric.js.map +1 -0
- package/dist/builtins/placeholder.d.ts +9 -0
- package/dist/builtins/placeholder.js +540 -0
- package/dist/builtins/placeholder.js.map +1 -0
- package/dist/builtins/radix.d.ts +12 -0
- package/dist/builtins/radix.js +220 -0
- package/dist/builtins/radix.js.map +1 -0
- package/dist/builtins/statistical-builtins.d.ts +13 -0
- package/dist/builtins/statistical-builtins.js +240 -0
- package/dist/builtins/statistical-builtins.js.map +1 -0
- package/dist/builtins/statistics.d.ts +8 -0
- package/dist/builtins/statistics.js +74 -0
- package/dist/builtins/statistics.js.map +1 -0
- package/dist/builtins/text.d.ts +5 -0
- package/dist/builtins/text.js +1879 -0
- package/dist/builtins/text.js.map +1 -0
- package/dist/builtins.d.ts +8 -0
- package/dist/builtins.js +695 -0
- package/dist/builtins.js.map +1 -0
- package/dist/compatibility.d.ts +25 -0
- package/dist/compatibility.js +498 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/compiler.d.ts +29 -0
- package/dist/compiler.js +474 -0
- package/dist/compiler.js.map +1 -0
- package/dist/external-function-adapter.d.ts +32 -0
- package/dist/external-function-adapter.js +42 -0
- package/dist/external-function-adapter.js.map +1 -0
- package/dist/generated/formula-inventory.d.ts +6839 -0
- package/dist/generated/formula-inventory.js +7368 -0
- package/dist/generated/formula-inventory.js.map +1 -0
- package/dist/group-pivot-evaluator.d.ts +28 -0
- package/dist/group-pivot-evaluator.js +435 -0
- package/dist/group-pivot-evaluator.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/js-evaluator.d.ts +107 -0
- package/dist/js-evaluator.js +1651 -0
- package/dist/js-evaluator.js.map +1 -0
- package/dist/lexer.d.ts +6 -0
- package/dist/lexer.js +115 -0
- package/dist/lexer.js.map +1 -0
- package/dist/optimizer.d.ts +2 -0
- package/dist/optimizer.js +353 -0
- package/dist/optimizer.js.map +1 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +352 -0
- package/dist/parser.js.map +1 -0
- package/dist/program-arena.d.ts +22 -0
- package/dist/program-arena.js +67 -0
- package/dist/program-arena.js.map +1 -0
- package/dist/runtime-values.d.ts +17 -0
- package/dist/runtime-values.js +11 -0
- package/dist/runtime-values.js.map +1 -0
- package/dist/special-call-rewrites.d.ts +2 -0
- package/dist/special-call-rewrites.js +74 -0
- package/dist/special-call-rewrites.js.map +1 -0
- package/dist/translation.d.ts +28 -0
- package/dist/translation.js +569 -0
- package/dist/translation.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
import { ErrorCode, ValueTag, formatErrorCode } from "@bilig/protocol";
|
|
2
|
+
import { indexToColumn, parseCellAddress, parseRangeAddress } from "./addressing.js";
|
|
3
|
+
import { getBuiltin, hasBuiltin } from "./builtins.js";
|
|
4
|
+
import { getLookupBuiltin } from "./builtins/lookup.js";
|
|
5
|
+
import { evaluateGroupBy, evaluatePivotBy } from "./group-pivot-evaluator.js";
|
|
6
|
+
import { isArrayValue, scalarFromEvaluationResult, } from "./runtime-values.js";
|
|
7
|
+
import { rewriteSpecialCall } from "./special-call-rewrites.js";
|
|
8
|
+
function emptyValue() {
|
|
9
|
+
return { tag: ValueTag.Empty };
|
|
10
|
+
}
|
|
11
|
+
function error(code) {
|
|
12
|
+
return { tag: ValueTag.Error, code };
|
|
13
|
+
}
|
|
14
|
+
function numberValue(value) {
|
|
15
|
+
return { tag: ValueTag.Number, value };
|
|
16
|
+
}
|
|
17
|
+
function stringValue(value) {
|
|
18
|
+
return { tag: ValueTag.String, value, stringId: 0 };
|
|
19
|
+
}
|
|
20
|
+
function toNumber(value) {
|
|
21
|
+
switch (value.tag) {
|
|
22
|
+
case ValueTag.Number:
|
|
23
|
+
return value.value;
|
|
24
|
+
case ValueTag.Boolean:
|
|
25
|
+
return value.value ? 1 : 0;
|
|
26
|
+
case ValueTag.Empty:
|
|
27
|
+
return 0;
|
|
28
|
+
case ValueTag.String:
|
|
29
|
+
case ValueTag.Error:
|
|
30
|
+
return undefined;
|
|
31
|
+
default:
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function toStringValue(value) {
|
|
36
|
+
switch (value.tag) {
|
|
37
|
+
case ValueTag.Empty:
|
|
38
|
+
return "";
|
|
39
|
+
case ValueTag.Number:
|
|
40
|
+
return String(value.value);
|
|
41
|
+
case ValueTag.Boolean:
|
|
42
|
+
return value.value ? "TRUE" : "FALSE";
|
|
43
|
+
case ValueTag.String:
|
|
44
|
+
return value.value;
|
|
45
|
+
case ValueTag.Error:
|
|
46
|
+
return formatErrorCode(value.code);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isTextLike(value) {
|
|
50
|
+
return value.tag === ValueTag.String || value.tag === ValueTag.Empty;
|
|
51
|
+
}
|
|
52
|
+
function compareText(left, right) {
|
|
53
|
+
const normalizedLeft = left.toUpperCase();
|
|
54
|
+
const normalizedRight = right.toUpperCase();
|
|
55
|
+
if (normalizedLeft === normalizedRight) {
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
return normalizedLeft < normalizedRight ? -1 : 1;
|
|
59
|
+
}
|
|
60
|
+
function compareScalars(left, right) {
|
|
61
|
+
if (isTextLike(left) && isTextLike(right)) {
|
|
62
|
+
return compareText(toStringValue(left), toStringValue(right));
|
|
63
|
+
}
|
|
64
|
+
const leftNum = toNumber(left);
|
|
65
|
+
const rightNum = toNumber(right);
|
|
66
|
+
if (leftNum === undefined || rightNum === undefined) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
if (leftNum === rightNum) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
return leftNum < rightNum ? -1 : 1;
|
|
73
|
+
}
|
|
74
|
+
function truthy(value) {
|
|
75
|
+
return (toNumber(value) ?? 0) !== 0;
|
|
76
|
+
}
|
|
77
|
+
function popScalar(stack) {
|
|
78
|
+
const value = stack.pop();
|
|
79
|
+
if (!value) {
|
|
80
|
+
return error(ErrorCode.Value);
|
|
81
|
+
}
|
|
82
|
+
if (value.kind === "scalar") {
|
|
83
|
+
return value.value;
|
|
84
|
+
}
|
|
85
|
+
if (value.kind === "omitted") {
|
|
86
|
+
return error(ErrorCode.Value);
|
|
87
|
+
}
|
|
88
|
+
if (value.kind === "lambda") {
|
|
89
|
+
return error(ErrorCode.Value);
|
|
90
|
+
}
|
|
91
|
+
return value.values[0] ?? emptyValue();
|
|
92
|
+
}
|
|
93
|
+
function popArgument(stack) {
|
|
94
|
+
return stack.pop() ?? { kind: "scalar", value: error(ErrorCode.Value) };
|
|
95
|
+
}
|
|
96
|
+
function toEvaluationResult(value) {
|
|
97
|
+
if (!value) {
|
|
98
|
+
return error(ErrorCode.Value);
|
|
99
|
+
}
|
|
100
|
+
if (value.kind === "scalar") {
|
|
101
|
+
return value.value;
|
|
102
|
+
}
|
|
103
|
+
if (value.kind === "omitted") {
|
|
104
|
+
return error(ErrorCode.Value);
|
|
105
|
+
}
|
|
106
|
+
if (value.kind === "lambda") {
|
|
107
|
+
return error(ErrorCode.Value);
|
|
108
|
+
}
|
|
109
|
+
if (value.kind === "range") {
|
|
110
|
+
return {
|
|
111
|
+
kind: "array",
|
|
112
|
+
rows: value.rows,
|
|
113
|
+
cols: value.cols,
|
|
114
|
+
values: value.values,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
function cloneStackValue(value) {
|
|
120
|
+
if (value.kind === "scalar") {
|
|
121
|
+
return { kind: "scalar", value: value.value };
|
|
122
|
+
}
|
|
123
|
+
if (value.kind === "omitted") {
|
|
124
|
+
return { kind: "omitted" };
|
|
125
|
+
}
|
|
126
|
+
if (value.kind === "range") {
|
|
127
|
+
return {
|
|
128
|
+
kind: "range",
|
|
129
|
+
values: value.values,
|
|
130
|
+
refKind: value.refKind,
|
|
131
|
+
rows: value.rows,
|
|
132
|
+
cols: value.cols,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (value.kind === "lambda") {
|
|
136
|
+
return {
|
|
137
|
+
kind: "lambda",
|
|
138
|
+
params: [...value.params],
|
|
139
|
+
body: value.body,
|
|
140
|
+
scopes: cloneScopes(value.scopes),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { kind: "array", values: value.values, rows: value.rows, cols: value.cols };
|
|
144
|
+
}
|
|
145
|
+
function cloneScopes(scopes) {
|
|
146
|
+
return scopes.map((scope) => new Map([...scope.entries()].map(([name, value]) => [name, cloneStackValue(value)])));
|
|
147
|
+
}
|
|
148
|
+
function toRangeLike(value) {
|
|
149
|
+
if (value.kind === "omitted") {
|
|
150
|
+
return { kind: "range", values: [error(ErrorCode.Value)], rows: 1, cols: 1, refKind: "cells" };
|
|
151
|
+
}
|
|
152
|
+
if (value.kind === "lambda") {
|
|
153
|
+
return { kind: "range", values: [error(ErrorCode.Value)], rows: 1, cols: 1, refKind: "cells" };
|
|
154
|
+
}
|
|
155
|
+
if (value.kind === "range") {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
if (value.kind === "array") {
|
|
159
|
+
return {
|
|
160
|
+
kind: "range",
|
|
161
|
+
values: value.values,
|
|
162
|
+
rows: value.rows,
|
|
163
|
+
cols: value.cols,
|
|
164
|
+
refKind: "cells",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return { kind: "range", values: [value.value], rows: 1, cols: 1, refKind: "cells" };
|
|
168
|
+
}
|
|
169
|
+
function scalarBinary(operator, leftValue, rightValue) {
|
|
170
|
+
if (leftValue.tag === ValueTag.Error) {
|
|
171
|
+
return leftValue;
|
|
172
|
+
}
|
|
173
|
+
if (rightValue.tag === ValueTag.Error) {
|
|
174
|
+
return rightValue;
|
|
175
|
+
}
|
|
176
|
+
if (operator === "&") {
|
|
177
|
+
return {
|
|
178
|
+
tag: ValueTag.String,
|
|
179
|
+
value: `${toStringValue(leftValue)}${toStringValue(rightValue)}`,
|
|
180
|
+
stringId: 0,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (["+", "-", "*", "/", "^"].includes(operator)) {
|
|
184
|
+
const left = toNumber(leftValue);
|
|
185
|
+
const right = toNumber(rightValue);
|
|
186
|
+
if (left === undefined || right === undefined) {
|
|
187
|
+
return error(ErrorCode.Value);
|
|
188
|
+
}
|
|
189
|
+
if (operator === "/" && right === 0) {
|
|
190
|
+
return error(ErrorCode.Div0);
|
|
191
|
+
}
|
|
192
|
+
const value = operator === "+"
|
|
193
|
+
? left + right
|
|
194
|
+
: operator === "-"
|
|
195
|
+
? left - right
|
|
196
|
+
: operator === "*"
|
|
197
|
+
? left * right
|
|
198
|
+
: operator === "/"
|
|
199
|
+
? left / right
|
|
200
|
+
: left ** right;
|
|
201
|
+
return { tag: ValueTag.Number, value };
|
|
202
|
+
}
|
|
203
|
+
const comparison = compareScalars(leftValue, rightValue);
|
|
204
|
+
if (comparison === undefined) {
|
|
205
|
+
return error(ErrorCode.Value);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
tag: ValueTag.Boolean,
|
|
209
|
+
value: operator === "="
|
|
210
|
+
? comparison === 0
|
|
211
|
+
: operator === "<>"
|
|
212
|
+
? comparison !== 0
|
|
213
|
+
: operator === ">"
|
|
214
|
+
? comparison > 0
|
|
215
|
+
: operator === ">="
|
|
216
|
+
? comparison >= 0
|
|
217
|
+
: operator === "<"
|
|
218
|
+
? comparison < 0
|
|
219
|
+
: comparison <= 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function evaluateBinary(operator, leftValue, rightValue) {
|
|
223
|
+
if (leftValue.kind === "scalar" && rightValue.kind === "scalar") {
|
|
224
|
+
return scalarBinary(operator, leftValue.value, rightValue.value);
|
|
225
|
+
}
|
|
226
|
+
const leftRange = toRangeLike(leftValue);
|
|
227
|
+
const rightRange = toRangeLike(rightValue);
|
|
228
|
+
const rows = leftRange.rows === rightRange.rows
|
|
229
|
+
? leftRange.rows
|
|
230
|
+
: leftRange.rows === 1
|
|
231
|
+
? rightRange.rows
|
|
232
|
+
: rightRange.rows === 1
|
|
233
|
+
? leftRange.rows
|
|
234
|
+
: 0;
|
|
235
|
+
const cols = leftRange.cols === rightRange.cols
|
|
236
|
+
? leftRange.cols
|
|
237
|
+
: leftRange.cols === 1
|
|
238
|
+
? rightRange.cols
|
|
239
|
+
: rightRange.cols === 1
|
|
240
|
+
? leftRange.cols
|
|
241
|
+
: 0;
|
|
242
|
+
if (rows === 0 || cols === 0) {
|
|
243
|
+
return error(ErrorCode.Value);
|
|
244
|
+
}
|
|
245
|
+
const values = [];
|
|
246
|
+
for (let row = 0; row < rows; row += 1) {
|
|
247
|
+
for (let col = 0; col < cols; col += 1) {
|
|
248
|
+
const leftIndex = Math.min(row, leftRange.rows - 1) * leftRange.cols + Math.min(col, leftRange.cols - 1);
|
|
249
|
+
const rightIndex = Math.min(row, rightRange.rows - 1) * rightRange.cols + Math.min(col, rightRange.cols - 1);
|
|
250
|
+
values.push(scalarBinary(operator, leftRange.values[leftIndex] ?? emptyValue(), rightRange.values[rightIndex] ?? emptyValue()));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return rows === 1 && cols === 1
|
|
254
|
+
? (values[0] ?? emptyValue())
|
|
255
|
+
: { kind: "array", values, rows, cols };
|
|
256
|
+
}
|
|
257
|
+
function stackScalar(value) {
|
|
258
|
+
return { kind: "scalar", value };
|
|
259
|
+
}
|
|
260
|
+
function normalizeScopeName(name) {
|
|
261
|
+
return name.toUpperCase();
|
|
262
|
+
}
|
|
263
|
+
function isSingleCellValue(value) {
|
|
264
|
+
if (value.kind === "scalar") {
|
|
265
|
+
return value.value;
|
|
266
|
+
}
|
|
267
|
+
if (value.kind === "omitted") {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
if (value.kind === "lambda") {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
return value.rows * value.cols === 1 ? (value.values[0] ?? emptyValue()) : undefined;
|
|
274
|
+
}
|
|
275
|
+
function toRangeArgument(value) {
|
|
276
|
+
if (value.kind === "scalar") {
|
|
277
|
+
return value.value;
|
|
278
|
+
}
|
|
279
|
+
if (value.kind === "omitted") {
|
|
280
|
+
return error(ErrorCode.Value);
|
|
281
|
+
}
|
|
282
|
+
if (value.kind === "lambda") {
|
|
283
|
+
return error(ErrorCode.Value);
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
kind: "range",
|
|
287
|
+
values: value.values,
|
|
288
|
+
refKind: value.kind === "range" ? value.refKind : "cells",
|
|
289
|
+
rows: value.rows,
|
|
290
|
+
cols: value.cols,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function toPositiveInteger(value) {
|
|
294
|
+
if (value === undefined) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const scalar = isSingleCellValue(value);
|
|
298
|
+
const numeric = scalar ? toNumber(scalar) : undefined;
|
|
299
|
+
if (numeric === undefined || !Number.isFinite(numeric)) {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
const integer = Math.trunc(numeric);
|
|
303
|
+
return integer >= 1 ? integer : undefined;
|
|
304
|
+
}
|
|
305
|
+
function referenceOperandFromNode(node) {
|
|
306
|
+
switch (node.kind) {
|
|
307
|
+
case "CellRef":
|
|
308
|
+
return {
|
|
309
|
+
kind: "cell",
|
|
310
|
+
...(node.sheetName === undefined ? {} : { sheetName: node.sheetName }),
|
|
311
|
+
address: node.ref,
|
|
312
|
+
};
|
|
313
|
+
case "RangeRef":
|
|
314
|
+
return {
|
|
315
|
+
kind: "range",
|
|
316
|
+
...(node.sheetName === undefined ? {} : { sheetName: node.sheetName }),
|
|
317
|
+
start: node.start,
|
|
318
|
+
end: node.end,
|
|
319
|
+
refKind: node.refKind,
|
|
320
|
+
};
|
|
321
|
+
case "RowRef":
|
|
322
|
+
return {
|
|
323
|
+
kind: "row",
|
|
324
|
+
...(node.sheetName === undefined ? {} : { sheetName: node.sheetName }),
|
|
325
|
+
address: node.ref,
|
|
326
|
+
};
|
|
327
|
+
case "ColumnRef":
|
|
328
|
+
return {
|
|
329
|
+
kind: "col",
|
|
330
|
+
...(node.sheetName === undefined ? {} : { sheetName: node.sheetName }),
|
|
331
|
+
address: node.ref,
|
|
332
|
+
};
|
|
333
|
+
case "BinaryExpr":
|
|
334
|
+
case "BooleanLiteral":
|
|
335
|
+
case "CallExpr":
|
|
336
|
+
case "ErrorLiteral":
|
|
337
|
+
case "InvokeExpr":
|
|
338
|
+
case "NameRef":
|
|
339
|
+
case "NumberLiteral":
|
|
340
|
+
case "SpillRef":
|
|
341
|
+
case "StringLiteral":
|
|
342
|
+
case "StructuredRef":
|
|
343
|
+
case "UnaryExpr":
|
|
344
|
+
return undefined;
|
|
345
|
+
default:
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function currentCellReference(context) {
|
|
350
|
+
return context.currentAddress
|
|
351
|
+
? { kind: "cell", sheetName: context.sheetName, address: context.currentAddress }
|
|
352
|
+
: undefined;
|
|
353
|
+
}
|
|
354
|
+
function referenceSheetName(ref, context) {
|
|
355
|
+
return ref?.sheetName ?? context.sheetName;
|
|
356
|
+
}
|
|
357
|
+
function referenceTopLeftAddress(ref) {
|
|
358
|
+
if (!ref) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
switch (ref.kind) {
|
|
362
|
+
case "cell":
|
|
363
|
+
case "row":
|
|
364
|
+
case "col":
|
|
365
|
+
return ref.address;
|
|
366
|
+
case "range":
|
|
367
|
+
return ref.start;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function referenceRowNumber(ref, context) {
|
|
371
|
+
const target = ref ?? currentCellReference(context);
|
|
372
|
+
if (!target) {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
switch (target.kind) {
|
|
376
|
+
case "cell":
|
|
377
|
+
return parseCellAddress(target.address, referenceSheetName(target, context)).row + 1;
|
|
378
|
+
case "range":
|
|
379
|
+
if (target.refKind === "rows") {
|
|
380
|
+
return Number.parseInt(target.start, 10);
|
|
381
|
+
}
|
|
382
|
+
if (target.refKind === "cells") {
|
|
383
|
+
return parseCellAddress(target.start, referenceSheetName(target, context)).row + 1;
|
|
384
|
+
}
|
|
385
|
+
return undefined;
|
|
386
|
+
case "row":
|
|
387
|
+
return Number.parseInt(target.address, 10);
|
|
388
|
+
case "col":
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function referenceColumnNumber(ref, context) {
|
|
393
|
+
const target = ref ?? currentCellReference(context);
|
|
394
|
+
if (!target) {
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
switch (target.kind) {
|
|
398
|
+
case "cell":
|
|
399
|
+
return parseCellAddress(target.address, referenceSheetName(target, context)).col + 1;
|
|
400
|
+
case "range":
|
|
401
|
+
if (target.refKind === "cols") {
|
|
402
|
+
return parseCellAddress(`${target.start}1`, referenceSheetName(target, context)).col + 1;
|
|
403
|
+
}
|
|
404
|
+
if (target.refKind === "cells") {
|
|
405
|
+
return parseCellAddress(target.start, referenceSheetName(target, context)).col + 1;
|
|
406
|
+
}
|
|
407
|
+
return undefined;
|
|
408
|
+
case "row":
|
|
409
|
+
return undefined;
|
|
410
|
+
case "col":
|
|
411
|
+
return parseCellAddress(`${target.address}1`, referenceSheetName(target, context)).col + 1;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function absoluteAddress(ref, context) {
|
|
415
|
+
const row = referenceRowNumber(ref, context);
|
|
416
|
+
const col = referenceColumnNumber(ref, context);
|
|
417
|
+
return row === undefined || col === undefined ? undefined : `$${indexToColumn(col - 1)}$${row}`;
|
|
418
|
+
}
|
|
419
|
+
function cellTypeCode(value) {
|
|
420
|
+
switch (value.tag) {
|
|
421
|
+
case ValueTag.Empty:
|
|
422
|
+
return "b";
|
|
423
|
+
case ValueTag.String:
|
|
424
|
+
return "l";
|
|
425
|
+
case ValueTag.Number:
|
|
426
|
+
case ValueTag.Boolean:
|
|
427
|
+
case ValueTag.Error:
|
|
428
|
+
return "v";
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function sheetNames(context) {
|
|
432
|
+
return context.listSheetNames?.() ?? [context.sheetName];
|
|
433
|
+
}
|
|
434
|
+
function sheetIndexByName(name, context) {
|
|
435
|
+
const index = sheetNames(context).findIndex((sheetName) => sheetName.trim().toUpperCase() === name.trim().toUpperCase());
|
|
436
|
+
return index === -1 ? undefined : index + 1;
|
|
437
|
+
}
|
|
438
|
+
function getRangeCell(range, row, col) {
|
|
439
|
+
return range.values[row * range.cols + col] ?? emptyValue();
|
|
440
|
+
}
|
|
441
|
+
function getBroadcastShape(values) {
|
|
442
|
+
const ranges = values.map(toRangeLike);
|
|
443
|
+
const rows = Math.max(...ranges.map((range) => range.rows));
|
|
444
|
+
const cols = Math.max(...ranges.map((range) => range.cols));
|
|
445
|
+
const compatible = ranges.every((range) => (range.rows === rows || range.rows === 1) && (range.cols === cols || range.cols === 1));
|
|
446
|
+
return compatible ? { rows, cols } : undefined;
|
|
447
|
+
}
|
|
448
|
+
function coerceScalarTextArgument(value) {
|
|
449
|
+
if (value === undefined) {
|
|
450
|
+
return error(ErrorCode.Value);
|
|
451
|
+
}
|
|
452
|
+
const scalar = isSingleCellValue(value);
|
|
453
|
+
if (!scalar) {
|
|
454
|
+
return error(ErrorCode.Value);
|
|
455
|
+
}
|
|
456
|
+
if (scalar.tag === ValueTag.Error) {
|
|
457
|
+
return scalar;
|
|
458
|
+
}
|
|
459
|
+
return toStringValue(scalar);
|
|
460
|
+
}
|
|
461
|
+
function coerceOptionalBooleanArgument(value, fallback) {
|
|
462
|
+
if (value === undefined) {
|
|
463
|
+
return fallback;
|
|
464
|
+
}
|
|
465
|
+
const scalar = isSingleCellValue(value);
|
|
466
|
+
if (!scalar) {
|
|
467
|
+
return error(ErrorCode.Value);
|
|
468
|
+
}
|
|
469
|
+
if (scalar.tag === ValueTag.Error) {
|
|
470
|
+
return scalar;
|
|
471
|
+
}
|
|
472
|
+
if (scalar.tag === ValueTag.Boolean) {
|
|
473
|
+
return scalar.value;
|
|
474
|
+
}
|
|
475
|
+
const numeric = toNumber(scalar);
|
|
476
|
+
return numeric === undefined ? error(ErrorCode.Value) : numeric !== 0;
|
|
477
|
+
}
|
|
478
|
+
function coerceOptionalMatchModeArgument(value, fallback) {
|
|
479
|
+
if (value === undefined) {
|
|
480
|
+
return fallback;
|
|
481
|
+
}
|
|
482
|
+
const scalar = isSingleCellValue(value);
|
|
483
|
+
if (!scalar) {
|
|
484
|
+
return error(ErrorCode.Value);
|
|
485
|
+
}
|
|
486
|
+
if (scalar.tag === ValueTag.Error) {
|
|
487
|
+
return scalar;
|
|
488
|
+
}
|
|
489
|
+
const numeric = toNumber(scalar);
|
|
490
|
+
if (numeric === undefined || !Number.isFinite(numeric)) {
|
|
491
|
+
return error(ErrorCode.Value);
|
|
492
|
+
}
|
|
493
|
+
const integer = Math.trunc(numeric);
|
|
494
|
+
return integer === 0 || integer === 1 ? integer : error(ErrorCode.Value);
|
|
495
|
+
}
|
|
496
|
+
function coerceOptionalPositiveIntegerArgument(value, fallback) {
|
|
497
|
+
if (value === undefined) {
|
|
498
|
+
return fallback;
|
|
499
|
+
}
|
|
500
|
+
const scalar = isSingleCellValue(value);
|
|
501
|
+
if (!scalar) {
|
|
502
|
+
return error(ErrorCode.Value);
|
|
503
|
+
}
|
|
504
|
+
if (scalar.tag === ValueTag.Error) {
|
|
505
|
+
return scalar;
|
|
506
|
+
}
|
|
507
|
+
const numeric = toNumber(scalar);
|
|
508
|
+
if (numeric === undefined || !Number.isFinite(numeric)) {
|
|
509
|
+
return error(ErrorCode.Value);
|
|
510
|
+
}
|
|
511
|
+
const integer = Math.trunc(numeric);
|
|
512
|
+
return integer >= 1 ? integer : error(ErrorCode.Value);
|
|
513
|
+
}
|
|
514
|
+
function coerceOptionalTrimModeArgument(value, fallback) {
|
|
515
|
+
if (value === undefined) {
|
|
516
|
+
return fallback;
|
|
517
|
+
}
|
|
518
|
+
const scalar = isSingleCellValue(value);
|
|
519
|
+
if (!scalar) {
|
|
520
|
+
return error(ErrorCode.Value);
|
|
521
|
+
}
|
|
522
|
+
if (scalar.tag === ValueTag.Error) {
|
|
523
|
+
return scalar;
|
|
524
|
+
}
|
|
525
|
+
const numeric = toNumber(scalar);
|
|
526
|
+
if (numeric === undefined || !Number.isFinite(numeric)) {
|
|
527
|
+
return error(ErrorCode.Value);
|
|
528
|
+
}
|
|
529
|
+
const integer = Math.trunc(numeric);
|
|
530
|
+
switch (integer) {
|
|
531
|
+
case 0:
|
|
532
|
+
case 1:
|
|
533
|
+
case 2:
|
|
534
|
+
case 3:
|
|
535
|
+
return integer;
|
|
536
|
+
default:
|
|
537
|
+
return error(ErrorCode.Value);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function isCellValueError(value) {
|
|
541
|
+
return typeof value === "object" && value !== null && "tag" in value;
|
|
542
|
+
}
|
|
543
|
+
function indexOfWithMatchMode(text, delimiter, startIndex, matchMode) {
|
|
544
|
+
if (matchMode === 1) {
|
|
545
|
+
return text.toLowerCase().indexOf(delimiter.toLowerCase(), startIndex);
|
|
546
|
+
}
|
|
547
|
+
return text.indexOf(delimiter, startIndex);
|
|
548
|
+
}
|
|
549
|
+
function splitTextByDelimiter(text, delimiter, matchMode) {
|
|
550
|
+
if (delimiter === "") {
|
|
551
|
+
return [text];
|
|
552
|
+
}
|
|
553
|
+
const parts = [];
|
|
554
|
+
let cursor = 0;
|
|
555
|
+
while (cursor <= text.length) {
|
|
556
|
+
const found = indexOfWithMatchMode(text, delimiter, cursor, matchMode);
|
|
557
|
+
if (found === -1) {
|
|
558
|
+
parts.push(text.slice(cursor));
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
parts.push(text.slice(cursor, found));
|
|
562
|
+
cursor = found + delimiter.length;
|
|
563
|
+
}
|
|
564
|
+
return parts;
|
|
565
|
+
}
|
|
566
|
+
function makeArrayStack(rows, cols, values) {
|
|
567
|
+
return { kind: "array", rows, cols, values };
|
|
568
|
+
}
|
|
569
|
+
function matrixFromStackValue(value) {
|
|
570
|
+
if (value.kind === "omitted" || value.kind === "lambda") {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
if (value.kind === "scalar") {
|
|
574
|
+
return { rows: 1, cols: 1, values: [value.value] };
|
|
575
|
+
}
|
|
576
|
+
return { rows: value.rows, cols: value.cols, values: value.values };
|
|
577
|
+
}
|
|
578
|
+
function scalarIntegerArgument(value) {
|
|
579
|
+
const scalar = value ? isSingleCellValue(value) : undefined;
|
|
580
|
+
const numeric = scalar ? toNumber(scalar) : undefined;
|
|
581
|
+
return numeric === undefined || !Number.isFinite(numeric) ? undefined : Math.trunc(numeric);
|
|
582
|
+
}
|
|
583
|
+
function vectorIntegerArgument(value) {
|
|
584
|
+
if (!value) {
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
const matrix = matrixFromStackValue(value);
|
|
588
|
+
if (!matrix || !(matrix.rows === 1 || matrix.cols === 1)) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
const values = [];
|
|
592
|
+
for (let index = 0; index < matrix.rows * matrix.cols; index += 1) {
|
|
593
|
+
const numeric = toNumber(matrix.values[index] ?? emptyValue());
|
|
594
|
+
if (numeric === undefined || !Number.isFinite(numeric)) {
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
values.push(Math.trunc(numeric));
|
|
598
|
+
}
|
|
599
|
+
return values;
|
|
600
|
+
}
|
|
601
|
+
function aggregateRangeSubset(functionArg, subset, context, totalSet) {
|
|
602
|
+
if (functionArg.kind === "lambda") {
|
|
603
|
+
const args = [makeArrayStack(Math.max(subset.length, 1), 1, [...subset])];
|
|
604
|
+
if (functionArg.params.length >= 2) {
|
|
605
|
+
args.push(makeArrayStack(Math.max(totalSet?.length ?? 0, 1), 1, [...(totalSet ?? [emptyValue()])]));
|
|
606
|
+
}
|
|
607
|
+
const result = applyLambda(functionArg, args, context);
|
|
608
|
+
return isSingleCellValue(result) ?? error(ErrorCode.Value);
|
|
609
|
+
}
|
|
610
|
+
const scalar = isSingleCellValue(functionArg);
|
|
611
|
+
if (scalar?.tag !== ValueTag.String) {
|
|
612
|
+
return scalar?.tag === ValueTag.Error ? scalar : error(ErrorCode.Value);
|
|
613
|
+
}
|
|
614
|
+
const name = scalar.value.trim().toUpperCase();
|
|
615
|
+
if (subset.length === 0) {
|
|
616
|
+
if (name === "SUM" || name === "COUNT" || name === "COUNTA") {
|
|
617
|
+
return numberValue(0);
|
|
618
|
+
}
|
|
619
|
+
if (name === "AVERAGE" || name === "AVG") {
|
|
620
|
+
return error(ErrorCode.Div0);
|
|
621
|
+
}
|
|
622
|
+
return numberValue(0);
|
|
623
|
+
}
|
|
624
|
+
const builtin = context.resolveBuiltin?.(name) ?? getBuiltin(name);
|
|
625
|
+
if (!builtin) {
|
|
626
|
+
return error(ErrorCode.Name);
|
|
627
|
+
}
|
|
628
|
+
const result = builtin(...subset);
|
|
629
|
+
return isArrayValue(result) ? scalarFromEvaluationResult(result) : result;
|
|
630
|
+
}
|
|
631
|
+
function isTrimRangeEmptyCell(value) {
|
|
632
|
+
return value.tag === ValueTag.Empty;
|
|
633
|
+
}
|
|
634
|
+
function applyLambda(lambdaValue, args, context) {
|
|
635
|
+
if (lambdaValue.kind !== "lambda") {
|
|
636
|
+
return stackScalar(lambdaValue.kind === "scalar" && lambdaValue.value.tag === ValueTag.Error
|
|
637
|
+
? lambdaValue.value
|
|
638
|
+
: error(ErrorCode.Value));
|
|
639
|
+
}
|
|
640
|
+
if (args.length > lambdaValue.params.length) {
|
|
641
|
+
return stackScalar(error(ErrorCode.Value));
|
|
642
|
+
}
|
|
643
|
+
const parameterScope = new Map();
|
|
644
|
+
lambdaValue.params.forEach((name, index) => {
|
|
645
|
+
parameterScope.set(normalizeScopeName(name), index < args.length ? cloneStackValue(args[index]) : { kind: "omitted" });
|
|
646
|
+
});
|
|
647
|
+
return (executePlan(lambdaValue.body, context, [...cloneScopes(lambdaValue.scopes), parameterScope]) ??
|
|
648
|
+
stackScalar(error(ErrorCode.Value)));
|
|
649
|
+
}
|
|
650
|
+
function evaluateSpecialCall(callee, rawArgs, context, argRefs = []) {
|
|
651
|
+
switch (callee) {
|
|
652
|
+
case "ROW": {
|
|
653
|
+
if (rawArgs.length > 1) {
|
|
654
|
+
return stackScalar(error(ErrorCode.Value));
|
|
655
|
+
}
|
|
656
|
+
const row = referenceRowNumber(argRefs[0], context);
|
|
657
|
+
return stackScalar(row === undefined ? error(ErrorCode.Value) : numberValue(row));
|
|
658
|
+
}
|
|
659
|
+
case "COLUMN": {
|
|
660
|
+
if (rawArgs.length > 1) {
|
|
661
|
+
return stackScalar(error(ErrorCode.Value));
|
|
662
|
+
}
|
|
663
|
+
const column = referenceColumnNumber(argRefs[0], context);
|
|
664
|
+
return stackScalar(column === undefined ? error(ErrorCode.Value) : numberValue(column));
|
|
665
|
+
}
|
|
666
|
+
case "ISOMITTED": {
|
|
667
|
+
if (rawArgs.length !== 1) {
|
|
668
|
+
return stackScalar(error(ErrorCode.Value));
|
|
669
|
+
}
|
|
670
|
+
return stackScalar({ tag: ValueTag.Boolean, value: rawArgs[0]?.kind === "omitted" });
|
|
671
|
+
}
|
|
672
|
+
case "FORMULATEXT": {
|
|
673
|
+
if (rawArgs.length !== 1) {
|
|
674
|
+
return stackScalar(error(ErrorCode.Value));
|
|
675
|
+
}
|
|
676
|
+
const address = referenceTopLeftAddress(argRefs[0]);
|
|
677
|
+
const sheetName = referenceSheetName(argRefs[0], context);
|
|
678
|
+
if (!address || !sheetName) {
|
|
679
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
680
|
+
}
|
|
681
|
+
const formula = context.resolveFormula?.(sheetName, address);
|
|
682
|
+
return stackScalar(formula
|
|
683
|
+
? stringValue(formula.startsWith("=") ? formula : `=${formula}`)
|
|
684
|
+
: error(ErrorCode.NA));
|
|
685
|
+
}
|
|
686
|
+
case "FORMULA": {
|
|
687
|
+
if (rawArgs.length !== 1) {
|
|
688
|
+
return stackScalar(error(ErrorCode.Value));
|
|
689
|
+
}
|
|
690
|
+
const address = referenceTopLeftAddress(argRefs[0]);
|
|
691
|
+
const sheetName = referenceSheetName(argRefs[0], context);
|
|
692
|
+
if (!address || !sheetName) {
|
|
693
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
694
|
+
}
|
|
695
|
+
const formula = context.resolveFormula?.(sheetName, address);
|
|
696
|
+
return stackScalar(formula
|
|
697
|
+
? stringValue(formula.startsWith("=") ? formula : `=${formula}`)
|
|
698
|
+
: error(ErrorCode.NA));
|
|
699
|
+
}
|
|
700
|
+
case "PHONETIC": {
|
|
701
|
+
if (rawArgs.length !== 1) {
|
|
702
|
+
return stackScalar(error(ErrorCode.Value));
|
|
703
|
+
}
|
|
704
|
+
const target = rawArgs[0];
|
|
705
|
+
if (target.kind === "scalar") {
|
|
706
|
+
return stackScalar(stringValue(toStringValue(target.value)));
|
|
707
|
+
}
|
|
708
|
+
if (target.kind === "range") {
|
|
709
|
+
return stackScalar(stringValue(toStringValue(target.values[0] ?? emptyValue())));
|
|
710
|
+
}
|
|
711
|
+
return stackScalar(error(ErrorCode.Value));
|
|
712
|
+
}
|
|
713
|
+
case "GETPIVOTDATA": {
|
|
714
|
+
if (rawArgs.length < 2 || (rawArgs.length - 2) % 2 !== 0) {
|
|
715
|
+
return stackScalar(error(ErrorCode.Value));
|
|
716
|
+
}
|
|
717
|
+
const dataFieldValue = isSingleCellValue(rawArgs[0]);
|
|
718
|
+
const address = referenceTopLeftAddress(argRefs[1]);
|
|
719
|
+
const sheetName = referenceSheetName(argRefs[1], context);
|
|
720
|
+
if (!dataFieldValue) {
|
|
721
|
+
return stackScalar(error(ErrorCode.Value));
|
|
722
|
+
}
|
|
723
|
+
if (!address || !sheetName) {
|
|
724
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
725
|
+
}
|
|
726
|
+
const filters = [];
|
|
727
|
+
for (let index = 2; index < rawArgs.length; index += 2) {
|
|
728
|
+
const fieldValue = isSingleCellValue(rawArgs[index]);
|
|
729
|
+
const itemValue = isSingleCellValue(rawArgs[index + 1]);
|
|
730
|
+
if (!fieldValue || !itemValue) {
|
|
731
|
+
return stackScalar(error(ErrorCode.Value));
|
|
732
|
+
}
|
|
733
|
+
filters.push({ field: toStringValue(fieldValue), item: itemValue });
|
|
734
|
+
}
|
|
735
|
+
return stackScalar(context.resolvePivotData?.({
|
|
736
|
+
dataField: toStringValue(dataFieldValue),
|
|
737
|
+
sheetName,
|
|
738
|
+
address,
|
|
739
|
+
filters,
|
|
740
|
+
}) ?? error(ErrorCode.Ref));
|
|
741
|
+
}
|
|
742
|
+
case "GROUPBY": {
|
|
743
|
+
if (rawArgs.length < 3 || rawArgs.length > 8) {
|
|
744
|
+
return stackScalar(error(ErrorCode.Value));
|
|
745
|
+
}
|
|
746
|
+
const rowFields = matrixFromStackValue(rawArgs[0]);
|
|
747
|
+
const values = matrixFromStackValue(rawArgs[1]);
|
|
748
|
+
if (!rowFields || !values) {
|
|
749
|
+
return stackScalar(error(ErrorCode.Value));
|
|
750
|
+
}
|
|
751
|
+
const sortOrder = vectorIntegerArgument(rawArgs[5]) ??
|
|
752
|
+
(rawArgs[5] ? [scalarIntegerArgument(rawArgs[5]) ?? Number.NaN] : undefined);
|
|
753
|
+
const fieldHeadersMode = scalarIntegerArgument(rawArgs[3]);
|
|
754
|
+
const totalDepth = scalarIntegerArgument(rawArgs[4]);
|
|
755
|
+
const filterArray = rawArgs[6] ? matrixFromStackValue(rawArgs[6]) : undefined;
|
|
756
|
+
const fieldRelationship = scalarIntegerArgument(rawArgs[7]);
|
|
757
|
+
const groupByOptions = {
|
|
758
|
+
aggregate: (subset, totalSet) => aggregateRangeSubset(rawArgs[2], subset, context, totalSet),
|
|
759
|
+
...(fieldHeadersMode !== undefined ? { fieldHeadersMode } : {}),
|
|
760
|
+
...(totalDepth !== undefined ? { totalDepth } : {}),
|
|
761
|
+
...(sortOrder?.every(Number.isFinite) ? { sortOrder } : {}),
|
|
762
|
+
...(filterArray !== undefined ? { filterArray } : {}),
|
|
763
|
+
...(fieldRelationship !== undefined ? { fieldRelationship } : {}),
|
|
764
|
+
};
|
|
765
|
+
const result = evaluateGroupBy(rowFields, values, groupByOptions);
|
|
766
|
+
return isArrayValue(result) ? result : stackScalar(result);
|
|
767
|
+
}
|
|
768
|
+
case "PIVOTBY": {
|
|
769
|
+
if (rawArgs.length < 4 || rawArgs.length > 11) {
|
|
770
|
+
return stackScalar(error(ErrorCode.Value));
|
|
771
|
+
}
|
|
772
|
+
const rowFields = matrixFromStackValue(rawArgs[0]);
|
|
773
|
+
const colFields = matrixFromStackValue(rawArgs[1]);
|
|
774
|
+
const values = matrixFromStackValue(rawArgs[2]);
|
|
775
|
+
if (!rowFields || !colFields || !values) {
|
|
776
|
+
return stackScalar(error(ErrorCode.Value));
|
|
777
|
+
}
|
|
778
|
+
const rowSortOrder = vectorIntegerArgument(rawArgs[6]) ??
|
|
779
|
+
(rawArgs[6] ? [scalarIntegerArgument(rawArgs[6]) ?? Number.NaN] : undefined);
|
|
780
|
+
const colSortOrder = vectorIntegerArgument(rawArgs[8]) ??
|
|
781
|
+
(rawArgs[8] ? [scalarIntegerArgument(rawArgs[8]) ?? Number.NaN] : undefined);
|
|
782
|
+
const fieldHeadersMode = scalarIntegerArgument(rawArgs[4]);
|
|
783
|
+
const rowTotalDepth = scalarIntegerArgument(rawArgs[5]);
|
|
784
|
+
const colTotalDepth = scalarIntegerArgument(rawArgs[7]);
|
|
785
|
+
const filterArray = rawArgs[9] ? matrixFromStackValue(rawArgs[9]) : undefined;
|
|
786
|
+
const relativeTo = scalarIntegerArgument(rawArgs[10]);
|
|
787
|
+
const pivotByOptions = {
|
|
788
|
+
aggregate: (subset, totalSet) => aggregateRangeSubset(rawArgs[3], subset, context, totalSet),
|
|
789
|
+
...(fieldHeadersMode !== undefined ? { fieldHeadersMode } : {}),
|
|
790
|
+
...(rowTotalDepth !== undefined ? { rowTotalDepth } : {}),
|
|
791
|
+
...(rowSortOrder?.every(Number.isFinite) ? { rowSortOrder } : {}),
|
|
792
|
+
...(colTotalDepth !== undefined ? { colTotalDepth } : {}),
|
|
793
|
+
...(colSortOrder?.every(Number.isFinite) ? { colSortOrder } : {}),
|
|
794
|
+
...(filterArray !== undefined ? { filterArray } : {}),
|
|
795
|
+
...(relativeTo !== undefined ? { relativeTo } : {}),
|
|
796
|
+
};
|
|
797
|
+
const result = evaluatePivotBy(rowFields, colFields, values, pivotByOptions);
|
|
798
|
+
return isArrayValue(result) ? result : stackScalar(result);
|
|
799
|
+
}
|
|
800
|
+
case "MULTIPLE.OPERATIONS": {
|
|
801
|
+
if (rawArgs.length !== 3 && rawArgs.length !== 5) {
|
|
802
|
+
return stackScalar(error(ErrorCode.Value));
|
|
803
|
+
}
|
|
804
|
+
const formulaAddress = referenceTopLeftAddress(argRefs[0]);
|
|
805
|
+
const formulaSheetName = referenceSheetName(argRefs[0], context);
|
|
806
|
+
const rowCellAddress = referenceTopLeftAddress(argRefs[1]);
|
|
807
|
+
const rowCellSheetName = referenceSheetName(argRefs[1], context);
|
|
808
|
+
const rowReplacementAddress = referenceTopLeftAddress(argRefs[2]);
|
|
809
|
+
const rowReplacementSheetName = referenceSheetName(argRefs[2], context);
|
|
810
|
+
if (!formulaAddress ||
|
|
811
|
+
!formulaSheetName ||
|
|
812
|
+
!rowCellAddress ||
|
|
813
|
+
!rowCellSheetName ||
|
|
814
|
+
!rowReplacementAddress ||
|
|
815
|
+
!rowReplacementSheetName) {
|
|
816
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
817
|
+
}
|
|
818
|
+
const columnCellAddress = rawArgs.length === 5 ? referenceTopLeftAddress(argRefs[3]) : undefined;
|
|
819
|
+
const columnCellSheetName = rawArgs.length === 5 ? referenceSheetName(argRefs[3], context) : undefined;
|
|
820
|
+
const columnReplacementAddress = rawArgs.length === 5 ? referenceTopLeftAddress(argRefs[4]) : undefined;
|
|
821
|
+
const columnReplacementSheetName = rawArgs.length === 5 ? referenceSheetName(argRefs[4], context) : undefined;
|
|
822
|
+
if (rawArgs.length === 5 &&
|
|
823
|
+
(!columnCellAddress ||
|
|
824
|
+
!columnCellSheetName ||
|
|
825
|
+
!columnReplacementAddress ||
|
|
826
|
+
!columnReplacementSheetName)) {
|
|
827
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
828
|
+
}
|
|
829
|
+
const request = {
|
|
830
|
+
formulaSheetName,
|
|
831
|
+
formulaAddress,
|
|
832
|
+
rowCellSheetName,
|
|
833
|
+
rowCellAddress,
|
|
834
|
+
rowReplacementSheetName,
|
|
835
|
+
rowReplacementAddress,
|
|
836
|
+
...(columnCellSheetName ? { columnCellSheetName } : {}),
|
|
837
|
+
...(columnCellAddress ? { columnCellAddress } : {}),
|
|
838
|
+
...(columnReplacementSheetName ? { columnReplacementSheetName } : {}),
|
|
839
|
+
...(columnReplacementAddress ? { columnReplacementAddress } : {}),
|
|
840
|
+
};
|
|
841
|
+
return stackScalar(context.resolveMultipleOperations?.(request) ?? error(ErrorCode.Ref));
|
|
842
|
+
}
|
|
843
|
+
case "CHOOSE": {
|
|
844
|
+
if (rawArgs.length < 2) {
|
|
845
|
+
return stackScalar(error(ErrorCode.Value));
|
|
846
|
+
}
|
|
847
|
+
const indexValue = isSingleCellValue(rawArgs[0]);
|
|
848
|
+
const choice = indexValue ? toNumber(indexValue) : undefined;
|
|
849
|
+
if (choice === undefined || !Number.isFinite(choice)) {
|
|
850
|
+
return stackScalar(error(ErrorCode.Value));
|
|
851
|
+
}
|
|
852
|
+
const truncated = Math.trunc(choice);
|
|
853
|
+
if (truncated < 1 || truncated >= rawArgs.length) {
|
|
854
|
+
return stackScalar(error(ErrorCode.Value));
|
|
855
|
+
}
|
|
856
|
+
return cloneStackValue(rawArgs[truncated]);
|
|
857
|
+
}
|
|
858
|
+
case "SHEET": {
|
|
859
|
+
if (rawArgs.length > 1) {
|
|
860
|
+
return stackScalar(error(ErrorCode.Value));
|
|
861
|
+
}
|
|
862
|
+
if (rawArgs.length === 0) {
|
|
863
|
+
const index = sheetIndexByName(context.sheetName, context);
|
|
864
|
+
return stackScalar(index === undefined ? error(ErrorCode.NA) : numberValue(index));
|
|
865
|
+
}
|
|
866
|
+
if (argRefs[0]) {
|
|
867
|
+
const index = sheetIndexByName(referenceSheetName(argRefs[0], context) ?? context.sheetName, context);
|
|
868
|
+
return stackScalar(index === undefined ? error(ErrorCode.NA) : numberValue(index));
|
|
869
|
+
}
|
|
870
|
+
const scalar = isSingleCellValue(rawArgs[0]);
|
|
871
|
+
if (scalar?.tag !== ValueTag.String) {
|
|
872
|
+
return stackScalar(error(ErrorCode.NA));
|
|
873
|
+
}
|
|
874
|
+
const index = sheetIndexByName(scalar.value, context);
|
|
875
|
+
return stackScalar(index === undefined ? error(ErrorCode.NA) : numberValue(index));
|
|
876
|
+
}
|
|
877
|
+
case "SHEETS": {
|
|
878
|
+
if (rawArgs.length > 1) {
|
|
879
|
+
return stackScalar(error(ErrorCode.Value));
|
|
880
|
+
}
|
|
881
|
+
if (rawArgs.length === 0) {
|
|
882
|
+
return stackScalar(numberValue(sheetNames(context).length));
|
|
883
|
+
}
|
|
884
|
+
if (argRefs[0]) {
|
|
885
|
+
return stackScalar(numberValue(1));
|
|
886
|
+
}
|
|
887
|
+
const scalar = isSingleCellValue(rawArgs[0]);
|
|
888
|
+
if (scalar?.tag !== ValueTag.String) {
|
|
889
|
+
return stackScalar(error(ErrorCode.NA));
|
|
890
|
+
}
|
|
891
|
+
return stackScalar(sheetIndexByName(scalar.value, context) === undefined
|
|
892
|
+
? error(ErrorCode.NA)
|
|
893
|
+
: numberValue(1));
|
|
894
|
+
}
|
|
895
|
+
case "CELL": {
|
|
896
|
+
if (rawArgs.length < 1 || rawArgs.length > 2) {
|
|
897
|
+
return stackScalar(error(ErrorCode.Value));
|
|
898
|
+
}
|
|
899
|
+
const infoType = isSingleCellValue(rawArgs[0]);
|
|
900
|
+
if (infoType?.tag !== ValueTag.String) {
|
|
901
|
+
return stackScalar(error(ErrorCode.Value));
|
|
902
|
+
}
|
|
903
|
+
const ref = rawArgs.length === 2 ? argRefs[1] : currentCellReference(context);
|
|
904
|
+
if (!ref) {
|
|
905
|
+
return stackScalar(error(ErrorCode.Value));
|
|
906
|
+
}
|
|
907
|
+
const normalizedInfoType = infoType.value.trim().toLowerCase();
|
|
908
|
+
switch (normalizedInfoType) {
|
|
909
|
+
case "address": {
|
|
910
|
+
const address = absoluteAddress(ref, context);
|
|
911
|
+
return stackScalar(address ? stringValue(address) : error(ErrorCode.Value));
|
|
912
|
+
}
|
|
913
|
+
case "row": {
|
|
914
|
+
const row = referenceRowNumber(ref, context);
|
|
915
|
+
return stackScalar(row === undefined ? error(ErrorCode.Value) : numberValue(row));
|
|
916
|
+
}
|
|
917
|
+
case "col": {
|
|
918
|
+
const column = referenceColumnNumber(ref, context);
|
|
919
|
+
return stackScalar(column === undefined ? error(ErrorCode.Value) : numberValue(column));
|
|
920
|
+
}
|
|
921
|
+
case "contents": {
|
|
922
|
+
const address = referenceTopLeftAddress(ref);
|
|
923
|
+
const sheetName = referenceSheetName(ref, context);
|
|
924
|
+
if (!address || !sheetName) {
|
|
925
|
+
return stackScalar(error(ErrorCode.Value));
|
|
926
|
+
}
|
|
927
|
+
return stackScalar(context.resolveCell(sheetName, address));
|
|
928
|
+
}
|
|
929
|
+
case "type": {
|
|
930
|
+
const address = referenceTopLeftAddress(ref);
|
|
931
|
+
const sheetName = referenceSheetName(ref, context);
|
|
932
|
+
if (!address || !sheetName) {
|
|
933
|
+
return stackScalar(error(ErrorCode.Value));
|
|
934
|
+
}
|
|
935
|
+
return stackScalar(stringValue(cellTypeCode(context.resolveCell(sheetName, address))));
|
|
936
|
+
}
|
|
937
|
+
case "filename":
|
|
938
|
+
return stackScalar(stringValue(""));
|
|
939
|
+
default:
|
|
940
|
+
return stackScalar(error(ErrorCode.Value));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
case "INDIRECT": {
|
|
944
|
+
if (rawArgs.length < 1 || rawArgs.length > 2) {
|
|
945
|
+
return stackScalar(error(ErrorCode.Value));
|
|
946
|
+
}
|
|
947
|
+
const refText = coerceScalarTextArgument(rawArgs[0]);
|
|
948
|
+
if (isCellValueError(refText)) {
|
|
949
|
+
return stackScalar(refText);
|
|
950
|
+
}
|
|
951
|
+
const a1Mode = coerceOptionalBooleanArgument(rawArgs[1], true);
|
|
952
|
+
if (isCellValueError(a1Mode)) {
|
|
953
|
+
return stackScalar(a1Mode);
|
|
954
|
+
}
|
|
955
|
+
if (!a1Mode) {
|
|
956
|
+
return stackScalar(error(ErrorCode.Value));
|
|
957
|
+
}
|
|
958
|
+
const normalizedRefText = refText.trim();
|
|
959
|
+
if (normalizedRefText === "") {
|
|
960
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
961
|
+
}
|
|
962
|
+
try {
|
|
963
|
+
const cell = parseCellAddress(normalizedRefText, context.sheetName);
|
|
964
|
+
return stackScalar(context.resolveCell(cell.sheetName ?? context.sheetName, cell.text));
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
// fall through to range/name resolution
|
|
968
|
+
}
|
|
969
|
+
try {
|
|
970
|
+
const range = parseRangeAddress(normalizedRefText, context.sheetName);
|
|
971
|
+
if (range.kind !== "cells") {
|
|
972
|
+
return stackScalar(error(ErrorCode.Ref));
|
|
973
|
+
}
|
|
974
|
+
const targetSheetName = range.sheetName ?? context.sheetName;
|
|
975
|
+
const values = context.resolveRange(targetSheetName, range.start.text, range.end.text, "cells");
|
|
976
|
+
const rows = range.end.row - range.start.row + 1;
|
|
977
|
+
const cols = range.end.col - range.start.col + 1;
|
|
978
|
+
return {
|
|
979
|
+
kind: "range",
|
|
980
|
+
values,
|
|
981
|
+
refKind: "cells",
|
|
982
|
+
rows,
|
|
983
|
+
cols,
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
catch {
|
|
987
|
+
// fall through to name resolution
|
|
988
|
+
}
|
|
989
|
+
const resolvedName = context.resolveName?.(normalizedRefText);
|
|
990
|
+
return stackScalar(resolvedName ?? error(ErrorCode.Ref));
|
|
991
|
+
}
|
|
992
|
+
case "EXPAND": {
|
|
993
|
+
if (rawArgs.length < 2 || rawArgs.length > 4) {
|
|
994
|
+
return stackScalar(error(ErrorCode.Value));
|
|
995
|
+
}
|
|
996
|
+
const source = toRangeLike(rawArgs[0]);
|
|
997
|
+
const rows = coerceOptionalPositiveIntegerArgument(rawArgs[1], source.rows);
|
|
998
|
+
const cols = coerceOptionalPositiveIntegerArgument(rawArgs[2], source.cols);
|
|
999
|
+
if (isCellValueError(rows)) {
|
|
1000
|
+
return stackScalar(rows);
|
|
1001
|
+
}
|
|
1002
|
+
if (isCellValueError(cols)) {
|
|
1003
|
+
return stackScalar(cols);
|
|
1004
|
+
}
|
|
1005
|
+
const padArgument = rawArgs[3];
|
|
1006
|
+
const padValue = padArgument === undefined
|
|
1007
|
+
? error(ErrorCode.NA)
|
|
1008
|
+
: (() => {
|
|
1009
|
+
const scalar = isSingleCellValue(padArgument);
|
|
1010
|
+
return scalar ?? error(ErrorCode.Value);
|
|
1011
|
+
})();
|
|
1012
|
+
if (padValue.tag === ValueTag.Error && padArgument !== undefined) {
|
|
1013
|
+
const scalar = isSingleCellValue(padArgument);
|
|
1014
|
+
if (!scalar) {
|
|
1015
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (rows < source.rows || cols < source.cols) {
|
|
1019
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1020
|
+
}
|
|
1021
|
+
const values = [];
|
|
1022
|
+
for (let row = 0; row < rows; row += 1) {
|
|
1023
|
+
for (let col = 0; col < cols; col += 1) {
|
|
1024
|
+
values.push(row < source.rows && col < source.cols ? getRangeCell(source, row, col) : padValue);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return makeArrayStack(rows, cols, values);
|
|
1028
|
+
}
|
|
1029
|
+
case "TEXTSPLIT": {
|
|
1030
|
+
if (rawArgs.length < 2 || rawArgs.length > 6) {
|
|
1031
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1032
|
+
}
|
|
1033
|
+
const text = coerceScalarTextArgument(rawArgs[0]);
|
|
1034
|
+
const columnDelimiter = coerceScalarTextArgument(rawArgs[1]);
|
|
1035
|
+
const rowDelimiter = rawArgs[2] === undefined ? undefined : coerceScalarTextArgument(rawArgs[2]);
|
|
1036
|
+
const ignoreEmpty = coerceOptionalBooleanArgument(rawArgs[3], false);
|
|
1037
|
+
const matchMode = coerceOptionalMatchModeArgument(rawArgs[4], 0);
|
|
1038
|
+
if (isCellValueError(text)) {
|
|
1039
|
+
return stackScalar(text);
|
|
1040
|
+
}
|
|
1041
|
+
if (isCellValueError(columnDelimiter)) {
|
|
1042
|
+
return stackScalar(columnDelimiter);
|
|
1043
|
+
}
|
|
1044
|
+
if (rowDelimiter !== undefined && isCellValueError(rowDelimiter)) {
|
|
1045
|
+
return stackScalar(rowDelimiter);
|
|
1046
|
+
}
|
|
1047
|
+
if (isCellValueError(ignoreEmpty)) {
|
|
1048
|
+
return stackScalar(ignoreEmpty);
|
|
1049
|
+
}
|
|
1050
|
+
if (isCellValueError(matchMode)) {
|
|
1051
|
+
return stackScalar(matchMode);
|
|
1052
|
+
}
|
|
1053
|
+
if (columnDelimiter === "" && rowDelimiter === undefined) {
|
|
1054
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1055
|
+
}
|
|
1056
|
+
const padArgument = rawArgs[5];
|
|
1057
|
+
const padValue = padArgument === undefined
|
|
1058
|
+
? error(ErrorCode.NA)
|
|
1059
|
+
: (() => {
|
|
1060
|
+
const scalar = isSingleCellValue(padArgument);
|
|
1061
|
+
return scalar ?? error(ErrorCode.Value);
|
|
1062
|
+
})();
|
|
1063
|
+
if (padArgument !== undefined && !isSingleCellValue(padArgument)) {
|
|
1064
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1065
|
+
}
|
|
1066
|
+
const rowSlices = rowDelimiter === undefined || rowDelimiter === ""
|
|
1067
|
+
? [text]
|
|
1068
|
+
: splitTextByDelimiter(text, rowDelimiter, matchMode);
|
|
1069
|
+
const matrix = rowSlices.map((rowSlice) => {
|
|
1070
|
+
const parts = columnDelimiter === ""
|
|
1071
|
+
? [rowSlice]
|
|
1072
|
+
: splitTextByDelimiter(rowSlice, columnDelimiter, matchMode);
|
|
1073
|
+
const filtered = ignoreEmpty ? parts.filter((part) => part !== "") : parts;
|
|
1074
|
+
return filtered.length === 0 ? [] : filtered;
|
|
1075
|
+
});
|
|
1076
|
+
const rows = Math.max(matrix.length, 1);
|
|
1077
|
+
const cols = Math.max(1, ...matrix.map((row) => row.length));
|
|
1078
|
+
const values = [];
|
|
1079
|
+
for (let rowIndex = 0; rowIndex < rows; rowIndex += 1) {
|
|
1080
|
+
const row = matrix[rowIndex] ?? [];
|
|
1081
|
+
for (let colIndex = 0; colIndex < cols; colIndex += 1) {
|
|
1082
|
+
values.push(colIndex < row.length ? stringValue(row[colIndex]) : padValue);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return makeArrayStack(rows, cols, values);
|
|
1086
|
+
}
|
|
1087
|
+
case "TRIMRANGE": {
|
|
1088
|
+
if (rawArgs.length < 1 || rawArgs.length > 3) {
|
|
1089
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1090
|
+
}
|
|
1091
|
+
const source = toRangeLike(rawArgs[0]);
|
|
1092
|
+
const trimRows = coerceOptionalTrimModeArgument(rawArgs[1], 3);
|
|
1093
|
+
const trimCols = coerceOptionalTrimModeArgument(rawArgs[2], 3);
|
|
1094
|
+
if (isCellValueError(trimRows)) {
|
|
1095
|
+
return stackScalar(trimRows);
|
|
1096
|
+
}
|
|
1097
|
+
if (isCellValueError(trimCols)) {
|
|
1098
|
+
return stackScalar(trimCols);
|
|
1099
|
+
}
|
|
1100
|
+
let startRow = 0;
|
|
1101
|
+
let endRow = source.rows - 1;
|
|
1102
|
+
let startCol = 0;
|
|
1103
|
+
let endCol = source.cols - 1;
|
|
1104
|
+
const trimLeadingRows = trimRows === 1 || trimRows === 3;
|
|
1105
|
+
const trimTrailingRows = trimRows === 2 || trimRows === 3;
|
|
1106
|
+
const trimLeadingCols = trimCols === 1 || trimCols === 3;
|
|
1107
|
+
const trimTrailingCols = trimCols === 2 || trimCols === 3;
|
|
1108
|
+
if (trimLeadingRows) {
|
|
1109
|
+
while (startRow <= endRow) {
|
|
1110
|
+
let hasNonEmpty = false;
|
|
1111
|
+
for (let col = 0; col < source.cols; col += 1) {
|
|
1112
|
+
if (!isTrimRangeEmptyCell(getRangeCell(source, startRow, col))) {
|
|
1113
|
+
hasNonEmpty = true;
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (hasNonEmpty) {
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
startRow += 1;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (trimTrailingRows) {
|
|
1124
|
+
while (endRow >= startRow) {
|
|
1125
|
+
let hasNonEmpty = false;
|
|
1126
|
+
for (let col = 0; col < source.cols; col += 1) {
|
|
1127
|
+
if (!isTrimRangeEmptyCell(getRangeCell(source, endRow, col))) {
|
|
1128
|
+
hasNonEmpty = true;
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (hasNonEmpty) {
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
endRow -= 1;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
if (startRow > endRow) {
|
|
1139
|
+
return makeArrayStack(1, 1, [emptyValue()]);
|
|
1140
|
+
}
|
|
1141
|
+
if (trimLeadingCols) {
|
|
1142
|
+
while (startCol <= endCol) {
|
|
1143
|
+
let hasNonEmpty = false;
|
|
1144
|
+
for (let row = startRow; row <= endRow; row += 1) {
|
|
1145
|
+
if (!isTrimRangeEmptyCell(getRangeCell(source, row, startCol))) {
|
|
1146
|
+
hasNonEmpty = true;
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (hasNonEmpty) {
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
startCol += 1;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (trimTrailingCols) {
|
|
1157
|
+
while (endCol >= startCol) {
|
|
1158
|
+
let hasNonEmpty = false;
|
|
1159
|
+
for (let row = startRow; row <= endRow; row += 1) {
|
|
1160
|
+
if (!isTrimRangeEmptyCell(getRangeCell(source, row, endCol))) {
|
|
1161
|
+
hasNonEmpty = true;
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (hasNonEmpty) {
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1168
|
+
endCol -= 1;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (startCol > endCol) {
|
|
1172
|
+
return makeArrayStack(1, 1, [emptyValue()]);
|
|
1173
|
+
}
|
|
1174
|
+
const rows = endRow - startRow + 1;
|
|
1175
|
+
const cols = endCol - startCol + 1;
|
|
1176
|
+
const values = [];
|
|
1177
|
+
for (let row = startRow; row <= endRow; row += 1) {
|
|
1178
|
+
for (let col = startCol; col <= endCol; col += 1) {
|
|
1179
|
+
values.push(getRangeCell(source, row, col));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return makeArrayStack(rows, cols, values);
|
|
1183
|
+
}
|
|
1184
|
+
case "MAKEARRAY": {
|
|
1185
|
+
if (rawArgs.length !== 3) {
|
|
1186
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1187
|
+
}
|
|
1188
|
+
const rows = toPositiveInteger(rawArgs[0]);
|
|
1189
|
+
const cols = toPositiveInteger(rawArgs[1]);
|
|
1190
|
+
if (rows === undefined || cols === undefined) {
|
|
1191
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1192
|
+
}
|
|
1193
|
+
const lambda = rawArgs[2];
|
|
1194
|
+
const values = [];
|
|
1195
|
+
for (let row = 1; row <= rows; row += 1) {
|
|
1196
|
+
for (let col = 1; col <= cols; col += 1) {
|
|
1197
|
+
const result = applyLambda(lambda, [
|
|
1198
|
+
stackScalar({ tag: ValueTag.Number, value: row }),
|
|
1199
|
+
stackScalar({ tag: ValueTag.Number, value: col }),
|
|
1200
|
+
], context);
|
|
1201
|
+
const scalar = isSingleCellValue(result);
|
|
1202
|
+
if (!scalar) {
|
|
1203
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1204
|
+
}
|
|
1205
|
+
values.push(scalar);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return { kind: "array", rows, cols, values };
|
|
1209
|
+
}
|
|
1210
|
+
case "MAP": {
|
|
1211
|
+
if (rawArgs.length < 2) {
|
|
1212
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1213
|
+
}
|
|
1214
|
+
const lambda = rawArgs[rawArgs.length - 1];
|
|
1215
|
+
const inputs = rawArgs.slice(0, -1);
|
|
1216
|
+
const shape = getBroadcastShape(inputs);
|
|
1217
|
+
if (!shape) {
|
|
1218
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1219
|
+
}
|
|
1220
|
+
const ranges = inputs.map(toRangeLike);
|
|
1221
|
+
const values = [];
|
|
1222
|
+
for (let row = 0; row < shape.rows; row += 1) {
|
|
1223
|
+
for (let col = 0; col < shape.cols; col += 1) {
|
|
1224
|
+
const lambdaArgs = ranges.map((range) => stackScalar(getRangeCell(range, Math.min(row, range.rows - 1), Math.min(col, range.cols - 1))));
|
|
1225
|
+
const result = applyLambda(lambda, lambdaArgs, context);
|
|
1226
|
+
const scalar = isSingleCellValue(result);
|
|
1227
|
+
if (!scalar) {
|
|
1228
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1229
|
+
}
|
|
1230
|
+
values.push(scalar);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return { kind: "array", rows: shape.rows, cols: shape.cols, values };
|
|
1234
|
+
}
|
|
1235
|
+
case "BYROW":
|
|
1236
|
+
case "BYCOL": {
|
|
1237
|
+
if (rawArgs.length !== 2) {
|
|
1238
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1239
|
+
}
|
|
1240
|
+
const source = toRangeLike(rawArgs[0]);
|
|
1241
|
+
const lambda = rawArgs[1];
|
|
1242
|
+
const values = [];
|
|
1243
|
+
if (callee === "BYROW") {
|
|
1244
|
+
for (let row = 0; row < source.rows; row += 1) {
|
|
1245
|
+
const rowValues = [];
|
|
1246
|
+
for (let col = 0; col < source.cols; col += 1) {
|
|
1247
|
+
rowValues.push(getRangeCell(source, row, col));
|
|
1248
|
+
}
|
|
1249
|
+
const result = applyLambda(lambda, [{ kind: "range", values: rowValues, rows: 1, cols: source.cols, refKind: "cells" }], context);
|
|
1250
|
+
const scalar = isSingleCellValue(result);
|
|
1251
|
+
if (!scalar) {
|
|
1252
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1253
|
+
}
|
|
1254
|
+
values.push(scalar);
|
|
1255
|
+
}
|
|
1256
|
+
return { kind: "array", rows: source.rows, cols: 1, values };
|
|
1257
|
+
}
|
|
1258
|
+
for (let col = 0; col < source.cols; col += 1) {
|
|
1259
|
+
const colValues = [];
|
|
1260
|
+
for (let row = 0; row < source.rows; row += 1) {
|
|
1261
|
+
colValues.push(getRangeCell(source, row, col));
|
|
1262
|
+
}
|
|
1263
|
+
const result = applyLambda(lambda, [{ kind: "range", values: colValues, rows: source.rows, cols: 1, refKind: "cells" }], context);
|
|
1264
|
+
const scalar = isSingleCellValue(result);
|
|
1265
|
+
if (!scalar) {
|
|
1266
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1267
|
+
}
|
|
1268
|
+
values.push(scalar);
|
|
1269
|
+
}
|
|
1270
|
+
return { kind: "array", rows: 1, cols: source.cols, values };
|
|
1271
|
+
}
|
|
1272
|
+
case "REDUCE":
|
|
1273
|
+
case "SCAN": {
|
|
1274
|
+
if (rawArgs.length !== 2 && rawArgs.length !== 3) {
|
|
1275
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1276
|
+
}
|
|
1277
|
+
const hasInitial = rawArgs.length === 3;
|
|
1278
|
+
let accumulator = hasInitial ? cloneStackValue(rawArgs[0]) : stackScalar(emptyValue());
|
|
1279
|
+
const source = toRangeLike(rawArgs[hasInitial ? 1 : 0]);
|
|
1280
|
+
const lambda = rawArgs[hasInitial ? 2 : 1];
|
|
1281
|
+
const scanValues = [];
|
|
1282
|
+
for (const cell of source.values) {
|
|
1283
|
+
accumulator = applyLambda(lambda, [accumulator, stackScalar(cell)], context);
|
|
1284
|
+
if (callee === "SCAN") {
|
|
1285
|
+
const scalar = isSingleCellValue(accumulator);
|
|
1286
|
+
if (!scalar) {
|
|
1287
|
+
return stackScalar(error(ErrorCode.Value));
|
|
1288
|
+
}
|
|
1289
|
+
scanValues.push(scalar);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return callee === "SCAN"
|
|
1293
|
+
? { kind: "array", rows: source.rows, cols: source.cols, values: scanValues }
|
|
1294
|
+
: accumulator;
|
|
1295
|
+
}
|
|
1296
|
+
default:
|
|
1297
|
+
return undefined;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function lowerNode(node, plan) {
|
|
1301
|
+
switch (node.kind) {
|
|
1302
|
+
case "NumberLiteral":
|
|
1303
|
+
plan.push({ opcode: "push-number", value: node.value });
|
|
1304
|
+
return;
|
|
1305
|
+
case "BooleanLiteral":
|
|
1306
|
+
plan.push({ opcode: "push-boolean", value: node.value });
|
|
1307
|
+
return;
|
|
1308
|
+
case "StringLiteral":
|
|
1309
|
+
plan.push({ opcode: "push-string", value: node.value });
|
|
1310
|
+
return;
|
|
1311
|
+
case "ErrorLiteral":
|
|
1312
|
+
plan.push({ opcode: "push-error", code: node.code });
|
|
1313
|
+
return;
|
|
1314
|
+
case "NameRef":
|
|
1315
|
+
plan.push({ opcode: "push-name", name: node.name });
|
|
1316
|
+
return;
|
|
1317
|
+
case "StructuredRef":
|
|
1318
|
+
case "SpillRef":
|
|
1319
|
+
plan.push({ opcode: "push-error", code: ErrorCode.Ref });
|
|
1320
|
+
return;
|
|
1321
|
+
case "CellRef":
|
|
1322
|
+
plan.push(node.sheetName
|
|
1323
|
+
? { opcode: "push-cell", sheetName: node.sheetName, address: node.ref }
|
|
1324
|
+
: { opcode: "push-cell", address: node.ref });
|
|
1325
|
+
return;
|
|
1326
|
+
case "RangeRef":
|
|
1327
|
+
plan.push(node.sheetName
|
|
1328
|
+
? {
|
|
1329
|
+
opcode: "push-range",
|
|
1330
|
+
sheetName: node.sheetName,
|
|
1331
|
+
start: node.start,
|
|
1332
|
+
end: node.end,
|
|
1333
|
+
refKind: node.refKind,
|
|
1334
|
+
}
|
|
1335
|
+
: {
|
|
1336
|
+
opcode: "push-range",
|
|
1337
|
+
start: node.start,
|
|
1338
|
+
end: node.end,
|
|
1339
|
+
refKind: node.refKind,
|
|
1340
|
+
});
|
|
1341
|
+
return;
|
|
1342
|
+
case "RowRef":
|
|
1343
|
+
case "ColumnRef":
|
|
1344
|
+
plan.push({ opcode: "push-number", value: Number.NaN });
|
|
1345
|
+
return;
|
|
1346
|
+
case "UnaryExpr":
|
|
1347
|
+
lowerNode(node.argument, plan);
|
|
1348
|
+
plan.push({ opcode: "unary", operator: node.operator });
|
|
1349
|
+
return;
|
|
1350
|
+
case "BinaryExpr":
|
|
1351
|
+
lowerNode(node.left, plan);
|
|
1352
|
+
lowerNode(node.right, plan);
|
|
1353
|
+
plan.push({ opcode: "binary", operator: node.operator });
|
|
1354
|
+
return;
|
|
1355
|
+
case "InvokeExpr":
|
|
1356
|
+
lowerNode(node.callee, plan);
|
|
1357
|
+
node.args.forEach((arg) => lowerNode(arg, plan));
|
|
1358
|
+
plan.push({ opcode: "invoke", argc: node.args.length });
|
|
1359
|
+
return;
|
|
1360
|
+
case "CallExpr": {
|
|
1361
|
+
const rewritten = rewriteSpecialCall(node);
|
|
1362
|
+
if (rewritten) {
|
|
1363
|
+
lowerNode(rewritten, plan);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const callee = node.callee.toUpperCase();
|
|
1367
|
+
if (callee === "LAMBDA") {
|
|
1368
|
+
if (node.args.length < 1) {
|
|
1369
|
+
plan.push({ opcode: "push-error", code: ErrorCode.Value });
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const params = [];
|
|
1373
|
+
for (let index = 0; index < node.args.length - 1; index += 1) {
|
|
1374
|
+
const paramNode = node.args[index];
|
|
1375
|
+
if (paramNode.kind !== "NameRef") {
|
|
1376
|
+
plan.push({ opcode: "push-error", code: ErrorCode.Value });
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
params.push(paramNode.name);
|
|
1380
|
+
}
|
|
1381
|
+
const body = [];
|
|
1382
|
+
lowerNode(node.args[node.args.length - 1], body);
|
|
1383
|
+
body.push({ opcode: "return" });
|
|
1384
|
+
plan.push({ opcode: "push-lambda", params, body });
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (callee === "LET") {
|
|
1388
|
+
if (node.args.length < 3 || node.args.length % 2 === 0) {
|
|
1389
|
+
plan.push({ opcode: "push-error", code: ErrorCode.Value });
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
plan.push({ opcode: "begin-scope" });
|
|
1393
|
+
for (let index = 0; index < node.args.length - 1; index += 2) {
|
|
1394
|
+
const nameNode = node.args[index];
|
|
1395
|
+
if (nameNode.kind !== "NameRef") {
|
|
1396
|
+
plan.push({ opcode: "push-error", code: ErrorCode.Value });
|
|
1397
|
+
plan.push({ opcode: "end-scope" });
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
lowerNode(node.args[index + 1], plan);
|
|
1401
|
+
plan.push({ opcode: "bind-name", name: nameNode.name });
|
|
1402
|
+
}
|
|
1403
|
+
lowerNode(node.args[node.args.length - 1], plan);
|
|
1404
|
+
plan.push({ opcode: "end-scope" });
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
if (callee === "IF" && node.args.length === 3) {
|
|
1408
|
+
lowerNode(node.args[0], plan);
|
|
1409
|
+
const jumpIfFalseIndex = plan.push({ opcode: "jump-if-false", target: -1 }) - 1;
|
|
1410
|
+
lowerNode(node.args[1], plan);
|
|
1411
|
+
const jumpIndex = plan.push({ opcode: "jump", target: -1 }) - 1;
|
|
1412
|
+
const falseTarget = plan.length;
|
|
1413
|
+
lowerNode(node.args[2], plan);
|
|
1414
|
+
const endTarget = plan.length;
|
|
1415
|
+
plan[jumpIfFalseIndex] = { opcode: "jump-if-false", target: falseTarget };
|
|
1416
|
+
plan[jumpIndex] = { opcode: "jump", target: endTarget };
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
if (!hasBuiltin(callee)) {
|
|
1420
|
+
lowerNode({ kind: "NameRef", name: node.callee }, plan);
|
|
1421
|
+
node.args.forEach((arg) => lowerNode(arg, plan));
|
|
1422
|
+
plan.push({ opcode: "invoke", argc: node.args.length });
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const aggregateArgumentIndex = callee === "GROUPBY" ? 2 : callee === "PIVOTBY" ? 3 : -1;
|
|
1426
|
+
node.args.forEach((arg, index) => {
|
|
1427
|
+
if (index === aggregateArgumentIndex && arg.kind === "NameRef") {
|
|
1428
|
+
plan.push({ opcode: "push-string", value: arg.name });
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
lowerNode(arg, plan);
|
|
1432
|
+
});
|
|
1433
|
+
plan.push({
|
|
1434
|
+
opcode: "call",
|
|
1435
|
+
callee,
|
|
1436
|
+
argc: node.args.length,
|
|
1437
|
+
argRefs: node.args.map((arg) => referenceOperandFromNode(arg)),
|
|
1438
|
+
});
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
export function lowerToPlan(node) {
|
|
1444
|
+
const plan = [];
|
|
1445
|
+
lowerNode(node, plan);
|
|
1446
|
+
plan.push({ opcode: "return" });
|
|
1447
|
+
return plan;
|
|
1448
|
+
}
|
|
1449
|
+
function executePlan(plan, context, initialScopes = []) {
|
|
1450
|
+
const stack = [];
|
|
1451
|
+
const scopes = cloneScopes(initialScopes);
|
|
1452
|
+
let pc = 0;
|
|
1453
|
+
while (pc < plan.length) {
|
|
1454
|
+
const instruction = plan[pc];
|
|
1455
|
+
switch (instruction.opcode) {
|
|
1456
|
+
case "push-number":
|
|
1457
|
+
stack.push({ kind: "scalar", value: { tag: ValueTag.Number, value: instruction.value } });
|
|
1458
|
+
break;
|
|
1459
|
+
case "push-boolean":
|
|
1460
|
+
stack.push({ kind: "scalar", value: { tag: ValueTag.Boolean, value: instruction.value } });
|
|
1461
|
+
break;
|
|
1462
|
+
case "push-string":
|
|
1463
|
+
stack.push({
|
|
1464
|
+
kind: "scalar",
|
|
1465
|
+
value: { tag: ValueTag.String, value: instruction.value, stringId: 0 },
|
|
1466
|
+
});
|
|
1467
|
+
break;
|
|
1468
|
+
case "push-error":
|
|
1469
|
+
stack.push({ kind: "scalar", value: error(instruction.code) });
|
|
1470
|
+
break;
|
|
1471
|
+
case "push-name":
|
|
1472
|
+
{
|
|
1473
|
+
let scopedValue;
|
|
1474
|
+
for (let index = scopes.length - 1; index >= 0; index -= 1) {
|
|
1475
|
+
const found = scopes[index].get(normalizeScopeName(instruction.name));
|
|
1476
|
+
if (found) {
|
|
1477
|
+
scopedValue = found;
|
|
1478
|
+
break;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
stack.push(scopedValue
|
|
1482
|
+
? cloneStackValue(scopedValue)
|
|
1483
|
+
: {
|
|
1484
|
+
kind: "scalar",
|
|
1485
|
+
value: context.resolveName?.(instruction.name) ?? error(ErrorCode.Name),
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
break;
|
|
1489
|
+
case "push-cell":
|
|
1490
|
+
stack.push({
|
|
1491
|
+
kind: "scalar",
|
|
1492
|
+
value: context.resolveCell(instruction.sheetName ?? context.sheetName, instruction.address),
|
|
1493
|
+
});
|
|
1494
|
+
break;
|
|
1495
|
+
case "push-range":
|
|
1496
|
+
{
|
|
1497
|
+
const values = context.resolveRange(instruction.sheetName ?? context.sheetName, instruction.start, instruction.end, instruction.refKind);
|
|
1498
|
+
let rows = values.length;
|
|
1499
|
+
let cols = 1;
|
|
1500
|
+
if (instruction.refKind === "cells") {
|
|
1501
|
+
try {
|
|
1502
|
+
const sheetPrefix = instruction.sheetName ? `${instruction.sheetName}!` : "";
|
|
1503
|
+
const range = parseRangeAddress(`${sheetPrefix}${instruction.start}:${instruction.end}`);
|
|
1504
|
+
if (range.kind === "cells") {
|
|
1505
|
+
rows = range.end.row - range.start.row + 1;
|
|
1506
|
+
cols = range.end.col - range.start.col + 1;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
catch {
|
|
1510
|
+
rows = values.length;
|
|
1511
|
+
cols = 1;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
stack.push({
|
|
1515
|
+
kind: "range",
|
|
1516
|
+
values,
|
|
1517
|
+
refKind: instruction.refKind,
|
|
1518
|
+
rows,
|
|
1519
|
+
cols,
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
break;
|
|
1523
|
+
case "push-lambda":
|
|
1524
|
+
stack.push({
|
|
1525
|
+
kind: "lambda",
|
|
1526
|
+
params: [...instruction.params],
|
|
1527
|
+
body: instruction.body,
|
|
1528
|
+
scopes: cloneScopes(scopes),
|
|
1529
|
+
});
|
|
1530
|
+
break;
|
|
1531
|
+
case "unary": {
|
|
1532
|
+
const value = popScalar(stack);
|
|
1533
|
+
const numeric = toNumber(value);
|
|
1534
|
+
stack.push({
|
|
1535
|
+
kind: "scalar",
|
|
1536
|
+
value: numeric === undefined
|
|
1537
|
+
? error(ErrorCode.Value)
|
|
1538
|
+
: { tag: ValueTag.Number, value: instruction.operator === "-" ? -numeric : numeric },
|
|
1539
|
+
});
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
case "binary": {
|
|
1543
|
+
const right = popArgument(stack);
|
|
1544
|
+
const left = popArgument(stack);
|
|
1545
|
+
const result = evaluateBinary(instruction.operator, left, right);
|
|
1546
|
+
stack.push(isArrayValue(result) ? result : { kind: "scalar", value: result });
|
|
1547
|
+
break;
|
|
1548
|
+
}
|
|
1549
|
+
case "begin-scope":
|
|
1550
|
+
scopes.push(new Map());
|
|
1551
|
+
break;
|
|
1552
|
+
case "bind-name": {
|
|
1553
|
+
const scope = scopes[scopes.length - 1];
|
|
1554
|
+
if (!scope) {
|
|
1555
|
+
stack.push({ kind: "scalar", value: error(ErrorCode.Value) });
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
scope.set(normalizeScopeName(instruction.name), cloneStackValue(popArgument(stack)));
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
case "end-scope":
|
|
1562
|
+
scopes.pop();
|
|
1563
|
+
break;
|
|
1564
|
+
case "call": {
|
|
1565
|
+
const rawArgs = [];
|
|
1566
|
+
for (let index = 0; index < instruction.argc; index += 1) {
|
|
1567
|
+
rawArgs.unshift(popArgument(stack));
|
|
1568
|
+
}
|
|
1569
|
+
const specialResult = evaluateSpecialCall(instruction.callee, rawArgs, context, instruction.argRefs);
|
|
1570
|
+
if (specialResult) {
|
|
1571
|
+
stack.push(specialResult);
|
|
1572
|
+
break;
|
|
1573
|
+
}
|
|
1574
|
+
const lookupBuiltin = getLookupBuiltin(instruction.callee);
|
|
1575
|
+
if (lookupBuiltin) {
|
|
1576
|
+
const args = [];
|
|
1577
|
+
for (const rawArg of rawArgs) {
|
|
1578
|
+
args.push(toRangeArgument(rawArg));
|
|
1579
|
+
}
|
|
1580
|
+
const result = lookupBuiltin(...args);
|
|
1581
|
+
stack.push(isArrayValue(result) ? result : { kind: "scalar", value: result });
|
|
1582
|
+
break;
|
|
1583
|
+
}
|
|
1584
|
+
const builtin = context.resolveBuiltin?.(instruction.callee) ?? getBuiltin(instruction.callee);
|
|
1585
|
+
if (!builtin) {
|
|
1586
|
+
stack.push({ kind: "scalar", value: error(ErrorCode.Name) });
|
|
1587
|
+
break;
|
|
1588
|
+
}
|
|
1589
|
+
const args = [];
|
|
1590
|
+
for (const rawArg of rawArgs) {
|
|
1591
|
+
if (rawArg.kind === "scalar") {
|
|
1592
|
+
args.push(rawArg.value);
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (rawArg.kind === "omitted") {
|
|
1596
|
+
args.push(error(ErrorCode.Value));
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
if (rawArg.kind === "lambda") {
|
|
1600
|
+
args.push(error(ErrorCode.Value));
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
args.push(...rawArg.values);
|
|
1604
|
+
}
|
|
1605
|
+
const result = builtin(...args);
|
|
1606
|
+
stack.push(isArrayValue(result) ? result : { kind: "scalar", value: result });
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
case "invoke": {
|
|
1610
|
+
const args = [];
|
|
1611
|
+
for (let index = 0; index < instruction.argc; index += 1) {
|
|
1612
|
+
args.unshift(popArgument(stack));
|
|
1613
|
+
}
|
|
1614
|
+
const callee = popArgument(stack);
|
|
1615
|
+
stack.push(applyLambda(callee, args, context));
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
case "jump-if-false": {
|
|
1619
|
+
const value = popScalar(stack);
|
|
1620
|
+
if (value.tag === ValueTag.Error) {
|
|
1621
|
+
return stackScalar(value);
|
|
1622
|
+
}
|
|
1623
|
+
if (!truthy(value)) {
|
|
1624
|
+
pc = instruction.target;
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
break;
|
|
1628
|
+
}
|
|
1629
|
+
case "jump":
|
|
1630
|
+
pc = instruction.target;
|
|
1631
|
+
continue;
|
|
1632
|
+
case "return":
|
|
1633
|
+
return stack.pop();
|
|
1634
|
+
}
|
|
1635
|
+
pc += 1;
|
|
1636
|
+
}
|
|
1637
|
+
return stack.pop();
|
|
1638
|
+
}
|
|
1639
|
+
export function evaluatePlanResult(plan, context) {
|
|
1640
|
+
return toEvaluationResult(executePlan(plan, context));
|
|
1641
|
+
}
|
|
1642
|
+
export function evaluatePlan(plan, context) {
|
|
1643
|
+
return scalarFromEvaluationResult(evaluatePlanResult(plan, context));
|
|
1644
|
+
}
|
|
1645
|
+
export function evaluateAst(node, context) {
|
|
1646
|
+
return evaluatePlan(lowerToPlan(node), context);
|
|
1647
|
+
}
|
|
1648
|
+
export function evaluateAstResult(node, context) {
|
|
1649
|
+
return evaluatePlanResult(lowerToPlan(node), context);
|
|
1650
|
+
}
|
|
1651
|
+
//# sourceMappingURL=js-evaluator.js.map
|