@devmm/puredocs-excel-formula 1.0.7 → 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.cjs CHANGED
@@ -448,8 +448,11 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
448
448
  TokenType2[TokenType2["SpillReference"] = 24] = "SpillReference";
449
449
  TokenType2[TokenType2["Exclamation"] = 25] = "Exclamation";
450
450
  TokenType2[TokenType2["AtSign"] = 26] = "AtSign";
451
- TokenType2[TokenType2["ErrorLiteral"] = 27] = "ErrorLiteral";
452
- TokenType2[TokenType2["EOF"] = 28] = "EOF";
451
+ TokenType2[TokenType2["LeftBrace"] = 27] = "LeftBrace";
452
+ TokenType2[TokenType2["RightBrace"] = 28] = "RightBrace";
453
+ TokenType2[TokenType2["Semicolon"] = 29] = "Semicolon";
454
+ TokenType2[TokenType2["ErrorLiteral"] = 30] = "ErrorLiteral";
455
+ TokenType2[TokenType2["EOF"] = 31] = "EOF";
453
456
  return TokenType2;
454
457
  })(TokenType || {});
455
458
  function makeToken(type, value2, position) {
@@ -463,6 +466,8 @@ var FormulaException = class extends Error {
463
466
  this.name = "FormulaException";
464
467
  }
465
468
  };
469
+ var UNDERSCORE = 95;
470
+ var RESERVED_PREFIXES = ["_XLFN.", "_XLWS.", "_XLPM."];
466
471
  var KNOWN_ERRORS = /* @__PURE__ */ new Set([
467
472
  "#NULL!",
468
473
  "#DIV/0!",
@@ -474,7 +479,7 @@ var KNOWN_ERRORS = /* @__PURE__ */ new Set([
474
479
  "#CALC!",
475
480
  "#SPILL!"
476
481
  ]);
477
- var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
482
+ 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;
478
483
  var FormulaLexer = class {
479
484
  constructor(formula) {
480
485
  __privateAdd(this, _FormulaLexer_instances);
@@ -492,7 +497,7 @@ var FormulaLexer = class {
492
497
  const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
493
498
  if (token) tokens.push(token);
494
499
  }
495
- tokens.push(makeToken(28 /* EOF */, "", __privateGet(this, _pos)));
500
+ tokens.push(makeToken(31 /* EOF */, "", __privateGet(this, _pos)));
496
501
  return tokens;
497
502
  }
498
503
  };
@@ -546,7 +551,7 @@ readErrorLiteral_fn = function() {
546
551
  if (!KNOWN_ERRORS.has(upper2)) {
547
552
  throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
548
553
  }
549
- return makeToken(27 /* ErrorLiteral */, upper2, start);
554
+ return makeToken(30 /* ErrorLiteral */, upper2, start);
550
555
  };
551
556
  readQuotedSheetRef_fn = function() {
552
557
  const start = __privateWrapper(this, _pos)._++;
@@ -609,10 +614,8 @@ readIdentifier_fn = function() {
609
614
  } else break;
610
615
  }
611
616
  let upper2 = raw.toUpperCase();
612
- if (upper2.startsWith("_XLFN.")) {
613
- raw = raw.slice(6);
614
- upper2 = upper2.slice(6);
615
- }
617
+ raw = __privateMethod(this, _FormulaLexer_instances, stripReservedPrefixes_fn).call(this, raw, upper2);
618
+ if (raw.length !== upper2.length) upper2 = upper2.slice(upper2.length - raw.length);
616
619
  if (upper2 === "TRUE") return makeToken(2 /* Boolean */, "TRUE", start);
617
620
  if (upper2 === "FALSE") return makeToken(2 /* Boolean */, "FALSE", start);
618
621
  if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "!") {
@@ -634,6 +637,30 @@ readIdentifier_fn = function() {
634
637
  }
635
638
  return makeToken(23 /* NamedRange */, raw, start);
636
639
  };
640
+ /**
641
+ * Drops Excel's storage-level name decorations. They carry no semantics — the
642
+ * writer stamps them so an older reader fails loudly instead of silently
643
+ * mis-evaluating — and they *stack*: `_xlfn._xlws.FILTER` wears two. Stripping
644
+ * one prefix once, as this used to, leaves `_XLWS.FILTER` unresolvable.
645
+ *
646
+ * Every identifier in every formula passes through here, so the common case is
647
+ * gated on a single char-code test: a name not starting with '_' returns
648
+ * untouched without so much as a comparison against the prefix table.
649
+ */
650
+ stripReservedPrefixes_fn = function(raw, upper2) {
651
+ if (raw.charCodeAt(0) !== UNDERSCORE) return raw;
652
+ let cut = 0;
653
+ outer: for (; ; ) {
654
+ for (const prefix of RESERVED_PREFIXES) {
655
+ if (upper2.startsWith(prefix, cut)) {
656
+ cut += prefix.length;
657
+ continue outer;
658
+ }
659
+ }
660
+ break;
661
+ }
662
+ return cut > 0 && cut < raw.length ? raw.slice(cut) : raw;
663
+ };
637
664
  readCellRefChars_fn = function() {
638
665
  let ref = "";
639
666
  while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
@@ -677,6 +704,14 @@ readOperator_fn = function() {
677
704
  return makeToken(26 /* AtSign */, "@", start);
678
705
  case "!":
679
706
  return makeToken(25 /* Exclamation */, "!", start);
707
+ case "{":
708
+ return makeToken(27 /* LeftBrace */, "{", start);
709
+ case "}":
710
+ return makeToken(28 /* RightBrace */, "}", start);
711
+ // Inside an array constant ';' separates rows. Excel always stores ',' as
712
+ // the argument separator regardless of locale, so ';' has no other role.
713
+ case ";":
714
+ return makeToken(29 /* Semicolon */, ";", start);
680
715
  case "<":
681
716
  if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
682
717
  __privateWrapper(this, _pos)._++;
@@ -759,6 +794,29 @@ var ErrorNode = class extends FormulaNode {
759
794
  return this.errorValue;
760
795
  }
761
796
  };
797
+ var _value;
798
+ var ArrayLiteralNode = class extends FormulaNode {
799
+ constructor(array) {
800
+ super();
801
+ this.array = array;
802
+ __privateAdd(this, _value);
803
+ __privateSet(this, _value, FormulaValue.array(array));
804
+ }
805
+ evaluate() {
806
+ return __privateGet(this, _value);
807
+ }
808
+ };
809
+ _value = new WeakMap();
810
+ var ValueNode = class extends FormulaNode {
811
+ /** Mutable so one wrapper can be re-pointed across a lifted call's cells. */
812
+ constructor(value2) {
813
+ super();
814
+ this.value = value2;
815
+ }
816
+ evaluate() {
817
+ return this.value;
818
+ }
819
+ };
762
820
  var BlankNode = class extends FormulaNode {
763
821
  evaluate() {
764
822
  return FormulaValue.blank;
@@ -1036,7 +1094,7 @@ var CallNode = class extends FormulaNode {
1036
1094
  };
1037
1095
 
1038
1096
  // src/formula-parser.ts
1039
- 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, parseFunction_fn, parseArgList_fn;
1097
+ 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;
1040
1098
  var FormulaParser = class {
1041
1099
  constructor(tokens) {
1042
1100
  __privateAdd(this, _FormulaParser_instances);
@@ -1047,7 +1105,7 @@ var FormulaParser = class {
1047
1105
  parse() {
1048
1106
  __privateSet(this, _pos2, 0);
1049
1107
  const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
1050
- if (__privateGet(this, _FormulaParser_instances, current_get).type !== 28 /* EOF */) {
1108
+ if (__privateGet(this, _FormulaParser_instances, current_get).type !== 31 /* EOF */) {
1051
1109
  throw new FormulaException(
1052
1110
  `Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
1053
1111
  );
@@ -1059,7 +1117,7 @@ _tokens = new WeakMap();
1059
1117
  _pos2 = new WeakMap();
1060
1118
  _FormulaParser_instances = new WeakSet();
1061
1119
  current_get = function() {
1062
- return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 28 /* EOF */, value: "", position: -1 };
1120
+ return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 31 /* EOF */, value: "", position: -1 };
1063
1121
  };
1064
1122
  advance_fn = function() {
1065
1123
  const t = __privateGet(this, _FormulaParser_instances, current_get);
@@ -1174,7 +1232,7 @@ parsePrimary_fn = function() {
1174
1232
  case 2 /* Boolean */:
1175
1233
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1176
1234
  return new BooleanNode(token.value === "TRUE");
1177
- case 27 /* ErrorLiteral */:
1235
+ case 30 /* ErrorLiteral */:
1178
1236
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1179
1237
  return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
1180
1238
  case 3 /* CellReference */: {
@@ -1213,6 +1271,8 @@ parsePrimary_fn = function() {
1213
1271
  return new SpillReferenceNode(token.value);
1214
1272
  case 5 /* Function */:
1215
1273
  return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
1274
+ case 27 /* LeftBrace */:
1275
+ return __privateMethod(this, _FormulaParser_instances, parseArrayLiteral_fn).call(this);
1216
1276
  case 6 /* LeftParen */: {
1217
1277
  __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1218
1278
  const expr = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
@@ -1247,6 +1307,79 @@ tryParseFullRange_fn = function(startText, sheetName) {
1247
1307
  const hi = Math.max(start.index, end.index);
1248
1308
  return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
1249
1309
  };
1310
+ /**
1311
+ * Parses an array constant: `{1,2;3,4}` — ',' separates columns, ';' rows.
1312
+ *
1313
+ * Excel permits only literals inside one (no references, no calls, not even
1314
+ * arithmetic beyond a leading sign), so the value is fully determined here and
1315
+ * is materialised once into the node instead of being rebuilt on every eval.
1316
+ *
1317
+ * A ragged constant is rejected rather than padded: Excel refuses to accept
1318
+ * one at entry, so a file cannot legitimately contain it, and silently filling
1319
+ * the gaps would invent data the author never wrote.
1320
+ */
1321
+ parseArrayLiteral_fn = function() {
1322
+ const open = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1323
+ const rows2 = [];
1324
+ let row2 = [];
1325
+ for (; ; ) {
1326
+ row2.push(__privateMethod(this, _FormulaParser_instances, parseArrayElement_fn).call(this));
1327
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
1328
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1329
+ continue;
1330
+ }
1331
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 29 /* Semicolon */) {
1332
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1333
+ rows2.push(row2);
1334
+ row2 = [];
1335
+ continue;
1336
+ }
1337
+ break;
1338
+ }
1339
+ rows2.push(row2);
1340
+ __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 28 /* RightBrace */);
1341
+ const cols = rows2[0].length;
1342
+ if (rows2.some((r) => r.length !== cols)) {
1343
+ throw new FormulaException(
1344
+ `Array constant at position ${open.position} has rows of differing lengths.`
1345
+ );
1346
+ }
1347
+ const array = new ArrayValue(rows2.length, cols);
1348
+ for (let r = 0; r < rows2.length; r++) {
1349
+ for (let c = 0; c < cols; c++) array.set(r, c, rows2[r][c]);
1350
+ }
1351
+ return new ArrayLiteralNode(array);
1352
+ };
1353
+ /** One element of an array constant: a literal, optionally signed. */
1354
+ parseArrayElement_fn = function() {
1355
+ let negate = false;
1356
+ let signed = false;
1357
+ while (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */ || __privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
1358
+ if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) negate = !negate;
1359
+ signed = true;
1360
+ __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1361
+ }
1362
+ const token = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1363
+ switch (token.type) {
1364
+ case 0 /* Number */: {
1365
+ const n = parseFloat(token.value);
1366
+ if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
1367
+ return FormulaValue.number(negate ? -n : n);
1368
+ }
1369
+ case 1 /* String */:
1370
+ if (signed) break;
1371
+ return FormulaValue.text(token.value);
1372
+ case 2 /* Boolean */:
1373
+ if (signed) break;
1374
+ return FormulaValue.boolean(token.value === "TRUE");
1375
+ case 30 /* ErrorLiteral */:
1376
+ if (signed) break;
1377
+ return FormulaValue.error(FormulaValue.errorFromString(token.value));
1378
+ }
1379
+ throw new FormulaException(
1380
+ `Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
1381
+ );
1382
+ };
1250
1383
  parseFunction_fn = function() {
1251
1384
  const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
1252
1385
  __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
@@ -1453,42 +1586,42 @@ function registerMath(r) {
1453
1586
  r.register("PRODUCT", product, 1);
1454
1587
  r.register("SUMPRODUCT", sumProduct, 1);
1455
1588
  r.register("SUMSQ", sumSq, 1);
1456
- r.register("ABS", abs, 1, 1);
1457
- r.register("ROUND", round, 2, 2);
1458
- r.register("ROUNDUP", roundUp, 2, 2);
1459
- r.register("ROUNDDOWN", roundDown, 2, 2);
1460
- r.register("CEILING", ceiling, 2, 2);
1461
- r.register("CEILING.MATH", ceiling, 2, 2);
1462
- r.register("FLOOR", floor, 2, 2);
1463
- r.register("FLOOR.MATH", floor, 2, 2);
1464
- r.register("INT", int_, 1, 1);
1465
- r.register("TRUNC", trunc, 1, 2);
1466
- r.register("MOD", mod, 2, 2);
1467
- r.register("QUOTIENT", quotient, 2, 2);
1468
- r.register("POWER", power, 2, 2);
1469
- r.register("SQRT", sqrt, 1, 1);
1470
- r.register("SIGN", sign, 1, 1);
1589
+ r.registerElementwise("ABS", abs, 1, 1);
1590
+ r.registerElementwise("ROUND", round, 2, 2);
1591
+ r.registerElementwise("ROUNDUP", roundUp, 2, 2);
1592
+ r.registerElementwise("ROUNDDOWN", roundDown, 2, 2);
1593
+ r.registerElementwise("CEILING", ceiling, 2, 2);
1594
+ r.registerElementwise("CEILING.MATH", ceiling, 2, 2);
1595
+ r.registerElementwise("FLOOR", floor, 2, 2);
1596
+ r.registerElementwise("FLOOR.MATH", floor, 2, 2);
1597
+ r.registerElementwise("INT", int_, 1, 1);
1598
+ r.registerElementwise("TRUNC", trunc, 1, 2);
1599
+ r.registerElementwise("MOD", mod, 2, 2);
1600
+ r.registerElementwise("QUOTIENT", quotient, 2, 2);
1601
+ r.registerElementwise("POWER", power, 2, 2);
1602
+ r.registerElementwise("SQRT", sqrt, 1, 1);
1603
+ r.registerElementwise("SIGN", sign, 1, 1);
1471
1604
  r.register("PI", pi, 0, 0);
1472
1605
  r.register("RAND", rand, 0, 0, true);
1473
1606
  r.register("RANDBETWEEN", randBetween, 2, 2, true);
1474
- r.register("LOG", log, 1, 2);
1475
- r.register("LOG10", log10, 1, 1);
1476
- r.register("LN", ln, 1, 1);
1477
- r.register("EXP", exp, 1, 1);
1478
- r.register("FACT", fact, 1, 1);
1479
- r.register("COMBIN", combin, 2, 2);
1480
- r.register("SIN", unary(Math.sin), 1, 1);
1481
- r.register("COS", unary(Math.cos), 1, 1);
1482
- r.register("TAN", unary(Math.tan), 1, 1);
1483
- r.register("ASIN", asin, 1, 1);
1484
- r.register("ACOS", acos, 1, 1);
1485
- r.register("ATAN", unary(Math.atan), 1, 1);
1486
- r.register("ATAN2", atan2, 2, 2);
1487
- r.register("SINH", unary(Math.sinh), 1, 1);
1488
- r.register("COSH", unary(Math.cosh), 1, 1);
1489
- r.register("TANH", unary(Math.tanh), 1, 1);
1490
- r.register("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1491
- r.register("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1607
+ r.registerElementwise("LOG", log, 1, 2);
1608
+ r.registerElementwise("LOG10", log10, 1, 1);
1609
+ r.registerElementwise("LN", ln, 1, 1);
1610
+ r.registerElementwise("EXP", exp, 1, 1);
1611
+ r.registerElementwise("FACT", fact, 1, 1);
1612
+ r.registerElementwise("COMBIN", combin, 2, 2);
1613
+ r.registerElementwise("SIN", unary(Math.sin), 1, 1);
1614
+ r.registerElementwise("COS", unary(Math.cos), 1, 1);
1615
+ r.registerElementwise("TAN", unary(Math.tan), 1, 1);
1616
+ r.registerElementwise("ASIN", asin, 1, 1);
1617
+ r.registerElementwise("ACOS", acos, 1, 1);
1618
+ r.registerElementwise("ATAN", unary(Math.atan), 1, 1);
1619
+ r.registerElementwise("ATAN2", atan2, 2, 2);
1620
+ r.registerElementwise("SINH", unary(Math.sinh), 1, 1);
1621
+ r.registerElementwise("COSH", unary(Math.cosh), 1, 1);
1622
+ r.registerElementwise("TANH", unary(Math.tanh), 1, 1);
1623
+ r.registerElementwise("DEGREES", unary((r2d) => r2d * 180 / Math.PI), 1, 1);
1624
+ r.registerElementwise("RADIANS", unary((d) => d * Math.PI / 180), 1, 1);
1492
1625
  }
1493
1626
  function valueAt(v, i) {
1494
1627
  return v.isArray ? v.arrayVal.getFlat(i) : v;
@@ -1920,31 +2053,31 @@ function combin(a, c) {
1920
2053
  return FormulaValue.number(Math.round(result));
1921
2054
  }
1922
2055
  function registerText(r) {
1923
- r.register("LEFT", left, 1, 2);
1924
- r.register("RIGHT", right, 1, 2);
1925
- r.register("MID", mid, 3, 3);
1926
- r.register("LEN", len, 1, 1);
1927
- r.register("UPPER", upper, 1, 1);
1928
- r.register("LOWER", lower, 1, 1);
1929
- r.register("PROPER", proper, 1, 1);
1930
- r.register("TRIM", trim, 1, 1);
1931
- r.register("CLEAN", clean, 1, 1);
1932
- r.register("SUBSTITUTE", substitute, 3, 4);
1933
- r.register("REPLACE", replace, 4, 4);
2056
+ r.registerElementwise("LEFT", left, 1, 2);
2057
+ r.registerElementwise("RIGHT", right, 1, 2);
2058
+ r.registerElementwise("MID", mid, 3, 3);
2059
+ r.registerElementwise("LEN", len, 1, 1);
2060
+ r.registerElementwise("UPPER", upper, 1, 1);
2061
+ r.registerElementwise("LOWER", lower, 1, 1);
2062
+ r.registerElementwise("PROPER", proper, 1, 1);
2063
+ r.registerElementwise("TRIM", trim, 1, 1);
2064
+ r.registerElementwise("CLEAN", clean, 1, 1);
2065
+ r.registerElementwise("SUBSTITUTE", substitute, 3, 4);
2066
+ r.registerElementwise("REPLACE", replace, 4, 4);
1934
2067
  r.register("CONCATENATE", concatenate, 1);
1935
2068
  r.register("CONCAT", concatenate, 1);
1936
2069
  r.register("TEXTJOIN", textJoin, 3);
1937
- r.register("TEXT", text_, 2, 2);
1938
- r.register("VALUE", value, 1, 1);
1939
- r.register("FIND", find, 2, 3);
1940
- r.register("SEARCH", search, 2, 3);
1941
- r.register("REPT", rept, 2, 2);
1942
- r.register("EXACT", exact, 2, 2);
1943
- r.register("CHAR", char_, 1, 1);
1944
- r.register("CODE", code, 1, 1);
1945
- r.register("T", t_, 1, 1);
1946
- r.register("TEXTBEFORE", textBefore, 2, 3);
1947
- r.register("TEXTAFTER", textAfter, 2, 3);
2070
+ r.registerElementwise("TEXT", text_, 2, 2);
2071
+ r.registerElementwise("VALUE", value, 1, 1);
2072
+ r.registerElementwise("FIND", find, 2, 3);
2073
+ r.registerElementwise("SEARCH", search, 2, 3);
2074
+ r.registerElementwise("REPT", rept, 2, 2);
2075
+ r.registerElementwise("EXACT", exact, 2, 2);
2076
+ r.registerElementwise("CHAR", char_, 1, 1);
2077
+ r.registerElementwise("CODE", code, 1, 1);
2078
+ r.registerElementwise("T", t_, 1, 1);
2079
+ r.registerElementwise("TEXTBEFORE", textBefore, 2, 3);
2080
+ r.registerElementwise("TEXTAFTER", textAfter, 2, 3);
1948
2081
  }
1949
2082
  function str(a, c, idx) {
1950
2083
  const r = exports.FormulaHelper.evalString(a[idx], c);
@@ -2258,6 +2391,7 @@ function registerLogical(r) {
2258
2391
  function if_(a, c) {
2259
2392
  const cond = a[0].evaluate(c);
2260
2393
  if (cond.isError) return cond;
2394
+ if (cond.isArray) return ifArray(cond.arrayVal, a, c);
2261
2395
  const b = cond.coerceToBool();
2262
2396
  if (b.isError) return b;
2263
2397
  return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
@@ -2334,13 +2468,63 @@ function xor(a, c) {
2334
2468
  }
2335
2469
  return FormulaValue.boolean(trueCount % 2 === 1);
2336
2470
  }
2337
- function ifError(a, c) {
2471
+ function ifArray(mask, a, c) {
2472
+ let wantTrue = false, wantFalse = false;
2473
+ for (const item of mask.values()) {
2474
+ const b = item.coerceToBool();
2475
+ if (b.isError) return b;
2476
+ if (b.booleanValue) wantTrue = true;
2477
+ else wantFalse = true;
2478
+ if (wantTrue && wantFalse) break;
2479
+ }
2480
+ const yes = wantTrue ? a[1].evaluate(c) : FormulaValue.blank;
2481
+ const no = wantFalse ? a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_ : FormulaValue.blank;
2482
+ const pick = (v, r, col) => {
2483
+ if (!v.isArray) return v;
2484
+ const arr = v.arrayVal;
2485
+ return r < arr.rows && col < arr.columns ? arr.get(r, col) : FormulaValue.errorNA;
2486
+ };
2487
+ const out = new ArrayValue(mask.rows, mask.columns);
2488
+ for (let r = 0; r < mask.rows; r++) {
2489
+ for (let col = 0; col < mask.columns; col++) {
2490
+ const b = mask.get(r, col).coerceToBool();
2491
+ out.set(r, col, b.booleanValue ? pick(yes, r, col) : pick(no, r, col));
2492
+ }
2493
+ }
2494
+ return FormulaValue.array(out);
2495
+ }
2496
+ function ifCaught(a, c, caught) {
2338
2497
  const v = a[0].evaluate(c);
2339
- return v.isError ? a[1].evaluate(c) : v;
2498
+ if (!v.isArray) return caught(v) ? a[1].evaluate(c) : v;
2499
+ const arr = v.arrayVal;
2500
+ let any = false;
2501
+ for (const item of arr.values()) {
2502
+ if (caught(item)) {
2503
+ any = true;
2504
+ break;
2505
+ }
2506
+ }
2507
+ if (!any) return v;
2508
+ const fb = a[1].evaluate(c);
2509
+ const fbArr = fb.isArray ? fb.arrayVal : null;
2510
+ const out = new ArrayValue(arr.rows, arr.columns);
2511
+ for (let r = 0; r < arr.rows; r++) {
2512
+ for (let col = 0; col < arr.columns; col++) {
2513
+ const item = arr.get(r, col);
2514
+ if (!caught(item)) {
2515
+ out.set(r, col, item);
2516
+ continue;
2517
+ }
2518
+ out.set(r, col, fbArr ? r < fbArr.rows && col < fbArr.columns ? fbArr.get(r, col) : FormulaValue.errorNA : fb);
2519
+ }
2520
+ }
2521
+ return FormulaValue.array(out);
2522
+ }
2523
+ function ifError(a, c) {
2524
+ return ifCaught(a, c, (v) => v.isError);
2340
2525
  }
2341
2526
  function ifNa(a, c) {
2342
- const v = a[0].evaluate(c);
2343
- return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
2527
+ return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
2344
2528
  }
2345
2529
  function ifs(a, c) {
2346
2530
  for (let i = 0; i + 1 < a.length; i += 2) {
@@ -2397,21 +2581,21 @@ function fromOA(oa) {
2397
2581
  function registerDate(r) {
2398
2582
  r.register("NOW", now, 0, 0, true);
2399
2583
  r.register("TODAY", today, 0, 0, true);
2400
- r.register("DATE", date_, 3, 3);
2401
- r.register("YEAR", year, 1, 1);
2402
- r.register("MONTH", month, 1, 1);
2403
- r.register("DAY", day, 1, 1);
2404
- r.register("HOUR", hour, 1, 1);
2405
- r.register("MINUTE", minute, 1, 1);
2406
- r.register("SECOND", second, 1, 1);
2407
- r.register("DATEVALUE", dateValue, 1, 1);
2408
- r.register("DAYS", days, 2, 2);
2409
- r.register("EDATE", edate, 2, 2);
2410
- r.register("EOMONTH", eomonth, 2, 2);
2411
- r.register("WEEKDAY", weekday, 1, 2);
2412
- r.register("WEEKNUM", weeknum, 1, 2);
2584
+ r.registerElementwise("DATE", date_, 3, 3);
2585
+ r.registerElementwise("YEAR", year, 1, 1);
2586
+ r.registerElementwise("MONTH", month, 1, 1);
2587
+ r.registerElementwise("DAY", day, 1, 1);
2588
+ r.registerElementwise("HOUR", hour, 1, 1);
2589
+ r.registerElementwise("MINUTE", minute, 1, 1);
2590
+ r.registerElementwise("SECOND", second, 1, 1);
2591
+ r.registerElementwise("DATEVALUE", dateValue, 1, 1);
2592
+ r.registerElementwise("DAYS", days, 2, 2);
2593
+ r.registerElementwise("EDATE", edate, 2, 2);
2594
+ r.registerElementwise("EOMONTH", eomonth, 2, 2);
2595
+ r.registerElementwise("WEEKDAY", weekday, 1, 2);
2596
+ r.registerElementwise("WEEKNUM", weeknum, 1, 2);
2413
2597
  r.register("NETWORKDAYS", networkdays, 2, 3);
2414
- r.register("DATEDIF", datedif, 3, 3);
2598
+ r.registerElementwise("DATEDIF", datedif, 3, 3);
2415
2599
  }
2416
2600
  function evalDate(a, c, idx) {
2417
2601
  const r = exports.FormulaHelper.evalDouble(a[idx], c);
@@ -3224,20 +3408,20 @@ function ifsAggregate(a, c, mode2) {
3224
3408
 
3225
3409
  // src/functions/info-functions.ts
3226
3410
  function registerInfo(r) {
3227
- r.register("ISBLANK", isBlank, 1, 1);
3228
- r.register("ISNUMBER", isNumber, 1, 1);
3229
- r.register("ISTEXT", isText, 1, 1);
3230
- r.register("ISERROR", isError, 1, 1);
3231
- r.register("ISERR", isErr, 1, 1);
3232
- r.register("ISLOGICAL", isLogical, 1, 1);
3233
- r.register("ISNA", isNa, 1, 1);
3234
- r.register("ISNONTEXT", isNonText, 1, 1);
3411
+ r.registerElementwise("ISBLANK", isBlank, 1, 1);
3412
+ r.registerElementwise("ISNUMBER", isNumber, 1, 1);
3413
+ r.registerElementwise("ISTEXT", isText, 1, 1);
3414
+ r.registerElementwise("ISERROR", isError, 1, 1);
3415
+ r.registerElementwise("ISERR", isErr, 1, 1);
3416
+ r.registerElementwise("ISLOGICAL", isLogical, 1, 1);
3417
+ r.registerElementwise("ISNA", isNa, 1, 1);
3418
+ r.registerElementwise("ISNONTEXT", isNonText, 1, 1);
3235
3419
  r.register("NA", na, 0, 0);
3236
- r.register("TYPE", type_, 1, 1);
3237
- r.register("N", n_, 1, 1);
3238
- r.register("ISODD", isOdd, 1, 1);
3239
- r.register("ISEVEN", isEven, 1, 1);
3240
- r.register("ERROR.TYPE", errorType, 1, 1);
3420
+ r.registerElementwise("TYPE", type_, 1, 1);
3421
+ r.registerElementwise("N", n_, 1, 1);
3422
+ r.registerElementwise("ISODD", isOdd, 1, 1);
3423
+ r.registerElementwise("ISEVEN", isEven, 1, 1);
3424
+ r.registerElementwise("ERROR.TYPE", errorType, 1, 1);
3241
3425
  }
3242
3426
  function isBlank(a, c) {
3243
3427
  return FormulaValue.boolean(a[0].evaluate(c).isBlank);
@@ -4063,22 +4247,46 @@ function unique(a, c) {
4063
4247
  const out = fromRows(result);
4064
4248
  return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
4065
4249
  }
4250
+ var numVector = (a, c, i, dflt) => {
4251
+ if (i >= a.length) return { ok: true, v: [dflt] };
4252
+ const val = a[i].evaluate(c);
4253
+ if (val.isError) return { ok: false, e: val };
4254
+ const out = [];
4255
+ for (const item of val.isArray ? val.arrayVal.values() : [val]) {
4256
+ if (item.isBlank) continue;
4257
+ const d = item.tryAsDouble();
4258
+ if (!d.ok) return { ok: false, e: FormulaValue.errorValue };
4259
+ out.push(d.value);
4260
+ }
4261
+ return out.length > 0 ? { ok: true, v: out } : { ok: true, v: [dflt] };
4262
+ };
4066
4263
  function sort(a, c) {
4067
4264
  const v = a[0].evaluate(c);
4068
4265
  if (v.isError) return v;
4069
- const sortIndex = num4(a, c, 1, 1);
4070
- if (!sortIndex.ok) return sortIndex.e;
4071
- const sortOrder = num4(a, c, 2, 1);
4072
- if (!sortOrder.ok) return sortOrder.e;
4266
+ const indices = numVector(a, c, 1, 1);
4267
+ if (!indices.ok) return indices.e;
4268
+ const orders = numVector(a, c, 2, 1);
4269
+ if (!orders.ok) return orders.e;
4073
4270
  const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
4271
+ if (orders.v.length !== 1 && orders.v.length !== indices.v.length) {
4272
+ return FormulaValue.errorValue;
4273
+ }
4074
4274
  const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
4075
4275
  const rows2 = toRows(base);
4076
- const idx = Math.trunc(sortIndex.n) - 1;
4077
- const dir = sortOrder.n < 0 ? -1 : 1;
4078
- if (rows2.length > 0 && (idx < 0 || idx >= rows2[0].length)) return FormulaValue.errorValue;
4276
+ const keys = indices.v.map((n, i) => ({
4277
+ col: Math.trunc(n) - 1,
4278
+ dir: (orders.v.length === 1 ? orders.v[0] : orders.v[i]) < 0 ? -1 : 1
4279
+ }));
4280
+ const width = rows2.length > 0 ? rows2[0].length : 0;
4281
+ if (rows2.length > 0 && keys.some((k) => k.col < 0 || k.col >= width)) {
4282
+ return FormulaValue.errorValue;
4283
+ }
4079
4284
  const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
4080
- const cmp = FormulaValue.compare(x.r[idx], y.r[idx]) * dir;
4081
- return cmp !== 0 ? cmp : x.i - y.i;
4285
+ for (const k of keys) {
4286
+ const cmp = FormulaValue.compare(x.r[k.col], y.r[k.col]) * k.dir;
4287
+ if (cmp !== 0) return cmp;
4288
+ }
4289
+ return x.i - y.i;
4082
4290
  }).map((o) => o.r);
4083
4291
  const out = fromRows(sorted);
4084
4292
  return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
@@ -4301,7 +4509,8 @@ function toRow(a, c) {
4301
4509
  }
4302
4510
 
4303
4511
  // src/function-registry.ts
4304
- var _default, _functions, _FunctionRegistry_instances, registerBuiltIns_fn;
4512
+ var MAX_LIFTED_CELLS = 1048576;
4513
+ var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
4305
4514
  var _FunctionRegistry = class _FunctionRegistry {
4306
4515
  constructor() {
4307
4516
  __privateAdd(this, _FunctionRegistry_instances);
@@ -4323,17 +4532,45 @@ var _FunctionRegistry = class _FunctionRegistry {
4323
4532
  minArgs,
4324
4533
  maxArgs,
4325
4534
  isVolatile,
4326
- handler
4535
+ handler,
4536
+ elementwise: false
4537
+ });
4538
+ }
4539
+ /**
4540
+ * Registers a **scalar** function — one whose arguments are single values —
4541
+ * and lifts it over arrays automatically.
4542
+ *
4543
+ * `LEFT(A2:A100, 3)` is not a special form in Excel: LEFT takes one string, and
4544
+ * the grid maps it over the range, giving a 99-row array. Without that lifting
4545
+ * the range collapses to one value, so the dynamic array built on top of it —
4546
+ * `FILTER(IFERROR(LEFT(A2:A100,3),""), …)` — filters a 1×1 input and returns
4547
+ * `#CALC!`. Lifting belongs here rather than in each handler: the ~40 scalar
4548
+ * text/math/date functions then get it for free and cannot each get it subtly
4549
+ * wrong.
4550
+ *
4551
+ * Only mark functions that are genuinely per-cell. Anything that *consumes* an
4552
+ * array (SUM, COUNTIF, FILTER, SORT, the lazy branch functions) must keep
4553
+ * `register`, or lifting would map it over the array it is meant to reduce.
4554
+ */
4555
+ registerElementwise(name, handler, minArgs = 0, maxArgs = -1) {
4556
+ __privateGet(this, _functions).set(name.toUpperCase(), {
4557
+ name: name.toUpperCase(),
4558
+ minArgs,
4559
+ maxArgs,
4560
+ isVolatile: false,
4561
+ handler,
4562
+ elementwise: true
4327
4563
  });
4328
4564
  }
4329
4565
  /** Executes a function by name with argument validation. */
4330
4566
  execute(name, args, ctx) {
4567
+ var _a;
4331
4568
  const def = __privateGet(this, _functions).get(name.toUpperCase());
4332
4569
  if (!def) return FormulaValue.errorName;
4333
4570
  if (args.length < def.minArgs) return FormulaValue.errorValue;
4334
4571
  if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
4335
4572
  try {
4336
- return def.handler(args, ctx);
4573
+ return def.elementwise ? __privateMethod(_a = _FunctionRegistry, _FunctionRegistry_static, executeElementwise_fn).call(_a, def, args, ctx) : def.handler(args, ctx);
4337
4574
  } catch {
4338
4575
  return FormulaValue.errorValue;
4339
4576
  }
@@ -4349,6 +4586,42 @@ var _FunctionRegistry = class _FunctionRegistry {
4349
4586
  };
4350
4587
  _default = new WeakMap();
4351
4588
  _functions = new WeakMap();
4589
+ _FunctionRegistry_static = new WeakSet();
4590
+ executeElementwise_fn = function(def, args, ctx) {
4591
+ const values = args.map((a) => a.evaluate(ctx));
4592
+ let rows2 = 1, cols = 1;
4593
+ for (const v of values) {
4594
+ if (!v.isArray) continue;
4595
+ const a = v.arrayVal;
4596
+ if (a.rows > rows2) rows2 = a.rows;
4597
+ if (a.columns > cols) cols = a.columns;
4598
+ }
4599
+ if (rows2 === 1 && cols === 1) {
4600
+ return def.handler(values.map((v) => new ValueNode(v.isArray ? v.arrayVal.get(0, 0) : v)), ctx);
4601
+ }
4602
+ if (rows2 * cols > MAX_LIFTED_CELLS) return FormulaValue.errorValue;
4603
+ for (const v of values) {
4604
+ if (!v.isArray) continue;
4605
+ const a = v.arrayVal;
4606
+ if (a.rows !== 1 && a.rows !== rows2 || a.columns !== 1 && a.columns !== cols) {
4607
+ return FormulaValue.errorValue;
4608
+ }
4609
+ }
4610
+ const nodes = values.map((v) => new ValueNode(v));
4611
+ const out = new ArrayValue(rows2, cols);
4612
+ for (let r = 0; r < rows2; r++) {
4613
+ for (let c = 0; c < cols; c++) {
4614
+ for (let i = 0; i < values.length; i++) {
4615
+ const v = values[i];
4616
+ if (!v.isArray) continue;
4617
+ const a = v.arrayVal;
4618
+ nodes[i].value = a.get(a.rows === 1 ? 0 : r, a.columns === 1 ? 0 : c);
4619
+ }
4620
+ out.set(r, c, def.handler(nodes, ctx));
4621
+ }
4622
+ }
4623
+ return FormulaValue.array(out);
4624
+ };
4352
4625
  _FunctionRegistry_instances = new WeakSet();
4353
4626
  registerBuiltIns_fn = function() {
4354
4627
  registerMath(this);
@@ -4363,6 +4636,7 @@ registerBuiltIns_fn = function() {
4363
4636
  registerAggregate(this);
4364
4637
  registerArray(this);
4365
4638
  };
4639
+ __privateAdd(_FunctionRegistry, _FunctionRegistry_static);
4366
4640
  __privateAdd(_FunctionRegistry, _default);
4367
4641
  var FunctionRegistry = _FunctionRegistry;
4368
4642
  exports.FormulaHelper = void 0;
@@ -5224,6 +5498,7 @@ function createWorkbookRecalcModel(workbook) {
5224
5498
  getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
5225
5499
  };
5226
5500
  }
5501
+ var MAX_ADOPTED_SPILL_CELLS = 262144;
5227
5502
  var normSheet = (s) => s.toUpperCase();
5228
5503
  var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
5229
5504
  var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
@@ -5234,7 +5509,7 @@ function parseKey(key) {
5234
5509
  const { row: row2, column: column2 } = puredocsExcel.parseCellRef(ref);
5235
5510
  return { sheet, ref, row: row2, col: column2 };
5236
5511
  }
5237
- 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;
5512
+ 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;
5238
5513
  var RecalcEngine = class {
5239
5514
  constructor(model, registry = FunctionRegistry.default) {
5240
5515
  __privateAdd(this, _RecalcEngine_instances);
@@ -5342,11 +5617,12 @@ var RecalcEngine = class {
5342
5617
  const ast = parseFormulaUncached(text);
5343
5618
  const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
5344
5619
  __privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
5620
+ const key = keyOf(s, r);
5345
5621
  __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
5346
5622
  sheet: s,
5347
5623
  sheetName: sheet,
5348
5624
  ref: r,
5349
- key: keyOf(s, r),
5625
+ key,
5350
5626
  row: row2,
5351
5627
  col: column2,
5352
5628
  formula: text,
@@ -5354,6 +5630,7 @@ var RecalcEngine = class {
5354
5630
  refs,
5355
5631
  volatile
5356
5632
  });
5633
+ __privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
5357
5634
  registered++;
5358
5635
  } catch (err) {
5359
5636
  failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
@@ -5521,6 +5798,51 @@ unregister_fn = function(key) {
5521
5798
  rememberSheet_fn = function(name) {
5522
5799
  __privateGet(this, _sheetNames).set(normSheet(name), name);
5523
5800
  };
5801
+ /**
5802
+ * Takes ownership of the spill block a *file* already declared for an anchor.
5803
+ *
5804
+ * Excel stores a dynamic array as one formula on the anchor plus the spilled
5805
+ * results written as plain cached values in the cells below/right, and records
5806
+ * the extent in the anchor's `<f t="array" ref="…">`. Without this step the
5807
+ * engine meets those cached values on the first recalculation, sees literal
5808
+ * content inside the block it wants to spill into, and reports `#SPILL!` for
5809
+ * every dynamic array in every real file. Registering the declared block as
5810
+ * this anchor's existing spill makes them what they actually are — the anchor's
5811
+ * own territory, free to overwrite — and reuses the ordinary shrink path, so a
5812
+ * result smaller than the declared block clears the leftovers.
5813
+ *
5814
+ * Cost is one Set entry per already-spilled cell, paid once at load. A declared
5815
+ * block far larger than any real spill (a corrupt or hand-written `ref`) is
5816
+ * skipped rather than materialised.
5817
+ */
5818
+ adoptDeclaredSpill_fn = function(s, sheetName, ref, anchorKey, row2, col) {
5819
+ const declared = __privateGet(this, _model).getSpillRange?.(sheetName, ref);
5820
+ if (!declared || !declared.includes(":")) return;
5821
+ let rect;
5822
+ try {
5823
+ rect = puredocsExcel.parseRangeRef(declared);
5824
+ } catch {
5825
+ return;
5826
+ }
5827
+ if (rect.startRow !== row2 || rect.startColumn !== col) return;
5828
+ const height = rect.endRow - rect.startRow + 1;
5829
+ const width = rect.endColumn - rect.startColumn + 1;
5830
+ if (height < 1 || width < 1) return;
5831
+ if (height * width > MAX_ADOPTED_SPILL_CELLS) return;
5832
+ if (height === 1 && width === 1) return;
5833
+ const members = /* @__PURE__ */ new Set();
5834
+ for (let r = rect.startRow; r <= rect.endRow; r++) {
5835
+ for (let c = rect.startColumn; c <= rect.endColumn; c++) {
5836
+ if (r === row2 && c === col) continue;
5837
+ const key = keyOf(s, puredocsExcel.cellRefFromRowCol(r, c));
5838
+ if (__privateGet(this, _formulas).has(key)) continue;
5839
+ if (__privateGet(this, _spillOwner).has(key)) continue;
5840
+ members.add(key);
5841
+ __privateGet(this, _spillOwner).set(key, anchorKey);
5842
+ }
5843
+ }
5844
+ if (members.size > 0) __privateGet(this, _spills).set(anchorKey, members);
5845
+ };
5524
5846
  /**
5525
5847
  * One pass in a caller-supplied order, watching for inversions.
5526
5848
  *