@devmm/puredocs-excel-formula 1.1.1 → 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.js CHANGED
@@ -1,21 +1,6 @@
1
- import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, parseRangeRef, shiftIndex, shiftFormulaRefs, shiftSpan, EXCEL_MAX_ROWS, EXCEL_MAX_COLUMNS, columnNumber } from '@devmm/puredocs-excel';
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
- var __typeError = (msg) => {
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 _num, _obj;
46
- var _FormulaValue = class _FormulaValue {
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
- __privateSet(this, _num, num5);
54
- __privateSet(this, _obj, obj);
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 __privateGet(this, _num);
125
+ return this.#num;
124
126
  }
125
127
  get textValue() {
126
- return __privateGet(this, _obj) ?? "";
128
+ return this.#obj ?? "";
127
129
  }
128
130
  get booleanValue() {
129
- return __privateGet(this, _num) !== 0;
131
+ return this.#num !== 0;
130
132
  }
131
133
  get arrayVal() {
132
- return __privateGet(this, _obj) ?? ArrayValue.empty;
134
+ return this.#obj ?? ArrayValue.empty;
133
135
  }
134
136
  get lambdaVal() {
135
- return __privateGet(this, _obj);
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(__privateGet(this, _num));
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: __privateGet(this, _num) };
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: __privateGet(this, _num) };
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(__privateGet(this, _num) !== 0);
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(__privateGet(this, _num));
199
+ return String(this.#num);
198
200
  case "boolean":
199
- return __privateGet(this, _num) !== 0 ? "TRUE" : "FALSE";
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(__privateGet(a, _num) - __privateGet(b, _num)) < 1e-10;
225
+ return Math.abs(a.#num - b.#num) < 1e-10;
224
226
  case "boolean":
225
- return __privateGet(a, _num) === __privateGet(b, _num);
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 __privateGet(a, _num) - __privateGet(b, _num);
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 __privateGet(this, _num);
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
- _num = new WeakMap();
314
- _obj = new WeakMap();
315
- // ── Cached singletons ────────────────────────────────────────────────────
316
- _FormulaValue.blank = new _FormulaValue("blank");
317
- _FormulaValue.zero = new _FormulaValue("number", 0);
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
- __privateSet(this, _data, new Array(rows2 * columns2).fill(FormulaValue.blank));
323
+ this.#data = new Array(rows2 * columns2).fill(FormulaValue.blank);
337
324
  }
338
325
  get length() {
339
- return __privateGet(this, _data).length;
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 __privateGet(this, _data)[row2 * this.columns + col];
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
- __privateGet(this, _data)[row2 * this.columns + col] = value2;
336
+ this.#data[row2 * this.columns + col] = value2;
350
337
  }
351
338
  }
352
339
  getFlat(index) {
353
- return index >= 0 && index < __privateGet(this, _data).length ? __privateGet(this, _data)[index] : FormulaValue.errorRef;
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 < __privateGet(this, _data).length) {
357
- __privateGet(this, _data)[index] = value2;
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 __privateGet(this, _data);
349
+ return this.#data;
363
350
  }
364
351
  toObjectArray() {
365
- return __privateGet(this, _data).map((v) => v.toObject());
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
- __privateSet(this, _formula, formula);
476
+ this.#formula = formula;
495
477
  }
496
478
  tokenize() {
497
- __privateSet(this, _pos, 0);
479
+ this.#pos = 0;
498
480
  const tokens = [];
499
- while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
500
- __privateMethod(this, _FormulaLexer_instances, skipWhitespace_fn).call(this);
501
- if (__privateGet(this, _pos) >= __privateGet(this, _formula).length) break;
502
- const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
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 */, "", __privateGet(this, _pos)));
487
+ tokens.push(makeToken(31 /* EOF */, "", this.#pos));
506
488
  return tokens;
507
489
  }
508
- };
509
- _formula = new WeakMap();
510
- _pos = new WeakMap();
511
- _FormulaLexer_instances = new WeakSet();
512
- // ── Private tokenisers ─────────────────────────────────────────────────────
513
- readNextToken_fn = function() {
514
- const c = __privateGet(this, _formula)[__privateGet(this, _pos)];
515
- if (c === '"') return __privateMethod(this, _FormulaLexer_instances, readString_fn).call(this);
516
- if (c === "#") return __privateMethod(this, _FormulaLexer_instances, readErrorLiteral_fn).call(this);
517
- if (c === "'") return __privateMethod(this, _FormulaLexer_instances, readQuotedSheetRef_fn).call(this);
518
- if (c === "." && __privateGet(this, _pos) + 1 < __privateGet(this, _formula).length && /\d/.test(__privateGet(this, _formula)[__privateGet(this, _pos) + 1])) {
519
- return __privateMethod(this, _FormulaLexer_instances, readNumber_fn).call(this);
520
- }
521
- if (/\d/.test(c)) return __privateMethod(this, _FormulaLexer_instances, readNumber_fn).call(this);
522
- if (/[a-zA-Z_$]/.test(c)) return __privateMethod(this, _FormulaLexer_instances, readIdentifier_fn).call(this);
523
- return __privateMethod(this, _FormulaLexer_instances, readOperator_fn).call(this);
524
- };
525
- readString_fn = function() {
526
- const start = __privateWrapper(this, _pos)._++;
527
- let value2 = "";
528
- while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
529
- const ch = __privateGet(this, _formula)[__privateGet(this, _pos)];
530
- if (ch === '"') {
531
- __privateWrapper(this, _pos)._++;
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
- if (__privateGet(this, _formula)[__privateGet(this, _pos)] !== "!") {
573
- throw new FormulaException(`Expected '!' after quoted sheet name at position ${start}.`);
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
- throw new FormulaException(`Unterminated quoted sheet name at position ${start}.`);
585
- };
586
- readNumber_fn = function() {
587
- const start = __privateGet(this, _pos);
588
- let raw = "";
589
- let hasDot = false;
590
- let hasE = false;
591
- while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
592
- const ch = __privateGet(this, _formula)[__privateGet(this, _pos)];
593
- if (/\d/.test(ch)) {
594
- raw += ch;
595
- __privateWrapper(this, _pos)._++;
596
- } else if (ch === "." && !hasDot && !hasE) {
597
- hasDot = true;
598
- raw += ch;
599
- __privateWrapper(this, _pos)._++;
600
- } else if ((ch === "e" || ch === "E") && !hasE && raw.length > 0) {
601
- hasE = true;
602
- raw += ch;
603
- __privateWrapper(this, _pos)._++;
604
- if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "+" || __privateGet(this, _formula)[__privateGet(this, _pos)] === "-") {
605
- raw += __privateGet(this, _formula)[__privateWrapper(this, _pos)._++];
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
- } else break;
608
- }
609
- return makeToken(0 /* Number */, raw, start);
610
- };
611
- readIdentifier_fn = function() {
612
- const start = __privateGet(this, _pos);
613
- let raw = "";
614
- while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
615
- const ch = __privateGet(this, _formula)[__privateGet(this, _pos)];
616
- if (/[a-zA-Z0-9_$.]/.test(ch)) {
617
- raw += ch;
618
- __privateWrapper(this, _pos)._++;
619
- } else break;
620
- }
621
- let upper2 = raw.toUpperCase();
622
- raw = __privateMethod(this, _FormulaLexer_instances, stripReservedPrefixes_fn).call(this, raw, upper2);
623
- if (raw.length !== upper2.length) upper2 = upper2.slice(upper2.length - raw.length);
624
- if (upper2 === "TRUE") return makeToken(2 /* Boolean */, "TRUE", start);
625
- if (upper2 === "FALSE") return makeToken(2 /* Boolean */, "FALSE", start);
626
- if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "!") {
627
- __privateWrapper(this, _pos)._++;
628
- const cellRef = __privateMethod(this, _FormulaLexer_instances, readCellRefChars_fn).call(this);
629
- return makeToken(22 /* SheetReference */, `${raw}!${cellRef}`, start);
630
- }
631
- __privateMethod(this, _FormulaLexer_instances, skipWhitespace_fn).call(this);
632
- if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "(") {
633
- return makeToken(5 /* Function */, upper2, start);
634
- }
635
- const stripped = raw.replace(/\$/g, "");
636
- if (isCellRef(stripped)) {
637
- if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "#") {
638
- __privateWrapper(this, _pos)._++;
639
- return makeToken(24 /* SpillReference */, stripped, start);
640
- }
641
- return makeToken(3 /* CellReference */, stripped, start);
642
- }
643
- return makeToken(23 /* NamedRange */, raw, start);
644
- };
645
- /**
646
- * Drops Excel's storage-level name decorations. They carry no semantics — the
647
- * writer stamps them so an older reader fails loudly instead of silently
648
- * mis-evaluating and they *stack*: `_xlfn._xlws.FILTER` wears two. Stripping
649
- * one prefix once, as this used to, leaves `_XLWS.FILTER` unresolvable.
650
- *
651
- * Every identifier in every formula passes through here, so the common case is
652
- * gated on a single char-code test: a name not starting with '_' returns
653
- * untouched without so much as a comparison against the prefix table.
654
- */
655
- stripReservedPrefixes_fn = function(raw, upper2) {
656
- if (raw.charCodeAt(0) !== UNDERSCORE) return raw;
657
- let cut = 0;
658
- outer: for (; ; ) {
659
- for (const prefix of RESERVED_PREFIXES) {
660
- if (upper2.startsWith(prefix, cut)) {
661
- cut += prefix.length;
662
- continue outer;
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
- break;
621
+ return makeToken(23 /* NamedRange */, raw, start);
666
622
  }
667
- return cut > 0 && cut < raw.length ? raw.slice(cut) : raw;
668
- };
669
- readCellRefChars_fn = function() {
670
- let ref = "";
671
- while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
672
- const ch = __privateGet(this, _formula)[__privateGet(this, _pos)];
673
- if (/[a-zA-Z0-9$]/.test(ch)) {
674
- ref += ch;
675
- __privateWrapper(this, _pos)._++;
676
- } else break;
677
- }
678
- return ref.replace(/\$/g, "");
679
- };
680
- readOperator_fn = function() {
681
- const start = __privateGet(this, _pos);
682
- const ch = __privateGet(this, _formula)[__privateWrapper(this, _pos)._++];
683
- switch (ch) {
684
- case "(":
685
- return makeToken(6 /* LeftParen */, "(", start);
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
- return makeToken(20 /* GreaterThan */, ">", start);
736
- default:
737
- throw new FormulaException(`Unexpected character '${ch}' at position ${start}.`);
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
- skipWhitespace_fn = function() {
741
- while (__privateGet(this, _pos) < __privateGet(this, _formula).length && /\s/.test(__privateGet(this, _formula)[__privateGet(this, _pos)])) {
742
- __privateWrapper(this, _pos)._++;
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
- __privateAdd(this, _value);
808
- __privateSet(this, _value, FormulaValue.array(array));
789
+ this.#value = FormulaValue.array(array);
809
790
  }
791
+ array;
792
+ #value;
810
793
  evaluate() {
811
- return __privateGet(this, _value);
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) => __privateMethod(this, _BinaryOpNode_instances, evalScalar_fn).call(this, a, b))
937
+ ArrayValue.broadcast(la, ra, (a, b) => this.#evalScalar(a, b))
939
938
  );
940
939
  }
941
- return __privateMethod(this, _BinaryOpNode_instances, evalScalar_fn).call(this, l, r);
942
- }
943
- };
944
- _BinaryOpNode_instances = new WeakSet();
945
- evalScalar_fn = function(l, r) {
946
- const op = this.op;
947
- if (op === 6 /* Equal */) return FormulaValue.boolean(FormulaValue.areEqual(l, r));
948
- if (op === 7 /* NotEqual */) return FormulaValue.boolean(!FormulaValue.areEqual(l, r));
949
- if (op === 8 /* LessThan */) return FormulaValue.boolean(FormulaValue.compare(l, r) < 0);
950
- if (op === 9 /* LessThanOrEqual */) return FormulaValue.boolean(FormulaValue.compare(l, r) <= 0);
951
- if (op === 10 /* GreaterThan */) return FormulaValue.boolean(FormulaValue.compare(l, r) > 0);
952
- if (op === 11 /* GreaterThanOrEqual */) return FormulaValue.boolean(FormulaValue.compare(l, r) >= 0);
953
- if (op === 5 /* Concatenate */) {
954
- if (l.isError) return l;
955
- if (r.isError) return r;
956
- return FormulaValue.text(l.asText() + r.asText());
957
- }
958
- const ln2 = l.coerceToNumber();
959
- if (ln2.isError) return ln2;
960
- const rn = r.coerceToNumber();
961
- if (rn.isError) return rn;
962
- const lv = ln2.numberValue;
963
- const rv = rn.numberValue;
964
- switch (op) {
965
- case 0 /* Add */:
966
- return FormulaValue.number(lv + rv);
967
- case 1 /* Subtract */:
968
- return FormulaValue.number(lv - rv);
969
- case 2 /* Multiply */:
970
- return FormulaValue.number(lv * rv);
971
- case 3 /* Divide */:
972
- if (rv === 0) return FormulaValue.errorDiv0;
973
- return FormulaValue.number(lv / rv);
974
- case 4 /* Power */:
975
- return FormulaValue.number(Math.pow(lv, rv));
976
- default:
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
- __privateAdd(this, _FormulaParser_instances);
1106
- __privateAdd(this, _tokens);
1107
- __privateAdd(this, _pos2, 0);
1108
- __privateSet(this, _tokens, tokens);
1111
+ this.#tokens = tokens;
1109
1112
  }
1110
1113
  parse() {
1111
- __privateSet(this, _pos2, 0);
1112
- const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
1113
- if (__privateGet(this, _FormulaParser_instances, current_get).type !== 31 /* EOF */) {
1114
+ this.#pos = 0;
1115
+ const node = this.#parseComparison();
1116
+ if (this.#current.type !== 31 /* EOF */) {
1114
1117
  throw new FormulaException(
1115
- `Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
1118
+ `Unexpected token '${this.#current.value}' at position ${this.#current.position}.`
1116
1119
  );
1117
1120
  }
1118
1121
  return node;
1119
1122
  }
1120
- };
1121
- _tokens = new WeakMap();
1122
- _pos2 = new WeakMap();
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));
1123
+ // ── Token helpers ──────────────────────────────────────────────────────────
1124
+ get #current() {
1125
+ return this.#tokens[this.#pos] ?? { type: 31 /* EOF */, value: "", position: -1 };
1185
1126
  }
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));
1127
+ #advance() {
1128
+ const t = this.#current;
1129
+ this.#pos++;
1130
+ return t;
1189
1131
  }
1190
- if (__privateGet(this, _FormulaParser_instances, current_get).type === 26 /* AtSign */) {
1191
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1192
- return new ImplicitIntersectionNode(__privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
1193
- }
1194
- return __privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this);
1195
- };
1196
- parsePower_fn = function() {
1197
- const left2 = __privateMethod(this, _FormulaParser_instances, parsePercent_fn).call(this);
1198
- if (__privateGet(this, _FormulaParser_instances, current_get).type === 13 /* Power */) {
1199
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1200
- return new BinaryOpNode(left2, 4 /* Power */, __privateMethod(this, _FormulaParser_instances, parseUnary_fn).call(this));
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
- return left2;
1203
- };
1204
- parsePercent_fn = function() {
1205
- let node = __privateMethod(this, _FormulaParser_instances, parsePostfix_fn).call(this);
1206
- while (__privateGet(this, _FormulaParser_instances, current_get).type === 14 /* Percent */) {
1207
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1208
- node = new UnaryOpNode(2 /* Percent */, node);
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
- return node;
1211
- };
1212
- /** Handles postfix call syntax on an expression, e.g. LAMBDA(x,x+1)(5). */
1213
- parsePostfix_fn = function() {
1214
- let node = __privateMethod(this, _FormulaParser_instances, parsePrimary_fn).call(this);
1215
- while (__privateGet(this, _FormulaParser_instances, current_get).type === 6 /* LeftParen */) {
1216
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1217
- node = new CallNode(node, __privateMethod(this, _FormulaParser_instances, parseArgList_fn).call(this));
1218
- }
1219
- return node;
1220
- };
1221
- parsePrimary_fn = function() {
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);
1233
+ }
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);
1251
+ }
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);
1229
1265
  }
1230
- const n = parseFloat(token.value);
1231
- if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
1232
- return new NumberNode(n);
1233
- }
1234
- case 1 /* String */:
1235
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1236
- return new StringNode(token.value);
1237
- case 2 /* Boolean */:
1238
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1239
- return new BooleanNode(token.value === "TRUE");
1240
- case 30 /* ErrorLiteral */:
1241
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1242
- return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
1243
- case 3 /* CellReference */: {
1244
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1245
- if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
1246
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1247
- const end = __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 3 /* CellReference */);
1248
- return new RangeReferenceNode(token.value, end.value);
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);
1249
1273
  }
1250
- return new CellReferenceNode(token.value);
1251
- }
1252
- case 22 /* SheetReference */: {
1253
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1254
- const parts = token.value.split("!");
1255
- const sheetName = parts[0];
1256
- const cellRef = parts[1] ?? "";
1257
- if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
1258
- const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, cellRef, sheetName);
1259
- if (full) return full;
1260
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1261
- const end = __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 3 /* CellReference */);
1262
- return new SheetRangeReferenceNode(sheetName, cellRef, end.value);
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;
1263
1286
  }
1264
- return new SheetCellReferenceNode(sheetName, cellRef);
1287
+ default:
1288
+ throw new FormulaException(
1289
+ `Unexpected token '${token.value}' (type ${token.type}) at position ${token.position}.`
1290
+ );
1265
1291
  }
1266
- case 23 /* NamedRange */: {
1267
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1268
- if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
1269
- const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, token.value);
1270
- if (full) return full;
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
- return new NamedRangeNode(token.value);
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
- default:
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
- `Unexpected token '${token.value}' (type ${token.type}) at position ${token.position}.`
1349
+ `Array constant at position ${open.position} has rows of differing lengths.`
1290
1350
  );
1291
- }
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_fn = function(startText, sheetName) {
1304
- const endToken = __privateGet(this, _tokens)[__privateGet(this, _pos2) + 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
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1310
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
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_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 constant at position ${open.position} has rows of differing lengths.`
1385
+ `Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
1350
1386
  );
1351
1387
  }
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]);
1388
+ #parseFunction() {
1389
+ const nameToken = this.#advance();
1390
+ this.#expect(6 /* LeftParen */);
1391
+ return new FunctionCallNode(nameToken.value.toUpperCase(), this.#parseArgList());
1355
1392
  }
1356
- return new ArrayLiteralNode(array);
1357
- };
1358
- /** One element of an array constant: a literal, optionally signed. */
1359
- parseArrayElement_fn = function() {
1360
- let negate = false;
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(__privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this));
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, "");
@@ -2815,6 +2816,8 @@ function registerLookup(r) {
2815
2816
  r.register("COLUMNS", columns, 1, 1);
2816
2817
  r.register("OFFSET", offset, 3, 5, true);
2817
2818
  r.register("INDIRECT", indirect, 1, 2, true);
2819
+ r.register("ANCHORARRAY", anchorArray, 1, 1);
2820
+ r.register("SINGLE", single, 1, 1);
2818
2821
  }
2819
2822
  function num2(a, c, i) {
2820
2823
  const r = FormulaHelper.evalDouble(a[i], c);
@@ -2838,8 +2841,9 @@ function vlookup(a, c) {
2838
2841
  const tbl = tableVal.arrayVal;
2839
2842
  if (colIdx < 0 || colIdx >= tbl.columns) return FormulaValue.errorRef;
2840
2843
  if (exactMatch) {
2844
+ const pattern = lookupPattern(lookupVal);
2841
2845
  for (let r = 0; r < tbl.rows; r++) {
2842
- if (FormulaValue.areEqual(tbl.get(r, 0), lookupVal)) return tbl.get(r, colIdx);
2846
+ if (matchesLookup(tbl.get(r, 0), lookupVal, pattern)) return tbl.get(r, colIdx);
2843
2847
  }
2844
2848
  return FormulaValue.errorNA;
2845
2849
  }
@@ -2868,8 +2872,9 @@ function hlookup(a, c) {
2868
2872
  const tbl = tableVal.arrayVal;
2869
2873
  if (rowIdx < 0 || rowIdx >= tbl.rows) return FormulaValue.errorRef;
2870
2874
  if (exactMatch) {
2875
+ const pattern = lookupPattern(lookupVal);
2871
2876
  for (let col = 0; col < tbl.columns; col++) {
2872
- if (FormulaValue.areEqual(tbl.get(0, col), lookupVal)) return tbl.get(rowIdx, col);
2877
+ if (matchesLookup(tbl.get(0, col), lookupVal, pattern)) return tbl.get(rowIdx, col);
2873
2878
  }
2874
2879
  return FormulaValue.errorNA;
2875
2880
  }
@@ -2880,18 +2885,13 @@ function hlookup(a, c) {
2880
2885
  }
2881
2886
  return best >= 0 ? tbl.get(rowIdx, best) : FormulaValue.errorNA;
2882
2887
  }
2883
- function wildcardRegex(pattern) {
2884
- let out = "";
2885
- for (let i = 0; i < pattern.length; i++) {
2886
- const ch = pattern[i];
2887
- if (ch === "~" && i + 1 < pattern.length) {
2888
- const next = pattern[++i];
2889
- out += next.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2890
- } else if (ch === "*") out += ".*";
2891
- else if (ch === "?") out += ".";
2892
- else out += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2893
- }
2894
- 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());
2895
2895
  }
2896
2896
  function findMatch(n, get, lookupVal, matchMode, searchMode) {
2897
2897
  if ((searchMode === 2 || searchMode === -2) && matchMode !== 2) {
@@ -2915,8 +2915,11 @@ function findMatch(n, get, lookupVal, matchMode, searchMode) {
2915
2915
  const reverse = searchMode === -1;
2916
2916
  const order = reverse ? Array.from({ length: n }, (_, k) => n - 1 - k) : Array.from({ length: n }, (_, k) => k);
2917
2917
  if (matchMode === 2) {
2918
- const re = wildcardRegex(lookupVal.asText());
2919
- for (const i of order) if (re.test(get(i).asText())) return i;
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
+ }
2920
2923
  return -1;
2921
2924
  }
2922
2925
  if (matchMode === 0) {
@@ -3026,13 +3029,15 @@ function match(a, c) {
3026
3029
  matchType = Math.round(r.n);
3027
3030
  }
3028
3031
  if (!arrVal.isArray) {
3029
- return FormulaValue.areEqual(arrVal, lookupVal) ? FormulaValue.one : FormulaValue.errorNA;
3032
+ const single2 = matchType === 0 ? matchesLookup(arrVal, lookupVal, lookupPattern(lookupVal)) : FormulaValue.areEqual(arrVal, lookupVal);
3033
+ return single2 ? FormulaValue.one : FormulaValue.errorNA;
3030
3034
  }
3031
3035
  const arr = arrVal.arrayVal;
3032
3036
  const len2 = arr.length;
3033
3037
  if (matchType === 0) {
3038
+ const pattern = lookupPattern(lookupVal);
3034
3039
  for (let i = 0; i < len2; i++) {
3035
- if (FormulaValue.areEqual(arr.getFlat(i), lookupVal)) return FormulaValue.number(i + 1);
3040
+ if (matchesLookup(arr.getFlat(i), lookupVal, pattern)) return FormulaValue.number(i + 1);
3036
3041
  }
3037
3042
  return FormulaValue.errorNA;
3038
3043
  }
@@ -3262,6 +3267,15 @@ function offset(a, c) {
3262
3267
  cellRefFromRowCol(newRow + height - 1, newCol + width - 1)
3263
3268
  );
3264
3269
  }
3270
+ function anchorArray(a, c) {
3271
+ const arg2 = a[0];
3272
+ if (arg2 instanceof CellReferenceNode) return c.resolveSpillRange(arg2.reference);
3273
+ if (arg2 instanceof SheetCellReferenceNode) return c.resolveSheetSpillRange(arg2.sheetName, arg2.cellReference);
3274
+ return FormulaValue.errorRef;
3275
+ }
3276
+ function single(a, c) {
3277
+ return new ImplicitIntersectionNode(a[0]).evaluate(c);
3278
+ }
3265
3279
 
3266
3280
  // src/functions/statistical-functions.ts
3267
3281
  function registerStatistical(r) {
@@ -4542,24 +4556,20 @@ function toRow(a, c) {
4542
4556
 
4543
4557
  // src/function-registry.ts
4544
4558
  var MAX_LIFTED_CELLS = 1048576;
4545
- var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
4546
- var _FunctionRegistry = class _FunctionRegistry {
4547
- constructor() {
4548
- __privateAdd(this, _FunctionRegistry_instances);
4549
- __privateAdd(this, _functions, /* @__PURE__ */ new Map());
4550
- }
4559
+ var FunctionRegistry = class _FunctionRegistry {
4560
+ static #default;
4551
4561
  /** Lazily-created default registry with all built-in functions registered. */
4552
4562
  static get default() {
4553
- var _a;
4554
- if (!__privateGet(_FunctionRegistry, _default)) {
4555
- __privateSet(_FunctionRegistry, _default, new _FunctionRegistry());
4556
- __privateMethod(_a = __privateGet(_FunctionRegistry, _default), _FunctionRegistry_instances, registerBuiltIns_fn).call(_a);
4563
+ if (!_FunctionRegistry.#default) {
4564
+ _FunctionRegistry.#default = new _FunctionRegistry();
4565
+ _FunctionRegistry.#default.#registerBuiltIns();
4557
4566
  }
4558
- return __privateGet(_FunctionRegistry, _default);
4567
+ return _FunctionRegistry.#default;
4559
4568
  }
4569
+ #functions = /* @__PURE__ */ new Map();
4560
4570
  /** Registers a function. Name is normalised to UPPERCASE. */
4561
4571
  register(name, handler, minArgs = 0, maxArgs = -1, isVolatile = false) {
4562
- __privateGet(this, _functions).set(name.toUpperCase(), {
4572
+ this.#functions.set(name.toUpperCase(), {
4563
4573
  name: name.toUpperCase(),
4564
4574
  minArgs,
4565
4575
  maxArgs,
@@ -4585,7 +4595,7 @@ var _FunctionRegistry = class _FunctionRegistry {
4585
4595
  * `register`, or lifting would map it over the array it is meant to reduce.
4586
4596
  */
4587
4597
  registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
4588
- __privateGet(this, _functions).set(name.toUpperCase(), {
4598
+ this.#functions.set(name.toUpperCase(), {
4589
4599
  name: name.toUpperCase(),
4590
4600
  minArgs,
4591
4601
  maxArgs,
@@ -4596,81 +4606,84 @@ var _FunctionRegistry = class _FunctionRegistry {
4596
4606
  }
4597
4607
  /** Executes a function by name with argument validation. */
4598
4608
  execute(name, args, ctx) {
4599
- var _a;
4600
- const def = __privateGet(this, _functions).get(name.toUpperCase());
4609
+ const def = this.#functions.get(name.toUpperCase());
4601
4610
  if (!def) return FormulaValue.errorName;
4602
4611
  if (args.length < def.minArgs) return FormulaValue.errorValue;
4603
4612
  if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
4604
4613
  try {
4605
- return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
4614
+ return def.elementwise ? _FunctionRegistry.#executeElementwise(def, args, ctx) : def.handler(args, ctx);
4606
4615
  } catch {
4607
4616
  return FormulaValue.errorValue;
4608
4617
  }
4609
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
+ }
4610
4665
  /** Returns true if the named function is volatile (NOW, RAND, etc.). */
4611
4666
  isVolatile(name) {
4612
- return __privateGet(this, _functions).get(name.toUpperCase())?.isVolatile ?? false;
4667
+ return this.#functions.get(name.toUpperCase())?.isVolatile ?? false;
4613
4668
  }
4614
4669
  /** Returns true if the function is registered. */
4615
4670
  has(name) {
4616
- return __privateGet(this, _functions).has(name.toUpperCase());
4617
- }
4618
- };
4619
- _default = new WeakMap();
4620
- _functions = new WeakMap();
4621
- _FunctionRegistry_static = new WeakSet();
4622
- executeElementwise_fn = function(def, args, ctx) {
4623
- const values = args.map((a) => a.evaluate(ctx));
4624
- let rows2 = 1, cols = 1;
4625
- for (const v of values) {
4626
- if (!v.isArray) continue;
4627
- const a = v.arrayVal;
4628
- if (a.rows > rows2) rows2 = a.rows;
4629
- if (a.columns > cols) cols = a.columns;
4630
- }
4631
- if (rows2 === 1 && cols === 1) {
4632
- return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4633
- }
4634
- if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4635
- for (const v of values) {
4636
- if (!v.isArray) continue;
4637
- const a = v.arrayVal;
4638
- if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4639
- return FormulaValue.errorValue;
4640
- }
4641
- }
4642
- const nodes = values.map((v) => new ValueNode(v));
4643
- const out = new ArrayValue(rows2, cols);
4644
- for (let r = 0; r < rows2; r++) {
4645
- for (let c = 0; c < cols; c++) {
4646
- for (let i = 0; i < values.length; i++) {
4647
- const v = values[i];
4648
- if (!v.isArray) continue;
4649
- const a = v.arrayVal;
4650
- nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4651
- }
4652
- out.set(r, c, def.handler(nodes, ctx));
4653
- }
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);
4654
4685
  }
4655
- return FormulaValue.array(out);
4656
- };
4657
- _FunctionRegistry_instances = new WeakSet();
4658
- registerBuiltIns_fn = function() {
4659
- registerMath(this);
4660
- registerText(this);
4661
- registerLogical(this);
4662
- registerDate(this);
4663
- registerLookup(this);
4664
- registerStatistical(this);
4665
- registerInfo(this);
4666
- registerFinance(this);
4667
- registerMeta(this);
4668
- registerAggregate(this);
4669
- registerArray(this);
4670
4686
  };
4671
- __privateAdd(_FunctionRegistry, _FunctionRegistry_static);
4672
- __privateAdd(_FunctionRegistry, _default);
4673
- var FunctionRegistry = _FunctionRegistry;
4674
4687
  var FormulaHelper;
4675
4688
  ((FormulaHelper2) => {
4676
4689
  function evalDouble(node, ctx) {
@@ -4743,7 +4756,7 @@ var FormulaHelper;
4743
4756
  const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
4744
4757
  return rhsNum !== null ? { kind: "compareNumber", number: rhsNum, op, test } : { kind: "other", test };
4745
4758
  }
4746
- if (trimmed.includes("*") || trimmed.includes("?")) {
4759
+ if (hasWildcardSyntax(trimmed)) {
4747
4760
  const pattern = wildcardToRegex(trimmed);
4748
4761
  return { kind: "other", test: (v) => pattern.test(v.asText()) };
4749
4762
  }
@@ -4775,53 +4788,106 @@ var FormulaHelper;
4775
4788
  return compileCriteria(criteria).test(value2);
4776
4789
  }
4777
4790
  FormulaHelper2.matchesCriteria = matchesCriteria;
4778
- function wildcardToRegex(pattern) {
4779
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
4780
- return new RegExp(`^${escaped}$`, "i");
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;
4781
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, "\\$&");
4782
4815
  })(FormulaHelper || (FormulaHelper = {}));
4783
4816
 
4784
4817
  // src/formula-context.ts
4785
- var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn, forSheet_fn;
4786
- var _FormulaContext = class _FormulaContext {
4787
- constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
4788
- __privateAdd(this, _FormulaContext_instances);
4789
- __privateAdd(this, _sheet);
4790
- __privateAdd(this, _functions2);
4791
- __privateAdd(this, _evaluating);
4792
- /**
4793
- * Lexical scope stack for LET/LAMBDA local names. Each frame maps an
4794
- * upper-cased local name to its value. Names resolve innermost-first and take
4795
- * precedence over workbook defined names.
4796
- */
4797
- __privateAdd(this, _scopes, []);
4798
- __privateSet(this, _sheet, sheet);
4799
- this.formulaRow = formulaRow;
4800
- this.formulaCol = formulaCol;
4801
- __privateSet(this, _evaluating, evaluating ?? /* @__PURE__ */ new Set());
4802
- __privateSet(this, _functions2, registry ?? FunctionRegistry.default);
4803
- }
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 = [];
4804
4854
  /** Pushes a new binding frame (used by LET/LAMBDA). */
4805
4855
  pushScope(bindings) {
4806
- __privateGet(this, _scopes).push(bindings);
4856
+ this.#scopes.push(bindings);
4807
4857
  }
4808
4858
  /** Pops the innermost binding frame. */
4809
4859
  popScope() {
4810
- __privateGet(this, _scopes).pop();
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;
4811
4877
  }
4812
4878
  get worksheet() {
4813
- return __privateGet(this, _sheet);
4879
+ return this.#sheet;
4814
4880
  }
4815
4881
  // ── Cell/Range resolution ─────────────────────────────────────────────────
4816
4882
  getCellValue(cellReference) {
4817
4883
  const norm = cellReference.replace(/\$/g, "").toUpperCase();
4818
- if (__privateGet(this, _evaluating).has(norm)) return FormulaValue.errorRef;
4819
- __privateGet(this, _evaluating).add(norm);
4884
+ if (this.#evaluating.has(norm)) return FormulaValue.errorRef;
4885
+ this.#evaluating.add(norm);
4820
4886
  try {
4821
- const raw = __privateGet(this, _sheet).getCellValue(cellReference);
4887
+ const raw = this.#sheet.getCellValue(cellReference);
4822
4888
  return FormulaValue.fromObjectSync(raw);
4823
4889
  } finally {
4824
- __privateGet(this, _evaluating).delete(norm);
4890
+ this.#evaluating.delete(norm);
4825
4891
  }
4826
4892
  }
4827
4893
  getRangeValues(startRef, endRef) {
@@ -4839,17 +4905,17 @@ var _FormulaContext = class _FormulaContext {
4839
4905
  const rows2 = endRow - startRow + 1;
4840
4906
  const cols = endCol - startCol + 1;
4841
4907
  const cache = rows2 * cols > 1 ? this.rangeCache : void 0;
4842
- const sheetKey = cache ? __privateGet(this, _sheet).name.toUpperCase() : "";
4908
+ const sheetKey = cache ? this.#sheet.name.toUpperCase() : "";
4843
4909
  if (cache) {
4844
4910
  const hit = cache.get(sheetKey, startRow, startCol, endRow, endCol);
4845
4911
  if (hit !== void 0) return hit;
4846
4912
  }
4847
4913
  const arr = new ArrayValue(rows2, cols);
4848
- const at = __privateGet(this, _sheet).getCellValueAt;
4914
+ const at = this.#sheet.getCellValueAt;
4849
4915
  if (at) {
4850
4916
  for (let r = 0; r < rows2; r++) {
4851
4917
  for (let c = 0; c < cols; c++) {
4852
- arr.set(r, c, FormulaValue.fromObjectSync(at.call(__privateGet(this, _sheet), startRow + r, startCol + c)));
4918
+ arr.set(r, c, FormulaValue.fromObjectSync(at.call(this.#sheet, startRow + r, startCol + c)));
4853
4919
  }
4854
4920
  }
4855
4921
  } else {
@@ -4875,7 +4941,7 @@ var _FormulaContext = class _FormulaContext {
4875
4941
  * silently reading nothing would make SUM(A:A) return 0 on a full column.
4876
4942
  */
4877
4943
  getFullRangeValues(startCol, endCol, startRow, endRow) {
4878
- const bounds = __privateGet(this, _sheet).usedBounds;
4944
+ const bounds = this.#sheet.usedBounds;
4879
4945
  if (!bounds) return FormulaValue.errorRef;
4880
4946
  if (bounds.rowCount === 0 || bounds.columnCount === 0) return FormulaValue.blank;
4881
4947
  const r0 = startRow ?? 1;
@@ -4890,7 +4956,7 @@ var _FormulaContext = class _FormulaContext {
4890
4956
  getSheetCellValue(sheetName, cellReference) {
4891
4957
  if (!this.workbook) return FormulaValue.errorRef;
4892
4958
  try {
4893
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getCellValue(cellReference);
4959
+ return this.#forSheet(sheetName).getCellValue(cellReference);
4894
4960
  } catch {
4895
4961
  return FormulaValue.errorRef;
4896
4962
  }
@@ -4898,7 +4964,7 @@ var _FormulaContext = class _FormulaContext {
4898
4964
  getSheetRangeValues(sheetName, startRef, endRef) {
4899
4965
  if (!this.workbook) return FormulaValue.errorRef;
4900
4966
  try {
4901
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getRangeValues(startRef, endRef);
4967
+ return this.#forSheet(sheetName).getRangeValues(startRef, endRef);
4902
4968
  } catch {
4903
4969
  return FormulaValue.errorRef;
4904
4970
  }
@@ -4907,13 +4973,28 @@ var _FormulaContext = class _FormulaContext {
4907
4973
  getSheetFullRangeValues(sheetName, startCol, endCol, startRow, endRow) {
4908
4974
  if (!this.workbook) return FormulaValue.errorRef;
4909
4975
  try {
4910
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
4976
+ return this.#forSheet(sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
4911
4977
  } catch {
4912
4978
  return FormulaValue.errorRef;
4913
4979
  }
4914
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
+ }
4915
4996
  resolveNamedRange(name) {
4916
- const scoped = __privateMethod(this, _FormulaContext_instances, lookupScope_fn).call(this, name);
4997
+ const scoped = this.#lookupScope(name);
4917
4998
  if (scoped !== void 0) return scoped;
4918
4999
  return this.nameResolver ? this.nameResolver(name) : FormulaValue.errorName;
4919
5000
  }
@@ -4922,12 +5003,21 @@ var _FormulaContext = class _FormulaContext {
4922
5003
  * cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
4923
5004
  */
4924
5005
  resolveSpillRange(anchorRef) {
4925
- const spillRef = __privateGet(this, _sheet).getSpillRange?.(anchorRef);
5006
+ const spillRef = this.#sheet.getSpillRange?.(anchorRef);
4926
5007
  if (!spillRef) return FormulaValue.errorRef;
4927
5008
  const colon = spillRef.indexOf(":");
4928
5009
  if (colon < 0) return this.getCellValue(spillRef);
4929
5010
  return this.getRangeValues(spillRef.slice(0, colon), spillRef.slice(colon + 1));
4930
5011
  }
5012
+ /** {@link resolveSpillRange} for an anchor on another sheet ('S'!A1#). */
5013
+ resolveSheetSpillRange(sheetName, anchorRef) {
5014
+ if (!this.workbook) return FormulaValue.errorRef;
5015
+ try {
5016
+ return this.#forSheet(sheetName).resolveSpillRange(anchorRef);
5017
+ } catch {
5018
+ return FormulaValue.errorRef;
5019
+ }
5020
+ }
4931
5021
  getRangeBounds(startRef, endRef) {
4932
5022
  const s = parseCellRef(startRef);
4933
5023
  const e = parseCellRef(endRef);
@@ -4936,94 +5026,63 @@ var _FormulaContext = class _FormulaContext {
4936
5026
  // ── Function dispatch ─────────────────────────────────────────────────────
4937
5027
  /** True if a built-in function with this name is registered. */
4938
5028
  hasFunction(name) {
4939
- return __privateGet(this, _functions2).has(name);
5029
+ return this.#functions.has(name);
4940
5030
  }
4941
5031
  evaluateFunction(name, args) {
4942
- if (!__privateGet(this, _functions2).has(name)) {
5032
+ if (!this.#functions.has(name)) {
4943
5033
  const named = this.resolveNamedRange(name);
4944
5034
  if (named.isLambda) {
4945
5035
  return named.lambdaVal.call(args.map((a) => a.evaluate(this)));
4946
5036
  }
4947
5037
  }
4948
- return __privateGet(this, _functions2).execute(name, args, this);
5038
+ return this.#functions.execute(name, args, this);
4949
5039
  }
4950
5040
  };
4951
- _sheet = new WeakMap();
4952
- _functions2 = new WeakMap();
4953
- _evaluating = new WeakMap();
4954
- _scopes = new WeakMap();
4955
- _FormulaContext_instances = new WeakSet();
4956
- lookupScope_fn = function(name) {
4957
- if (__privateGet(this, _scopes).length === 0) return void 0;
4958
- const key = name.toUpperCase();
4959
- for (let i = __privateGet(this, _scopes).length - 1; i >= 0; i--) {
4960
- const v = __privateGet(this, _scopes)[i].get(key);
4961
- if (v !== void 0) return v;
4962
- }
4963
- return void 0;
4964
- };
4965
- /**
4966
- * A sibling context bound to another sheet, sharing this one's circular-
4967
- * reference guard and range memo. These contexts only read values — they
4968
- * never evaluate an AST — so the name resolver and recursion budget are
4969
- * deliberately not carried over; passing them would suggest a scoping
4970
- * relationship that does not exist. The memo, on the other hand, must be
4971
- * carried: cross-sheet ranges are the most expensive reads in a workbook.
4972
- */
4973
- forSheet_fn = function(sheetName) {
4974
- const sheet = this.workbook.getWorksheet(sheetName);
4975
- const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, __privateGet(this, _evaluating), __privateGet(this, _functions2));
4976
- ctx.workbook = this.workbook;
4977
- ctx.rangeCache = this.rangeCache;
4978
- return ctx;
4979
- };
4980
- var FormulaContext = _FormulaContext;
4981
5041
 
4982
5042
  // src/lru-cache.ts
4983
- var _map, _capacity, _hits, _misses, _evictions;
4984
5043
  var LruCache = class {
5044
+ #map;
5045
+ #capacity;
5046
+ #hits = 0;
5047
+ #misses = 0;
5048
+ #evictions = 0;
4985
5049
  constructor(capacity) {
4986
- __privateAdd(this, _map);
4987
- __privateAdd(this, _capacity);
4988
- __privateAdd(this, _hits, 0);
4989
- __privateAdd(this, _misses, 0);
4990
- __privateAdd(this, _evictions, 0);
4991
5050
  if (capacity <= 0) throw new RangeError("LruCache capacity must be > 0.");
4992
- __privateSet(this, _capacity, capacity);
4993
- __privateSet(this, _map, /* @__PURE__ */ new Map());
5051
+ this.#capacity = capacity;
5052
+ this.#map = /* @__PURE__ */ new Map();
4994
5053
  }
4995
5054
  get capacity() {
4996
- return __privateGet(this, _capacity);
5055
+ return this.#capacity;
4997
5056
  }
4998
5057
  get size() {
4999
- return __privateGet(this, _map).size;
5058
+ return this.#map.size;
5000
5059
  }
5001
5060
  get hits() {
5002
- return __privateGet(this, _hits);
5061
+ return this.#hits;
5003
5062
  }
5004
5063
  get misses() {
5005
- return __privateGet(this, _misses);
5064
+ return this.#misses;
5006
5065
  }
5007
5066
  get evictions() {
5008
- return __privateGet(this, _evictions);
5067
+ return this.#evictions;
5009
5068
  }
5010
5069
  get hitRate() {
5011
- const total = __privateGet(this, _hits) + __privateGet(this, _misses);
5012
- return total === 0 ? 0 : __privateGet(this, _hits) / total;
5070
+ const total = this.#hits + this.#misses;
5071
+ return total === 0 ? 0 : this.#hits / total;
5013
5072
  }
5014
5073
  /**
5015
5074
  * Returns the value for key, or undefined if not present.
5016
5075
  * Moves the accessed key to the "most recently used" end.
5017
5076
  */
5018
5077
  get(key) {
5019
- const value2 = __privateGet(this, _map).get(key);
5078
+ const value2 = this.#map.get(key);
5020
5079
  if (value2 === void 0) {
5021
- __privateWrapper(this, _misses)._++;
5080
+ this.#misses++;
5022
5081
  return void 0;
5023
5082
  }
5024
- __privateGet(this, _map).delete(key);
5025
- __privateGet(this, _map).set(key, value2);
5026
- __privateWrapper(this, _hits)._++;
5083
+ this.#map.delete(key);
5084
+ this.#map.set(key, value2);
5085
+ this.#hits++;
5027
5086
  return value2;
5028
5087
  }
5029
5088
  /**
@@ -5031,44 +5090,39 @@ var LruCache = class {
5031
5090
  * Evicts the least-recently-used entry if over capacity.
5032
5091
  */
5033
5092
  set(key, value2) {
5034
- if (__privateGet(this, _map).has(key)) {
5035
- __privateGet(this, _map).delete(key);
5036
- } else if (__privateGet(this, _map).size >= __privateGet(this, _capacity)) {
5037
- const oldest = __privateGet(this, _map).keys().next().value;
5038
- __privateGet(this, _map).delete(oldest);
5039
- __privateWrapper(this, _evictions)._++;
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++;
5040
5099
  }
5041
- __privateGet(this, _map).set(key, value2);
5100
+ this.#map.set(key, value2);
5042
5101
  }
5043
5102
  /** Removes a key from the cache. Returns true if it existed. */
5044
5103
  delete(key) {
5045
- return __privateGet(this, _map).delete(key);
5104
+ return this.#map.delete(key);
5046
5105
  }
5047
5106
  /** Returns true if the key is present (does NOT update LRU order). */
5048
5107
  has(key) {
5049
- return __privateGet(this, _map).has(key);
5108
+ return this.#map.has(key);
5050
5109
  }
5051
5110
  /** Removes all entries and resets statistics. */
5052
5111
  clear() {
5053
- __privateGet(this, _map).clear();
5054
- __privateSet(this, _hits, __privateSet(this, _misses, __privateSet(this, _evictions, 0)));
5112
+ this.#map.clear();
5113
+ this.#hits = this.#misses = this.#evictions = 0;
5055
5114
  }
5056
5115
  /** Resizes the cache, evicting oldest entries if necessary. */
5057
5116
  resize(newCapacity) {
5058
5117
  if (newCapacity <= 0) throw new RangeError("Capacity must be > 0.");
5059
- __privateSet(this, _capacity, newCapacity);
5060
- while (__privateGet(this, _map).size > newCapacity) {
5061
- const oldest = __privateGet(this, _map).keys().next().value;
5062
- __privateGet(this, _map).delete(oldest);
5063
- __privateWrapper(this, _evictions)._++;
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++;
5064
5123
  }
5065
5124
  }
5066
5125
  };
5067
- _map = new WeakMap();
5068
- _capacity = new WeakMap();
5069
- _hits = new WeakMap();
5070
- _misses = new WeakMap();
5071
- _evictions = new WeakMap();
5072
5126
 
5073
5127
  // src/formula-evaluator.ts
5074
5128
  var DEFAULT_CACHE_CAPACITY = 1e4;
@@ -5176,143 +5230,126 @@ var TILE_ROWS = 64;
5176
5230
  var TILE_COLS = 64;
5177
5231
  var MAX_TILES_PER_RANGE = 32;
5178
5232
  var DEFAULT_MAX_CELLS = 2e6;
5179
- var _entries, _tiles, _wide, _placements, _sizes, _cells, _maxCells, _RangeCache_static, contains_fn, _RangeCache_instances, drop_fn, fileIn_fn;
5180
- var _RangeCache = class _RangeCache {
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;
5181
5247
  constructor(maxCells = DEFAULT_MAX_CELLS) {
5182
- __privateAdd(this, _RangeCache_instances);
5183
- /** `SHEET r0 c0 r1 c1` → the memoised array value. Insertion-ordered (FIFO eviction). */
5184
- __privateAdd(this, _entries, /* @__PURE__ */ new Map());
5185
- /** `sheet tr tc` → entry keys whose rectangle overlaps that tile. */
5186
- __privateAdd(this, _tiles, /* @__PURE__ */ new Map());
5187
- /** sheet → entry keys whose rectangle is too broad to tile. */
5188
- __privateAdd(this, _wide, /* @__PURE__ */ new Map());
5189
- /** entry key → buckets it was filed under, for cheap removal. */
5190
- __privateAdd(this, _placements, /* @__PURE__ */ new Map());
5191
- /** entry key → its area, so eviction can give the budget back. */
5192
- __privateAdd(this, _sizes, /* @__PURE__ */ new Map());
5193
- /** Number of cells across all cached rectangles, kept under {@link #maxCells}. */
5194
- __privateAdd(this, _cells, 0);
5195
- __privateAdd(this, _maxCells);
5196
- this.hits = 0;
5197
- this.misses = 0;
5198
- __privateSet(this, _maxCells, maxCells);
5248
+ this.#maxCells = maxCells;
5199
5249
  }
5200
5250
  get size() {
5201
- return __privateGet(this, _entries).size;
5251
+ return this.#entries.size;
5202
5252
  }
5203
5253
  get cachedCells() {
5204
- return __privateGet(this, _cells);
5254
+ return this.#cells;
5205
5255
  }
5206
5256
  static key(sheet, r0, c0, r1, c1) {
5207
5257
  return `${sheet} ${r0} ${c0} ${r1} ${c1}`;
5208
5258
  }
5209
5259
  get(sheet, r0, c0, r1, c1) {
5210
- const hit = __privateGet(this, _entries).get(_RangeCache.key(sheet, r0, c0, r1, c1));
5260
+ const hit = this.#entries.get(_RangeCache.key(sheet, r0, c0, r1, c1));
5211
5261
  if (hit === void 0) this.misses++;
5212
5262
  else this.hits++;
5213
- return hit;
5263
+ return hit?.value;
5214
5264
  }
5215
5265
  /** Memoises a rectangle's value. `cells` is its area, for the budget. */
5216
5266
  set(sheet, r0, c0, r1, c1, value2, cells) {
5217
- if (cells <= 0 || cells > __privateGet(this, _maxCells)) return;
5267
+ if (cells <= 0 || cells > this.#maxCells) return;
5218
5268
  const key = _RangeCache.key(sheet, r0, c0, r1, c1);
5219
- if (__privateGet(this, _entries).has(key)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
5220
- __privateGet(this, _entries).set(key, value2);
5221
- __privateSet(this, _cells, __privateGet(this, _cells) + cells);
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;
5222
5272
  const buckets = [];
5223
5273
  const tileRow0 = r0 / TILE_ROWS | 0, tileRow1 = r1 / TILE_ROWS | 0;
5224
5274
  const tileCol0 = c0 / TILE_COLS | 0, tileCol1 = c1 / TILE_COLS | 0;
5225
5275
  const tileCount = (tileRow1 - tileRow0 + 1) * (tileCol1 - tileCol0 + 1);
5226
5276
  if (tileCount > MAX_TILES_PER_RANGE) {
5227
- __privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _wide), sheet, key, buckets, `w ${sheet}`);
5277
+ this.#fileIn(this.#wide, sheet, key, buckets);
5228
5278
  } else {
5229
5279
  for (let tr = tileRow0; tr <= tileRow1; tr++) {
5230
5280
  for (let tc = tileCol0; tc <= tileCol1; tc++) {
5231
- const bucket = `${sheet} ${tr} ${tc}`;
5232
- __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);
5233
5282
  }
5234
5283
  }
5235
5284
  }
5236
- __privateGet(this, _placements).set(key, buckets);
5237
- __privateGet(this, _sizes).set(key, cells);
5238
- if (__privateGet(this, _cells) > __privateGet(this, _maxCells)) {
5239
- for (const oldest of __privateGet(this, _entries).keys()) {
5240
- 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;
5241
5289
  if (oldest === key) continue;
5242
- __privateMethod(this, _RangeCache_instances, drop_fn).call(this, oldest);
5290
+ this.#drop(oldest);
5243
5291
  }
5244
5292
  }
5245
5293
  }
5246
- /** Drops every cached rectangle containing the given cell. */
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
+ */
5247
5302
  invalidateCell(sheet, row2, col) {
5248
- var _a, _b;
5249
- const tile = __privateGet(this, _tiles).get(`${sheet} ${row2 / TILE_ROWS | 0} ${col / TILE_COLS | 0}`);
5250
- if (tile) {
5251
- for (const key of [...tile]) {
5252
- if (__privateMethod(_a = _RangeCache, _RangeCache_static, contains_fn).call(_a, key, sheet, row2, col)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
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);
5253
5308
  }
5254
5309
  }
5255
- const wide = __privateGet(this, _wide).get(sheet);
5256
- if (wide) {
5257
- for (const key of [...wide]) {
5258
- if (__privateMethod(_b = _RangeCache, _RangeCache_static, contains_fn).call(_b, key, sheet, row2, col)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
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);
5259
5315
  }
5260
5316
  }
5261
5317
  }
5262
5318
  clear() {
5263
- __privateGet(this, _entries).clear();
5264
- __privateGet(this, _tiles).clear();
5265
- __privateGet(this, _wide).clear();
5266
- __privateGet(this, _placements).clear();
5267
- __privateGet(this, _sizes).clear();
5268
- __privateSet(this, _cells, 0);
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
+ }
5269
5341
  }
5270
- };
5271
- _entries = new WeakMap();
5272
- _tiles = new WeakMap();
5273
- _wide = new WeakMap();
5274
- _placements = new WeakMap();
5275
- _sizes = new WeakMap();
5276
- _cells = new WeakMap();
5277
- _maxCells = new WeakMap();
5278
- _RangeCache_static = new WeakSet();
5279
- contains_fn = function(key, sheet, row2, col) {
5280
- const parts = key.split(" ");
5281
- const n = parts.length;
5282
- const c1 = +parts[n - 1], r1 = +parts[n - 2], c0 = +parts[n - 3], r0 = +parts[n - 4];
5283
- if (parts.slice(0, n - 4).join(" ") !== sheet) return false;
5284
- return row2 >= r0 && row2 <= r1 && col >= c0 && col <= c1;
5285
- };
5286
- _RangeCache_instances = new WeakSet();
5287
- drop_fn = function(key) {
5288
- if (!__privateGet(this, _entries).delete(key)) return;
5289
- __privateSet(this, _cells, __privateGet(this, _cells) - (__privateGet(this, _sizes).get(key) ?? 0));
5290
- __privateGet(this, _sizes).delete(key);
5291
- const buckets = __privateGet(this, _placements).get(key);
5292
- if (buckets) {
5293
- for (const bucket of buckets) {
5294
- const store = bucket.charCodeAt(0) === 119 ? __privateGet(this, _wide) : __privateGet(this, _tiles);
5295
- const bucketKey = bucket.slice(2);
5296
- const set = store.get(bucketKey);
5297
- if (!set) continue;
5298
- set.delete(key);
5299
- 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);
5300
5347
  }
5301
- __privateGet(this, _placements).delete(key);
5348
+ if (set.has(entryKey)) return;
5349
+ set.add(entryKey);
5350
+ buckets.push({ store, bucketKey, set });
5302
5351
  }
5303
5352
  };
5304
- fileIn_fn = function(store, bucketKey, entryKey, buckets, bucketId) {
5305
- let set = store.get(bucketKey);
5306
- if (!set) {
5307
- set = /* @__PURE__ */ new Set();
5308
- store.set(bucketKey, set);
5309
- }
5310
- if (set.has(entryKey)) return;
5311
- set.add(entryKey);
5312
- buckets.push(bucketId);
5313
- };
5314
- __privateAdd(_RangeCache, _RangeCache_static);
5315
- var RangeCache = _RangeCache;
5316
5353
  function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
5317
5354
  const refs = [];
5318
5355
  let volatile = false;
@@ -5392,24 +5429,20 @@ var MAX_EXACT_SPAN = 16;
5392
5429
  var MAX_BUCKETS_PER_REF = 64;
5393
5430
  var tileOfRow = (row2) => row2 / TILE_ROWS2 | 0;
5394
5431
  var tileOfCol = (col) => col / TILE_COLS2 | 0;
5395
- var _buckets, _placements2, _DependencyIndex_instances, visit_fn, file_fn;
5396
5432
  var DependencyIndex = class {
5397
- constructor() {
5398
- __privateAdd(this, _DependencyIndex_instances);
5399
- /**
5400
- * All buckets in one map, keyed by a discriminating prefix:
5401
- *
5402
- * `t s tr tc` rectangle compact in both dimensions
5403
- * `c s col tr` narrow columns, row tiles (the common case)
5404
- * `C s col` narrow columns, every row (`A:A`)
5405
- * `r s row tc` narrow rows, column tiles
5406
- * `R s row` narrow rows, every column (`1:1`)
5407
- * `w s` broad in both dimensions
5408
- */
5409
- __privateAdd(this, _buckets, /* @__PURE__ */ new Map());
5410
- /** formula key → the buckets it was filed under, for cheap removal. */
5411
- __privateAdd(this, _placements2, /* @__PURE__ */ new Map());
5412
- }
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();
5413
5446
  /** Registers (or re-registers) a formula's precedent rectangles. */
5414
5447
  add(formulaKey, refs) {
5415
5448
  this.remove(formulaKey);
@@ -5426,12 +5459,12 @@ var DependencyIndex = class {
5426
5459
  if (colSpan * rowTiles <= MAX_BUCKETS_PER_REF && rowTiles <= MAX_TILES_PER_REF) {
5427
5460
  for (let col = ref.startCol; col <= ref.endCol; col++) {
5428
5461
  for (let tr = rowTile0; tr <= rowTile1; tr++) {
5429
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `c ${ref.sheet} ${col} ${tr}`, formulaKey, buckets);
5462
+ this.#file(`c ${ref.sheet} ${col} ${tr}`, formulaKey, buckets);
5430
5463
  }
5431
5464
  }
5432
5465
  } else {
5433
5466
  for (let col = ref.startCol; col <= ref.endCol; col++) {
5434
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `C ${ref.sheet} ${col}`, formulaKey, buckets);
5467
+ this.#file(`C ${ref.sheet} ${col}`, formulaKey, buckets);
5435
5468
  }
5436
5469
  }
5437
5470
  continue;
@@ -5440,12 +5473,12 @@ var DependencyIndex = class {
5440
5473
  if (rowSpan * colTiles <= MAX_BUCKETS_PER_REF && colTiles <= MAX_TILES_PER_REF) {
5441
5474
  for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
5442
5475
  for (let tc = colTile0; tc <= colTile1; tc++) {
5443
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `r ${ref.sheet} ${row2} ${tc}`, formulaKey, buckets);
5476
+ this.#file(`r ${ref.sheet} ${row2} ${tc}`, formulaKey, buckets);
5444
5477
  }
5445
5478
  }
5446
5479
  } else {
5447
5480
  for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
5448
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `R ${ref.sheet} ${row2}`, formulaKey, buckets);
5481
+ this.#file(`R ${ref.sheet} ${row2}`, formulaKey, buckets);
5449
5482
  }
5450
5483
  }
5451
5484
  continue;
@@ -5453,31 +5486,31 @@ var DependencyIndex = class {
5453
5486
  if (rowTiles * colTiles <= MAX_TILES_PER_REF) {
5454
5487
  for (let tr = rowTile0; tr <= rowTile1; tr++) {
5455
5488
  for (let tc = colTile0; tc <= colTile1; tc++) {
5456
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `t ${ref.sheet} ${tr} ${tc}`, formulaKey, buckets);
5489
+ this.#file(`t ${ref.sheet} ${tr} ${tc}`, formulaKey, buckets);
5457
5490
  }
5458
5491
  }
5459
5492
  continue;
5460
5493
  }
5461
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `w ${ref.sheet}`, formulaKey, buckets);
5494
+ this.#file(`w ${ref.sheet}`, formulaKey, buckets);
5462
5495
  }
5463
- __privateGet(this, _placements2).set(formulaKey, buckets);
5496
+ this.#placements.set(formulaKey, buckets);
5464
5497
  }
5465
5498
  /** Forgets a formula. */
5466
5499
  remove(formulaKey) {
5467
- const buckets = __privateGet(this, _placements2).get(formulaKey);
5500
+ const buckets = this.#placements.get(formulaKey);
5468
5501
  if (!buckets) return;
5469
5502
  for (const bucket of buckets) {
5470
- const set = __privateGet(this, _buckets).get(bucket);
5503
+ const set = this.#buckets.get(bucket);
5471
5504
  if (!set) continue;
5472
5505
  set.delete(formulaKey);
5473
- if (set.size === 0) __privateGet(this, _buckets).delete(bucket);
5506
+ if (set.size === 0) this.#buckets.delete(bucket);
5474
5507
  }
5475
- __privateGet(this, _placements2).delete(formulaKey);
5508
+ this.#placements.delete(formulaKey);
5476
5509
  }
5477
5510
  /** Drops every registration. */
5478
5511
  clear() {
5479
- __privateGet(this, _buckets).clear();
5480
- __privateGet(this, _placements2).clear();
5512
+ this.#buckets.clear();
5513
+ this.#placements.clear();
5481
5514
  }
5482
5515
  /**
5483
5516
  * Calls `visit` with the key of each formula that *may* read the given cell.
@@ -5489,31 +5522,28 @@ var DependencyIndex = class {
5489
5522
  candidates(sheet, row2, col, visit) {
5490
5523
  const tr = tileOfRow(row2);
5491
5524
  const tc = tileOfCol(col);
5492
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `c ${sheet} ${col} ${tr}`, visit);
5493
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `C ${sheet} ${col}`, visit);
5494
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `r ${sheet} ${row2} ${tc}`, visit);
5495
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `R ${sheet} ${row2}`, visit);
5496
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `t ${sheet} ${tr} ${tc}`, visit);
5497
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `w ${sheet}`, visit);
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);
5498
5545
  }
5499
5546
  };
5500
- _buckets = new WeakMap();
5501
- _placements2 = new WeakMap();
5502
- _DependencyIndex_instances = new WeakSet();
5503
- visit_fn = function(bucket, visit) {
5504
- const set = __privateGet(this, _buckets).get(bucket);
5505
- if (set) for (const key of set) visit(key);
5506
- };
5507
- file_fn = function(bucket, formulaKey, buckets) {
5508
- let set = __privateGet(this, _buckets).get(bucket);
5509
- if (!set) {
5510
- set = /* @__PURE__ */ new Set();
5511
- __privateGet(this, _buckets).set(bucket, set);
5512
- }
5513
- if (set.has(formulaKey)) return;
5514
- set.add(formulaKey);
5515
- buckets.push(bucket);
5516
- };
5517
5547
 
5518
5548
  // src/recalc-engine.ts
5519
5549
  function createWorkbookRecalcModel(workbook) {
@@ -5546,6 +5576,7 @@ function createWorkbookRecalcModel(workbook) {
5546
5576
  };
5547
5577
  }
5548
5578
  var MAX_ADOPTED_SPILL_CELLS = 262144;
5579
+ var resultOf = (fv2) => fv2.isBlank ? 0 : fv2.toObject();
5549
5580
  var normSheet = (s) => s.toUpperCase();
5550
5581
  var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
5551
5582
  var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
@@ -5556,42 +5587,70 @@ function parseKey(key) {
5556
5587
  const { row: row2, column: column2 } = parseCellRef(ref);
5557
5588
  return { sheet, ref, row: row2, col: column2 };
5558
5589
  }
5559
- 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;
5560
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();
5561
5621
  constructor(model, registry = FunctionRegistry.default) {
5562
- __privateAdd(this, _RecalcEngine_instances);
5563
- __privateAdd(this, _model);
5564
- __privateAdd(this, _registry);
5565
- __privateAdd(this, _formulas, /* @__PURE__ */ new Map());
5566
- /** upper-cased sheet key original casing, for case-correct model I/O. */
5567
- __privateAdd(this, _sheetNames, /* @__PURE__ */ new Map());
5568
- /** anchor cellKey set of spilled (non-anchor) cellKeys it currently owns. */
5569
- __privateAdd(this, _spills, /* @__PURE__ */ new Map());
5570
- /** spilled cellKey → the anchor cellKey that owns it. */
5571
- __privateAdd(this, _spillOwner, /* @__PURE__ */ new Map());
5572
- /**
5573
- * Reverse dependency index, so finding the formulas that read a cell does not
5574
- * mean testing every registered formula.
5575
- */
5576
- __privateAdd(this, _dependents, new DependencyIndex());
5577
- /**
5578
- * Keys of the volatile formulas. Maintained incrementally: scanning every
5579
- * formula for volatility on each edit is O(F) per keystroke on its own.
5580
- */
5581
- __privateAdd(this, _volatiles, /* @__PURE__ */ new Set());
5582
- /** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
5583
- __privateAdd(this, _sheetLikes, /* @__PURE__ */ new Map());
5584
- __privateAdd(this, _workbookLike);
5585
- /**
5586
- * Memo for range reads. The engine owns it because it owns the writes: every
5587
- * value written goes through {@link #writeCell}, which drops the cached
5588
- * rectangles containing that cell. Nothing else may write to the model behind
5589
- * the engine's back without calling {@link onCellChanged} — that was already
5590
- * true for dependency tracking, and now also keeps this memo honest.
5591
- */
5592
- __privateAdd(this, _rangeCache, new RangeCache());
5593
- __privateSet(this, _model, model);
5594
- __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);
5595
5654
  }
5596
5655
  // ── Registration ─────────────────────────────────────────────────────────────
5597
5656
  /**
@@ -5609,12 +5668,12 @@ var RecalcEngine = class {
5609
5668
  const { refs, volatile } = extractReferences(
5610
5669
  ast,
5611
5670
  s,
5612
- __privateGet(this, _registry),
5613
- __privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0
5671
+ this.#registry,
5672
+ this.#model.getDefinedName ? (n, sh) => this.#model.getDefinedName(n, sh) : void 0
5614
5673
  );
5615
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5674
+ this.#rememberSheet(sheet);
5616
5675
  const key = keyOf(s, r);
5617
- __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5676
+ this.#register({
5618
5677
  sheet: s,
5619
5678
  sheetName: sheet,
5620
5679
  ref: r,
@@ -5626,9 +5685,9 @@ var RecalcEngine = class {
5626
5685
  refs,
5627
5686
  volatile
5628
5687
  });
5629
- __privateGet(this, _model).setCellFormula?.(sheet, r, text);
5630
- __privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
5631
- return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }], key);
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);
5632
5691
  }
5633
5692
  /**
5634
5693
  * Indexes many formulas **without evaluating anything**.
@@ -5650,7 +5709,7 @@ var RecalcEngine = class {
5650
5709
  * construct in a large workbook should not abort the load.
5651
5710
  */
5652
5711
  registerBulk(entries) {
5653
- const resolveName2 = __privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0;
5712
+ const resolveName2 = this.#model.getDefinedName ? (n, sh) => this.#model.getDefinedName(n, sh) : void 0;
5654
5713
  let registered = 0;
5655
5714
  const failed = [];
5656
5715
  for (const entry of entries) {
@@ -5662,10 +5721,10 @@ var RecalcEngine = class {
5662
5721
  try {
5663
5722
  const { row: row2, column: column2 } = parseCellRef(r);
5664
5723
  const ast = parseFormulaUncached(text);
5665
- const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
5666
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5724
+ const { refs, volatile } = extractReferences(ast, s, this.#registry, resolveName2);
5725
+ this.#rememberSheet(sheet);
5667
5726
  const key = keyOf(s, r);
5668
- __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5727
+ this.#register({
5669
5728
  sheet: s,
5670
5729
  sheetName: sheet,
5671
5730
  ref: r,
@@ -5677,15 +5736,60 @@ var RecalcEngine = class {
5677
5736
  refs,
5678
5737
  volatile
5679
5738
  });
5680
- __privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
5739
+ this.#adoptDeclaredSpill(s, sheet, r, key, row2, column2);
5681
5740
  registered++;
5682
5741
  } catch (err) {
5683
5742
  failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
5684
5743
  }
5685
5744
  }
5686
- if (registered > 0) __privateGet(this, _rangeCache).clear();
5745
+ if (registered > 0) this.#rangeCache.clear();
5687
5746
  return { registered, failed };
5688
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
+ }
5689
5793
  /**
5690
5794
  * Removes a formula from a cell and recalculates its dependents (which may now
5691
5795
  * read a plain input value instead). Returns the recomputed dependent cells.
@@ -5694,36 +5798,36 @@ var RecalcEngine = class {
5694
5798
  const s = normSheet(sheet);
5695
5799
  const r = normRef(ref);
5696
5800
  const key = keyOf(s, r);
5697
- if (!__privateGet(this, _formulas).has(key)) return [];
5698
- __privateMethod(this, _RecalcEngine_instances, unregister_fn).call(this, key);
5699
- __privateGet(this, _model).clearCell?.(sheet, r);
5801
+ if (!this.#formulas.has(key)) return [];
5802
+ this.#unregister(key);
5803
+ this.#model.clearCell?.(sheet, r);
5700
5804
  const { row: row2, column: column2 } = parseCellRef(r);
5701
- __privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
5702
- return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
5805
+ this.#rangeCache.invalidateCell(s, row2, column2);
5806
+ return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }]);
5703
5807
  }
5704
5808
  /**
5705
5809
  * Writes a raw input value to a cell and recalculates every formula that
5706
5810
  * depends on it. Use this for non-formula edits so dependents stay current.
5707
5811
  */
5708
5812
  setCellValue(sheet, ref, value2) {
5709
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5813
+ this.#rememberSheet(sheet);
5710
5814
  const s = normSheet(sheet);
5711
5815
  const r = normRef(ref);
5712
5816
  const { row: row2, column: column2 } = parseCellRef(r);
5713
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, sheet, r, value2, row2, column2);
5714
- return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
5817
+ this.#writeCell(sheet, r, value2, row2, column2);
5818
+ return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }]);
5715
5819
  }
5716
5820
  /**
5717
5821
  * Signals that a cell changed by some external means (the model was already
5718
5822
  * updated) and recalculates its dependents.
5719
5823
  */
5720
5824
  onCellChanged(sheet, ref) {
5721
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5825
+ this.#rememberSheet(sheet);
5722
5826
  const s = normSheet(sheet);
5723
5827
  const r = normRef(ref);
5724
5828
  const { row: row2, column: column2 } = parseCellRef(r);
5725
- __privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
5726
- return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
5829
+ this.#rangeCache.invalidateCell(s, row2, column2);
5830
+ return this.#recalcFrom([{ sheet: s, row: row2, col: column2 }]);
5727
5831
  }
5728
5832
  /**
5729
5833
  * Recalculates every registered formula.
@@ -5756,18 +5860,61 @@ var RecalcEngine = class {
5756
5860
  recalcAll(options) {
5757
5861
  const order = options?.order;
5758
5862
  if (!order || order.length === 0) {
5759
- return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
5863
+ return this.#evaluatePass([...this.#formulas.values()]).results;
5760
5864
  }
5761
- const attempt = __privateMethod(this, _RecalcEngine_instances, recalcInOrder_fn).call(this, order);
5865
+ const attempt = this.#recalcInOrder(order);
5762
5866
  if (attempt.conflict) {
5763
5867
  options?.onOrderConflict?.(attempt.conflict);
5764
- return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
5868
+ return this.#evaluatePass([...this.#formulas.values()]).results;
5765
5869
  }
5766
5870
  return attempt.results;
5767
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
+ }
5768
5915
  /** The precedents recorded for a formula cell (mainly for tests/inspection). */
5769
5916
  getPrecedents(sheet, ref) {
5770
- return __privateGet(this, _formulas).get(keyOf(sheet, ref))?.refs ?? [];
5917
+ return this.#formulas.get(keyOf(sheet, ref))?.refs ?? [];
5771
5918
  }
5772
5919
  // ── Structural edits ─────────────────────────────────────────────────────────
5773
5920
  /**
@@ -5782,7 +5929,7 @@ var RecalcEngine = class {
5782
5929
  * engine.onRowsInserted('S1', 3, 2);
5783
5930
  */
5784
5931
  onRowsInserted(sheet, at, count2 = 1) {
5785
- return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "row", kind: "insert", at, count: count2, sheetName: sheet });
5932
+ return this.#applyShift({ dim: "row", kind: "insert", at, count: count2, sheetName: sheet });
5786
5933
  }
5787
5934
  /**
5788
5935
  * Tells the engine rows were deleted on a sheet. Call AFTER the
@@ -5792,556 +5939,420 @@ var RecalcEngine = class {
5792
5939
  * volatiles) are recomputed.
5793
5940
  */
5794
5941
  onRowsDeleted(sheet, at, count2 = 1) {
5795
- return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "row", kind: "delete", at, count: count2, sheetName: sheet });
5942
+ return this.#applyShift({ dim: "row", kind: "delete", at, count: count2, sheetName: sheet });
5796
5943
  }
5797
5944
  /** Column counterpart to {@link onRowsInserted}. */
5798
5945
  onColumnsInserted(sheet, at, count2 = 1) {
5799
- return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "col", kind: "insert", at, count: count2, sheetName: sheet });
5946
+ return this.#applyShift({ dim: "col", kind: "insert", at, count: count2, sheetName: sheet });
5800
5947
  }
5801
5948
  /** Column counterpart to {@link onRowsDeleted}. */
5802
5949
  onColumnsDeleted(sheet, at, count2 = 1) {
5803
- return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "col", kind: "delete", at, count: count2, sheetName: sheet });
5804
- }
5805
- };
5806
- _model = new WeakMap();
5807
- _registry = new WeakMap();
5808
- _formulas = new WeakMap();
5809
- _sheetNames = new WeakMap();
5810
- _spills = new WeakMap();
5811
- _spillOwner = new WeakMap();
5812
- _dependents = new WeakMap();
5813
- _volatiles = new WeakMap();
5814
- _sheetLikes = new WeakMap();
5815
- _workbookLike = new WeakMap();
5816
- _rangeCache = new WeakMap();
5817
- _RecalcEngine_instances = new WeakSet();
5818
- /**
5819
- * The single write path into the model. Writing a value invalidates the
5820
- * cached rectangles that contain the cell, so a formula evaluated later in the
5821
- * same pass cannot read a pre-write copy of a range. Coordinates are passed in
5822
- * where the caller already knows them, to avoid re-parsing the reference.
5823
- */
5824
- writeCell_fn = function(sheetName, ref, value2, row2, col) {
5825
- __privateGet(this, _model).setCellValue(sheetName, ref, value2);
5826
- if (row2 === void 0 || col === void 0) {
5827
- const parsed = parseCellRef(ref);
5828
- row2 = parsed.row;
5829
- col = parsed.column;
5830
- }
5831
- __privateGet(this, _rangeCache).invalidateCell(normSheet(sheetName), row2, col);
5832
- };
5833
- /** Registers an entry in the map, the reverse index and the volatile set. */
5834
- register_fn = function(entry) {
5835
- __privateGet(this, _formulas).set(entry.key, entry);
5836
- __privateGet(this, _dependents).add(entry.key, entry.refs);
5837
- if (entry.volatile) __privateGet(this, _volatiles).add(entry.key);
5838
- else __privateGet(this, _volatiles).delete(entry.key);
5839
- };
5840
- unregister_fn = function(key) {
5841
- __privateGet(this, _formulas).delete(key);
5842
- __privateGet(this, _dependents).remove(key);
5843
- __privateGet(this, _volatiles).delete(key);
5844
- };
5845
- rememberSheet_fn = function(name) {
5846
- __privateGet(this, _sheetNames).set(normSheet(name), name);
5847
- };
5848
- /**
5849
- * Takes ownership of the spill block a *file* already declared for an anchor.
5850
- *
5851
- * Excel stores a dynamic array as one formula on the anchor plus the spilled
5852
- * results written as plain cached values in the cells below/right, and records
5853
- * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5854
- * engine meets those cached values on the first recalculation, sees literal
5855
- * content inside the block it wants to spill into, and reports `#SPILL!` for
5856
- * every dynamic array in every real file. Registering the declared block as
5857
- * this anchor's existing spill makes them what they actually are — the anchor's
5858
- * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5859
- * result smaller than the declared block clears the leftovers.
5860
- *
5861
- * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5862
- * block far larger than any real spill (a corrupt or hand-written `ref`) is
5863
- * skipped rather than materialised.
5864
- */
5865
- adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
5866
- const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
5867
- if (!declared || !declared.includes(":")) return;
5868
- let rect;
5869
- try {
5870
- rect = parseRangeRef(declared);
5871
- } catch {
5872
- return;
5873
- }
5874
- if (rect.startRow !== row2 || rect.startColumn !== col) return;
5875
- const height = rect.endRow - rect.startRow + 1;
5876
- const width = rect.endColumn - rect.startColumn + 1;
5877
- if (height < 1 || width < 1) return;
5878
- if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5879
- if (height === 1 && width === 1) return;
5880
- const members = /* @__PURE__ */ new Set();
5881
- for (let r = rect.startRow; r <= rect.endRow; r++) {
5882
- for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5883
- if (r === row2 && c === col) continue;
5884
- const key = keyOf(s, cellRefFromRowCol(r, c));
5885
- if (__privateGet(this, _formulas).has(key)) continue;
5886
- if (__privateGet(this, _spillOwner).has(key)) continue;
5887
- members.add(key);
5888
- __privateGet(this, _spillOwner).set(key, anchorKey);
5889
- }
5890
- }
5891
- if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
5892
- };
5893
- /**
5894
- * One pass in a caller-supplied order, watching for inversions.
5895
- *
5896
- * The check is the mirror image of the topological sort: instead of asking
5897
- * "which of my precedents come first?", it asks, of each formula just
5898
- * evaluated, "has anything that reads me already run?". The dependency index
5899
- * answers that directly, and one positive answer is enough to know the order
5900
- * cannot be trusted.
5901
- */
5902
- recalcInOrder_fn = function(order) {
5903
- const results = [];
5904
- const changed = [];
5905
- const done = /* @__PURE__ */ new Set();
5906
- let spilled = false;
5907
- for (const { sheet, ref } of order) {
5908
- const key = keyOf(sheet, ref);
5909
- if (done.has(key)) continue;
5910
- const entry = __privateGet(this, _formulas).get(key);
5911
- if (!entry) continue;
5912
- done.add(key);
5913
- const written = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, entry);
5914
- results.push(written.result);
5915
- changed.push(...written.changed);
5916
- if (written.result.spill) spilled = true;
5917
- let conflict;
5918
- __privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
5919
- if (conflict || dep.key === entry.key) return;
5920
- if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
5921
- });
5922
- if (conflict) return { results: [], conflict };
5923
- }
5924
- const rest = [...__privateGet(this, _formulas).values()].filter((e) => !done.has(e.key));
5925
- if (rest.length > 0) {
5926
- const pass = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, rest);
5927
- results.push(...pass.results);
5928
- changed.push(...pass.changed);
5929
- }
5930
- if (spilled) {
5931
- const evaluated = new Set(done);
5932
- results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated), evaluated));
5933
- }
5934
- return { results };
5935
- };
5936
- applyShift_fn = function(shift) {
5937
- const editedSheet = normSheet(shift.sheetName);
5938
- const isRowShift = shift.dim === "row";
5939
- const bandEnd = shift.at + shift.count - 1;
5940
- const rekeyed = [];
5941
- const needsRecalc = /* @__PURE__ */ new Set();
5942
- for (const e of __privateGet(this, _formulas).values()) {
5943
- let row2 = e.row;
5944
- let col = e.col;
5945
- if (e.sheet === editedSheet) {
5946
- const shifted = shiftIndex(isRowShift ? row2 : col, shift);
5947
- if (shifted === null) continue;
5948
- if (isRowShift) row2 = shifted;
5949
- else col = shifted;
5950
- }
5951
- 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));
5952
- const rewritten = shiftFormulaRefs(e.formula, shift, e.sheetName);
5953
- const moved = row2 !== e.row || col !== e.col;
5954
- const ref = moved ? cellRefFromRowCol(row2, col) : e.ref;
5955
- const key = moved ? keyOf(e.sheet, ref) : e.key;
5956
- rekeyed.push({
5957
- ...e,
5958
- ref,
5959
- key,
5960
- row: row2,
5961
- col,
5962
- formula: rewritten.changed ? rewritten.text : e.formula,
5963
- // A rewritten formula's cached AST no longer matches its text; it is
5964
- // re-parsed lazily, and only if the cell is actually evaluated again.
5965
- ast: rewritten.changed ? void 0 : e.ast,
5966
- refs: __privateMethod(this, _RecalcEngine_instances, shiftPrecedents_fn).call(this, e.refs, shift, editedSheet)
5967
- // Volatility is a property of which *functions* a formula calls, and
5968
- // rewriting references cannot change that.
5969
- });
5970
- if (touchedBand || rewritten.hasRefError) needsRecalc.add(key);
5971
- }
5972
- __privateSet(this, _formulas, /* @__PURE__ */ new Map());
5973
- __privateGet(this, _dependents).clear();
5974
- __privateGet(this, _volatiles).clear();
5975
- for (const e of rekeyed) __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, e);
5976
- __privateMethod(this, _RecalcEngine_instances, shiftSpillBookkeeping_fn).call(this, shift, editedSheet);
5977
- __privateGet(this, _rangeCache).clear();
5978
- const pending = /* @__PURE__ */ new Map();
5979
- for (const k of needsRecalc) {
5980
- const e = __privateGet(this, _formulas).get(k);
5981
- if (e) pending.set(k, e);
5982
- }
5983
- for (const e of __privateGet(this, _formulas).values()) {
5984
- if (e.volatile) pending.set(e.key, e);
5985
- }
5986
- return __privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, /* @__PURE__ */ new Set());
5987
- };
5988
- /**
5989
- * Translates precedent rectangles across a structural edit.
5990
- *
5991
- * This is arithmetic rather than a re-parse of the rewritten formula, and the
5992
- * two are equivalent by construction: `shiftFormulaRefs` derives the new
5993
- * reference text by calling `shiftSpan`/`shiftIndex` on exactly these numbers,
5994
- * so applying the same functions to the stored rectangle yields the same
5995
- * region. A rectangle that is entirely deleted disappears, matching the
5996
- * `#REF!` the text rewrite produces.
5997
- */
5998
- shiftPrecedents_fn = function(refs, shift, editedSheet) {
5999
- const out = [];
6000
- for (const r of refs) {
6001
- if (r.sheet !== editedSheet) {
6002
- out.push(r);
6003
- continue;
6004
- }
6005
- if (shift.dim === "row") {
6006
- const span = shiftSpan(r.startRow, r.endRow, shift);
6007
- if (!span) continue;
6008
- out.push({
6009
- sheet: r.sheet,
6010
- startRow: span.start,
6011
- endRow: Math.min(span.end, EXCEL_MAX_ROWS),
6012
- startCol: r.startCol,
6013
- endCol: r.endCol
6014
- });
6015
- } else {
6016
- const span = shiftSpan(r.startCol, r.endCol, shift);
6017
- if (!span) continue;
6018
- out.push({
6019
- sheet: r.sheet,
6020
- startRow: r.startRow,
6021
- endRow: r.endRow,
6022
- startCol: span.start,
6023
- 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.
6024
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);
6025
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());
6026
6003
  }
6027
- return out;
6028
- };
6029
- /** Translates spill ownership across a structural edit on `editedSheet`. */
6030
- shiftSpillBookkeeping_fn = function(shift, editedSheet) {
6031
- const shiftCellKey = (key) => {
6032
- const { sheet, row: row2, col } = parseKey(key);
6033
- if (sheet !== editedSheet) return key;
6034
- const r = shift.dim === "row" ? shiftIndex(row2, shift) : row2;
6035
- const c = shift.dim === "col" ? shiftIndex(col, shift) : col;
6036
- if (r === null || c === null) return null;
6037
- return keyOf(sheet, cellRefFromRowCol(r, c));
6038
- };
6039
- const newSpills = /* @__PURE__ */ new Map();
6040
- __privateGet(this, _spillOwner).clear();
6041
- for (const [anchor, members] of __privateGet(this, _spills)) {
6042
- const newAnchor = shiftCellKey(anchor);
6043
- if (newAnchor === null) {
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();
6044
6069
  for (const m of members) {
6045
6070
  const shifted = shiftCellKey(m);
6046
6071
  if (!shifted) continue;
6047
- const cell = parseKey(shifted);
6048
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
6072
+ newMembers.add(shifted);
6073
+ this.#spillOwner.set(shifted, newAnchor);
6049
6074
  }
6050
- continue;
6075
+ newSpills.set(newAnchor, newMembers);
6051
6076
  }
6052
- const newMembers = /* @__PURE__ */ new Set();
6053
- for (const m of members) {
6054
- const shifted = shiftCellKey(m);
6055
- if (!shifted) continue;
6056
- newMembers.add(shifted);
6057
- __privateGet(this, _spillOwner).set(shifted, newAnchor);
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);
6058
6120
  }
6059
- newSpills.set(newAnchor, newMembers);
6060
6121
  }
6061
- __privateSet(this, _spills, newSpills);
6062
- };
6063
- // ── Internals ────────────────────────────────────────────────────────────────
6064
- /**
6065
- * Recomputes formulas affected by the given changed cells, to a fixpoint.
6066
- *
6067
- * A single topological pass is not enough once dynamic arrays exist: a
6068
- * formula that spills changes cells the graph could not know about until the
6069
- * array was computed, and those cells may have their own dependents. So we
6070
- * evaluate a pass, collect every cell whose value actually changed (formula
6071
- * outputs plus spilled/cleared cells), then recompute the dependents of those
6072
- * cells, repeating until nothing new is affected (bounded for safety).
6073
- */
6074
- recalcFrom_fn = function(seeds, includeKey) {
6075
- const results = [];
6076
- const evaluated = /* @__PURE__ */ new Set();
6077
- let pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, seeds, evaluated);
6078
- __privateMethod(this, _RecalcEngine_instances, addVolatiles_fn).call(this, pending);
6079
- if (includeKey) {
6080
- const self = __privateGet(this, _formulas).get(includeKey);
6081
- if (self) pending.set(includeKey, self);
6082
- }
6083
- results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, evaluated));
6084
- return results;
6085
- };
6086
- /** Evaluates `pending` entries to a fixpoint, following spill-induced changes. */
6087
- runFixpoint_fn = function(pending, evaluated) {
6088
- const results = [];
6089
- let guard = 0;
6090
- const maxIterations = __privateGet(this, _formulas).size + 8;
6091
- while (pending.size > 0 && guard++ <= maxIterations) {
6092
- const { results: passResults, changed } = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...pending.values()]);
6093
- results.push(...passResults);
6094
- for (const k of pending.keys()) evaluated.add(k);
6095
- pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated);
6096
- }
6097
- return results;
6098
- };
6099
- /** Adds every volatile formula to a pending set. */
6100
- addVolatiles_fn = function(pending) {
6101
- for (const key of __privateGet(this, _volatiles)) {
6102
- const e = __privateGet(this, _formulas).get(key);
6103
- 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;
6104
6139
  }
6105
- };
6106
- /**
6107
- * Formula entries (not yet evaluated) whose precedents include one of the
6108
- * cells, transitively. The queue is walked with a head index — Array.shift()
6109
- * is O(n) per dequeue, which turns a long dependency chain quadratic.
6110
- */
6111
- dependentClosure_fn = function(cells, evaluated) {
6112
- const out = /* @__PURE__ */ new Map();
6113
- const queue = [...cells];
6114
- for (let head = 0; head < queue.length; head++) {
6115
- const cell = queue[head];
6116
- __privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, cell, (dep) => {
6117
- if (out.has(dep.key) || evaluated.has(dep.key)) return;
6118
- out.set(dep.key, dep);
6119
- queue.push({ sheet: dep.sheet, row: dep.row, col: dep.col });
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
+ }
6120
6155
  });
6121
6156
  }
6122
- return out;
6123
- };
6124
- /**
6125
- * Visits the formula entries whose precedents include the given cell, via the
6126
- * reverse index. The index over-approximates (it buckets rectangles into
6127
- * tiles), so each candidate is still confirmed against its real rectangles.
6128
- */
6129
- forEachDirectDependent_fn = function(cell, visit) {
6130
- __privateGet(this, _dependents).candidates(cell.sheet, cell.row, cell.col, (key) => {
6131
- const e = __privateGet(this, _formulas).get(key);
6132
- if (!e) return;
6133
- for (const ref of e.refs) {
6134
- if (refContains(ref, cell.sheet, cell.row, cell.col)) {
6135
- visit(e);
6136
- return;
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);
6137
6190
  }
6138
6191
  }
6139
- });
6140
- };
6141
- /**
6142
- * Topologically sorts the given entries and evaluates them, writing results
6143
- * (scalars or spilled arrays) back to the model. Returns the results and the
6144
- * flat list of cells whose value changed. Entries left in a cycle are #REF!.
6145
- */
6146
- evaluatePass_fn = function(entries) {
6147
- const inSet = /* @__PURE__ */ new Map();
6148
- for (const e of entries) inSet.set(e.key, e);
6149
- const indegree = /* @__PURE__ */ new Map();
6150
- const dependents = /* @__PURE__ */ new Map();
6151
- for (const k of inSet.keys()) {
6152
- indegree.set(k, 0);
6153
- dependents.set(k, []);
6154
- }
6155
- for (const pe of entries) {
6156
- const seen = /* @__PURE__ */ new Set();
6157
- __privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: pe.sheet, row: pe.row, col: pe.col }, (dep) => {
6158
- if (dep.key === pe.key || !inSet.has(dep.key) || seen.has(dep.key)) return;
6159
- seen.add(dep.key);
6160
- dependents.get(pe.key).push(dep.key);
6161
- indegree.set(dep.key, indegree.get(dep.key) + 1);
6162
- });
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 };
6163
6210
  }
6164
- const ready = [];
6165
- for (const [k, d] of indegree) if (d === 0) ready.push(k);
6166
- const order = [];
6167
- for (let head = 0; head < ready.length; head++) {
6168
- const k = ready[head];
6169
- order.push(k);
6170
- for (const d of dependents.get(k)) {
6171
- const left2 = indegree.get(d) - 1;
6172
- indegree.set(d, left2);
6173
- if (left2 === 0) ready.push(d);
6174
- }
6175
- }
6176
- const results = [];
6177
- const changed = [];
6178
- const emitted = new Set(order);
6179
- for (const k of order) {
6180
- const e = inSet.get(k);
6181
- const { result, changed: c } = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, e);
6182
- results.push(result);
6183
- changed.push(...c);
6184
- }
6185
- for (const [k, e] of inSet) {
6186
- if (emitted.has(k)) continue;
6187
- __privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, e.key, changed);
6188
- const value2 = FormulaValue.errorRef.toObject();
6189
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
6190
- changed.push({ sheet: e.sheet, row: e.row, col: e.col });
6191
- results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
6192
- }
6193
- return { results, changed };
6194
- };
6195
- /**
6196
- * Evaluates one formula and writes its result. Scalars go to the anchor cell.
6197
- * Arrays spill across a block anchored at the formula cell: on a collision
6198
- * with existing content the anchor becomes #SPILL! and nothing spills; when
6199
- * an array shrinks, previously-spilled cells it no longer covers are cleared.
6200
- */
6201
- writeResult_fn = function(e) {
6202
- const anchorKey = e.key;
6203
- const fv2 = __privateMethod(this, _RecalcEngine_instances, evaluateFormulaValue_fn).call(this, e);
6204
- const changed = [];
6205
- const prevSpill = __privateGet(this, _spills).get(anchorKey) ?? /* @__PURE__ */ new Set();
6206
- if (!fv2.isArray) {
6207
- __privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
6208
- __privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
6209
- const value2 = fv2.toObject();
6210
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
6211
- changed.push({ sheet: e.sheet, row: e.row, col: e.col });
6212
- return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
6213
- }
6214
- const arr = fv2.arrayVal;
6215
- const rows2 = arr.rows, cols = arr.columns;
6216
- let collides = false;
6217
- for (let dr = 0; dr < rows2 && !collides; dr++) {
6218
- for (let dc = 0; dc < cols && !collides; dc++) {
6219
- if (dr === 0 && dc === 0) continue;
6220
- const r = e.row + dr, c = e.col + dc;
6221
- const cellKey2 = keyOf(e.sheet, cellRefFromRowCol(r, c));
6222
- if (__privateMethod(this, _RecalcEngine_instances, isOccupied_fn).call(this, cellKey2, e.sheetName, cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
6223
- 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
+ }
6224
6241
  }
6225
6242
  }
6226
- }
6227
- if (collides) {
6228
- __privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
6229
- __privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
6230
- const value2 = FormulaValue.error(9 /* Spill */).toObject();
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 newSpill = /* @__PURE__ */ new Set();
6236
- for (let dr = 0; dr < rows2; dr++) {
6237
- for (let dc = 0; dc < cols; dc++) {
6238
- const r = e.row + dr, c = e.col + dc;
6239
- const ref = cellRefFromRowCol(r, c);
6240
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, ref, arr.get(dr, dc).toObject(), r, c);
6241
- changed.push({ sheet: e.sheet, row: r, col: c });
6242
- if (dr !== 0 || dc !== 0) {
6243
- const cellKey2 = keyOf(e.sheet, ref);
6244
- newSpill.add(cellKey2);
6245
- __privateGet(this, _spillOwner).set(cellKey2, anchorKey);
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
+ }
6246
6263
  }
6247
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
+ };
6248
6279
  }
6249
- for (const oldKey of prevSpill) {
6250
- if (newSpill.has(oldKey)) continue;
6251
- const cell = parseKey(oldKey);
6252
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
6253
- __privateGet(this, _spillOwner).delete(oldKey);
6254
- changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
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
+ });
6255
6315
  }
6256
- __privateGet(this, _spills).set(anchorKey, newSpill);
6257
- const spillRef = `${e.ref}:${cellRefFromRowCol(e.row + rows2 - 1, e.col + cols - 1)}`;
6258
- __privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, spillRef);
6259
- return {
6260
- result: { sheet: e.sheetName, ref: e.ref, value: arr.get(0, 0).toObject(), spill: { rows: rows2, cols } },
6261
- changed
6262
- };
6263
- };
6264
- /** Whether a target cell already holds content that blocks a spill. */
6265
- isOccupied_fn = function(cellKey2, sheetName, ref, anchorKey, prevSpill) {
6266
- if (__privateGet(this, _formulas).has(cellKey2) && cellKey2 !== anchorKey) return true;
6267
- const owner = __privateGet(this, _spillOwner).get(cellKey2);
6268
- if (owner && owner !== anchorKey) return true;
6269
- if (prevSpill.has(cellKey2)) return false;
6270
- const v = __privateGet(this, _model).getCellValue(sheetName, ref);
6271
- return v !== null && v !== void 0 && v !== "";
6272
- };
6273
- /** Clears an anchor's spilled cells (not the anchor itself) and forgets them. */
6274
- clearSpill_fn = function(anchorKey, changed) {
6275
- const spill = __privateGet(this, _spills).get(anchorKey);
6276
- if (!spill) return;
6277
- for (const cellKey2 of spill) {
6278
- const cell = parseKey(cellKey2);
6279
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
6280
- __privateGet(this, _spillOwner).delete(cellKey2);
6281
- changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
6282
- }
6283
- __privateGet(this, _spills).delete(anchorKey);
6284
- };
6285
- evaluateFormulaValue_fn = function(e) {
6286
- if (!e.ast) {
6287
- try {
6288
- e.ast = parseFormulaUncached(e.formula);
6289
- } catch {
6290
- 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);
6291
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
+ };
6292
6355
  }
6293
- return evaluateAst(e.ast, __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName), {
6294
- workbook: __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this),
6295
- formulaRow: e.row,
6296
- formulaCol: e.col,
6297
- rangeCache: __privateGet(this, _rangeCache)
6298
- });
6299
- };
6300
- /**
6301
- * Per-sheet evaluator adapter. Memoised: these hold no per-formula state, and
6302
- * rebuilding one (with its four closures) for every formula evaluated made a
6303
- * full recalc allocate several objects per cell.
6304
- */
6305
- sheetLike_fn = function(sheetName) {
6306
- let cached = __privateGet(this, _sheetLikes).get(sheetName);
6307
- if (!cached) {
6308
- cached = __privateMethod(this, _RecalcEngine_instances, buildSheetLike_fn).call(this, sheetName);
6309
- __privateGet(this, _sheetLikes).set(sheetName, cached);
6310
- }
6311
- return cached;
6312
- };
6313
- buildSheetLike_fn = function(sheetName) {
6314
- const model = __privateGet(this, _model);
6315
- return {
6316
- name: sheetName,
6317
- getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
6318
- getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
6319
- // A getter, not a snapshot: the extent grows as formulas write results,
6320
- // and a whole-column reference read later in the same pass must see it.
6321
- get usedBounds() {
6322
- return model.getUsedBounds?.(sheetName);
6323
- },
6324
- isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
6325
- getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
6326
- getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
6327
- };
6328
- };
6329
- buildWorkbookLike_fn = function() {
6330
- if (__privateGet(this, _workbookLike)) return __privateGet(this, _workbookLike);
6331
- const self = this;
6332
- return __privateSet(this, _workbookLike, {
6333
- get worksheets() {
6334
- return [...__privateGet(self, _sheetNames).values()].map((n) => {
6335
- var _a;
6336
- return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, n);
6337
- });
6338
- },
6339
- getWorksheet: (name) => {
6340
- var _a;
6341
- return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, name);
6342
- },
6343
- getDefinedName: __privateGet(this, _model).getDefinedName ? (name, sheet) => __privateGet(this, _model).getDefinedName(name, sheet) : void 0
6344
- });
6345
6356
  };
6346
6357
 
6347
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 };