@devmm/puredocs-excel-formula 1.0.8 → 1.0.9

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.d.cts CHANGED
@@ -202,6 +202,8 @@ interface FunctionDef {
202
202
  readonly maxArgs: number;
203
203
  readonly isVolatile: boolean;
204
204
  readonly handler: FormulaFunction;
205
+ /** Scalar function: an array argument maps it over the array. See {@link FunctionRegistry.registerElementwise}. */
206
+ readonly elementwise: boolean;
205
207
  }
206
208
  declare class FunctionRegistry {
207
209
  #private;
@@ -209,6 +211,23 @@ declare class FunctionRegistry {
209
211
  static get default(): FunctionRegistry;
210
212
  /** Registers a function. Name is normalised to UPPERCASE. */
211
213
  register(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number, isVolatile?: boolean): void;
214
+ /**
215
+ * Registers a **scalar** function — one whose arguments are single values —
216
+ * and lifts it over arrays automatically.
217
+ *
218
+ * `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
219
+ * the grid maps it over the range, giving a 99-row array. Without that lifting
220
+ * the range collapses to one value, so the dynamic array built on top of it —
221
+ * `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
222
+ * `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
223
+ * text/math/date functions then get it for free and cannot each get it subtly
224
+ * wrong.
225
+ *
226
+ * Only mark functions that are genuinely per-cell. Anything that *consumes* an
227
+ * array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
228
+ * `register`, or lifting would map it over the array it is meant to reduce.
229
+ */
230
+ registerElementwise(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number): void;
212
231
  /** Executes a function by name with argument validation. */
213
232
  execute(name: string, args: FormulaNode[], ctx: FormulaContext): FormulaValue;
214
233
  /** Returns true if the named function is volatile (NOW, RAND, etc.). */
package/dist/index.d.ts CHANGED
@@ -202,6 +202,8 @@ interface FunctionDef {
202
202
  readonly maxArgs: number;
203
203
  readonly isVolatile: boolean;
204
204
  readonly handler: FormulaFunction;
205
+ /** Scalar function: an array argument maps it over the array. See {@link FunctionRegistry.registerElementwise}. */
206
+ readonly elementwise: boolean;
205
207
  }
206
208
  declare class FunctionRegistry {
207
209
  #private;
@@ -209,6 +211,23 @@ declare class FunctionRegistry {
209
211
  static get default(): FunctionRegistry;
210
212
  /** Registers a function. Name is normalised to UPPERCASE. */
211
213
  register(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number, isVolatile?: boolean): void;
214
+ /**
215
+ * Registers a **scalar** function — one whose arguments are single values —
216
+ * and lifts it over arrays automatically.
217
+ *
218
+ * `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
219
+ * the grid maps it over the range, giving a 99-row array. Without that lifting
220
+ * the range collapses to one value, so the dynamic array built on top of it —
221
+ * `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
222
+ * `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
223
+ * text/math/date functions then get it for free and cannot each get it subtly
224
+ * wrong.
225
+ *
226
+ * Only mark functions that are genuinely per-cell. Anything that *consumes* an
227
+ * array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
228
+ * `register`, or lifting would map it over the array it is meant to reduce.
229
+ */
230
+ registerElementwise(name: string, handler: FormulaFunction, minArgs?: number, maxArgs?: number): void;
212
231
  /** Executes a function by name with argument validation. */
213
232
  execute(name: string, args: FormulaNode[], ctx: FormulaContext): FormulaValue;
214
233
  /** Returns true if the named function is volatile (NOW, RAND, etc.). */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, shiftIndex, shiftFormulaRefs, shiftSpan, EXCEL_MAX_ROWS, EXCEL_MAX_COLUMNS, columnNumber } from '@devmm/puredocs-excel';
1
+ import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, parseRangeRef, shiftIndex, shiftFormulaRefs, shiftSpan, EXCEL_MAX_ROWS, EXCEL_MAX_COLUMNS, columnNumber } from '@devmm/puredocs-excel';
2
2
 
3
3
  var __typeError = (msg) => {
4
4
  throw TypeError(msg);
@@ -805,6 +805,16 @@ var ArrayLiteralNode = class extends FormulaNode {
805
805
  }
806
806
  };
807
807
  _value = new WeakMap();
808
+ var ValueNode = class extends FormulaNode {
809
+ /** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
810
+ constructor(value2) {
811
+ super();
812
+ this.value = value2;
813
+ }
814
+ evaluate() {
815
+ return this.value;
816
+ }
817
+ };
808
818
  var BlankNode = class extends FormulaNode {
809
819
  evaluate() {
810
820
  return FormulaValue.blank;
@@ -1574,42 +1584,42 @@ function registerMath(r) {
1574
1584
  r.register("PRODUCT", product, 1);
1575
1585
  r.register("SUMPRODUCT", sumProduct, 1);
1576
1586
  r.register("SUMSQ", sumSq, 1);
1577
- r.register("ABS", abs, 1, 1);
1578
- r.register("ROUND", round, 2, 2);
1579
- r.register("ROUNDUP", roundUp, 2, 2);
1580
- r.register("ROUNDDOWN", roundDown, 2, 2);
1581
- r.register("CEILING", ceiling, 2, 2);
1582
- r.register("CEILING.MATH", ceiling, 2, 2);
1583
- r.register("FLOOR", floor, 2, 2);
1584
- r.register("FLOOR.MATH", floor, 2, 2);
1585
- r.register("INT", int_, 1, 1);
1586
- r.register("TRUNC", trunc, 1, 2);
1587
- r.register("MOD", mod, 2, 2);
1588
- r.register("QUOTIENT", quotient, 2, 2);
1589
- r.register("POWER", power, 2, 2);
1590
- r.register("SQRT", sqrt, 1, 1);
1591
- r.register("SIGN", sign, 1, 1);
1587
+ r.registerElementwise("ABS", abs, 1, 1);
1588
+ r.registerElementwise("ROUND", round, 2, 2);
1589
+ r.registerElementwise("ROUNDUP", roundUp, 2, 2);
1590
+ r.registerElementwise("ROUNDDOWN", roundDown, 2, 2);
1591
+ r.registerElementwise("CEILING", ceiling, 2, 2);
1592
+ r.registerElementwise("CEILING.MATH", ceiling, 2, 2);
1593
+ r.registerElementwise("FLOOR", floor, 2, 2);
1594
+ r.registerElementwise("FLOOR.MATH", floor, 2, 2);
1595
+ r.registerElementwise("INT", int_, 1, 1);
1596
+ r.registerElementwise("TRUNC", trunc, 1, 2);
1597
+ r.registerElementwise("MOD", mod, 2, 2);
1598
+ r.registerElementwise("QUOTIENT", quotient, 2, 2);
1599
+ r.registerElementwise("POWER", power, 2, 2);
1600
+ r.registerElementwise("SQRT", sqrt, 1, 1);
1601
+ r.registerElementwise("SIGN", sign, 1, 1);
1592
1602
  r.register("PI", pi, 0, 0);
1593
1603
  r.register("RAND", rand, 0, 0, true);
1594
1604
  r.register("RANDBETWEEN", randBetween, 2, 2, true);
1595
- r.register("LOG", log, 1, 2);
1596
- r.register("LOG10", log10, 1, 1);
1597
- r.register("LN", ln, 1, 1);
1598
- r.register("EXP", exp, 1, 1);
1599
- r.register("FACT", fact, 1, 1);
1600
- r.register("COMBIN", combin, 2, 2);
1601
- r.register("SIN", unary(Math.sin), 1, 1);
1602
- r.register("COS", unary(Math.cos), 1, 1);
1603
- r.register("TAN", unary(Math.tan), 1, 1);
1604
- r.register("ASIN", asin, 1, 1);
1605
- r.register("ACOS", acos, 1, 1);
1606
- r.register("ATAN", unary(Math.atan), 1, 1);
1607
- r.register("ATAN2", atan2, 2, 2);
1608
- r.register("SINH", unary(Math.sinh), 1, 1);
1609
- r.register("COSH", unary(Math.cosh), 1, 1);
1610
- r.register("TANH", unary(Math.tanh), 1, 1);
1611
- r.register("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1612
- r.register("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1605
+ r.registerElementwise("LOG", log, 1, 2);
1606
+ r.registerElementwise("LOG10", log10, 1, 1);
1607
+ r.registerElementwise("LN", ln, 1, 1);
1608
+ r.registerElementwise("EXP", exp, 1, 1);
1609
+ r.registerElementwise("FACT", fact, 1, 1);
1610
+ r.registerElementwise("COMBIN", combin, 2, 2);
1611
+ r.registerElementwise("SIN", unary(Math.sin), 1, 1);
1612
+ r.registerElementwise("COS", unary(Math.cos), 1, 1);
1613
+ r.registerElementwise("TAN", unary(Math.tan), 1, 1);
1614
+ r.registerElementwise("ASIN", asin, 1, 1);
1615
+ r.registerElementwise("ACOS", acos, 1, 1);
1616
+ r.registerElementwise("ATAN", unary(Math.atan), 1, 1);
1617
+ r.registerElementwise("ATAN2", atan2, 2, 2);
1618
+ r.registerElementwise("SINH", unary(Math.sinh), 1, 1);
1619
+ r.registerElementwise("COSH", unary(Math.cosh), 1, 1);
1620
+ r.registerElementwise("TANH", unary(Math.tanh), 1, 1);
1621
+ r.registerElementwise("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1622
+ r.registerElementwise("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1613
1623
  }
1614
1624
  function valueAt(v, i) {
1615
1625
  return v.isArray ? v.arrayVal.getFlat(i) : v;
@@ -2041,31 +2051,31 @@ function combin(a, c) {
2041
2051
  return FormulaValue.number(Math.round(result));
2042
2052
  }
2043
2053
  function registerText(r) {
2044
- r.register("LEFT", left, 1, 2);
2045
- r.register("RIGHT", right, 1, 2);
2046
- r.register("MID", mid, 3, 3);
2047
- r.register("LEN", len, 1, 1);
2048
- r.register("UPPER", upper, 1, 1);
2049
- r.register("LOWER", lower, 1, 1);
2050
- r.register("PROPER", proper, 1, 1);
2051
- r.register("TRIM", trim, 1, 1);
2052
- r.register("CLEAN", clean, 1, 1);
2053
- r.register("SUBSTITUTE", substitute, 3, 4);
2054
- r.register("REPLACE", replace, 4, 4);
2054
+ r.registerElementwise("LEFT", left, 1, 2);
2055
+ r.registerElementwise("RIGHT", right, 1, 2);
2056
+ r.registerElementwise("MID", mid, 3, 3);
2057
+ r.registerElementwise("LEN", len, 1, 1);
2058
+ r.registerElementwise("UPPER", upper, 1, 1);
2059
+ r.registerElementwise("LOWER", lower, 1, 1);
2060
+ r.registerElementwise("PROPER", proper, 1, 1);
2061
+ r.registerElementwise("TRIM", trim, 1, 1);
2062
+ r.registerElementwise("CLEAN", clean, 1, 1);
2063
+ r.registerElementwise("SUBSTITUTE", substitute, 3, 4);
2064
+ r.registerElementwise("REPLACE", replace, 4, 4);
2055
2065
  r.register("CONCATENATE", concatenate, 1);
2056
2066
  r.register("CONCAT", concatenate, 1);
2057
2067
  r.register("TEXTJOIN", textJoin, 3);
2058
- r.register("TEXT", text_, 2, 2);
2059
- r.register("VALUE", value, 1, 1);
2060
- r.register("FIND", find, 2, 3);
2061
- r.register("SEARCH", search, 2, 3);
2062
- r.register("REPT", rept, 2, 2);
2063
- r.register("EXACT", exact, 2, 2);
2064
- r.register("CHAR", char_, 1, 1);
2065
- r.register("CODE", code, 1, 1);
2066
- r.register("T", t_, 1, 1);
2067
- r.register("TEXTBEFORE", textBefore, 2, 3);
2068
- r.register("TEXTAFTER", textAfter, 2, 3);
2068
+ r.registerElementwise("TEXT", text_, 2, 2);
2069
+ r.registerElementwise("VALUE", value, 1, 1);
2070
+ r.registerElementwise("FIND", find, 2, 3);
2071
+ r.registerElementwise("SEARCH", search, 2, 3);
2072
+ r.registerElementwise("REPT", rept, 2, 2);
2073
+ r.registerElementwise("EXACT", exact, 2, 2);
2074
+ r.registerElementwise("CHAR", char_, 1, 1);
2075
+ r.registerElementwise("CODE", code, 1, 1);
2076
+ r.registerElementwise("T", t_, 1, 1);
2077
+ r.registerElementwise("TEXTBEFORE", textBefore, 2, 3);
2078
+ r.registerElementwise("TEXTAFTER", textAfter, 2, 3);
2069
2079
  }
2070
2080
  function str(a, c, idx) {
2071
2081
  const r = FormulaHelper.evalString(a[idx], c);
@@ -2379,6 +2389,7 @@ function registerLogical(r) {
2379
2389
  function if_(a, c) {
2380
2390
  const cond = a[0].evaluate(c);
2381
2391
  if (cond.isError) return cond;
2392
+ if (cond.isArray) return ifArray(cond.arrayVal, a, c);
2382
2393
  const b = cond.coerceToBool();
2383
2394
  if (b.isError) return b;
2384
2395
  return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
@@ -2455,13 +2466,63 @@ function xor(a, c) {
2455
2466
  }
2456
2467
  return FormulaValue.boolean(trueCount % 2 === 1);
2457
2468
  }
2458
- function ifError(a, c) {
2469
+ function ifArray(mask, a, c) {
2470
+ let wantTrue = false, wantFalse = false;
2471
+ for (const item of mask.values()) {
2472
+ const b = item.coerceToBool();
2473
+ if (b.isError) return b;
2474
+ if (b.booleanValue) wantTrue = true;
2475
+ else wantFalse = true;
2476
+ if (wantTrue && wantFalse) break;
2477
+ }
2478
+ const yes = wantTrue ? a[1].evaluate(c) : FormulaValue.blank;
2479
+ const no = wantFalse ? a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_ : FormulaValue.blank;
2480
+ const pick = (v, r, col) => {
2481
+ if (!v.isArray) return v;
2482
+ const arr = v.arrayVal;
2483
+ return r < arr.rows && col < arr.columns ? arr.get(r, col) : FormulaValue.errorNA;
2484
+ };
2485
+ const out = new ArrayValue(mask.rows, mask.columns);
2486
+ for (let r = 0; r < mask.rows; r++) {
2487
+ for (let col = 0; col < mask.columns; col++) {
2488
+ const b = mask.get(r, col).coerceToBool();
2489
+ out.set(r, col, b.booleanValue ? pick(yes, r, col) : pick(no, r, col));
2490
+ }
2491
+ }
2492
+ return FormulaValue.array(out);
2493
+ }
2494
+ function ifCaught(a, c, caught) {
2459
2495
  const v = a[0].evaluate(c);
2460
- return v.isError ? a[1].evaluate(c) : v;
2496
+ if (!v.isArray) return caught(v) ? a[1].evaluate(c) : v;
2497
+ const arr = v.arrayVal;
2498
+ let any = false;
2499
+ for (const item of arr.values()) {
2500
+ if (caught(item)) {
2501
+ any = true;
2502
+ break;
2503
+ }
2504
+ }
2505
+ if (!any) return v;
2506
+ const fb = a[1].evaluate(c);
2507
+ const fbArr = fb.isArray ? fb.arrayVal : null;
2508
+ const out = new ArrayValue(arr.rows, arr.columns);
2509
+ for (let r = 0; r < arr.rows; r++) {
2510
+ for (let col = 0; col < arr.columns; col++) {
2511
+ const item = arr.get(r, col);
2512
+ if (!caught(item)) {
2513
+ out.set(r, col, item);
2514
+ continue;
2515
+ }
2516
+ out.set(r, col, fbArr ? r < fbArr.rows && col < fbArr.columns ? fbArr.get(r, col) : FormulaValue.errorNA : fb);
2517
+ }
2518
+ }
2519
+ return FormulaValue.array(out);
2520
+ }
2521
+ function ifError(a, c) {
2522
+ return ifCaught(a, c, (v) => v.isError);
2461
2523
  }
2462
2524
  function ifNa(a, c) {
2463
- const v = a[0].evaluate(c);
2464
- return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
2525
+ return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
2465
2526
  }
2466
2527
  function ifs(a, c) {
2467
2528
  for (let i = 0; i + 1 < a.length; i += 2) {
@@ -2518,21 +2579,21 @@ function fromOA(oa) {
2518
2579
  function registerDate(r) {
2519
2580
  r.register("NOW", now, 0, 0, true);
2520
2581
  r.register("TODAY", today, 0, 0, true);
2521
- r.register("DATE", date_, 3, 3);
2522
- r.register("YEAR", year, 1, 1);
2523
- r.register("MONTH", month, 1, 1);
2524
- r.register("DAY", day, 1, 1);
2525
- r.register("HOUR", hour, 1, 1);
2526
- r.register("MINUTE", minute, 1, 1);
2527
- r.register("SECOND", second, 1, 1);
2528
- r.register("DATEVALUE", dateValue, 1, 1);
2529
- r.register("DAYS", days, 2, 2);
2530
- r.register("EDATE", edate, 2, 2);
2531
- r.register("EOMONTH", eomonth, 2, 2);
2532
- r.register("WEEKDAY", weekday, 1, 2);
2533
- r.register("WEEKNUM", weeknum, 1, 2);
2582
+ r.registerElementwise("DATE", date_, 3, 3);
2583
+ r.registerElementwise("YEAR", year, 1, 1);
2584
+ r.registerElementwise("MONTH", month, 1, 1);
2585
+ r.registerElementwise("DAY", day, 1, 1);
2586
+ r.registerElementwise("HOUR", hour, 1, 1);
2587
+ r.registerElementwise("MINUTE", minute, 1, 1);
2588
+ r.registerElementwise("SECOND", second, 1, 1);
2589
+ r.registerElementwise("DATEVALUE", dateValue, 1, 1);
2590
+ r.registerElementwise("DAYS", days, 2, 2);
2591
+ r.registerElementwise("EDATE", edate, 2, 2);
2592
+ r.registerElementwise("EOMONTH", eomonth, 2, 2);
2593
+ r.registerElementwise("WEEKDAY", weekday, 1, 2);
2594
+ r.registerElementwise("WEEKNUM", weeknum, 1, 2);
2534
2595
  r.register("NETWORKDAYS", networkdays, 2, 3);
2535
- r.register("DATEDIF", datedif, 3, 3);
2596
+ r.registerElementwise("DATEDIF", datedif, 3, 3);
2536
2597
  }
2537
2598
  function evalDate(a, c, idx) {
2538
2599
  const r = FormulaHelper.evalDouble(a[idx], c);
@@ -3345,20 +3406,20 @@ function ifsAggregate(a, c, mode2) {
3345
3406
 
3346
3407
  // src/functions/info-functions.ts
3347
3408
  function registerInfo(r) {
3348
- r.register("ISBLANK", isBlank, 1, 1);
3349
- r.register("ISNUMBER", isNumber, 1, 1);
3350
- r.register("ISTEXT", isText, 1, 1);
3351
- r.register("ISERROR", isError, 1, 1);
3352
- r.register("ISERR", isErr, 1, 1);
3353
- r.register("ISLOGICAL", isLogical, 1, 1);
3354
- r.register("ISNA", isNa, 1, 1);
3355
- r.register("ISNONTEXT", isNonText, 1, 1);
3409
+ r.registerElementwise("ISBLANK", isBlank, 1, 1);
3410
+ r.registerElementwise("ISNUMBER", isNumber, 1, 1);
3411
+ r.registerElementwise("ISTEXT", isText, 1, 1);
3412
+ r.registerElementwise("ISERROR", isError, 1, 1);
3413
+ r.registerElementwise("ISERR", isErr, 1, 1);
3414
+ r.registerElementwise("ISLOGICAL", isLogical, 1, 1);
3415
+ r.registerElementwise("ISNA", isNa, 1, 1);
3416
+ r.registerElementwise("ISNONTEXT", isNonText, 1, 1);
3356
3417
  r.register("NA", na, 0, 0);
3357
- r.register("TYPE", type_, 1, 1);
3358
- r.register("N", n_, 1, 1);
3359
- r.register("ISODD", isOdd, 1, 1);
3360
- r.register("ISEVEN", isEven, 1, 1);
3361
- r.register("ERROR.TYPE", errorType, 1, 1);
3418
+ r.registerElementwise("TYPE", type_, 1, 1);
3419
+ r.registerElementwise("N", n_, 1, 1);
3420
+ r.registerElementwise("ISODD", isOdd, 1, 1);
3421
+ r.registerElementwise("ISEVEN", isEven, 1, 1);
3422
+ r.registerElementwise("ERROR.TYPE", errorType, 1, 1);
3362
3423
  }
3363
3424
  function isBlank(a, c) {
3364
3425
  return FormulaValue.boolean(a[0].evaluate(c).isBlank);
@@ -4446,7 +4507,8 @@ function toRow(a, c) {
4446
4507
  }
4447
4508
 
4448
4509
  // src/function-registry.ts
4449
- var _default, _functions, _FunctionRegistry_instances, registerBuiltIns_fn;
4510
+ var MAX_LIFTED_CELLS = 1048576;
4511
+ var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
4450
4512
  var _FunctionRegistry = class _FunctionRegistry {
4451
4513
  constructor() {
4452
4514
  __privateAdd(this, _FunctionRegistry_instances);
@@ -4468,17 +4530,45 @@ var _FunctionRegistry = class _FunctionRegistry {
4468
4530
  minArgs,
4469
4531
  maxArgs,
4470
4532
  isVolatile,
4471
- handler
4533
+ handler,
4534
+ elementwise: false
4535
+ });
4536
+ }
4537
+ /**
4538
+ * Registers a **scalar** function — one whose arguments are single values —
4539
+ * and lifts it over arrays automatically.
4540
+ *
4541
+ * `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
4542
+ * the grid maps it over the range, giving a 99-row array. Without that lifting
4543
+ * the range collapses to one value, so the dynamic array built on top of it —
4544
+ * `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
4545
+ * `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
4546
+ * text/math/date functions then get it for free and cannot each get it subtly
4547
+ * wrong.
4548
+ *
4549
+ * Only mark functions that are genuinely per-cell. Anything that *consumes* an
4550
+ * array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
4551
+ * `register`, or lifting would map it over the array it is meant to reduce.
4552
+ */
4553
+ registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
4554
+ __privateGet(this, _functions).set(name.toUpperCase(), {
4555
+ name: name.toUpperCase(),
4556
+ minArgs,
4557
+ maxArgs,
4558
+ isVolatile: false,
4559
+ handler,
4560
+ elementwise: true
4472
4561
  });
4473
4562
  }
4474
4563
  /** Executes a function by name with argument validation. */
4475
4564
  execute(name, args, ctx) {
4565
+ var _a;
4476
4566
  const def = __privateGet(this, _functions).get(name.toUpperCase());
4477
4567
  if (!def) return FormulaValue.errorName;
4478
4568
  if (args.length < def.minArgs) return FormulaValue.errorValue;
4479
4569
  if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
4480
4570
  try {
4481
- return def.handler(args, ctx);
4571
+ return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
4482
4572
  } catch {
4483
4573
  return FormulaValue.errorValue;
4484
4574
  }
@@ -4494,6 +4584,42 @@ var _FunctionRegistry = class _FunctionRegistry {
4494
4584
  };
4495
4585
  _default = new WeakMap();
4496
4586
  _functions = new WeakMap();
4587
+ _FunctionRegistry_static = new WeakSet();
4588
+ executeElementwise_fn = function(def, args, ctx) {
4589
+ const values = args.map((a) => a.evaluate(ctx));
4590
+ let rows2 = 1, cols = 1;
4591
+ for (const v of values) {
4592
+ if (!v.isArray) continue;
4593
+ const a = v.arrayVal;
4594
+ if (a.rows > rows2) rows2 = a.rows;
4595
+ if (a.columns > cols) cols = a.columns;
4596
+ }
4597
+ if (rows2 === 1 && cols === 1) {
4598
+ return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4599
+ }
4600
+ if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4601
+ for (const v of values) {
4602
+ if (!v.isArray) continue;
4603
+ const a = v.arrayVal;
4604
+ if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4605
+ return FormulaValue.errorValue;
4606
+ }
4607
+ }
4608
+ const nodes = values.map((v) => new ValueNode(v));
4609
+ const out = new ArrayValue(rows2, cols);
4610
+ for (let r = 0; r < rows2; r++) {
4611
+ for (let c = 0; c < cols; c++) {
4612
+ for (let i = 0; i < values.length; i++) {
4613
+ const v = values[i];
4614
+ if (!v.isArray) continue;
4615
+ const a = v.arrayVal;
4616
+ nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4617
+ }
4618
+ out.set(r, c, def.handler(nodes, ctx));
4619
+ }
4620
+ }
4621
+ return FormulaValue.array(out);
4622
+ };
4497
4623
  _FunctionRegistry_instances = new WeakSet();
4498
4624
  registerBuiltIns_fn = function() {
4499
4625
  registerMath(this);
@@ -4508,6 +4634,7 @@ registerBuiltIns_fn = function() {
4508
4634
  registerAggregate(this);
4509
4635
  registerArray(this);
4510
4636
  };
4637
+ __privateAdd(_FunctionRegistry, _FunctionRegistry_static);
4511
4638
  __privateAdd(_FunctionRegistry, _default);
4512
4639
  var FunctionRegistry = _FunctionRegistry;
4513
4640
  var FormulaHelper;
@@ -5369,6 +5496,7 @@ function createWorkbookRecalcModel(workbook) {
5369
5496
  getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
5370
5497
  };
5371
5498
  }
5499
+ var MAX_ADOPTED_SPILL_CELLS = 262144;
5372
5500
  var normSheet = (s) => s.toUpperCase();
5373
5501
  var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
5374
5502
  var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
@@ -5379,7 +5507,7 @@ function parseKey(key) {
5379
5507
  const { row: row2, column: column2 } = parseCellRef(ref);
5380
5508
  return { sheet, ref, row: row2, col: column2 };
5381
5509
  }
5382
- var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_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;
5510
+ 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;
5383
5511
  var RecalcEngine = class {
5384
5512
  constructor(model, registry = FunctionRegistry.default) {
5385
5513
  __privateAdd(this, _RecalcEngine_instances);
@@ -5487,11 +5615,12 @@ var RecalcEngine = class {
5487
5615
  const ast = parseFormulaUncached(text);
5488
5616
  const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
5489
5617
  __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5618
+ const key = keyOf(s, r);
5490
5619
  __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5491
5620
  sheet: s,
5492
5621
  sheetName: sheet,
5493
5622
  ref: r,
5494
- key: keyOf(s, r),
5623
+ key,
5495
5624
  row: row2,
5496
5625
  col: column2,
5497
5626
  formula: text,
@@ -5499,6 +5628,7 @@ var RecalcEngine = class {
5499
5628
  refs,
5500
5629
  volatile
5501
5630
  });
5631
+ __privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
5502
5632
  registered++;
5503
5633
  } catch (err) {
5504
5634
  failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
@@ -5666,6 +5796,51 @@ unregister_fn = function(key) {
5666
5796
  rememberSheet_fn = function(name) {
5667
5797
  __privateGet(this, _sheetNames).set(normSheet(name), name);
5668
5798
  };
5799
+ /**
5800
+ * Takes ownership of the spill block a *file* already declared for an anchor.
5801
+ *
5802
+ * Excel stores a dynamic array as one formula on the anchor plus the spilled
5803
+ * results written as plain cached values in the cells below/right, and records
5804
+ * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5805
+ * engine meets those cached values on the first recalculation, sees literal
5806
+ * content inside the block it wants to spill into, and reports `#SPILL!` for
5807
+ * every dynamic array in every real file. Registering the declared block as
5808
+ * this anchor's existing spill makes them what they actually are — the anchor's
5809
+ * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5810
+ * result smaller than the declared block clears the leftovers.
5811
+ *
5812
+ * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5813
+ * block far larger than any real spill (a corrupt or hand-written `ref`) is
5814
+ * skipped rather than materialised.
5815
+ */
5816
+ adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
5817
+ const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
5818
+ if (!declared || !declared.includes(":")) return;
5819
+ let rect;
5820
+ try {
5821
+ rect = parseRangeRef(declared);
5822
+ } catch {
5823
+ return;
5824
+ }
5825
+ if (rect.startRow !== row2 || rect.startColumn !== col) return;
5826
+ const height = rect.endRow - rect.startRow + 1;
5827
+ const width = rect.endColumn - rect.startColumn + 1;
5828
+ if (height < 1 || width < 1) return;
5829
+ if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5830
+ if (height === 1 && width === 1) return;
5831
+ const members = /* @__PURE__ */ new Set();
5832
+ for (let r = rect.startRow; r <= rect.endRow; r++) {
5833
+ for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5834
+ if (r === row2 && c === col) continue;
5835
+ const key = keyOf(s, cellRefFromRowCol(r, c));
5836
+ if (__privateGet(this, _formulas).has(key)) continue;
5837
+ if (__privateGet(this, _spillOwner).has(key)) continue;
5838
+ members.add(key);
5839
+ __privateGet(this, _spillOwner).set(key, anchorKey);
5840
+ }
5841
+ }
5842
+ if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
5843
+ };
5669
5844
  /**
5670
5845
  * One pass in a caller-supplied order, watching for inversions.
5671
5846
  *