@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 +435 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +436 -114
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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);
|
|
@@ -446,8 +446,11 @@ var TokenType = /* @__PURE__ */ ((TokenType2) => {
|
|
|
446
446
|
TokenType2[TokenType2["SpillReference"] = 24] = "SpillReference";
|
|
447
447
|
TokenType2[TokenType2["Exclamation"] = 25] = "Exclamation";
|
|
448
448
|
TokenType2[TokenType2["AtSign"] = 26] = "AtSign";
|
|
449
|
-
TokenType2[TokenType2["
|
|
450
|
-
TokenType2[TokenType2["
|
|
449
|
+
TokenType2[TokenType2["LeftBrace"] = 27] = "LeftBrace";
|
|
450
|
+
TokenType2[TokenType2["RightBrace"] = 28] = "RightBrace";
|
|
451
|
+
TokenType2[TokenType2["Semicolon"] = 29] = "Semicolon";
|
|
452
|
+
TokenType2[TokenType2["ErrorLiteral"] = 30] = "ErrorLiteral";
|
|
453
|
+
TokenType2[TokenType2["EOF"] = 31] = "EOF";
|
|
451
454
|
return TokenType2;
|
|
452
455
|
})(TokenType || {});
|
|
453
456
|
function makeToken(type, value2, position) {
|
|
@@ -461,6 +464,8 @@ var FormulaException = class extends Error {
|
|
|
461
464
|
this.name = "FormulaException";
|
|
462
465
|
}
|
|
463
466
|
};
|
|
467
|
+
var UNDERSCORE = 95;
|
|
468
|
+
var RESERVED_PREFIXES = ["_XLFN.", "_XLWS.", "_XLPM."];
|
|
464
469
|
var KNOWN_ERRORS = /* @__PURE__ */ new Set([
|
|
465
470
|
"#NULL!",
|
|
466
471
|
"#DIV/0!",
|
|
@@ -472,7 +477,7 @@ var KNOWN_ERRORS = /* @__PURE__ */ new Set([
|
|
|
472
477
|
"#CALC!",
|
|
473
478
|
"#SPILL!"
|
|
474
479
|
]);
|
|
475
|
-
var _formula, _pos, _FormulaLexer_instances, readNextToken_fn, readString_fn, readErrorLiteral_fn, readQuotedSheetRef_fn, readNumber_fn, readIdentifier_fn, readCellRefChars_fn, readOperator_fn, skipWhitespace_fn;
|
|
480
|
+
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;
|
|
476
481
|
var FormulaLexer = class {
|
|
477
482
|
constructor(formula) {
|
|
478
483
|
__privateAdd(this, _FormulaLexer_instances);
|
|
@@ -490,7 +495,7 @@ var FormulaLexer = class {
|
|
|
490
495
|
const token = __privateMethod(this, _FormulaLexer_instances, readNextToken_fn).call(this);
|
|
491
496
|
if (token) tokens.push(token);
|
|
492
497
|
}
|
|
493
|
-
tokens.push(makeToken(
|
|
498
|
+
tokens.push(makeToken(31 /* EOF */, "", __privateGet(this, _pos)));
|
|
494
499
|
return tokens;
|
|
495
500
|
}
|
|
496
501
|
};
|
|
@@ -544,7 +549,7 @@ readErrorLiteral_fn = function() {
|
|
|
544
549
|
if (!KNOWN_ERRORS.has(upper2)) {
|
|
545
550
|
throw new FormulaException(`Unknown error literal "${raw}" at position ${start}.`);
|
|
546
551
|
}
|
|
547
|
-
return makeToken(
|
|
552
|
+
return makeToken(30 /* ErrorLiteral */, upper2, start);
|
|
548
553
|
};
|
|
549
554
|
readQuotedSheetRef_fn = function() {
|
|
550
555
|
const start = __privateWrapper(this, _pos)._++;
|
|
@@ -607,10 +612,8 @@ readIdentifier_fn = function() {
|
|
|
607
612
|
} else break;
|
|
608
613
|
}
|
|
609
614
|
let upper2 = raw.toUpperCase();
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
upper2 = upper2.slice(6);
|
|
613
|
-
}
|
|
615
|
+
raw = __privateMethod(this, _FormulaLexer_instances, stripReservedPrefixes_fn).call(this, raw, upper2);
|
|
616
|
+
if (raw.length !== upper2.length) upper2 = upper2.slice(upper2.length - raw.length);
|
|
614
617
|
if (upper2 === "TRUE") return makeToken(2 /* Boolean */, "TRUE", start);
|
|
615
618
|
if (upper2 === "FALSE") return makeToken(2 /* Boolean */, "FALSE", start);
|
|
616
619
|
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === "!") {
|
|
@@ -632,6 +635,30 @@ readIdentifier_fn = function() {
|
|
|
632
635
|
}
|
|
633
636
|
return makeToken(23 /* NamedRange */, raw, start);
|
|
634
637
|
};
|
|
638
|
+
/**
|
|
639
|
+
* Drops Excel's storage-level name decorations. They carry no semantics — the
|
|
640
|
+
* writer stamps them so an older reader fails loudly instead of silently
|
|
641
|
+
* mis-evaluating — and they *stack*: `_xlfn._xlws.FILTER` wears two. Stripping
|
|
642
|
+
* one prefix once, as this used to, leaves `_XLWS.FILTER` unresolvable.
|
|
643
|
+
*
|
|
644
|
+
* Every identifier in every formula passes through here, so the common case is
|
|
645
|
+
* gated on a single char-code test: a name not starting with '_' returns
|
|
646
|
+
* untouched without so much as a comparison against the prefix table.
|
|
647
|
+
*/
|
|
648
|
+
stripReservedPrefixes_fn = function(raw, upper2) {
|
|
649
|
+
if (raw.charCodeAt(0) !== UNDERSCORE) return raw;
|
|
650
|
+
let cut = 0;
|
|
651
|
+
outer: for (; ; ) {
|
|
652
|
+
for (const prefix of RESERVED_PREFIXES) {
|
|
653
|
+
if (upper2.startsWith(prefix, cut)) {
|
|
654
|
+
cut += prefix.length;
|
|
655
|
+
continue outer;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
return cut > 0 && cut < raw.length ? raw.slice(cut) : raw;
|
|
661
|
+
};
|
|
635
662
|
readCellRefChars_fn = function() {
|
|
636
663
|
let ref = "";
|
|
637
664
|
while (__privateGet(this, _pos) < __privateGet(this, _formula).length) {
|
|
@@ -675,6 +702,14 @@ readOperator_fn = function() {
|
|
|
675
702
|
return makeToken(26 /* AtSign */, "@", start);
|
|
676
703
|
case "!":
|
|
677
704
|
return makeToken(25 /* Exclamation */, "!", start);
|
|
705
|
+
case "{":
|
|
706
|
+
return makeToken(27 /* LeftBrace */, "{", start);
|
|
707
|
+
case "}":
|
|
708
|
+
return makeToken(28 /* RightBrace */, "}", start);
|
|
709
|
+
// Inside an array constant ';' separates rows. Excel always stores ',' as
|
|
710
|
+
// the argument separator regardless of locale, so ';' has no other role.
|
|
711
|
+
case ";":
|
|
712
|
+
return makeToken(29 /* Semicolon */, ";", start);
|
|
678
713
|
case "<":
|
|
679
714
|
if (__privateGet(this, _formula)[__privateGet(this, _pos)] === ">") {
|
|
680
715
|
__privateWrapper(this, _pos)._++;
|
|
@@ -757,6 +792,29 @@ var ErrorNode = class extends FormulaNode {
|
|
|
757
792
|
return this.errorValue;
|
|
758
793
|
}
|
|
759
794
|
};
|
|
795
|
+
var _value;
|
|
796
|
+
var ArrayLiteralNode = class extends FormulaNode {
|
|
797
|
+
constructor(array) {
|
|
798
|
+
super();
|
|
799
|
+
this.array = array;
|
|
800
|
+
__privateAdd(this, _value);
|
|
801
|
+
__privateSet(this, _value, FormulaValue.array(array));
|
|
802
|
+
}
|
|
803
|
+
evaluate() {
|
|
804
|
+
return __privateGet(this, _value);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
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
|
+
};
|
|
760
818
|
var BlankNode = class extends FormulaNode {
|
|
761
819
|
evaluate() {
|
|
762
820
|
return FormulaValue.blank;
|
|
@@ -1034,7 +1092,7 @@ var CallNode = class extends FormulaNode {
|
|
|
1034
1092
|
};
|
|
1035
1093
|
|
|
1036
1094
|
// src/formula-parser.ts
|
|
1037
|
-
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;
|
|
1095
|
+
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;
|
|
1038
1096
|
var FormulaParser = class {
|
|
1039
1097
|
constructor(tokens) {
|
|
1040
1098
|
__privateAdd(this, _FormulaParser_instances);
|
|
@@ -1045,7 +1103,7 @@ var FormulaParser = class {
|
|
|
1045
1103
|
parse() {
|
|
1046
1104
|
__privateSet(this, _pos2, 0);
|
|
1047
1105
|
const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
1048
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type !==
|
|
1106
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 31 /* EOF */) {
|
|
1049
1107
|
throw new FormulaException(
|
|
1050
1108
|
`Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
|
|
1051
1109
|
);
|
|
@@ -1057,7 +1115,7 @@ _tokens = new WeakMap();
|
|
|
1057
1115
|
_pos2 = new WeakMap();
|
|
1058
1116
|
_FormulaParser_instances = new WeakSet();
|
|
1059
1117
|
current_get = function() {
|
|
1060
|
-
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type:
|
|
1118
|
+
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 31 /* EOF */, value: "", position: -1 };
|
|
1061
1119
|
};
|
|
1062
1120
|
advance_fn = function() {
|
|
1063
1121
|
const t = __privateGet(this, _FormulaParser_instances, current_get);
|
|
@@ -1172,7 +1230,7 @@ parsePrimary_fn = function() {
|
|
|
1172
1230
|
case 2 /* Boolean */:
|
|
1173
1231
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1174
1232
|
return new BooleanNode(token.value === "TRUE");
|
|
1175
|
-
case
|
|
1233
|
+
case 30 /* ErrorLiteral */:
|
|
1176
1234
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1177
1235
|
return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
|
|
1178
1236
|
case 3 /* CellReference */: {
|
|
@@ -1211,6 +1269,8 @@ parsePrimary_fn = function() {
|
|
|
1211
1269
|
return new SpillReferenceNode(token.value);
|
|
1212
1270
|
case 5 /* Function */:
|
|
1213
1271
|
return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
|
|
1272
|
+
case 27 /* LeftBrace */:
|
|
1273
|
+
return __privateMethod(this, _FormulaParser_instances, parseArrayLiteral_fn).call(this);
|
|
1214
1274
|
case 6 /* LeftParen */: {
|
|
1215
1275
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1216
1276
|
const expr = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
@@ -1245,6 +1305,79 @@ tryParseFullRange_fn = function(startText, sheetName) {
|
|
|
1245
1305
|
const hi = Math.max(start.index, end.index);
|
|
1246
1306
|
return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
|
|
1247
1307
|
};
|
|
1308
|
+
/**
|
|
1309
|
+
* Parses an array constant: `{1,2;3,4}` — ',' separates columns, ';' rows.
|
|
1310
|
+
*
|
|
1311
|
+
* Excel permits only literals inside one (no references, no calls, not even
|
|
1312
|
+
* arithmetic beyond a leading sign), so the value is fully determined here and
|
|
1313
|
+
* is materialised once into the node instead of being rebuilt on every eval.
|
|
1314
|
+
*
|
|
1315
|
+
* A ragged constant is rejected rather than padded: Excel refuses to accept
|
|
1316
|
+
* one at entry, so a file cannot legitimately contain it, and silently filling
|
|
1317
|
+
* the gaps would invent data the author never wrote.
|
|
1318
|
+
*/
|
|
1319
|
+
parseArrayLiteral_fn = function() {
|
|
1320
|
+
const open = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1321
|
+
const rows2 = [];
|
|
1322
|
+
let row2 = [];
|
|
1323
|
+
for (; ; ) {
|
|
1324
|
+
row2.push(__privateMethod(this, _FormulaParser_instances, parseArrayElement_fn).call(this));
|
|
1325
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
1326
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 29 /* Semicolon */) {
|
|
1330
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1331
|
+
rows2.push(row2);
|
|
1332
|
+
row2 = [];
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
rows2.push(row2);
|
|
1338
|
+
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 28 /* RightBrace */);
|
|
1339
|
+
const cols = rows2[0].length;
|
|
1340
|
+
if (rows2.some((r) => r.length !== cols)) {
|
|
1341
|
+
throw new FormulaException(
|
|
1342
|
+
`Array constant at position ${open.position} has rows of differing lengths.`
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
const array = new ArrayValue(rows2.length, cols);
|
|
1346
|
+
for (let r = 0; r < rows2.length; r++) {
|
|
1347
|
+
for (let c = 0; c < cols; c++) array.set(r, c, rows2[r][c]);
|
|
1348
|
+
}
|
|
1349
|
+
return new ArrayLiteralNode(array);
|
|
1350
|
+
};
|
|
1351
|
+
/** One element of an array constant: a literal, optionally signed. */
|
|
1352
|
+
parseArrayElement_fn = function() {
|
|
1353
|
+
let negate = false;
|
|
1354
|
+
let signed = false;
|
|
1355
|
+
while (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */ || __privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
|
|
1356
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) negate = !negate;
|
|
1357
|
+
signed = true;
|
|
1358
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1359
|
+
}
|
|
1360
|
+
const token = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1361
|
+
switch (token.type) {
|
|
1362
|
+
case 0 /* Number */: {
|
|
1363
|
+
const n = parseFloat(token.value);
|
|
1364
|
+
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1365
|
+
return FormulaValue.number(negate ? -n : n);
|
|
1366
|
+
}
|
|
1367
|
+
case 1 /* String */:
|
|
1368
|
+
if (signed) break;
|
|
1369
|
+
return FormulaValue.text(token.value);
|
|
1370
|
+
case 2 /* Boolean */:
|
|
1371
|
+
if (signed) break;
|
|
1372
|
+
return FormulaValue.boolean(token.value === "TRUE");
|
|
1373
|
+
case 30 /* ErrorLiteral */:
|
|
1374
|
+
if (signed) break;
|
|
1375
|
+
return FormulaValue.error(FormulaValue.errorFromString(token.value));
|
|
1376
|
+
}
|
|
1377
|
+
throw new FormulaException(
|
|
1378
|
+
`Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
|
|
1379
|
+
);
|
|
1380
|
+
};
|
|
1248
1381
|
parseFunction_fn = function() {
|
|
1249
1382
|
const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1250
1383
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
|
|
@@ -1451,42 +1584,42 @@ function registerMath(r) {
|
|
|
1451
1584
|
r.register("PRODUCT", product, 1);
|
|
1452
1585
|
r.register("SUMPRODUCT", sumProduct, 1);
|
|
1453
1586
|
r.register("SUMSQ", sumSq, 1);
|
|
1454
|
-
r.
|
|
1455
|
-
r.
|
|
1456
|
-
r.
|
|
1457
|
-
r.
|
|
1458
|
-
r.
|
|
1459
|
-
r.
|
|
1460
|
-
r.
|
|
1461
|
-
r.
|
|
1462
|
-
r.
|
|
1463
|
-
r.
|
|
1464
|
-
r.
|
|
1465
|
-
r.
|
|
1466
|
-
r.
|
|
1467
|
-
r.
|
|
1468
|
-
r.
|
|
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);
|
|
1469
1602
|
r.register("PI", pi, 0, 0);
|
|
1470
1603
|
r.register("RAND", rand, 0, 0, true);
|
|
1471
1604
|
r.register("RANDBETWEEN", randBetween, 2, 2, true);
|
|
1472
|
-
r.
|
|
1473
|
-
r.
|
|
1474
|
-
r.
|
|
1475
|
-
r.
|
|
1476
|
-
r.
|
|
1477
|
-
r.
|
|
1478
|
-
r.
|
|
1479
|
-
r.
|
|
1480
|
-
r.
|
|
1481
|
-
r.
|
|
1482
|
-
r.
|
|
1483
|
-
r.
|
|
1484
|
-
r.
|
|
1485
|
-
r.
|
|
1486
|
-
r.
|
|
1487
|
-
r.
|
|
1488
|
-
r.
|
|
1489
|
-
r.
|
|
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);
|
|
1490
1623
|
}
|
|
1491
1624
|
function valueAt(v, i) {
|
|
1492
1625
|
return v.isArray ? v.arrayVal.getFlat(i) : v;
|
|
@@ -1918,31 +2051,31 @@ function combin(a, c) {
|
|
|
1918
2051
|
return FormulaValue.number(Math.round(result));
|
|
1919
2052
|
}
|
|
1920
2053
|
function registerText(r) {
|
|
1921
|
-
r.
|
|
1922
|
-
r.
|
|
1923
|
-
r.
|
|
1924
|
-
r.
|
|
1925
|
-
r.
|
|
1926
|
-
r.
|
|
1927
|
-
r.
|
|
1928
|
-
r.
|
|
1929
|
-
r.
|
|
1930
|
-
r.
|
|
1931
|
-
r.
|
|
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);
|
|
1932
2065
|
r.register("CONCATENATE", concatenate, 1);
|
|
1933
2066
|
r.register("CONCAT", concatenate, 1);
|
|
1934
2067
|
r.register("TEXTJOIN", textJoin, 3);
|
|
1935
|
-
r.
|
|
1936
|
-
r.
|
|
1937
|
-
r.
|
|
1938
|
-
r.
|
|
1939
|
-
r.
|
|
1940
|
-
r.
|
|
1941
|
-
r.
|
|
1942
|
-
r.
|
|
1943
|
-
r.
|
|
1944
|
-
r.
|
|
1945
|
-
r.
|
|
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);
|
|
1946
2079
|
}
|
|
1947
2080
|
function str(a, c, idx) {
|
|
1948
2081
|
const r = FormulaHelper.evalString(a[idx], c);
|
|
@@ -2256,6 +2389,7 @@ function registerLogical(r) {
|
|
|
2256
2389
|
function if_(a, c) {
|
|
2257
2390
|
const cond = a[0].evaluate(c);
|
|
2258
2391
|
if (cond.isError) return cond;
|
|
2392
|
+
if (cond.isArray) return ifArray(cond.arrayVal, a, c);
|
|
2259
2393
|
const b = cond.coerceToBool();
|
|
2260
2394
|
if (b.isError) return b;
|
|
2261
2395
|
return b.booleanValue ? a[1].evaluate(c) : a.length > 2 ? a[2].evaluate(c) : FormulaValue.false_;
|
|
@@ -2332,13 +2466,63 @@ function xor(a, c) {
|
|
|
2332
2466
|
}
|
|
2333
2467
|
return FormulaValue.boolean(trueCount % 2 === 1);
|
|
2334
2468
|
}
|
|
2335
|
-
function
|
|
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) {
|
|
2336
2495
|
const v = a[0].evaluate(c);
|
|
2337
|
-
|
|
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);
|
|
2338
2523
|
}
|
|
2339
2524
|
function ifNa(a, c) {
|
|
2340
|
-
|
|
2341
|
-
return v.isError && v.errorCode === 7 /* NA */ ? a[1].evaluate(c) : v;
|
|
2525
|
+
return ifCaught(a, c, (v) => v.isError && v.errorCode === 7 /* NA */);
|
|
2342
2526
|
}
|
|
2343
2527
|
function ifs(a, c) {
|
|
2344
2528
|
for (let i = 0; i + 1 < a.length; i += 2) {
|
|
@@ -2395,21 +2579,21 @@ function fromOA(oa) {
|
|
|
2395
2579
|
function registerDate(r) {
|
|
2396
2580
|
r.register("NOW", now, 0, 0, true);
|
|
2397
2581
|
r.register("TODAY", today, 0, 0, true);
|
|
2398
|
-
r.
|
|
2399
|
-
r.
|
|
2400
|
-
r.
|
|
2401
|
-
r.
|
|
2402
|
-
r.
|
|
2403
|
-
r.
|
|
2404
|
-
r.
|
|
2405
|
-
r.
|
|
2406
|
-
r.
|
|
2407
|
-
r.
|
|
2408
|
-
r.
|
|
2409
|
-
r.
|
|
2410
|
-
r.
|
|
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);
|
|
2411
2595
|
r.register("NETWORKDAYS", networkdays, 2, 3);
|
|
2412
|
-
r.
|
|
2596
|
+
r.registerElementwise("DATEDIF", datedif, 3, 3);
|
|
2413
2597
|
}
|
|
2414
2598
|
function evalDate(a, c, idx) {
|
|
2415
2599
|
const r = FormulaHelper.evalDouble(a[idx], c);
|
|
@@ -3222,20 +3406,20 @@ function ifsAggregate(a, c, mode2) {
|
|
|
3222
3406
|
|
|
3223
3407
|
// src/functions/info-functions.ts
|
|
3224
3408
|
function registerInfo(r) {
|
|
3225
|
-
r.
|
|
3226
|
-
r.
|
|
3227
|
-
r.
|
|
3228
|
-
r.
|
|
3229
|
-
r.
|
|
3230
|
-
r.
|
|
3231
|
-
r.
|
|
3232
|
-
r.
|
|
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);
|
|
3233
3417
|
r.register("NA", na, 0, 0);
|
|
3234
|
-
r.
|
|
3235
|
-
r.
|
|
3236
|
-
r.
|
|
3237
|
-
r.
|
|
3238
|
-
r.
|
|
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);
|
|
3239
3423
|
}
|
|
3240
3424
|
function isBlank(a, c) {
|
|
3241
3425
|
return FormulaValue.boolean(a[0].evaluate(c).isBlank);
|
|
@@ -4061,22 +4245,46 @@ function unique(a, c) {
|
|
|
4061
4245
|
const out = fromRows(result);
|
|
4062
4246
|
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
4063
4247
|
}
|
|
4248
|
+
var numVector = (a, c, i, dflt) => {
|
|
4249
|
+
if (i >= a.length) return { ok: true, v: [dflt] };
|
|
4250
|
+
const val = a[i].evaluate(c);
|
|
4251
|
+
if (val.isError) return { ok: false, e: val };
|
|
4252
|
+
const out = [];
|
|
4253
|
+
for (const item of val.isArray ? val.arrayVal.values() : [val]) {
|
|
4254
|
+
if (item.isBlank) continue;
|
|
4255
|
+
const d = item.tryAsDouble();
|
|
4256
|
+
if (!d.ok) return { ok: false, e: FormulaValue.errorValue };
|
|
4257
|
+
out.push(d.value);
|
|
4258
|
+
}
|
|
4259
|
+
return out.length > 0 ? { ok: true, v: out } : { ok: true, v: [dflt] };
|
|
4260
|
+
};
|
|
4064
4261
|
function sort(a, c) {
|
|
4065
4262
|
const v = a[0].evaluate(c);
|
|
4066
4263
|
if (v.isError) return v;
|
|
4067
|
-
const
|
|
4068
|
-
if (!
|
|
4069
|
-
const
|
|
4070
|
-
if (!
|
|
4264
|
+
const indices = numVector(a, c, 1, 1);
|
|
4265
|
+
if (!indices.ok) return indices.e;
|
|
4266
|
+
const orders = numVector(a, c, 2, 1);
|
|
4267
|
+
if (!orders.ok) return orders.e;
|
|
4071
4268
|
const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
|
|
4269
|
+
if (orders.v.length !== 1 && orders.v.length !== indices.v.length) {
|
|
4270
|
+
return FormulaValue.errorValue;
|
|
4271
|
+
}
|
|
4072
4272
|
const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
|
|
4073
4273
|
const rows2 = toRows(base);
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
4274
|
+
const keys = indices.v.map((n, i) => ({
|
|
4275
|
+
col: Math.trunc(n) - 1,
|
|
4276
|
+
dir: (orders.v.length === 1 ? orders.v[0] : orders.v[i]) < 0 ? -1 : 1
|
|
4277
|
+
}));
|
|
4278
|
+
const width = rows2.length > 0 ? rows2[0].length : 0;
|
|
4279
|
+
if (rows2.length > 0 && keys.some((k) => k.col < 0 || k.col >= width)) {
|
|
4280
|
+
return FormulaValue.errorValue;
|
|
4281
|
+
}
|
|
4077
4282
|
const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
|
|
4078
|
-
const
|
|
4079
|
-
|
|
4283
|
+
for (const k of keys) {
|
|
4284
|
+
const cmp = FormulaValue.compare(x.r[k.col], y.r[k.col]) * k.dir;
|
|
4285
|
+
if (cmp !== 0) return cmp;
|
|
4286
|
+
}
|
|
4287
|
+
return x.i - y.i;
|
|
4080
4288
|
}).map((o) => o.r);
|
|
4081
4289
|
const out = fromRows(sorted);
|
|
4082
4290
|
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
@@ -4299,7 +4507,8 @@ function toRow(a, c) {
|
|
|
4299
4507
|
}
|
|
4300
4508
|
|
|
4301
4509
|
// src/function-registry.ts
|
|
4302
|
-
var
|
|
4510
|
+
var MAX_LIFTED_CELLS = 1048576;
|
|
4511
|
+
var _default, _functions, _FunctionRegistry_static, executeElementwise_fn, _FunctionRegistry_instances, registerBuiltIns_fn;
|
|
4303
4512
|
var _FunctionRegistry = class _FunctionRegistry {
|
|
4304
4513
|
constructor() {
|
|
4305
4514
|
__privateAdd(this, _FunctionRegistry_instances);
|
|
@@ -4321,17 +4530,45 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4321
4530
|
minArgs,
|
|
4322
4531
|
maxArgs,
|
|
4323
4532
|
isVolatile,
|
|
4324
|
-
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
|
|
4325
4561
|
});
|
|
4326
4562
|
}
|
|
4327
4563
|
/** Executes a function by name with argument validation. */
|
|
4328
4564
|
execute(name, args, ctx) {
|
|
4565
|
+
var _a;
|
|
4329
4566
|
const def = __privateGet(this, _functions).get(name.toUpperCase());
|
|
4330
4567
|
if (!def) return FormulaValue.errorName;
|
|
4331
4568
|
if (args.length < def.minArgs) return FormulaValue.errorValue;
|
|
4332
4569
|
if (def.maxArgs >= 0 && args.length > def.maxArgs) return FormulaValue.errorValue;
|
|
4333
4570
|
try {
|
|
4334
|
-
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);
|
|
4335
4572
|
} catch {
|
|
4336
4573
|
return FormulaValue.errorValue;
|
|
4337
4574
|
}
|
|
@@ -4347,6 +4584,42 @@ var _FunctionRegistry = class _FunctionRegistry {
|
|
|
4347
4584
|
};
|
|
4348
4585
|
_default = new WeakMap();
|
|
4349
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
|
+
};
|
|
4350
4623
|
_FunctionRegistry_instances = new WeakSet();
|
|
4351
4624
|
registerBuiltIns_fn = function() {
|
|
4352
4625
|
registerMath(this);
|
|
@@ -4361,6 +4634,7 @@ registerBuiltIns_fn = function() {
|
|
|
4361
4634
|
registerAggregate(this);
|
|
4362
4635
|
registerArray(this);
|
|
4363
4636
|
};
|
|
4637
|
+
__privateAdd(_FunctionRegistry, _FunctionRegistry_static);
|
|
4364
4638
|
__privateAdd(_FunctionRegistry, _default);
|
|
4365
4639
|
var FunctionRegistry = _FunctionRegistry;
|
|
4366
4640
|
var FormulaHelper;
|
|
@@ -5222,6 +5496,7 @@ function createWorkbookRecalcModel(workbook) {
|
|
|
5222
5496
|
getDefinedName: (name, sheet) => workbook.getDefinedName(name, sheet)
|
|
5223
5497
|
};
|
|
5224
5498
|
}
|
|
5499
|
+
var MAX_ADOPTED_SPILL_CELLS = 262144;
|
|
5225
5500
|
var normSheet = (s) => s.toUpperCase();
|
|
5226
5501
|
var normRef = (r) => r.replace(/\$/g, "").toUpperCase();
|
|
5227
5502
|
var keyOf = (sheet, ref) => `${normSheet(sheet)}!${normRef(ref)}`;
|
|
@@ -5232,7 +5507,7 @@ function parseKey(key) {
|
|
|
5232
5507
|
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
5233
5508
|
return { sheet, ref, row: row2, col: column2 };
|
|
5234
5509
|
}
|
|
5235
|
-
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;
|
|
5236
5511
|
var RecalcEngine = class {
|
|
5237
5512
|
constructor(model, registry = FunctionRegistry.default) {
|
|
5238
5513
|
__privateAdd(this, _RecalcEngine_instances);
|
|
@@ -5340,11 +5615,12 @@ var RecalcEngine = class {
|
|
|
5340
5615
|
const ast = parseFormulaUncached(text);
|
|
5341
5616
|
const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
|
|
5342
5617
|
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
5618
|
+
const key = keyOf(s, r);
|
|
5343
5619
|
__privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
|
|
5344
5620
|
sheet: s,
|
|
5345
5621
|
sheetName: sheet,
|
|
5346
5622
|
ref: r,
|
|
5347
|
-
key
|
|
5623
|
+
key,
|
|
5348
5624
|
row: row2,
|
|
5349
5625
|
col: column2,
|
|
5350
5626
|
formula: text,
|
|
@@ -5352,6 +5628,7 @@ var RecalcEngine = class {
|
|
|
5352
5628
|
refs,
|
|
5353
5629
|
volatile
|
|
5354
5630
|
});
|
|
5631
|
+
__privateMethod(this, _RecalcEngine_instances, adoptDeclaredSpill_fn).call(this, s, sheet, r, key, row2, column2);
|
|
5355
5632
|
registered++;
|
|
5356
5633
|
} catch (err) {
|
|
5357
5634
|
failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
|
|
@@ -5519,6 +5796,51 @@ unregister_fn = function(key) {
|
|
|
5519
5796
|
rememberSheet_fn = function(name) {
|
|
5520
5797
|
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
5521
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
|
+
};
|
|
5522
5844
|
/**
|
|
5523
5845
|
* One pass in a caller-supplied order, watching for inversions.
|
|
5524
5846
|
*
|