@devmm/puredocs-excel-formula 1.1.2 → 1.1.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/index.cjs +1560 -1570
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +1561 -1571
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, parseRangeRef, shiftIndex, shiftFormulaRefs, shiftSpan
|
|
1
|
+
import { toOADate, EXCEL_MAX_ROWS, columnNumber, EXCEL_MAX_COLUMNS, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, parseRangeRef, shiftIndex, shiftFormulaRefs, shiftSpan } from '@devmm/puredocs-excel';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
throw TypeError(msg);
|
|
5
|
-
};
|
|
6
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
7
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
8
|
-
var __privateAdd = (obj, member, value2) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value2);
|
|
9
|
-
var __privateSet = (obj, member, value2, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value2), value2);
|
|
10
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
11
|
-
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
12
|
-
set _(value2) {
|
|
13
|
-
__privateSet(obj, member, value2);
|
|
14
|
-
},
|
|
15
|
-
get _() {
|
|
16
|
-
return __privateGet(obj, member, getter);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
3
|
+
// src/formula-value.ts
|
|
19
4
|
var FormulaError = /* @__PURE__ */ ((FormulaError2) => {
|
|
20
5
|
FormulaError2[FormulaError2["None"] = 0] = "None";
|
|
21
6
|
FormulaError2[FormulaError2["Null"] = 1] = "Null";
|
|
@@ -41,19 +26,36 @@ var TailCall = class {
|
|
|
41
26
|
this.lambda = lambda;
|
|
42
27
|
this.args = args;
|
|
43
28
|
}
|
|
29
|
+
lambda;
|
|
30
|
+
args;
|
|
44
31
|
};
|
|
45
|
-
var
|
|
46
|
-
|
|
32
|
+
var FormulaValue = class _FormulaValue {
|
|
33
|
+
kind;
|
|
34
|
+
/** Numeric payload (also used for boolean: 1=true, 0=false). */
|
|
35
|
+
#num;
|
|
36
|
+
/** String, ArrayValue, or LambdaLike payload. */
|
|
37
|
+
#obj;
|
|
38
|
+
errorCode;
|
|
47
39
|
constructor(kind, num5 = 0, obj, errorCode = 0 /* None */) {
|
|
48
|
-
/** Numeric payload (also used for boolean: 1=true, 0=false). */
|
|
49
|
-
__privateAdd(this, _num);
|
|
50
|
-
/** String, ArrayValue, or LambdaLike payload. */
|
|
51
|
-
__privateAdd(this, _obj);
|
|
52
40
|
this.kind = kind;
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
this.#num = num5;
|
|
42
|
+
this.#obj = obj;
|
|
55
43
|
this.errorCode = errorCode;
|
|
56
44
|
}
|
|
45
|
+
// ── Cached singletons ────────────────────────────────────────────────────
|
|
46
|
+
static blank = new _FormulaValue("blank");
|
|
47
|
+
static zero = new _FormulaValue("number", 0);
|
|
48
|
+
static one = new _FormulaValue("number", 1);
|
|
49
|
+
static true_ = new _FormulaValue("boolean", 1);
|
|
50
|
+
static false_ = new _FormulaValue("boolean", 0);
|
|
51
|
+
static emptyString = new _FormulaValue("text", 0, "");
|
|
52
|
+
static errorDiv0 = new _FormulaValue("error", 0, void 0, 2 /* Div0 */);
|
|
53
|
+
static errorValue = new _FormulaValue("error", 0, void 0, 3 /* Value */);
|
|
54
|
+
static errorRef = new _FormulaValue("error", 0, void 0, 4 /* Ref */);
|
|
55
|
+
static errorName = new _FormulaValue("error", 0, void 0, 5 /* Name */);
|
|
56
|
+
static errorNA = new _FormulaValue("error", 0, void 0, 7 /* NA */);
|
|
57
|
+
static errorNum = new _FormulaValue("error", 0, void 0, 6 /* Num */);
|
|
58
|
+
static errorNull = new _FormulaValue("error", 0, void 0, 1 /* Null */);
|
|
57
59
|
// ── Static factories ─────────────────────────────────────────────────────
|
|
58
60
|
static number(v) {
|
|
59
61
|
if (v === 0) return _FormulaValue.zero;
|
|
@@ -120,19 +122,19 @@ var _FormulaValue = class _FormulaValue {
|
|
|
120
122
|
}
|
|
121
123
|
// ── Value accessors ──────────────────────────────────────────────────────
|
|
122
124
|
get numberValue() {
|
|
123
|
-
return
|
|
125
|
+
return this.#num;
|
|
124
126
|
}
|
|
125
127
|
get textValue() {
|
|
126
|
-
return
|
|
128
|
+
return this.#obj ?? "";
|
|
127
129
|
}
|
|
128
130
|
get booleanValue() {
|
|
129
|
-
return
|
|
131
|
+
return this.#num !== 0;
|
|
130
132
|
}
|
|
131
133
|
get arrayVal() {
|
|
132
|
-
return
|
|
134
|
+
return this.#obj ?? ArrayValue.empty;
|
|
133
135
|
}
|
|
134
136
|
get lambdaVal() {
|
|
135
|
-
return
|
|
137
|
+
return this.#obj;
|
|
136
138
|
}
|
|
137
139
|
// ── Coercion (Excel semantics, no exceptions) ─────────────────────────────
|
|
138
140
|
coerceToNumber() {
|
|
@@ -142,7 +144,7 @@ var _FormulaValue = class _FormulaValue {
|
|
|
142
144
|
case "blank":
|
|
143
145
|
return _FormulaValue.zero;
|
|
144
146
|
case "boolean":
|
|
145
|
-
return _FormulaValue.number(
|
|
147
|
+
return _FormulaValue.number(this.#num);
|
|
146
148
|
case "error":
|
|
147
149
|
return this;
|
|
148
150
|
case "text": {
|
|
@@ -156,11 +158,11 @@ var _FormulaValue = class _FormulaValue {
|
|
|
156
158
|
tryAsDouble() {
|
|
157
159
|
switch (this.kind) {
|
|
158
160
|
case "number":
|
|
159
|
-
return { ok: true, value:
|
|
161
|
+
return { ok: true, value: this.#num };
|
|
160
162
|
case "blank":
|
|
161
163
|
return { ok: true, value: 0 };
|
|
162
164
|
case "boolean":
|
|
163
|
-
return { ok: true, value:
|
|
165
|
+
return { ok: true, value: this.#num };
|
|
164
166
|
case "text": {
|
|
165
167
|
const n = parseFloat(this.textValue);
|
|
166
168
|
return isNaN(n) ? { ok: false } : { ok: true, value: n };
|
|
@@ -174,7 +176,7 @@ var _FormulaValue = class _FormulaValue {
|
|
|
174
176
|
case "boolean":
|
|
175
177
|
return this;
|
|
176
178
|
case "number":
|
|
177
|
-
return _FormulaValue.boolean(
|
|
179
|
+
return _FormulaValue.boolean(this.#num !== 0);
|
|
178
180
|
case "blank":
|
|
179
181
|
return _FormulaValue.false_;
|
|
180
182
|
case "error":
|
|
@@ -194,9 +196,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
194
196
|
case "text":
|
|
195
197
|
return this.textValue;
|
|
196
198
|
case "number":
|
|
197
|
-
return String(
|
|
199
|
+
return String(this.#num);
|
|
198
200
|
case "boolean":
|
|
199
|
-
return
|
|
201
|
+
return this.#num !== 0 ? "TRUE" : "FALSE";
|
|
200
202
|
case "blank":
|
|
201
203
|
return "";
|
|
202
204
|
case "error":
|
|
@@ -220,9 +222,9 @@ var _FormulaValue = class _FormulaValue {
|
|
|
220
222
|
if (a.kind !== b.kind) return false;
|
|
221
223
|
switch (a.kind) {
|
|
222
224
|
case "number":
|
|
223
|
-
return Math.abs(
|
|
225
|
+
return Math.abs(a.#num - b.#num) < 1e-10;
|
|
224
226
|
case "boolean":
|
|
225
|
-
return
|
|
227
|
+
return a.#num === b.#num;
|
|
226
228
|
case "text":
|
|
227
229
|
return a.textValue.toUpperCase() === b.textValue.toUpperCase();
|
|
228
230
|
default:
|
|
@@ -240,7 +242,7 @@ var _FormulaValue = class _FormulaValue {
|
|
|
240
242
|
case 1:
|
|
241
243
|
return a.textValue.toUpperCase() < b.textValue.toUpperCase() ? -1 : a.textValue.toUpperCase() > b.textValue.toUpperCase() ? 1 : 0;
|
|
242
244
|
case 2:
|
|
243
|
-
return
|
|
245
|
+
return a.#num - b.#num;
|
|
244
246
|
default:
|
|
245
247
|
return 0;
|
|
246
248
|
}
|
|
@@ -289,7 +291,7 @@ var _FormulaValue = class _FormulaValue {
|
|
|
289
291
|
toObject() {
|
|
290
292
|
switch (this.kind) {
|
|
291
293
|
case "number":
|
|
292
|
-
return
|
|
294
|
+
return this.#num;
|
|
293
295
|
case "text":
|
|
294
296
|
return this.textValue;
|
|
295
297
|
case "boolean":
|
|
@@ -310,59 +312,44 @@ var _FormulaValue = class _FormulaValue {
|
|
|
310
312
|
return this.asText();
|
|
311
313
|
}
|
|
312
314
|
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
_FormulaValue.one = new _FormulaValue("number", 1);
|
|
319
|
-
_FormulaValue.true_ = new _FormulaValue("boolean", 1);
|
|
320
|
-
_FormulaValue.false_ = new _FormulaValue("boolean", 0);
|
|
321
|
-
_FormulaValue.emptyString = new _FormulaValue("text", 0, "");
|
|
322
|
-
_FormulaValue.errorDiv0 = new _FormulaValue("error", 0, void 0, 2 /* Div0 */);
|
|
323
|
-
_FormulaValue.errorValue = new _FormulaValue("error", 0, void 0, 3 /* Value */);
|
|
324
|
-
_FormulaValue.errorRef = new _FormulaValue("error", 0, void 0, 4 /* Ref */);
|
|
325
|
-
_FormulaValue.errorName = new _FormulaValue("error", 0, void 0, 5 /* Name */);
|
|
326
|
-
_FormulaValue.errorNA = new _FormulaValue("error", 0, void 0, 7 /* NA */);
|
|
327
|
-
_FormulaValue.errorNum = new _FormulaValue("error", 0, void 0, 6 /* Num */);
|
|
328
|
-
_FormulaValue.errorNull = new _FormulaValue("error", 0, void 0, 1 /* Null */);
|
|
329
|
-
var FormulaValue = _FormulaValue;
|
|
330
|
-
var _data;
|
|
331
|
-
var _ArrayValue = class _ArrayValue {
|
|
315
|
+
var ArrayValue = class _ArrayValue {
|
|
316
|
+
static empty = new _ArrayValue(0, 0);
|
|
317
|
+
#data;
|
|
318
|
+
rows;
|
|
319
|
+
columns;
|
|
332
320
|
constructor(rows2, columns2) {
|
|
333
|
-
__privateAdd(this, _data);
|
|
334
321
|
this.rows = rows2;
|
|
335
322
|
this.columns = columns2;
|
|
336
|
-
|
|
323
|
+
this.#data = new Array(rows2 * columns2).fill(FormulaValue.blank);
|
|
337
324
|
}
|
|
338
325
|
get length() {
|
|
339
|
-
return
|
|
326
|
+
return this.#data.length;
|
|
340
327
|
}
|
|
341
328
|
get(row2, col) {
|
|
342
329
|
if (row2 < 0 || row2 >= this.rows || col < 0 || col >= this.columns) {
|
|
343
330
|
return FormulaValue.errorRef;
|
|
344
331
|
}
|
|
345
|
-
return
|
|
332
|
+
return this.#data[row2 * this.columns + col];
|
|
346
333
|
}
|
|
347
334
|
set(row2, col, value2) {
|
|
348
335
|
if (row2 >= 0 && row2 < this.rows && col >= 0 && col < this.columns) {
|
|
349
|
-
|
|
336
|
+
this.#data[row2 * this.columns + col] = value2;
|
|
350
337
|
}
|
|
351
338
|
}
|
|
352
339
|
getFlat(index) {
|
|
353
|
-
return index >= 0 && index <
|
|
340
|
+
return index >= 0 && index < this.#data.length ? this.#data[index] : FormulaValue.errorRef;
|
|
354
341
|
}
|
|
355
342
|
setFlat(index, value2) {
|
|
356
|
-
if (index >= 0 && index <
|
|
357
|
-
|
|
343
|
+
if (index >= 0 && index < this.#data.length) {
|
|
344
|
+
this.#data[index] = value2;
|
|
358
345
|
}
|
|
359
346
|
}
|
|
360
347
|
/** Iterate all values in row-major order. */
|
|
361
348
|
values() {
|
|
362
|
-
return
|
|
349
|
+
return this.#data;
|
|
363
350
|
}
|
|
364
351
|
toObjectArray() {
|
|
365
|
-
return
|
|
352
|
+
return this.#data.map((v) => v.toObject());
|
|
366
353
|
}
|
|
367
354
|
/** Create from a flat array (single column). */
|
|
368
355
|
static fromFlat(values) {
|
|
@@ -420,9 +407,6 @@ var _ArrayValue = class _ArrayValue {
|
|
|
420
407
|
return result;
|
|
421
408
|
}
|
|
422
409
|
};
|
|
423
|
-
_data = new WeakMap();
|
|
424
|
-
_ArrayValue.empty = new _ArrayValue(0, 0);
|
|
425
|
-
var ArrayValue = _ArrayValue;
|
|
426
410
|
|
|
427
411
|
// src/formula-token.ts
|
|
428
412
|
var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
@@ -484,262 +468,257 @@ var KNOWN_ERRORS = /* @__PURE__ */ new Set([
|
|
|
484
468
|
"#CALC!",
|
|
485
469
|
"#SPILL!"
|
|
486
470
|
]);
|
|
487
|
-
var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, stripReservedPrefixes_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
|
|
488
471
|
var FormulaLexer = class {
|
|
472
|
+
#formula;
|
|
473
|
+
#pos = 0;
|
|
489
474
|
constructor(formula) {
|
|
490
|
-
__privateAdd(this, _FormulaLexer_instances);
|
|
491
|
-
__privateAdd(this, _formula);
|
|
492
|
-
__privateAdd(this, _pos, 0);
|
|
493
475
|
if (!formula) throw new FormulaException("Formula cannot be empty.");
|
|
494
|
-
|
|
476
|
+
this.#formula = formula;
|
|
495
477
|
}
|
|
496
478
|
tokenize() {
|
|
497
|
-
|
|
479
|
+
this.#pos = 0;
|
|
498
480
|
const tokens = [];
|
|
499
|
-
while (
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
const token =
|
|
481
|
+
while (this.#pos < this.#formula.length) {
|
|
482
|
+
this.#skipWhitespace();
|
|
483
|
+
if (this.#pos >= this.#formula.length) break;
|
|
484
|
+
const token = this.#readNextToken();
|
|
503
485
|
if (token) tokens.push(token);
|
|
504
486
|
}
|
|
505
|
-
tokens.push(makeToken(31 /* EOF */, "",
|
|
487
|
+
tokens.push(makeToken(31 /* EOF */, "", this.#pos));
|
|
506
488
|
return tokens;
|
|
507
489
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
return
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === '"') {
|
|
533
|
-
value2 += '"';
|
|
534
|
-
__privateWrapper(this, _pos)._++;
|
|
535
|
-
} else return makeToken(1 /* String */, value2, start);
|
|
536
|
-
} else {
|
|
537
|
-
value2 += ch;
|
|
538
|
-
__privateWrapper(this, _pos)._++;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
throw new FormulaException(`Unterminated string at position ${start}.`);
|
|
542
|
-
};
|
|
543
|
-
readErrorLiteral_fn = function() {
|
|
544
|
-
const start = __privateGet(this, _pos);
|
|
545
|
-
let raw = "#";
|
|
546
|
-
__privateWrapper(this, _pos)._++;
|
|
547
|
-
while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
|
|
548
|
-
const ch = __privateGet(this, _formula)[__privateGet(this, _pos)];
|
|
549
|
-
if (/[a-zA-Z0-9/]/.test(ch) || ch === "!" || ch === "?") {
|
|
550
|
-
raw += ch;
|
|
551
|
-
__privateWrapper(this, _pos)._++;
|
|
552
|
-
if (ch === "!" || ch === "?") break;
|
|
553
|
-
} else break;
|
|
554
|
-
}
|
|
555
|
-
const upper2 = raw.toUpperCase();
|
|
556
|
-
if (!KNOWN_ERRORS.has(upper2)) {
|
|
557
|
-
throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
|
|
558
|
-
}
|
|
559
|
-
return makeToken(30 /* ErrorLiteral */, upper2, start);
|
|
560
|
-
};
|
|
561
|
-
readQuotedSheetRef_fn = function() {
|
|
562
|
-
const start = __privateWrapper(this, _pos)._++;
|
|
563
|
-
let sheetName = "";
|
|
564
|
-
while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
|
|
565
|
-
const ch = __privateGet(this, _formula)[__privateGet(this, _pos)];
|
|
566
|
-
if (ch === "'") {
|
|
567
|
-
__privateWrapper(this, _pos)._++;
|
|
568
|
-
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "'") {
|
|
569
|
-
sheetName += "'";
|
|
570
|
-
__privateWrapper(this, _pos)._++;
|
|
490
|
+
// ── Private tokenisers ─────────────────────────────────────────────────────
|
|
491
|
+
#readNextToken() {
|
|
492
|
+
const c = this.#formula[this.#pos];
|
|
493
|
+
if (c === '"') return this.#readString();
|
|
494
|
+
if (c === "#") return this.#readErrorLiteral();
|
|
495
|
+
if (c === "'") return this.#readQuotedSheetRef();
|
|
496
|
+
if (c === "." && this.#pos + 1 < this.#formula.length && /\d/.test(this.#formula[this.#pos + 1])) {
|
|
497
|
+
return this.#readNumber();
|
|
498
|
+
}
|
|
499
|
+
if (/\d/.test(c)) return this.#readNumber();
|
|
500
|
+
if (/[a-zA-Z_$]/.test(c)) return this.#readIdentifier();
|
|
501
|
+
return this.#readOperator();
|
|
502
|
+
}
|
|
503
|
+
#readString() {
|
|
504
|
+
const start = this.#pos++;
|
|
505
|
+
let value2 = "";
|
|
506
|
+
while (this.#pos < this.#formula.length) {
|
|
507
|
+
const ch = this.#formula[this.#pos];
|
|
508
|
+
if (ch === '"') {
|
|
509
|
+
this.#pos++;
|
|
510
|
+
if (this.#formula[this.#pos] === '"') {
|
|
511
|
+
value2 += '"';
|
|
512
|
+
this.#pos++;
|
|
513
|
+
} else return makeToken(1 /* String */, value2, start);
|
|
571
514
|
} else {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
__privateWrapper(this, _pos)._++;
|
|
576
|
-
const cellRef = __privateMethod(this, _FormulaLexer_instances, readCellRefChars_fn).call(this);
|
|
577
|
-
return makeToken(22 /* SheetReference */, `${sheetName}!${cellRef}`, start);
|
|
515
|
+
value2 += ch;
|
|
516
|
+
this.#pos++;
|
|
578
517
|
}
|
|
579
|
-
} else {
|
|
580
|
-
sheetName += ch;
|
|
581
|
-
__privateWrapper(this, _pos)._++;
|
|
582
518
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
519
|
+
throw new FormulaException(`Unterminated string at position ${start}.`);
|
|
520
|
+
}
|
|
521
|
+
#readErrorLiteral() {
|
|
522
|
+
const start = this.#pos;
|
|
523
|
+
let raw = "#";
|
|
524
|
+
this.#pos++;
|
|
525
|
+
while (this.#pos < this.#formula.length) {
|
|
526
|
+
const ch = this.#formula[this.#pos];
|
|
527
|
+
if (/[a-zA-Z0-9/]/.test(ch) || ch === "!" || ch === "?") {
|
|
528
|
+
raw += ch;
|
|
529
|
+
this.#pos++;
|
|
530
|
+
if (ch === "!" || ch === "?") break;
|
|
531
|
+
} else break;
|
|
532
|
+
}
|
|
533
|
+
const upper2 = raw.toUpperCase();
|
|
534
|
+
if (!KNOWN_ERRORS.has(upper2)) {
|
|
535
|
+
throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
|
|
536
|
+
}
|
|
537
|
+
return makeToken(30 /* ErrorLiteral */, upper2, start);
|
|
538
|
+
}
|
|
539
|
+
#readQuotedSheetRef() {
|
|
540
|
+
const start = this.#pos++;
|
|
541
|
+
let sheetName = "";
|
|
542
|
+
while (this.#pos < this.#formula.length) {
|
|
543
|
+
const ch = this.#formula[this.#pos];
|
|
544
|
+
if (ch === "'") {
|
|
545
|
+
this.#pos++;
|
|
546
|
+
if (this.#formula[this.#pos] === "'") {
|
|
547
|
+
sheetName += "'";
|
|
548
|
+
this.#pos++;
|
|
549
|
+
} else {
|
|
550
|
+
if (this.#formula[this.#pos] !== "!") {
|
|
551
|
+
throw new FormulaException(`Expected '!' after quoted sheet name at position ${start}.`);
|
|
552
|
+
}
|
|
553
|
+
this.#pos++;
|
|
554
|
+
const cellRef = this.#readCellRefChars();
|
|
555
|
+
return makeToken(22 /* SheetReference */, `${sheetName}!${cellRef}`, start);
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
sheetName += ch;
|
|
559
|
+
this.#pos++;
|
|
606
560
|
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
return makeToken(
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
561
|
+
}
|
|
562
|
+
throw new FormulaException(`Unterminated quoted sheet name at position ${start}.`);
|
|
563
|
+
}
|
|
564
|
+
#readNumber() {
|
|
565
|
+
const start = this.#pos;
|
|
566
|
+
let raw = "";
|
|
567
|
+
let hasDot = false;
|
|
568
|
+
let hasE = false;
|
|
569
|
+
while (this.#pos < this.#formula.length) {
|
|
570
|
+
const ch = this.#formula[this.#pos];
|
|
571
|
+
if (/\d/.test(ch)) {
|
|
572
|
+
raw += ch;
|
|
573
|
+
this.#pos++;
|
|
574
|
+
} else if (ch === "." && !hasDot && !hasE) {
|
|
575
|
+
hasDot = true;
|
|
576
|
+
raw += ch;
|
|
577
|
+
this.#pos++;
|
|
578
|
+
} else if ((ch === "e" || ch === "E") && !hasE && raw.length > 0) {
|
|
579
|
+
hasE = true;
|
|
580
|
+
raw += ch;
|
|
581
|
+
this.#pos++;
|
|
582
|
+
if (this.#formula[this.#pos] === "+" || this.#formula[this.#pos] === "-") {
|
|
583
|
+
raw += this.#formula[this.#pos++];
|
|
584
|
+
}
|
|
585
|
+
} else break;
|
|
586
|
+
}
|
|
587
|
+
return makeToken(0 /* Number */, raw, start);
|
|
588
|
+
}
|
|
589
|
+
#readIdentifier() {
|
|
590
|
+
const start = this.#pos;
|
|
591
|
+
let raw = "";
|
|
592
|
+
while (this.#pos < this.#formula.length) {
|
|
593
|
+
const ch = this.#formula[this.#pos];
|
|
594
|
+
if (/[a-zA-Z0-9_$.]/.test(ch)) {
|
|
595
|
+
raw += ch;
|
|
596
|
+
this.#pos++;
|
|
597
|
+
} else break;
|
|
598
|
+
}
|
|
599
|
+
let upper2 = raw.toUpperCase();
|
|
600
|
+
raw = this.#stripReservedPrefixes(raw, upper2);
|
|
601
|
+
if (raw.length !== upper2.length) upper2 = upper2.slice(upper2.length - raw.length);
|
|
602
|
+
if (upper2 === "TRUE") return makeToken(2 /* Boolean */, "TRUE", start);
|
|
603
|
+
if (upper2 === "FALSE") return makeToken(2 /* Boolean */, "FALSE", start);
|
|
604
|
+
if (this.#formula[this.#pos] === "!") {
|
|
605
|
+
this.#pos++;
|
|
606
|
+
const cellRef = this.#readCellRefChars();
|
|
607
|
+
return makeToken(22 /* SheetReference */, `${raw}!${cellRef}`, start);
|
|
608
|
+
}
|
|
609
|
+
this.#skipWhitespace();
|
|
610
|
+
if (this.#formula[this.#pos] === "(") {
|
|
611
|
+
return makeToken(5 /* Function */, upper2, start);
|
|
612
|
+
}
|
|
613
|
+
const stripped = raw.replace(/\$/g, "");
|
|
614
|
+
if (isCellRef(stripped)) {
|
|
615
|
+
if (this.#formula[this.#pos] === "#") {
|
|
616
|
+
this.#pos++;
|
|
617
|
+
return makeToken(24 /* SpillReference */, stripped, start);
|
|
663
618
|
}
|
|
619
|
+
return makeToken(3 /* CellReference */, stripped, start);
|
|
664
620
|
}
|
|
665
|
-
|
|
621
|
+
return makeToken(23 /* NamedRange */, raw, start);
|
|
666
622
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
case ")":
|
|
687
|
-
return makeToken(7 /* RightParen */, ")", start);
|
|
688
|
-
case ",":
|
|
689
|
-
return makeToken(8 /* Comma */, ",", start);
|
|
690
|
-
case ":":
|
|
691
|
-
return makeToken(4 /* Colon */, ":", start);
|
|
692
|
-
case "+":
|
|
693
|
-
return makeToken(9 /* Plus */, "+", start);
|
|
694
|
-
case "-":
|
|
695
|
-
return makeToken(10 /* Minus */, "-", start);
|
|
696
|
-
case "*":
|
|
697
|
-
return makeToken(11 /* Multiply */, "*", start);
|
|
698
|
-
case "/":
|
|
699
|
-
return makeToken(12 /* Divide */, "/", start);
|
|
700
|
-
case "^":
|
|
701
|
-
return makeToken(13 /* Power */, "^", start);
|
|
702
|
-
case "%":
|
|
703
|
-
return makeToken(14 /* Percent */, "%", start);
|
|
704
|
-
case "&":
|
|
705
|
-
return makeToken(15 /* Ampersand */, "&", start);
|
|
706
|
-
case "=":
|
|
707
|
-
return makeToken(16 /* Equal */, "=", start);
|
|
708
|
-
case "@":
|
|
709
|
-
return makeToken(26 /* AtSign */, "@", start);
|
|
710
|
-
case "!":
|
|
711
|
-
return makeToken(25 /* Exclamation */, "!", start);
|
|
712
|
-
case "{":
|
|
713
|
-
return makeToken(27 /* LeftBrace */, "{", start);
|
|
714
|
-
case "}":
|
|
715
|
-
return makeToken(28 /* RightBrace */, "}", start);
|
|
716
|
-
// Inside an array constant ';' separates rows. Excel always stores ',' as
|
|
717
|
-
// the argument separator regardless of locale, so ';' has no other role.
|
|
718
|
-
case ";":
|
|
719
|
-
return makeToken(29 /* Semicolon */, ";", start);
|
|
720
|
-
case "<":
|
|
721
|
-
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
|
|
722
|
-
__privateWrapper(this, _pos)._++;
|
|
723
|
-
return makeToken(17 /* NotEqual */, "<>", start);
|
|
724
|
-
}
|
|
725
|
-
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "=") {
|
|
726
|
-
__privateWrapper(this, _pos)._++;
|
|
727
|
-
return makeToken(19 /* LessThanOrEqual */, "<=", start);
|
|
728
|
-
}
|
|
729
|
-
return makeToken(18 /* LessThan */, "<", start);
|
|
730
|
-
case ">":
|
|
731
|
-
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "=") {
|
|
732
|
-
__privateWrapper(this, _pos)._++;
|
|
733
|
-
return makeToken(21 /* GreaterThanOrEqual */, ">=", start);
|
|
623
|
+
/**
|
|
624
|
+
* Drops Excel's storage-level name decorations. They carry no semantics — the
|
|
625
|
+
* writer stamps them so an older reader fails loudly instead of silently
|
|
626
|
+
* mis-evaluating — and they *stack*: `_xlfn._xlws.FILTER` wears two. Stripping
|
|
627
|
+
* one prefix once, as this used to, leaves `_XLWS.FILTER` unresolvable.
|
|
628
|
+
*
|
|
629
|
+
* Every identifier in every formula passes through here, so the common case is
|
|
630
|
+
* gated on a single char-code test: a name not starting with '_' returns
|
|
631
|
+
* untouched without so much as a comparison against the prefix table.
|
|
632
|
+
*/
|
|
633
|
+
#stripReservedPrefixes(raw, upper2) {
|
|
634
|
+
if (raw.charCodeAt(0) !== UNDERSCORE) return raw;
|
|
635
|
+
let cut = 0;
|
|
636
|
+
outer: for (; ; ) {
|
|
637
|
+
for (const prefix of RESERVED_PREFIXES) {
|
|
638
|
+
if (upper2.startsWith(prefix, cut)) {
|
|
639
|
+
cut += prefix.length;
|
|
640
|
+
continue outer;
|
|
641
|
+
}
|
|
734
642
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
return cut > 0 && cut < raw.length ? raw.slice(cut) : raw;
|
|
646
|
+
}
|
|
647
|
+
#readCellRefChars() {
|
|
648
|
+
let ref = "";
|
|
649
|
+
while (this.#pos < this.#formula.length) {
|
|
650
|
+
const ch = this.#formula[this.#pos];
|
|
651
|
+
if (/[a-zA-Z0-9$]/.test(ch)) {
|
|
652
|
+
ref += ch;
|
|
653
|
+
this.#pos++;
|
|
654
|
+
} else break;
|
|
655
|
+
}
|
|
656
|
+
return ref.replace(/\$/g, "");
|
|
657
|
+
}
|
|
658
|
+
#readOperator() {
|
|
659
|
+
const start = this.#pos;
|
|
660
|
+
const ch = this.#formula[this.#pos++];
|
|
661
|
+
switch (ch) {
|
|
662
|
+
case "(":
|
|
663
|
+
return makeToken(6 /* LeftParen */, "(", start);
|
|
664
|
+
case ")":
|
|
665
|
+
return makeToken(7 /* RightParen */, ")", start);
|
|
666
|
+
case ",":
|
|
667
|
+
return makeToken(8 /* Comma */, ",", start);
|
|
668
|
+
case ":":
|
|
669
|
+
return makeToken(4 /* Colon */, ":", start);
|
|
670
|
+
case "+":
|
|
671
|
+
return makeToken(9 /* Plus */, "+", start);
|
|
672
|
+
case "-":
|
|
673
|
+
return makeToken(10 /* Minus */, "-", start);
|
|
674
|
+
case "*":
|
|
675
|
+
return makeToken(11 /* Multiply */, "*", start);
|
|
676
|
+
case "/":
|
|
677
|
+
return makeToken(12 /* Divide */, "/", start);
|
|
678
|
+
case "^":
|
|
679
|
+
return makeToken(13 /* Power */, "^", start);
|
|
680
|
+
case "%":
|
|
681
|
+
return makeToken(14 /* Percent */, "%", start);
|
|
682
|
+
case "&":
|
|
683
|
+
return makeToken(15 /* Ampersand */, "&", start);
|
|
684
|
+
case "=":
|
|
685
|
+
return makeToken(16 /* Equal */, "=", start);
|
|
686
|
+
case "@":
|
|
687
|
+
return makeToken(26 /* AtSign */, "@", start);
|
|
688
|
+
case "!":
|
|
689
|
+
return makeToken(25 /* Exclamation */, "!", start);
|
|
690
|
+
case "{":
|
|
691
|
+
return makeToken(27 /* LeftBrace */, "{", start);
|
|
692
|
+
case "}":
|
|
693
|
+
return makeToken(28 /* RightBrace */, "}", start);
|
|
694
|
+
// Inside an array constant ';' separates rows. Excel always stores ',' as
|
|
695
|
+
// the argument separator regardless of locale, so ';' has no other role.
|
|
696
|
+
case ";":
|
|
697
|
+
return makeToken(29 /* Semicolon */, ";", start);
|
|
698
|
+
case "<":
|
|
699
|
+
if (this.#formula[this.#pos] === ">") {
|
|
700
|
+
this.#pos++;
|
|
701
|
+
return makeToken(17 /* NotEqual */, "<>", start);
|
|
702
|
+
}
|
|
703
|
+
if (this.#formula[this.#pos] === "=") {
|
|
704
|
+
this.#pos++;
|
|
705
|
+
return makeToken(19 /* LessThanOrEqual */, "<=", start);
|
|
706
|
+
}
|
|
707
|
+
return makeToken(18 /* LessThan */, "<", start);
|
|
708
|
+
case ">":
|
|
709
|
+
if (this.#formula[this.#pos] === "=") {
|
|
710
|
+
this.#pos++;
|
|
711
|
+
return makeToken(21 /* GreaterThanOrEqual */, ">=", start);
|
|
712
|
+
}
|
|
713
|
+
return makeToken(20 /* GreaterThan */, ">", start);
|
|
714
|
+
default:
|
|
715
|
+
throw new FormulaException(`Unexpected character '${ch}' at position ${start}.`);
|
|
716
|
+
}
|
|
738
717
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
718
|
+
#skipWhitespace() {
|
|
719
|
+
while (this.#pos < this.#formula.length && /\s/.test(this.#formula[this.#pos])) {
|
|
720
|
+
this.#pos++;
|
|
721
|
+
}
|
|
743
722
|
}
|
|
744
723
|
};
|
|
745
724
|
function isCellRef(s) {
|
|
@@ -768,6 +747,7 @@ var NumberNode = class extends FormulaNode {
|
|
|
768
747
|
super();
|
|
769
748
|
this.value = value2;
|
|
770
749
|
}
|
|
750
|
+
value;
|
|
771
751
|
evaluate() {
|
|
772
752
|
return FormulaValue.number(this.value);
|
|
773
753
|
}
|
|
@@ -777,6 +757,7 @@ var StringNode = class extends FormulaNode {
|
|
|
777
757
|
super();
|
|
778
758
|
this.value = value2;
|
|
779
759
|
}
|
|
760
|
+
value;
|
|
780
761
|
evaluate() {
|
|
781
762
|
return FormulaValue.text(this.value);
|
|
782
763
|
}
|
|
@@ -786,6 +767,7 @@ var BooleanNode = class extends FormulaNode {
|
|
|
786
767
|
super();
|
|
787
768
|
this.value = value2;
|
|
788
769
|
}
|
|
770
|
+
value;
|
|
789
771
|
evaluate() {
|
|
790
772
|
return FormulaValue.boolean(this.value);
|
|
791
773
|
}
|
|
@@ -795,29 +777,30 @@ var ErrorNode = class extends FormulaNode {
|
|
|
795
777
|
super();
|
|
796
778
|
this.errorValue = errorValue;
|
|
797
779
|
}
|
|
780
|
+
errorValue;
|
|
798
781
|
evaluate() {
|
|
799
782
|
return this.errorValue;
|
|
800
783
|
}
|
|
801
784
|
};
|
|
802
|
-
var _value;
|
|
803
785
|
var ArrayLiteralNode = class extends FormulaNode {
|
|
804
786
|
constructor(array) {
|
|
805
787
|
super();
|
|
806
788
|
this.array = array;
|
|
807
|
-
|
|
808
|
-
__privateSet(this, _value, FormulaValue.array(array));
|
|
789
|
+
this.#value = FormulaValue.array(array);
|
|
809
790
|
}
|
|
791
|
+
array;
|
|
792
|
+
#value;
|
|
810
793
|
evaluate() {
|
|
811
|
-
return
|
|
794
|
+
return this.#value;
|
|
812
795
|
}
|
|
813
796
|
};
|
|
814
|
-
_value = new WeakMap();
|
|
815
797
|
var ValueNode = class extends FormulaNode {
|
|
816
798
|
/** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
|
|
817
799
|
constructor(value2) {
|
|
818
800
|
super();
|
|
819
801
|
this.value = value2;
|
|
820
802
|
}
|
|
803
|
+
value;
|
|
821
804
|
evaluate() {
|
|
822
805
|
return this.value;
|
|
823
806
|
}
|
|
@@ -832,6 +815,7 @@ var CellReferenceNode = class extends FormulaNode {
|
|
|
832
815
|
super();
|
|
833
816
|
this.reference = reference;
|
|
834
817
|
}
|
|
818
|
+
reference;
|
|
835
819
|
evaluate(ctx) {
|
|
836
820
|
return ctx.getCellValue(this.reference);
|
|
837
821
|
}
|
|
@@ -842,6 +826,8 @@ var RangeReferenceNode = class extends FormulaNode {
|
|
|
842
826
|
this.startRef = startRef;
|
|
843
827
|
this.endRef = endRef;
|
|
844
828
|
}
|
|
829
|
+
startRef;
|
|
830
|
+
endRef;
|
|
845
831
|
evaluate(ctx) {
|
|
846
832
|
return ctx.getRangeValues(this.startRef, this.endRef);
|
|
847
833
|
}
|
|
@@ -852,6 +838,8 @@ var SheetCellReferenceNode = class extends FormulaNode {
|
|
|
852
838
|
this.sheetName = sheetName;
|
|
853
839
|
this.cellReference = cellReference;
|
|
854
840
|
}
|
|
841
|
+
sheetName;
|
|
842
|
+
cellReference;
|
|
855
843
|
evaluate(ctx) {
|
|
856
844
|
return ctx.getSheetCellValue(this.sheetName, this.cellReference);
|
|
857
845
|
}
|
|
@@ -863,6 +851,9 @@ var SheetRangeReferenceNode = class extends FormulaNode {
|
|
|
863
851
|
this.startRef = startRef;
|
|
864
852
|
this.endRef = endRef;
|
|
865
853
|
}
|
|
854
|
+
sheetName;
|
|
855
|
+
startRef;
|
|
856
|
+
endRef;
|
|
866
857
|
evaluate(ctx) {
|
|
867
858
|
return ctx.getSheetRangeValues(this.sheetName, this.startRef, this.endRef);
|
|
868
859
|
}
|
|
@@ -876,6 +867,11 @@ var FullRangeReferenceNode = class extends FormulaNode {
|
|
|
876
867
|
this.endRow = endRow;
|
|
877
868
|
this.sheetName = sheetName;
|
|
878
869
|
}
|
|
870
|
+
startCol;
|
|
871
|
+
endCol;
|
|
872
|
+
startRow;
|
|
873
|
+
endRow;
|
|
874
|
+
sheetName;
|
|
879
875
|
evaluate(ctx) {
|
|
880
876
|
return this.sheetName === void 0 ? ctx.getFullRangeValues(this.startCol, this.endCol, this.startRow, this.endRow) : ctx.getSheetFullRangeValues(this.sheetName, this.startCol, this.endCol, this.startRow, this.endRow);
|
|
881
877
|
}
|
|
@@ -885,6 +881,7 @@ var NamedRangeNode = class extends FormulaNode {
|
|
|
885
881
|
super();
|
|
886
882
|
this.name = name;
|
|
887
883
|
}
|
|
884
|
+
name;
|
|
888
885
|
evaluate(ctx) {
|
|
889
886
|
return ctx.resolveNamedRange(this.name);
|
|
890
887
|
}
|
|
@@ -894,6 +891,7 @@ var SpillReferenceNode = class extends FormulaNode {
|
|
|
894
891
|
super();
|
|
895
892
|
this.anchorRef = anchorRef;
|
|
896
893
|
}
|
|
894
|
+
anchorRef;
|
|
897
895
|
evaluate(ctx) {
|
|
898
896
|
return ctx.resolveSpillRange(this.anchorRef);
|
|
899
897
|
}
|
|
@@ -919,15 +917,16 @@ var UnaryOperator = /* @__PURE__ */ ((UnaryOperator2) => {
|
|
|
919
917
|
UnaryOperator2[UnaryOperator2["Percent"] = 2] = "Percent";
|
|
920
918
|
return UnaryOperator2;
|
|
921
919
|
})(UnaryOperator || {});
|
|
922
|
-
var _BinaryOpNode_instances, evalScalar_fn;
|
|
923
920
|
var BinaryOpNode = class extends FormulaNode {
|
|
924
921
|
constructor(left2, op, right2) {
|
|
925
922
|
super();
|
|
926
923
|
this.left = left2;
|
|
927
924
|
this.op = op;
|
|
928
925
|
this.right = right2;
|
|
929
|
-
__privateAdd(this, _BinaryOpNode_instances);
|
|
930
926
|
}
|
|
927
|
+
left;
|
|
928
|
+
op;
|
|
929
|
+
right;
|
|
931
930
|
evaluate(ctx) {
|
|
932
931
|
const l = this.left.evaluate(ctx);
|
|
933
932
|
const r = this.right.evaluate(ctx);
|
|
@@ -935,46 +934,45 @@ var BinaryOpNode = class extends FormulaNode {
|
|
|
935
934
|
const la = l.isArray ? l.arrayVal : ArrayValue.fromScalar(l);
|
|
936
935
|
const ra = r.isArray ? r.arrayVal : ArrayValue.fromScalar(r);
|
|
937
936
|
return FormulaValue.array(
|
|
938
|
-
ArrayValue.broadcast(la, ra, (a, b) =>
|
|
937
|
+
ArrayValue.broadcast(la, ra, (a, b) => this.#evalScalar(a, b))
|
|
939
938
|
);
|
|
940
939
|
}
|
|
941
|
-
return
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
return FormulaValue.errorValue;
|
|
940
|
+
return this.#evalScalar(l, r);
|
|
941
|
+
}
|
|
942
|
+
#evalScalar(l, r) {
|
|
943
|
+
const op = this.op;
|
|
944
|
+
if (op === 6 /* Equal */) return FormulaValue.boolean(FormulaValue.areEqual(l, r));
|
|
945
|
+
if (op === 7 /* NotEqual */) return FormulaValue.boolean(!FormulaValue.areEqual(l, r));
|
|
946
|
+
if (op === 8 /* LessThan */) return FormulaValue.boolean(FormulaValue.compare(l, r) < 0);
|
|
947
|
+
if (op === 9 /* LessThanOrEqual */) return FormulaValue.boolean(FormulaValue.compare(l, r) <= 0);
|
|
948
|
+
if (op === 10 /* GreaterThan */) return FormulaValue.boolean(FormulaValue.compare(l, r) > 0);
|
|
949
|
+
if (op === 11 /* GreaterThanOrEqual */) return FormulaValue.boolean(FormulaValue.compare(l, r) >= 0);
|
|
950
|
+
if (op === 5 /* Concatenate */) {
|
|
951
|
+
if (l.isError) return l;
|
|
952
|
+
if (r.isError) return r;
|
|
953
|
+
return FormulaValue.text(l.asText() + r.asText());
|
|
954
|
+
}
|
|
955
|
+
const ln2 = l.coerceToNumber();
|
|
956
|
+
if (ln2.isError) return ln2;
|
|
957
|
+
const rn = r.coerceToNumber();
|
|
958
|
+
if (rn.isError) return rn;
|
|
959
|
+
const lv = ln2.numberValue;
|
|
960
|
+
const rv = rn.numberValue;
|
|
961
|
+
switch (op) {
|
|
962
|
+
case 0 /* Add */:
|
|
963
|
+
return FormulaValue.number(lv + rv);
|
|
964
|
+
case 1 /* Subtract */:
|
|
965
|
+
return FormulaValue.number(lv - rv);
|
|
966
|
+
case 2 /* Multiply */:
|
|
967
|
+
return FormulaValue.number(lv * rv);
|
|
968
|
+
case 3 /* Divide */:
|
|
969
|
+
if (rv === 0) return FormulaValue.errorDiv0;
|
|
970
|
+
return FormulaValue.number(lv / rv);
|
|
971
|
+
case 4 /* Power */:
|
|
972
|
+
return FormulaValue.number(Math.pow(lv, rv));
|
|
973
|
+
default:
|
|
974
|
+
return FormulaValue.errorValue;
|
|
975
|
+
}
|
|
978
976
|
}
|
|
979
977
|
};
|
|
980
978
|
var UnaryOpNode = class extends FormulaNode {
|
|
@@ -983,6 +981,8 @@ var UnaryOpNode = class extends FormulaNode {
|
|
|
983
981
|
this.op = op;
|
|
984
982
|
this.operand = operand;
|
|
985
983
|
}
|
|
984
|
+
op;
|
|
985
|
+
operand;
|
|
986
986
|
evaluate(ctx) {
|
|
987
987
|
const val = this.operand.evaluate(ctx);
|
|
988
988
|
if (this.op === 1 /* Plus */) {
|
|
@@ -1006,6 +1006,7 @@ var ImplicitIntersectionNode = class extends FormulaNode {
|
|
|
1006
1006
|
super();
|
|
1007
1007
|
this.inner = inner;
|
|
1008
1008
|
}
|
|
1009
|
+
inner;
|
|
1009
1010
|
evaluate(ctx) {
|
|
1010
1011
|
const val = this.inner.evaluate(ctx);
|
|
1011
1012
|
if (!val.isArray) return val;
|
|
@@ -1018,6 +1019,8 @@ var FunctionCallNode = class extends FormulaNode {
|
|
|
1018
1019
|
this.functionName = functionName;
|
|
1019
1020
|
this.args = args;
|
|
1020
1021
|
}
|
|
1022
|
+
functionName;
|
|
1023
|
+
args;
|
|
1021
1024
|
evaluate(ctx) {
|
|
1022
1025
|
return ctx.evaluateFunction(this.functionName, this.args);
|
|
1023
1026
|
}
|
|
@@ -1084,6 +1087,8 @@ var CallNode = class extends FormulaNode {
|
|
|
1084
1087
|
this.callee = callee;
|
|
1085
1088
|
this.args = args;
|
|
1086
1089
|
}
|
|
1090
|
+
callee;
|
|
1091
|
+
args;
|
|
1087
1092
|
evaluate(ctx) {
|
|
1088
1093
|
const fn = this.callee.evaluate(ctx);
|
|
1089
1094
|
if (fn.isError) return fn;
|
|
@@ -1099,317 +1104,313 @@ var CallNode = class extends FormulaNode {
|
|
|
1099
1104
|
};
|
|
1100
1105
|
|
|
1101
1106
|
// src/formula-parser.ts
|
|
1102
|
-
var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, tryParseFullRange_fn, parseArrayLiteral_fn, parseArrayElement_fn, parseFunction_fn, parseArgList_fn;
|
|
1103
1107
|
var FormulaParser = class {
|
|
1108
|
+
#tokens;
|
|
1109
|
+
#pos = 0;
|
|
1104
1110
|
constructor(tokens) {
|
|
1105
|
-
|
|
1106
|
-
__privateAdd(this, _tokens);
|
|
1107
|
-
__privateAdd(this, _pos2, 0);
|
|
1108
|
-
__privateSet(this, _tokens, tokens);
|
|
1111
|
+
this.#tokens = tokens;
|
|
1109
1112
|
}
|
|
1110
1113
|
parse() {
|
|
1111
|
-
|
|
1112
|
-
const node =
|
|
1113
|
-
if (
|
|
1114
|
+
this.#pos = 0;
|
|
1115
|
+
const node = this.#parseComparison();
|
|
1116
|
+
if (this.#current.type !== 31 /* EOF */) {
|
|
1114
1117
|
throw new FormulaException(
|
|
1115
|
-
`Unexpected token '${
|
|
1118
|
+
`Unexpected token '${this.#current.value}' at position ${this.#current.position}.`
|
|
1116
1119
|
);
|
|
1117
1120
|
}
|
|
1118
1121
|
return node;
|
|
1119
1122
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
_FormulaParser_instances = new WeakSet();
|
|
1124
|
-
current_get = function() {
|
|
1125
|
-
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 31 /* EOF */, value: "", position: -1 };
|
|
1126
|
-
};
|
|
1127
|
-
advance_fn = function() {
|
|
1128
|
-
const t = __privateGet(this, _FormulaParser_instances, current_get);
|
|
1129
|
-
__privateWrapper(this, _pos2)._++;
|
|
1130
|
-
return t;
|
|
1131
|
-
};
|
|
1132
|
-
expect_fn = function(type) {
|
|
1133
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type !== type) {
|
|
1134
|
-
throw new FormulaException(
|
|
1135
|
-
`Expected token type ${type} but got ${__privateGet(this, _FormulaParser_instances, current_get).type} ('${__privateGet(this, _FormulaParser_instances, current_get).value}') at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
|
|
1136
|
-
);
|
|
1137
|
-
}
|
|
1138
|
-
return __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1139
|
-
};
|
|
1140
|
-
// ── Precedence levels ──────────────────────────────────────────────────────
|
|
1141
|
-
parseComparison_fn = function() {
|
|
1142
|
-
let left2 = __privateMethod(this, _FormulaParser_instances, parseConcatenation_fn).call(this);
|
|
1143
|
-
while (true) {
|
|
1144
|
-
const op = comparisonOp(__privateGet(this, _FormulaParser_instances, current_get).type);
|
|
1145
|
-
if (op === null) break;
|
|
1146
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1147
|
-
left2 = new BinaryOpNode(left2, op, __privateMethod(this, _FormulaParser_instances, parseConcatenation_fn).call(this));
|
|
1148
|
-
}
|
|
1149
|
-
return left2;
|
|
1150
|
-
};
|
|
1151
|
-
parseConcatenation_fn = function() {
|
|
1152
|
-
let left2 = __privateMethod(this, _FormulaParser_instances, parseAddSub_fn).call(this);
|
|
1153
|
-
while (__privateGet(this, _FormulaParser_instances, current_get).type === 15 /* Ampersand */) {
|
|
1154
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1155
|
-
left2 = new BinaryOpNode(left2, 5 /* Concatenate */, __privateMethod(this, _FormulaParser_instances, parseAddSub_fn).call(this));
|
|
1156
|
-
}
|
|
1157
|
-
return left2;
|
|
1158
|
-
};
|
|
1159
|
-
parseAddSub_fn = function() {
|
|
1160
|
-
let left2 = __privateMethod(this, _FormulaParser_instances, parseMulDiv_fn).call(this);
|
|
1161
|
-
while (true) {
|
|
1162
|
-
const t = __privateGet(this, _FormulaParser_instances, current_get).type;
|
|
1163
|
-
if (t !== 9 /* Plus */ && t !== 10 /* Minus */) break;
|
|
1164
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1165
|
-
const op = t === 9 /* Plus */ ? 0 /* Add */ : 1 /* Subtract */;
|
|
1166
|
-
left2 = new BinaryOpNode(left2, op, __privateMethod(this, _FormulaParser_instances, parseMulDiv_fn).call(this));
|
|
1167
|
-
}
|
|
1168
|
-
return left2;
|
|
1169
|
-
};
|
|
1170
|
-
parseMulDiv_fn = function() {
|
|
1171
|
-
let left2 = __privateMethod(this, _FormulaParser_instances, parseUnary_fn).call(this);
|
|
1172
|
-
while (true) {
|
|
1173
|
-
const t = __privateGet(this, _FormulaParser_instances, current_get).type;
|
|
1174
|
-
if (t !== 11 /* Multiply */ && t !== 12 /* Divide */) break;
|
|
1175
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1176
|
-
const op = t === 11 /* Multiply */ ? 2 /* Multiply */ : 3 /* Divide */;
|
|
1177
|
-
left2 = new BinaryOpNode(left2, op, __privateMethod(this, _FormulaParser_instances, parseUnary_fn).call(this));
|
|
1178
|
-
}
|
|
1179
|
-
return left2;
|
|
1180
|
-
};
|
|
1181
|
-
parseUnary_fn = function() {
|
|
1182
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) {
|
|
1183
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1184
|
-
return new UnaryOpNode(0 /* Negate */, __privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
|
|
1185
|
-
}
|
|
1186
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
|
|
1187
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1188
|
-
return new UnaryOpNode(1 /* Plus */, __privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
|
|
1123
|
+
// ── Token helpers ──────────────────────────────────────────────────────────
|
|
1124
|
+
get #current() {
|
|
1125
|
+
return this.#tokens[this.#pos] ?? { type: 31 /* EOF */, value: "", position: -1 };
|
|
1189
1126
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1127
|
+
#advance() {
|
|
1128
|
+
const t = this.#current;
|
|
1129
|
+
this.#pos++;
|
|
1130
|
+
return t;
|
|
1193
1131
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
return
|
|
1132
|
+
#expect(type) {
|
|
1133
|
+
if (this.#current.type !== type) {
|
|
1134
|
+
throw new FormulaException(
|
|
1135
|
+
`Expected token type ${type} but got ${this.#current.type} ('${this.#current.value}') at position ${this.#current.position}.`
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
return this.#advance();
|
|
1139
|
+
}
|
|
1140
|
+
// ── Precedence levels ──────────────────────────────────────────────────────
|
|
1141
|
+
#parseComparison() {
|
|
1142
|
+
let left2 = this.#parseConcatenation();
|
|
1143
|
+
while (true) {
|
|
1144
|
+
const op = comparisonOp(this.#current.type);
|
|
1145
|
+
if (op === null) break;
|
|
1146
|
+
this.#advance();
|
|
1147
|
+
left2 = new BinaryOpNode(left2, op, this.#parseConcatenation());
|
|
1148
|
+
}
|
|
1149
|
+
return left2;
|
|
1150
|
+
}
|
|
1151
|
+
#parseConcatenation() {
|
|
1152
|
+
let left2 = this.#parseAddSub();
|
|
1153
|
+
while (this.#current.type === 15 /* Ampersand */) {
|
|
1154
|
+
this.#advance();
|
|
1155
|
+
left2 = new BinaryOpNode(left2, 5 /* Concatenate */, this.#parseAddSub());
|
|
1156
|
+
}
|
|
1157
|
+
return left2;
|
|
1158
|
+
}
|
|
1159
|
+
#parseAddSub() {
|
|
1160
|
+
let left2 = this.#parseMulDiv();
|
|
1161
|
+
while (true) {
|
|
1162
|
+
const t = this.#current.type;
|
|
1163
|
+
if (t !== 9 /* Plus */ && t !== 10 /* Minus */) break;
|
|
1164
|
+
this.#advance();
|
|
1165
|
+
const op = t === 9 /* Plus */ ? 0 /* Add */ : 1 /* Subtract */;
|
|
1166
|
+
left2 = new BinaryOpNode(left2, op, this.#parseMulDiv());
|
|
1167
|
+
}
|
|
1168
|
+
return left2;
|
|
1169
|
+
}
|
|
1170
|
+
#parseMulDiv() {
|
|
1171
|
+
let left2 = this.#parseUnary();
|
|
1172
|
+
while (true) {
|
|
1173
|
+
const t = this.#current.type;
|
|
1174
|
+
if (t !== 11 /* Multiply */ && t !== 12 /* Divide */) break;
|
|
1175
|
+
this.#advance();
|
|
1176
|
+
const op = t === 11 /* Multiply */ ? 2 /* Multiply */ : 3 /* Divide */;
|
|
1177
|
+
left2 = new BinaryOpNode(left2, op, this.#parseUnary());
|
|
1178
|
+
}
|
|
1179
|
+
return left2;
|
|
1180
|
+
}
|
|
1181
|
+
#parseUnary() {
|
|
1182
|
+
if (this.#current.type === 10 /* Minus */) {
|
|
1183
|
+
this.#advance();
|
|
1184
|
+
return new UnaryOpNode(0 /* Negate */, this.#parsePower());
|
|
1185
|
+
}
|
|
1186
|
+
if (this.#current.type === 9 /* Plus */) {
|
|
1187
|
+
this.#advance();
|
|
1188
|
+
return new UnaryOpNode(1 /* Plus */, this.#parsePower());
|
|
1189
|
+
}
|
|
1190
|
+
if (this.#current.type === 26 /* AtSign */) {
|
|
1191
|
+
this.#advance();
|
|
1192
|
+
return new ImplicitIntersectionNode(this.#parsePower());
|
|
1193
|
+
}
|
|
1194
|
+
return this.#parsePower();
|
|
1195
|
+
}
|
|
1196
|
+
#parsePower() {
|
|
1197
|
+
const left2 = this.#parsePercent();
|
|
1198
|
+
if (this.#current.type === 13 /* Power */) {
|
|
1199
|
+
this.#advance();
|
|
1200
|
+
return new BinaryOpNode(left2, 4 /* Power */, this.#parseUnary());
|
|
1201
|
+
}
|
|
1202
|
+
return left2;
|
|
1203
|
+
}
|
|
1204
|
+
#parsePercent() {
|
|
1205
|
+
let node = this.#parsePostfix();
|
|
1206
|
+
while (this.#current.type === 14 /* Percent */) {
|
|
1207
|
+
this.#advance();
|
|
1208
|
+
node = new UnaryOpNode(2 /* Percent */, node);
|
|
1209
|
+
}
|
|
1210
|
+
return node;
|
|
1201
1211
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1212
|
+
/** Handles postfix call syntax on an expression, e.g. LAMBDA(x,x+1)(5). */
|
|
1213
|
+
#parsePostfix() {
|
|
1214
|
+
let node = this.#parsePrimary();
|
|
1215
|
+
while (this.#current.type === 6 /* LeftParen */) {
|
|
1216
|
+
this.#advance();
|
|
1217
|
+
node = new CallNode(node, this.#parseArgList());
|
|
1218
|
+
}
|
|
1219
|
+
return node;
|
|
1209
1220
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
};
|
|
1221
|
-
|
|
1222
|
-
const token = __privateGet(this, _FormulaParser_instances, current_get);
|
|
1223
|
-
switch (token.type) {
|
|
1224
|
-
case 0 /* Number */: {
|
|
1225
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1226
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1227
|
-
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, token.value);
|
|
1228
|
-
if (full) return full;
|
|
1221
|
+
#parsePrimary() {
|
|
1222
|
+
const token = this.#current;
|
|
1223
|
+
switch (token.type) {
|
|
1224
|
+
case 0 /* Number */: {
|
|
1225
|
+
this.#advance();
|
|
1226
|
+
if (this.#current.type === 4 /* Colon */) {
|
|
1227
|
+
const full = this.#tryParseFullRange(token.value);
|
|
1228
|
+
if (full) return full;
|
|
1229
|
+
}
|
|
1230
|
+
const n = parseFloat(token.value);
|
|
1231
|
+
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1232
|
+
return new NumberNode(n);
|
|
1229
1233
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const end = __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 3 /* CellReference */);
|
|
1248
|
-
return new RangeReferenceNode(token.value, end.value);
|
|
1234
|
+
case 1 /* String */:
|
|
1235
|
+
this.#advance();
|
|
1236
|
+
return new StringNode(token.value);
|
|
1237
|
+
case 2 /* Boolean */:
|
|
1238
|
+
this.#advance();
|
|
1239
|
+
return new BooleanNode(token.value === "TRUE");
|
|
1240
|
+
case 30 /* ErrorLiteral */:
|
|
1241
|
+
this.#advance();
|
|
1242
|
+
return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
|
|
1243
|
+
case 3 /* CellReference */: {
|
|
1244
|
+
this.#advance();
|
|
1245
|
+
if (this.#current.type === 4 /* Colon */) {
|
|
1246
|
+
this.#advance();
|
|
1247
|
+
const end = this.#expect(3 /* CellReference */);
|
|
1248
|
+
return new RangeReferenceNode(token.value, end.value);
|
|
1249
|
+
}
|
|
1250
|
+
return new CellReferenceNode(token.value);
|
|
1249
1251
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
return new
|
|
1252
|
+
case 22 /* SheetReference */: {
|
|
1253
|
+
this.#advance();
|
|
1254
|
+
const parts = token.value.split("!");
|
|
1255
|
+
const sheetName = parts[0];
|
|
1256
|
+
const cellRef = parts[1] ?? "";
|
|
1257
|
+
if (this.#current.type === 4 /* Colon */) {
|
|
1258
|
+
const full = this.#tryParseFullRange(cellRef, sheetName);
|
|
1259
|
+
if (full) return full;
|
|
1260
|
+
this.#advance();
|
|
1261
|
+
const end = this.#expect(3 /* CellReference */);
|
|
1262
|
+
return new SheetRangeReferenceNode(sheetName, cellRef, end.value);
|
|
1263
|
+
}
|
|
1264
|
+
return new SheetCellReferenceNode(sheetName, cellRef);
|
|
1263
1265
|
}
|
|
1264
|
-
|
|
1266
|
+
case 23 /* NamedRange */: {
|
|
1267
|
+
this.#advance();
|
|
1268
|
+
if (this.#current.type === 4 /* Colon */) {
|
|
1269
|
+
const full = this.#tryParseFullRange(token.value);
|
|
1270
|
+
if (full) return full;
|
|
1271
|
+
}
|
|
1272
|
+
return new NamedRangeNode(token.value);
|
|
1273
|
+
}
|
|
1274
|
+
case 24 /* SpillReference */:
|
|
1275
|
+
this.#advance();
|
|
1276
|
+
return new SpillReferenceNode(token.value);
|
|
1277
|
+
case 5 /* Function */:
|
|
1278
|
+
return this.#parseFunction();
|
|
1279
|
+
case 27 /* LeftBrace */:
|
|
1280
|
+
return this.#parseArrayLiteral();
|
|
1281
|
+
case 6 /* LeftParen */: {
|
|
1282
|
+
this.#advance();
|
|
1283
|
+
const expr = this.#parseComparison();
|
|
1284
|
+
this.#expect(7 /* RightParen */);
|
|
1285
|
+
return expr;
|
|
1286
|
+
}
|
|
1287
|
+
default:
|
|
1288
|
+
throw new FormulaException(
|
|
1289
|
+
`Unexpected token '${token.value}' (type ${token.type}) at position ${token.position}.`
|
|
1290
|
+
);
|
|
1265
1291
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Completes a whole-column or whole-row reference whose first half has already
|
|
1295
|
+
* been consumed and whose ':' is the current token. Returns null — consuming
|
|
1296
|
+
* nothing — when the two halves are not a matching pair of columns or rows, so
|
|
1297
|
+
* the caller can fall back to its normal interpretation.
|
|
1298
|
+
*
|
|
1299
|
+
* Both halves must be the same kind: `A:A` and `1:5` are ranges, `A:1` is not
|
|
1300
|
+
* a reference at all in Excel and must stay an error rather than becoming a
|
|
1301
|
+
* silently different region.
|
|
1302
|
+
*/
|
|
1303
|
+
#tryParseFullRange(startText, sheetName) {
|
|
1304
|
+
const endToken = this.#tokens[this.#pos + 1];
|
|
1305
|
+
if (!endToken) return null;
|
|
1306
|
+
const start = classifyBound(startText);
|
|
1307
|
+
const end = classifyBound(endToken.value);
|
|
1308
|
+
if (!start || !end || start.kind !== end.kind) return null;
|
|
1309
|
+
this.#advance();
|
|
1310
|
+
this.#advance();
|
|
1311
|
+
const lo = Math.min(start.index, end.index);
|
|
1312
|
+
const hi = Math.max(start.index, end.index);
|
|
1313
|
+
return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Parses an array constant: `{1,2;3,4}` — ',' separates columns, ';' rows.
|
|
1317
|
+
*
|
|
1318
|
+
* Excel permits only literals inside one (no references, no calls, not even
|
|
1319
|
+
* arithmetic beyond a leading sign), so the value is fully determined here and
|
|
1320
|
+
* is materialised once into the node instead of being rebuilt on every eval.
|
|
1321
|
+
*
|
|
1322
|
+
* A ragged constant is rejected rather than padded: Excel refuses to accept
|
|
1323
|
+
* one at entry, so a file cannot legitimately contain it, and silently filling
|
|
1324
|
+
* the gaps would invent data the author never wrote.
|
|
1325
|
+
*/
|
|
1326
|
+
#parseArrayLiteral() {
|
|
1327
|
+
const open = this.#advance();
|
|
1328
|
+
const rows2 = [];
|
|
1329
|
+
let row2 = [];
|
|
1330
|
+
for (; ; ) {
|
|
1331
|
+
row2.push(this.#parseArrayElement());
|
|
1332
|
+
if (this.#current.type === 8 /* Comma */) {
|
|
1333
|
+
this.#advance();
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (this.#current.type === 29 /* Semicolon */) {
|
|
1337
|
+
this.#advance();
|
|
1338
|
+
rows2.push(row2);
|
|
1339
|
+
row2 = [];
|
|
1340
|
+
continue;
|
|
1271
1341
|
}
|
|
1272
|
-
|
|
1273
|
-
}
|
|
1274
|
-
case 24 /* SpillReference */:
|
|
1275
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1276
|
-
return new SpillReferenceNode(token.value);
|
|
1277
|
-
case 5 /* Function */:
|
|
1278
|
-
return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
|
|
1279
|
-
case 27 /* LeftBrace */:
|
|
1280
|
-
return __privateMethod(this, _FormulaParser_instances, parseArrayLiteral_fn).call(this);
|
|
1281
|
-
case 6 /* LeftParen */: {
|
|
1282
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1283
|
-
const expr = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
1284
|
-
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 7 /* RightParen */);
|
|
1285
|
-
return expr;
|
|
1342
|
+
break;
|
|
1286
1343
|
}
|
|
1287
|
-
|
|
1344
|
+
rows2.push(row2);
|
|
1345
|
+
this.#expect(28 /* RightBrace */);
|
|
1346
|
+
const cols = rows2[0].length;
|
|
1347
|
+
if (rows2.some((r) => r.length !== cols)) {
|
|
1288
1348
|
throw new FormulaException(
|
|
1289
|
-
`
|
|
1349
|
+
`Array constant at position ${open.position} has rows of differing lengths.`
|
|
1290
1350
|
);
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
*/
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
* the gaps would invent data the author never wrote.
|
|
1325
|
-
*/
|
|
1326
|
-
parseArrayLiteral_fn = function() {
|
|
1327
|
-
const open = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1328
|
-
const rows2 = [];
|
|
1329
|
-
let row2 = [];
|
|
1330
|
-
for (; ; ) {
|
|
1331
|
-
row2.push(__privateMethod(this, _FormulaParser_instances, parseArrayElement_fn).call(this));
|
|
1332
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
1333
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1334
|
-
continue;
|
|
1335
|
-
}
|
|
1336
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 29 /* Semicolon */) {
|
|
1337
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1338
|
-
rows2.push(row2);
|
|
1339
|
-
row2 = [];
|
|
1340
|
-
continue;
|
|
1341
|
-
}
|
|
1342
|
-
break;
|
|
1343
|
-
}
|
|
1344
|
-
rows2.push(row2);
|
|
1345
|
-
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 28 /* RightBrace */);
|
|
1346
|
-
const cols = rows2[0].length;
|
|
1347
|
-
if (rows2.some((r) => r.length !== cols)) {
|
|
1351
|
+
}
|
|
1352
|
+
const array = new ArrayValue(rows2.length, cols);
|
|
1353
|
+
for (let r = 0; r < rows2.length; r++) {
|
|
1354
|
+
for (let c = 0; c < cols; c++) array.set(r, c, rows2[r][c]);
|
|
1355
|
+
}
|
|
1356
|
+
return new ArrayLiteralNode(array);
|
|
1357
|
+
}
|
|
1358
|
+
/** One element of an array constant: a literal, optionally signed. */
|
|
1359
|
+
#parseArrayElement() {
|
|
1360
|
+
let negate = false;
|
|
1361
|
+
let signed = false;
|
|
1362
|
+
while (this.#current.type === 10 /* Minus */ || this.#current.type === 9 /* Plus */) {
|
|
1363
|
+
if (this.#current.type === 10 /* Minus */) negate = !negate;
|
|
1364
|
+
signed = true;
|
|
1365
|
+
this.#advance();
|
|
1366
|
+
}
|
|
1367
|
+
const token = this.#advance();
|
|
1368
|
+
switch (token.type) {
|
|
1369
|
+
case 0 /* Number */: {
|
|
1370
|
+
const n = parseFloat(token.value);
|
|
1371
|
+
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1372
|
+
return FormulaValue.number(negate ? -n : n);
|
|
1373
|
+
}
|
|
1374
|
+
case 1 /* String */:
|
|
1375
|
+
if (signed) break;
|
|
1376
|
+
return FormulaValue.text(token.value);
|
|
1377
|
+
case 2 /* Boolean */:
|
|
1378
|
+
if (signed) break;
|
|
1379
|
+
return FormulaValue.boolean(token.value === "TRUE");
|
|
1380
|
+
case 30 /* ErrorLiteral */:
|
|
1381
|
+
if (signed) break;
|
|
1382
|
+
return FormulaValue.error(FormulaValue.errorFromString(token.value));
|
|
1383
|
+
}
|
|
1348
1384
|
throw new FormulaException(
|
|
1349
|
-
`Array
|
|
1385
|
+
`Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
|
|
1350
1386
|
);
|
|
1351
1387
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1388
|
+
#parseFunction() {
|
|
1389
|
+
const nameToken = this.#advance();
|
|
1390
|
+
this.#expect(6 /* LeftParen */);
|
|
1391
|
+
return new FunctionCallNode(nameToken.value.toUpperCase(), this.#parseArgList());
|
|
1355
1392
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
let signed = false;
|
|
1362
|
-
while (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */ || __privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
|
|
1363
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) negate = !negate;
|
|
1364
|
-
signed = true;
|
|
1365
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1366
|
-
}
|
|
1367
|
-
const token = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1368
|
-
switch (token.type) {
|
|
1369
|
-
case 0 /* Number */: {
|
|
1370
|
-
const n = parseFloat(token.value);
|
|
1371
|
-
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1372
|
-
return FormulaValue.number(negate ? -n : n);
|
|
1373
|
-
}
|
|
1374
|
-
case 1 /* String */:
|
|
1375
|
-
if (signed) break;
|
|
1376
|
-
return FormulaValue.text(token.value);
|
|
1377
|
-
case 2 /* Boolean */:
|
|
1378
|
-
if (signed) break;
|
|
1379
|
-
return FormulaValue.boolean(token.value === "TRUE");
|
|
1380
|
-
case 30 /* ErrorLiteral */:
|
|
1381
|
-
if (signed) break;
|
|
1382
|
-
return FormulaValue.error(FormulaValue.errorFromString(token.value));
|
|
1383
|
-
}
|
|
1384
|
-
throw new FormulaException(
|
|
1385
|
-
`Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
|
|
1386
|
-
);
|
|
1387
|
-
};
|
|
1388
|
-
parseFunction_fn = function() {
|
|
1389
|
-
const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1390
|
-
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
|
|
1391
|
-
return new FunctionCallNode(nameToken.value.toUpperCase(), __privateMethod(this, _FormulaParser_instances, parseArgList_fn).call(this));
|
|
1392
|
-
};
|
|
1393
|
-
/** Parses a comma-separated argument list up to and including the ')'. */
|
|
1394
|
-
parseArgList_fn = function() {
|
|
1395
|
-
const args = [];
|
|
1396
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 7 /* RightParen */) {
|
|
1397
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
1398
|
-
args.push(new BlankNode());
|
|
1399
|
-
} else {
|
|
1400
|
-
args.push(__privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this));
|
|
1401
|
-
}
|
|
1402
|
-
while (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
1403
|
-
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1404
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */ || __privateGet(this, _FormulaParser_instances, current_get).type === 7 /* RightParen */) {
|
|
1393
|
+
/** Parses a comma-separated argument list up to and including the ')'. */
|
|
1394
|
+
#parseArgList() {
|
|
1395
|
+
const args = [];
|
|
1396
|
+
if (this.#current.type !== 7 /* RightParen */) {
|
|
1397
|
+
if (this.#current.type === 8 /* Comma */) {
|
|
1405
1398
|
args.push(new BlankNode());
|
|
1406
1399
|
} else {
|
|
1407
|
-
args.push(
|
|
1400
|
+
args.push(this.#parseComparison());
|
|
1401
|
+
}
|
|
1402
|
+
while (this.#current.type === 8 /* Comma */) {
|
|
1403
|
+
this.#advance();
|
|
1404
|
+
if (this.#current.type === 8 /* Comma */ || this.#current.type === 7 /* RightParen */) {
|
|
1405
|
+
args.push(new BlankNode());
|
|
1406
|
+
} else {
|
|
1407
|
+
args.push(this.#parseComparison());
|
|
1408
|
+
}
|
|
1408
1409
|
}
|
|
1409
1410
|
}
|
|
1411
|
+
this.#expect(7 /* RightParen */);
|
|
1412
|
+
return args;
|
|
1410
1413
|
}
|
|
1411
|
-
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 7 /* RightParen */);
|
|
1412
|
-
return args;
|
|
1413
1414
|
};
|
|
1414
1415
|
function classifyBound(text) {
|
|
1415
1416
|
const bare = text.replace(/\$/g, "");
|
|
@@ -2840,8 +2841,9 @@ function vlookup(a, c) {
|
|
|
2840
2841
|
const tbl = tableVal.arrayVal;
|
|
2841
2842
|
if (colIdx < 0 || colIdx >= tbl.columns) return FormulaValue.errorRef;
|
|
2842
2843
|
if (exactMatch) {
|
|
2844
|
+
const pattern = lookupPattern(lookupVal);
|
|
2843
2845
|
for (let r = 0; r < tbl.rows; r++) {
|
|
2844
|
-
if (
|
|
2846
|
+
if (matchesLookup(tbl.get(r, 0), lookupVal, pattern)) return tbl.get(r, colIdx);
|
|
2845
2847
|
}
|
|
2846
2848
|
return FormulaValue.errorNA;
|
|
2847
2849
|
}
|
|
@@ -2870,8 +2872,9 @@ function hlookup(a, c) {
|
|
|
2870
2872
|
const tbl = tableVal.arrayVal;
|
|
2871
2873
|
if (rowIdx < 0 || rowIdx >= tbl.rows) return FormulaValue.errorRef;
|
|
2872
2874
|
if (exactMatch) {
|
|
2875
|
+
const pattern = lookupPattern(lookupVal);
|
|
2873
2876
|
for (let col = 0; col < tbl.columns; col++) {
|
|
2874
|
-
if (
|
|
2877
|
+
if (matchesLookup(tbl.get(0, col), lookupVal, pattern)) return tbl.get(rowIdx, col);
|
|
2875
2878
|
}
|
|
2876
2879
|
return FormulaValue.errorNA;
|
|
2877
2880
|
}
|
|
@@ -2882,18 +2885,13 @@ function hlookup(a, c) {
|
|
|
2882
2885
|
}
|
|
2883
2886
|
return best >= 0 ? tbl.get(rowIdx, best) : FormulaValue.errorNA;
|
|
2884
2887
|
}
|
|
2885
|
-
function
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
} else if (ch === "*") out += ".*";
|
|
2893
|
-
else if (ch === "?") out += ".";
|
|
2894
|
-
else out += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2895
|
-
}
|
|
2896
|
-
return new RegExp(`^${out}$`, "i");
|
|
2888
|
+
function lookupPattern(lookupVal) {
|
|
2889
|
+
if (!lookupVal.isText) return null;
|
|
2890
|
+
const text = lookupVal.textValue;
|
|
2891
|
+
return FormulaHelper.hasWildcardSyntax(text) ? FormulaHelper.wildcardToRegex(text) : null;
|
|
2892
|
+
}
|
|
2893
|
+
function matchesLookup(candidate, lookupVal, pattern) {
|
|
2894
|
+
return pattern === null ? FormulaValue.areEqual(candidate, lookupVal) : !candidate.isBlank && pattern.test(candidate.asText());
|
|
2897
2895
|
}
|
|
2898
2896
|
function findMatch(n, get, lookupVal, matchMode, searchMode) {
|
|
2899
2897
|
if ((searchMode === 2 || searchMode === -2) && matchMode !== 2) {
|
|
@@ -2917,8 +2915,11 @@ function findMatch(n, get, lookupVal, matchMode, searchMode) {
|
|
|
2917
2915
|
const reverse = searchMode === -1;
|
|
2918
2916
|
const order = reverse ? Array.from({ length: n }, (_, k) => n - 1 - k) : Array.from({ length: n }, (_, k) => k);
|
|
2919
2917
|
if (matchMode === 2) {
|
|
2920
|
-
const re =
|
|
2921
|
-
for (const i of order)
|
|
2918
|
+
const re = FormulaHelper.wildcardToRegex(lookupVal.asText());
|
|
2919
|
+
for (const i of order) {
|
|
2920
|
+
const v = get(i);
|
|
2921
|
+
if (!v.isBlank && re.test(v.asText())) return i;
|
|
2922
|
+
}
|
|
2922
2923
|
return -1;
|
|
2923
2924
|
}
|
|
2924
2925
|
if (matchMode === 0) {
|
|
@@ -3028,13 +3029,15 @@ function match(a, c) {
|
|
|
3028
3029
|
matchType = Math.round(r.n);
|
|
3029
3030
|
}
|
|
3030
3031
|
if (!arrVal.isArray) {
|
|
3031
|
-
|
|
3032
|
+
const single2 = matchType === 0 ? matchesLookup(arrVal, lookupVal, lookupPattern(lookupVal)) : FormulaValue.areEqual(arrVal, lookupVal);
|
|
3033
|
+
return single2 ? FormulaValue.one : FormulaValue.errorNA;
|
|
3032
3034
|
}
|
|
3033
3035
|
const arr = arrVal.arrayVal;
|
|
3034
3036
|
const len2 = arr.length;
|
|
3035
3037
|
if (matchType === 0) {
|
|
3038
|
+
const pattern = lookupPattern(lookupVal);
|
|
3036
3039
|
for (let i = 0; i < len2; i++) {
|
|
3037
|
-
if (
|
|
3040
|
+
if (matchesLookup(arr.getFlat(i), lookupVal, pattern)) return FormulaValue.number(i + 1);
|
|
3038
3041
|
}
|
|
3039
3042
|
return FormulaValue.errorNA;
|
|
3040
3043
|
}
|
|
@@ -4553,24 +4556,20 @@ function toRow(a, c) {
|
|
|
4553
4556
|
|
|
4554
4557
|
// src/function-registry.ts
|
|
4555
4558
|
var MAX_LIFTED_CELLS = 1048576;
|
|
4556
|
-
var
|
|
4557
|
-
|
|
4558
|
-
constructor() {
|
|
4559
|
-
__privateAdd(this, _FunctionRegistry_instances);
|
|
4560
|
-
__privateAdd(this, _functions, /* @__PURE__ */ new Map());
|
|
4561
|
-
}
|
|
4559
|
+
var FunctionRegistry = class _FunctionRegistry {
|
|
4560
|
+
static #default;
|
|
4562
4561
|
/** Lazily-created default registry with all built-in functions registered. */
|
|
4563
4562
|
static get default() {
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
__privateMethod(_a = __privateGet(_FunctionRegistry, _default), _FunctionRegistry_instances, registerBuiltIns_fn).call(_a);
|
|
4563
|
+
if (!_FunctionRegistry.#default) {
|
|
4564
|
+
_FunctionRegistry.#default = new _FunctionRegistry();
|
|
4565
|
+
_FunctionRegistry.#default.#registerBuiltIns();
|
|
4568
4566
|
}
|
|
4569
|
-
return
|
|
4567
|
+
return _FunctionRegistry.#default;
|
|
4570
4568
|
}
|
|
4569
|
+
#functions = /* @__PURE__ */ new Map();
|
|
4571
4570
|
/** Registers a function. Name is normalised to UPPERCASE. */
|
|
4572
4571
|
register(name, handler, minArgs = 0, maxArgs = -1, isVolatile = false) {
|
|
4573
|
-
|
|
4572
|
+
this.#functions.set(name.toUpperCase(), {
|
|
4574
4573
|
name: name.toUpperCase(),
|
|
4575
4574
|
minArgs,
|
|
4576
4575
|
maxArgs,
|
|
@@ -4596,7 +4595,7 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4596
4595
|
* `register`, or lifting would map it over the array it is meant to reduce.
|
|
4597
4596
|
*/
|
|
4598
4597
|
registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
|
|
4599
|
-
|
|
4598
|
+
this.#functions.set(name.toUpperCase(), {
|
|
4600
4599
|
name: name.toUpperCase(),
|
|
4601
4600
|
minArgs,
|
|
4602
4601
|
maxArgs,
|
|
@@ -4607,81 +4606,84 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4607
4606
|
}
|
|
4608
4607
|
/** Executes a function by name with argument validation. */
|
|
4609
4608
|
execute(name, args, ctx) {
|
|
4610
|
-
|
|
4611
|
-
const def = __privateGet(this, _functions).get(name.toUpperCase());
|
|
4609
|
+
const def = this.#functions.get(name.toUpperCase());
|
|
4612
4610
|
if (!def) return FormulaValue.errorName;
|
|
4613
4611
|
if (args.length < def.minArgs) return FormulaValue.errorValue;
|
|
4614
4612
|
if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
|
|
4615
4613
|
try {
|
|
4616
|
-
return def.elementwise ?
|
|
4614
|
+
return def.elementwise ? _FunctionRegistry.#executeElementwise(def, args, ctx) : def.handler(args, ctx);
|
|
4617
4615
|
} catch {
|
|
4618
4616
|
return FormulaValue.errorValue;
|
|
4619
4617
|
}
|
|
4620
4618
|
}
|
|
4619
|
+
/**
|
|
4620
|
+
* Runs a scalar handler, mapping it over any array arguments.
|
|
4621
|
+
*
|
|
4622
|
+
* Arguments are evaluated exactly once and the handler is re-driven through
|
|
4623
|
+
* {@link ValueNode}s, so a nested call in an argument is not recomputed per
|
|
4624
|
+
* output cell — the alternative (letting the handler re-evaluate the original
|
|
4625
|
+
* nodes) would multiply the cost of `LEFT(expensive(), 3)` by the array size.
|
|
4626
|
+
* The all-scalar case — the overwhelming majority of calls — costs one array of
|
|
4627
|
+
* wrappers and takes the same path, so there is no second code path to keep in
|
|
4628
|
+
* step.
|
|
4629
|
+
*/
|
|
4630
|
+
static #executeElementwise(def, args, ctx) {
|
|
4631
|
+
const values = args.map((a) => a.evaluate(ctx));
|
|
4632
|
+
let rows2 = 1, cols = 1;
|
|
4633
|
+
for (const v of values) {
|
|
4634
|
+
if (!v.isArray) continue;
|
|
4635
|
+
const a = v.arrayVal;
|
|
4636
|
+
if (a.rows > rows2) rows2 = a.rows;
|
|
4637
|
+
if (a.columns > cols) cols = a.columns;
|
|
4638
|
+
}
|
|
4639
|
+
if (rows2 === 1 && cols === 1) {
|
|
4640
|
+
return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
|
|
4641
|
+
}
|
|
4642
|
+
if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
|
|
4643
|
+
for (const v of values) {
|
|
4644
|
+
if (!v.isArray) continue;
|
|
4645
|
+
const a = v.arrayVal;
|
|
4646
|
+
if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
|
|
4647
|
+
return FormulaValue.errorValue;
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
const nodes = values.map((v) => new ValueNode(v));
|
|
4651
|
+
const out = new ArrayValue(rows2, cols);
|
|
4652
|
+
for (let r = 0; r < rows2; r++) {
|
|
4653
|
+
for (let c = 0; c < cols; c++) {
|
|
4654
|
+
for (let i = 0; i < values.length; i++) {
|
|
4655
|
+
const v = values[i];
|
|
4656
|
+
if (!v.isArray) continue;
|
|
4657
|
+
const a = v.arrayVal;
|
|
4658
|
+
nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
|
|
4659
|
+
}
|
|
4660
|
+
out.set(r, c, def.handler(nodes, ctx));
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
return FormulaValue.array(out);
|
|
4664
|
+
}
|
|
4621
4665
|
/** Returns true if the named function is volatile (NOW, RAND, etc.). */
|
|
4622
4666
|
isVolatile(name) {
|
|
4623
|
-
return
|
|
4667
|
+
return this.#functions.get(name.toUpperCase())?.isVolatile ?? false;
|
|
4624
4668
|
}
|
|
4625
4669
|
/** Returns true if the function is registered. */
|
|
4626
4670
|
has(name) {
|
|
4627
|
-
return
|
|
4628
|
-
}
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
}
|
|
4642
|
-
if (rows2 === 1 && cols === 1) {
|
|
4643
|
-
return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
|
|
4671
|
+
return this.#functions.has(name.toUpperCase());
|
|
4672
|
+
}
|
|
4673
|
+
#registerBuiltIns() {
|
|
4674
|
+
registerMath(this);
|
|
4675
|
+
registerText(this);
|
|
4676
|
+
registerLogical(this);
|
|
4677
|
+
registerDate(this);
|
|
4678
|
+
registerLookup(this);
|
|
4679
|
+
registerStatistical(this);
|
|
4680
|
+
registerInfo(this);
|
|
4681
|
+
registerFinance(this);
|
|
4682
|
+
registerMeta(this);
|
|
4683
|
+
registerAggregate(this);
|
|
4684
|
+
registerArray(this);
|
|
4644
4685
|
}
|
|
4645
|
-
if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
|
|
4646
|
-
for (const v of values) {
|
|
4647
|
-
if (!v.isArray) continue;
|
|
4648
|
-
const a = v.arrayVal;
|
|
4649
|
-
if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
|
|
4650
|
-
return FormulaValue.errorValue;
|
|
4651
|
-
}
|
|
4652
|
-
}
|
|
4653
|
-
const nodes = values.map((v) => new ValueNode(v));
|
|
4654
|
-
const out = new ArrayValue(rows2, cols);
|
|
4655
|
-
for (let r = 0; r < rows2; r++) {
|
|
4656
|
-
for (let c = 0; c < cols; c++) {
|
|
4657
|
-
for (let i = 0; i < values.length; i++) {
|
|
4658
|
-
const v = values[i];
|
|
4659
|
-
if (!v.isArray) continue;
|
|
4660
|
-
const a = v.arrayVal;
|
|
4661
|
-
nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
|
|
4662
|
-
}
|
|
4663
|
-
out.set(r, c, def.handler(nodes, ctx));
|
|
4664
|
-
}
|
|
4665
|
-
}
|
|
4666
|
-
return FormulaValue.array(out);
|
|
4667
4686
|
};
|
|
4668
|
-
_FunctionRegistry_instances = new WeakSet();
|
|
4669
|
-
registerBuiltIns_fn = function() {
|
|
4670
|
-
registerMath(this);
|
|
4671
|
-
registerText(this);
|
|
4672
|
-
registerLogical(this);
|
|
4673
|
-
registerDate(this);
|
|
4674
|
-
registerLookup(this);
|
|
4675
|
-
registerStatistical(this);
|
|
4676
|
-
registerInfo(this);
|
|
4677
|
-
registerFinance(this);
|
|
4678
|
-
registerMeta(this);
|
|
4679
|
-
registerAggregate(this);
|
|
4680
|
-
registerArray(this);
|
|
4681
|
-
};
|
|
4682
|
-
__privateAdd(_FunctionRegistry, _FunctionRegistry_static);
|
|
4683
|
-
__privateAdd(_FunctionRegistry, _default);
|
|
4684
|
-
var FunctionRegistry = _FunctionRegistry;
|
|
4685
4687
|
var FormulaHelper;
|
|
4686
4688
|
((FormulaHelper2) => {
|
|
4687
4689
|
function evalDouble(node, ctx) {
|
|
@@ -4754,7 +4756,7 @@ var FormulaHelper;
|
|
|
4754
4756
|
const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
|
|
4755
4757
|
return rhsNum !== null ? { kind: "compareNumber", number: rhsNum, op, test } : { kind: "other", test };
|
|
4756
4758
|
}
|
|
4757
|
-
if (
|
|
4759
|
+
if (hasWildcardSyntax(trimmed)) {
|
|
4758
4760
|
const pattern = wildcardToRegex(trimmed);
|
|
4759
4761
|
return { kind: "other", test: (v) => pattern.test(v.asText()) };
|
|
4760
4762
|
}
|
|
@@ -4786,53 +4788,106 @@ var FormulaHelper;
|
|
|
4786
4788
|
return compileCriteria(criteria).test(value2);
|
|
4787
4789
|
}
|
|
4788
4790
|
FormulaHelper2.matchesCriteria = matchesCriteria;
|
|
4789
|
-
function
|
|
4790
|
-
|
|
4791
|
-
|
|
4791
|
+
function hasWildcardSyntax(pattern) {
|
|
4792
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
4793
|
+
const ch = pattern.charCodeAt(i);
|
|
4794
|
+
if (ch === 42 || ch === 63 || ch === 126) return true;
|
|
4795
|
+
}
|
|
4796
|
+
return false;
|
|
4792
4797
|
}
|
|
4798
|
+
FormulaHelper2.hasWildcardSyntax = hasWildcardSyntax;
|
|
4799
|
+
function wildcardToRegex(pattern) {
|
|
4800
|
+
let out = "";
|
|
4801
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
4802
|
+
const ch = pattern[i];
|
|
4803
|
+
const next = pattern[i + 1];
|
|
4804
|
+
if (ch === "~" && (next === "*" || next === "?" || next === "~")) {
|
|
4805
|
+
out += escapeRegex(next);
|
|
4806
|
+
i++;
|
|
4807
|
+
} else if (ch === "*") out += ".*";
|
|
4808
|
+
else if (ch === "?") out += ".";
|
|
4809
|
+
else out += escapeRegex(ch);
|
|
4810
|
+
}
|
|
4811
|
+
return new RegExp(`^${out}$`, "i");
|
|
4812
|
+
}
|
|
4813
|
+
FormulaHelper2.wildcardToRegex = wildcardToRegex;
|
|
4814
|
+
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4793
4815
|
})(FormulaHelper || (FormulaHelper = {}));
|
|
4794
4816
|
|
|
4795
4817
|
// src/formula-context.ts
|
|
4796
|
-
var
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4818
|
+
var FormulaContext = class _FormulaContext {
|
|
4819
|
+
#sheet;
|
|
4820
|
+
#functions;
|
|
4821
|
+
#evaluating;
|
|
4822
|
+
/** Row of the formula cell (for @ implicit intersection). */
|
|
4823
|
+
formulaRow;
|
|
4824
|
+
/** Column of the formula cell (for @ implicit intersection). */
|
|
4825
|
+
formulaCol;
|
|
4826
|
+
/** Optional workbook reference for cross-sheet references. */
|
|
4827
|
+
workbook;
|
|
4828
|
+
/**
|
|
4829
|
+
* Optional memo for range reads, supplied by whoever owns the write side —
|
|
4830
|
+
* the recalculation engine, which invalidates it as it writes results. Left
|
|
4831
|
+
* undefined for one-shot evaluation, where a stale entry could otherwise
|
|
4832
|
+
* outlive a caller's direct edit to the model.
|
|
4833
|
+
*/
|
|
4834
|
+
rangeCache;
|
|
4835
|
+
/**
|
|
4836
|
+
* Optional named-range resolver, installed by the evaluator when the workbook
|
|
4837
|
+
* exposes getDefinedName. Given a name, returns its evaluated value. Left
|
|
4838
|
+
* undefined when no resolver is available, in which case named ranges are
|
|
4839
|
+
* #NAME? (unchanged legacy behaviour).
|
|
4840
|
+
*/
|
|
4841
|
+
nameResolver;
|
|
4842
|
+
/**
|
|
4843
|
+
* Shared recursion budget for LAMBDA calls, threaded across all contexts in a
|
|
4844
|
+
* single evaluation. Guards non-tail recursion (which grows the JS stack);
|
|
4845
|
+
* tail recursion is trampolined and does not count against it.
|
|
4846
|
+
*/
|
|
4847
|
+
recursionGuard;
|
|
4848
|
+
/**
|
|
4849
|
+
* Lexical scope stack for LET/LAMBDA local names. Each frame maps an
|
|
4850
|
+
* upper-cased local name to its value. Names resolve innermost-first and take
|
|
4851
|
+
* precedence over workbook defined names.
|
|
4852
|
+
*/
|
|
4853
|
+
#scopes = [];
|
|
4815
4854
|
/** Pushes a new binding frame (used by LET/LAMBDA). */
|
|
4816
4855
|
pushScope(bindings) {
|
|
4817
|
-
|
|
4856
|
+
this.#scopes.push(bindings);
|
|
4818
4857
|
}
|
|
4819
4858
|
/** Pops the innermost binding frame. */
|
|
4820
4859
|
popScope() {
|
|
4821
|
-
|
|
4860
|
+
this.#scopes.pop();
|
|
4861
|
+
}
|
|
4862
|
+
#lookupScope(name) {
|
|
4863
|
+
if (this.#scopes.length === 0) return void 0;
|
|
4864
|
+
const key = name.toUpperCase();
|
|
4865
|
+
for (let i = this.#scopes.length - 1; i >= 0; i--) {
|
|
4866
|
+
const v = this.#scopes[i].get(key);
|
|
4867
|
+
if (v !== void 0) return v;
|
|
4868
|
+
}
|
|
4869
|
+
return void 0;
|
|
4870
|
+
}
|
|
4871
|
+
constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
|
|
4872
|
+
this.#sheet = sheet;
|
|
4873
|
+
this.formulaRow = formulaRow;
|
|
4874
|
+
this.formulaCol = formulaCol;
|
|
4875
|
+
this.#evaluating = evaluating ?? /* @__PURE__ */ new Set();
|
|
4876
|
+
this.#functions = registry ?? FunctionRegistry.default;
|
|
4822
4877
|
}
|
|
4823
4878
|
get worksheet() {
|
|
4824
|
-
return
|
|
4879
|
+
return this.#sheet;
|
|
4825
4880
|
}
|
|
4826
4881
|
// ── Cell/Range resolution ─────────────────────────────────────────────────
|
|
4827
4882
|
getCellValue(cellReference) {
|
|
4828
4883
|
const norm = cellReference.replace(/\$/g, "").toUpperCase();
|
|
4829
|
-
if (
|
|
4830
|
-
|
|
4884
|
+
if (this.#evaluating.has(norm)) return FormulaValue.errorRef;
|
|
4885
|
+
this.#evaluating.add(norm);
|
|
4831
4886
|
try {
|
|
4832
|
-
const raw =
|
|
4887
|
+
const raw = this.#sheet.getCellValue(cellReference);
|
|
4833
4888
|
return FormulaValue.fromObjectSync(raw);
|
|
4834
4889
|
} finally {
|
|
4835
|
-
|
|
4890
|
+
this.#evaluating.delete(norm);
|
|
4836
4891
|
}
|
|
4837
4892
|
}
|
|
4838
4893
|
getRangeValues(startRef, endRef) {
|
|
@@ -4850,17 +4905,17 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4850
4905
|
const rows2 = endRow - startRow + 1;
|
|
4851
4906
|
const cols = endCol - startCol + 1;
|
|
4852
4907
|
const cache = rows2 * cols > 1 ? this.rangeCache : void 0;
|
|
4853
|
-
const sheetKey = cache ?
|
|
4908
|
+
const sheetKey = cache ? this.#sheet.name.toUpperCase() : "";
|
|
4854
4909
|
if (cache) {
|
|
4855
4910
|
const hit = cache.get(sheetKey, startRow, startCol, endRow, endCol);
|
|
4856
4911
|
if (hit !== void 0) return hit;
|
|
4857
4912
|
}
|
|
4858
4913
|
const arr = new ArrayValue(rows2, cols);
|
|
4859
|
-
const at =
|
|
4914
|
+
const at = this.#sheet.getCellValueAt;
|
|
4860
4915
|
if (at) {
|
|
4861
4916
|
for (let r = 0; r < rows2; r++) {
|
|
4862
4917
|
for (let c = 0; c < cols; c++) {
|
|
4863
|
-
arr.set(r, c, FormulaValue.fromObjectSync(at.call(
|
|
4918
|
+
arr.set(r, c, FormulaValue.fromObjectSync(at.call(this.#sheet, startRow + r, startCol + c)));
|
|
4864
4919
|
}
|
|
4865
4920
|
}
|
|
4866
4921
|
} else {
|
|
@@ -4886,7 +4941,7 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4886
4941
|
* silently reading nothing would make SUM(A:A) return 0 on a full column.
|
|
4887
4942
|
*/
|
|
4888
4943
|
getFullRangeValues(startCol, endCol, startRow, endRow) {
|
|
4889
|
-
const bounds =
|
|
4944
|
+
const bounds = this.#sheet.usedBounds;
|
|
4890
4945
|
if (!bounds) return FormulaValue.errorRef;
|
|
4891
4946
|
if (bounds.rowCount === 0 || bounds.columnCount === 0) return FormulaValue.blank;
|
|
4892
4947
|
const r0 = startRow ?? 1;
|
|
@@ -4901,7 +4956,7 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4901
4956
|
getSheetCellValue(sheetName, cellReference) {
|
|
4902
4957
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4903
4958
|
try {
|
|
4904
|
-
return
|
|
4959
|
+
return this.#forSheet(sheetName).getCellValue(cellReference);
|
|
4905
4960
|
} catch {
|
|
4906
4961
|
return FormulaValue.errorRef;
|
|
4907
4962
|
}
|
|
@@ -4909,7 +4964,7 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4909
4964
|
getSheetRangeValues(sheetName, startRef, endRef) {
|
|
4910
4965
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4911
4966
|
try {
|
|
4912
|
-
return
|
|
4967
|
+
return this.#forSheet(sheetName).getRangeValues(startRef, endRef);
|
|
4913
4968
|
} catch {
|
|
4914
4969
|
return FormulaValue.errorRef;
|
|
4915
4970
|
}
|
|
@@ -4918,13 +4973,28 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4918
4973
|
getSheetFullRangeValues(sheetName, startCol, endCol, startRow, endRow) {
|
|
4919
4974
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4920
4975
|
try {
|
|
4921
|
-
return
|
|
4976
|
+
return this.#forSheet(sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
|
|
4922
4977
|
} catch {
|
|
4923
4978
|
return FormulaValue.errorRef;
|
|
4924
4979
|
}
|
|
4925
4980
|
}
|
|
4981
|
+
/**
|
|
4982
|
+
* A sibling context bound to another sheet, sharing this one's circular-
|
|
4983
|
+
* reference guard and range memo. These contexts only read values — they
|
|
4984
|
+
* never evaluate an AST — so the name resolver and recursion budget are
|
|
4985
|
+
* deliberately not carried over; passing them would suggest a scoping
|
|
4986
|
+
* relationship that does not exist. The memo, on the other hand, must be
|
|
4987
|
+
* carried: cross-sheet ranges are the most expensive reads in a workbook.
|
|
4988
|
+
*/
|
|
4989
|
+
#forSheet(sheetName) {
|
|
4990
|
+
const sheet = this.workbook.getWorksheet(sheetName);
|
|
4991
|
+
const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, this.#evaluating, this.#functions);
|
|
4992
|
+
ctx.workbook = this.workbook;
|
|
4993
|
+
ctx.rangeCache = this.rangeCache;
|
|
4994
|
+
return ctx;
|
|
4995
|
+
}
|
|
4926
4996
|
resolveNamedRange(name) {
|
|
4927
|
-
const scoped =
|
|
4997
|
+
const scoped = this.#lookupScope(name);
|
|
4928
4998
|
if (scoped !== void 0) return scoped;
|
|
4929
4999
|
return this.nameResolver ? this.nameResolver(name) : FormulaValue.errorName;
|
|
4930
5000
|
}
|
|
@@ -4933,7 +5003,7 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4933
5003
|
* cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
|
|
4934
5004
|
*/
|
|
4935
5005
|
resolveSpillRange(anchorRef) {
|
|
4936
|
-
const spillRef =
|
|
5006
|
+
const spillRef = this.#sheet.getSpillRange?.(anchorRef);
|
|
4937
5007
|
if (!spillRef) return FormulaValue.errorRef;
|
|
4938
5008
|
const colon = spillRef.indexOf(":");
|
|
4939
5009
|
if (colon < 0) return this.getCellValue(spillRef);
|
|
@@ -4943,7 +5013,7 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4943
5013
|
resolveSheetSpillRange(sheetName, anchorRef) {
|
|
4944
5014
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4945
5015
|
try {
|
|
4946
|
-
return
|
|
5016
|
+
return this.#forSheet(sheetName).resolveSpillRange(anchorRef);
|
|
4947
5017
|
} catch {
|
|
4948
5018
|
return FormulaValue.errorRef;
|
|
4949
5019
|
}
|
|
@@ -4956,94 +5026,63 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4956
5026
|
// ── Function dispatch ─────────────────────────────────────────────────────
|
|
4957
5027
|
/** True if a built-in function with this name is registered. */
|
|
4958
5028
|
hasFunction(name) {
|
|
4959
|
-
return
|
|
5029
|
+
return this.#functions.has(name);
|
|
4960
5030
|
}
|
|
4961
5031
|
evaluateFunction(name, args) {
|
|
4962
|
-
if (!
|
|
5032
|
+
if (!this.#functions.has(name)) {
|
|
4963
5033
|
const named = this.resolveNamedRange(name);
|
|
4964
5034
|
if (named.isLambda) {
|
|
4965
5035
|
return named.lambdaVal.call(args.map((a) => a.evaluate(this)));
|
|
4966
5036
|
}
|
|
4967
5037
|
}
|
|
4968
|
-
return
|
|
5038
|
+
return this.#functions.execute(name, args, this);
|
|
4969
5039
|
}
|
|
4970
5040
|
};
|
|
4971
|
-
_sheet = new WeakMap();
|
|
4972
|
-
_functions2 = new WeakMap();
|
|
4973
|
-
_evaluating = new WeakMap();
|
|
4974
|
-
_scopes = new WeakMap();
|
|
4975
|
-
_FormulaContext_instances = new WeakSet();
|
|
4976
|
-
lookupScope_fn = function(name) {
|
|
4977
|
-
if (__privateGet(this, _scopes).length === 0) return void 0;
|
|
4978
|
-
const key = name.toUpperCase();
|
|
4979
|
-
for (let i = __privateGet(this, _scopes).length - 1; i >= 0; i--) {
|
|
4980
|
-
const v = __privateGet(this, _scopes)[i].get(key);
|
|
4981
|
-
if (v !== void 0) return v;
|
|
4982
|
-
}
|
|
4983
|
-
return void 0;
|
|
4984
|
-
};
|
|
4985
|
-
/**
|
|
4986
|
-
* A sibling context bound to another sheet, sharing this one's circular-
|
|
4987
|
-
* reference guard and range memo. These contexts only read values — they
|
|
4988
|
-
* never evaluate an AST — so the name resolver and recursion budget are
|
|
4989
|
-
* deliberately not carried over; passing them would suggest a scoping
|
|
4990
|
-
* relationship that does not exist. The memo, on the other hand, must be
|
|
4991
|
-
* carried: cross-sheet ranges are the most expensive reads in a workbook.
|
|
4992
|
-
*/
|
|
4993
|
-
forSheet_fn = function(sheetName) {
|
|
4994
|
-
const sheet = this.workbook.getWorksheet(sheetName);
|
|
4995
|
-
const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, __privateGet(this, _evaluating), __privateGet(this, _functions2));
|
|
4996
|
-
ctx.workbook = this.workbook;
|
|
4997
|
-
ctx.rangeCache = this.rangeCache;
|
|
4998
|
-
return ctx;
|
|
4999
|
-
};
|
|
5000
|
-
var FormulaContext = _FormulaContext;
|
|
5001
5041
|
|
|
5002
5042
|
// src/lru-cache.ts
|
|
5003
|
-
var _map, _capacity, _hits, _misses, _evictions;
|
|
5004
5043
|
var LruCache = class {
|
|
5044
|
+
#map;
|
|
5045
|
+
#capacity;
|
|
5046
|
+
#hits = 0;
|
|
5047
|
+
#misses = 0;
|
|
5048
|
+
#evictions = 0;
|
|
5005
5049
|
constructor(capacity) {
|
|
5006
|
-
__privateAdd(this, _map);
|
|
5007
|
-
__privateAdd(this, _capacity);
|
|
5008
|
-
__privateAdd(this, _hits, 0);
|
|
5009
|
-
__privateAdd(this, _misses, 0);
|
|
5010
|
-
__privateAdd(this, _evictions, 0);
|
|
5011
5050
|
if (capacity <= 0) throw new RangeError("LruCache capacity must be > 0.");
|
|
5012
|
-
|
|
5013
|
-
|
|
5051
|
+
this.#capacity = capacity;
|
|
5052
|
+
this.#map = /* @__PURE__ */ new Map();
|
|
5014
5053
|
}
|
|
5015
5054
|
get capacity() {
|
|
5016
|
-
return
|
|
5055
|
+
return this.#capacity;
|
|
5017
5056
|
}
|
|
5018
5057
|
get size() {
|
|
5019
|
-
return
|
|
5058
|
+
return this.#map.size;
|
|
5020
5059
|
}
|
|
5021
5060
|
get hits() {
|
|
5022
|
-
return
|
|
5061
|
+
return this.#hits;
|
|
5023
5062
|
}
|
|
5024
5063
|
get misses() {
|
|
5025
|
-
return
|
|
5064
|
+
return this.#misses;
|
|
5026
5065
|
}
|
|
5027
5066
|
get evictions() {
|
|
5028
|
-
return
|
|
5067
|
+
return this.#evictions;
|
|
5029
5068
|
}
|
|
5030
5069
|
get hitRate() {
|
|
5031
|
-
const total =
|
|
5032
|
-
return total === 0 ? 0 :
|
|
5070
|
+
const total = this.#hits + this.#misses;
|
|
5071
|
+
return total === 0 ? 0 : this.#hits / total;
|
|
5033
5072
|
}
|
|
5034
5073
|
/**
|
|
5035
5074
|
* Returns the value for key, or undefined if not present.
|
|
5036
5075
|
* Moves the accessed key to the "most recently used" end.
|
|
5037
5076
|
*/
|
|
5038
5077
|
get(key) {
|
|
5039
|
-
const value2 =
|
|
5078
|
+
const value2 = this.#map.get(key);
|
|
5040
5079
|
if (value2 === void 0) {
|
|
5041
|
-
|
|
5080
|
+
this.#misses++;
|
|
5042
5081
|
return void 0;
|
|
5043
5082
|
}
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5083
|
+
this.#map.delete(key);
|
|
5084
|
+
this.#map.set(key, value2);
|
|
5085
|
+
this.#hits++;
|
|
5047
5086
|
return value2;
|
|
5048
5087
|
}
|
|
5049
5088
|
/**
|
|
@@ -5051,44 +5090,39 @@ var LruCache = class {
|
|
|
5051
5090
|
* Evicts the least-recently-used entry if over capacity.
|
|
5052
5091
|
*/
|
|
5053
5092
|
set(key, value2) {
|
|
5054
|
-
if (
|
|
5055
|
-
|
|
5056
|
-
} else if (
|
|
5057
|
-
const oldest =
|
|
5058
|
-
|
|
5059
|
-
|
|
5093
|
+
if (this.#map.has(key)) {
|
|
5094
|
+
this.#map.delete(key);
|
|
5095
|
+
} else if (this.#map.size >= this.#capacity) {
|
|
5096
|
+
const oldest = this.#map.keys().next().value;
|
|
5097
|
+
this.#map.delete(oldest);
|
|
5098
|
+
this.#evictions++;
|
|
5060
5099
|
}
|
|
5061
|
-
|
|
5100
|
+
this.#map.set(key, value2);
|
|
5062
5101
|
}
|
|
5063
5102
|
/** Removes a key from the cache. Returns true if it existed. */
|
|
5064
5103
|
delete(key) {
|
|
5065
|
-
return
|
|
5104
|
+
return this.#map.delete(key);
|
|
5066
5105
|
}
|
|
5067
5106
|
/** Returns true if the key is present (does NOT update LRU order). */
|
|
5068
5107
|
has(key) {
|
|
5069
|
-
return
|
|
5108
|
+
return this.#map.has(key);
|
|
5070
5109
|
}
|
|
5071
5110
|
/** Removes all entries and resets statistics. */
|
|
5072
5111
|
clear() {
|
|
5073
|
-
|
|
5074
|
-
|
|
5112
|
+
this.#map.clear();
|
|
5113
|
+
this.#hits = this.#misses = this.#evictions = 0;
|
|
5075
5114
|
}
|
|
5076
5115
|
/** Resizes the cache, evicting oldest entries if necessary. */
|
|
5077
5116
|
resize(newCapacity) {
|
|
5078
5117
|
if (newCapacity <= 0) throw new RangeError("Capacity must be > 0.");
|
|
5079
|
-
|
|
5080
|
-
while (
|
|
5081
|
-
const oldest =
|
|
5082
|
-
|
|
5083
|
-
|
|
5118
|
+
this.#capacity = newCapacity;
|
|
5119
|
+
while (this.#map.size > newCapacity) {
|
|
5120
|
+
const oldest = this.#map.keys().next().value;
|
|
5121
|
+
this.#map.delete(oldest);
|
|
5122
|
+
this.#evictions++;
|
|
5084
5123
|
}
|
|
5085
5124
|
}
|
|
5086
5125
|
};
|
|
5087
|
-
_map = new WeakMap();
|
|
5088
|
-
_capacity = new WeakMap();
|
|
5089
|
-
_hits = new WeakMap();
|
|
5090
|
-
_misses = new WeakMap();
|
|
5091
|
-
_evictions = new WeakMap();
|
|
5092
5126
|
|
|
5093
5127
|
// src/formula-evaluator.ts
|
|
5094
5128
|
var DEFAULT_CACHE_CAPACITY = 1e4;
|
|
@@ -5196,143 +5230,126 @@ var TILE_ROWS = 64;
|
|
|
5196
5230
|
var TILE_COLS = 64;
|
|
5197
5231
|
var MAX_TILES_PER_RANGE = 32;
|
|
5198
5232
|
var DEFAULT_MAX_CELLS = 2e6;
|
|
5199
|
-
var
|
|
5200
|
-
|
|
5233
|
+
var RangeCache = class _RangeCache {
|
|
5234
|
+
/** entry key → the memoised rectangle. Insertion-ordered (FIFO eviction). */
|
|
5235
|
+
#entries = /* @__PURE__ */ new Map();
|
|
5236
|
+
/** `sheet tr tc` → entry keys whose rectangle overlaps that tile. */
|
|
5237
|
+
#tiles = /* @__PURE__ */ new Map();
|
|
5238
|
+
/** sheet → entry keys whose rectangle is too broad to tile. */
|
|
5239
|
+
#wide = /* @__PURE__ */ new Map();
|
|
5240
|
+
/** entry key → buckets it was filed under, for cheap removal. */
|
|
5241
|
+
#placements = /* @__PURE__ */ new Map();
|
|
5242
|
+
/** Number of cells across all cached rectangles, kept under {@link #maxCells}. */
|
|
5243
|
+
#cells = 0;
|
|
5244
|
+
#maxCells;
|
|
5245
|
+
hits = 0;
|
|
5246
|
+
misses = 0;
|
|
5201
5247
|
constructor(maxCells = DEFAULT_MAX_CELLS) {
|
|
5202
|
-
|
|
5203
|
-
/** `SHEET r0 c0 r1 c1` → the memoised array value. Insertion-ordered (FIFO eviction). */
|
|
5204
|
-
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
5205
|
-
/** `sheet tr tc` → entry keys whose rectangle overlaps that tile. */
|
|
5206
|
-
__privateAdd(this, _tiles, /* @__PURE__ */ new Map());
|
|
5207
|
-
/** sheet → entry keys whose rectangle is too broad to tile. */
|
|
5208
|
-
__privateAdd(this, _wide, /* @__PURE__ */ new Map());
|
|
5209
|
-
/** entry key → buckets it was filed under, for cheap removal. */
|
|
5210
|
-
__privateAdd(this, _placements, /* @__PURE__ */ new Map());
|
|
5211
|
-
/** entry key → its area, so eviction can give the budget back. */
|
|
5212
|
-
__privateAdd(this, _sizes, /* @__PURE__ */ new Map());
|
|
5213
|
-
/** Number of cells across all cached rectangles, kept under {@link #maxCells}. */
|
|
5214
|
-
__privateAdd(this, _cells, 0);
|
|
5215
|
-
__privateAdd(this, _maxCells);
|
|
5216
|
-
this.hits = 0;
|
|
5217
|
-
this.misses = 0;
|
|
5218
|
-
__privateSet(this, _maxCells, maxCells);
|
|
5248
|
+
this.#maxCells = maxCells;
|
|
5219
5249
|
}
|
|
5220
5250
|
get size() {
|
|
5221
|
-
return
|
|
5251
|
+
return this.#entries.size;
|
|
5222
5252
|
}
|
|
5223
5253
|
get cachedCells() {
|
|
5224
|
-
return
|
|
5254
|
+
return this.#cells;
|
|
5225
5255
|
}
|
|
5226
5256
|
static key(sheet, r0, c0, r1, c1) {
|
|
5227
5257
|
return `${sheet} ${r0} ${c0} ${r1} ${c1}`;
|
|
5228
5258
|
}
|
|
5229
5259
|
get(sheet, r0, c0, r1, c1) {
|
|
5230
|
-
const hit =
|
|
5260
|
+
const hit = this.#entries.get(_RangeCache.key(sheet, r0, c0, r1, c1));
|
|
5231
5261
|
if (hit === void 0) this.misses++;
|
|
5232
5262
|
else this.hits++;
|
|
5233
|
-
return hit;
|
|
5263
|
+
return hit?.value;
|
|
5234
5264
|
}
|
|
5235
5265
|
/** Memoises a rectangle's value. `cells` is its area, for the budget. */
|
|
5236
5266
|
set(sheet, r0, c0, r1, c1, value2, cells) {
|
|
5237
|
-
if (cells <= 0 || cells >
|
|
5267
|
+
if (cells <= 0 || cells > this.#maxCells) return;
|
|
5238
5268
|
const key = _RangeCache.key(sheet, r0, c0, r1, c1);
|
|
5239
|
-
if (
|
|
5240
|
-
|
|
5241
|
-
|
|
5269
|
+
if (this.#entries.has(key)) this.#drop(key);
|
|
5270
|
+
this.#entries.set(key, { value: value2, sheet, r0, c0, r1, c1, cells });
|
|
5271
|
+
this.#cells += cells;
|
|
5242
5272
|
const buckets = [];
|
|
5243
5273
|
const tileRow0 = r0 / TILE_ROWS | 0, tileRow1 = r1 / TILE_ROWS | 0;
|
|
5244
5274
|
const tileCol0 = c0 / TILE_COLS | 0, tileCol1 = c1 / TILE_COLS | 0;
|
|
5245
5275
|
const tileCount = (tileRow1 - tileRow0 + 1) * (tileCol1 - tileCol0 + 1);
|
|
5246
5276
|
if (tileCount > MAX_TILES_PER_RANGE) {
|
|
5247
|
-
|
|
5277
|
+
this.#fileIn(this.#wide, sheet, key, buckets);
|
|
5248
5278
|
} else {
|
|
5249
5279
|
for (let tr = tileRow0; tr <= tileRow1; tr++) {
|
|
5250
5280
|
for (let tc = tileCol0; tc <= tileCol1; tc++) {
|
|
5251
|
-
|
|
5252
|
-
__privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _tiles), bucket, key, buckets, `t ${bucket}`);
|
|
5281
|
+
this.#fileIn(this.#tiles, `${sheet} ${tr} ${tc}`, key, buckets);
|
|
5253
5282
|
}
|
|
5254
5283
|
}
|
|
5255
5284
|
}
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
if (__privateGet(this, _cells) <= __privateGet(this, _maxCells)) break;
|
|
5285
|
+
this.#placements.set(key, buckets);
|
|
5286
|
+
if (this.#cells > this.#maxCells) {
|
|
5287
|
+
for (const oldest of this.#entries.keys()) {
|
|
5288
|
+
if (this.#cells <= this.#maxCells) break;
|
|
5261
5289
|
if (oldest === key) continue;
|
|
5262
|
-
|
|
5290
|
+
this.#drop(oldest);
|
|
5263
5291
|
}
|
|
5264
5292
|
}
|
|
5265
5293
|
}
|
|
5266
|
-
/**
|
|
5294
|
+
/**
|
|
5295
|
+
* Drops every cached rectangle containing the given cell.
|
|
5296
|
+
*
|
|
5297
|
+
* The bucket sets are iterated in place. Removing an element of a `Set` while
|
|
5298
|
+
* iterating it is defined behaviour — the iterator simply does not revisit it —
|
|
5299
|
+
* and copying instead cost one array per write, which at one write per formula
|
|
5300
|
+
* was a real share of a recalculation.
|
|
5301
|
+
*/
|
|
5267
5302
|
invalidateCell(sheet, row2, col) {
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
if (
|
|
5303
|
+
const tile = this.#tiles.get(`${sheet} ${row2 / TILE_ROWS | 0} ${col / TILE_COLS | 0}`);
|
|
5304
|
+
if (tile !== void 0) {
|
|
5305
|
+
for (const key of tile) {
|
|
5306
|
+
const entry = this.#entries.get(key);
|
|
5307
|
+
if (entry !== void 0 && _RangeCache.#contains(entry, sheet, row2, col)) this.#drop(key);
|
|
5273
5308
|
}
|
|
5274
5309
|
}
|
|
5275
|
-
const wide =
|
|
5276
|
-
if (wide) {
|
|
5277
|
-
for (const key of
|
|
5278
|
-
|
|
5310
|
+
const wide = this.#wide.get(sheet);
|
|
5311
|
+
if (wide !== void 0) {
|
|
5312
|
+
for (const key of wide) {
|
|
5313
|
+
const entry = this.#entries.get(key);
|
|
5314
|
+
if (entry !== void 0 && _RangeCache.#contains(entry, sheet, row2, col)) this.#drop(key);
|
|
5279
5315
|
}
|
|
5280
5316
|
}
|
|
5281
5317
|
}
|
|
5282
5318
|
clear() {
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5319
|
+
this.#entries.clear();
|
|
5320
|
+
this.#tiles.clear();
|
|
5321
|
+
this.#wide.clear();
|
|
5322
|
+
this.#placements.clear();
|
|
5323
|
+
this.#cells = 0;
|
|
5324
|
+
}
|
|
5325
|
+
static #contains(entry, sheet, row2, col) {
|
|
5326
|
+
return row2 >= entry.r0 && row2 <= entry.r1 && col >= entry.c0 && col <= entry.c1 && entry.sheet === sheet;
|
|
5327
|
+
}
|
|
5328
|
+
#drop(key) {
|
|
5329
|
+
const entry = this.#entries.get(key);
|
|
5330
|
+
if (entry === void 0) return;
|
|
5331
|
+
this.#entries.delete(key);
|
|
5332
|
+
this.#cells -= entry.cells;
|
|
5333
|
+
const buckets = this.#placements.get(key);
|
|
5334
|
+
if (buckets !== void 0) {
|
|
5335
|
+
for (const { store, bucketKey, set } of buckets) {
|
|
5336
|
+
set.delete(key);
|
|
5337
|
+
if (set.size === 0) store.delete(bucketKey);
|
|
5338
|
+
}
|
|
5339
|
+
this.#placements.delete(key);
|
|
5340
|
+
}
|
|
5289
5341
|
}
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
_sizes = new WeakMap();
|
|
5296
|
-
_cells = new WeakMap();
|
|
5297
|
-
_maxCells = new WeakMap();
|
|
5298
|
-
_RangeCache_static = new WeakSet();
|
|
5299
|
-
contains_fn = function(key, sheet, row2, col) {
|
|
5300
|
-
const parts = key.split(" ");
|
|
5301
|
-
const n = parts.length;
|
|
5302
|
-
const c1 = +parts[n - 1], r1 = +parts[n - 2], c0 = +parts[n - 3], r0 = +parts[n - 4];
|
|
5303
|
-
if (parts.slice(0, n - 4).join(" ") !== sheet) return false;
|
|
5304
|
-
return row2 >= r0 && row2 <= r1 && col >= c0 && col <= c1;
|
|
5305
|
-
};
|
|
5306
|
-
_RangeCache_instances = new WeakSet();
|
|
5307
|
-
drop_fn = function(key) {
|
|
5308
|
-
if (!__privateGet(this, _entries).delete(key)) return;
|
|
5309
|
-
__privateSet(this, _cells, __privateGet(this, _cells) - (__privateGet(this, _sizes).get(key) ?? 0));
|
|
5310
|
-
__privateGet(this, _sizes).delete(key);
|
|
5311
|
-
const buckets = __privateGet(this, _placements).get(key);
|
|
5312
|
-
if (buckets) {
|
|
5313
|
-
for (const bucket of buckets) {
|
|
5314
|
-
const store = bucket.charCodeAt(0) === 119 ? __privateGet(this, _wide) : __privateGet(this, _tiles);
|
|
5315
|
-
const bucketKey = bucket.slice(2);
|
|
5316
|
-
const set = store.get(bucketKey);
|
|
5317
|
-
if (!set) continue;
|
|
5318
|
-
set.delete(key);
|
|
5319
|
-
if (set.size === 0) store.delete(bucketKey);
|
|
5342
|
+
#fileIn(store, bucketKey, entryKey, buckets) {
|
|
5343
|
+
let set = store.get(bucketKey);
|
|
5344
|
+
if (!set) {
|
|
5345
|
+
set = /* @__PURE__ */ new Set();
|
|
5346
|
+
store.set(bucketKey, set);
|
|
5320
5347
|
}
|
|
5321
|
-
|
|
5348
|
+
if (set.has(entryKey)) return;
|
|
5349
|
+
set.add(entryKey);
|
|
5350
|
+
buckets.push({ store, bucketKey, set });
|
|
5322
5351
|
}
|
|
5323
5352
|
};
|
|
5324
|
-
fileIn_fn = function(store, bucketKey, entryKey, buckets, bucketId) {
|
|
5325
|
-
let set = store.get(bucketKey);
|
|
5326
|
-
if (!set) {
|
|
5327
|
-
set = /* @__PURE__ */ new Set();
|
|
5328
|
-
store.set(bucketKey, set);
|
|
5329
|
-
}
|
|
5330
|
-
if (set.has(entryKey)) return;
|
|
5331
|
-
set.add(entryKey);
|
|
5332
|
-
buckets.push(bucketId);
|
|
5333
|
-
};
|
|
5334
|
-
__privateAdd(_RangeCache, _RangeCache_static);
|
|
5335
|
-
var RangeCache = _RangeCache;
|
|
5336
5353
|
function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
|
|
5337
5354
|
const refs = [];
|
|
5338
5355
|
let volatile = false;
|
|
@@ -5412,24 +5429,20 @@ var MAX_EXACT_SPAN = 16;
|
|
|
5412
5429
|
var MAX_BUCKETS_PER_REF = 64;
|
|
5413
5430
|
var tileOfRow = (row2) => row2 / TILE_ROWS2 | 0;
|
|
5414
5431
|
var tileOfCol = (col) => col / TILE_COLS2 | 0;
|
|
5415
|
-
var _buckets, _placements2, _DependencyIndex_instances, visit_fn, file_fn;
|
|
5416
5432
|
var DependencyIndex = class {
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
/** formula key → the buckets it was filed under, for cheap removal. */
|
|
5431
|
-
__privateAdd(this, _placements2, /* @__PURE__ */ new Map());
|
|
5432
|
-
}
|
|
5433
|
+
/**
|
|
5434
|
+
* All buckets in one map, keyed by a discriminating prefix:
|
|
5435
|
+
*
|
|
5436
|
+
* `t s tr tc` rectangle compact in both dimensions
|
|
5437
|
+
* `c s col tr` narrow columns, row tiles (the common case)
|
|
5438
|
+
* `C s col` narrow columns, every row (`A:A`)
|
|
5439
|
+
* `r s row tc` narrow rows, column tiles
|
|
5440
|
+
* `R s row` narrow rows, every column (`1:1`)
|
|
5441
|
+
* `w s` broad in both dimensions
|
|
5442
|
+
*/
|
|
5443
|
+
#buckets = /* @__PURE__ */ new Map();
|
|
5444
|
+
/** formula key → the buckets it was filed under, for cheap removal. */
|
|
5445
|
+
#placements = /* @__PURE__ */ new Map();
|
|
5433
5446
|
/** Registers (or re-registers) a formula's precedent rectangles. */
|
|
5434
5447
|
add(formulaKey, refs) {
|
|
5435
5448
|
this.remove(formulaKey);
|
|
@@ -5446,12 +5459,12 @@ var DependencyIndex = class {
|
|
|
5446
5459
|
if (colSpan * rowTiles <= MAX_BUCKETS_PER_REF && rowTiles <= MAX_TILES_PER_REF) {
|
|
5447
5460
|
for (let col = ref.startCol; col <= ref.endCol; col++) {
|
|
5448
5461
|
for (let tr = rowTile0; tr <= rowTile1; tr++) {
|
|
5449
|
-
|
|
5462
|
+
this.#file(`c ${ref.sheet} ${col} ${tr}`, formulaKey, buckets);
|
|
5450
5463
|
}
|
|
5451
5464
|
}
|
|
5452
5465
|
} else {
|
|
5453
5466
|
for (let col = ref.startCol; col <= ref.endCol; col++) {
|
|
5454
|
-
|
|
5467
|
+
this.#file(`C ${ref.sheet} ${col}`, formulaKey, buckets);
|
|
5455
5468
|
}
|
|
5456
5469
|
}
|
|
5457
5470
|
continue;
|
|
@@ -5460,12 +5473,12 @@ var DependencyIndex = class {
|
|
|
5460
5473
|
if (rowSpan * colTiles <= MAX_BUCKETS_PER_REF && colTiles <= MAX_TILES_PER_REF) {
|
|
5461
5474
|
for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
|
|
5462
5475
|
for (let tc = colTile0; tc <= colTile1; tc++) {
|
|
5463
|
-
|
|
5476
|
+
this.#file(`r ${ref.sheet} ${row2} ${tc}`, formulaKey, buckets);
|
|
5464
5477
|
}
|
|
5465
5478
|
}
|
|
5466
5479
|
} else {
|
|
5467
5480
|
for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
|
|
5468
|
-
|
|
5481
|
+
this.#file(`R ${ref.sheet} ${row2}`, formulaKey, buckets);
|
|
5469
5482
|
}
|
|
5470
5483
|
}
|
|
5471
5484
|
continue;
|
|
@@ -5473,31 +5486,31 @@ var DependencyIndex = class {
|
|
|
5473
5486
|
if (rowTiles * colTiles <= MAX_TILES_PER_REF) {
|
|
5474
5487
|
for (let tr = rowTile0; tr <= rowTile1; tr++) {
|
|
5475
5488
|
for (let tc = colTile0; tc <= colTile1; tc++) {
|
|
5476
|
-
|
|
5489
|
+
this.#file(`t ${ref.sheet} ${tr} ${tc}`, formulaKey, buckets);
|
|
5477
5490
|
}
|
|
5478
5491
|
}
|
|
5479
5492
|
continue;
|
|
5480
5493
|
}
|
|
5481
|
-
|
|
5494
|
+
this.#file(`w ${ref.sheet}`, formulaKey, buckets);
|
|
5482
5495
|
}
|
|
5483
|
-
|
|
5496
|
+
this.#placements.set(formulaKey, buckets);
|
|
5484
5497
|
}
|
|
5485
5498
|
/** Forgets a formula. */
|
|
5486
5499
|
remove(formulaKey) {
|
|
5487
|
-
const buckets =
|
|
5500
|
+
const buckets = this.#placements.get(formulaKey);
|
|
5488
5501
|
if (!buckets) return;
|
|
5489
5502
|
for (const bucket of buckets) {
|
|
5490
|
-
const set =
|
|
5503
|
+
const set = this.#buckets.get(bucket);
|
|
5491
5504
|
if (!set) continue;
|
|
5492
5505
|
set.delete(formulaKey);
|
|
5493
|
-
if (set.size === 0)
|
|
5506
|
+
if (set.size === 0) this.#buckets.delete(bucket);
|
|
5494
5507
|
}
|
|
5495
|
-
|
|
5508
|
+
this.#placements.delete(formulaKey);
|
|
5496
5509
|
}
|
|
5497
5510
|
/** Drops every registration. */
|
|
5498
5511
|
clear() {
|
|
5499
|
-
|
|
5500
|
-
|
|
5512
|
+
this.#buckets.clear();
|
|
5513
|
+
this.#placements.clear();
|
|
5501
5514
|
}
|
|
5502
5515
|
/**
|
|
5503
5516
|
* Calls `visit` with the key of each formula that *may* read the given cell.
|
|
@@ -5509,31 +5522,28 @@ var DependencyIndex = class {
|
|
|
5509
5522
|
candidates(sheet, row2, col, visit) {
|
|
5510
5523
|
const tr = tileOfRow(row2);
|
|
5511
5524
|
const tc = tileOfCol(col);
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5525
|
+
this.#visit(`c ${sheet} ${col} ${tr}`, visit);
|
|
5526
|
+
this.#visit(`C ${sheet} ${col}`, visit);
|
|
5527
|
+
this.#visit(`r ${sheet} ${row2} ${tc}`, visit);
|
|
5528
|
+
this.#visit(`R ${sheet} ${row2}`, visit);
|
|
5529
|
+
this.#visit(`t ${sheet} ${tr} ${tc}`, visit);
|
|
5530
|
+
this.#visit(`w ${sheet}`, visit);
|
|
5531
|
+
}
|
|
5532
|
+
#visit(bucket, visit) {
|
|
5533
|
+
const set = this.#buckets.get(bucket);
|
|
5534
|
+
if (set) for (const key of set) visit(key);
|
|
5535
|
+
}
|
|
5536
|
+
#file(bucket, formulaKey, buckets) {
|
|
5537
|
+
let set = this.#buckets.get(bucket);
|
|
5538
|
+
if (!set) {
|
|
5539
|
+
set = /* @__PURE__ */ new Set();
|
|
5540
|
+
this.#buckets.set(bucket, set);
|
|
5541
|
+
}
|
|
5542
|
+
if (set.has(formulaKey)) return;
|
|
5543
|
+
set.add(formulaKey);
|
|
5544
|
+
buckets.push(bucket);
|
|
5518
5545
|
}
|
|
5519
5546
|
};
|
|
5520
|
-
_buckets = new WeakMap();
|
|
5521
|
-
_placements2 = new WeakMap();
|
|
5522
|
-
_DependencyIndex_instances = new WeakSet();
|
|
5523
|
-
visit_fn = function(bucket, visit) {
|
|
5524
|
-
const set = __privateGet(this, _buckets).get(bucket);
|
|
5525
|
-
if (set) for (const key of set) visit(key);
|
|
5526
|
-
};
|
|
5527
|
-
file_fn = function(bucket, formulaKey, buckets) {
|
|
5528
|
-
let set = __privateGet(this, _buckets).get(bucket);
|
|
5529
|
-
if (!set) {
|
|
5530
|
-
set = /* @__PURE__ */ new Set();
|
|
5531
|
-
__privateGet(this, _buckets).set(bucket, set);
|
|
5532
|
-
}
|
|
5533
|
-
if (set.has(formulaKey)) return;
|
|
5534
|
-
set.add(formulaKey);
|
|
5535
|
-
buckets.push(bucket);
|
|
5536
|
-
};
|
|
5537
5547
|
|
|
5538
5548
|
// src/recalc-engine.ts
|
|
5539
5549
|
function createWorkbookRecalcModel(workbook) {
|
|
@@ -5577,42 +5587,70 @@ function parseKey(key) {
|
|
|
5577
5587
|
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
5578
5588
|
return { sheet, ref, row: row2, col: column2 };
|
|
5579
5589
|
}
|
|
5580
|
-
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, adoptDeclaredSpill_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
|
|
5581
5590
|
var RecalcEngine = class {
|
|
5591
|
+
#model;
|
|
5592
|
+
#registry;
|
|
5593
|
+
#formulas = /* @__PURE__ */ new Map();
|
|
5594
|
+
/** upper-cased sheet key → original casing, for case-correct model I/O. */
|
|
5595
|
+
#sheetNames = /* @__PURE__ */ new Map();
|
|
5596
|
+
/** anchor cellKey → set of spilled (non-anchor) cellKeys it currently owns. */
|
|
5597
|
+
#spills = /* @__PURE__ */ new Map();
|
|
5598
|
+
/** spilled cellKey → the anchor cellKey that owns it. */
|
|
5599
|
+
#spillOwner = /* @__PURE__ */ new Map();
|
|
5600
|
+
/**
|
|
5601
|
+
* Reverse dependency index, so finding the formulas that read a cell does not
|
|
5602
|
+
* mean testing every registered formula.
|
|
5603
|
+
*/
|
|
5604
|
+
#dependents = new DependencyIndex();
|
|
5605
|
+
/**
|
|
5606
|
+
* Keys of the volatile formulas. Maintained incrementally: scanning every
|
|
5607
|
+
* formula for volatility on each edit is O(F) per keystroke on its own.
|
|
5608
|
+
*/
|
|
5609
|
+
#volatiles = /* @__PURE__ */ new Set();
|
|
5610
|
+
/** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
|
|
5611
|
+
#sheetLikes = /* @__PURE__ */ new Map();
|
|
5612
|
+
#workbookLike;
|
|
5613
|
+
/**
|
|
5614
|
+
* Memo for range reads. The engine owns it because it owns the writes: every
|
|
5615
|
+
* value written goes through {@link #writeCell}, which drops the cached
|
|
5616
|
+
* rectangles containing that cell. Nothing else may write to the model behind
|
|
5617
|
+
* the engine's back without calling {@link onCellChanged} — that was already
|
|
5618
|
+
* true for dependency tracking, and now also keeps this memo honest.
|
|
5619
|
+
*/
|
|
5620
|
+
#rangeCache = new RangeCache();
|
|
5582
5621
|
constructor(model, registry = FunctionRegistry.default) {
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
__privateSet(this, _registry, registry);
|
|
5622
|
+
this.#model = model;
|
|
5623
|
+
this.#registry = registry;
|
|
5624
|
+
}
|
|
5625
|
+
/**
|
|
5626
|
+
* The single write path into the model. Writing a value invalidates the
|
|
5627
|
+
* cached rectangles that contain the cell, so a formula evaluated later in the
|
|
5628
|
+
* same pass cannot read a pre-write copy of a range. Coordinates are passed in
|
|
5629
|
+
* where the caller already knows them, to avoid re-parsing the reference.
|
|
5630
|
+
*/
|
|
5631
|
+
#writeCell(sheetName, ref, value2, row2, col) {
|
|
5632
|
+
this.#model.setCellValue(sheetName, ref, value2);
|
|
5633
|
+
if (row2 === void 0 || col === void 0) {
|
|
5634
|
+
const parsed = parseCellRef(ref);
|
|
5635
|
+
row2 = parsed.row;
|
|
5636
|
+
col = parsed.column;
|
|
5637
|
+
}
|
|
5638
|
+
this.#rangeCache.invalidateCell(normSheet(sheetName), row2, col);
|
|
5639
|
+
}
|
|
5640
|
+
/** Registers an entry in the map, the reverse index and the volatile set. */
|
|
5641
|
+
#register(entry) {
|
|
5642
|
+
this.#formulas.set(entry.key, entry);
|
|
5643
|
+
this.#dependents.add(entry.key, entry.refs);
|
|
5644
|
+
if (entry.volatile) this.#volatiles.add(entry.key);
|
|
5645
|
+
else this.#volatiles.delete(entry.key);
|
|
5646
|
+
}
|
|
5647
|
+
#unregister(key) {
|
|
5648
|
+
this.#formulas.delete(key);
|
|
5649
|
+
this.#dependents.remove(key);
|
|
5650
|
+
this.#volatiles.delete(key);
|
|
5651
|
+
}
|
|
5652
|
+
#rememberSheet(name) {
|
|
5653
|
+
this.#sheetNames.set(normSheet(name), name);
|
|
5616
5654
|
}
|
|
5617
5655
|
// ── Registration ─────────────────────────────────────────────────────────────
|
|
5618
5656
|
/**
|
|
@@ -5630,12 +5668,12 @@ var RecalcEngine = class {
|
|
|
5630
5668
|
const { refs, volatile } = extractReferences(
|
|
5631
5669
|
ast,
|
|
5632
5670
|
s,
|
|
5633
|
-
|
|
5634
|
-
|
|
5671
|
+
this.#registry,
|
|
5672
|
+
this.#model.getDefinedName ? (n, sh) => this.#model.getDefinedName(n, sh) : void 0
|
|
5635
5673
|
);
|
|
5636
|
-
|
|
5674
|
+
this.#rememberSheet(sheet);
|
|
5637
5675
|
const key = keyOf(s, r);
|
|
5638
|
-
|
|
5676
|
+
this.#register({
|
|
5639
5677
|
sheet: s,
|
|
5640
5678
|
sheetName: sheet,
|
|
5641
5679
|
ref: r,
|
|
@@ -5647,9 +5685,9 @@ var RecalcEngine = class {
|
|
|
5647
5685
|
refs,
|
|
5648
5686
|
volatile
|
|
5649
5687
|
});
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
return
|
|
5688
|
+
this.#model.setCellFormula?.(sheet, r, text);
|
|
5689
|
+
this.#rangeCache.invalidateCell(s, row2, column2);
|
|
5690
|
+
return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }], key);
|
|
5653
5691
|
}
|
|
5654
5692
|
/**
|
|
5655
5693
|
* Indexes many formulas **without evaluating anything**.
|
|
@@ -5671,7 +5709,7 @@ var RecalcEngine = class {
|
|
|
5671
5709
|
* construct in a large workbook should not abort the load.
|
|
5672
5710
|
*/
|
|
5673
5711
|
registerBulk(entries) {
|
|
5674
|
-
const resolveName2 =
|
|
5712
|
+
const resolveName2 = this.#model.getDefinedName ? (n, sh) => this.#model.getDefinedName(n, sh) : void 0;
|
|
5675
5713
|
let registered = 0;
|
|
5676
5714
|
const failed = [];
|
|
5677
5715
|
for (const entry of entries) {
|
|
@@ -5683,10 +5721,10 @@ var RecalcEngine = class {
|
|
|
5683
5721
|
try {
|
|
5684
5722
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5685
5723
|
const ast = parseFormulaUncached(text);
|
|
5686
|
-
const { refs, volatile } = extractReferences(ast, s,
|
|
5687
|
-
|
|
5724
|
+
const { refs, volatile } = extractReferences(ast, s, this.#registry, resolveName2);
|
|
5725
|
+
this.#rememberSheet(sheet);
|
|
5688
5726
|
const key = keyOf(s, r);
|
|
5689
|
-
|
|
5727
|
+
this.#register({
|
|
5690
5728
|
sheet: s,
|
|
5691
5729
|
sheetName: sheet,
|
|
5692
5730
|
ref: r,
|
|
@@ -5698,15 +5736,60 @@ var RecalcEngine = class {
|
|
|
5698
5736
|
refs,
|
|
5699
5737
|
volatile
|
|
5700
5738
|
});
|
|
5701
|
-
|
|
5739
|
+
this.#adoptDeclaredSpill(s, sheet, r, key, row2, column2);
|
|
5702
5740
|
registered++;
|
|
5703
5741
|
} catch (err) {
|
|
5704
5742
|
failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
|
|
5705
5743
|
}
|
|
5706
5744
|
}
|
|
5707
|
-
if (registered > 0)
|
|
5745
|
+
if (registered > 0) this.#rangeCache.clear();
|
|
5708
5746
|
return { registered, failed };
|
|
5709
5747
|
}
|
|
5748
|
+
/**
|
|
5749
|
+
* Takes ownership of the spill block a *file* already declared for an anchor.
|
|
5750
|
+
*
|
|
5751
|
+
* Excel stores a dynamic array as one formula on the anchor plus the spilled
|
|
5752
|
+
* results written as plain cached values in the cells below/right, and records
|
|
5753
|
+
* the extent in the anchor's `<f t="array" ref="…">`. Without this step the
|
|
5754
|
+
* engine meets those cached values on the first recalculation, sees literal
|
|
5755
|
+
* content inside the block it wants to spill into, and reports `#SPILL!` for
|
|
5756
|
+
* every dynamic array in every real file. Registering the declared block as
|
|
5757
|
+
* this anchor's existing spill makes them what they actually are — the anchor's
|
|
5758
|
+
* own territory, free to overwrite — and reuses the ordinary shrink path, so a
|
|
5759
|
+
* result smaller than the declared block clears the leftovers.
|
|
5760
|
+
*
|
|
5761
|
+
* Cost is one Set entry per already-spilled cell, paid once at load. A declared
|
|
5762
|
+
* block far larger than any real spill (a corrupt or hand-written `ref`) is
|
|
5763
|
+
* skipped rather than materialised.
|
|
5764
|
+
*/
|
|
5765
|
+
#adoptDeclaredSpill(s, sheetName, ref, anchorKey, row2, col) {
|
|
5766
|
+
const declared = this.#model.getSpillRange?.(sheetName, ref);
|
|
5767
|
+
if (!declared || !declared.includes(":")) return;
|
|
5768
|
+
let rect;
|
|
5769
|
+
try {
|
|
5770
|
+
rect = parseRangeRef(declared);
|
|
5771
|
+
} catch {
|
|
5772
|
+
return;
|
|
5773
|
+
}
|
|
5774
|
+
if (rect.startRow !== row2 || rect.startColumn !== col) return;
|
|
5775
|
+
const height = rect.endRow - rect.startRow + 1;
|
|
5776
|
+
const width = rect.endColumn - rect.startColumn + 1;
|
|
5777
|
+
if (height < 1 || width < 1) return;
|
|
5778
|
+
if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
|
|
5779
|
+
if (height === 1 && width === 1) return;
|
|
5780
|
+
const members = /* @__PURE__ */ new Set();
|
|
5781
|
+
for (let r = rect.startRow; r <= rect.endRow; r++) {
|
|
5782
|
+
for (let c = rect.startColumn; c <= rect.endColumn; c++) {
|
|
5783
|
+
if (r === row2 && c === col) continue;
|
|
5784
|
+
const key = keyOf(s, cellRefFromRowCol(r, c));
|
|
5785
|
+
if (this.#formulas.has(key)) continue;
|
|
5786
|
+
if (this.#spillOwner.has(key)) continue;
|
|
5787
|
+
members.add(key);
|
|
5788
|
+
this.#spillOwner.set(key, anchorKey);
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
if (members.size > 0) this.#spills.set(anchorKey, members);
|
|
5792
|
+
}
|
|
5710
5793
|
/**
|
|
5711
5794
|
* Removes a formula from a cell and recalculates its dependents (which may now
|
|
5712
5795
|
* read a plain input value instead). Returns the recomputed dependent cells.
|
|
@@ -5715,36 +5798,36 @@ var RecalcEngine = class {
|
|
|
5715
5798
|
const s = normSheet(sheet);
|
|
5716
5799
|
const r = normRef(ref);
|
|
5717
5800
|
const key = keyOf(s, r);
|
|
5718
|
-
if (!
|
|
5719
|
-
|
|
5720
|
-
|
|
5801
|
+
if (!this.#formulas.has(key)) return [];
|
|
5802
|
+
this.#unregister(key);
|
|
5803
|
+
this.#model.clearCell?.(sheet, r);
|
|
5721
5804
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5722
|
-
|
|
5723
|
-
return
|
|
5805
|
+
this.#rangeCache.invalidateCell(s, row2, column2);
|
|
5806
|
+
return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }]);
|
|
5724
5807
|
}
|
|
5725
5808
|
/**
|
|
5726
5809
|
* Writes a raw input value to a cell and recalculates every formula that
|
|
5727
5810
|
* depends on it. Use this for non-formula edits so dependents stay current.
|
|
5728
5811
|
*/
|
|
5729
5812
|
setCellValue(sheet, ref, value2) {
|
|
5730
|
-
|
|
5813
|
+
this.#rememberSheet(sheet);
|
|
5731
5814
|
const s = normSheet(sheet);
|
|
5732
5815
|
const r = normRef(ref);
|
|
5733
5816
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5734
|
-
|
|
5735
|
-
return
|
|
5817
|
+
this.#writeCell(sheet, r, value2, row2, column2);
|
|
5818
|
+
return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }]);
|
|
5736
5819
|
}
|
|
5737
5820
|
/**
|
|
5738
5821
|
* Signals that a cell changed by some external means (the model was already
|
|
5739
5822
|
* updated) and recalculates its dependents.
|
|
5740
5823
|
*/
|
|
5741
5824
|
onCellChanged(sheet, ref) {
|
|
5742
|
-
|
|
5825
|
+
this.#rememberSheet(sheet);
|
|
5743
5826
|
const s = normSheet(sheet);
|
|
5744
5827
|
const r = normRef(ref);
|
|
5745
5828
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5746
|
-
|
|
5747
|
-
return
|
|
5829
|
+
this.#rangeCache.invalidateCell(s, row2, column2);
|
|
5830
|
+
return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }]);
|
|
5748
5831
|
}
|
|
5749
5832
|
/**
|
|
5750
5833
|
* Recalculates every registered formula.
|
|
@@ -5777,18 +5860,61 @@ var RecalcEngine = class {
|
|
|
5777
5860
|
recalcAll(options) {
|
|
5778
5861
|
const order = options?.order;
|
|
5779
5862
|
if (!order || order.length === 0) {
|
|
5780
|
-
return
|
|
5863
|
+
return this.#evaluatePass([...this.#formulas.values()]).results;
|
|
5781
5864
|
}
|
|
5782
|
-
const attempt =
|
|
5865
|
+
const attempt = this.#recalcInOrder(order);
|
|
5783
5866
|
if (attempt.conflict) {
|
|
5784
5867
|
options?.onOrderConflict?.(attempt.conflict);
|
|
5785
|
-
return
|
|
5868
|
+
return this.#evaluatePass([...this.#formulas.values()]).results;
|
|
5786
5869
|
}
|
|
5787
5870
|
return attempt.results;
|
|
5788
5871
|
}
|
|
5872
|
+
/**
|
|
5873
|
+
* One pass in a caller-supplied order, watching for inversions.
|
|
5874
|
+
*
|
|
5875
|
+
* The check is the mirror image of the topological sort: instead of asking
|
|
5876
|
+
* "which of my precedents come first?", it asks, of each formula just
|
|
5877
|
+
* evaluated, "has anything that reads me already run?". The dependency index
|
|
5878
|
+
* answers that directly, and one positive answer is enough to know the order
|
|
5879
|
+
* cannot be trusted.
|
|
5880
|
+
*/
|
|
5881
|
+
#recalcInOrder(order) {
|
|
5882
|
+
const results = [];
|
|
5883
|
+
const changed = [];
|
|
5884
|
+
const done = /* @__PURE__ */ new Set();
|
|
5885
|
+
let spilled = false;
|
|
5886
|
+
for (const { sheet, ref } of order) {
|
|
5887
|
+
const key = keyOf(sheet, ref);
|
|
5888
|
+
if (done.has(key)) continue;
|
|
5889
|
+
const entry = this.#formulas.get(key);
|
|
5890
|
+
if (!entry) continue;
|
|
5891
|
+
done.add(key);
|
|
5892
|
+
const written = this.#writeResult(entry);
|
|
5893
|
+
results.push(written.result);
|
|
5894
|
+
changed.push(...written.changed);
|
|
5895
|
+
if (written.result.spill) spilled = true;
|
|
5896
|
+
let conflict;
|
|
5897
|
+
this.#forEachDirectDependent({ sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
|
|
5898
|
+
if (conflict || dep.key === entry.key) return;
|
|
5899
|
+
if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
|
|
5900
|
+
});
|
|
5901
|
+
if (conflict) return { results: [], conflict };
|
|
5902
|
+
}
|
|
5903
|
+
const rest = [...this.#formulas.values()].filter((e) => !done.has(e.key));
|
|
5904
|
+
if (rest.length > 0) {
|
|
5905
|
+
const pass = this.#evaluatePass(rest);
|
|
5906
|
+
results.push(...pass.results);
|
|
5907
|
+
changed.push(...pass.changed);
|
|
5908
|
+
}
|
|
5909
|
+
if (spilled) {
|
|
5910
|
+
const evaluated = new Set(done);
|
|
5911
|
+
results.push(...this.#runFixpoint(this.#dependentClosure(changed, evaluated), evaluated));
|
|
5912
|
+
}
|
|
5913
|
+
return { results };
|
|
5914
|
+
}
|
|
5789
5915
|
/** The precedents recorded for a formula cell (mainly for tests/inspection). */
|
|
5790
5916
|
getPrecedents(sheet, ref) {
|
|
5791
|
-
return
|
|
5917
|
+
return this.#formulas.get(keyOf(sheet, ref))?.refs ?? [];
|
|
5792
5918
|
}
|
|
5793
5919
|
// ── Structural edits ─────────────────────────────────────────────────────────
|
|
5794
5920
|
/**
|
|
@@ -5803,7 +5929,7 @@ var RecalcEngine = class {
|
|
|
5803
5929
|
* engine.onRowsInserted('S1', 3, 2);
|
|
5804
5930
|
*/
|
|
5805
5931
|
onRowsInserted(sheet, at, count2 = 1) {
|
|
5806
|
-
return
|
|
5932
|
+
return this.#applyShift({ dim: "row", kind: "insert", at, count: count2, sheetName: sheet });
|
|
5807
5933
|
}
|
|
5808
5934
|
/**
|
|
5809
5935
|
* Tells the engine rows were deleted on a sheet. Call AFTER the
|
|
@@ -5813,556 +5939,420 @@ var RecalcEngine = class {
|
|
|
5813
5939
|
* volatiles) are recomputed.
|
|
5814
5940
|
*/
|
|
5815
5941
|
onRowsDeleted(sheet, at, count2 = 1) {
|
|
5816
|
-
return
|
|
5942
|
+
return this.#applyShift({ dim: "row", kind: "delete", at, count: count2, sheetName: sheet });
|
|
5817
5943
|
}
|
|
5818
5944
|
/** Column counterpart to {@link onRowsInserted}. */
|
|
5819
5945
|
onColumnsInserted(sheet, at, count2 = 1) {
|
|
5820
|
-
return
|
|
5946
|
+
return this.#applyShift({ dim: "col", kind: "insert", at, count: count2, sheetName: sheet });
|
|
5821
5947
|
}
|
|
5822
5948
|
/** Column counterpart to {@link onRowsDeleted}. */
|
|
5823
5949
|
onColumnsDeleted(sheet, at, count2 = 1) {
|
|
5824
|
-
return
|
|
5825
|
-
}
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
else __privateGet(this, _volatiles).delete(entry.key);
|
|
5860
|
-
};
|
|
5861
|
-
unregister_fn = function(key) {
|
|
5862
|
-
__privateGet(this, _formulas).delete(key);
|
|
5863
|
-
__privateGet(this, _dependents).remove(key);
|
|
5864
|
-
__privateGet(this, _volatiles).delete(key);
|
|
5865
|
-
};
|
|
5866
|
-
rememberSheet_fn = function(name) {
|
|
5867
|
-
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
5868
|
-
};
|
|
5869
|
-
/**
|
|
5870
|
-
* Takes ownership of the spill block a *file* already declared for an anchor.
|
|
5871
|
-
*
|
|
5872
|
-
* Excel stores a dynamic array as one formula on the anchor plus the spilled
|
|
5873
|
-
* results written as plain cached values in the cells below/right, and records
|
|
5874
|
-
* the extent in the anchor's `<f t="array" ref="…">`. Without this step the
|
|
5875
|
-
* engine meets those cached values on the first recalculation, sees literal
|
|
5876
|
-
* content inside the block it wants to spill into, and reports `#SPILL!` for
|
|
5877
|
-
* every dynamic array in every real file. Registering the declared block as
|
|
5878
|
-
* this anchor's existing spill makes them what they actually are — the anchor's
|
|
5879
|
-
* own territory, free to overwrite — and reuses the ordinary shrink path, so a
|
|
5880
|
-
* result smaller than the declared block clears the leftovers.
|
|
5881
|
-
*
|
|
5882
|
-
* Cost is one Set entry per already-spilled cell, paid once at load. A declared
|
|
5883
|
-
* block far larger than any real spill (a corrupt or hand-written `ref`) is
|
|
5884
|
-
* skipped rather than materialised.
|
|
5885
|
-
*/
|
|
5886
|
-
adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
|
|
5887
|
-
const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
|
|
5888
|
-
if (!declared || !declared.includes(":")) return;
|
|
5889
|
-
let rect;
|
|
5890
|
-
try {
|
|
5891
|
-
rect = parseRangeRef(declared);
|
|
5892
|
-
} catch {
|
|
5893
|
-
return;
|
|
5894
|
-
}
|
|
5895
|
-
if (rect.startRow !== row2 || rect.startColumn !== col) return;
|
|
5896
|
-
const height = rect.endRow - rect.startRow + 1;
|
|
5897
|
-
const width = rect.endColumn - rect.startColumn + 1;
|
|
5898
|
-
if (height < 1 || width < 1) return;
|
|
5899
|
-
if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
|
|
5900
|
-
if (height === 1 && width === 1) return;
|
|
5901
|
-
const members = /* @__PURE__ */ new Set();
|
|
5902
|
-
for (let r = rect.startRow; r <= rect.endRow; r++) {
|
|
5903
|
-
for (let c = rect.startColumn; c <= rect.endColumn; c++) {
|
|
5904
|
-
if (r === row2 && c === col) continue;
|
|
5905
|
-
const key = keyOf(s, cellRefFromRowCol(r, c));
|
|
5906
|
-
if (__privateGet(this, _formulas).has(key)) continue;
|
|
5907
|
-
if (__privateGet(this, _spillOwner).has(key)) continue;
|
|
5908
|
-
members.add(key);
|
|
5909
|
-
__privateGet(this, _spillOwner).set(key, anchorKey);
|
|
5910
|
-
}
|
|
5911
|
-
}
|
|
5912
|
-
if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
|
|
5913
|
-
};
|
|
5914
|
-
/**
|
|
5915
|
-
* One pass in a caller-supplied order, watching for inversions.
|
|
5916
|
-
*
|
|
5917
|
-
* The check is the mirror image of the topological sort: instead of asking
|
|
5918
|
-
* "which of my precedents come first?", it asks, of each formula just
|
|
5919
|
-
* evaluated, "has anything that reads me already run?". The dependency index
|
|
5920
|
-
* answers that directly, and one positive answer is enough to know the order
|
|
5921
|
-
* cannot be trusted.
|
|
5922
|
-
*/
|
|
5923
|
-
recalcInOrder_fn = function(order) {
|
|
5924
|
-
const results = [];
|
|
5925
|
-
const changed = [];
|
|
5926
|
-
const done = /* @__PURE__ */ new Set();
|
|
5927
|
-
let spilled = false;
|
|
5928
|
-
for (const { sheet, ref } of order) {
|
|
5929
|
-
const key = keyOf(sheet, ref);
|
|
5930
|
-
if (done.has(key)) continue;
|
|
5931
|
-
const entry = __privateGet(this, _formulas).get(key);
|
|
5932
|
-
if (!entry) continue;
|
|
5933
|
-
done.add(key);
|
|
5934
|
-
const written = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, entry);
|
|
5935
|
-
results.push(written.result);
|
|
5936
|
-
changed.push(...written.changed);
|
|
5937
|
-
if (written.result.spill) spilled = true;
|
|
5938
|
-
let conflict;
|
|
5939
|
-
__privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
|
|
5940
|
-
if (conflict || dep.key === entry.key) return;
|
|
5941
|
-
if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
|
|
5942
|
-
});
|
|
5943
|
-
if (conflict) return { results: [], conflict };
|
|
5944
|
-
}
|
|
5945
|
-
const rest = [...__privateGet(this, _formulas).values()].filter((e) => !done.has(e.key));
|
|
5946
|
-
if (rest.length > 0) {
|
|
5947
|
-
const pass = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, rest);
|
|
5948
|
-
results.push(...pass.results);
|
|
5949
|
-
changed.push(...pass.changed);
|
|
5950
|
-
}
|
|
5951
|
-
if (spilled) {
|
|
5952
|
-
const evaluated = new Set(done);
|
|
5953
|
-
results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated), evaluated));
|
|
5954
|
-
}
|
|
5955
|
-
return { results };
|
|
5956
|
-
};
|
|
5957
|
-
applyShift_fn = function(shift) {
|
|
5958
|
-
const editedSheet = normSheet(shift.sheetName);
|
|
5959
|
-
const isRowShift = shift.dim === "row";
|
|
5960
|
-
const bandEnd = shift.at + shift.count - 1;
|
|
5961
|
-
const rekeyed = [];
|
|
5962
|
-
const needsRecalc = /* @__PURE__ */ new Set();
|
|
5963
|
-
for (const e of __privateGet(this, _formulas).values()) {
|
|
5964
|
-
let row2 = e.row;
|
|
5965
|
-
let col = e.col;
|
|
5966
|
-
if (e.sheet === editedSheet) {
|
|
5967
|
-
const shifted = shiftIndex(isRowShift ? row2 : col, shift);
|
|
5968
|
-
if (shifted === null) continue;
|
|
5969
|
-
if (isRowShift) row2 = shifted;
|
|
5970
|
-
else col = shifted;
|
|
5971
|
-
}
|
|
5972
|
-
const touchedBand = shift.kind === "delete" && e.refs.some((r) => r.sheet === editedSheet && (isRowShift ? r.endRow >= shift.at && r.startRow <= bandEnd : r.endCol >= shift.at && r.startCol <= bandEnd));
|
|
5973
|
-
const rewritten = shiftFormulaRefs(e.formula, shift, e.sheetName);
|
|
5974
|
-
const moved = row2 !== e.row || col !== e.col;
|
|
5975
|
-
const ref = moved ? cellRefFromRowCol(row2, col) : e.ref;
|
|
5976
|
-
const key = moved ? keyOf(e.sheet, ref) : e.key;
|
|
5977
|
-
rekeyed.push({
|
|
5978
|
-
...e,
|
|
5979
|
-
ref,
|
|
5980
|
-
key,
|
|
5981
|
-
row: row2,
|
|
5982
|
-
col,
|
|
5983
|
-
formula: rewritten.changed ? rewritten.text : e.formula,
|
|
5984
|
-
// A rewritten formula's cached AST no longer matches its text; it is
|
|
5985
|
-
// re-parsed lazily, and only if the cell is actually evaluated again.
|
|
5986
|
-
ast: rewritten.changed ? void 0 : e.ast,
|
|
5987
|
-
refs: __privateMethod(this, _RecalcEngine_instances, shiftPrecedents_fn).call(this, e.refs, shift, editedSheet)
|
|
5988
|
-
// Volatility is a property of which *functions* a formula calls, and
|
|
5989
|
-
// rewriting references cannot change that.
|
|
5990
|
-
});
|
|
5991
|
-
if (touchedBand || rewritten.hasRefError) needsRecalc.add(key);
|
|
5992
|
-
}
|
|
5993
|
-
__privateSet(this, _formulas, /* @__PURE__ */ new Map());
|
|
5994
|
-
__privateGet(this, _dependents).clear();
|
|
5995
|
-
__privateGet(this, _volatiles).clear();
|
|
5996
|
-
for (const e of rekeyed) __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, e);
|
|
5997
|
-
__privateMethod(this, _RecalcEngine_instances, shiftSpillBookkeeping_fn).call(this, shift, editedSheet);
|
|
5998
|
-
__privateGet(this, _rangeCache).clear();
|
|
5999
|
-
const pending = /* @__PURE__ */ new Map();
|
|
6000
|
-
for (const k of needsRecalc) {
|
|
6001
|
-
const e = __privateGet(this, _formulas).get(k);
|
|
6002
|
-
if (e) pending.set(k, e);
|
|
6003
|
-
}
|
|
6004
|
-
for (const e of __privateGet(this, _formulas).values()) {
|
|
6005
|
-
if (e.volatile) pending.set(e.key, e);
|
|
6006
|
-
}
|
|
6007
|
-
return __privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, /* @__PURE__ */ new Set());
|
|
6008
|
-
};
|
|
6009
|
-
/**
|
|
6010
|
-
* Translates precedent rectangles across a structural edit.
|
|
6011
|
-
*
|
|
6012
|
-
* This is arithmetic rather than a re-parse of the rewritten formula, and the
|
|
6013
|
-
* two are equivalent by construction: `shiftFormulaRefs` derives the new
|
|
6014
|
-
* reference text by calling `shiftSpan`/`shiftIndex` on exactly these numbers,
|
|
6015
|
-
* so applying the same functions to the stored rectangle yields the same
|
|
6016
|
-
* region. A rectangle that is entirely deleted disappears, matching the
|
|
6017
|
-
* `#REF!` the text rewrite produces.
|
|
6018
|
-
*/
|
|
6019
|
-
shiftPrecedents_fn = function(refs, shift, editedSheet) {
|
|
6020
|
-
const out = [];
|
|
6021
|
-
for (const r of refs) {
|
|
6022
|
-
if (r.sheet !== editedSheet) {
|
|
6023
|
-
out.push(r);
|
|
6024
|
-
continue;
|
|
6025
|
-
}
|
|
6026
|
-
if (shift.dim === "row") {
|
|
6027
|
-
const span = shiftSpan(r.startRow, r.endRow, shift);
|
|
6028
|
-
if (!span) continue;
|
|
6029
|
-
out.push({
|
|
6030
|
-
sheet: r.sheet,
|
|
6031
|
-
startRow: span.start,
|
|
6032
|
-
endRow: Math.min(span.end, EXCEL_MAX_ROWS),
|
|
6033
|
-
startCol: r.startCol,
|
|
6034
|
-
endCol: r.endCol
|
|
6035
|
-
});
|
|
6036
|
-
} else {
|
|
6037
|
-
const span = shiftSpan(r.startCol, r.endCol, shift);
|
|
6038
|
-
if (!span) continue;
|
|
6039
|
-
out.push({
|
|
6040
|
-
sheet: r.sheet,
|
|
6041
|
-
startRow: r.startRow,
|
|
6042
|
-
endRow: r.endRow,
|
|
6043
|
-
startCol: span.start,
|
|
6044
|
-
endCol: Math.min(span.end, EXCEL_MAX_COLUMNS)
|
|
5950
|
+
return this.#applyShift({ dim: "col", kind: "delete", at, count: count2, sheetName: sheet });
|
|
5951
|
+
}
|
|
5952
|
+
#applyShift(shift) {
|
|
5953
|
+
const editedSheet = normSheet(shift.sheetName);
|
|
5954
|
+
const isRowShift = shift.dim === "row";
|
|
5955
|
+
const bandEnd = shift.at + shift.count - 1;
|
|
5956
|
+
const rekeyed = [];
|
|
5957
|
+
const needsRecalc = /* @__PURE__ */ new Set();
|
|
5958
|
+
for (const e of this.#formulas.values()) {
|
|
5959
|
+
let row2 = e.row;
|
|
5960
|
+
let col = e.col;
|
|
5961
|
+
if (e.sheet === editedSheet) {
|
|
5962
|
+
const shifted = shiftIndex(isRowShift ? row2 : col, shift);
|
|
5963
|
+
if (shifted === null) continue;
|
|
5964
|
+
if (isRowShift) row2 = shifted;
|
|
5965
|
+
else col = shifted;
|
|
5966
|
+
}
|
|
5967
|
+
const touchedBand = shift.kind === "delete" && e.refs.some((r) => r.sheet === editedSheet && (isRowShift ? r.endRow >= shift.at && r.startRow <= bandEnd : r.endCol >= shift.at && r.startCol <= bandEnd));
|
|
5968
|
+
const rewritten = shiftFormulaRefs(e.formula, shift, e.sheetName);
|
|
5969
|
+
const moved = row2 !== e.row || col !== e.col;
|
|
5970
|
+
const ref = moved ? cellRefFromRowCol(row2, col) : e.ref;
|
|
5971
|
+
const key = moved ? keyOf(e.sheet, ref) : e.key;
|
|
5972
|
+
rekeyed.push({
|
|
5973
|
+
...e,
|
|
5974
|
+
ref,
|
|
5975
|
+
key,
|
|
5976
|
+
row: row2,
|
|
5977
|
+
col,
|
|
5978
|
+
formula: rewritten.changed ? rewritten.text : e.formula,
|
|
5979
|
+
// A rewritten formula's cached AST no longer matches its text; it is
|
|
5980
|
+
// re-parsed lazily, and only if the cell is actually evaluated again.
|
|
5981
|
+
ast: rewritten.changed ? void 0 : e.ast,
|
|
5982
|
+
refs: this.#shiftPrecedents(e.refs, shift, editedSheet)
|
|
5983
|
+
// Volatility is a property of which *functions* a formula calls, and
|
|
5984
|
+
// rewriting references cannot change that.
|
|
6045
5985
|
});
|
|
5986
|
+
if (touchedBand || rewritten.hasRefError) needsRecalc.add(key);
|
|
5987
|
+
}
|
|
5988
|
+
this.#formulas = /* @__PURE__ */ new Map();
|
|
5989
|
+
this.#dependents.clear();
|
|
5990
|
+
this.#volatiles.clear();
|
|
5991
|
+
for (const e of rekeyed) this.#register(e);
|
|
5992
|
+
this.#shiftSpillBookkeeping(shift, editedSheet);
|
|
5993
|
+
this.#rangeCache.clear();
|
|
5994
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5995
|
+
for (const k of needsRecalc) {
|
|
5996
|
+
const e = this.#formulas.get(k);
|
|
5997
|
+
if (e) pending.set(k, e);
|
|
6046
5998
|
}
|
|
5999
|
+
for (const e of this.#formulas.values()) {
|
|
6000
|
+
if (e.volatile) pending.set(e.key, e);
|
|
6001
|
+
}
|
|
6002
|
+
return this.#runFixpoint(pending, /* @__PURE__ */ new Set());
|
|
6047
6003
|
}
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6004
|
+
/**
|
|
6005
|
+
* Translates precedent rectangles across a structural edit.
|
|
6006
|
+
*
|
|
6007
|
+
* This is arithmetic rather than a re-parse of the rewritten formula, and the
|
|
6008
|
+
* two are equivalent by construction: `shiftFormulaRefs` derives the new
|
|
6009
|
+
* reference text by calling `shiftSpan`/`shiftIndex` on exactly these numbers,
|
|
6010
|
+
* so applying the same functions to the stored rectangle yields the same
|
|
6011
|
+
* region. A rectangle that is entirely deleted disappears, matching the
|
|
6012
|
+
* `#REF!` the text rewrite produces.
|
|
6013
|
+
*/
|
|
6014
|
+
#shiftPrecedents(refs, shift, editedSheet) {
|
|
6015
|
+
const out = [];
|
|
6016
|
+
for (const r of refs) {
|
|
6017
|
+
if (r.sheet !== editedSheet) {
|
|
6018
|
+
out.push(r);
|
|
6019
|
+
continue;
|
|
6020
|
+
}
|
|
6021
|
+
if (shift.dim === "row") {
|
|
6022
|
+
const span = shiftSpan(r.startRow, r.endRow, shift);
|
|
6023
|
+
if (!span) continue;
|
|
6024
|
+
out.push({
|
|
6025
|
+
sheet: r.sheet,
|
|
6026
|
+
startRow: span.start,
|
|
6027
|
+
endRow: Math.min(span.end, EXCEL_MAX_ROWS),
|
|
6028
|
+
startCol: r.startCol,
|
|
6029
|
+
endCol: r.endCol
|
|
6030
|
+
});
|
|
6031
|
+
} else {
|
|
6032
|
+
const span = shiftSpan(r.startCol, r.endCol, shift);
|
|
6033
|
+
if (!span) continue;
|
|
6034
|
+
out.push({
|
|
6035
|
+
sheet: r.sheet,
|
|
6036
|
+
startRow: r.startRow,
|
|
6037
|
+
endRow: r.endRow,
|
|
6038
|
+
startCol: span.start,
|
|
6039
|
+
endCol: Math.min(span.end, EXCEL_MAX_COLUMNS)
|
|
6040
|
+
});
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
return out;
|
|
6044
|
+
}
|
|
6045
|
+
/** Translates spill ownership across a structural edit on `editedSheet`. */
|
|
6046
|
+
#shiftSpillBookkeeping(shift, editedSheet) {
|
|
6047
|
+
const shiftCellKey = (key) => {
|
|
6048
|
+
const { sheet, row: row2, col } = parseKey(key);
|
|
6049
|
+
if (sheet !== editedSheet) return key;
|
|
6050
|
+
const r = shift.dim === "row" ? shiftIndex(row2, shift) : row2;
|
|
6051
|
+
const c = shift.dim === "col" ? shiftIndex(col, shift) : col;
|
|
6052
|
+
if (r === null || c === null) return null;
|
|
6053
|
+
return keyOf(sheet, cellRefFromRowCol(r, c));
|
|
6054
|
+
};
|
|
6055
|
+
const newSpills = /* @__PURE__ */ new Map();
|
|
6056
|
+
this.#spillOwner.clear();
|
|
6057
|
+
for (const [anchor, members] of this.#spills) {
|
|
6058
|
+
const newAnchor = shiftCellKey(anchor);
|
|
6059
|
+
if (newAnchor === null) {
|
|
6060
|
+
for (const m of members) {
|
|
6061
|
+
const shifted = shiftCellKey(m);
|
|
6062
|
+
if (!shifted) continue;
|
|
6063
|
+
const cell = parseKey(shifted);
|
|
6064
|
+
this.#writeCell(this.#sheetNames.get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
6065
|
+
}
|
|
6066
|
+
continue;
|
|
6067
|
+
}
|
|
6068
|
+
const newMembers = /* @__PURE__ */ new Set();
|
|
6065
6069
|
for (const m of members) {
|
|
6066
6070
|
const shifted = shiftCellKey(m);
|
|
6067
6071
|
if (!shifted) continue;
|
|
6068
|
-
|
|
6069
|
-
|
|
6072
|
+
newMembers.add(shifted);
|
|
6073
|
+
this.#spillOwner.set(shifted, newAnchor);
|
|
6070
6074
|
}
|
|
6071
|
-
|
|
6075
|
+
newSpills.set(newAnchor, newMembers);
|
|
6072
6076
|
}
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6077
|
+
this.#spills = newSpills;
|
|
6078
|
+
}
|
|
6079
|
+
// ── Internals ────────────────────────────────────────────────────────────────
|
|
6080
|
+
/**
|
|
6081
|
+
* Recomputes formulas affected by the given changed cells, to a fixpoint.
|
|
6082
|
+
*
|
|
6083
|
+
* A single topological pass is not enough once dynamic arrays exist: a
|
|
6084
|
+
* formula that spills changes cells the graph could not know about until the
|
|
6085
|
+
* array was computed, and those cells may have their own dependents. So we
|
|
6086
|
+
* evaluate a pass, collect every cell whose value actually changed (formula
|
|
6087
|
+
* outputs plus spilled/cleared cells), then recompute the dependents of those
|
|
6088
|
+
* cells, repeating until nothing new is affected (bounded for safety).
|
|
6089
|
+
*/
|
|
6090
|
+
#recalcFrom(seeds, includeKey) {
|
|
6091
|
+
const results = [];
|
|
6092
|
+
const evaluated = /* @__PURE__ */ new Set();
|
|
6093
|
+
let pending = this.#dependentClosure(seeds, evaluated);
|
|
6094
|
+
this.#addVolatiles(pending);
|
|
6095
|
+
if (includeKey) {
|
|
6096
|
+
const self = this.#formulas.get(includeKey);
|
|
6097
|
+
if (self) pending.set(includeKey, self);
|
|
6098
|
+
}
|
|
6099
|
+
results.push(...this.#runFixpoint(pending, evaluated));
|
|
6100
|
+
return results;
|
|
6101
|
+
}
|
|
6102
|
+
/** Evaluates `pending` entries to a fixpoint, following spill-induced changes. */
|
|
6103
|
+
#runFixpoint(pending, evaluated) {
|
|
6104
|
+
const results = [];
|
|
6105
|
+
let guard = 0;
|
|
6106
|
+
const maxIterations = this.#formulas.size + 8;
|
|
6107
|
+
while (pending.size > 0 && guard++ <= maxIterations) {
|
|
6108
|
+
const { results: passResults, changed } = this.#evaluatePass([...pending.values()]);
|
|
6109
|
+
results.push(...passResults);
|
|
6110
|
+
for (const k of pending.keys()) evaluated.add(k);
|
|
6111
|
+
pending = this.#dependentClosure(changed, evaluated);
|
|
6112
|
+
}
|
|
6113
|
+
return results;
|
|
6114
|
+
}
|
|
6115
|
+
/** Adds every volatile formula to a pending set. */
|
|
6116
|
+
#addVolatiles(pending) {
|
|
6117
|
+
for (const key of this.#volatiles) {
|
|
6118
|
+
const e = this.#formulas.get(key);
|
|
6119
|
+
if (e) pending.set(key, e);
|
|
6079
6120
|
}
|
|
6080
|
-
newSpills.set(newAnchor, newMembers);
|
|
6081
6121
|
}
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
__privateMethod(this, _RecalcEngine_instances, addVolatiles_fn).call(this, pending);
|
|
6100
|
-
if (includeKey) {
|
|
6101
|
-
const self = __privateGet(this, _formulas).get(includeKey);
|
|
6102
|
-
if (self) pending.set(includeKey, self);
|
|
6103
|
-
}
|
|
6104
|
-
results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, evaluated));
|
|
6105
|
-
return results;
|
|
6106
|
-
};
|
|
6107
|
-
/** Evaluates `pending` entries to a fixpoint, following spill-induced changes. */
|
|
6108
|
-
runFixpoint_fn = function(pending, evaluated) {
|
|
6109
|
-
const results = [];
|
|
6110
|
-
let guard = 0;
|
|
6111
|
-
const maxIterations = __privateGet(this, _formulas).size + 8;
|
|
6112
|
-
while (pending.size > 0 && guard++ <= maxIterations) {
|
|
6113
|
-
const { results: passResults, changed } = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...pending.values()]);
|
|
6114
|
-
results.push(...passResults);
|
|
6115
|
-
for (const k of pending.keys()) evaluated.add(k);
|
|
6116
|
-
pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated);
|
|
6117
|
-
}
|
|
6118
|
-
return results;
|
|
6119
|
-
};
|
|
6120
|
-
/** Adds every volatile formula to a pending set. */
|
|
6121
|
-
addVolatiles_fn = function(pending) {
|
|
6122
|
-
for (const key of __privateGet(this, _volatiles)) {
|
|
6123
|
-
const e = __privateGet(this, _formulas).get(key);
|
|
6124
|
-
if (e) pending.set(key, e);
|
|
6122
|
+
/**
|
|
6123
|
+
* Formula entries (not yet evaluated) whose precedents include one of the
|
|
6124
|
+
* cells, transitively. The queue is walked with a head index — Array.shift()
|
|
6125
|
+
* is O(n) per dequeue, which turns a long dependency chain quadratic.
|
|
6126
|
+
*/
|
|
6127
|
+
#dependentClosure(cells, evaluated) {
|
|
6128
|
+
const out = /* @__PURE__ */ new Map();
|
|
6129
|
+
const queue = [...cells];
|
|
6130
|
+
for (let head = 0; head < queue.length; head++) {
|
|
6131
|
+
const cell = queue[head];
|
|
6132
|
+
this.#forEachDirectDependent(cell, (dep) => {
|
|
6133
|
+
if (out.has(dep.key) || evaluated.has(dep.key)) return;
|
|
6134
|
+
out.set(dep.key, dep);
|
|
6135
|
+
queue.push({ sheet: dep.sheet, row: dep.row, col: dep.col });
|
|
6136
|
+
});
|
|
6137
|
+
}
|
|
6138
|
+
return out;
|
|
6125
6139
|
}
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6140
|
+
/**
|
|
6141
|
+
* Visits the formula entries whose precedents include the given cell, via the
|
|
6142
|
+
* reverse index. The index over-approximates (it buckets rectangles into
|
|
6143
|
+
* tiles), so each candidate is still confirmed against its real rectangles.
|
|
6144
|
+
*/
|
|
6145
|
+
#forEachDirectDependent(cell, visit) {
|
|
6146
|
+
this.#dependents.candidates(cell.sheet, cell.row, cell.col, (key) => {
|
|
6147
|
+
const e = this.#formulas.get(key);
|
|
6148
|
+
if (!e) return;
|
|
6149
|
+
for (const ref of e.refs) {
|
|
6150
|
+
if (refContains(ref, cell.sheet, cell.row, cell.col)) {
|
|
6151
|
+
visit(e);
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6141
6155
|
});
|
|
6142
6156
|
}
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
*/
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
const
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6157
|
+
/**
|
|
6158
|
+
* Topologically sorts the given entries and evaluates them, writing results
|
|
6159
|
+
* (scalars or spilled arrays) back to the model. Returns the results and the
|
|
6160
|
+
* flat list of cells whose value changed. Entries left in a cycle are #REF!.
|
|
6161
|
+
*/
|
|
6162
|
+
#evaluatePass(entries) {
|
|
6163
|
+
const inSet = /* @__PURE__ */ new Map();
|
|
6164
|
+
for (const e of entries) inSet.set(e.key, e);
|
|
6165
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
6166
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
6167
|
+
for (const k of inSet.keys()) {
|
|
6168
|
+
indegree.set(k, 0);
|
|
6169
|
+
dependents.set(k, []);
|
|
6170
|
+
}
|
|
6171
|
+
for (const pe of entries) {
|
|
6172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6173
|
+
this.#forEachDirectDependent({ sheet: pe.sheet, row: pe.row, col: pe.col }, (dep) => {
|
|
6174
|
+
if (dep.key === pe.key || !inSet.has(dep.key) || seen.has(dep.key)) return;
|
|
6175
|
+
seen.add(dep.key);
|
|
6176
|
+
dependents.get(pe.key).push(dep.key);
|
|
6177
|
+
indegree.set(dep.key, indegree.get(dep.key) + 1);
|
|
6178
|
+
});
|
|
6179
|
+
}
|
|
6180
|
+
const ready = [];
|
|
6181
|
+
for (const [k, d] of indegree) if (d === 0) ready.push(k);
|
|
6182
|
+
const order = [];
|
|
6183
|
+
for (let head = 0; head < ready.length; head++) {
|
|
6184
|
+
const k = ready[head];
|
|
6185
|
+
order.push(k);
|
|
6186
|
+
for (const d of dependents.get(k)) {
|
|
6187
|
+
const left2 = indegree.get(d) - 1;
|
|
6188
|
+
indegree.set(d, left2);
|
|
6189
|
+
if (left2 === 0) ready.push(d);
|
|
6158
6190
|
}
|
|
6159
6191
|
}
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
__privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: pe.sheet, row: pe.row, col: pe.col }, (dep) => {
|
|
6179
|
-
if (dep.key === pe.key || !inSet.has(dep.key) || seen.has(dep.key)) return;
|
|
6180
|
-
seen.add(dep.key);
|
|
6181
|
-
dependents.get(pe.key).push(dep.key);
|
|
6182
|
-
indegree.set(dep.key, indegree.get(dep.key) + 1);
|
|
6183
|
-
});
|
|
6192
|
+
const results = [];
|
|
6193
|
+
const changed = [];
|
|
6194
|
+
const emitted = new Set(order);
|
|
6195
|
+
for (const k of order) {
|
|
6196
|
+
const e = inSet.get(k);
|
|
6197
|
+
const { result, changed: c } = this.#writeResult(e);
|
|
6198
|
+
results.push(result);
|
|
6199
|
+
changed.push(...c);
|
|
6200
|
+
}
|
|
6201
|
+
for (const [k, e] of inSet) {
|
|
6202
|
+
if (emitted.has(k)) continue;
|
|
6203
|
+
this.#clearSpill(e.key, changed);
|
|
6204
|
+
const value2 = FormulaValue.errorRef.toObject();
|
|
6205
|
+
this.#writeCell(e.sheetName, e.ref, value2, e.row, e.col);
|
|
6206
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
6207
|
+
results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
|
|
6208
|
+
}
|
|
6209
|
+
return { results, changed };
|
|
6184
6210
|
}
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
};
|
|
6216
|
-
/**
|
|
6217
|
-
* Evaluates one formula and writes its result. Scalars go to the anchor cell.
|
|
6218
|
-
* Arrays spill across a block anchored at the formula cell: on a collision
|
|
6219
|
-
* with existing content the anchor becomes #SPILL! and nothing spills; when
|
|
6220
|
-
* an array shrinks, previously-spilled cells it no longer covers are cleared.
|
|
6221
|
-
*/
|
|
6222
|
-
writeResult_fn = function(e) {
|
|
6223
|
-
const anchorKey = e.key;
|
|
6224
|
-
const fv2 = __privateMethod(this, _RecalcEngine_instances, evaluateFormulaValue_fn).call(this, e);
|
|
6225
|
-
const changed = [];
|
|
6226
|
-
const prevSpill = __privateGet(this, _spills).get(anchorKey) ?? /* @__PURE__ */ new Set();
|
|
6227
|
-
if (!fv2.isArray) {
|
|
6228
|
-
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
6229
|
-
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
6230
|
-
const value2 = resultOf(fv2);
|
|
6231
|
-
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
6232
|
-
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
6233
|
-
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
6234
|
-
}
|
|
6235
|
-
const arr = fv2.arrayVal;
|
|
6236
|
-
const rows2 = arr.rows, cols = arr.columns;
|
|
6237
|
-
let collides = false;
|
|
6238
|
-
for (let dr = 0; dr < rows2 && !collides; dr++) {
|
|
6239
|
-
for (let dc = 0; dc < cols && !collides; dc++) {
|
|
6240
|
-
if (dr === 0 && dc === 0) continue;
|
|
6241
|
-
const r = e.row + dr, c = e.col + dc;
|
|
6242
|
-
const cellKey2 = keyOf(e.sheet, cellRefFromRowCol(r, c));
|
|
6243
|
-
if (__privateMethod(this, _RecalcEngine_instances, isOccupied_fn).call(this, cellKey2, e.sheetName, cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
|
|
6244
|
-
collides = true;
|
|
6211
|
+
/**
|
|
6212
|
+
* Evaluates one formula and writes its result. Scalars go to the anchor cell.
|
|
6213
|
+
* Arrays spill across a block anchored at the formula cell: on a collision
|
|
6214
|
+
* with existing content the anchor becomes #SPILL! and nothing spills; when
|
|
6215
|
+
* an array shrinks, previously-spilled cells it no longer covers are cleared.
|
|
6216
|
+
*/
|
|
6217
|
+
#writeResult(e) {
|
|
6218
|
+
const anchorKey = e.key;
|
|
6219
|
+
const fv2 = this.#evaluateFormulaValue(e);
|
|
6220
|
+
const changed = [];
|
|
6221
|
+
const prevSpill = this.#spills.get(anchorKey) ?? /* @__PURE__ */ new Set();
|
|
6222
|
+
if (!fv2.isArray) {
|
|
6223
|
+
this.#clearSpill(anchorKey, changed);
|
|
6224
|
+
this.#model.setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
6225
|
+
const value2 = resultOf(fv2);
|
|
6226
|
+
this.#writeCell(e.sheetName, e.ref, value2, e.row, e.col);
|
|
6227
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
6228
|
+
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
6229
|
+
}
|
|
6230
|
+
const arr = fv2.arrayVal;
|
|
6231
|
+
const rows2 = arr.rows, cols = arr.columns;
|
|
6232
|
+
let collides = false;
|
|
6233
|
+
for (let dr = 0; dr < rows2 && !collides; dr++) {
|
|
6234
|
+
for (let dc = 0; dc < cols && !collides; dc++) {
|
|
6235
|
+
if (dr === 0 && dc === 0) continue;
|
|
6236
|
+
const r = e.row + dr, c = e.col + dc;
|
|
6237
|
+
const cellKey2 = keyOf(e.sheet, cellRefFromRowCol(r, c));
|
|
6238
|
+
if (this.#isOccupied(cellKey2, e.sheetName, cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
|
|
6239
|
+
collides = true;
|
|
6240
|
+
}
|
|
6245
6241
|
}
|
|
6246
6242
|
}
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6243
|
+
if (collides) {
|
|
6244
|
+
this.#clearSpill(anchorKey, changed);
|
|
6245
|
+
this.#model.setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
6246
|
+
const value2 = FormulaValue.error(9 /* Spill */).toObject();
|
|
6247
|
+
this.#writeCell(e.sheetName, e.ref, value2, e.row, e.col);
|
|
6248
|
+
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
6249
|
+
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
6250
|
+
}
|
|
6251
|
+
const newSpill = /* @__PURE__ */ new Set();
|
|
6252
|
+
for (let dr = 0; dr < rows2; dr++) {
|
|
6253
|
+
for (let dc = 0; dc < cols; dc++) {
|
|
6254
|
+
const r = e.row + dr, c = e.col + dc;
|
|
6255
|
+
const ref = cellRefFromRowCol(r, c);
|
|
6256
|
+
this.#writeCell(e.sheetName, ref, resultOf(arr.get(dr, dc)), r, c);
|
|
6257
|
+
changed.push({ sheet: e.sheet, row: r, col: c });
|
|
6258
|
+
if (dr !== 0 || dc !== 0) {
|
|
6259
|
+
const cellKey2 = keyOf(e.sheet, ref);
|
|
6260
|
+
newSpill.add(cellKey2);
|
|
6261
|
+
this.#spillOwner.set(cellKey2, anchorKey);
|
|
6262
|
+
}
|
|
6267
6263
|
}
|
|
6268
6264
|
}
|
|
6265
|
+
for (const oldKey of prevSpill) {
|
|
6266
|
+
if (newSpill.has(oldKey)) continue;
|
|
6267
|
+
const cell = parseKey(oldKey);
|
|
6268
|
+
this.#writeCell(this.#sheetNames.get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
6269
|
+
this.#spillOwner.delete(oldKey);
|
|
6270
|
+
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
6271
|
+
}
|
|
6272
|
+
this.#spills.set(anchorKey, newSpill);
|
|
6273
|
+
const spillRef = `${e.ref}:${cellRefFromRowCol(e.row + rows2 - 1, e.col + cols - 1)}`;
|
|
6274
|
+
this.#model.setCellArrayRef?.(e.sheetName, e.ref, spillRef);
|
|
6275
|
+
return {
|
|
6276
|
+
result: { sheet: e.sheetName, ref: e.ref, value: resultOf(arr.get(0, 0)), spill: { rows: rows2, cols } },
|
|
6277
|
+
changed
|
|
6278
|
+
};
|
|
6269
6279
|
}
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6280
|
+
/** Whether a target cell already holds content that blocks a spill. */
|
|
6281
|
+
#isOccupied(cellKey2, sheetName, ref, anchorKey, prevSpill) {
|
|
6282
|
+
if (this.#formulas.has(cellKey2) && cellKey2 !== anchorKey) return true;
|
|
6283
|
+
const owner = this.#spillOwner.get(cellKey2);
|
|
6284
|
+
if (owner && owner !== anchorKey) return true;
|
|
6285
|
+
if (prevSpill.has(cellKey2)) return false;
|
|
6286
|
+
const v = this.#model.getCellValue(sheetName, ref);
|
|
6287
|
+
return v !== null && v !== void 0 && v !== "";
|
|
6288
|
+
}
|
|
6289
|
+
/** Clears an anchor's spilled cells (not the anchor itself) and forgets them. */
|
|
6290
|
+
#clearSpill(anchorKey, changed) {
|
|
6291
|
+
const spill = this.#spills.get(anchorKey);
|
|
6292
|
+
if (!spill) return;
|
|
6293
|
+
for (const cellKey2 of spill) {
|
|
6294
|
+
const cell = parseKey(cellKey2);
|
|
6295
|
+
this.#writeCell(this.#sheetNames.get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
6296
|
+
this.#spillOwner.delete(cellKey2);
|
|
6297
|
+
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
6298
|
+
}
|
|
6299
|
+
this.#spills.delete(anchorKey);
|
|
6300
|
+
}
|
|
6301
|
+
#evaluateFormulaValue(e) {
|
|
6302
|
+
if (!e.ast) {
|
|
6303
|
+
try {
|
|
6304
|
+
e.ast = parseFormulaUncached(e.formula);
|
|
6305
|
+
} catch {
|
|
6306
|
+
return FormulaValue.errorValue;
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
return evaluateAst(e.ast, this.#sheetLike(e.sheetName), {
|
|
6310
|
+
workbook: this.#buildWorkbookLike(),
|
|
6311
|
+
formulaRow: e.row,
|
|
6312
|
+
formulaCol: e.col,
|
|
6313
|
+
rangeCache: this.#rangeCache
|
|
6314
|
+
});
|
|
6276
6315
|
}
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
if (__privateGet(this, _formulas).has(cellKey2) && cellKey2 !== anchorKey) return true;
|
|
6288
|
-
const owner = __privateGet(this, _spillOwner).get(cellKey2);
|
|
6289
|
-
if (owner && owner !== anchorKey) return true;
|
|
6290
|
-
if (prevSpill.has(cellKey2)) return false;
|
|
6291
|
-
const v = __privateGet(this, _model).getCellValue(sheetName, ref);
|
|
6292
|
-
return v !== null && v !== void 0 && v !== "";
|
|
6293
|
-
};
|
|
6294
|
-
/** Clears an anchor's spilled cells (not the anchor itself) and forgets them. */
|
|
6295
|
-
clearSpill_fn = function(anchorKey, changed) {
|
|
6296
|
-
const spill = __privateGet(this, _spills).get(anchorKey);
|
|
6297
|
-
if (!spill) return;
|
|
6298
|
-
for (const cellKey2 of spill) {
|
|
6299
|
-
const cell = parseKey(cellKey2);
|
|
6300
|
-
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
6301
|
-
__privateGet(this, _spillOwner).delete(cellKey2);
|
|
6302
|
-
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
6303
|
-
}
|
|
6304
|
-
__privateGet(this, _spills).delete(anchorKey);
|
|
6305
|
-
};
|
|
6306
|
-
evaluateFormulaValue_fn = function(e) {
|
|
6307
|
-
if (!e.ast) {
|
|
6308
|
-
try {
|
|
6309
|
-
e.ast = parseFormulaUncached(e.formula);
|
|
6310
|
-
} catch {
|
|
6311
|
-
return FormulaValue.errorValue;
|
|
6316
|
+
/**
|
|
6317
|
+
* Per-sheet evaluator adapter. Memoised: these hold no per-formula state, and
|
|
6318
|
+
* rebuilding one (with its four closures) for every formula evaluated made a
|
|
6319
|
+
* full recalc allocate several objects per cell.
|
|
6320
|
+
*/
|
|
6321
|
+
#sheetLike(sheetName) {
|
|
6322
|
+
let cached = this.#sheetLikes.get(sheetName);
|
|
6323
|
+
if (!cached) {
|
|
6324
|
+
cached = this.#buildSheetLike(sheetName);
|
|
6325
|
+
this.#sheetLikes.set(sheetName, cached);
|
|
6312
6326
|
}
|
|
6327
|
+
return cached;
|
|
6328
|
+
}
|
|
6329
|
+
#buildSheetLike(sheetName) {
|
|
6330
|
+
const model = this.#model;
|
|
6331
|
+
return {
|
|
6332
|
+
name: sheetName,
|
|
6333
|
+
getCellValue: (ref) => this.#model.getCellValue(sheetName, ref),
|
|
6334
|
+
getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
|
|
6335
|
+
// A getter, not a snapshot: the extent grows as formulas write results,
|
|
6336
|
+
// and a whole-column reference read later in the same pass must see it.
|
|
6337
|
+
get usedBounds() {
|
|
6338
|
+
return model.getUsedBounds?.(sheetName);
|
|
6339
|
+
},
|
|
6340
|
+
isRowHidden: this.#model.isRowHidden ? (row2) => this.#model.isRowHidden(sheetName, row2) : void 0,
|
|
6341
|
+
getCellFormula: this.#model.getCellFormula ? (ref) => this.#model.getCellFormula(sheetName, ref) : void 0,
|
|
6342
|
+
getSpillRange: this.#model.getSpillRange ? (ref) => this.#model.getSpillRange(sheetName, ref) : void 0
|
|
6343
|
+
};
|
|
6344
|
+
}
|
|
6345
|
+
#buildWorkbookLike() {
|
|
6346
|
+
if (this.#workbookLike) return this.#workbookLike;
|
|
6347
|
+
const self = this;
|
|
6348
|
+
return this.#workbookLike = {
|
|
6349
|
+
get worksheets() {
|
|
6350
|
+
return [...self.#sheetNames.values()].map((n) => self.#sheetLike(n));
|
|
6351
|
+
},
|
|
6352
|
+
getWorksheet: (name) => self.#sheetLike(name),
|
|
6353
|
+
getDefinedName: this.#model.getDefinedName ? (name, sheet) => this.#model.getDefinedName(name, sheet) : void 0
|
|
6354
|
+
};
|
|
6313
6355
|
}
|
|
6314
|
-
return evaluateAst(e.ast, __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName), {
|
|
6315
|
-
workbook: __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this),
|
|
6316
|
-
formulaRow: e.row,
|
|
6317
|
-
formulaCol: e.col,
|
|
6318
|
-
rangeCache: __privateGet(this, _rangeCache)
|
|
6319
|
-
});
|
|
6320
|
-
};
|
|
6321
|
-
/**
|
|
6322
|
-
* Per-sheet evaluator adapter. Memoised: these hold no per-formula state, and
|
|
6323
|
-
* rebuilding one (with its four closures) for every formula evaluated made a
|
|
6324
|
-
* full recalc allocate several objects per cell.
|
|
6325
|
-
*/
|
|
6326
|
-
sheetLike_fn = function(sheetName) {
|
|
6327
|
-
let cached = __privateGet(this, _sheetLikes).get(sheetName);
|
|
6328
|
-
if (!cached) {
|
|
6329
|
-
cached = __privateMethod(this, _RecalcEngine_instances, buildSheetLike_fn).call(this, sheetName);
|
|
6330
|
-
__privateGet(this, _sheetLikes).set(sheetName, cached);
|
|
6331
|
-
}
|
|
6332
|
-
return cached;
|
|
6333
|
-
};
|
|
6334
|
-
buildSheetLike_fn = function(sheetName) {
|
|
6335
|
-
const model = __privateGet(this, _model);
|
|
6336
|
-
return {
|
|
6337
|
-
name: sheetName,
|
|
6338
|
-
getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
|
|
6339
|
-
getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
|
|
6340
|
-
// A getter, not a snapshot: the extent grows as formulas write results,
|
|
6341
|
-
// and a whole-column reference read later in the same pass must see it.
|
|
6342
|
-
get usedBounds() {
|
|
6343
|
-
return model.getUsedBounds?.(sheetName);
|
|
6344
|
-
},
|
|
6345
|
-
isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
|
|
6346
|
-
getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
|
|
6347
|
-
getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
|
|
6348
|
-
};
|
|
6349
|
-
};
|
|
6350
|
-
buildWorkbookLike_fn = function() {
|
|
6351
|
-
if (__privateGet(this, _workbookLike)) return __privateGet(this, _workbookLike);
|
|
6352
|
-
const self = this;
|
|
6353
|
-
return __privateSet(this, _workbookLike, {
|
|
6354
|
-
get worksheets() {
|
|
6355
|
-
return [...__privateGet(self, _sheetNames).values()].map((n) => {
|
|
6356
|
-
var _a;
|
|
6357
|
-
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, n);
|
|
6358
|
-
});
|
|
6359
|
-
},
|
|
6360
|
-
getWorksheet: (name) => {
|
|
6361
|
-
var _a;
|
|
6362
|
-
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, name);
|
|
6363
|
-
},
|
|
6364
|
-
getDefinedName: __privateGet(this, _model).getDefinedName ? (name, sheet) => __privateGet(this, _model).getDefinedName(name, sheet) : void 0
|
|
6365
|
-
});
|
|
6366
6356
|
};
|
|
6367
6357
|
|
|
6368
6358
|
export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CallNode, CellReferenceNode, ErrorNode, FormulaContext, FormulaError, FormulaException, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, FullRangeReferenceNode, FunctionCallNode, FunctionRegistry, ImplicitIntersectionNode, LruCache, NamedRangeNode, NumberNode, RangeCache, RangeReferenceNode, RecalcEngine, SheetCellReferenceNode, SheetRangeReferenceNode, SpillReferenceNode, StringNode, TokenType, UnaryOpNode, UnaryOperator, clearAstCache, createWorkbookRecalcModel, evaluateAst, evaluateFormula, extractReferences, getAstCacheStats, parseFormula, parseFormulaUncached, refContains, registerAggregate, registerArray, registerDate, registerFinance, registerInfo, registerLogical, registerLookup, registerMath, registerMeta, registerStatistical, registerText, setAstCacheCapacity };
|