@devmm/puredocs-excel-formula 1.1.2 → 1.1.4

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));
1185
- }
1186
- if (__privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
1187
- __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1188
- return new UnaryOpNode(1 /* Plus */, __privateMethod(this, _FormulaParser_instances, parsePower_fn).call(this));
1123
+ // ── Token helpers ──────────────────────────────────────────────────────────
1124
+ get #current() {
1125
+ return this.#tokens[this.#pos] ?? { type: 31 /* EOF */, value: "", position: -1 };
1189
1126
  }
1190
- 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));
1127
+ #advance() {
1128
+ const t = this.#current;
1129
+ this.#pos++;
1130
+ return t;
1193
1131
  }
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);
1229
1233
  }
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);
1234
+ case 1 /* String */:
1235
+ this.#advance();
1236
+ return new StringNode(token.value);
1237
+ case 2 /* Boolean */:
1238
+ this.#advance();
1239
+ return new BooleanNode(token.value === "TRUE");
1240
+ case 30 /* ErrorLiteral */:
1241
+ this.#advance();
1242
+ return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
1243
+ case 3 /* CellReference */: {
1244
+ this.#advance();
1245
+ if (this.#current.type === 4 /* Colon */) {
1246
+ this.#advance();
1247
+ const end = this.#expect(3 /* CellReference */);
1248
+ return new RangeReferenceNode(token.value, end.value);
1249
+ }
1250
+ return new CellReferenceNode(token.value);
1249
1251
  }
1250
- 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);
1252
+ case 22 /* SheetReference */: {
1253
+ this.#advance();
1254
+ const parts = token.value.split("!");
1255
+ const sheetName = parts[0];
1256
+ const cellRef = parts[1] ?? "";
1257
+ if (this.#current.type === 4 /* Colon */) {
1258
+ const full = this.#tryParseFullRange(cellRef, sheetName);
1259
+ if (full) return full;
1260
+ this.#advance();
1261
+ const end = this.#expect(3 /* CellReference */);
1262
+ return new SheetRangeReferenceNode(sheetName, cellRef, end.value);
1263
+ }
1264
+ return new SheetCellReferenceNode(sheetName, cellRef);
1263
1265
  }
1264
- return new SheetCellReferenceNode(sheetName, cellRef);
1266
+ case 23 /* NamedRange */: {
1267
+ this.#advance();
1268
+ if (this.#current.type === 4 /* Colon */) {
1269
+ const full = this.#tryParseFullRange(token.value);
1270
+ if (full) return full;
1271
+ }
1272
+ return new NamedRangeNode(token.value);
1273
+ }
1274
+ case 24 /* SpillReference */:
1275
+ this.#advance();
1276
+ return new SpillReferenceNode(token.value);
1277
+ case 5 /* Function */:
1278
+ return this.#parseFunction();
1279
+ case 27 /* LeftBrace */:
1280
+ return this.#parseArrayLiteral();
1281
+ case 6 /* LeftParen */: {
1282
+ this.#advance();
1283
+ const expr = this.#parseComparison();
1284
+ this.#expect(7 /* RightParen */);
1285
+ return expr;
1286
+ }
1287
+ default:
1288
+ throw new FormulaException(
1289
+ `Unexpected token '${token.value}' (type ${token.type}) at position ${token.position}.`
1290
+ );
1265
1291
  }
1266
- 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, "");
@@ -2840,8 +2841,9 @@ function vlookup(a, c) {
2840
2841
  const tbl = tableVal.arrayVal;
2841
2842
  if (colIdx < 0 || colIdx >= tbl.columns) return FormulaValue.errorRef;
2842
2843
  if (exactMatch) {
2844
+ const pattern = lookupPattern(lookupVal);
2843
2845
  for (let r = 0; r < tbl.rows; r++) {
2844
- if (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);
2845
2847
  }
2846
2848
  return FormulaValue.errorNA;
2847
2849
  }
@@ -2870,8 +2872,9 @@ function hlookup(a, c) {
2870
2872
  const tbl = tableVal.arrayVal;
2871
2873
  if (rowIdx < 0 || rowIdx >= tbl.rows) return FormulaValue.errorRef;
2872
2874
  if (exactMatch) {
2875
+ const pattern = lookupPattern(lookupVal);
2873
2876
  for (let col = 0; col < tbl.columns; col++) {
2874
- if (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);
2875
2878
  }
2876
2879
  return FormulaValue.errorNA;
2877
2880
  }
@@ -2882,18 +2885,13 @@ function hlookup(a, c) {
2882
2885
  }
2883
2886
  return best >= 0 ? tbl.get(rowIdx, best) : FormulaValue.errorNA;
2884
2887
  }
2885
- function wildcardRegex(pattern) {
2886
- let out = "";
2887
- for (let i = 0; i < pattern.length; i++) {
2888
- const ch = pattern[i];
2889
- if (ch === "~" && i + 1 < pattern.length) {
2890
- const next = pattern[++i];
2891
- out += next.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2892
- } else if (ch === "*") out += ".*";
2893
- else if (ch === "?") out += ".";
2894
- else out += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2895
- }
2896
- return new RegExp(`^${out}$`, "i");
2888
+ function lookupPattern(lookupVal) {
2889
+ if (!lookupVal.isText) return null;
2890
+ const text = lookupVal.textValue;
2891
+ return FormulaHelper.hasWildcardSyntax(text) ? FormulaHelper.wildcardToRegex(text) : null;
2892
+ }
2893
+ function matchesLookup(candidate, lookupVal, pattern) {
2894
+ return pattern === null ? FormulaValue.areEqual(candidate, lookupVal) : !candidate.isBlank && pattern.test(candidate.asText());
2897
2895
  }
2898
2896
  function findMatch(n, get, lookupVal, matchMode, searchMode) {
2899
2897
  if ((searchMode === 2 || searchMode === -2) && matchMode !== 2) {
@@ -2917,8 +2915,11 @@ function findMatch(n, get, lookupVal, matchMode, searchMode) {
2917
2915
  const reverse = searchMode === -1;
2918
2916
  const order = reverse ? Array.from({ length: n }, (_, k) => n - 1 - k) : Array.from({ length: n }, (_, k) => k);
2919
2917
  if (matchMode === 2) {
2920
- const re = wildcardRegex(lookupVal.asText());
2921
- 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
+ }
2922
2923
  return -1;
2923
2924
  }
2924
2925
  if (matchMode === 0) {
@@ -3028,13 +3029,15 @@ function match(a, c) {
3028
3029
  matchType = Math.round(r.n);
3029
3030
  }
3030
3031
  if (!arrVal.isArray) {
3031
- 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;
3032
3034
  }
3033
3035
  const arr = arrVal.arrayVal;
3034
3036
  const len2 = arr.length;
3035
3037
  if (matchType === 0) {
3038
+ const pattern = lookupPattern(lookupVal);
3036
3039
  for (let i = 0; i < len2; i++) {
3037
- if (FormulaValue.areEqual(arr.getFlat(i), lookupVal)) return FormulaValue.number(i + 1);
3040
+ if (matchesLookup(arr.getFlat(i), lookupVal, pattern)) return FormulaValue.number(i + 1);
3038
3041
  }
3039
3042
  return FormulaValue.errorNA;
3040
3043
  }
@@ -4553,24 +4556,20 @@ function toRow(a, c) {
4553
4556
 
4554
4557
  // src/function-registry.ts
4555
4558
  var MAX_LIFTED_CELLS = 1048576;
4556
- var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
4557
- var _FunctionRegistry = class _FunctionRegistry {
4558
- constructor() {
4559
- __privateAdd(this, _FunctionRegistry_instances);
4560
- __privateAdd(this, _functions, /* @__PURE__ */ new Map());
4561
- }
4559
+ var FunctionRegistry = class _FunctionRegistry {
4560
+ static #default;
4562
4561
  /** Lazily-created default registry with all built-in functions registered. */
4563
4562
  static get default() {
4564
- var _a;
4565
- if (!__privateGet(_FunctionRegistry, _default)) {
4566
- __privateSet(_FunctionRegistry, _default, new _FunctionRegistry());
4567
- __privateMethod(_a = __privateGet(_FunctionRegistry, _default), _FunctionRegistry_instances, registerBuiltIns_fn).call(_a);
4563
+ if (!_FunctionRegistry.#default) {
4564
+ _FunctionRegistry.#default = new _FunctionRegistry();
4565
+ _FunctionRegistry.#default.#registerBuiltIns();
4568
4566
  }
4569
- return __privateGet(_FunctionRegistry, _default);
4567
+ return _FunctionRegistry.#default;
4570
4568
  }
4569
+ #functions = /* @__PURE__ */ new Map();
4571
4570
  /** Registers a function. Name is normalised to UPPERCASE. */
4572
4571
  register(name, handler, minArgs = 0, maxArgs = -1, isVolatile = false) {
4573
- __privateGet(this, _functions).set(name.toUpperCase(), {
4572
+ this.#functions.set(name.toUpperCase(), {
4574
4573
  name: name.toUpperCase(),
4575
4574
  minArgs,
4576
4575
  maxArgs,
@@ -4596,7 +4595,7 @@ var _FunctionRegistry = class _FunctionRegistry {
4596
4595
  * `register`, or lifting would map it over the array it is meant to reduce.
4597
4596
  */
4598
4597
  registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
4599
- __privateGet(this, _functions).set(name.toUpperCase(), {
4598
+ this.#functions.set(name.toUpperCase(), {
4600
4599
  name: name.toUpperCase(),
4601
4600
  minArgs,
4602
4601
  maxArgs,
@@ -4607,81 +4606,84 @@ var _FunctionRegistry = class _FunctionRegistry {
4607
4606
  }
4608
4607
  /** Executes a function by name with argument validation. */
4609
4608
  execute(name, args, ctx) {
4610
- var _a;
4611
- const def = __privateGet(this, _functions).get(name.toUpperCase());
4609
+ const def = this.#functions.get(name.toUpperCase());
4612
4610
  if (!def) return FormulaValue.errorName;
4613
4611
  if (args.length < def.minArgs) return FormulaValue.errorValue;
4614
4612
  if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
4615
4613
  try {
4616
- return def.elementwise ? __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);
4617
4615
  } catch {
4618
4616
  return FormulaValue.errorValue;
4619
4617
  }
4620
4618
  }
4619
+ /**
4620
+ * Runs a scalar handler, mapping it over any array arguments.
4621
+ *
4622
+ * Arguments are evaluated exactly once and the handler is re-driven through
4623
+ * {@link ValueNode}s, so a nested call in an argument is not recomputed per
4624
+ * output cell — the alternative (letting the handler re-evaluate the original
4625
+ * nodes) would multiply the cost of `LEFT(expensive(), 3)` by the array size.
4626
+ * The all-scalar case — the overwhelming majority of calls — costs one array of
4627
+ * wrappers and takes the same path, so there is no second code path to keep in
4628
+ * step.
4629
+ */
4630
+ static #executeElementwise(def, args, ctx) {
4631
+ const values = args.map((a) => a.evaluate(ctx));
4632
+ let rows2 = 1, cols = 1;
4633
+ for (const v of values) {
4634
+ if (!v.isArray) continue;
4635
+ const a = v.arrayVal;
4636
+ if (a.rows > rows2) rows2 = a.rows;
4637
+ if (a.columns > cols) cols = a.columns;
4638
+ }
4639
+ if (rows2 === 1 && cols === 1) {
4640
+ return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4641
+ }
4642
+ if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4643
+ for (const v of values) {
4644
+ if (!v.isArray) continue;
4645
+ const a = v.arrayVal;
4646
+ if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4647
+ return FormulaValue.errorValue;
4648
+ }
4649
+ }
4650
+ const nodes = values.map((v) => new ValueNode(v));
4651
+ const out = new ArrayValue(rows2, cols);
4652
+ for (let r = 0; r < rows2; r++) {
4653
+ for (let c = 0; c < cols; c++) {
4654
+ for (let i = 0; i < values.length; i++) {
4655
+ const v = values[i];
4656
+ if (!v.isArray) continue;
4657
+ const a = v.arrayVal;
4658
+ nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4659
+ }
4660
+ out.set(r, c, def.handler(nodes, ctx));
4661
+ }
4662
+ }
4663
+ return FormulaValue.array(out);
4664
+ }
4621
4665
  /** Returns true if the named function is volatile (NOW, RAND, etc.). */
4622
4666
  isVolatile(name) {
4623
- return __privateGet(this, _functions).get(name.toUpperCase())?.isVolatile ?? false;
4667
+ return this.#functions.get(name.toUpperCase())?.isVolatile ?? false;
4624
4668
  }
4625
4669
  /** Returns true if the function is registered. */
4626
4670
  has(name) {
4627
- return __privateGet(this, _functions).has(name.toUpperCase());
4628
- }
4629
- };
4630
- _default = new WeakMap();
4631
- _functions = new WeakMap();
4632
- _FunctionRegistry_static = new WeakSet();
4633
- executeElementwise_fn = function(def, args, ctx) {
4634
- const values = args.map((a) => a.evaluate(ctx));
4635
- let rows2 = 1, cols = 1;
4636
- for (const v of values) {
4637
- if (!v.isArray) continue;
4638
- const a = v.arrayVal;
4639
- if (a.rows > rows2) rows2 = a.rows;
4640
- if (a.columns > cols) cols = a.columns;
4641
- }
4642
- if (rows2 === 1 && cols === 1) {
4643
- return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4671
+ return this.#functions.has(name.toUpperCase());
4672
+ }
4673
+ #registerBuiltIns() {
4674
+ registerMath(this);
4675
+ registerText(this);
4676
+ registerLogical(this);
4677
+ registerDate(this);
4678
+ registerLookup(this);
4679
+ registerStatistical(this);
4680
+ registerInfo(this);
4681
+ registerFinance(this);
4682
+ registerMeta(this);
4683
+ registerAggregate(this);
4684
+ registerArray(this);
4644
4685
  }
4645
- if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4646
- for (const v of values) {
4647
- if (!v.isArray) continue;
4648
- const a = v.arrayVal;
4649
- if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4650
- return FormulaValue.errorValue;
4651
- }
4652
- }
4653
- const nodes = values.map((v) => new ValueNode(v));
4654
- const out = new ArrayValue(rows2, cols);
4655
- for (let r = 0; r < rows2; r++) {
4656
- for (let c = 0; c < cols; c++) {
4657
- for (let i = 0; i < values.length; i++) {
4658
- const v = values[i];
4659
- if (!v.isArray) continue;
4660
- const a = v.arrayVal;
4661
- nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4662
- }
4663
- out.set(r, c, def.handler(nodes, ctx));
4664
- }
4665
- }
4666
- return FormulaValue.array(out);
4667
4686
  };
4668
- _FunctionRegistry_instances = new WeakSet();
4669
- registerBuiltIns_fn = function() {
4670
- registerMath(this);
4671
- registerText(this);
4672
- registerLogical(this);
4673
- registerDate(this);
4674
- registerLookup(this);
4675
- registerStatistical(this);
4676
- registerInfo(this);
4677
- registerFinance(this);
4678
- registerMeta(this);
4679
- registerAggregate(this);
4680
- registerArray(this);
4681
- };
4682
- __privateAdd(_FunctionRegistry, _FunctionRegistry_static);
4683
- __privateAdd(_FunctionRegistry, _default);
4684
- var FunctionRegistry = _FunctionRegistry;
4685
4687
  var FormulaHelper;
4686
4688
  ((FormulaHelper2) => {
4687
4689
  function evalDouble(node, ctx) {
@@ -4754,7 +4756,7 @@ var FormulaHelper;
4754
4756
  const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
4755
4757
  return rhsNum !== null ? { kind: "compareNumber", number: rhsNum, op, test } : { kind: "other", test };
4756
4758
  }
4757
- if (trimmed.includes("*") || trimmed.includes("?")) {
4759
+ if (hasWildcardSyntax(trimmed)) {
4758
4760
  const pattern = wildcardToRegex(trimmed);
4759
4761
  return { kind: "other", test: (v) => pattern.test(v.asText()) };
4760
4762
  }
@@ -4786,53 +4788,106 @@ var FormulaHelper;
4786
4788
  return compileCriteria(criteria).test(value2);
4787
4789
  }
4788
4790
  FormulaHelper2.matchesCriteria = matchesCriteria;
4789
- function wildcardToRegex(pattern) {
4790
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
4791
- 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;
4792
4797
  }
4798
+ FormulaHelper2.hasWildcardSyntax = hasWildcardSyntax;
4799
+ function wildcardToRegex(pattern) {
4800
+ let out = "";
4801
+ for (let i = 0; i < pattern.length; i++) {
4802
+ const ch = pattern[i];
4803
+ const next = pattern[i + 1];
4804
+ if (ch === "~" && (next === "*" || next === "?" || next === "~")) {
4805
+ out += escapeRegex(next);
4806
+ i++;
4807
+ } else if (ch === "*") out += ".*";
4808
+ else if (ch === "?") out += ".";
4809
+ else out += escapeRegex(ch);
4810
+ }
4811
+ return new RegExp(`^${out}$`, "i");
4812
+ }
4813
+ FormulaHelper2.wildcardToRegex = wildcardToRegex;
4814
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4793
4815
  })(FormulaHelper || (FormulaHelper = {}));
4794
4816
 
4795
4817
  // src/formula-context.ts
4796
- var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn, forSheet_fn;
4797
- var _FormulaContext = class _FormulaContext {
4798
- constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
4799
- __privateAdd(this, _FormulaContext_instances);
4800
- __privateAdd(this, _sheet);
4801
- __privateAdd(this, _functions2);
4802
- __privateAdd(this, _evaluating);
4803
- /**
4804
- * Lexical scope stack for LET/LAMBDA local names. Each frame maps an
4805
- * upper-cased local name to its value. Names resolve innermost-first and take
4806
- * precedence over workbook defined names.
4807
- */
4808
- __privateAdd(this, _scopes, []);
4809
- __privateSet(this, _sheet, sheet);
4810
- this.formulaRow = formulaRow;
4811
- this.formulaCol = formulaCol;
4812
- __privateSet(this, _evaluating, evaluating ?? /* @__PURE__ */ new Set());
4813
- __privateSet(this, _functions2, registry ?? FunctionRegistry.default);
4814
- }
4818
+ var FormulaContext = class _FormulaContext {
4819
+ #sheet;
4820
+ #functions;
4821
+ #evaluating;
4822
+ /** Row of the formula cell (for @ implicit intersection). */
4823
+ formulaRow;
4824
+ /** Column of the formula cell (for @ implicit intersection). */
4825
+ formulaCol;
4826
+ /** Optional workbook reference for cross-sheet references. */
4827
+ workbook;
4828
+ /**
4829
+ * Optional memo for range reads, supplied by whoever owns the write side —
4830
+ * the recalculation engine, which invalidates it as it writes results. Left
4831
+ * undefined for one-shot evaluation, where a stale entry could otherwise
4832
+ * outlive a caller's direct edit to the model.
4833
+ */
4834
+ rangeCache;
4835
+ /**
4836
+ * Optional named-range resolver, installed by the evaluator when the workbook
4837
+ * exposes getDefinedName. Given a name, returns its evaluated value. Left
4838
+ * undefined when no resolver is available, in which case named ranges are
4839
+ * #NAME? (unchanged legacy behaviour).
4840
+ */
4841
+ nameResolver;
4842
+ /**
4843
+ * Shared recursion budget for LAMBDA calls, threaded across all contexts in a
4844
+ * single evaluation. Guards non-tail recursion (which grows the JS stack);
4845
+ * tail recursion is trampolined and does not count against it.
4846
+ */
4847
+ recursionGuard;
4848
+ /**
4849
+ * Lexical scope stack for LET/LAMBDA local names. Each frame maps an
4850
+ * upper-cased local name to its value. Names resolve innermost-first and take
4851
+ * precedence over workbook defined names.
4852
+ */
4853
+ #scopes = [];
4815
4854
  /** Pushes a new binding frame (used by LET/LAMBDA). */
4816
4855
  pushScope(bindings) {
4817
- __privateGet(this, _scopes).push(bindings);
4856
+ this.#scopes.push(bindings);
4818
4857
  }
4819
4858
  /** Pops the innermost binding frame. */
4820
4859
  popScope() {
4821
- __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;
4822
4877
  }
4823
4878
  get worksheet() {
4824
- return __privateGet(this, _sheet);
4879
+ return this.#sheet;
4825
4880
  }
4826
4881
  // ── Cell/Range resolution ─────────────────────────────────────────────────
4827
4882
  getCellValue(cellReference) {
4828
4883
  const norm = cellReference.replace(/\$/g, "").toUpperCase();
4829
- if (__privateGet(this, _evaluating).has(norm)) return FormulaValue.errorRef;
4830
- __privateGet(this, _evaluating).add(norm);
4884
+ if (this.#evaluating.has(norm)) return FormulaValue.errorRef;
4885
+ this.#evaluating.add(norm);
4831
4886
  try {
4832
- const raw = __privateGet(this, _sheet).getCellValue(cellReference);
4887
+ const raw = this.#sheet.getCellValue(cellReference);
4833
4888
  return FormulaValue.fromObjectSync(raw);
4834
4889
  } finally {
4835
- __privateGet(this, _evaluating).delete(norm);
4890
+ this.#evaluating.delete(norm);
4836
4891
  }
4837
4892
  }
4838
4893
  getRangeValues(startRef, endRef) {
@@ -4850,17 +4905,17 @@ var _FormulaContext = class _FormulaContext {
4850
4905
  const rows2 = endRow - startRow + 1;
4851
4906
  const cols = endCol - startCol + 1;
4852
4907
  const cache = rows2 * cols > 1 ? this.rangeCache : void 0;
4853
- const sheetKey = cache ? __privateGet(this, _sheet).name.toUpperCase() : "";
4908
+ const sheetKey = cache ? this.#sheet.name.toUpperCase() : "";
4854
4909
  if (cache) {
4855
4910
  const hit = cache.get(sheetKey, startRow, startCol, endRow, endCol);
4856
4911
  if (hit !== void 0) return hit;
4857
4912
  }
4858
4913
  const arr = new ArrayValue(rows2, cols);
4859
- const at = __privateGet(this, _sheet).getCellValueAt;
4914
+ const at = this.#sheet.getCellValueAt;
4860
4915
  if (at) {
4861
4916
  for (let r = 0; r < rows2; r++) {
4862
4917
  for (let c = 0; c < cols; c++) {
4863
- arr.set(r, c, FormulaValue.fromObjectSync(at.call(__privateGet(this, _sheet), startRow + r, startCol + c)));
4918
+ arr.set(r, c, FormulaValue.fromObjectSync(at.call(this.#sheet, startRow + r, startCol + c)));
4864
4919
  }
4865
4920
  }
4866
4921
  } else {
@@ -4886,7 +4941,7 @@ var _FormulaContext = class _FormulaContext {
4886
4941
  * silently reading nothing would make SUM(A:A) return 0 on a full column.
4887
4942
  */
4888
4943
  getFullRangeValues(startCol, endCol, startRow, endRow) {
4889
- const bounds = __privateGet(this, _sheet).usedBounds;
4944
+ const bounds = this.#sheet.usedBounds;
4890
4945
  if (!bounds) return FormulaValue.errorRef;
4891
4946
  if (bounds.rowCount === 0 || bounds.columnCount === 0) return FormulaValue.blank;
4892
4947
  const r0 = startRow ?? 1;
@@ -4901,7 +4956,7 @@ var _FormulaContext = class _FormulaContext {
4901
4956
  getSheetCellValue(sheetName, cellReference) {
4902
4957
  if (!this.workbook) return FormulaValue.errorRef;
4903
4958
  try {
4904
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getCellValue(cellReference);
4959
+ return this.#forSheet(sheetName).getCellValue(cellReference);
4905
4960
  } catch {
4906
4961
  return FormulaValue.errorRef;
4907
4962
  }
@@ -4909,7 +4964,7 @@ var _FormulaContext = class _FormulaContext {
4909
4964
  getSheetRangeValues(sheetName, startRef, endRef) {
4910
4965
  if (!this.workbook) return FormulaValue.errorRef;
4911
4966
  try {
4912
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getRangeValues(startRef, endRef);
4967
+ return this.#forSheet(sheetName).getRangeValues(startRef, endRef);
4913
4968
  } catch {
4914
4969
  return FormulaValue.errorRef;
4915
4970
  }
@@ -4918,13 +4973,28 @@ var _FormulaContext = class _FormulaContext {
4918
4973
  getSheetFullRangeValues(sheetName, startCol, endCol, startRow, endRow) {
4919
4974
  if (!this.workbook) return FormulaValue.errorRef;
4920
4975
  try {
4921
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
4976
+ return this.#forSheet(sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
4922
4977
  } catch {
4923
4978
  return FormulaValue.errorRef;
4924
4979
  }
4925
4980
  }
4981
+ /**
4982
+ * A sibling context bound to another sheet, sharing this one's circular-
4983
+ * reference guard and range memo. These contexts only read values — they
4984
+ * never evaluate an AST — so the name resolver and recursion budget are
4985
+ * deliberately not carried over; passing them would suggest a scoping
4986
+ * relationship that does not exist. The memo, on the other hand, must be
4987
+ * carried: cross-sheet ranges are the most expensive reads in a workbook.
4988
+ */
4989
+ #forSheet(sheetName) {
4990
+ const sheet = this.workbook.getWorksheet(sheetName);
4991
+ const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, this.#evaluating, this.#functions);
4992
+ ctx.workbook = this.workbook;
4993
+ ctx.rangeCache = this.rangeCache;
4994
+ return ctx;
4995
+ }
4926
4996
  resolveNamedRange(name) {
4927
- const scoped = __privateMethod(this, _FormulaContext_instances, lookupScope_fn).call(this, name);
4997
+ const scoped = this.#lookupScope(name);
4928
4998
  if (scoped !== void 0) return scoped;
4929
4999
  return this.nameResolver ? this.nameResolver(name) : FormulaValue.errorName;
4930
5000
  }
@@ -4933,7 +5003,7 @@ var _FormulaContext = class _FormulaContext {
4933
5003
  * cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
4934
5004
  */
4935
5005
  resolveSpillRange(anchorRef) {
4936
- const spillRef = __privateGet(this, _sheet).getSpillRange?.(anchorRef);
5006
+ const spillRef = this.#sheet.getSpillRange?.(anchorRef);
4937
5007
  if (!spillRef) return FormulaValue.errorRef;
4938
5008
  const colon = spillRef.indexOf(":");
4939
5009
  if (colon < 0) return this.getCellValue(spillRef);
@@ -4943,7 +5013,7 @@ var _FormulaContext = class _FormulaContext {
4943
5013
  resolveSheetSpillRange(sheetName, anchorRef) {
4944
5014
  if (!this.workbook) return FormulaValue.errorRef;
4945
5015
  try {
4946
- return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).resolveSpillRange(anchorRef);
5016
+ return this.#forSheet(sheetName).resolveSpillRange(anchorRef);
4947
5017
  } catch {
4948
5018
  return FormulaValue.errorRef;
4949
5019
  }
@@ -4956,94 +5026,63 @@ var _FormulaContext = class _FormulaContext {
4956
5026
  // ── Function dispatch ─────────────────────────────────────────────────────
4957
5027
  /** True if a built-in function with this name is registered. */
4958
5028
  hasFunction(name) {
4959
- return __privateGet(this, _functions2).has(name);
5029
+ return this.#functions.has(name);
4960
5030
  }
4961
5031
  evaluateFunction(name, args) {
4962
- if (!__privateGet(this, _functions2).has(name)) {
5032
+ if (!this.#functions.has(name)) {
4963
5033
  const named = this.resolveNamedRange(name);
4964
5034
  if (named.isLambda) {
4965
5035
  return named.lambdaVal.call(args.map((a) => a.evaluate(this)));
4966
5036
  }
4967
5037
  }
4968
- return __privateGet(this, _functions2).execute(name, args, this);
5038
+ return this.#functions.execute(name, args, this);
4969
5039
  }
4970
5040
  };
4971
- _sheet = new WeakMap();
4972
- _functions2 = new WeakMap();
4973
- _evaluating = new WeakMap();
4974
- _scopes = new WeakMap();
4975
- _FormulaContext_instances = new WeakSet();
4976
- lookupScope_fn = function(name) {
4977
- if (__privateGet(this, _scopes).length === 0) return void 0;
4978
- const key = name.toUpperCase();
4979
- for (let i = __privateGet(this, _scopes).length - 1; i >= 0; i--) {
4980
- const v = __privateGet(this, _scopes)[i].get(key);
4981
- if (v !== void 0) return v;
4982
- }
4983
- return void 0;
4984
- };
4985
- /**
4986
- * A sibling context bound to another sheet, sharing this one's circular-
4987
- * reference guard and range memo. These contexts only read values — they
4988
- * never evaluate an AST — so the name resolver and recursion budget are
4989
- * deliberately not carried over; passing them would suggest a scoping
4990
- * relationship that does not exist. The memo, on the other hand, must be
4991
- * carried: cross-sheet ranges are the most expensive reads in a workbook.
4992
- */
4993
- forSheet_fn = function(sheetName) {
4994
- const sheet = this.workbook.getWorksheet(sheetName);
4995
- const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, __privateGet(this, _evaluating), __privateGet(this, _functions2));
4996
- ctx.workbook = this.workbook;
4997
- ctx.rangeCache = this.rangeCache;
4998
- return ctx;
4999
- };
5000
- var FormulaContext = _FormulaContext;
5001
5041
 
5002
5042
  // src/lru-cache.ts
5003
- var _map, _capacity, _hits, _misses, _evictions;
5004
5043
  var LruCache = class {
5044
+ #map;
5045
+ #capacity;
5046
+ #hits = 0;
5047
+ #misses = 0;
5048
+ #evictions = 0;
5005
5049
  constructor(capacity) {
5006
- __privateAdd(this, _map);
5007
- __privateAdd(this, _capacity);
5008
- __privateAdd(this, _hits, 0);
5009
- __privateAdd(this, _misses, 0);
5010
- __privateAdd(this, _evictions, 0);
5011
5050
  if (capacity <= 0) throw new RangeError("LruCache capacity must be > 0.");
5012
- __privateSet(this, _capacity, capacity);
5013
- __privateSet(this, _map, /* @__PURE__ */ new Map());
5051
+ this.#capacity = capacity;
5052
+ this.#map = /* @__PURE__ */ new Map();
5014
5053
  }
5015
5054
  get capacity() {
5016
- return __privateGet(this, _capacity);
5055
+ return this.#capacity;
5017
5056
  }
5018
5057
  get size() {
5019
- return __privateGet(this, _map).size;
5058
+ return this.#map.size;
5020
5059
  }
5021
5060
  get hits() {
5022
- return __privateGet(this, _hits);
5061
+ return this.#hits;
5023
5062
  }
5024
5063
  get misses() {
5025
- return __privateGet(this, _misses);
5064
+ return this.#misses;
5026
5065
  }
5027
5066
  get evictions() {
5028
- return __privateGet(this, _evictions);
5067
+ return this.#evictions;
5029
5068
  }
5030
5069
  get hitRate() {
5031
- const total = __privateGet(this, _hits) + __privateGet(this, _misses);
5032
- return total === 0 ? 0 : __privateGet(this, _hits) / total;
5070
+ const total = this.#hits + this.#misses;
5071
+ return total === 0 ? 0 : this.#hits / total;
5033
5072
  }
5034
5073
  /**
5035
5074
  * Returns the value for key, or undefined if not present.
5036
5075
  * Moves the accessed key to the "most recently used" end.
5037
5076
  */
5038
5077
  get(key) {
5039
- const value2 = __privateGet(this, _map).get(key);
5078
+ const value2 = this.#map.get(key);
5040
5079
  if (value2 === void 0) {
5041
- __privateWrapper(this, _misses)._++;
5080
+ this.#misses++;
5042
5081
  return void 0;
5043
5082
  }
5044
- __privateGet(this, _map).delete(key);
5045
- __privateGet(this, _map).set(key, value2);
5046
- __privateWrapper(this, _hits)._++;
5083
+ this.#map.delete(key);
5084
+ this.#map.set(key, value2);
5085
+ this.#hits++;
5047
5086
  return value2;
5048
5087
  }
5049
5088
  /**
@@ -5051,44 +5090,39 @@ var LruCache = class {
5051
5090
  * Evicts the least-recently-used entry if over capacity.
5052
5091
  */
5053
5092
  set(key, value2) {
5054
- if (__privateGet(this, _map).has(key)) {
5055
- __privateGet(this, _map).delete(key);
5056
- } else if (__privateGet(this, _map).size >= __privateGet(this, _capacity)) {
5057
- const oldest = __privateGet(this, _map).keys().next().value;
5058
- __privateGet(this, _map).delete(oldest);
5059
- __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++;
5060
5099
  }
5061
- __privateGet(this, _map).set(key, value2);
5100
+ this.#map.set(key, value2);
5062
5101
  }
5063
5102
  /** Removes a key from the cache. Returns true if it existed. */
5064
5103
  delete(key) {
5065
- return __privateGet(this, _map).delete(key);
5104
+ return this.#map.delete(key);
5066
5105
  }
5067
5106
  /** Returns true if the key is present (does NOT update LRU order). */
5068
5107
  has(key) {
5069
- return __privateGet(this, _map).has(key);
5108
+ return this.#map.has(key);
5070
5109
  }
5071
5110
  /** Removes all entries and resets statistics. */
5072
5111
  clear() {
5073
- __privateGet(this, _map).clear();
5074
- __privateSet(this, _hits, __privateSet(this, _misses, __privateSet(this, _evictions, 0)));
5112
+ this.#map.clear();
5113
+ this.#hits = this.#misses = this.#evictions = 0;
5075
5114
  }
5076
5115
  /** Resizes the cache, evicting oldest entries if necessary. */
5077
5116
  resize(newCapacity) {
5078
5117
  if (newCapacity <= 0) throw new RangeError("Capacity must be > 0.");
5079
- __privateSet(this, _capacity, newCapacity);
5080
- while (__privateGet(this, _map).size > newCapacity) {
5081
- const oldest = __privateGet(this, _map).keys().next().value;
5082
- __privateGet(this, _map).delete(oldest);
5083
- __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++;
5084
5123
  }
5085
5124
  }
5086
5125
  };
5087
- _map = new WeakMap();
5088
- _capacity = new WeakMap();
5089
- _hits = new WeakMap();
5090
- _misses = new WeakMap();
5091
- _evictions = new WeakMap();
5092
5126
 
5093
5127
  // src/formula-evaluator.ts
5094
5128
  var DEFAULT_CACHE_CAPACITY = 1e4;
@@ -5196,143 +5230,126 @@ var TILE_ROWS = 64;
5196
5230
  var TILE_COLS = 64;
5197
5231
  var MAX_TILES_PER_RANGE = 32;
5198
5232
  var DEFAULT_MAX_CELLS = 2e6;
5199
- var _entries, _tiles, _wide, _placements, _sizes, _cells, _maxCells, _RangeCache_static, contains_fn, _RangeCache_instances, drop_fn, fileIn_fn;
5200
- 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;
5201
5247
  constructor(maxCells = DEFAULT_MAX_CELLS) {
5202
- __privateAdd(this, _RangeCache_instances);
5203
- /** `SHEET r0 c0 r1 c1` → the memoised array value. Insertion-ordered (FIFO eviction). */
5204
- __privateAdd(this, _entries, /* @__PURE__ */ new Map());
5205
- /** `sheet tr tc` → entry keys whose rectangle overlaps that tile. */
5206
- __privateAdd(this, _tiles, /* @__PURE__ */ new Map());
5207
- /** sheet → entry keys whose rectangle is too broad to tile. */
5208
- __privateAdd(this, _wide, /* @__PURE__ */ new Map());
5209
- /** entry key → buckets it was filed under, for cheap removal. */
5210
- __privateAdd(this, _placements, /* @__PURE__ */ new Map());
5211
- /** entry key → its area, so eviction can give the budget back. */
5212
- __privateAdd(this, _sizes, /* @__PURE__ */ new Map());
5213
- /** Number of cells across all cached rectangles, kept under {@link #maxCells}. */
5214
- __privateAdd(this, _cells, 0);
5215
- __privateAdd(this, _maxCells);
5216
- this.hits = 0;
5217
- this.misses = 0;
5218
- __privateSet(this, _maxCells, maxCells);
5248
+ this.#maxCells = maxCells;
5219
5249
  }
5220
5250
  get size() {
5221
- return __privateGet(this, _entries).size;
5251
+ return this.#entries.size;
5222
5252
  }
5223
5253
  get cachedCells() {
5224
- return __privateGet(this, _cells);
5254
+ return this.#cells;
5225
5255
  }
5226
5256
  static key(sheet, r0, c0, r1, c1) {
5227
5257
  return `${sheet} ${r0} ${c0} ${r1} ${c1}`;
5228
5258
  }
5229
5259
  get(sheet, r0, c0, r1, c1) {
5230
- const hit = __privateGet(this, _entries).get(_RangeCache.key(sheet, r0, c0, r1, c1));
5260
+ const hit = this.#entries.get(_RangeCache.key(sheet, r0, c0, r1, c1));
5231
5261
  if (hit === void 0) this.misses++;
5232
5262
  else this.hits++;
5233
- return hit;
5263
+ return hit?.value;
5234
5264
  }
5235
5265
  /** Memoises a rectangle's value. `cells` is its area, for the budget. */
5236
5266
  set(sheet, r0, c0, r1, c1, value2, cells) {
5237
- if (cells <= 0 || cells > __privateGet(this, _maxCells)) return;
5267
+ if (cells <= 0 || cells > this.#maxCells) return;
5238
5268
  const key = _RangeCache.key(sheet, r0, c0, r1, c1);
5239
- if (__privateGet(this, _entries).has(key)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
5240
- __privateGet(this, _entries).set(key, value2);
5241
- __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;
5242
5272
  const buckets = [];
5243
5273
  const tileRow0 = r0 / TILE_ROWS | 0, tileRow1 = r1 / TILE_ROWS | 0;
5244
5274
  const tileCol0 = c0 / TILE_COLS | 0, tileCol1 = c1 / TILE_COLS | 0;
5245
5275
  const tileCount = (tileRow1 - tileRow0 + 1) * (tileCol1 - tileCol0 + 1);
5246
5276
  if (tileCount > MAX_TILES_PER_RANGE) {
5247
- __privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _wide), sheet, key, buckets, `w ${sheet}`);
5277
+ this.#fileIn(this.#wide, sheet, key, buckets);
5248
5278
  } else {
5249
5279
  for (let tr = tileRow0; tr <= tileRow1; tr++) {
5250
5280
  for (let tc = tileCol0; tc <= tileCol1; tc++) {
5251
- const bucket = `${sheet} ${tr} ${tc}`;
5252
- __privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _tiles), bucket, key, buckets, `t ${bucket}`);
5281
+ this.#fileIn(this.#tiles, `${sheet} ${tr} ${tc}`, key, buckets);
5253
5282
  }
5254
5283
  }
5255
5284
  }
5256
- __privateGet(this, _placements).set(key, buckets);
5257
- __privateGet(this, _sizes).set(key, cells);
5258
- if (__privateGet(this, _cells) > __privateGet(this, _maxCells)) {
5259
- for (const oldest of __privateGet(this, _entries).keys()) {
5260
- if (__privateGet(this, _cells) <= __privateGet(this, _maxCells)) break;
5285
+ this.#placements.set(key, buckets);
5286
+ if (this.#cells > this.#maxCells) {
5287
+ for (const oldest of this.#entries.keys()) {
5288
+ if (this.#cells <= this.#maxCells) break;
5261
5289
  if (oldest === key) continue;
5262
- __privateMethod(this, _RangeCache_instances, drop_fn).call(this, oldest);
5290
+ this.#drop(oldest);
5263
5291
  }
5264
5292
  }
5265
5293
  }
5266
- /** 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
+ */
5267
5302
  invalidateCell(sheet, row2, col) {
5268
- var _a, _b;
5269
- const tile = __privateGet(this, _tiles).get(`${sheet} ${row2 / TILE_ROWS | 0} ${col / TILE_COLS | 0}`);
5270
- if (tile) {
5271
- for (const key of [...tile]) {
5272
- 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);
5273
5308
  }
5274
5309
  }
5275
- const wide = __privateGet(this, _wide).get(sheet);
5276
- if (wide) {
5277
- for (const key of [...wide]) {
5278
- 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);
5279
5315
  }
5280
5316
  }
5281
5317
  }
5282
5318
  clear() {
5283
- __privateGet(this, _entries).clear();
5284
- __privateGet(this, _tiles).clear();
5285
- __privateGet(this, _wide).clear();
5286
- __privateGet(this, _placements).clear();
5287
- __privateGet(this, _sizes).clear();
5288
- __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
+ }
5289
5341
  }
5290
- };
5291
- _entries = new WeakMap();
5292
- _tiles = new WeakMap();
5293
- _wide = new WeakMap();
5294
- _placements = new WeakMap();
5295
- _sizes = new WeakMap();
5296
- _cells = new WeakMap();
5297
- _maxCells = new WeakMap();
5298
- _RangeCache_static = new WeakSet();
5299
- contains_fn = function(key, sheet, row2, col) {
5300
- const parts = key.split(" ");
5301
- const n = parts.length;
5302
- const c1 = +parts[n - 1], r1 = +parts[n - 2], c0 = +parts[n - 3], r0 = +parts[n - 4];
5303
- if (parts.slice(0, n - 4).join(" ") !== sheet) return false;
5304
- return row2 >= r0 && row2 <= r1 && col >= c0 && col <= c1;
5305
- };
5306
- _RangeCache_instances = new WeakSet();
5307
- drop_fn = function(key) {
5308
- if (!__privateGet(this, _entries).delete(key)) return;
5309
- __privateSet(this, _cells, __privateGet(this, _cells) - (__privateGet(this, _sizes).get(key) ?? 0));
5310
- __privateGet(this, _sizes).delete(key);
5311
- const buckets = __privateGet(this, _placements).get(key);
5312
- if (buckets) {
5313
- for (const bucket of buckets) {
5314
- const store = bucket.charCodeAt(0) === 119 ? __privateGet(this, _wide) : __privateGet(this, _tiles);
5315
- const bucketKey = bucket.slice(2);
5316
- const set = store.get(bucketKey);
5317
- if (!set) continue;
5318
- set.delete(key);
5319
- if (set.size === 0) store.delete(bucketKey);
5342
+ #fileIn(store, bucketKey, entryKey, buckets) {
5343
+ let set = store.get(bucketKey);
5344
+ if (!set) {
5345
+ set = /* @__PURE__ */ new Set();
5346
+ store.set(bucketKey, set);
5320
5347
  }
5321
- __privateGet(this, _placements).delete(key);
5348
+ if (set.has(entryKey)) return;
5349
+ set.add(entryKey);
5350
+ buckets.push({ store, bucketKey, set });
5322
5351
  }
5323
5352
  };
5324
- fileIn_fn = function(store, bucketKey, entryKey, buckets, bucketId) {
5325
- let set = store.get(bucketKey);
5326
- if (!set) {
5327
- set = /* @__PURE__ */ new Set();
5328
- store.set(bucketKey, set);
5329
- }
5330
- if (set.has(entryKey)) return;
5331
- set.add(entryKey);
5332
- buckets.push(bucketId);
5333
- };
5334
- __privateAdd(_RangeCache, _RangeCache_static);
5335
- var RangeCache = _RangeCache;
5336
5353
  function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
5337
5354
  const refs = [];
5338
5355
  let volatile = false;
@@ -5412,24 +5429,20 @@ var MAX_EXACT_SPAN = 16;
5412
5429
  var MAX_BUCKETS_PER_REF = 64;
5413
5430
  var tileOfRow = (row2) => row2 / TILE_ROWS2 | 0;
5414
5431
  var tileOfCol = (col) => col / TILE_COLS2 | 0;
5415
- var _buckets, _placements2, _DependencyIndex_instances, visit_fn, file_fn;
5416
5432
  var DependencyIndex = class {
5417
- constructor() {
5418
- __privateAdd(this, _DependencyIndex_instances);
5419
- /**
5420
- * All buckets in one map, keyed by a discriminating prefix:
5421
- *
5422
- * `t s tr tc` rectangle compact in both dimensions
5423
- * `c s col tr` narrow columns, row tiles (the common case)
5424
- * `C s col` narrow columns, every row (`A:A`)
5425
- * `r s row tc` narrow rows, column tiles
5426
- * `R s row` narrow rows, every column (`1:1`)
5427
- * `w s` broad in both dimensions
5428
- */
5429
- __privateAdd(this, _buckets, /* @__PURE__ */ new Map());
5430
- /** formula key → the buckets it was filed under, for cheap removal. */
5431
- __privateAdd(this, _placements2, /* @__PURE__ */ new Map());
5432
- }
5433
+ /**
5434
+ * All buckets in one map, keyed by a discriminating prefix:
5435
+ *
5436
+ * `t s tr tc` rectangle compact in both dimensions
5437
+ * `c s col tr` narrow columns, row tiles (the common case)
5438
+ * `C s col` narrow columns, every row (`A:A`)
5439
+ * `r s row tc` narrow rows, column tiles
5440
+ * `R s row` narrow rows, every column (`1:1`)
5441
+ * `w s` broad in both dimensions
5442
+ */
5443
+ #buckets = /* @__PURE__ */ new Map();
5444
+ /** formula key → the buckets it was filed under, for cheap removal. */
5445
+ #placements = /* @__PURE__ */ new Map();
5433
5446
  /** Registers (or re-registers) a formula's precedent rectangles. */
5434
5447
  add(formulaKey, refs) {
5435
5448
  this.remove(formulaKey);
@@ -5446,12 +5459,12 @@ var DependencyIndex = class {
5446
5459
  if (colSpan * rowTiles <= MAX_BUCKETS_PER_REF && rowTiles <= MAX_TILES_PER_REF) {
5447
5460
  for (let col = ref.startCol; col <= ref.endCol; col++) {
5448
5461
  for (let tr = rowTile0; tr <= rowTile1; tr++) {
5449
- __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);
5450
5463
  }
5451
5464
  }
5452
5465
  } else {
5453
5466
  for (let col = ref.startCol; col <= ref.endCol; col++) {
5454
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `C ${ref.sheet} ${col}`, formulaKey, buckets);
5467
+ this.#file(`C ${ref.sheet} ${col}`, formulaKey, buckets);
5455
5468
  }
5456
5469
  }
5457
5470
  continue;
@@ -5460,12 +5473,12 @@ var DependencyIndex = class {
5460
5473
  if (rowSpan * colTiles <= MAX_BUCKETS_PER_REF && colTiles <= MAX_TILES_PER_REF) {
5461
5474
  for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
5462
5475
  for (let tc = colTile0; tc <= colTile1; tc++) {
5463
- __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);
5464
5477
  }
5465
5478
  }
5466
5479
  } else {
5467
5480
  for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
5468
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `R ${ref.sheet} ${row2}`, formulaKey, buckets);
5481
+ this.#file(`R ${ref.sheet} ${row2}`, formulaKey, buckets);
5469
5482
  }
5470
5483
  }
5471
5484
  continue;
@@ -5473,31 +5486,31 @@ var DependencyIndex = class {
5473
5486
  if (rowTiles * colTiles <= MAX_TILES_PER_REF) {
5474
5487
  for (let tr = rowTile0; tr <= rowTile1; tr++) {
5475
5488
  for (let tc = colTile0; tc <= colTile1; tc++) {
5476
- __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);
5477
5490
  }
5478
5491
  }
5479
5492
  continue;
5480
5493
  }
5481
- __privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `w ${ref.sheet}`, formulaKey, buckets);
5494
+ this.#file(`w ${ref.sheet}`, formulaKey, buckets);
5482
5495
  }
5483
- __privateGet(this, _placements2).set(formulaKey, buckets);
5496
+ this.#placements.set(formulaKey, buckets);
5484
5497
  }
5485
5498
  /** Forgets a formula. */
5486
5499
  remove(formulaKey) {
5487
- const buckets = __privateGet(this, _placements2).get(formulaKey);
5500
+ const buckets = this.#placements.get(formulaKey);
5488
5501
  if (!buckets) return;
5489
5502
  for (const bucket of buckets) {
5490
- const set = __privateGet(this, _buckets).get(bucket);
5503
+ const set = this.#buckets.get(bucket);
5491
5504
  if (!set) continue;
5492
5505
  set.delete(formulaKey);
5493
- if (set.size === 0) __privateGet(this, _buckets).delete(bucket);
5506
+ if (set.size === 0) this.#buckets.delete(bucket);
5494
5507
  }
5495
- __privateGet(this, _placements2).delete(formulaKey);
5508
+ this.#placements.delete(formulaKey);
5496
5509
  }
5497
5510
  /** Drops every registration. */
5498
5511
  clear() {
5499
- __privateGet(this, _buckets).clear();
5500
- __privateGet(this, _placements2).clear();
5512
+ this.#buckets.clear();
5513
+ this.#placements.clear();
5501
5514
  }
5502
5515
  /**
5503
5516
  * Calls `visit` with the key of each formula that *may* read the given cell.
@@ -5509,31 +5522,28 @@ var DependencyIndex = class {
5509
5522
  candidates(sheet, row2, col, visit) {
5510
5523
  const tr = tileOfRow(row2);
5511
5524
  const tc = tileOfCol(col);
5512
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `c ${sheet} ${col} ${tr}`, visit);
5513
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `C ${sheet} ${col}`, visit);
5514
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `r ${sheet} ${row2} ${tc}`, visit);
5515
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `R ${sheet} ${row2}`, visit);
5516
- __privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `t ${sheet} ${tr} ${tc}`, visit);
5517
- __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);
5518
5545
  }
5519
5546
  };
5520
- _buckets = new WeakMap();
5521
- _placements2 = new WeakMap();
5522
- _DependencyIndex_instances = new WeakSet();
5523
- visit_fn = function(bucket, visit) {
5524
- const set = __privateGet(this, _buckets).get(bucket);
5525
- if (set) for (const key of set) visit(key);
5526
- };
5527
- file_fn = function(bucket, formulaKey, buckets) {
5528
- let set = __privateGet(this, _buckets).get(bucket);
5529
- if (!set) {
5530
- set = /* @__PURE__ */ new Set();
5531
- __privateGet(this, _buckets).set(bucket, set);
5532
- }
5533
- if (set.has(formulaKey)) return;
5534
- set.add(formulaKey);
5535
- buckets.push(bucket);
5536
- };
5537
5547
 
5538
5548
  // src/recalc-engine.ts
5539
5549
  function createWorkbookRecalcModel(workbook) {
@@ -5577,42 +5587,70 @@ function parseKey(key) {
5577
5587
  const { row: row2, column: column2 } = parseCellRef(ref);
5578
5588
  return { sheet, ref, row: row2, col: column2 };
5579
5589
  }
5580
- var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, adoptDeclaredSpill_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
5581
5590
  var RecalcEngine = class {
5591
+ #model;
5592
+ #registry;
5593
+ #formulas = /* @__PURE__ */ new Map();
5594
+ /** upper-cased sheet key → original casing, for case-correct model I/O. */
5595
+ #sheetNames = /* @__PURE__ */ new Map();
5596
+ /** anchor cellKey → set of spilled (non-anchor) cellKeys it currently owns. */
5597
+ #spills = /* @__PURE__ */ new Map();
5598
+ /** spilled cellKey → the anchor cellKey that owns it. */
5599
+ #spillOwner = /* @__PURE__ */ new Map();
5600
+ /**
5601
+ * Reverse dependency index, so finding the formulas that read a cell does not
5602
+ * mean testing every registered formula.
5603
+ */
5604
+ #dependents = new DependencyIndex();
5605
+ /**
5606
+ * Keys of the volatile formulas. Maintained incrementally: scanning every
5607
+ * formula for volatility on each edit is O(F) per keystroke on its own.
5608
+ */
5609
+ #volatiles = /* @__PURE__ */ new Set();
5610
+ /** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
5611
+ #sheetLikes = /* @__PURE__ */ new Map();
5612
+ #workbookLike;
5613
+ /**
5614
+ * Memo for range reads. The engine owns it because it owns the writes: every
5615
+ * value written goes through {@link #writeCell}, which drops the cached
5616
+ * rectangles containing that cell. Nothing else may write to the model behind
5617
+ * the engine's back without calling {@link onCellChanged} — that was already
5618
+ * true for dependency tracking, and now also keeps this memo honest.
5619
+ */
5620
+ #rangeCache = new RangeCache();
5582
5621
  constructor(model, registry = FunctionRegistry.default) {
5583
- __privateAdd(this, _RecalcEngine_instances);
5584
- __privateAdd(this, _model);
5585
- __privateAdd(this, _registry);
5586
- __privateAdd(this, _formulas, /* @__PURE__ */ new Map());
5587
- /** upper-cased sheet key original casing, for case-correct model I/O. */
5588
- __privateAdd(this, _sheetNames, /* @__PURE__ */ new Map());
5589
- /** anchor cellKey set of spilled (non-anchor) cellKeys it currently owns. */
5590
- __privateAdd(this, _spills, /* @__PURE__ */ new Map());
5591
- /** spilled cellKey → the anchor cellKey that owns it. */
5592
- __privateAdd(this, _spillOwner, /* @__PURE__ */ new Map());
5593
- /**
5594
- * Reverse dependency index, so finding the formulas that read a cell does not
5595
- * mean testing every registered formula.
5596
- */
5597
- __privateAdd(this, _dependents, new DependencyIndex());
5598
- /**
5599
- * Keys of the volatile formulas. Maintained incrementally: scanning every
5600
- * formula for volatility on each edit is O(F) per keystroke on its own.
5601
- */
5602
- __privateAdd(this, _volatiles, /* @__PURE__ */ new Set());
5603
- /** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
5604
- __privateAdd(this, _sheetLikes, /* @__PURE__ */ new Map());
5605
- __privateAdd(this, _workbookLike);
5606
- /**
5607
- * Memo for range reads. The engine owns it because it owns the writes: every
5608
- * value written goes through {@link #writeCell}, which drops the cached
5609
- * rectangles containing that cell. Nothing else may write to the model behind
5610
- * the engine's back without calling {@link onCellChanged} — that was already
5611
- * true for dependency tracking, and now also keeps this memo honest.
5612
- */
5613
- __privateAdd(this, _rangeCache, new RangeCache());
5614
- __privateSet(this, _model, model);
5615
- __privateSet(this, _registry, registry);
5622
+ this.#model = model;
5623
+ this.#registry = registry;
5624
+ }
5625
+ /**
5626
+ * The single write path into the model. Writing a value invalidates the
5627
+ * cached rectangles that contain the cell, so a formula evaluated later in the
5628
+ * same pass cannot read a pre-write copy of a range. Coordinates are passed in
5629
+ * where the caller already knows them, to avoid re-parsing the reference.
5630
+ */
5631
+ #writeCell(sheetName, ref, value2, row2, col) {
5632
+ this.#model.setCellValue(sheetName, ref, value2);
5633
+ if (row2 === void 0 || col === void 0) {
5634
+ const parsed = parseCellRef(ref);
5635
+ row2 = parsed.row;
5636
+ col = parsed.column;
5637
+ }
5638
+ this.#rangeCache.invalidateCell(normSheet(sheetName), row2, col);
5639
+ }
5640
+ /** Registers an entry in the map, the reverse index and the volatile set. */
5641
+ #register(entry) {
5642
+ this.#formulas.set(entry.key, entry);
5643
+ this.#dependents.add(entry.key, entry.refs);
5644
+ if (entry.volatile) this.#volatiles.add(entry.key);
5645
+ else this.#volatiles.delete(entry.key);
5646
+ }
5647
+ #unregister(key) {
5648
+ this.#formulas.delete(key);
5649
+ this.#dependents.remove(key);
5650
+ this.#volatiles.delete(key);
5651
+ }
5652
+ #rememberSheet(name) {
5653
+ this.#sheetNames.set(normSheet(name), name);
5616
5654
  }
5617
5655
  // ── Registration ─────────────────────────────────────────────────────────────
5618
5656
  /**
@@ -5630,12 +5668,12 @@ var RecalcEngine = class {
5630
5668
  const { refs, volatile } = extractReferences(
5631
5669
  ast,
5632
5670
  s,
5633
- __privateGet(this, _registry),
5634
- __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
5635
5673
  );
5636
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5674
+ this.#rememberSheet(sheet);
5637
5675
  const key = keyOf(s, r);
5638
- __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5676
+ this.#register({
5639
5677
  sheet: s,
5640
5678
  sheetName: sheet,
5641
5679
  ref: r,
@@ -5647,9 +5685,9 @@ var RecalcEngine = class {
5647
5685
  refs,
5648
5686
  volatile
5649
5687
  });
5650
- __privateGet(this, _model).setCellFormula?.(sheet, r, text);
5651
- __privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
5652
- 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);
5653
5691
  }
5654
5692
  /**
5655
5693
  * Indexes many formulas **without evaluating anything**.
@@ -5671,7 +5709,7 @@ var RecalcEngine = class {
5671
5709
  * construct in a large workbook should not abort the load.
5672
5710
  */
5673
5711
  registerBulk(entries) {
5674
- const resolveName2 = __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;
5675
5713
  let registered = 0;
5676
5714
  const failed = [];
5677
5715
  for (const entry of entries) {
@@ -5683,10 +5721,10 @@ var RecalcEngine = class {
5683
5721
  try {
5684
5722
  const { row: row2, column: column2 } = parseCellRef(r);
5685
5723
  const ast = parseFormulaUncached(text);
5686
- const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
5687
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5724
+ const { refs, volatile } = extractReferences(ast, s, this.#registry, resolveName2);
5725
+ this.#rememberSheet(sheet);
5688
5726
  const key = keyOf(s, r);
5689
- __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5727
+ this.#register({
5690
5728
  sheet: s,
5691
5729
  sheetName: sheet,
5692
5730
  ref: r,
@@ -5698,15 +5736,60 @@ var RecalcEngine = class {
5698
5736
  refs,
5699
5737
  volatile
5700
5738
  });
5701
- __privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
5739
+ this.#adoptDeclaredSpill(s, sheet, r, key, row2, column2);
5702
5740
  registered++;
5703
5741
  } catch (err) {
5704
5742
  failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
5705
5743
  }
5706
5744
  }
5707
- if (registered > 0) __privateGet(this, _rangeCache).clear();
5745
+ if (registered > 0) this.#rangeCache.clear();
5708
5746
  return { registered, failed };
5709
5747
  }
5748
+ /**
5749
+ * Takes ownership of the spill block a *file* already declared for an anchor.
5750
+ *
5751
+ * Excel stores a dynamic array as one formula on the anchor plus the spilled
5752
+ * results written as plain cached values in the cells below/right, and records
5753
+ * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5754
+ * engine meets those cached values on the first recalculation, sees literal
5755
+ * content inside the block it wants to spill into, and reports `#SPILL!` for
5756
+ * every dynamic array in every real file. Registering the declared block as
5757
+ * this anchor's existing spill makes them what they actually are — the anchor's
5758
+ * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5759
+ * result smaller than the declared block clears the leftovers.
5760
+ *
5761
+ * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5762
+ * block far larger than any real spill (a corrupt or hand-written `ref`) is
5763
+ * skipped rather than materialised.
5764
+ */
5765
+ #adoptDeclaredSpill(s, sheetName, ref, anchorKey, row2, col) {
5766
+ const declared = this.#model.getSpillRange?.(sheetName, ref);
5767
+ if (!declared || !declared.includes(":")) return;
5768
+ let rect;
5769
+ try {
5770
+ rect = parseRangeRef(declared);
5771
+ } catch {
5772
+ return;
5773
+ }
5774
+ if (rect.startRow !== row2 || rect.startColumn !== col) return;
5775
+ const height = rect.endRow - rect.startRow + 1;
5776
+ const width = rect.endColumn - rect.startColumn + 1;
5777
+ if (height < 1 || width < 1) return;
5778
+ if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5779
+ if (height === 1 && width === 1) return;
5780
+ const members = /* @__PURE__ */ new Set();
5781
+ for (let r = rect.startRow; r <= rect.endRow; r++) {
5782
+ for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5783
+ if (r === row2 && c === col) continue;
5784
+ const key = keyOf(s, cellRefFromRowCol(r, c));
5785
+ if (this.#formulas.has(key)) continue;
5786
+ if (this.#spillOwner.has(key)) continue;
5787
+ members.add(key);
5788
+ this.#spillOwner.set(key, anchorKey);
5789
+ }
5790
+ }
5791
+ if (members.size > 0) this.#spills.set(anchorKey, members);
5792
+ }
5710
5793
  /**
5711
5794
  * Removes a formula from a cell and recalculates its dependents (which may now
5712
5795
  * read a plain input value instead). Returns the recomputed dependent cells.
@@ -5715,36 +5798,36 @@ var RecalcEngine = class {
5715
5798
  const s = normSheet(sheet);
5716
5799
  const r = normRef(ref);
5717
5800
  const key = keyOf(s, r);
5718
- if (!__privateGet(this, _formulas).has(key)) return [];
5719
- __privateMethod(this, _RecalcEngine_instances, unregister_fn).call(this, key);
5720
- __privateGet(this, _model).clearCell?.(sheet, r);
5801
+ if (!this.#formulas.has(key)) return [];
5802
+ this.#unregister(key);
5803
+ this.#model.clearCell?.(sheet, r);
5721
5804
  const { row: row2, column: column2 } = parseCellRef(r);
5722
- __privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
5723
- 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 }]);
5724
5807
  }
5725
5808
  /**
5726
5809
  * Writes a raw input value to a cell and recalculates every formula that
5727
5810
  * depends on it. Use this for non-formula edits so dependents stay current.
5728
5811
  */
5729
5812
  setCellValue(sheet, ref, value2) {
5730
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5813
+ this.#rememberSheet(sheet);
5731
5814
  const s = normSheet(sheet);
5732
5815
  const r = normRef(ref);
5733
5816
  const { row: row2, column: column2 } = parseCellRef(r);
5734
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, sheet, r, value2, row2, column2);
5735
- 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 }]);
5736
5819
  }
5737
5820
  /**
5738
5821
  * Signals that a cell changed by some external means (the model was already
5739
5822
  * updated) and recalculates its dependents.
5740
5823
  */
5741
5824
  onCellChanged(sheet, ref) {
5742
- __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5825
+ this.#rememberSheet(sheet);
5743
5826
  const s = normSheet(sheet);
5744
5827
  const r = normRef(ref);
5745
5828
  const { row: row2, column: column2 } = parseCellRef(r);
5746
- __privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
5747
- 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 }]);
5748
5831
  }
5749
5832
  /**
5750
5833
  * Recalculates every registered formula.
@@ -5777,18 +5860,61 @@ var RecalcEngine = class {
5777
5860
  recalcAll(options) {
5778
5861
  const order = options?.order;
5779
5862
  if (!order || order.length === 0) {
5780
- return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
5863
+ return this.#evaluatePass([...this.#formulas.values()]).results;
5781
5864
  }
5782
- const attempt = __privateMethod(this, _RecalcEngine_instances, recalcInOrder_fn).call(this, order);
5865
+ const attempt = this.#recalcInOrder(order);
5783
5866
  if (attempt.conflict) {
5784
5867
  options?.onOrderConflict?.(attempt.conflict);
5785
- return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
5868
+ return this.#evaluatePass([...this.#formulas.values()]).results;
5786
5869
  }
5787
5870
  return attempt.results;
5788
5871
  }
5872
+ /**
5873
+ * One pass in a caller-supplied order, watching for inversions.
5874
+ *
5875
+ * The check is the mirror image of the topological sort: instead of asking
5876
+ * "which of my precedents come first?", it asks, of each formula just
5877
+ * evaluated, "has anything that reads me already run?". The dependency index
5878
+ * answers that directly, and one positive answer is enough to know the order
5879
+ * cannot be trusted.
5880
+ */
5881
+ #recalcInOrder(order) {
5882
+ const results = [];
5883
+ const changed = [];
5884
+ const done = /* @__PURE__ */ new Set();
5885
+ let spilled = false;
5886
+ for (const { sheet, ref } of order) {
5887
+ const key = keyOf(sheet, ref);
5888
+ if (done.has(key)) continue;
5889
+ const entry = this.#formulas.get(key);
5890
+ if (!entry) continue;
5891
+ done.add(key);
5892
+ const written = this.#writeResult(entry);
5893
+ results.push(written.result);
5894
+ changed.push(...written.changed);
5895
+ if (written.result.spill) spilled = true;
5896
+ let conflict;
5897
+ this.#forEachDirectDependent({ sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
5898
+ if (conflict || dep.key === entry.key) return;
5899
+ if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
5900
+ });
5901
+ if (conflict) return { results: [], conflict };
5902
+ }
5903
+ const rest = [...this.#formulas.values()].filter((e) => !done.has(e.key));
5904
+ if (rest.length > 0) {
5905
+ const pass = this.#evaluatePass(rest);
5906
+ results.push(...pass.results);
5907
+ changed.push(...pass.changed);
5908
+ }
5909
+ if (spilled) {
5910
+ const evaluated = new Set(done);
5911
+ results.push(...this.#runFixpoint(this.#dependentClosure(changed, evaluated), evaluated));
5912
+ }
5913
+ return { results };
5914
+ }
5789
5915
  /** The precedents recorded for a formula cell (mainly for tests/inspection). */
5790
5916
  getPrecedents(sheet, ref) {
5791
- return __privateGet(this, _formulas).get(keyOf(sheet, ref))?.refs ?? [];
5917
+ return this.#formulas.get(keyOf(sheet, ref))?.refs ?? [];
5792
5918
  }
5793
5919
  // ── Structural edits ─────────────────────────────────────────────────────────
5794
5920
  /**
@@ -5803,7 +5929,7 @@ var RecalcEngine = class {
5803
5929
  * engine.onRowsInserted('S1', 3, 2);
5804
5930
  */
5805
5931
  onRowsInserted(sheet, at, count2 = 1) {
5806
- return __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 });
5807
5933
  }
5808
5934
  /**
5809
5935
  * Tells the engine rows were deleted on a sheet. Call AFTER the
@@ -5813,556 +5939,420 @@ var RecalcEngine = class {
5813
5939
  * volatiles) are recomputed.
5814
5940
  */
5815
5941
  onRowsDeleted(sheet, at, count2 = 1) {
5816
- return __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 });
5817
5943
  }
5818
5944
  /** Column counterpart to {@link onRowsInserted}. */
5819
5945
  onColumnsInserted(sheet, at, count2 = 1) {
5820
- 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 });
5821
5947
  }
5822
5948
  /** Column counterpart to {@link onRowsDeleted}. */
5823
5949
  onColumnsDeleted(sheet, at, count2 = 1) {
5824
- return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "col", kind: "delete", at, count: count2, sheetName: sheet });
5825
- }
5826
- };
5827
- _model = new WeakMap();
5828
- _registry = new WeakMap();
5829
- _formulas = new WeakMap();
5830
- _sheetNames = new WeakMap();
5831
- _spills = new WeakMap();
5832
- _spillOwner = new WeakMap();
5833
- _dependents = new WeakMap();
5834
- _volatiles = new WeakMap();
5835
- _sheetLikes = new WeakMap();
5836
- _workbookLike = new WeakMap();
5837
- _rangeCache = new WeakMap();
5838
- _RecalcEngine_instances = new WeakSet();
5839
- /**
5840
- * The single write path into the model. Writing a value invalidates the
5841
- * cached rectangles that contain the cell, so a formula evaluated later in the
5842
- * same pass cannot read a pre-write copy of a range. Coordinates are passed in
5843
- * where the caller already knows them, to avoid re-parsing the reference.
5844
- */
5845
- writeCell_fn = function(sheetName, ref, value2, row2, col) {
5846
- __privateGet(this, _model).setCellValue(sheetName, ref, value2);
5847
- if (row2 === void 0 || col === void 0) {
5848
- const parsed = parseCellRef(ref);
5849
- row2 = parsed.row;
5850
- col = parsed.column;
5851
- }
5852
- __privateGet(this, _rangeCache).invalidateCell(normSheet(sheetName), row2, col);
5853
- };
5854
- /** Registers an entry in the map, the reverse index and the volatile set. */
5855
- register_fn = function(entry) {
5856
- __privateGet(this, _formulas).set(entry.key, entry);
5857
- __privateGet(this, _dependents).add(entry.key, entry.refs);
5858
- if (entry.volatile) __privateGet(this, _volatiles).add(entry.key);
5859
- else __privateGet(this, _volatiles).delete(entry.key);
5860
- };
5861
- unregister_fn = function(key) {
5862
- __privateGet(this, _formulas).delete(key);
5863
- __privateGet(this, _dependents).remove(key);
5864
- __privateGet(this, _volatiles).delete(key);
5865
- };
5866
- rememberSheet_fn = function(name) {
5867
- __privateGet(this, _sheetNames).set(normSheet(name), name);
5868
- };
5869
- /**
5870
- * Takes ownership of the spill block a *file* already declared for an anchor.
5871
- *
5872
- * Excel stores a dynamic array as one formula on the anchor plus the spilled
5873
- * results written as plain cached values in the cells below/right, and records
5874
- * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5875
- * engine meets those cached values on the first recalculation, sees literal
5876
- * content inside the block it wants to spill into, and reports `#SPILL!` for
5877
- * every dynamic array in every real file. Registering the declared block as
5878
- * this anchor's existing spill makes them what they actually are — the anchor's
5879
- * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5880
- * result smaller than the declared block clears the leftovers.
5881
- *
5882
- * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5883
- * block far larger than any real spill (a corrupt or hand-written `ref`) is
5884
- * skipped rather than materialised.
5885
- */
5886
- adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
5887
- const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
5888
- if (!declared || !declared.includes(":")) return;
5889
- let rect;
5890
- try {
5891
- rect = parseRangeRef(declared);
5892
- } catch {
5893
- return;
5894
- }
5895
- if (rect.startRow !== row2 || rect.startColumn !== col) return;
5896
- const height = rect.endRow - rect.startRow + 1;
5897
- const width = rect.endColumn - rect.startColumn + 1;
5898
- if (height < 1 || width < 1) return;
5899
- if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5900
- if (height === 1 && width === 1) return;
5901
- const members = /* @__PURE__ */ new Set();
5902
- for (let r = rect.startRow; r <= rect.endRow; r++) {
5903
- for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5904
- if (r === row2 && c === col) continue;
5905
- const key = keyOf(s, cellRefFromRowCol(r, c));
5906
- if (__privateGet(this, _formulas).has(key)) continue;
5907
- if (__privateGet(this, _spillOwner).has(key)) continue;
5908
- members.add(key);
5909
- __privateGet(this, _spillOwner).set(key, anchorKey);
5910
- }
5911
- }
5912
- if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
5913
- };
5914
- /**
5915
- * One pass in a caller-supplied order, watching for inversions.
5916
- *
5917
- * The check is the mirror image of the topological sort: instead of asking
5918
- * "which of my precedents come first?", it asks, of each formula just
5919
- * evaluated, "has anything that reads me already run?". The dependency index
5920
- * answers that directly, and one positive answer is enough to know the order
5921
- * cannot be trusted.
5922
- */
5923
- recalcInOrder_fn = function(order) {
5924
- const results = [];
5925
- const changed = [];
5926
- const done = /* @__PURE__ */ new Set();
5927
- let spilled = false;
5928
- for (const { sheet, ref } of order) {
5929
- const key = keyOf(sheet, ref);
5930
- if (done.has(key)) continue;
5931
- const entry = __privateGet(this, _formulas).get(key);
5932
- if (!entry) continue;
5933
- done.add(key);
5934
- const written = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, entry);
5935
- results.push(written.result);
5936
- changed.push(...written.changed);
5937
- if (written.result.spill) spilled = true;
5938
- let conflict;
5939
- __privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
5940
- if (conflict || dep.key === entry.key) return;
5941
- if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
5942
- });
5943
- if (conflict) return { results: [], conflict };
5944
- }
5945
- const rest = [...__privateGet(this, _formulas).values()].filter((e) => !done.has(e.key));
5946
- if (rest.length > 0) {
5947
- const pass = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, rest);
5948
- results.push(...pass.results);
5949
- changed.push(...pass.changed);
5950
- }
5951
- if (spilled) {
5952
- const evaluated = new Set(done);
5953
- results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated), evaluated));
5954
- }
5955
- return { results };
5956
- };
5957
- applyShift_fn = function(shift) {
5958
- const editedSheet = normSheet(shift.sheetName);
5959
- const isRowShift = shift.dim === "row";
5960
- const bandEnd = shift.at + shift.count - 1;
5961
- const rekeyed = [];
5962
- const needsRecalc = /* @__PURE__ */ new Set();
5963
- for (const e of __privateGet(this, _formulas).values()) {
5964
- let row2 = e.row;
5965
- let col = e.col;
5966
- if (e.sheet === editedSheet) {
5967
- const shifted = shiftIndex(isRowShift ? row2 : col, shift);
5968
- if (shifted === null) continue;
5969
- if (isRowShift) row2 = shifted;
5970
- else col = shifted;
5971
- }
5972
- const touchedBand = shift.kind === "delete" && e.refs.some((r) => r.sheet === editedSheet && (isRowShift ? r.endRow >= shift.at && r.startRow <= bandEnd : r.endCol >= shift.at && r.startCol <= bandEnd));
5973
- const rewritten = shiftFormulaRefs(e.formula, shift, e.sheetName);
5974
- const moved = row2 !== e.row || col !== e.col;
5975
- const ref = moved ? cellRefFromRowCol(row2, col) : e.ref;
5976
- const key = moved ? keyOf(e.sheet, ref) : e.key;
5977
- rekeyed.push({
5978
- ...e,
5979
- ref,
5980
- key,
5981
- row: row2,
5982
- col,
5983
- formula: rewritten.changed ? rewritten.text : e.formula,
5984
- // A rewritten formula's cached AST no longer matches its text; it is
5985
- // re-parsed lazily, and only if the cell is actually evaluated again.
5986
- ast: rewritten.changed ? void 0 : e.ast,
5987
- refs: __privateMethod(this, _RecalcEngine_instances, shiftPrecedents_fn).call(this, e.refs, shift, editedSheet)
5988
- // Volatility is a property of which *functions* a formula calls, and
5989
- // rewriting references cannot change that.
5990
- });
5991
- if (touchedBand || rewritten.hasRefError) needsRecalc.add(key);
5992
- }
5993
- __privateSet(this, _formulas, /* @__PURE__ */ new Map());
5994
- __privateGet(this, _dependents).clear();
5995
- __privateGet(this, _volatiles).clear();
5996
- for (const e of rekeyed) __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, e);
5997
- __privateMethod(this, _RecalcEngine_instances, shiftSpillBookkeeping_fn).call(this, shift, editedSheet);
5998
- __privateGet(this, _rangeCache).clear();
5999
- const pending = /* @__PURE__ */ new Map();
6000
- for (const k of needsRecalc) {
6001
- const e = __privateGet(this, _formulas).get(k);
6002
- if (e) pending.set(k, e);
6003
- }
6004
- for (const e of __privateGet(this, _formulas).values()) {
6005
- if (e.volatile) pending.set(e.key, e);
6006
- }
6007
- return __privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, /* @__PURE__ */ new Set());
6008
- };
6009
- /**
6010
- * Translates precedent rectangles across a structural edit.
6011
- *
6012
- * This is arithmetic rather than a re-parse of the rewritten formula, and the
6013
- * two are equivalent by construction: `shiftFormulaRefs` derives the new
6014
- * reference text by calling `shiftSpan`/`shiftIndex` on exactly these numbers,
6015
- * so applying the same functions to the stored rectangle yields the same
6016
- * region. A rectangle that is entirely deleted disappears, matching the
6017
- * `#REF!` the text rewrite produces.
6018
- */
6019
- shiftPrecedents_fn = function(refs, shift, editedSheet) {
6020
- const out = [];
6021
- for (const r of refs) {
6022
- if (r.sheet !== editedSheet) {
6023
- out.push(r);
6024
- continue;
6025
- }
6026
- if (shift.dim === "row") {
6027
- const span = shiftSpan(r.startRow, r.endRow, shift);
6028
- if (!span) continue;
6029
- out.push({
6030
- sheet: r.sheet,
6031
- startRow: span.start,
6032
- endRow: Math.min(span.end, EXCEL_MAX_ROWS),
6033
- startCol: r.startCol,
6034
- endCol: r.endCol
6035
- });
6036
- } else {
6037
- const span = shiftSpan(r.startCol, r.endCol, shift);
6038
- if (!span) continue;
6039
- out.push({
6040
- sheet: r.sheet,
6041
- startRow: r.startRow,
6042
- endRow: r.endRow,
6043
- startCol: span.start,
6044
- endCol: Math.min(span.end, EXCEL_MAX_COLUMNS)
5950
+ return this.#applyShift({ dim: "col", kind: "delete", at, count: count2, sheetName: sheet });
5951
+ }
5952
+ #applyShift(shift) {
5953
+ const editedSheet = normSheet(shift.sheetName);
5954
+ const isRowShift = shift.dim === "row";
5955
+ const bandEnd = shift.at + shift.count - 1;
5956
+ const rekeyed = [];
5957
+ const needsRecalc = /* @__PURE__ */ new Set();
5958
+ for (const e of this.#formulas.values()) {
5959
+ let row2 = e.row;
5960
+ let col = e.col;
5961
+ if (e.sheet === editedSheet) {
5962
+ const shifted = shiftIndex(isRowShift ? row2 : col, shift);
5963
+ if (shifted === null) continue;
5964
+ if (isRowShift) row2 = shifted;
5965
+ else col = shifted;
5966
+ }
5967
+ const touchedBand = shift.kind === "delete" && e.refs.some((r) => r.sheet === editedSheet && (isRowShift ? r.endRow >= shift.at && r.startRow <= bandEnd : r.endCol >= shift.at && r.startCol <= bandEnd));
5968
+ const rewritten = shiftFormulaRefs(e.formula, shift, e.sheetName);
5969
+ const moved = row2 !== e.row || col !== e.col;
5970
+ const ref = moved ? cellRefFromRowCol(row2, col) : e.ref;
5971
+ const key = moved ? keyOf(e.sheet, ref) : e.key;
5972
+ rekeyed.push({
5973
+ ...e,
5974
+ ref,
5975
+ key,
5976
+ row: row2,
5977
+ col,
5978
+ formula: rewritten.changed ? rewritten.text : e.formula,
5979
+ // A rewritten formula's cached AST no longer matches its text; it is
5980
+ // re-parsed lazily, and only if the cell is actually evaluated again.
5981
+ ast: rewritten.changed ? void 0 : e.ast,
5982
+ refs: this.#shiftPrecedents(e.refs, shift, editedSheet)
5983
+ // Volatility is a property of which *functions* a formula calls, and
5984
+ // rewriting references cannot change that.
6045
5985
  });
5986
+ if (touchedBand || rewritten.hasRefError) needsRecalc.add(key);
5987
+ }
5988
+ this.#formulas = /* @__PURE__ */ new Map();
5989
+ this.#dependents.clear();
5990
+ this.#volatiles.clear();
5991
+ for (const e of rekeyed) this.#register(e);
5992
+ this.#shiftSpillBookkeeping(shift, editedSheet);
5993
+ this.#rangeCache.clear();
5994
+ const pending = /* @__PURE__ */ new Map();
5995
+ for (const k of needsRecalc) {
5996
+ const e = this.#formulas.get(k);
5997
+ if (e) pending.set(k, e);
6046
5998
  }
5999
+ for (const e of this.#formulas.values()) {
6000
+ if (e.volatile) pending.set(e.key, e);
6001
+ }
6002
+ return this.#runFixpoint(pending, /* @__PURE__ */ new Set());
6047
6003
  }
6048
- return out;
6049
- };
6050
- /** Translates spill ownership across a structural edit on `editedSheet`. */
6051
- shiftSpillBookkeeping_fn = function(shift, editedSheet) {
6052
- const shiftCellKey = (key) => {
6053
- const { sheet, row: row2, col } = parseKey(key);
6054
- if (sheet !== editedSheet) return key;
6055
- const r = shift.dim === "row" ? shiftIndex(row2, shift) : row2;
6056
- const c = shift.dim === "col" ? shiftIndex(col, shift) : col;
6057
- if (r === null || c === null) return null;
6058
- return keyOf(sheet, cellRefFromRowCol(r, c));
6059
- };
6060
- const newSpills = /* @__PURE__ */ new Map();
6061
- __privateGet(this, _spillOwner).clear();
6062
- for (const [anchor, members] of __privateGet(this, _spills)) {
6063
- const newAnchor = shiftCellKey(anchor);
6064
- 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();
6065
6069
  for (const m of members) {
6066
6070
  const shifted = shiftCellKey(m);
6067
6071
  if (!shifted) continue;
6068
- const cell = parseKey(shifted);
6069
- __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);
6070
6074
  }
6071
- continue;
6075
+ newSpills.set(newAnchor, newMembers);
6072
6076
  }
6073
- const newMembers = /* @__PURE__ */ new Set();
6074
- for (const m of members) {
6075
- const shifted = shiftCellKey(m);
6076
- if (!shifted) continue;
6077
- newMembers.add(shifted);
6078
- __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);
6079
6120
  }
6080
- newSpills.set(newAnchor, newMembers);
6081
6121
  }
6082
- __privateSet(this, _spills, newSpills);
6083
- };
6084
- // ── Internals ────────────────────────────────────────────────────────────────
6085
- /**
6086
- * Recomputes formulas affected by the given changed cells, to a fixpoint.
6087
- *
6088
- * A single topological pass is not enough once dynamic arrays exist: a
6089
- * formula that spills changes cells the graph could not know about until the
6090
- * array was computed, and those cells may have their own dependents. So we
6091
- * evaluate a pass, collect every cell whose value actually changed (formula
6092
- * outputs plus spilled/cleared cells), then recompute the dependents of those
6093
- * cells, repeating until nothing new is affected (bounded for safety).
6094
- */
6095
- recalcFrom_fn = function(seeds, includeKey) {
6096
- const results = [];
6097
- const evaluated = /* @__PURE__ */ new Set();
6098
- let pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, seeds, evaluated);
6099
- __privateMethod(this, _RecalcEngine_instances, addVolatiles_fn).call(this, pending);
6100
- if (includeKey) {
6101
- const self = __privateGet(this, _formulas).get(includeKey);
6102
- if (self) pending.set(includeKey, self);
6103
- }
6104
- results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, evaluated));
6105
- return results;
6106
- };
6107
- /** Evaluates `pending` entries to a fixpoint, following spill-induced changes. */
6108
- runFixpoint_fn = function(pending, evaluated) {
6109
- const results = [];
6110
- let guard = 0;
6111
- const maxIterations = __privateGet(this, _formulas).size + 8;
6112
- while (pending.size > 0 && guard++ <= maxIterations) {
6113
- const { results: passResults, changed } = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...pending.values()]);
6114
- results.push(...passResults);
6115
- for (const k of pending.keys()) evaluated.add(k);
6116
- pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated);
6117
- }
6118
- return results;
6119
- };
6120
- /** Adds every volatile formula to a pending set. */
6121
- addVolatiles_fn = function(pending) {
6122
- for (const key of __privateGet(this, _volatiles)) {
6123
- const e = __privateGet(this, _formulas).get(key);
6124
- if (e) pending.set(key, e);
6122
+ /**
6123
+ * Formula entries (not yet evaluated) whose precedents include one of the
6124
+ * cells, transitively. The queue is walked with a head index — Array.shift()
6125
+ * is O(n) per dequeue, which turns a long dependency chain quadratic.
6126
+ */
6127
+ #dependentClosure(cells, evaluated) {
6128
+ const out = /* @__PURE__ */ new Map();
6129
+ const queue = [...cells];
6130
+ for (let head = 0; head < queue.length; head++) {
6131
+ const cell = queue[head];
6132
+ this.#forEachDirectDependent(cell, (dep) => {
6133
+ if (out.has(dep.key) || evaluated.has(dep.key)) return;
6134
+ out.set(dep.key, dep);
6135
+ queue.push({ sheet: dep.sheet, row: dep.row, col: dep.col });
6136
+ });
6137
+ }
6138
+ return out;
6125
6139
  }
6126
- };
6127
- /**
6128
- * Formula entries (not yet evaluated) whose precedents include one of the
6129
- * cells, transitively. The queue is walked with a head index — Array.shift()
6130
- * is O(n) per dequeue, which turns a long dependency chain quadratic.
6131
- */
6132
- dependentClosure_fn = function(cells, evaluated) {
6133
- const out = /* @__PURE__ */ new Map();
6134
- const queue = [...cells];
6135
- for (let head = 0; head < queue.length; head++) {
6136
- const cell = queue[head];
6137
- __privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, cell, (dep) => {
6138
- if (out.has(dep.key) || evaluated.has(dep.key)) return;
6139
- out.set(dep.key, dep);
6140
- 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
+ }
6141
6155
  });
6142
6156
  }
6143
- return out;
6144
- };
6145
- /**
6146
- * Visits the formula entries whose precedents include the given cell, via the
6147
- * reverse index. The index over-approximates (it buckets rectangles into
6148
- * tiles), so each candidate is still confirmed against its real rectangles.
6149
- */
6150
- forEachDirectDependent_fn = function(cell, visit) {
6151
- __privateGet(this, _dependents).candidates(cell.sheet, cell.row, cell.col, (key) => {
6152
- const e = __privateGet(this, _formulas).get(key);
6153
- if (!e) return;
6154
- for (const ref of e.refs) {
6155
- if (refContains(ref, cell.sheet, cell.row, cell.col)) {
6156
- visit(e);
6157
- 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);
6158
6190
  }
6159
6191
  }
6160
- });
6161
- };
6162
- /**
6163
- * Topologically sorts the given entries and evaluates them, writing results
6164
- * (scalars or spilled arrays) back to the model. Returns the results and the
6165
- * flat list of cells whose value changed. Entries left in a cycle are #REF!.
6166
- */
6167
- evaluatePass_fn = function(entries) {
6168
- const inSet = /* @__PURE__ */ new Map();
6169
- for (const e of entries) inSet.set(e.key, e);
6170
- const indegree = /* @__PURE__ */ new Map();
6171
- const dependents = /* @__PURE__ */ new Map();
6172
- for (const k of inSet.keys()) {
6173
- indegree.set(k, 0);
6174
- dependents.set(k, []);
6175
- }
6176
- for (const pe of entries) {
6177
- const seen = /* @__PURE__ */ new Set();
6178
- __privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: pe.sheet, row: pe.row, col: pe.col }, (dep) => {
6179
- if (dep.key === pe.key || !inSet.has(dep.key) || seen.has(dep.key)) return;
6180
- seen.add(dep.key);
6181
- dependents.get(pe.key).push(dep.key);
6182
- indegree.set(dep.key, indegree.get(dep.key) + 1);
6183
- });
6192
+ const results = [];
6193
+ const changed = [];
6194
+ const emitted = new Set(order);
6195
+ for (const k of order) {
6196
+ const e = inSet.get(k);
6197
+ const { result, changed: c } = this.#writeResult(e);
6198
+ results.push(result);
6199
+ changed.push(...c);
6200
+ }
6201
+ for (const [k, e] of inSet) {
6202
+ if (emitted.has(k)) continue;
6203
+ this.#clearSpill(e.key, changed);
6204
+ const value2 = FormulaValue.errorRef.toObject();
6205
+ this.#writeCell(e.sheetName, e.ref, value2, e.row, e.col);
6206
+ changed.push({ sheet: e.sheet, row: e.row, col: e.col });
6207
+ results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
6208
+ }
6209
+ return { results, changed };
6184
6210
  }
6185
- const ready = [];
6186
- for (const [k, d] of indegree) if (d === 0) ready.push(k);
6187
- const order = [];
6188
- for (let head = 0; head < ready.length; head++) {
6189
- const k = ready[head];
6190
- order.push(k);
6191
- for (const d of dependents.get(k)) {
6192
- const left2 = indegree.get(d) - 1;
6193
- indegree.set(d, left2);
6194
- if (left2 === 0) ready.push(d);
6195
- }
6196
- }
6197
- const results = [];
6198
- const changed = [];
6199
- const emitted = new Set(order);
6200
- for (const k of order) {
6201
- const e = inSet.get(k);
6202
- const { result, changed: c } = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, e);
6203
- results.push(result);
6204
- changed.push(...c);
6205
- }
6206
- for (const [k, e] of inSet) {
6207
- if (emitted.has(k)) continue;
6208
- __privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, e.key, changed);
6209
- const value2 = FormulaValue.errorRef.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
- results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
6213
- }
6214
- return { results, changed };
6215
- };
6216
- /**
6217
- * Evaluates one formula and writes its result. Scalars go to the anchor cell.
6218
- * Arrays spill across a block anchored at the formula cell: on a collision
6219
- * with existing content the anchor becomes #SPILL! and nothing spills; when
6220
- * an array shrinks, previously-spilled cells it no longer covers are cleared.
6221
- */
6222
- writeResult_fn = function(e) {
6223
- const anchorKey = e.key;
6224
- const fv2 = __privateMethod(this, _RecalcEngine_instances, evaluateFormulaValue_fn).call(this, e);
6225
- const changed = [];
6226
- const prevSpill = __privateGet(this, _spills).get(anchorKey) ?? /* @__PURE__ */ new Set();
6227
- if (!fv2.isArray) {
6228
- __privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
6229
- __privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
6230
- const value2 = resultOf(fv2);
6231
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
6232
- changed.push({ sheet: e.sheet, row: e.row, col: e.col });
6233
- return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
6234
- }
6235
- const arr = fv2.arrayVal;
6236
- const rows2 = arr.rows, cols = arr.columns;
6237
- let collides = false;
6238
- for (let dr = 0; dr < rows2 && !collides; dr++) {
6239
- for (let dc = 0; dc < cols && !collides; dc++) {
6240
- if (dr === 0 && dc === 0) continue;
6241
- const r = e.row + dr, c = e.col + dc;
6242
- const cellKey2 = keyOf(e.sheet, cellRefFromRowCol(r, c));
6243
- if (__privateMethod(this, _RecalcEngine_instances, isOccupied_fn).call(this, cellKey2, e.sheetName, cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
6244
- collides = true;
6211
+ /**
6212
+ * Evaluates one formula and writes its result. Scalars go to the anchor cell.
6213
+ * Arrays spill across a block anchored at the formula cell: on a collision
6214
+ * with existing content the anchor becomes #SPILL! and nothing spills; when
6215
+ * an array shrinks, previously-spilled cells it no longer covers are cleared.
6216
+ */
6217
+ #writeResult(e) {
6218
+ const anchorKey = e.key;
6219
+ const fv2 = this.#evaluateFormulaValue(e);
6220
+ const changed = [];
6221
+ const prevSpill = this.#spills.get(anchorKey) ?? /* @__PURE__ */ new Set();
6222
+ if (!fv2.isArray) {
6223
+ this.#clearSpill(anchorKey, changed);
6224
+ this.#model.setCellArrayRef?.(e.sheetName, e.ref, null);
6225
+ const value2 = resultOf(fv2);
6226
+ this.#writeCell(e.sheetName, e.ref, value2, e.row, e.col);
6227
+ changed.push({ sheet: e.sheet, row: e.row, col: e.col });
6228
+ return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
6229
+ }
6230
+ const arr = fv2.arrayVal;
6231
+ const rows2 = arr.rows, cols = arr.columns;
6232
+ let collides = false;
6233
+ for (let dr = 0; dr < rows2 && !collides; dr++) {
6234
+ for (let dc = 0; dc < cols && !collides; dc++) {
6235
+ if (dr === 0 && dc === 0) continue;
6236
+ const r = e.row + dr, c = e.col + dc;
6237
+ const cellKey2 = keyOf(e.sheet, cellRefFromRowCol(r, c));
6238
+ if (this.#isOccupied(cellKey2, e.sheetName, cellRefFromRowCol(r, c), anchorKey, prevSpill)) {
6239
+ collides = true;
6240
+ }
6245
6241
  }
6246
6242
  }
6247
- }
6248
- if (collides) {
6249
- __privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
6250
- __privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
6251
- const value2 = FormulaValue.error(9 /* Spill */).toObject();
6252
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
6253
- changed.push({ sheet: e.sheet, row: e.row, col: e.col });
6254
- return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
6255
- }
6256
- const newSpill = /* @__PURE__ */ new Set();
6257
- for (let dr = 0; dr < rows2; dr++) {
6258
- for (let dc = 0; dc < cols; dc++) {
6259
- const r = e.row + dr, c = e.col + dc;
6260
- const ref = cellRefFromRowCol(r, c);
6261
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, ref, resultOf(arr.get(dr, dc)), r, c);
6262
- changed.push({ sheet: e.sheet, row: r, col: c });
6263
- if (dr !== 0 || dc !== 0) {
6264
- const cellKey2 = keyOf(e.sheet, ref);
6265
- newSpill.add(cellKey2);
6266
- __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
+ }
6267
6263
  }
6268
6264
  }
6265
+ for (const oldKey of prevSpill) {
6266
+ if (newSpill.has(oldKey)) continue;
6267
+ const cell = parseKey(oldKey);
6268
+ this.#writeCell(this.#sheetNames.get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
6269
+ this.#spillOwner.delete(oldKey);
6270
+ changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
6271
+ }
6272
+ this.#spills.set(anchorKey, newSpill);
6273
+ const spillRef = `${e.ref}:${cellRefFromRowCol(e.row + rows2 - 1, e.col + cols - 1)}`;
6274
+ this.#model.setCellArrayRef?.(e.sheetName, e.ref, spillRef);
6275
+ return {
6276
+ result: { sheet: e.sheetName, ref: e.ref, value: resultOf(arr.get(0, 0)), spill: { rows: rows2, cols } },
6277
+ changed
6278
+ };
6269
6279
  }
6270
- for (const oldKey of prevSpill) {
6271
- if (newSpill.has(oldKey)) continue;
6272
- const cell = parseKey(oldKey);
6273
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
6274
- __privateGet(this, _spillOwner).delete(oldKey);
6275
- 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
+ });
6276
6315
  }
6277
- __privateGet(this, _spills).set(anchorKey, newSpill);
6278
- const spillRef = `${e.ref}:${cellRefFromRowCol(e.row + rows2 - 1, e.col + cols - 1)}`;
6279
- __privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, spillRef);
6280
- return {
6281
- result: { sheet: e.sheetName, ref: e.ref, value: resultOf(arr.get(0, 0)), spill: { rows: rows2, cols } },
6282
- changed
6283
- };
6284
- };
6285
- /** Whether a target cell already holds content that blocks a spill. */
6286
- isOccupied_fn = function(cellKey2, sheetName, ref, anchorKey, prevSpill) {
6287
- if (__privateGet(this, _formulas).has(cellKey2) && cellKey2 !== anchorKey) return true;
6288
- const owner = __privateGet(this, _spillOwner).get(cellKey2);
6289
- if (owner && owner !== anchorKey) return true;
6290
- if (prevSpill.has(cellKey2)) return false;
6291
- const v = __privateGet(this, _model).getCellValue(sheetName, ref);
6292
- return v !== null && v !== void 0 && v !== "";
6293
- };
6294
- /** Clears an anchor's spilled cells (not the anchor itself) and forgets them. */
6295
- clearSpill_fn = function(anchorKey, changed) {
6296
- const spill = __privateGet(this, _spills).get(anchorKey);
6297
- if (!spill) return;
6298
- for (const cellKey2 of spill) {
6299
- const cell = parseKey(cellKey2);
6300
- __privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
6301
- __privateGet(this, _spillOwner).delete(cellKey2);
6302
- changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
6303
- }
6304
- __privateGet(this, _spills).delete(anchorKey);
6305
- };
6306
- evaluateFormulaValue_fn = function(e) {
6307
- if (!e.ast) {
6308
- try {
6309
- e.ast = parseFormulaUncached(e.formula);
6310
- } catch {
6311
- return FormulaValue.errorValue;
6316
+ /**
6317
+ * Per-sheet evaluator adapter. Memoised: these hold no per-formula state, and
6318
+ * rebuilding one (with its four closures) for every formula evaluated made a
6319
+ * full recalc allocate several objects per cell.
6320
+ */
6321
+ #sheetLike(sheetName) {
6322
+ let cached = this.#sheetLikes.get(sheetName);
6323
+ if (!cached) {
6324
+ cached = this.#buildSheetLike(sheetName);
6325
+ this.#sheetLikes.set(sheetName, cached);
6312
6326
  }
6327
+ return cached;
6328
+ }
6329
+ #buildSheetLike(sheetName) {
6330
+ const model = this.#model;
6331
+ return {
6332
+ name: sheetName,
6333
+ getCellValue: (ref) => this.#model.getCellValue(sheetName, ref),
6334
+ getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
6335
+ // A getter, not a snapshot: the extent grows as formulas write results,
6336
+ // and a whole-column reference read later in the same pass must see it.
6337
+ get usedBounds() {
6338
+ return model.getUsedBounds?.(sheetName);
6339
+ },
6340
+ isRowHidden: this.#model.isRowHidden ? (row2) => this.#model.isRowHidden(sheetName, row2) : void 0,
6341
+ getCellFormula: this.#model.getCellFormula ? (ref) => this.#model.getCellFormula(sheetName, ref) : void 0,
6342
+ getSpillRange: this.#model.getSpillRange ? (ref) => this.#model.getSpillRange(sheetName, ref) : void 0
6343
+ };
6344
+ }
6345
+ #buildWorkbookLike() {
6346
+ if (this.#workbookLike) return this.#workbookLike;
6347
+ const self = this;
6348
+ return this.#workbookLike = {
6349
+ get worksheets() {
6350
+ return [...self.#sheetNames.values()].map((n) => self.#sheetLike(n));
6351
+ },
6352
+ getWorksheet: (name) => self.#sheetLike(name),
6353
+ getDefinedName: this.#model.getDefinedName ? (name, sheet) => this.#model.getDefinedName(name, sheet) : void 0
6354
+ };
6313
6355
  }
6314
- return evaluateAst(e.ast, __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName), {
6315
- workbook: __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this),
6316
- formulaRow: e.row,
6317
- formulaCol: e.col,
6318
- rangeCache: __privateGet(this, _rangeCache)
6319
- });
6320
- };
6321
- /**
6322
- * Per-sheet evaluator adapter. Memoised: these hold no per-formula state, and
6323
- * rebuilding one (with its four closures) for every formula evaluated made a
6324
- * full recalc allocate several objects per cell.
6325
- */
6326
- sheetLike_fn = function(sheetName) {
6327
- let cached = __privateGet(this, _sheetLikes).get(sheetName);
6328
- if (!cached) {
6329
- cached = __privateMethod(this, _RecalcEngine_instances, buildSheetLike_fn).call(this, sheetName);
6330
- __privateGet(this, _sheetLikes).set(sheetName, cached);
6331
- }
6332
- return cached;
6333
- };
6334
- buildSheetLike_fn = function(sheetName) {
6335
- const model = __privateGet(this, _model);
6336
- return {
6337
- name: sheetName,
6338
- getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
6339
- getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
6340
- // A getter, not a snapshot: the extent grows as formulas write results,
6341
- // and a whole-column reference read later in the same pass must see it.
6342
- get usedBounds() {
6343
- return model.getUsedBounds?.(sheetName);
6344
- },
6345
- isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
6346
- getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
6347
- getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
6348
- };
6349
- };
6350
- buildWorkbookLike_fn = function() {
6351
- if (__privateGet(this, _workbookLike)) return __privateGet(this, _workbookLike);
6352
- const self = this;
6353
- return __privateSet(this, _workbookLike, {
6354
- get worksheets() {
6355
- return [...__privateGet(self, _sheetNames).values()].map((n) => {
6356
- var _a;
6357
- return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, n);
6358
- });
6359
- },
6360
- getWorksheet: (name) => {
6361
- var _a;
6362
- return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, name);
6363
- },
6364
- getDefinedName: __privateGet(this, _model).getDefinedName ? (name, sheet) => __privateGet(this, _model).getDefinedName(name, sheet) : void 0
6365
- });
6366
6356
  };
6367
6357
 
6368
6358
  export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CallNode, CellReferenceNode, ErrorNode, FormulaContext, FormulaError, FormulaException, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, FullRangeReferenceNode, FunctionCallNode, FunctionRegistry, ImplicitIntersectionNode, LruCache, NamedRangeNode, NumberNode, RangeCache, RangeReferenceNode, RecalcEngine, SheetCellReferenceNode, SheetRangeReferenceNode, SpillReferenceNode, StringNode, TokenType, UnaryOpNode, UnaryOperator, clearAstCache, createWorkbookRecalcModel, evaluateAst, evaluateFormula, extractReferences, getAstCacheStats, parseFormula, parseFormulaUncached, refContains, registerAggregate, registerArray, registerDate, registerFinance, registerInfo, registerLogical, registerLookup, registerMath, registerMeta, registerStatistical, registerText, setAstCacheCapacity };