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