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