@devmm/puredocs-excel-formula 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,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);
@@ -29,6 +29,13 @@ var FormulaError = /* @__PURE__ */ ((FormulaError2) => {
29
29
  FormulaError2[FormulaError2["Spill"] = 9] = "Spill";
30
30
  return FormulaError2;
31
31
  })(FormulaError || {});
32
+ var DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
33
+ function numericText(text) {
34
+ const trimmed = text.trim();
35
+ if (!DECIMAL.test(trimmed)) return null;
36
+ const n = Number(trimmed);
37
+ return Number.isFinite(n) ? n : null;
38
+ }
32
39
  var TailCall = class {
33
40
  constructor(lambda, args) {
34
41
  this.lambda = lambda;
@@ -805,6 +812,16 @@ var ArrayLiteralNode = class extends FormulaNode {
805
812
  }
806
813
  };
807
814
  _value = new WeakMap();
815
+ var ValueNode = class extends FormulaNode {
816
+ /** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
817
+ constructor(value2) {
818
+ super();
819
+ this.value = value2;
820
+ }
821
+ evaluate() {
822
+ return this.value;
823
+ }
824
+ };
808
825
  var BlankNode = class extends FormulaNode {
809
826
  evaluate() {
810
827
  return FormulaValue.blank;
@@ -1431,6 +1448,7 @@ var indexes = /* @__PURE__ */ new WeakMap();
1431
1448
  function build(arr) {
1432
1449
  const byText = /* @__PURE__ */ new Map();
1433
1450
  const byNumber = /* @__PURE__ */ new Map();
1451
+ const byTextNumber = /* @__PURE__ */ new Map();
1434
1452
  const classes = /* @__PURE__ */ new Map();
1435
1453
  const addToClass = (kind, v, i2) => {
1436
1454
  const group = classes.get(kind);
@@ -1450,6 +1468,14 @@ function build(arr) {
1450
1468
  else byNumber.set(n, [i]);
1451
1469
  } else {
1452
1470
  addToClass(v.kind, v, i);
1471
+ if (v.isText) {
1472
+ const n = numericText(v.textValue);
1473
+ if (n !== null) {
1474
+ const atText2 = byTextNumber.get(n);
1475
+ if (atText2) atText2.push(i);
1476
+ else byTextNumber.set(n, [i]);
1477
+ }
1478
+ }
1453
1479
  }
1454
1480
  i++;
1455
1481
  }
@@ -1458,7 +1484,15 @@ function build(arr) {
1458
1484
  for (let k = 0; k < sortedNumbers.length; k++) {
1459
1485
  cumulative[k + 1] = cumulative[k] + byNumber.get(sortedNumbers[k]).length;
1460
1486
  }
1461
- return { byText, byNumber, sortedNumbers, cumulative, classes: [...classes.values()] };
1487
+ return {
1488
+ byText,
1489
+ byNumber,
1490
+ sortedNumbers,
1491
+ cumulative,
1492
+ byTextNumber,
1493
+ sortedTextNumbers: Float64Array.from(byTextNumber.keys()).sort(),
1494
+ classes: [...classes.values()]
1495
+ };
1462
1496
  }
1463
1497
  function indexOf(arr) {
1464
1498
  let idx = indexes.get(arr);
@@ -1496,19 +1530,25 @@ function matchingPositions(criteriaRange, criteria, minSize) {
1496
1530
  if (criteria.kind === "equalsNumber") {
1497
1531
  const target = criteria.number;
1498
1532
  const idx = indexOf(arr);
1499
- const exact2 = idx.byNumber.get(target);
1500
- let near;
1501
- const { sortedNumbers } = idx;
1502
- for (let k = lowerBound(sortedNumbers, target - EPSILON); k < sortedNumbers.length; k++) {
1503
- const value2 = sortedNumbers[k];
1504
- if (value2 > target + EPSILON) break;
1505
- if (value2 === target) continue;
1506
- if (!criteria.test(FormulaValue.number(value2))) continue;
1507
- (near ?? (near = [])).push(...idx.byNumber.get(value2));
1508
- }
1533
+ const groups = [];
1534
+ const collect = (byValue, sorted) => {
1535
+ const exact2 = byValue.get(target);
1536
+ if (exact2) groups.push(exact2);
1537
+ for (let k = lowerBound(sorted, target - EPSILON); k < sorted.length; k++) {
1538
+ const value2 = sorted[k];
1539
+ if (value2 > target + EPSILON) break;
1540
+ if (value2 === target) continue;
1541
+ if (!criteria.test(FormulaValue.number(value2))) continue;
1542
+ groups.push(byValue.get(value2));
1543
+ }
1544
+ };
1545
+ collect(idx.byNumber, idx.sortedNumbers);
1546
+ collect(idx.byTextNumber, idx.sortedTextNumbers);
1509
1547
  const blanks = criteria.test(FormulaValue.blank) ? blankPositions(idx) : void 0;
1510
- if (!near && !blanks?.length) return exact2 ?? EMPTY;
1511
- const merged = [...exact2 ?? EMPTY, ...near ?? EMPTY, ...blanks ?? EMPTY];
1548
+ if (blanks?.length) groups.push(blanks);
1549
+ if (groups.length === 0) return EMPTY;
1550
+ if (groups.length === 1) return groups[0];
1551
+ const merged = groups.flat();
1512
1552
  merged.sort((a, b) => a - b);
1513
1553
  return merged;
1514
1554
  }
@@ -1574,42 +1614,42 @@ function registerMath(r) {
1574
1614
  r.register("PRODUCT", product, 1);
1575
1615
  r.register("SUMPRODUCT", sumProduct, 1);
1576
1616
  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);
1617
+ r.registerElementwise("ABS", abs, 1, 1);
1618
+ r.registerElementwise("ROUND", round, 2, 2);
1619
+ r.registerElementwise("ROUNDUP", roundUp, 2, 2);
1620
+ r.registerElementwise("ROUNDDOWN", roundDown, 2, 2);
1621
+ r.registerElementwise("CEILING", ceiling, 2, 2);
1622
+ r.registerElementwise("CEILING.MATH", ceiling, 2, 2);
1623
+ r.registerElementwise("FLOOR", floor, 2, 2);
1624
+ r.registerElementwise("FLOOR.MATH", floor, 2, 2);
1625
+ r.registerElementwise("INT", int_, 1, 1);
1626
+ r.registerElementwise("TRUNC", trunc, 1, 2);
1627
+ r.registerElementwise("MOD", mod, 2, 2);
1628
+ r.registerElementwise("QUOTIENT", quotient, 2, 2);
1629
+ r.registerElementwise("POWER", power, 2, 2);
1630
+ r.registerElementwise("SQRT", sqrt, 1, 1);
1631
+ r.registerElementwise("SIGN", sign, 1, 1);
1592
1632
  r.register("PI", pi, 0, 0);
1593
1633
  r.register("RAND", rand, 0, 0, true);
1594
1634
  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);
1635
+ r.registerElementwise("LOG", log, 1, 2);
1636
+ r.registerElementwise("LOG10", log10, 1, 1);
1637
+ r.registerElementwise("LN", ln, 1, 1);
1638
+ r.registerElementwise("EXP", exp, 1, 1);
1639
+ r.registerElementwise("FACT", fact, 1, 1);
1640
+ r.registerElementwise("COMBIN", combin, 2, 2);
1641
+ r.registerElementwise("SIN", unary(Math.sin), 1, 1);
1642
+ r.registerElementwise("COS", unary(Math.cos), 1, 1);
1643
+ r.registerElementwise("TAN", unary(Math.tan), 1, 1);
1644
+ r.registerElementwise("ASIN", asin, 1, 1);
1645
+ r.registerElementwise("ACOS", acos, 1, 1);
1646
+ r.registerElementwise("ATAN", unary(Math.atan), 1, 1);
1647
+ r.registerElementwise("ATAN2", atan2, 2, 2);
1648
+ r.registerElementwise("SINH", unary(Math.sinh), 1, 1);
1649
+ r.registerElementwise("COSH", unary(Math.cosh), 1, 1);
1650
+ r.registerElementwise("TANH", unary(Math.tanh), 1, 1);
1651
+ r.registerElementwise("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1652
+ r.registerElementwise("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1613
1653
  }
1614
1654
  function valueAt(v, i) {
1615
1655
  return v.isArray ? v.arrayVal.getFlat(i) : v;
@@ -2041,31 +2081,31 @@ function combin(a, c) {
2041
2081
  return FormulaValue.number(Math.round(result));
2042
2082
  }
2043
2083
  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);
2084
+ r.registerElementwise("LEFT", left, 1, 2);
2085
+ r.registerElementwise("RIGHT", right, 1, 2);
2086
+ r.registerElementwise("MID", mid, 3, 3);
2087
+ r.registerElementwise("LEN", len, 1, 1);
2088
+ r.registerElementwise("UPPER", upper, 1, 1);
2089
+ r.registerElementwise("LOWER", lower, 1, 1);
2090
+ r.registerElementwise("PROPER", proper, 1, 1);
2091
+ r.registerElementwise("TRIM", trim, 1, 1);
2092
+ r.registerElementwise("CLEAN", clean, 1, 1);
2093
+ r.registerElementwise("SUBSTITUTE", substitute, 3, 4);
2094
+ r.registerElementwise("REPLACE", replace, 4, 4);
2055
2095
  r.register("CONCATENATE", concatenate, 1);
2056
2096
  r.register("CONCAT", concatenate, 1);
2057
2097
  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);
2098
+ r.registerElementwise("TEXT", text_, 2, 2);
2099
+ r.registerElementwise("VALUE", value, 1, 1);
2100
+ r.registerElementwise("FIND", find, 2, 3);
2101
+ r.registerElementwise("SEARCH", search, 2, 3);
2102
+ r.registerElementwise("REPT", rept, 2, 2);
2103
+ r.registerElementwise("EXACT", exact, 2, 2);
2104
+ r.registerElementwise("CHAR", char_, 1, 1);
2105
+ r.registerElementwise("CODE", code, 1, 1);
2106
+ r.registerElementwise("T", t_, 1, 1);
2107
+ r.registerElementwise("TEXTBEFORE", textBefore, 2, 3);
2108
+ r.registerElementwise("TEXTAFTER", textAfter, 2, 3);
2069
2109
  }
2070
2110
  function str(a, c, idx) {
2071
2111
  const r = FormulaHelper.evalString(a[idx], c);
@@ -2379,6 +2419,7 @@ function registerLogical(r) {
2379
2419
  function if_(a, c) {
2380
2420
  const cond = a[0].evaluate(c);
2381
2421
  if (cond.isError) return cond;
2422
+ if (cond.isArray) return ifArray(cond.arrayVal, a, c);
2382
2423
  const b = cond.coerceToBool();
2383
2424
  if (b.isError) return b;
2384
2425
  return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
@@ -2455,13 +2496,63 @@ function xor(a, c) {
2455
2496
  }
2456
2497
  return FormulaValue.boolean(trueCount % 2 === 1);
2457
2498
  }
2458
- function ifError(a, c) {
2499
+ function ifArray(mask, a, c) {
2500
+ let wantTrue = false, wantFalse = false;
2501
+ for (const item of mask.values()) {
2502
+ const b = item.coerceToBool();
2503
+ if (b.isError) return b;
2504
+ if (b.booleanValue) wantTrue = true;
2505
+ else wantFalse = true;
2506
+ if (wantTrue && wantFalse) break;
2507
+ }
2508
+ const yes = wantTrue ? a[1].evaluate(c) : FormulaValue.blank;
2509
+ const no = wantFalse ? a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_ : FormulaValue.blank;
2510
+ const pick = (v, r, col) => {
2511
+ if (!v.isArray) return v;
2512
+ const arr = v.arrayVal;
2513
+ return r < arr.rows && col < arr.columns ? arr.get(r, col) : FormulaValue.errorNA;
2514
+ };
2515
+ const out = new ArrayValue(mask.rows, mask.columns);
2516
+ for (let r = 0; r < mask.rows; r++) {
2517
+ for (let col = 0; col < mask.columns; col++) {
2518
+ const b = mask.get(r, col).coerceToBool();
2519
+ out.set(r, col, b.booleanValue ? pick(yes, r, col) : pick(no, r, col));
2520
+ }
2521
+ }
2522
+ return FormulaValue.array(out);
2523
+ }
2524
+ function ifCaught(a, c, caught) {
2459
2525
  const v = a[0].evaluate(c);
2460
- return v.isError ? a[1].evaluate(c) : v;
2526
+ if (!v.isArray) return caught(v) ? a[1].evaluate(c) : v;
2527
+ const arr = v.arrayVal;
2528
+ let any = false;
2529
+ for (const item of arr.values()) {
2530
+ if (caught(item)) {
2531
+ any = true;
2532
+ break;
2533
+ }
2534
+ }
2535
+ if (!any) return v;
2536
+ const fb = a[1].evaluate(c);
2537
+ const fbArr = fb.isArray ? fb.arrayVal : null;
2538
+ const out = new ArrayValue(arr.rows, arr.columns);
2539
+ for (let r = 0; r < arr.rows; r++) {
2540
+ for (let col = 0; col < arr.columns; col++) {
2541
+ const item = arr.get(r, col);
2542
+ if (!caught(item)) {
2543
+ out.set(r, col, item);
2544
+ continue;
2545
+ }
2546
+ out.set(r, col, fbArr ? r < fbArr.rows && col < fbArr.columns ? fbArr.get(r, col) : FormulaValue.errorNA : fb);
2547
+ }
2548
+ }
2549
+ return FormulaValue.array(out);
2550
+ }
2551
+ function ifError(a, c) {
2552
+ return ifCaught(a, c, (v) => v.isError);
2461
2553
  }
2462
2554
  function ifNa(a, c) {
2463
- const v = a[0].evaluate(c);
2464
- return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
2555
+ return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
2465
2556
  }
2466
2557
  function ifs(a, c) {
2467
2558
  for (let i = 0; i + 1 < a.length; i += 2) {
@@ -2518,21 +2609,21 @@ function fromOA(oa) {
2518
2609
  function registerDate(r) {
2519
2610
  r.register("NOW", now, 0, 0, true);
2520
2611
  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);
2612
+ r.registerElementwise("DATE", date_, 3, 3);
2613
+ r.registerElementwise("YEAR", year, 1, 1);
2614
+ r.registerElementwise("MONTH", month, 1, 1);
2615
+ r.registerElementwise("DAY", day, 1, 1);
2616
+ r.registerElementwise("HOUR", hour, 1, 1);
2617
+ r.registerElementwise("MINUTE", minute, 1, 1);
2618
+ r.registerElementwise("SECOND", second, 1, 1);
2619
+ r.registerElementwise("DATEVALUE", dateValue, 1, 1);
2620
+ r.registerElementwise("DAYS", days, 2, 2);
2621
+ r.registerElementwise("EDATE", edate, 2, 2);
2622
+ r.registerElementwise("EOMONTH", eomonth, 2, 2);
2623
+ r.registerElementwise("WEEKDAY", weekday, 1, 2);
2624
+ r.registerElementwise("WEEKNUM", weeknum, 1, 2);
2534
2625
  r.register("NETWORKDAYS", networkdays, 2, 3);
2535
- r.register("DATEDIF", datedif, 3, 3);
2626
+ r.registerElementwise("DATEDIF", datedif, 3, 3);
2536
2627
  }
2537
2628
  function evalDate(a, c, idx) {
2538
2629
  const r = FormulaHelper.evalDouble(a[idx], c);
@@ -3345,20 +3436,20 @@ function ifsAggregate(a, c, mode2) {
3345
3436
 
3346
3437
  // src/functions/info-functions.ts
3347
3438
  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);
3439
+ r.registerElementwise("ISBLANK", isBlank, 1, 1);
3440
+ r.registerElementwise("ISNUMBER", isNumber, 1, 1);
3441
+ r.registerElementwise("ISTEXT", isText, 1, 1);
3442
+ r.registerElementwise("ISERROR", isError, 1, 1);
3443
+ r.registerElementwise("ISERR", isErr, 1, 1);
3444
+ r.registerElementwise("ISLOGICAL", isLogical, 1, 1);
3445
+ r.registerElementwise("ISNA", isNa, 1, 1);
3446
+ r.registerElementwise("ISNONTEXT", isNonText, 1, 1);
3356
3447
  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);
3448
+ r.registerElementwise("TYPE", type_, 1, 1);
3449
+ r.registerElementwise("N", n_, 1, 1);
3450
+ r.registerElementwise("ISODD", isOdd, 1, 1);
3451
+ r.registerElementwise("ISEVEN", isEven, 1, 1);
3452
+ r.registerElementwise("ERROR.TYPE", errorType, 1, 1);
3362
3453
  }
3363
3454
  function isBlank(a, c) {
3364
3455
  return FormulaValue.boolean(a[0].evaluate(c).isBlank);
@@ -4446,7 +4537,8 @@ function toRow(a, c) {
4446
4537
  }
4447
4538
 
4448
4539
  // src/function-registry.ts
4449
- var _default, _functions, _FunctionRegistry_instances, registerBuiltIns_fn;
4540
+ var MAX_LIFTED_CELLS = 1048576;
4541
+ var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
4450
4542
  var _FunctionRegistry = class _FunctionRegistry {
4451
4543
  constructor() {
4452
4544
  __privateAdd(this, _FunctionRegistry_instances);
@@ -4468,17 +4560,45 @@ var _FunctionRegistry = class _FunctionRegistry {
4468
4560
  minArgs,
4469
4561
  maxArgs,
4470
4562
  isVolatile,
4471
- handler
4563
+ handler,
4564
+ elementwise: false
4565
+ });
4566
+ }
4567
+ /**
4568
+ * Registers a **scalar** function — one whose arguments are single values —
4569
+ * and lifts it over arrays automatically.
4570
+ *
4571
+ * `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
4572
+ * the grid maps it over the range, giving a 99-row array. Without that lifting
4573
+ * the range collapses to one value, so the dynamic array built on top of it —
4574
+ * `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
4575
+ * `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
4576
+ * text/math/date functions then get it for free and cannot each get it subtly
4577
+ * wrong.
4578
+ *
4579
+ * Only mark functions that are genuinely per-cell. Anything that *consumes* an
4580
+ * array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
4581
+ * `register`, or lifting would map it over the array it is meant to reduce.
4582
+ */
4583
+ registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
4584
+ __privateGet(this, _functions).set(name.toUpperCase(), {
4585
+ name: name.toUpperCase(),
4586
+ minArgs,
4587
+ maxArgs,
4588
+ isVolatile: false,
4589
+ handler,
4590
+ elementwise: true
4472
4591
  });
4473
4592
  }
4474
4593
  /** Executes a function by name with argument validation. */
4475
4594
  execute(name, args, ctx) {
4595
+ var _a;
4476
4596
  const def = __privateGet(this, _functions).get(name.toUpperCase());
4477
4597
  if (!def) return FormulaValue.errorName;
4478
4598
  if (args.length < def.minArgs) return FormulaValue.errorValue;
4479
4599
  if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
4480
4600
  try {
4481
- return def.handler(args, ctx);
4601
+ return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
4482
4602
  } catch {
4483
4603
  return FormulaValue.errorValue;
4484
4604
  }
@@ -4494,6 +4614,42 @@ var _FunctionRegistry = class _FunctionRegistry {
4494
4614
  };
4495
4615
  _default = new WeakMap();
4496
4616
  _functions = new WeakMap();
4617
+ _FunctionRegistry_static = new WeakSet();
4618
+ executeElementwise_fn = function(def, args, ctx) {
4619
+ const values = args.map((a) => a.evaluate(ctx));
4620
+ let rows2 = 1, cols = 1;
4621
+ for (const v of values) {
4622
+ if (!v.isArray) continue;
4623
+ const a = v.arrayVal;
4624
+ if (a.rows > rows2) rows2 = a.rows;
4625
+ if (a.columns > cols) cols = a.columns;
4626
+ }
4627
+ if (rows2 === 1 && cols === 1) {
4628
+ return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4629
+ }
4630
+ if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4631
+ for (const v of values) {
4632
+ if (!v.isArray) continue;
4633
+ const a = v.arrayVal;
4634
+ if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4635
+ return FormulaValue.errorValue;
4636
+ }
4637
+ }
4638
+ const nodes = values.map((v) => new ValueNode(v));
4639
+ const out = new ArrayValue(rows2, cols);
4640
+ for (let r = 0; r < rows2; r++) {
4641
+ for (let c = 0; c < cols; c++) {
4642
+ for (let i = 0; i < values.length; i++) {
4643
+ const v = values[i];
4644
+ if (!v.isArray) continue;
4645
+ const a = v.arrayVal;
4646
+ nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4647
+ }
4648
+ out.set(r, c, def.handler(nodes, ctx));
4649
+ }
4650
+ }
4651
+ return FormulaValue.array(out);
4652
+ };
4497
4653
  _FunctionRegistry_instances = new WeakSet();
4498
4654
  registerBuiltIns_fn = function() {
4499
4655
  registerMath(this);
@@ -4508,6 +4664,7 @@ registerBuiltIns_fn = function() {
4508
4664
  registerAggregate(this);
4509
4665
  registerArray(this);
4510
4666
  };
4667
+ __privateAdd(_FunctionRegistry, _FunctionRegistry_static);
4511
4668
  __privateAdd(_FunctionRegistry, _default);
4512
4669
  var FunctionRegistry = _FunctionRegistry;
4513
4670
  var FormulaHelper;
@@ -4573,7 +4730,8 @@ var FormulaHelper;
4573
4730
  return rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
4574
4731
  }
4575
4732
  if (op === "<>") {
4576
- return { kind: "other", test: (v) => !FormulaValue.areEqual(v, rhsValue) };
4733
+ const equals = rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
4734
+ return { kind: "other", test: (v) => !equals.test(v) };
4577
4735
  }
4578
4736
  const wanted = op === ">" ? (c) => c > 0 : op === ">=" ? (c) => c >= 0 : op === "<" ? (c) => c < 0 : (c) => c <= 0;
4579
4737
  const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
@@ -4589,7 +4747,16 @@ var FormulaHelper;
4589
4747
  FormulaHelper2.compileCriteria = compileCriteria;
4590
4748
  function numberEquality(n) {
4591
4749
  const target = FormulaValue.number(n);
4592
- return { kind: "equalsNumber", number: n, test: (v) => FormulaValue.areEqual(v, target) };
4750
+ return {
4751
+ kind: "equalsNumber",
4752
+ number: n,
4753
+ test: (v) => {
4754
+ if (FormulaValue.areEqual(v, target)) return true;
4755
+ if (!v.isText) return false;
4756
+ const asNumber = numericText(v.textValue);
4757
+ return asNumber !== null && Math.abs(asNumber - n) < 1e-10;
4758
+ }
4759
+ };
4593
4760
  }
4594
4761
  function textEquality(s) {
4595
4762
  const upper2 = s.toUpperCase();
@@ -5369,6 +5536,7 @@ function createWorkbookRecalcModel(workbook) {
5369
5536
  getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
5370
5537
  };
5371
5538
  }
5539
+ var MAX_ADOPTED_SPILL_CELLS = 262144;
5372
5540
  var normSheet = (s) => s.toUpperCase();
5373
5541
  var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
5374
5542
  var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
@@ -5379,7 +5547,7 @@ function parseKey(key) {
5379
5547
  const { row: row2, column: column2 } = parseCellRef(ref);
5380
5548
  return { sheet, ref, row: row2, col: column2 };
5381
5549
  }
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;
5550
+ 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
5551
  var RecalcEngine = class {
5384
5552
  constructor(model, registry = FunctionRegistry.default) {
5385
5553
  __privateAdd(this, _RecalcEngine_instances);
@@ -5487,11 +5655,12 @@ var RecalcEngine = class {
5487
5655
  const ast = parseFormulaUncached(text);
5488
5656
  const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
5489
5657
  __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5658
+ const key = keyOf(s, r);
5490
5659
  __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5491
5660
  sheet: s,
5492
5661
  sheetName: sheet,
5493
5662
  ref: r,
5494
- key: keyOf(s, r),
5663
+ key,
5495
5664
  row: row2,
5496
5665
  col: column2,
5497
5666
  formula: text,
@@ -5499,6 +5668,7 @@ var RecalcEngine = class {
5499
5668
  refs,
5500
5669
  volatile
5501
5670
  });
5671
+ __privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
5502
5672
  registered++;
5503
5673
  } catch (err) {
5504
5674
  failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
@@ -5666,6 +5836,51 @@ unregister_fn = function(key) {
5666
5836
  rememberSheet_fn = function(name) {
5667
5837
  __privateGet(this, _sheetNames).set(normSheet(name), name);
5668
5838
  };
5839
+ /**
5840
+ * Takes ownership of the spill block a *file* already declared for an anchor.
5841
+ *
5842
+ * Excel stores a dynamic array as one formula on the anchor plus the spilled
5843
+ * results written as plain cached values in the cells below/right, and records
5844
+ * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5845
+ * engine meets those cached values on the first recalculation, sees literal
5846
+ * content inside the block it wants to spill into, and reports `#SPILL!` for
5847
+ * every dynamic array in every real file. Registering the declared block as
5848
+ * this anchor's existing spill makes them what they actually are — the anchor's
5849
+ * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5850
+ * result smaller than the declared block clears the leftovers.
5851
+ *
5852
+ * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5853
+ * block far larger than any real spill (a corrupt or hand-written `ref`) is
5854
+ * skipped rather than materialised.
5855
+ */
5856
+ adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
5857
+ const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
5858
+ if (!declared || !declared.includes(":")) return;
5859
+ let rect;
5860
+ try {
5861
+ rect = parseRangeRef(declared);
5862
+ } catch {
5863
+ return;
5864
+ }
5865
+ if (rect.startRow !== row2 || rect.startColumn !== col) return;
5866
+ const height = rect.endRow - rect.startRow + 1;
5867
+ const width = rect.endColumn - rect.startColumn + 1;
5868
+ if (height < 1 || width < 1) return;
5869
+ if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5870
+ if (height === 1 && width === 1) return;
5871
+ const members = /* @__PURE__ */ new Set();
5872
+ for (let r = rect.startRow; r <= rect.endRow; r++) {
5873
+ for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5874
+ if (r === row2 && c === col) continue;
5875
+ const key = keyOf(s, cellRefFromRowCol(r, c));
5876
+ if (__privateGet(this, _formulas).has(key)) continue;
5877
+ if (__privateGet(this, _spillOwner).has(key)) continue;
5878
+ members.add(key);
5879
+ __privateGet(this, _spillOwner).set(key, anchorKey);
5880
+ }
5881
+ }
5882
+ if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
5883
+ };
5669
5884
  /**
5670
5885
  * One pass in a caller-supplied order, watching for inversions.
5671
5886
  *