@devmm/puredocs-excel-formula 1.0.6 → 1.0.8
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 +961 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -21
- package/dist/index.d.ts +233 -21
- package/dist/index.js +961 -143
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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["
|
|
452
|
-
TokenType2[TokenType2["
|
|
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(
|
|
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(
|
|
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
|
-
|
|
613
|
-
|
|
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,19 @@ 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();
|
|
762
810
|
var BlankNode = class extends FormulaNode {
|
|
763
811
|
evaluate() {
|
|
764
812
|
return FormulaValue.blank;
|
|
@@ -804,6 +852,19 @@ var SheetRangeReferenceNode = class extends FormulaNode {
|
|
|
804
852
|
return ctx.getSheetRangeValues(this.sheetName, this.startRef, this.endRef);
|
|
805
853
|
}
|
|
806
854
|
};
|
|
855
|
+
var FullRangeReferenceNode = class extends FormulaNode {
|
|
856
|
+
constructor(startCol, endCol, startRow, endRow, sheetName) {
|
|
857
|
+
super();
|
|
858
|
+
this.startCol = startCol;
|
|
859
|
+
this.endCol = endCol;
|
|
860
|
+
this.startRow = startRow;
|
|
861
|
+
this.endRow = endRow;
|
|
862
|
+
this.sheetName = sheetName;
|
|
863
|
+
}
|
|
864
|
+
evaluate(ctx) {
|
|
865
|
+
return this.sheetName === void 0 ? ctx.getFullRangeValues(this.startCol, this.endCol, this.startRow, this.endRow) : ctx.getSheetFullRangeValues(this.sheetName, this.startCol, this.endCol, this.startRow, this.endRow);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
807
868
|
var NamedRangeNode = class extends FormulaNode {
|
|
808
869
|
constructor(name) {
|
|
809
870
|
super();
|
|
@@ -1023,7 +1084,7 @@ var CallNode = class extends FormulaNode {
|
|
|
1023
1084
|
};
|
|
1024
1085
|
|
|
1025
1086
|
// src/formula-parser.ts
|
|
1026
|
-
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, parseFunction_fn, parseArgList_fn;
|
|
1087
|
+
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;
|
|
1027
1088
|
var FormulaParser = class {
|
|
1028
1089
|
constructor(tokens) {
|
|
1029
1090
|
__privateAdd(this, _FormulaParser_instances);
|
|
@@ -1034,7 +1095,7 @@ var FormulaParser = class {
|
|
|
1034
1095
|
parse() {
|
|
1035
1096
|
__privateSet(this, _pos2, 0);
|
|
1036
1097
|
const node = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
1037
|
-
if (__privateGet(this, _FormulaParser_instances, current_get).type !==
|
|
1098
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type !== 31 /* EOF */) {
|
|
1038
1099
|
throw new FormulaException(
|
|
1039
1100
|
`Unexpected token '${__privateGet(this, _FormulaParser_instances, current_get).value}' at position ${__privateGet(this, _FormulaParser_instances, current_get).position}.`
|
|
1040
1101
|
);
|
|
@@ -1046,7 +1107,7 @@ _tokens = new WeakMap();
|
|
|
1046
1107
|
_pos2 = new WeakMap();
|
|
1047
1108
|
_FormulaParser_instances = new WeakSet();
|
|
1048
1109
|
current_get = function() {
|
|
1049
|
-
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type:
|
|
1110
|
+
return __privateGet(this, _tokens)[__privateGet(this, _pos2)] ?? { type: 31 /* EOF */, value: "", position: -1 };
|
|
1050
1111
|
};
|
|
1051
1112
|
advance_fn = function() {
|
|
1052
1113
|
const t = __privateGet(this, _FormulaParser_instances, current_get);
|
|
@@ -1147,6 +1208,10 @@ parsePrimary_fn = function() {
|
|
|
1147
1208
|
switch (token.type) {
|
|
1148
1209
|
case 0 /* Number */: {
|
|
1149
1210
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1211
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1212
|
+
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, token.value);
|
|
1213
|
+
if (full) return full;
|
|
1214
|
+
}
|
|
1150
1215
|
const n = parseFloat(token.value);
|
|
1151
1216
|
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1152
1217
|
return new NumberNode(n);
|
|
@@ -1157,7 +1222,7 @@ parsePrimary_fn = function() {
|
|
|
1157
1222
|
case 2 /* Boolean */:
|
|
1158
1223
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1159
1224
|
return new BooleanNode(token.value === "TRUE");
|
|
1160
|
-
case
|
|
1225
|
+
case 30 /* ErrorLiteral */:
|
|
1161
1226
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1162
1227
|
return new ErrorNode(FormulaValue.error(FormulaValue.errorFromString(token.value)));
|
|
1163
1228
|
case 3 /* CellReference */: {
|
|
@@ -1175,20 +1240,29 @@ parsePrimary_fn = function() {
|
|
|
1175
1240
|
const sheetName = parts[0];
|
|
1176
1241
|
const cellRef = parts[1] ?? "";
|
|
1177
1242
|
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1243
|
+
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, cellRef, sheetName);
|
|
1244
|
+
if (full) return full;
|
|
1178
1245
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1179
1246
|
const end = __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 3 /* CellReference */);
|
|
1180
1247
|
return new SheetRangeReferenceNode(sheetName, cellRef, end.value);
|
|
1181
1248
|
}
|
|
1182
1249
|
return new SheetCellReferenceNode(sheetName, cellRef);
|
|
1183
1250
|
}
|
|
1184
|
-
case 23 /* NamedRange */:
|
|
1251
|
+
case 23 /* NamedRange */: {
|
|
1185
1252
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1253
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1254
|
+
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, token.value);
|
|
1255
|
+
if (full) return full;
|
|
1256
|
+
}
|
|
1186
1257
|
return new NamedRangeNode(token.value);
|
|
1258
|
+
}
|
|
1187
1259
|
case 24 /* SpillReference */:
|
|
1188
1260
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1189
1261
|
return new SpillReferenceNode(token.value);
|
|
1190
1262
|
case 5 /* Function */:
|
|
1191
1263
|
return __privateMethod(this, _FormulaParser_instances, parseFunction_fn).call(this);
|
|
1264
|
+
case 27 /* LeftBrace */:
|
|
1265
|
+
return __privateMethod(this, _FormulaParser_instances, parseArrayLiteral_fn).call(this);
|
|
1192
1266
|
case 6 /* LeftParen */: {
|
|
1193
1267
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1194
1268
|
const expr = __privateMethod(this, _FormulaParser_instances, parseComparison_fn).call(this);
|
|
@@ -1201,6 +1275,101 @@ parsePrimary_fn = function() {
|
|
|
1201
1275
|
);
|
|
1202
1276
|
}
|
|
1203
1277
|
};
|
|
1278
|
+
/**
|
|
1279
|
+
* Completes a whole-column or whole-row reference whose first half has already
|
|
1280
|
+
* been consumed and whose ':' is the current token. Returns null — consuming
|
|
1281
|
+
* nothing — when the two halves are not a matching pair of columns or rows, so
|
|
1282
|
+
* the caller can fall back to its normal interpretation.
|
|
1283
|
+
*
|
|
1284
|
+
* Both halves must be the same kind: `A:A` and `1:5` are ranges, `A:1` is not
|
|
1285
|
+
* a reference at all in Excel and must stay an error rather than becoming a
|
|
1286
|
+
* silently different region.
|
|
1287
|
+
*/
|
|
1288
|
+
tryParseFullRange_fn = function(startText, sheetName) {
|
|
1289
|
+
const endToken = __privateGet(this, _tokens)[__privateGet(this, _pos2) + 1];
|
|
1290
|
+
if (!endToken) return null;
|
|
1291
|
+
const start = classifyBound(startText);
|
|
1292
|
+
const end = classifyBound(endToken.value);
|
|
1293
|
+
if (!start || !end || start.kind !== end.kind) return null;
|
|
1294
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1295
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1296
|
+
const lo = Math.min(start.index, end.index);
|
|
1297
|
+
const hi = Math.max(start.index, end.index);
|
|
1298
|
+
return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
|
|
1299
|
+
};
|
|
1300
|
+
/**
|
|
1301
|
+
* Parses an array constant: `{1,2;3,4}` — ',' separates columns, ';' rows.
|
|
1302
|
+
*
|
|
1303
|
+
* Excel permits only literals inside one (no references, no calls, not even
|
|
1304
|
+
* arithmetic beyond a leading sign), so the value is fully determined here and
|
|
1305
|
+
* is materialised once into the node instead of being rebuilt on every eval.
|
|
1306
|
+
*
|
|
1307
|
+
* A ragged constant is rejected rather than padded: Excel refuses to accept
|
|
1308
|
+
* one at entry, so a file cannot legitimately contain it, and silently filling
|
|
1309
|
+
* the gaps would invent data the author never wrote.
|
|
1310
|
+
*/
|
|
1311
|
+
parseArrayLiteral_fn = function() {
|
|
1312
|
+
const open = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1313
|
+
const rows2 = [];
|
|
1314
|
+
let row2 = [];
|
|
1315
|
+
for (; ; ) {
|
|
1316
|
+
row2.push(__privateMethod(this, _FormulaParser_instances, parseArrayElement_fn).call(this));
|
|
1317
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 8 /* Comma */) {
|
|
1318
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 29 /* Semicolon */) {
|
|
1322
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1323
|
+
rows2.push(row2);
|
|
1324
|
+
row2 = [];
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
rows2.push(row2);
|
|
1330
|
+
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 28 /* RightBrace */);
|
|
1331
|
+
const cols = rows2[0].length;
|
|
1332
|
+
if (rows2.some((r) => r.length !== cols)) {
|
|
1333
|
+
throw new FormulaException(
|
|
1334
|
+
`Array constant at position ${open.position} has rows of differing lengths.`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
const array = new ArrayValue(rows2.length, cols);
|
|
1338
|
+
for (let r = 0; r < rows2.length; r++) {
|
|
1339
|
+
for (let c = 0; c < cols; c++) array.set(r, c, rows2[r][c]);
|
|
1340
|
+
}
|
|
1341
|
+
return new ArrayLiteralNode(array);
|
|
1342
|
+
};
|
|
1343
|
+
/** One element of an array constant: a literal, optionally signed. */
|
|
1344
|
+
parseArrayElement_fn = function() {
|
|
1345
|
+
let negate = false;
|
|
1346
|
+
let signed = false;
|
|
1347
|
+
while (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */ || __privateGet(this, _FormulaParser_instances, current_get).type === 9 /* Plus */) {
|
|
1348
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 10 /* Minus */) negate = !negate;
|
|
1349
|
+
signed = true;
|
|
1350
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1351
|
+
}
|
|
1352
|
+
const token = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1353
|
+
switch (token.type) {
|
|
1354
|
+
case 0 /* Number */: {
|
|
1355
|
+
const n = parseFloat(token.value);
|
|
1356
|
+
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1357
|
+
return FormulaValue.number(negate ? -n : n);
|
|
1358
|
+
}
|
|
1359
|
+
case 1 /* String */:
|
|
1360
|
+
if (signed) break;
|
|
1361
|
+
return FormulaValue.text(token.value);
|
|
1362
|
+
case 2 /* Boolean */:
|
|
1363
|
+
if (signed) break;
|
|
1364
|
+
return FormulaValue.boolean(token.value === "TRUE");
|
|
1365
|
+
case 30 /* ErrorLiteral */:
|
|
1366
|
+
if (signed) break;
|
|
1367
|
+
return FormulaValue.error(FormulaValue.errorFromString(token.value));
|
|
1368
|
+
}
|
|
1369
|
+
throw new FormulaException(
|
|
1370
|
+
`Array constants accept only literal values; got '${token.value}' at position ${token.position}.`
|
|
1371
|
+
);
|
|
1372
|
+
};
|
|
1204
1373
|
parseFunction_fn = function() {
|
|
1205
1374
|
const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1206
1375
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
|
|
@@ -1227,6 +1396,18 @@ parseArgList_fn = function() {
|
|
|
1227
1396
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 7 /* RightParen */);
|
|
1228
1397
|
return args;
|
|
1229
1398
|
};
|
|
1399
|
+
function classifyBound(text) {
|
|
1400
|
+
const bare = text.replace(/\$/g, "");
|
|
1401
|
+
if (/^\d+$/.test(bare)) {
|
|
1402
|
+
const row2 = parseInt(bare, 10);
|
|
1403
|
+
return row2 >= 1 && row2 <= puredocsExcel.EXCEL_MAX_ROWS ? { kind: "row", index: row2 } : null;
|
|
1404
|
+
}
|
|
1405
|
+
if (/^[A-Za-z]{1,3}$/.test(bare)) {
|
|
1406
|
+
const col = puredocsExcel.columnNumber(bare.toUpperCase());
|
|
1407
|
+
return col >= 1 && col <= puredocsExcel.EXCEL_MAX_COLUMNS ? { kind: "col", index: col } : null;
|
|
1408
|
+
}
|
|
1409
|
+
return null;
|
|
1410
|
+
}
|
|
1230
1411
|
function comparisonOp(type) {
|
|
1231
1412
|
switch (type) {
|
|
1232
1413
|
case 16 /* Equal */:
|
|
@@ -1246,6 +1427,137 @@ function comparisonOp(type) {
|
|
|
1246
1427
|
}
|
|
1247
1428
|
}
|
|
1248
1429
|
|
|
1430
|
+
// src/criteria-index.ts
|
|
1431
|
+
var EPSILON = 1e-10;
|
|
1432
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1433
|
+
function build(arr) {
|
|
1434
|
+
const byText = /* @__PURE__ */ new Map();
|
|
1435
|
+
const byNumber = /* @__PURE__ */ new Map();
|
|
1436
|
+
const classes = /* @__PURE__ */ new Map();
|
|
1437
|
+
const addToClass = (kind, v, i2) => {
|
|
1438
|
+
const group = classes.get(kind);
|
|
1439
|
+
if (group) group.positions.push(i2);
|
|
1440
|
+
else classes.set(kind, { positions: [i2], representative: v });
|
|
1441
|
+
};
|
|
1442
|
+
let i = 0;
|
|
1443
|
+
for (const v of arr.values()) {
|
|
1444
|
+
const textKey = v.asText().toUpperCase();
|
|
1445
|
+
const atText = byText.get(textKey);
|
|
1446
|
+
if (atText) atText.push(i);
|
|
1447
|
+
else byText.set(textKey, [i]);
|
|
1448
|
+
if (v.isNumber) {
|
|
1449
|
+
const n = v.numberValue;
|
|
1450
|
+
const atNum = byNumber.get(n);
|
|
1451
|
+
if (atNum) atNum.push(i);
|
|
1452
|
+
else byNumber.set(n, [i]);
|
|
1453
|
+
} else {
|
|
1454
|
+
addToClass(v.kind, v, i);
|
|
1455
|
+
}
|
|
1456
|
+
i++;
|
|
1457
|
+
}
|
|
1458
|
+
const sortedNumbers = Float64Array.from(byNumber.keys()).sort();
|
|
1459
|
+
const cumulative = new Int32Array(sortedNumbers.length + 1);
|
|
1460
|
+
for (let k = 0; k < sortedNumbers.length; k++) {
|
|
1461
|
+
cumulative[k + 1] = cumulative[k] + byNumber.get(sortedNumbers[k]).length;
|
|
1462
|
+
}
|
|
1463
|
+
return { byText, byNumber, sortedNumbers, cumulative, classes: [...classes.values()] };
|
|
1464
|
+
}
|
|
1465
|
+
function indexOf(arr) {
|
|
1466
|
+
let idx = indexes.get(arr);
|
|
1467
|
+
if (!idx) {
|
|
1468
|
+
idx = build(arr);
|
|
1469
|
+
indexes.set(arr, idx);
|
|
1470
|
+
}
|
|
1471
|
+
return idx;
|
|
1472
|
+
}
|
|
1473
|
+
function lowerBound(sorted, target) {
|
|
1474
|
+
let lo = 0, hi = sorted.length;
|
|
1475
|
+
while (lo < hi) {
|
|
1476
|
+
const mid2 = lo + hi >> 1;
|
|
1477
|
+
if (sorted[mid2] < target) lo = mid2 + 1;
|
|
1478
|
+
else hi = mid2;
|
|
1479
|
+
}
|
|
1480
|
+
return lo;
|
|
1481
|
+
}
|
|
1482
|
+
function upperBound(sorted, target) {
|
|
1483
|
+
let lo = 0, hi = sorted.length;
|
|
1484
|
+
while (lo < hi) {
|
|
1485
|
+
const mid2 = lo + hi >> 1;
|
|
1486
|
+
if (sorted[mid2] <= target) lo = mid2 + 1;
|
|
1487
|
+
else hi = mid2;
|
|
1488
|
+
}
|
|
1489
|
+
return lo;
|
|
1490
|
+
}
|
|
1491
|
+
function matchingPositions(criteriaRange, criteria, minSize) {
|
|
1492
|
+
if (!criteriaRange.isArray) return null;
|
|
1493
|
+
const arr = criteriaRange.arrayVal;
|
|
1494
|
+
if (arr.length < minSize) return null;
|
|
1495
|
+
if (criteria.kind === "equalsText") {
|
|
1496
|
+
return indexOf(arr).byText.get(criteria.text) ?? EMPTY;
|
|
1497
|
+
}
|
|
1498
|
+
if (criteria.kind === "equalsNumber") {
|
|
1499
|
+
const target = criteria.number;
|
|
1500
|
+
const idx = indexOf(arr);
|
|
1501
|
+
const exact2 = idx.byNumber.get(target);
|
|
1502
|
+
let near;
|
|
1503
|
+
const { sortedNumbers } = idx;
|
|
1504
|
+
for (let k = lowerBound(sortedNumbers, target - EPSILON); k < sortedNumbers.length; k++) {
|
|
1505
|
+
const value2 = sortedNumbers[k];
|
|
1506
|
+
if (value2 > target + EPSILON) break;
|
|
1507
|
+
if (value2 === target) continue;
|
|
1508
|
+
if (!criteria.test(FormulaValue.number(value2))) continue;
|
|
1509
|
+
(near ?? (near = [])).push(...idx.byNumber.get(value2));
|
|
1510
|
+
}
|
|
1511
|
+
const blanks = criteria.test(FormulaValue.blank) ? blankPositions(idx) : void 0;
|
|
1512
|
+
if (!near && !blanks?.length) return exact2 ?? EMPTY;
|
|
1513
|
+
const merged = [...exact2 ?? EMPTY, ...near ?? EMPTY, ...blanks ?? EMPTY];
|
|
1514
|
+
merged.sort((a, b) => a - b);
|
|
1515
|
+
return merged;
|
|
1516
|
+
}
|
|
1517
|
+
if (criteria.kind === "compareNumber") {
|
|
1518
|
+
return comparePositions(indexOf(arr), criteria, arr.length);
|
|
1519
|
+
}
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
function comparePositions(idx, criteria, length) {
|
|
1523
|
+
const target = criteria.number;
|
|
1524
|
+
const { sortedNumbers } = idx;
|
|
1525
|
+
let from = 0;
|
|
1526
|
+
let to = sortedNumbers.length;
|
|
1527
|
+
switch (criteria.op) {
|
|
1528
|
+
case ">":
|
|
1529
|
+
from = upperBound(sortedNumbers, target);
|
|
1530
|
+
break;
|
|
1531
|
+
case ">=":
|
|
1532
|
+
from = lowerBound(sortedNumbers, target);
|
|
1533
|
+
break;
|
|
1534
|
+
case "<":
|
|
1535
|
+
to = lowerBound(sortedNumbers, target);
|
|
1536
|
+
break;
|
|
1537
|
+
default:
|
|
1538
|
+
to = upperBound(sortedNumbers, target);
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
const budget = length >> 1;
|
|
1542
|
+
const matchedClasses = idx.classes.filter((g) => criteria.test(g.representative));
|
|
1543
|
+
let total = idx.cumulative[to] - idx.cumulative[from];
|
|
1544
|
+
for (const group of matchedClasses) total += group.positions.length;
|
|
1545
|
+
if (total > budget) return null;
|
|
1546
|
+
const positions = [];
|
|
1547
|
+
for (let k = from; k < to; k++) {
|
|
1548
|
+
for (const i of idx.byNumber.get(sortedNumbers[k])) positions.push(i);
|
|
1549
|
+
}
|
|
1550
|
+
for (const group of matchedClasses) {
|
|
1551
|
+
for (const i of group.positions) positions.push(i);
|
|
1552
|
+
}
|
|
1553
|
+
positions.sort((a, b) => a - b);
|
|
1554
|
+
return positions;
|
|
1555
|
+
}
|
|
1556
|
+
function blankPositions(idx) {
|
|
1557
|
+
return idx.classes.find((g) => g.representative.isBlank)?.positions;
|
|
1558
|
+
}
|
|
1559
|
+
var EMPTY = [];
|
|
1560
|
+
|
|
1249
1561
|
// src/functions/math-functions.ts
|
|
1250
1562
|
function registerMath(r) {
|
|
1251
1563
|
r.register("SUM", sum, 1);
|
|
@@ -1307,34 +1619,72 @@ function valueAt(v, i) {
|
|
|
1307
1619
|
function lengthOf(v) {
|
|
1308
1620
|
return v.isArray ? v.arrayVal.length : 1;
|
|
1309
1621
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1622
|
+
var INDEX_MIN_RANGE = 32;
|
|
1623
|
+
function resolveIfsSelection(a, c, startIdx, len2) {
|
|
1624
|
+
const ranges = [];
|
|
1625
|
+
const criteria = [];
|
|
1626
|
+
let seed;
|
|
1627
|
+
let seedPair = -1;
|
|
1312
1628
|
for (let p = startIdx; p + 1 < a.length; p += 2) {
|
|
1313
1629
|
const cr = a[p].evaluate(c);
|
|
1314
1630
|
if (cr.isError) return { ok: false, error: cr };
|
|
1315
1631
|
const crit = a[p + 1].evaluate(c);
|
|
1316
1632
|
if (crit.isError) return { ok: false, error: crit };
|
|
1317
|
-
const
|
|
1633
|
+
const compiled = exports.FormulaHelper.compileCriteria(crit.asText());
|
|
1634
|
+
ranges.push(cr);
|
|
1635
|
+
criteria.push(compiled);
|
|
1636
|
+
if (seed === void 0) {
|
|
1637
|
+
const positions = matchingPositions(cr, compiled, INDEX_MIN_RANGE);
|
|
1638
|
+
if (positions) {
|
|
1639
|
+
seed = positions;
|
|
1640
|
+
seedPair = ranges.length - 1;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
if (seed !== void 0) {
|
|
1645
|
+
let candidates = [];
|
|
1646
|
+
for (const i of seed) if (i < len2) candidates.push(i);
|
|
1647
|
+
for (let k = 0; k < ranges.length && candidates.length > 0; k++) {
|
|
1648
|
+
if (k === seedPair) continue;
|
|
1649
|
+
const range = ranges[k];
|
|
1650
|
+
const test = criteria[k].test;
|
|
1651
|
+
const next = [];
|
|
1652
|
+
for (const i of candidates) if (test(valueAt(range, i))) next.push(i);
|
|
1653
|
+
candidates = next;
|
|
1654
|
+
}
|
|
1655
|
+
return { ok: true, positions: candidates };
|
|
1656
|
+
}
|
|
1657
|
+
const mask = new Uint8Array(len2).fill(1);
|
|
1658
|
+
for (let k = 0; k < ranges.length; k++) {
|
|
1659
|
+
const range = ranges[k];
|
|
1660
|
+
const test = criteria[k].test;
|
|
1318
1661
|
for (let i = 0; i < len2; i++) {
|
|
1319
1662
|
if (!mask[i]) continue;
|
|
1320
|
-
if (!
|
|
1663
|
+
if (!test(valueAt(range, i))) mask[i] = 0;
|
|
1321
1664
|
}
|
|
1322
1665
|
}
|
|
1323
1666
|
return { ok: true, mask };
|
|
1324
1667
|
}
|
|
1668
|
+
function forEachSelected(sel, len2, visit) {
|
|
1669
|
+
if ("positions" in sel) {
|
|
1670
|
+
for (const i of sel.positions) visit(i);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const { mask } = sel;
|
|
1674
|
+
for (let i = 0; i < len2; i++) if (mask[i]) visit(i);
|
|
1675
|
+
}
|
|
1325
1676
|
function sumIfs(a, c) {
|
|
1326
1677
|
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
1327
1678
|
const sumRange = a[0].evaluate(c);
|
|
1328
1679
|
if (sumRange.isError) return sumRange;
|
|
1329
1680
|
const len2 = lengthOf(sumRange);
|
|
1330
|
-
const
|
|
1331
|
-
if (!
|
|
1681
|
+
const sel = resolveIfsSelection(a, c, 1, len2);
|
|
1682
|
+
if (!sel.ok) return sel.error;
|
|
1332
1683
|
let total = 0;
|
|
1333
|
-
|
|
1334
|
-
if (!m.mask[i]) continue;
|
|
1684
|
+
forEachSelected(sel, len2, (i) => {
|
|
1335
1685
|
const r = valueAt(sumRange, i).tryAsDouble();
|
|
1336
1686
|
if (r.ok) total += r.value;
|
|
1337
|
-
}
|
|
1687
|
+
});
|
|
1338
1688
|
return FormulaValue.number(total);
|
|
1339
1689
|
}
|
|
1340
1690
|
function countIfs(a, c) {
|
|
@@ -1342,10 +1692,12 @@ function countIfs(a, c) {
|
|
|
1342
1692
|
const first = a[0].evaluate(c);
|
|
1343
1693
|
if (first.isError) return first;
|
|
1344
1694
|
const len2 = lengthOf(first);
|
|
1345
|
-
const
|
|
1346
|
-
if (!
|
|
1695
|
+
const sel = resolveIfsSelection(a, c, 0, len2);
|
|
1696
|
+
if (!sel.ok) return sel.error;
|
|
1347
1697
|
let n = 0;
|
|
1348
|
-
|
|
1698
|
+
forEachSelected(sel, len2, () => {
|
|
1699
|
+
n++;
|
|
1700
|
+
});
|
|
1349
1701
|
return FormulaValue.number(n);
|
|
1350
1702
|
}
|
|
1351
1703
|
function averageIfs(a, c) {
|
|
@@ -1353,17 +1705,16 @@ function averageIfs(a, c) {
|
|
|
1353
1705
|
const avgRange = a[0].evaluate(c);
|
|
1354
1706
|
if (avgRange.isError) return avgRange;
|
|
1355
1707
|
const len2 = lengthOf(avgRange);
|
|
1356
|
-
const
|
|
1357
|
-
if (!
|
|
1708
|
+
const sel = resolveIfsSelection(a, c, 1, len2);
|
|
1709
|
+
if (!sel.ok) return sel.error;
|
|
1358
1710
|
let total = 0, cnt = 0;
|
|
1359
|
-
|
|
1360
|
-
if (!m.mask[i]) continue;
|
|
1711
|
+
forEachSelected(sel, len2, (i) => {
|
|
1361
1712
|
const r = valueAt(avgRange, i).tryAsDouble();
|
|
1362
1713
|
if (r.ok) {
|
|
1363
1714
|
total += r.value;
|
|
1364
1715
|
cnt++;
|
|
1365
1716
|
}
|
|
1366
|
-
}
|
|
1717
|
+
});
|
|
1367
1718
|
return cnt === 0 ? FormulaValue.errorDiv0 : FormulaValue.number(total / cnt);
|
|
1368
1719
|
}
|
|
1369
1720
|
function sumSq(a, c) {
|
|
@@ -3835,22 +4186,46 @@ function unique(a, c) {
|
|
|
3835
4186
|
const out = fromRows(result);
|
|
3836
4187
|
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
3837
4188
|
}
|
|
4189
|
+
var numVector = (a, c, i, dflt) => {
|
|
4190
|
+
if (i >= a.length) return { ok: true, v: [dflt] };
|
|
4191
|
+
const val = a[i].evaluate(c);
|
|
4192
|
+
if (val.isError) return { ok: false, e: val };
|
|
4193
|
+
const out = [];
|
|
4194
|
+
for (const item of val.isArray ? val.arrayVal.values() : [val]) {
|
|
4195
|
+
if (item.isBlank) continue;
|
|
4196
|
+
const d = item.tryAsDouble();
|
|
4197
|
+
if (!d.ok) return { ok: false, e: FormulaValue.errorValue };
|
|
4198
|
+
out.push(d.value);
|
|
4199
|
+
}
|
|
4200
|
+
return out.length > 0 ? { ok: true, v: out } : { ok: true, v: [dflt] };
|
|
4201
|
+
};
|
|
3838
4202
|
function sort(a, c) {
|
|
3839
4203
|
const v = a[0].evaluate(c);
|
|
3840
4204
|
if (v.isError) return v;
|
|
3841
|
-
const
|
|
3842
|
-
if (!
|
|
3843
|
-
const
|
|
3844
|
-
if (!
|
|
4205
|
+
const indices = numVector(a, c, 1, 1);
|
|
4206
|
+
if (!indices.ok) return indices.e;
|
|
4207
|
+
const orders = numVector(a, c, 2, 1);
|
|
4208
|
+
if (!orders.ok) return orders.e;
|
|
3845
4209
|
const byCol2 = a.length > 3 ? a[3].evaluate(c).coerceToBool().booleanValue : false;
|
|
4210
|
+
if (orders.v.length !== 1 && orders.v.length !== indices.v.length) {
|
|
4211
|
+
return FormulaValue.errorValue;
|
|
4212
|
+
}
|
|
3846
4213
|
const base = byCol2 ? asArray2(v).transpose() : asArray2(v);
|
|
3847
4214
|
const rows2 = toRows(base);
|
|
3848
|
-
const
|
|
3849
|
-
|
|
3850
|
-
|
|
4215
|
+
const keys = indices.v.map((n, i) => ({
|
|
4216
|
+
col: Math.trunc(n) - 1,
|
|
4217
|
+
dir: (orders.v.length === 1 ? orders.v[0] : orders.v[i]) < 0 ? -1 : 1
|
|
4218
|
+
}));
|
|
4219
|
+
const width = rows2.length > 0 ? rows2[0].length : 0;
|
|
4220
|
+
if (rows2.length > 0 && keys.some((k) => k.col < 0 || k.col >= width)) {
|
|
4221
|
+
return FormulaValue.errorValue;
|
|
4222
|
+
}
|
|
3851
4223
|
const sorted = rows2.map((r, i) => ({ r, i })).sort((x, y) => {
|
|
3852
|
-
const
|
|
3853
|
-
|
|
4224
|
+
for (const k of keys) {
|
|
4225
|
+
const cmp = FormulaValue.compare(x.r[k.col], y.r[k.col]) * k.dir;
|
|
4226
|
+
if (cmp !== 0) return cmp;
|
|
4227
|
+
}
|
|
4228
|
+
return x.i - y.i;
|
|
3854
4229
|
}).map((o) => o.r);
|
|
3855
4230
|
const out = fromRows(sorted);
|
|
3856
4231
|
return byCol2 ? FormulaValue.array(out.arrayVal.transpose()) : out;
|
|
@@ -4187,39 +4562,43 @@ exports.FormulaHelper = void 0;
|
|
|
4187
4562
|
return result;
|
|
4188
4563
|
}
|
|
4189
4564
|
FormulaHelper2.flattenArgs = flattenArgs;
|
|
4190
|
-
function
|
|
4191
|
-
|
|
4192
|
-
const opMatch = /^(>=|<=|<>|>|<|=)(.*)$/.exec(
|
|
4565
|
+
function compileCriteria(criteria) {
|
|
4566
|
+
const trimmed = criteria.trim();
|
|
4567
|
+
const opMatch = /^(>=|<=|<>|>|<|=)(.*)$/.exec(trimmed);
|
|
4193
4568
|
if (opMatch) {
|
|
4194
4569
|
const op = opMatch[1];
|
|
4195
4570
|
const rhs = opMatch[2];
|
|
4196
4571
|
const rhsVal = parseFloat(rhs);
|
|
4197
4572
|
const rhsNum = isNaN(rhsVal) ? null : rhsVal;
|
|
4198
|
-
const
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
return cmp > 0;
|
|
4202
|
-
case ">=":
|
|
4203
|
-
return cmp >= 0;
|
|
4204
|
-
case "<":
|
|
4205
|
-
return cmp < 0;
|
|
4206
|
-
case "<=":
|
|
4207
|
-
return cmp <= 0;
|
|
4208
|
-
case "<>":
|
|
4209
|
-
return !FormulaValue.areEqual(value2, rhsNum !== null ? FormulaValue.number(rhsNum) : FormulaValue.text(rhs));
|
|
4210
|
-
case "=":
|
|
4211
|
-
return FormulaValue.areEqual(value2, rhsNum !== null ? FormulaValue.number(rhsNum) : FormulaValue.text(rhs));
|
|
4573
|
+
const rhsValue = rhsNum !== null ? FormulaValue.number(rhsNum) : FormulaValue.text(rhs);
|
|
4574
|
+
if (op === "=") {
|
|
4575
|
+
return rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
|
|
4212
4576
|
}
|
|
4577
|
+
if (op === "<>") {
|
|
4578
|
+
return { kind: "other", test: (v) => !FormulaValue.areEqual(v, rhsValue) };
|
|
4579
|
+
}
|
|
4580
|
+
const wanted = op === ">" ? (c) => c > 0 : op === ">=" ? (c) => c >= 0 : op === "<" ? (c) => c < 0 : (c) => c <= 0;
|
|
4581
|
+
const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
|
|
4582
|
+
return rhsNum !== null ? { kind: "compareNumber", number: rhsNum, op, test } : { kind: "other", test };
|
|
4213
4583
|
}
|
|
4214
|
-
if (
|
|
4215
|
-
const pattern = wildcardToRegex(
|
|
4216
|
-
return pattern.test(
|
|
4217
|
-
}
|
|
4218
|
-
const num5 = parseFloat(criteria);
|
|
4219
|
-
if (!isNaN(num5)) {
|
|
4220
|
-
return FormulaValue.areEqual(value2, FormulaValue.number(num5));
|
|
4584
|
+
if (trimmed.includes("*") || trimmed.includes("?")) {
|
|
4585
|
+
const pattern = wildcardToRegex(trimmed);
|
|
4586
|
+
return { kind: "other", test: (v) => pattern.test(v.asText()) };
|
|
4221
4587
|
}
|
|
4222
|
-
|
|
4588
|
+
const num5 = parseFloat(trimmed);
|
|
4589
|
+
return isNaN(num5) ? textEquality(trimmed) : numberEquality(num5);
|
|
4590
|
+
}
|
|
4591
|
+
FormulaHelper2.compileCriteria = compileCriteria;
|
|
4592
|
+
function numberEquality(n) {
|
|
4593
|
+
const target = FormulaValue.number(n);
|
|
4594
|
+
return { kind: "equalsNumber", number: n, test: (v) => FormulaValue.areEqual(v, target) };
|
|
4595
|
+
}
|
|
4596
|
+
function textEquality(s) {
|
|
4597
|
+
const upper2 = s.toUpperCase();
|
|
4598
|
+
return { kind: "equalsText", text: upper2, test: (v) => v.asText().toUpperCase() === upper2 };
|
|
4599
|
+
}
|
|
4600
|
+
function matchesCriteria(value2, criteria) {
|
|
4601
|
+
return compileCriteria(criteria).test(value2);
|
|
4223
4602
|
}
|
|
4224
4603
|
FormulaHelper2.matchesCriteria = matchesCriteria;
|
|
4225
4604
|
function wildcardToRegex(pattern) {
|
|
@@ -4229,7 +4608,7 @@ exports.FormulaHelper = void 0;
|
|
|
4229
4608
|
})(exports.FormulaHelper || (exports.FormulaHelper = {}));
|
|
4230
4609
|
|
|
4231
4610
|
// src/formula-context.ts
|
|
4232
|
-
var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn;
|
|
4611
|
+
var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn, forSheet_fn;
|
|
4233
4612
|
var _FormulaContext = class _FormulaContext {
|
|
4234
4613
|
constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
|
|
4235
4614
|
__privateAdd(this, _FormulaContext_instances);
|
|
@@ -4274,24 +4653,70 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4274
4653
|
getRangeValues(startRef, endRef) {
|
|
4275
4654
|
const s = puredocsExcel.parseCellRef(startRef);
|
|
4276
4655
|
const e = puredocsExcel.parseCellRef(endRef);
|
|
4277
|
-
|
|
4278
|
-
|
|
4656
|
+
return this.getRangeValuesByIndex(s.row, s.column, e.row, e.column);
|
|
4657
|
+
}
|
|
4658
|
+
/**
|
|
4659
|
+
* Resolves a rectangle already known in coordinates. Split out from
|
|
4660
|
+
* {@link getRangeValues} because whole-column references and the range memo
|
|
4661
|
+
* both work in numbers, and formatting them back into `A1` strings only to
|
|
4662
|
+
* re-parse them is measurable at range scale.
|
|
4663
|
+
*/
|
|
4664
|
+
getRangeValuesByIndex(startRow, startCol, endRow, endCol) {
|
|
4665
|
+
const rows2 = endRow - startRow + 1;
|
|
4666
|
+
const cols = endCol - startCol + 1;
|
|
4667
|
+
const cache = rows2 * cols > 1 ? this.rangeCache : void 0;
|
|
4668
|
+
const sheetKey = cache ? __privateGet(this, _sheet).name.toUpperCase() : "";
|
|
4669
|
+
if (cache) {
|
|
4670
|
+
const hit = cache.get(sheetKey, startRow, startCol, endRow, endCol);
|
|
4671
|
+
if (hit !== void 0) return hit;
|
|
4672
|
+
}
|
|
4279
4673
|
const arr = new ArrayValue(rows2, cols);
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4674
|
+
const at = __privateGet(this, _sheet).getCellValueAt;
|
|
4675
|
+
if (at) {
|
|
4676
|
+
for (let r = 0; r < rows2; r++) {
|
|
4677
|
+
for (let c = 0; c < cols; c++) {
|
|
4678
|
+
arr.set(r, c, FormulaValue.fromObjectSync(at.call(__privateGet(this, _sheet), startRow + r, startCol + c)));
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
} else {
|
|
4682
|
+
for (let r = 0; r < rows2; r++) {
|
|
4683
|
+
for (let c = 0; c < cols; c++) {
|
|
4684
|
+
const ref = puredocsExcel.cellRefFromRowCol(startRow + r, startCol + c);
|
|
4685
|
+
arr.set(r, c, this.getCellValue(ref));
|
|
4686
|
+
}
|
|
4284
4687
|
}
|
|
4285
4688
|
}
|
|
4286
|
-
|
|
4689
|
+
const value2 = FormulaValue.array(arr);
|
|
4690
|
+
if (cache && rows2 > 0 && cols > 0) {
|
|
4691
|
+
cache.set(sheetKey, startRow, startCol, endRow, endCol, value2, rows2 * cols);
|
|
4692
|
+
}
|
|
4693
|
+
return value2;
|
|
4694
|
+
}
|
|
4695
|
+
/**
|
|
4696
|
+
* Resolves a whole-column (`A:A`) or whole-row (`1:1`) reference, bounded by
|
|
4697
|
+
* the sheet's used range the way Excel does. Without a bound this would
|
|
4698
|
+
* materialise a million-cell array for a sheet with twelve rows in it.
|
|
4699
|
+
*
|
|
4700
|
+
* A sheet that cannot report its extent yields `#REF!` rather than a guess:
|
|
4701
|
+
* silently reading nothing would make SUM(A:A) return 0 on a full column.
|
|
4702
|
+
*/
|
|
4703
|
+
getFullRangeValues(startCol, endCol, startRow, endRow) {
|
|
4704
|
+
const bounds = __privateGet(this, _sheet).usedBounds;
|
|
4705
|
+
if (!bounds) return FormulaValue.errorRef;
|
|
4706
|
+
if (bounds.rowCount === 0 || bounds.columnCount === 0) return FormulaValue.blank;
|
|
4707
|
+
const r0 = startRow ?? 1;
|
|
4708
|
+
const r1 = endRow ?? bounds.rowCount;
|
|
4709
|
+
const c0 = startCol ?? 1;
|
|
4710
|
+
const c1 = endCol ?? bounds.columnCount;
|
|
4711
|
+
const lastRow = Math.min(r1, bounds.rowCount);
|
|
4712
|
+
const lastCol = Math.min(c1, bounds.columnCount);
|
|
4713
|
+
if (lastRow < r0 || lastCol < c0) return FormulaValue.blank;
|
|
4714
|
+
return this.getRangeValuesByIndex(r0, c0, lastRow, lastCol);
|
|
4287
4715
|
}
|
|
4288
4716
|
getSheetCellValue(sheetName, cellReference) {
|
|
4289
4717
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4290
4718
|
try {
|
|
4291
|
-
|
|
4292
|
-
const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, __privateGet(this, _evaluating), __privateGet(this, _functions2));
|
|
4293
|
-
ctx.workbook = this.workbook;
|
|
4294
|
-
return ctx.getCellValue(cellReference);
|
|
4719
|
+
return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getCellValue(cellReference);
|
|
4295
4720
|
} catch {
|
|
4296
4721
|
return FormulaValue.errorRef;
|
|
4297
4722
|
}
|
|
@@ -4299,10 +4724,16 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4299
4724
|
getSheetRangeValues(sheetName, startRef, endRef) {
|
|
4300
4725
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4301
4726
|
try {
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4727
|
+
return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getRangeValues(startRef, endRef);
|
|
4728
|
+
} catch {
|
|
4729
|
+
return FormulaValue.errorRef;
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
/** Whole-column/whole-row reference qualified by a sheet name. */
|
|
4733
|
+
getSheetFullRangeValues(sheetName, startCol, endCol, startRow, endRow) {
|
|
4734
|
+
if (!this.workbook) return FormulaValue.errorRef;
|
|
4735
|
+
try {
|
|
4736
|
+
return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
|
|
4306
4737
|
} catch {
|
|
4307
4738
|
return FormulaValue.errorRef;
|
|
4308
4739
|
}
|
|
@@ -4357,6 +4788,21 @@ lookupScope_fn = function(name) {
|
|
|
4357
4788
|
}
|
|
4358
4789
|
return void 0;
|
|
4359
4790
|
};
|
|
4791
|
+
/**
|
|
4792
|
+
* A sibling context bound to another sheet, sharing this one's circular-
|
|
4793
|
+
* reference guard and range memo. These contexts only read values — they
|
|
4794
|
+
* never evaluate an AST — so the name resolver and recursion budget are
|
|
4795
|
+
* deliberately not carried over; passing them would suggest a scoping
|
|
4796
|
+
* relationship that does not exist. The memo, on the other hand, must be
|
|
4797
|
+
* carried: cross-sheet ranges are the most expensive reads in a workbook.
|
|
4798
|
+
*/
|
|
4799
|
+
forSheet_fn = function(sheetName) {
|
|
4800
|
+
const sheet = this.workbook.getWorksheet(sheetName);
|
|
4801
|
+
const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, __privateGet(this, _evaluating), __privateGet(this, _functions2));
|
|
4802
|
+
ctx.workbook = this.workbook;
|
|
4803
|
+
ctx.rangeCache = this.rangeCache;
|
|
4804
|
+
return ctx;
|
|
4805
|
+
};
|
|
4360
4806
|
var FormulaContext = _FormulaContext;
|
|
4361
4807
|
|
|
4362
4808
|
// src/lru-cache.ts
|
|
@@ -4476,6 +4922,7 @@ function buildContext(sheet, opts) {
|
|
|
4476
4922
|
evaluating
|
|
4477
4923
|
);
|
|
4478
4924
|
ctx.recursionGuard = opts.recursionGuard ?? { depth: 0, max: DEFAULT_LAMBDA_DEPTH };
|
|
4925
|
+
ctx.rangeCache = opts.rangeCache;
|
|
4479
4926
|
const wb = opts.workbook;
|
|
4480
4927
|
if (wb) {
|
|
4481
4928
|
ctx.workbook = wb;
|
|
@@ -4527,7 +4974,8 @@ function evaluateAst(ast, sheet, options) {
|
|
|
4527
4974
|
const ctx = buildContext(sheet, {
|
|
4528
4975
|
workbook: options?.workbook,
|
|
4529
4976
|
formulaRow: options?.formulaRow,
|
|
4530
|
-
formulaCol: options?.formulaCol
|
|
4977
|
+
formulaCol: options?.formulaCol,
|
|
4978
|
+
rangeCache: options?.rangeCache
|
|
4531
4979
|
});
|
|
4532
4980
|
return ast.evaluate(ctx);
|
|
4533
4981
|
} catch {
|
|
@@ -4548,6 +4996,149 @@ function getAstCacheStats() {
|
|
|
4548
4996
|
function setAstCacheCapacity(capacity) {
|
|
4549
4997
|
astCache.resize(capacity);
|
|
4550
4998
|
}
|
|
4999
|
+
|
|
5000
|
+
// src/range-cache.ts
|
|
5001
|
+
var TILE_ROWS = 64;
|
|
5002
|
+
var TILE_COLS = 64;
|
|
5003
|
+
var MAX_TILES_PER_RANGE = 32;
|
|
5004
|
+
var DEFAULT_MAX_CELLS = 2e6;
|
|
5005
|
+
var _entries, _tiles, _wide, _placements, _sizes, _cells, _maxCells, _RangeCache_static, contains_fn, _RangeCache_instances, drop_fn, fileIn_fn;
|
|
5006
|
+
var _RangeCache = class _RangeCache {
|
|
5007
|
+
constructor(maxCells = DEFAULT_MAX_CELLS) {
|
|
5008
|
+
__privateAdd(this, _RangeCache_instances);
|
|
5009
|
+
/** `SHEET r0 c0 r1 c1` → the memoised array value. Insertion-ordered (FIFO eviction). */
|
|
5010
|
+
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
5011
|
+
/** `sheet tr tc` → entry keys whose rectangle overlaps that tile. */
|
|
5012
|
+
__privateAdd(this, _tiles, /* @__PURE__ */ new Map());
|
|
5013
|
+
/** sheet → entry keys whose rectangle is too broad to tile. */
|
|
5014
|
+
__privateAdd(this, _wide, /* @__PURE__ */ new Map());
|
|
5015
|
+
/** entry key → buckets it was filed under, for cheap removal. */
|
|
5016
|
+
__privateAdd(this, _placements, /* @__PURE__ */ new Map());
|
|
5017
|
+
/** entry key → its area, so eviction can give the budget back. */
|
|
5018
|
+
__privateAdd(this, _sizes, /* @__PURE__ */ new Map());
|
|
5019
|
+
/** Number of cells across all cached rectangles, kept under {@link #maxCells}. */
|
|
5020
|
+
__privateAdd(this, _cells, 0);
|
|
5021
|
+
__privateAdd(this, _maxCells);
|
|
5022
|
+
this.hits = 0;
|
|
5023
|
+
this.misses = 0;
|
|
5024
|
+
__privateSet(this, _maxCells, maxCells);
|
|
5025
|
+
}
|
|
5026
|
+
get size() {
|
|
5027
|
+
return __privateGet(this, _entries).size;
|
|
5028
|
+
}
|
|
5029
|
+
get cachedCells() {
|
|
5030
|
+
return __privateGet(this, _cells);
|
|
5031
|
+
}
|
|
5032
|
+
static key(sheet, r0, c0, r1, c1) {
|
|
5033
|
+
return `${sheet} ${r0} ${c0} ${r1} ${c1}`;
|
|
5034
|
+
}
|
|
5035
|
+
get(sheet, r0, c0, r1, c1) {
|
|
5036
|
+
const hit = __privateGet(this, _entries).get(_RangeCache.key(sheet, r0, c0, r1, c1));
|
|
5037
|
+
if (hit === void 0) this.misses++;
|
|
5038
|
+
else this.hits++;
|
|
5039
|
+
return hit;
|
|
5040
|
+
}
|
|
5041
|
+
/** Memoises a rectangle's value. `cells` is its area, for the budget. */
|
|
5042
|
+
set(sheet, r0, c0, r1, c1, value2, cells) {
|
|
5043
|
+
if (cells <= 0 || cells > __privateGet(this, _maxCells)) return;
|
|
5044
|
+
const key = _RangeCache.key(sheet, r0, c0, r1, c1);
|
|
5045
|
+
if (__privateGet(this, _entries).has(key)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
|
|
5046
|
+
__privateGet(this, _entries).set(key, value2);
|
|
5047
|
+
__privateSet(this, _cells, __privateGet(this, _cells) + cells);
|
|
5048
|
+
const buckets = [];
|
|
5049
|
+
const tileRow0 = r0 / TILE_ROWS | 0, tileRow1 = r1 / TILE_ROWS | 0;
|
|
5050
|
+
const tileCol0 = c0 / TILE_COLS | 0, tileCol1 = c1 / TILE_COLS | 0;
|
|
5051
|
+
const tileCount = (tileRow1 - tileRow0 + 1) * (tileCol1 - tileCol0 + 1);
|
|
5052
|
+
if (tileCount > MAX_TILES_PER_RANGE) {
|
|
5053
|
+
__privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _wide), sheet, key, buckets, `w ${sheet}`);
|
|
5054
|
+
} else {
|
|
5055
|
+
for (let tr = tileRow0; tr <= tileRow1; tr++) {
|
|
5056
|
+
for (let tc = tileCol0; tc <= tileCol1; tc++) {
|
|
5057
|
+
const bucket = `${sheet} ${tr} ${tc}`;
|
|
5058
|
+
__privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _tiles), bucket, key, buckets, `t ${bucket}`);
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
__privateGet(this, _placements).set(key, buckets);
|
|
5063
|
+
__privateGet(this, _sizes).set(key, cells);
|
|
5064
|
+
if (__privateGet(this, _cells) > __privateGet(this, _maxCells)) {
|
|
5065
|
+
for (const oldest of __privateGet(this, _entries).keys()) {
|
|
5066
|
+
if (__privateGet(this, _cells) <= __privateGet(this, _maxCells)) break;
|
|
5067
|
+
if (oldest === key) continue;
|
|
5068
|
+
__privateMethod(this, _RangeCache_instances, drop_fn).call(this, oldest);
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
/** Drops every cached rectangle containing the given cell. */
|
|
5073
|
+
invalidateCell(sheet, row2, col) {
|
|
5074
|
+
var _a, _b;
|
|
5075
|
+
const tile = __privateGet(this, _tiles).get(`${sheet} ${row2 / TILE_ROWS | 0} ${col / TILE_COLS | 0}`);
|
|
5076
|
+
if (tile) {
|
|
5077
|
+
for (const key of [...tile]) {
|
|
5078
|
+
if (__privateMethod(_a = _RangeCache, _RangeCache_static, contains_fn).call(_a, key, sheet, row2, col)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
const wide = __privateGet(this, _wide).get(sheet);
|
|
5082
|
+
if (wide) {
|
|
5083
|
+
for (const key of [...wide]) {
|
|
5084
|
+
if (__privateMethod(_b = _RangeCache, _RangeCache_static, contains_fn).call(_b, key, sheet, row2, col)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
clear() {
|
|
5089
|
+
__privateGet(this, _entries).clear();
|
|
5090
|
+
__privateGet(this, _tiles).clear();
|
|
5091
|
+
__privateGet(this, _wide).clear();
|
|
5092
|
+
__privateGet(this, _placements).clear();
|
|
5093
|
+
__privateGet(this, _sizes).clear();
|
|
5094
|
+
__privateSet(this, _cells, 0);
|
|
5095
|
+
}
|
|
5096
|
+
};
|
|
5097
|
+
_entries = new WeakMap();
|
|
5098
|
+
_tiles = new WeakMap();
|
|
5099
|
+
_wide = new WeakMap();
|
|
5100
|
+
_placements = new WeakMap();
|
|
5101
|
+
_sizes = new WeakMap();
|
|
5102
|
+
_cells = new WeakMap();
|
|
5103
|
+
_maxCells = new WeakMap();
|
|
5104
|
+
_RangeCache_static = new WeakSet();
|
|
5105
|
+
contains_fn = function(key, sheet, row2, col) {
|
|
5106
|
+
const parts = key.split(" ");
|
|
5107
|
+
const n = parts.length;
|
|
5108
|
+
const c1 = +parts[n - 1], r1 = +parts[n - 2], c0 = +parts[n - 3], r0 = +parts[n - 4];
|
|
5109
|
+
if (parts.slice(0, n - 4).join(" ") !== sheet) return false;
|
|
5110
|
+
return row2 >= r0 && row2 <= r1 && col >= c0 && col <= c1;
|
|
5111
|
+
};
|
|
5112
|
+
_RangeCache_instances = new WeakSet();
|
|
5113
|
+
drop_fn = function(key) {
|
|
5114
|
+
if (!__privateGet(this, _entries).delete(key)) return;
|
|
5115
|
+
__privateSet(this, _cells, __privateGet(this, _cells) - (__privateGet(this, _sizes).get(key) ?? 0));
|
|
5116
|
+
__privateGet(this, _sizes).delete(key);
|
|
5117
|
+
const buckets = __privateGet(this, _placements).get(key);
|
|
5118
|
+
if (buckets) {
|
|
5119
|
+
for (const bucket of buckets) {
|
|
5120
|
+
const store = bucket.charCodeAt(0) === 119 ? __privateGet(this, _wide) : __privateGet(this, _tiles);
|
|
5121
|
+
const bucketKey = bucket.slice(2);
|
|
5122
|
+
const set = store.get(bucketKey);
|
|
5123
|
+
if (!set) continue;
|
|
5124
|
+
set.delete(key);
|
|
5125
|
+
if (set.size === 0) store.delete(bucketKey);
|
|
5126
|
+
}
|
|
5127
|
+
__privateGet(this, _placements).delete(key);
|
|
5128
|
+
}
|
|
5129
|
+
};
|
|
5130
|
+
fileIn_fn = function(store, bucketKey, entryKey, buckets, bucketId) {
|
|
5131
|
+
let set = store.get(bucketKey);
|
|
5132
|
+
if (!set) {
|
|
5133
|
+
set = /* @__PURE__ */ new Set();
|
|
5134
|
+
store.set(bucketKey, set);
|
|
5135
|
+
}
|
|
5136
|
+
if (set.has(entryKey)) return;
|
|
5137
|
+
set.add(entryKey);
|
|
5138
|
+
buckets.push(bucketId);
|
|
5139
|
+
};
|
|
5140
|
+
__privateAdd(_RangeCache, _RangeCache_static);
|
|
5141
|
+
var RangeCache = _RangeCache;
|
|
4551
5142
|
function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
|
|
4552
5143
|
const refs = [];
|
|
4553
5144
|
let volatile = false;
|
|
@@ -4576,6 +5167,14 @@ function extractReferences(ast, formulaSheet, registry = FunctionRegistry.defaul
|
|
|
4576
5167
|
refs.push(cell(node.sheetName, node.cellReference));
|
|
4577
5168
|
} else if (node instanceof SheetRangeReferenceNode) {
|
|
4578
5169
|
refs.push(range(node.sheetName, node.startRef, node.endRef));
|
|
5170
|
+
} else if (node instanceof FullRangeReferenceNode) {
|
|
5171
|
+
refs.push({
|
|
5172
|
+
sheet: (node.sheetName ?? formulaSheet).toUpperCase(),
|
|
5173
|
+
startRow: node.startRow ?? 1,
|
|
5174
|
+
endRow: node.endRow ?? puredocsExcel.EXCEL_MAX_ROWS,
|
|
5175
|
+
startCol: node.startCol ?? 1,
|
|
5176
|
+
endCol: node.endCol ?? puredocsExcel.EXCEL_MAX_COLUMNS
|
|
5177
|
+
});
|
|
4579
5178
|
} else if (node instanceof NamedRangeNode) {
|
|
4580
5179
|
expandName(node.name);
|
|
4581
5180
|
} else if (node instanceof SpillReferenceNode) {
|
|
@@ -4612,19 +5211,30 @@ function refContains(ref, sheet, row2, col) {
|
|
|
4612
5211
|
}
|
|
4613
5212
|
|
|
4614
5213
|
// src/dependency-index.ts
|
|
4615
|
-
var
|
|
4616
|
-
var
|
|
5214
|
+
var TILE_ROWS2 = 64;
|
|
5215
|
+
var TILE_COLS2 = 64;
|
|
4617
5216
|
var MAX_TILES_PER_REF = 32;
|
|
4618
|
-
var
|
|
5217
|
+
var MAX_EXACT_SPAN = 16;
|
|
5218
|
+
var MAX_BUCKETS_PER_REF = 64;
|
|
5219
|
+
var tileOfRow = (row2) => row2 / TILE_ROWS2 | 0;
|
|
5220
|
+
var tileOfCol = (col) => col / TILE_COLS2 | 0;
|
|
5221
|
+
var _buckets, _placements2, _DependencyIndex_instances, visit_fn, file_fn;
|
|
4619
5222
|
var DependencyIndex = class {
|
|
4620
5223
|
constructor() {
|
|
4621
5224
|
__privateAdd(this, _DependencyIndex_instances);
|
|
4622
|
-
/**
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
5225
|
+
/**
|
|
5226
|
+
* All buckets in one map, keyed by a discriminating prefix:
|
|
5227
|
+
*
|
|
5228
|
+
* `t s tr tc` rectangle compact in both dimensions
|
|
5229
|
+
* `c s col tr` narrow columns, row tiles (the common case)
|
|
5230
|
+
* `C s col` narrow columns, every row (`A:A`)
|
|
5231
|
+
* `r s row tc` narrow rows, column tiles
|
|
5232
|
+
* `R s row` narrow rows, every column (`1:1`)
|
|
5233
|
+
* `w s` broad in both dimensions
|
|
5234
|
+
*/
|
|
5235
|
+
__privateAdd(this, _buckets, /* @__PURE__ */ new Map());
|
|
5236
|
+
/** formula key → the buckets it was filed under, for cheap removal. */
|
|
5237
|
+
__privateAdd(this, _placements2, /* @__PURE__ */ new Map());
|
|
4628
5238
|
}
|
|
4629
5239
|
/** Registers (or re-registers) a formula's precedent rectangles. */
|
|
4630
5240
|
add(formulaKey, refs) {
|
|
@@ -4632,78 +5242,117 @@ var DependencyIndex = class {
|
|
|
4632
5242
|
if (refs.length === 0) return;
|
|
4633
5243
|
const buckets = [];
|
|
4634
5244
|
for (const ref of refs) {
|
|
4635
|
-
const
|
|
4636
|
-
const
|
|
4637
|
-
const
|
|
4638
|
-
const
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
|
|
5245
|
+
const rowSpan = ref.endRow - ref.startRow + 1;
|
|
5246
|
+
const colSpan = ref.endCol - ref.startCol + 1;
|
|
5247
|
+
const rowTile0 = tileOfRow(ref.startRow), rowTile1 = tileOfRow(ref.endRow);
|
|
5248
|
+
const colTile0 = tileOfCol(ref.startCol), colTile1 = tileOfCol(ref.endCol);
|
|
5249
|
+
const rowTiles = rowTile1 - rowTile0 + 1;
|
|
5250
|
+
const colTiles = colTile1 - colTile0 + 1;
|
|
5251
|
+
if (colSpan <= MAX_EXACT_SPAN && colSpan <= rowSpan) {
|
|
5252
|
+
if (colSpan * rowTiles <= MAX_BUCKETS_PER_REF && rowTiles <= MAX_TILES_PER_REF) {
|
|
5253
|
+
for (let col = ref.startCol; col <= ref.endCol; col++) {
|
|
5254
|
+
for (let tr = rowTile0; tr <= rowTile1; tr++) {
|
|
5255
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `c ${ref.sheet} ${col} ${tr}`, formulaKey, buckets);
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
} else {
|
|
5259
|
+
for (let col = ref.startCol; col <= ref.endCol; col++) {
|
|
5260
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `C ${ref.sheet} ${col}`, formulaKey, buckets);
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
4642
5263
|
continue;
|
|
4643
5264
|
}
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
5265
|
+
if (rowSpan <= MAX_EXACT_SPAN) {
|
|
5266
|
+
if (rowSpan * colTiles <= MAX_BUCKETS_PER_REF && colTiles <= MAX_TILES_PER_REF) {
|
|
5267
|
+
for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
|
|
5268
|
+
for (let tc = colTile0; tc <= colTile1; tc++) {
|
|
5269
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `r ${ref.sheet} ${row2} ${tc}`, formulaKey, buckets);
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
} else {
|
|
5273
|
+
for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
|
|
5274
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `R ${ref.sheet} ${row2}`, formulaKey, buckets);
|
|
5275
|
+
}
|
|
4648
5276
|
}
|
|
5277
|
+
continue;
|
|
4649
5278
|
}
|
|
5279
|
+
if (rowTiles * colTiles <= MAX_TILES_PER_REF) {
|
|
5280
|
+
for (let tr = rowTile0; tr <= rowTile1; tr++) {
|
|
5281
|
+
for (let tc = colTile0; tc <= colTile1; tc++) {
|
|
5282
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `t ${ref.sheet} ${tr} ${tc}`, formulaKey, buckets);
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
continue;
|
|
5286
|
+
}
|
|
5287
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `w ${ref.sheet}`, formulaKey, buckets);
|
|
4650
5288
|
}
|
|
4651
|
-
__privateGet(this,
|
|
5289
|
+
__privateGet(this, _placements2).set(formulaKey, buckets);
|
|
4652
5290
|
}
|
|
4653
5291
|
/** Forgets a formula. */
|
|
4654
5292
|
remove(formulaKey) {
|
|
4655
|
-
const buckets = __privateGet(this,
|
|
5293
|
+
const buckets = __privateGet(this, _placements2).get(formulaKey);
|
|
4656
5294
|
if (!buckets) return;
|
|
4657
5295
|
for (const bucket of buckets) {
|
|
4658
|
-
const
|
|
4659
|
-
const store = wide ? __privateGet(this, _wide) : __privateGet(this, _tiles);
|
|
4660
|
-
const key = bucket.slice(2);
|
|
4661
|
-
const set = store.get(key);
|
|
5296
|
+
const set = __privateGet(this, _buckets).get(bucket);
|
|
4662
5297
|
if (!set) continue;
|
|
4663
5298
|
set.delete(formulaKey);
|
|
4664
|
-
if (set.size === 0)
|
|
5299
|
+
if (set.size === 0) __privateGet(this, _buckets).delete(bucket);
|
|
4665
5300
|
}
|
|
4666
|
-
__privateGet(this,
|
|
5301
|
+
__privateGet(this, _placements2).delete(formulaKey);
|
|
4667
5302
|
}
|
|
4668
5303
|
/** Drops every registration. */
|
|
4669
5304
|
clear() {
|
|
4670
|
-
__privateGet(this,
|
|
4671
|
-
__privateGet(this,
|
|
4672
|
-
__privateGet(this, _placements).clear();
|
|
5305
|
+
__privateGet(this, _buckets).clear();
|
|
5306
|
+
__privateGet(this, _placements2).clear();
|
|
4673
5307
|
}
|
|
4674
5308
|
/**
|
|
4675
5309
|
* Calls `visit` with the key of each formula that *may* read the given cell.
|
|
4676
|
-
* Callers still confirm with {@link refContains}:
|
|
4677
|
-
*
|
|
5310
|
+
* Callers still confirm with {@link refContains}: buckets are coarser than
|
|
5311
|
+
* rectangles in the broad dimension, so this over-approximates.
|
|
5312
|
+
*
|
|
5313
|
+
* A fixed six lookups, whatever the workbook contains.
|
|
4678
5314
|
*/
|
|
4679
5315
|
candidates(sheet, row2, col, visit) {
|
|
4680
|
-
const
|
|
4681
|
-
|
|
4682
|
-
);
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
5316
|
+
const tr = tileOfRow(row2);
|
|
5317
|
+
const tc = tileOfCol(col);
|
|
5318
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `c ${sheet} ${col} ${tr}`, visit);
|
|
5319
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `C ${sheet} ${col}`, visit);
|
|
5320
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `r ${sheet} ${row2} ${tc}`, visit);
|
|
5321
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `R ${sheet} ${row2}`, visit);
|
|
5322
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `t ${sheet} ${tr} ${tc}`, visit);
|
|
5323
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `w ${sheet}`, visit);
|
|
4686
5324
|
}
|
|
4687
5325
|
};
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
_placements = new WeakMap();
|
|
5326
|
+
_buckets = new WeakMap();
|
|
5327
|
+
_placements2 = new WeakMap();
|
|
4691
5328
|
_DependencyIndex_instances = new WeakSet();
|
|
4692
|
-
|
|
4693
|
-
|
|
5329
|
+
visit_fn = function(bucket, visit) {
|
|
5330
|
+
const set = __privateGet(this, _buckets).get(bucket);
|
|
5331
|
+
if (set) for (const key of set) visit(key);
|
|
5332
|
+
};
|
|
5333
|
+
file_fn = function(bucket, formulaKey, buckets) {
|
|
5334
|
+
let set = __privateGet(this, _buckets).get(bucket);
|
|
4694
5335
|
if (!set) {
|
|
4695
5336
|
set = /* @__PURE__ */ new Set();
|
|
4696
|
-
|
|
5337
|
+
__privateGet(this, _buckets).set(bucket, set);
|
|
4697
5338
|
}
|
|
4698
5339
|
if (set.has(formulaKey)) return;
|
|
4699
5340
|
set.add(formulaKey);
|
|
4700
|
-
buckets.push(
|
|
5341
|
+
buckets.push(bucket);
|
|
4701
5342
|
};
|
|
4702
5343
|
|
|
4703
5344
|
// src/recalc-engine.ts
|
|
4704
5345
|
function createWorkbookRecalcModel(workbook) {
|
|
4705
5346
|
return {
|
|
4706
5347
|
getCellValue: (sheet, ref) => workbook.getWorksheet(sheet).getCellValue(ref),
|
|
5348
|
+
// Falls back to the reference-string read rather than reporting "no value":
|
|
5349
|
+
// an older core without getCellValueAt must still return the real value,
|
|
5350
|
+
// because a silent undefined here reads as a blank cell.
|
|
5351
|
+
getCellValueAt: (sheet, row2, column2) => {
|
|
5352
|
+
const ws = workbook.getWorksheet(sheet);
|
|
5353
|
+
return ws.getCellValueAt ? ws.getCellValueAt(row2, column2) : ws.getCellValue(puredocsExcel.cellRefFromRowCol(row2, column2));
|
|
5354
|
+
},
|
|
5355
|
+
getUsedBounds: (sheet) => workbook.getWorksheet(sheet).usedBounds,
|
|
4707
5356
|
setCellValue: (sheet, ref, value2) => {
|
|
4708
5357
|
workbook.getWorksheet(sheet).getCell(ref).setComputedValue(value2);
|
|
4709
5358
|
},
|
|
@@ -4732,7 +5381,7 @@ function parseKey(key) {
|
|
|
4732
5381
|
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(ref);
|
|
4733
5382
|
return { sheet, ref, row: row2, col: column2 };
|
|
4734
5383
|
}
|
|
4735
|
-
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _RecalcEngine_instances, register_fn, unregister_fn, rememberSheet_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;
|
|
5384
|
+
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;
|
|
4736
5385
|
var RecalcEngine = class {
|
|
4737
5386
|
constructor(model, registry = FunctionRegistry.default) {
|
|
4738
5387
|
__privateAdd(this, _RecalcEngine_instances);
|
|
@@ -4758,6 +5407,14 @@ var RecalcEngine = class {
|
|
|
4758
5407
|
/** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
|
|
4759
5408
|
__privateAdd(this, _sheetLikes, /* @__PURE__ */ new Map());
|
|
4760
5409
|
__privateAdd(this, _workbookLike);
|
|
5410
|
+
/**
|
|
5411
|
+
* Memo for range reads. The engine owns it because it owns the writes: every
|
|
5412
|
+
* value written goes through {@link #writeCell}, which drops the cached
|
|
5413
|
+
* rectangles containing that cell. Nothing else may write to the model behind
|
|
5414
|
+
* the engine's back without calling {@link onCellChanged} — that was already
|
|
5415
|
+
* true for dependency tracking, and now also keeps this memo honest.
|
|
5416
|
+
*/
|
|
5417
|
+
__privateAdd(this, _rangeCache, new RangeCache());
|
|
4761
5418
|
__privateSet(this, _model, model);
|
|
4762
5419
|
__privateSet(this, _registry, registry);
|
|
4763
5420
|
}
|
|
@@ -4795,8 +5452,63 @@ var RecalcEngine = class {
|
|
|
4795
5452
|
volatile
|
|
4796
5453
|
});
|
|
4797
5454
|
__privateGet(this, _model).setCellFormula?.(sheet, r, text);
|
|
5455
|
+
__privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
|
|
4798
5456
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }], key);
|
|
4799
5457
|
}
|
|
5458
|
+
/**
|
|
5459
|
+
* Indexes many formulas **without evaluating anything**.
|
|
5460
|
+
*
|
|
5461
|
+
* This is the path for loading a workbook. `setCellFormula` is built for an
|
|
5462
|
+
* edit: it recalculates the new cell and everything downstream of it, which is
|
|
5463
|
+
* right for one keystroke and quadratic for a file — registering N formulas one
|
|
5464
|
+
* by one evaluates the whole workbook N times. Here the formulas are only
|
|
5465
|
+
* parsed and filed into the dependency index; call {@link recalcAll} once
|
|
5466
|
+
* afterwards, or nothing at all if the cached values from the file will do.
|
|
5467
|
+
*
|
|
5468
|
+
* The model is deliberately **not** written to. These formulas are assumed to
|
|
5469
|
+
* be the ones the model already holds (they came from the file), and persisting
|
|
5470
|
+
* them again would clear each cell's cached value — leaving every formula cell
|
|
5471
|
+
* blank until a full recalculation had run. Use `setCellFormula` for formulas
|
|
5472
|
+
* the user is actually introducing.
|
|
5473
|
+
*
|
|
5474
|
+
* A formula that fails to parse is reported rather than thrown: one unsupported
|
|
5475
|
+
* construct in a large workbook should not abort the load.
|
|
5476
|
+
*/
|
|
5477
|
+
registerBulk(entries) {
|
|
5478
|
+
const resolveName2 = __privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0;
|
|
5479
|
+
let registered = 0;
|
|
5480
|
+
const failed = [];
|
|
5481
|
+
for (const entry of entries) {
|
|
5482
|
+
const { sheet, ref, formula } = entry;
|
|
5483
|
+
if (!formula) continue;
|
|
5484
|
+
const text = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
5485
|
+
const s = normSheet(sheet);
|
|
5486
|
+
const r = normRef(ref);
|
|
5487
|
+
try {
|
|
5488
|
+
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
5489
|
+
const ast = parseFormulaUncached(text);
|
|
5490
|
+
const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
|
|
5491
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
5492
|
+
__privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
|
|
5493
|
+
sheet: s,
|
|
5494
|
+
sheetName: sheet,
|
|
5495
|
+
ref: r,
|
|
5496
|
+
key: keyOf(s, r),
|
|
5497
|
+
row: row2,
|
|
5498
|
+
col: column2,
|
|
5499
|
+
formula: text,
|
|
5500
|
+
ast,
|
|
5501
|
+
refs,
|
|
5502
|
+
volatile
|
|
5503
|
+
});
|
|
5504
|
+
registered++;
|
|
5505
|
+
} catch (err) {
|
|
5506
|
+
failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
if (registered > 0) __privateGet(this, _rangeCache).clear();
|
|
5510
|
+
return { registered, failed };
|
|
5511
|
+
}
|
|
4800
5512
|
/**
|
|
4801
5513
|
* Removes a formula from a cell and recalculates its dependents (which may now
|
|
4802
5514
|
* read a plain input value instead). Returns the recomputed dependent cells.
|
|
@@ -4809,6 +5521,7 @@ var RecalcEngine = class {
|
|
|
4809
5521
|
__privateMethod(this, _RecalcEngine_instances, unregister_fn).call(this, key);
|
|
4810
5522
|
__privateGet(this, _model).clearCell?.(sheet, r);
|
|
4811
5523
|
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
5524
|
+
__privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
|
|
4812
5525
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4813
5526
|
}
|
|
4814
5527
|
/**
|
|
@@ -4819,8 +5532,8 @@ var RecalcEngine = class {
|
|
|
4819
5532
|
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4820
5533
|
const s = normSheet(sheet);
|
|
4821
5534
|
const r = normRef(ref);
|
|
4822
|
-
__privateGet(this, _model).setCellValue(sheet, r, value2);
|
|
4823
5535
|
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
5536
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, sheet, r, value2, row2, column2);
|
|
4824
5537
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4825
5538
|
}
|
|
4826
5539
|
/**
|
|
@@ -4832,11 +5545,48 @@ var RecalcEngine = class {
|
|
|
4832
5545
|
const s = normSheet(sheet);
|
|
4833
5546
|
const r = normRef(ref);
|
|
4834
5547
|
const { row: row2, column: column2 } = puredocsExcel.parseCellRef(r);
|
|
5548
|
+
__privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
|
|
4835
5549
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4836
5550
|
}
|
|
4837
|
-
/**
|
|
4838
|
-
|
|
4839
|
-
|
|
5551
|
+
/**
|
|
5552
|
+
* Recalculates every registered formula.
|
|
5553
|
+
*
|
|
5554
|
+
* By default the order is derived here, with a topological sort over the
|
|
5555
|
+
* dependency index. `order` — typically `workbook.calcChain` — supplies one
|
|
5556
|
+
* instead, so the sort is not needed.
|
|
5557
|
+
*
|
|
5558
|
+
* **A supplied order is verified, not trusted.** Evaluating in a stale order
|
|
5559
|
+
* makes a formula read a precedent that has not been computed yet, and the
|
|
5560
|
+
* result is a plausible wrong number with nothing to distinguish it from a
|
|
5561
|
+
* right one. In a calculation engine that is the worst failure available, so
|
|
5562
|
+
* this checks as it goes: after each formula is written, the dependency index
|
|
5563
|
+
* says who reads it, and any reader that was already evaluated is an inversion.
|
|
5564
|
+
* On the first inversion the ordered attempt is abandoned and the whole
|
|
5565
|
+
* recalculation is redone through the sorted path, whose result is returned;
|
|
5566
|
+
* `onOrderConflict`, if given, is told which pair conflicted.
|
|
5567
|
+
*
|
|
5568
|
+
* The verification costs about what the sort it replaces costs, so `order` is
|
|
5569
|
+
* worth roughly nothing now (measured at ~9% of a full recalculation on a
|
|
5570
|
+
* 2 000-formula workbook, after the dependency index stopped being the
|
|
5571
|
+
* bottleneck). It is kept because reading an order Excel already computed is a
|
|
5572
|
+
* reasonable thing to want; prefer plain `recalcAll()` unless you have measured
|
|
5573
|
+
* a reason not to.
|
|
5574
|
+
*
|
|
5575
|
+
* Registered formulas the order does not mention are evaluated afterwards
|
|
5576
|
+
* through the sorted path, and a dynamic array that spills still triggers the
|
|
5577
|
+
* usual fixpoint, so neither silently misses cells.
|
|
5578
|
+
*/
|
|
5579
|
+
recalcAll(options) {
|
|
5580
|
+
const order = options?.order;
|
|
5581
|
+
if (!order || order.length === 0) {
|
|
5582
|
+
return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
|
|
5583
|
+
}
|
|
5584
|
+
const attempt = __privateMethod(this, _RecalcEngine_instances, recalcInOrder_fn).call(this, order);
|
|
5585
|
+
if (attempt.conflict) {
|
|
5586
|
+
options?.onOrderConflict?.(attempt.conflict);
|
|
5587
|
+
return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
|
|
5588
|
+
}
|
|
5589
|
+
return attempt.results;
|
|
4840
5590
|
}
|
|
4841
5591
|
/** The precedents recorded for a formula cell (mainly for tests/inspection). */
|
|
4842
5592
|
getPrecedents(sheet, ref) {
|
|
@@ -4886,7 +5636,23 @@ _dependents = new WeakMap();
|
|
|
4886
5636
|
_volatiles = new WeakMap();
|
|
4887
5637
|
_sheetLikes = new WeakMap();
|
|
4888
5638
|
_workbookLike = new WeakMap();
|
|
5639
|
+
_rangeCache = new WeakMap();
|
|
4889
5640
|
_RecalcEngine_instances = new WeakSet();
|
|
5641
|
+
/**
|
|
5642
|
+
* The single write path into the model. Writing a value invalidates the
|
|
5643
|
+
* cached rectangles that contain the cell, so a formula evaluated later in the
|
|
5644
|
+
* same pass cannot read a pre-write copy of a range. Coordinates are passed in
|
|
5645
|
+
* where the caller already knows them, to avoid re-parsing the reference.
|
|
5646
|
+
*/
|
|
5647
|
+
writeCell_fn = function(sheetName, ref, value2, row2, col) {
|
|
5648
|
+
__privateGet(this, _model).setCellValue(sheetName, ref, value2);
|
|
5649
|
+
if (row2 === void 0 || col === void 0) {
|
|
5650
|
+
const parsed = puredocsExcel.parseCellRef(ref);
|
|
5651
|
+
row2 = parsed.row;
|
|
5652
|
+
col = parsed.column;
|
|
5653
|
+
}
|
|
5654
|
+
__privateGet(this, _rangeCache).invalidateCell(normSheet(sheetName), row2, col);
|
|
5655
|
+
};
|
|
4890
5656
|
/** Registers an entry in the map, the reverse index and the volatile set. */
|
|
4891
5657
|
register_fn = function(entry) {
|
|
4892
5658
|
__privateGet(this, _formulas).set(entry.key, entry);
|
|
@@ -4902,6 +5668,49 @@ unregister_fn = function(key) {
|
|
|
4902
5668
|
rememberSheet_fn = function(name) {
|
|
4903
5669
|
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
4904
5670
|
};
|
|
5671
|
+
/**
|
|
5672
|
+
* One pass in a caller-supplied order, watching for inversions.
|
|
5673
|
+
*
|
|
5674
|
+
* The check is the mirror image of the topological sort: instead of asking
|
|
5675
|
+
* "which of my precedents come first?", it asks, of each formula just
|
|
5676
|
+
* evaluated, "has anything that reads me already run?". The dependency index
|
|
5677
|
+
* answers that directly, and one positive answer is enough to know the order
|
|
5678
|
+
* cannot be trusted.
|
|
5679
|
+
*/
|
|
5680
|
+
recalcInOrder_fn = function(order) {
|
|
5681
|
+
const results = [];
|
|
5682
|
+
const changed = [];
|
|
5683
|
+
const done = /* @__PURE__ */ new Set();
|
|
5684
|
+
let spilled = false;
|
|
5685
|
+
for (const { sheet, ref } of order) {
|
|
5686
|
+
const key = keyOf(sheet, ref);
|
|
5687
|
+
if (done.has(key)) continue;
|
|
5688
|
+
const entry = __privateGet(this, _formulas).get(key);
|
|
5689
|
+
if (!entry) continue;
|
|
5690
|
+
done.add(key);
|
|
5691
|
+
const written = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, entry);
|
|
5692
|
+
results.push(written.result);
|
|
5693
|
+
changed.push(...written.changed);
|
|
5694
|
+
if (written.result.spill) spilled = true;
|
|
5695
|
+
let conflict;
|
|
5696
|
+
__privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
|
|
5697
|
+
if (conflict || dep.key === entry.key) return;
|
|
5698
|
+
if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
|
|
5699
|
+
});
|
|
5700
|
+
if (conflict) return { results: [], conflict };
|
|
5701
|
+
}
|
|
5702
|
+
const rest = [...__privateGet(this, _formulas).values()].filter((e) => !done.has(e.key));
|
|
5703
|
+
if (rest.length > 0) {
|
|
5704
|
+
const pass = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, rest);
|
|
5705
|
+
results.push(...pass.results);
|
|
5706
|
+
changed.push(...pass.changed);
|
|
5707
|
+
}
|
|
5708
|
+
if (spilled) {
|
|
5709
|
+
const evaluated = new Set(done);
|
|
5710
|
+
results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated), evaluated));
|
|
5711
|
+
}
|
|
5712
|
+
return { results };
|
|
5713
|
+
};
|
|
4905
5714
|
applyShift_fn = function(shift) {
|
|
4906
5715
|
const editedSheet = normSheet(shift.sheetName);
|
|
4907
5716
|
const isRowShift = shift.dim === "row";
|
|
@@ -4943,6 +5752,7 @@ applyShift_fn = function(shift) {
|
|
|
4943
5752
|
__privateGet(this, _volatiles).clear();
|
|
4944
5753
|
for (const e of rekeyed) __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, e);
|
|
4945
5754
|
__privateMethod(this, _RecalcEngine_instances, shiftSpillBookkeeping_fn).call(this, shift, editedSheet);
|
|
5755
|
+
__privateGet(this, _rangeCache).clear();
|
|
4946
5756
|
const pending = /* @__PURE__ */ new Map();
|
|
4947
5757
|
for (const k of needsRecalc) {
|
|
4948
5758
|
const e = __privateGet(this, _formulas).get(k);
|
|
@@ -5013,7 +5823,7 @@ shiftSpillBookkeeping_fn = function(shift, editedSheet) {
|
|
|
5013
5823
|
const shifted = shiftCellKey(m);
|
|
5014
5824
|
if (!shifted) continue;
|
|
5015
5825
|
const cell = parseKey(shifted);
|
|
5016
|
-
|
|
5826
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
5017
5827
|
}
|
|
5018
5828
|
continue;
|
|
5019
5829
|
}
|
|
@@ -5154,7 +5964,7 @@ evaluatePass_fn = function(entries) {
|
|
|
5154
5964
|
if (emitted.has(k)) continue;
|
|
5155
5965
|
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, e.key, changed);
|
|
5156
5966
|
const value2 = FormulaValue.errorRef.toObject();
|
|
5157
|
-
|
|
5967
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
5158
5968
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
5159
5969
|
results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
|
|
5160
5970
|
}
|
|
@@ -5175,7 +5985,7 @@ writeResult_fn = function(e) {
|
|
|
5175
5985
|
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
5176
5986
|
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
5177
5987
|
const value2 = fv2.toObject();
|
|
5178
|
-
|
|
5988
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
5179
5989
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
5180
5990
|
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
5181
5991
|
}
|
|
@@ -5196,7 +6006,7 @@ writeResult_fn = function(e) {
|
|
|
5196
6006
|
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
5197
6007
|
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
5198
6008
|
const value2 = FormulaValue.error(9 /* Spill */).toObject();
|
|
5199
|
-
|
|
6009
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
5200
6010
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
5201
6011
|
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
5202
6012
|
}
|
|
@@ -5205,7 +6015,7 @@ writeResult_fn = function(e) {
|
|
|
5205
6015
|
for (let dc = 0; dc < cols; dc++) {
|
|
5206
6016
|
const r = e.row + dr, c = e.col + dc;
|
|
5207
6017
|
const ref = puredocsExcel.cellRefFromRowCol(r, c);
|
|
5208
|
-
|
|
6018
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, ref, arr.get(dr, dc).toObject(), r, c);
|
|
5209
6019
|
changed.push({ sheet: e.sheet, row: r, col: c });
|
|
5210
6020
|
if (dr !== 0 || dc !== 0) {
|
|
5211
6021
|
const cellKey2 = keyOf(e.sheet, ref);
|
|
@@ -5217,7 +6027,7 @@ writeResult_fn = function(e) {
|
|
|
5217
6027
|
for (const oldKey of prevSpill) {
|
|
5218
6028
|
if (newSpill.has(oldKey)) continue;
|
|
5219
6029
|
const cell = parseKey(oldKey);
|
|
5220
|
-
|
|
6030
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
5221
6031
|
__privateGet(this, _spillOwner).delete(oldKey);
|
|
5222
6032
|
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
5223
6033
|
}
|
|
@@ -5244,7 +6054,7 @@ clearSpill_fn = function(anchorKey, changed) {
|
|
|
5244
6054
|
if (!spill) return;
|
|
5245
6055
|
for (const cellKey2 of spill) {
|
|
5246
6056
|
const cell = parseKey(cellKey2);
|
|
5247
|
-
|
|
6057
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
5248
6058
|
__privateGet(this, _spillOwner).delete(cellKey2);
|
|
5249
6059
|
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
5250
6060
|
}
|
|
@@ -5261,7 +6071,8 @@ evaluateFormulaValue_fn = function(e) {
|
|
|
5261
6071
|
return evaluateAst(e.ast, __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName), {
|
|
5262
6072
|
workbook: __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this),
|
|
5263
6073
|
formulaRow: e.row,
|
|
5264
|
-
formulaCol: e.col
|
|
6074
|
+
formulaCol: e.col,
|
|
6075
|
+
rangeCache: __privateGet(this, _rangeCache)
|
|
5265
6076
|
});
|
|
5266
6077
|
};
|
|
5267
6078
|
/**
|
|
@@ -5278,9 +6089,16 @@ sheetLike_fn = function(sheetName) {
|
|
|
5278
6089
|
return cached;
|
|
5279
6090
|
};
|
|
5280
6091
|
buildSheetLike_fn = function(sheetName) {
|
|
6092
|
+
const model = __privateGet(this, _model);
|
|
5281
6093
|
return {
|
|
5282
6094
|
name: sheetName,
|
|
5283
6095
|
getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
|
|
6096
|
+
getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
|
|
6097
|
+
// A getter, not a snapshot: the extent grows as formulas write results,
|
|
6098
|
+
// and a whole-column reference read later in the same pass must see it.
|
|
6099
|
+
get usedBounds() {
|
|
6100
|
+
return model.getUsedBounds?.(sheetName);
|
|
6101
|
+
},
|
|
5284
6102
|
isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
|
|
5285
6103
|
getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
|
|
5286
6104
|
getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
|
|
@@ -5319,12 +6137,14 @@ exports.FormulaLexer = FormulaLexer;
|
|
|
5319
6137
|
exports.FormulaNode = FormulaNode;
|
|
5320
6138
|
exports.FormulaParser = FormulaParser;
|
|
5321
6139
|
exports.FormulaValue = FormulaValue;
|
|
6140
|
+
exports.FullRangeReferenceNode = FullRangeReferenceNode;
|
|
5322
6141
|
exports.FunctionCallNode = FunctionCallNode;
|
|
5323
6142
|
exports.FunctionRegistry = FunctionRegistry;
|
|
5324
6143
|
exports.ImplicitIntersectionNode = ImplicitIntersectionNode;
|
|
5325
6144
|
exports.LruCache = LruCache;
|
|
5326
6145
|
exports.NamedRangeNode = NamedRangeNode;
|
|
5327
6146
|
exports.NumberNode = NumberNode;
|
|
6147
|
+
exports.RangeCache = RangeCache;
|
|
5328
6148
|
exports.RangeReferenceNode = RangeReferenceNode;
|
|
5329
6149
|
exports.RecalcEngine = RecalcEngine;
|
|
5330
6150
|
exports.SheetCellReferenceNode = SheetCellReferenceNode;
|