@devmm/puredocs-excel-formula 1.1.1 → 1.1.3

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