@devmm/puredocs-excel-formula 1.0.6 → 1.0.7
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 +793 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -18
- package/dist/index.d.ts +226 -18
- package/dist/index.js +793 -122
- 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);
|
|
@@ -802,6 +802,19 @@ var SheetRangeReferenceNode = class extends FormulaNode {
|
|
|
802
802
|
return ctx.getSheetRangeValues(this.sheetName, this.startRef, this.endRef);
|
|
803
803
|
}
|
|
804
804
|
};
|
|
805
|
+
var FullRangeReferenceNode = class extends FormulaNode {
|
|
806
|
+
constructor(startCol, endCol, startRow, endRow, sheetName) {
|
|
807
|
+
super();
|
|
808
|
+
this.startCol = startCol;
|
|
809
|
+
this.endCol = endCol;
|
|
810
|
+
this.startRow = startRow;
|
|
811
|
+
this.endRow = endRow;
|
|
812
|
+
this.sheetName = sheetName;
|
|
813
|
+
}
|
|
814
|
+
evaluate(ctx) {
|
|
815
|
+
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);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
805
818
|
var NamedRangeNode = class extends FormulaNode {
|
|
806
819
|
constructor(name) {
|
|
807
820
|
super();
|
|
@@ -1021,7 +1034,7 @@ var CallNode = class extends FormulaNode {
|
|
|
1021
1034
|
};
|
|
1022
1035
|
|
|
1023
1036
|
// 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;
|
|
1037
|
+
var _tokens, _pos2, _FormulaParser_instances, current_get, advance_fn, expect_fn, parseComparison_fn, parseConcatenation_fn, parseAddSub_fn, parseMulDiv_fn, parseUnary_fn, parsePower_fn, parsePercent_fn, parsePostfix_fn, parsePrimary_fn, tryParseFullRange_fn, parseFunction_fn, parseArgList_fn;
|
|
1025
1038
|
var FormulaParser = class {
|
|
1026
1039
|
constructor(tokens) {
|
|
1027
1040
|
__privateAdd(this, _FormulaParser_instances);
|
|
@@ -1145,6 +1158,10 @@ parsePrimary_fn = function() {
|
|
|
1145
1158
|
switch (token.type) {
|
|
1146
1159
|
case 0 /* Number */: {
|
|
1147
1160
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1161
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1162
|
+
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, token.value);
|
|
1163
|
+
if (full) return full;
|
|
1164
|
+
}
|
|
1148
1165
|
const n = parseFloat(token.value);
|
|
1149
1166
|
if (isNaN(n)) throw new FormulaException(`Invalid number: ${token.value}`);
|
|
1150
1167
|
return new NumberNode(n);
|
|
@@ -1173,15 +1190,22 @@ parsePrimary_fn = function() {
|
|
|
1173
1190
|
const sheetName = parts[0];
|
|
1174
1191
|
const cellRef = parts[1] ?? "";
|
|
1175
1192
|
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1193
|
+
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, cellRef, sheetName);
|
|
1194
|
+
if (full) return full;
|
|
1176
1195
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1177
1196
|
const end = __privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 3 /* CellReference */);
|
|
1178
1197
|
return new SheetRangeReferenceNode(sheetName, cellRef, end.value);
|
|
1179
1198
|
}
|
|
1180
1199
|
return new SheetCellReferenceNode(sheetName, cellRef);
|
|
1181
1200
|
}
|
|
1182
|
-
case 23 /* NamedRange */:
|
|
1201
|
+
case 23 /* NamedRange */: {
|
|
1183
1202
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1203
|
+
if (__privateGet(this, _FormulaParser_instances, current_get).type === 4 /* Colon */) {
|
|
1204
|
+
const full = __privateMethod(this, _FormulaParser_instances, tryParseFullRange_fn).call(this, token.value);
|
|
1205
|
+
if (full) return full;
|
|
1206
|
+
}
|
|
1184
1207
|
return new NamedRangeNode(token.value);
|
|
1208
|
+
}
|
|
1185
1209
|
case 24 /* SpillReference */:
|
|
1186
1210
|
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1187
1211
|
return new SpillReferenceNode(token.value);
|
|
@@ -1199,6 +1223,28 @@ parsePrimary_fn = function() {
|
|
|
1199
1223
|
);
|
|
1200
1224
|
}
|
|
1201
1225
|
};
|
|
1226
|
+
/**
|
|
1227
|
+
* Completes a whole-column or whole-row reference whose first half has already
|
|
1228
|
+
* been consumed and whose ':' is the current token. Returns null — consuming
|
|
1229
|
+
* nothing — when the two halves are not a matching pair of columns or rows, so
|
|
1230
|
+
* the caller can fall back to its normal interpretation.
|
|
1231
|
+
*
|
|
1232
|
+
* Both halves must be the same kind: `A:A` and `1:5` are ranges, `A:1` is not
|
|
1233
|
+
* a reference at all in Excel and must stay an error rather than becoming a
|
|
1234
|
+
* silently different region.
|
|
1235
|
+
*/
|
|
1236
|
+
tryParseFullRange_fn = function(startText, sheetName) {
|
|
1237
|
+
const endToken = __privateGet(this, _tokens)[__privateGet(this, _pos2) + 1];
|
|
1238
|
+
if (!endToken) return null;
|
|
1239
|
+
const start = classifyBound(startText);
|
|
1240
|
+
const end = classifyBound(endToken.value);
|
|
1241
|
+
if (!start || !end || start.kind !== end.kind) return null;
|
|
1242
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1243
|
+
__privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1244
|
+
const lo = Math.min(start.index, end.index);
|
|
1245
|
+
const hi = Math.max(start.index, end.index);
|
|
1246
|
+
return start.kind === "col" ? new FullRangeReferenceNode(lo, hi, null, null, sheetName) : new FullRangeReferenceNode(null, null, lo, hi, sheetName);
|
|
1247
|
+
};
|
|
1202
1248
|
parseFunction_fn = function() {
|
|
1203
1249
|
const nameToken = __privateMethod(this, _FormulaParser_instances, advance_fn).call(this);
|
|
1204
1250
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 6 /* LeftParen */);
|
|
@@ -1225,6 +1271,18 @@ parseArgList_fn = function() {
|
|
|
1225
1271
|
__privateMethod(this, _FormulaParser_instances, expect_fn).call(this, 7 /* RightParen */);
|
|
1226
1272
|
return args;
|
|
1227
1273
|
};
|
|
1274
|
+
function classifyBound(text) {
|
|
1275
|
+
const bare = text.replace(/\$/g, "");
|
|
1276
|
+
if (/^\d+$/.test(bare)) {
|
|
1277
|
+
const row2 = parseInt(bare, 10);
|
|
1278
|
+
return row2 >= 1 && row2 <= EXCEL_MAX_ROWS ? { kind: "row", index: row2 } : null;
|
|
1279
|
+
}
|
|
1280
|
+
if (/^[A-Za-z]{1,3}$/.test(bare)) {
|
|
1281
|
+
const col = columnNumber(bare.toUpperCase());
|
|
1282
|
+
return col >= 1 && col <= EXCEL_MAX_COLUMNS ? { kind: "col", index: col } : null;
|
|
1283
|
+
}
|
|
1284
|
+
return null;
|
|
1285
|
+
}
|
|
1228
1286
|
function comparisonOp(type) {
|
|
1229
1287
|
switch (type) {
|
|
1230
1288
|
case 16 /* Equal */:
|
|
@@ -1244,6 +1302,137 @@ function comparisonOp(type) {
|
|
|
1244
1302
|
}
|
|
1245
1303
|
}
|
|
1246
1304
|
|
|
1305
|
+
// src/criteria-index.ts
|
|
1306
|
+
var EPSILON = 1e-10;
|
|
1307
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1308
|
+
function build(arr) {
|
|
1309
|
+
const byText = /* @__PURE__ */ new Map();
|
|
1310
|
+
const byNumber = /* @__PURE__ */ new Map();
|
|
1311
|
+
const classes = /* @__PURE__ */ new Map();
|
|
1312
|
+
const addToClass = (kind, v, i2) => {
|
|
1313
|
+
const group = classes.get(kind);
|
|
1314
|
+
if (group) group.positions.push(i2);
|
|
1315
|
+
else classes.set(kind, { positions: [i2], representative: v });
|
|
1316
|
+
};
|
|
1317
|
+
let i = 0;
|
|
1318
|
+
for (const v of arr.values()) {
|
|
1319
|
+
const textKey = v.asText().toUpperCase();
|
|
1320
|
+
const atText = byText.get(textKey);
|
|
1321
|
+
if (atText) atText.push(i);
|
|
1322
|
+
else byText.set(textKey, [i]);
|
|
1323
|
+
if (v.isNumber) {
|
|
1324
|
+
const n = v.numberValue;
|
|
1325
|
+
const atNum = byNumber.get(n);
|
|
1326
|
+
if (atNum) atNum.push(i);
|
|
1327
|
+
else byNumber.set(n, [i]);
|
|
1328
|
+
} else {
|
|
1329
|
+
addToClass(v.kind, v, i);
|
|
1330
|
+
}
|
|
1331
|
+
i++;
|
|
1332
|
+
}
|
|
1333
|
+
const sortedNumbers = Float64Array.from(byNumber.keys()).sort();
|
|
1334
|
+
const cumulative = new Int32Array(sortedNumbers.length + 1);
|
|
1335
|
+
for (let k = 0; k < sortedNumbers.length; k++) {
|
|
1336
|
+
cumulative[k + 1] = cumulative[k] + byNumber.get(sortedNumbers[k]).length;
|
|
1337
|
+
}
|
|
1338
|
+
return { byText, byNumber, sortedNumbers, cumulative, classes: [...classes.values()] };
|
|
1339
|
+
}
|
|
1340
|
+
function indexOf(arr) {
|
|
1341
|
+
let idx = indexes.get(arr);
|
|
1342
|
+
if (!idx) {
|
|
1343
|
+
idx = build(arr);
|
|
1344
|
+
indexes.set(arr, idx);
|
|
1345
|
+
}
|
|
1346
|
+
return idx;
|
|
1347
|
+
}
|
|
1348
|
+
function lowerBound(sorted, target) {
|
|
1349
|
+
let lo = 0, hi = sorted.length;
|
|
1350
|
+
while (lo < hi) {
|
|
1351
|
+
const mid2 = lo + hi >> 1;
|
|
1352
|
+
if (sorted[mid2] < target) lo = mid2 + 1;
|
|
1353
|
+
else hi = mid2;
|
|
1354
|
+
}
|
|
1355
|
+
return lo;
|
|
1356
|
+
}
|
|
1357
|
+
function upperBound(sorted, target) {
|
|
1358
|
+
let lo = 0, hi = sorted.length;
|
|
1359
|
+
while (lo < hi) {
|
|
1360
|
+
const mid2 = lo + hi >> 1;
|
|
1361
|
+
if (sorted[mid2] <= target) lo = mid2 + 1;
|
|
1362
|
+
else hi = mid2;
|
|
1363
|
+
}
|
|
1364
|
+
return lo;
|
|
1365
|
+
}
|
|
1366
|
+
function matchingPositions(criteriaRange, criteria, minSize) {
|
|
1367
|
+
if (!criteriaRange.isArray) return null;
|
|
1368
|
+
const arr = criteriaRange.arrayVal;
|
|
1369
|
+
if (arr.length < minSize) return null;
|
|
1370
|
+
if (criteria.kind === "equalsText") {
|
|
1371
|
+
return indexOf(arr).byText.get(criteria.text) ?? EMPTY;
|
|
1372
|
+
}
|
|
1373
|
+
if (criteria.kind === "equalsNumber") {
|
|
1374
|
+
const target = criteria.number;
|
|
1375
|
+
const idx = indexOf(arr);
|
|
1376
|
+
const exact2 = idx.byNumber.get(target);
|
|
1377
|
+
let near;
|
|
1378
|
+
const { sortedNumbers } = idx;
|
|
1379
|
+
for (let k = lowerBound(sortedNumbers, target - EPSILON); k < sortedNumbers.length; k++) {
|
|
1380
|
+
const value2 = sortedNumbers[k];
|
|
1381
|
+
if (value2 > target + EPSILON) break;
|
|
1382
|
+
if (value2 === target) continue;
|
|
1383
|
+
if (!criteria.test(FormulaValue.number(value2))) continue;
|
|
1384
|
+
(near ?? (near = [])).push(...idx.byNumber.get(value2));
|
|
1385
|
+
}
|
|
1386
|
+
const blanks = criteria.test(FormulaValue.blank) ? blankPositions(idx) : void 0;
|
|
1387
|
+
if (!near && !blanks?.length) return exact2 ?? EMPTY;
|
|
1388
|
+
const merged = [...exact2 ?? EMPTY, ...near ?? EMPTY, ...blanks ?? EMPTY];
|
|
1389
|
+
merged.sort((a, b) => a - b);
|
|
1390
|
+
return merged;
|
|
1391
|
+
}
|
|
1392
|
+
if (criteria.kind === "compareNumber") {
|
|
1393
|
+
return comparePositions(indexOf(arr), criteria, arr.length);
|
|
1394
|
+
}
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
function comparePositions(idx, criteria, length) {
|
|
1398
|
+
const target = criteria.number;
|
|
1399
|
+
const { sortedNumbers } = idx;
|
|
1400
|
+
let from = 0;
|
|
1401
|
+
let to = sortedNumbers.length;
|
|
1402
|
+
switch (criteria.op) {
|
|
1403
|
+
case ">":
|
|
1404
|
+
from = upperBound(sortedNumbers, target);
|
|
1405
|
+
break;
|
|
1406
|
+
case ">=":
|
|
1407
|
+
from = lowerBound(sortedNumbers, target);
|
|
1408
|
+
break;
|
|
1409
|
+
case "<":
|
|
1410
|
+
to = lowerBound(sortedNumbers, target);
|
|
1411
|
+
break;
|
|
1412
|
+
default:
|
|
1413
|
+
to = upperBound(sortedNumbers, target);
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
const budget = length >> 1;
|
|
1417
|
+
const matchedClasses = idx.classes.filter((g) => criteria.test(g.representative));
|
|
1418
|
+
let total = idx.cumulative[to] - idx.cumulative[from];
|
|
1419
|
+
for (const group of matchedClasses) total += group.positions.length;
|
|
1420
|
+
if (total > budget) return null;
|
|
1421
|
+
const positions = [];
|
|
1422
|
+
for (let k = from; k < to; k++) {
|
|
1423
|
+
for (const i of idx.byNumber.get(sortedNumbers[k])) positions.push(i);
|
|
1424
|
+
}
|
|
1425
|
+
for (const group of matchedClasses) {
|
|
1426
|
+
for (const i of group.positions) positions.push(i);
|
|
1427
|
+
}
|
|
1428
|
+
positions.sort((a, b) => a - b);
|
|
1429
|
+
return positions;
|
|
1430
|
+
}
|
|
1431
|
+
function blankPositions(idx) {
|
|
1432
|
+
return idx.classes.find((g) => g.representative.isBlank)?.positions;
|
|
1433
|
+
}
|
|
1434
|
+
var EMPTY = [];
|
|
1435
|
+
|
|
1247
1436
|
// src/functions/math-functions.ts
|
|
1248
1437
|
function registerMath(r) {
|
|
1249
1438
|
r.register("SUM", sum, 1);
|
|
@@ -1305,34 +1494,72 @@ function valueAt(v, i) {
|
|
|
1305
1494
|
function lengthOf(v) {
|
|
1306
1495
|
return v.isArray ? v.arrayVal.length : 1;
|
|
1307
1496
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1497
|
+
var INDEX_MIN_RANGE = 32;
|
|
1498
|
+
function resolveIfsSelection(a, c, startIdx, len2) {
|
|
1499
|
+
const ranges = [];
|
|
1500
|
+
const criteria = [];
|
|
1501
|
+
let seed;
|
|
1502
|
+
let seedPair = -1;
|
|
1310
1503
|
for (let p = startIdx; p + 1 < a.length; p += 2) {
|
|
1311
1504
|
const cr = a[p].evaluate(c);
|
|
1312
1505
|
if (cr.isError) return { ok: false, error: cr };
|
|
1313
1506
|
const crit = a[p + 1].evaluate(c);
|
|
1314
1507
|
if (crit.isError) return { ok: false, error: crit };
|
|
1315
|
-
const
|
|
1508
|
+
const compiled = FormulaHelper.compileCriteria(crit.asText());
|
|
1509
|
+
ranges.push(cr);
|
|
1510
|
+
criteria.push(compiled);
|
|
1511
|
+
if (seed === void 0) {
|
|
1512
|
+
const positions = matchingPositions(cr, compiled, INDEX_MIN_RANGE);
|
|
1513
|
+
if (positions) {
|
|
1514
|
+
seed = positions;
|
|
1515
|
+
seedPair = ranges.length - 1;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (seed !== void 0) {
|
|
1520
|
+
let candidates = [];
|
|
1521
|
+
for (const i of seed) if (i < len2) candidates.push(i);
|
|
1522
|
+
for (let k = 0; k < ranges.length && candidates.length > 0; k++) {
|
|
1523
|
+
if (k === seedPair) continue;
|
|
1524
|
+
const range = ranges[k];
|
|
1525
|
+
const test = criteria[k].test;
|
|
1526
|
+
const next = [];
|
|
1527
|
+
for (const i of candidates) if (test(valueAt(range, i))) next.push(i);
|
|
1528
|
+
candidates = next;
|
|
1529
|
+
}
|
|
1530
|
+
return { ok: true, positions: candidates };
|
|
1531
|
+
}
|
|
1532
|
+
const mask = new Uint8Array(len2).fill(1);
|
|
1533
|
+
for (let k = 0; k < ranges.length; k++) {
|
|
1534
|
+
const range = ranges[k];
|
|
1535
|
+
const test = criteria[k].test;
|
|
1316
1536
|
for (let i = 0; i < len2; i++) {
|
|
1317
1537
|
if (!mask[i]) continue;
|
|
1318
|
-
if (!
|
|
1538
|
+
if (!test(valueAt(range, i))) mask[i] = 0;
|
|
1319
1539
|
}
|
|
1320
1540
|
}
|
|
1321
1541
|
return { ok: true, mask };
|
|
1322
1542
|
}
|
|
1543
|
+
function forEachSelected(sel, len2, visit) {
|
|
1544
|
+
if ("positions" in sel) {
|
|
1545
|
+
for (const i of sel.positions) visit(i);
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
const { mask } = sel;
|
|
1549
|
+
for (let i = 0; i < len2; i++) if (mask[i]) visit(i);
|
|
1550
|
+
}
|
|
1323
1551
|
function sumIfs(a, c) {
|
|
1324
1552
|
if (a.length < 3 || a.length % 2 === 0) return FormulaValue.errorValue;
|
|
1325
1553
|
const sumRange = a[0].evaluate(c);
|
|
1326
1554
|
if (sumRange.isError) return sumRange;
|
|
1327
1555
|
const len2 = lengthOf(sumRange);
|
|
1328
|
-
const
|
|
1329
|
-
if (!
|
|
1556
|
+
const sel = resolveIfsSelection(a, c, 1, len2);
|
|
1557
|
+
if (!sel.ok) return sel.error;
|
|
1330
1558
|
let total = 0;
|
|
1331
|
-
|
|
1332
|
-
if (!m.mask[i]) continue;
|
|
1559
|
+
forEachSelected(sel, len2, (i) => {
|
|
1333
1560
|
const r = valueAt(sumRange, i).tryAsDouble();
|
|
1334
1561
|
if (r.ok) total += r.value;
|
|
1335
|
-
}
|
|
1562
|
+
});
|
|
1336
1563
|
return FormulaValue.number(total);
|
|
1337
1564
|
}
|
|
1338
1565
|
function countIfs(a, c) {
|
|
@@ -1340,10 +1567,12 @@ function countIfs(a, c) {
|
|
|
1340
1567
|
const first = a[0].evaluate(c);
|
|
1341
1568
|
if (first.isError) return first;
|
|
1342
1569
|
const len2 = lengthOf(first);
|
|
1343
|
-
const
|
|
1344
|
-
if (!
|
|
1570
|
+
const sel = resolveIfsSelection(a, c, 0, len2);
|
|
1571
|
+
if (!sel.ok) return sel.error;
|
|
1345
1572
|
let n = 0;
|
|
1346
|
-
|
|
1573
|
+
forEachSelected(sel, len2, () => {
|
|
1574
|
+
n++;
|
|
1575
|
+
});
|
|
1347
1576
|
return FormulaValue.number(n);
|
|
1348
1577
|
}
|
|
1349
1578
|
function averageIfs(a, c) {
|
|
@@ -1351,17 +1580,16 @@ function averageIfs(a, c) {
|
|
|
1351
1580
|
const avgRange = a[0].evaluate(c);
|
|
1352
1581
|
if (avgRange.isError) return avgRange;
|
|
1353
1582
|
const len2 = lengthOf(avgRange);
|
|
1354
|
-
const
|
|
1355
|
-
if (!
|
|
1583
|
+
const sel = resolveIfsSelection(a, c, 1, len2);
|
|
1584
|
+
if (!sel.ok) return sel.error;
|
|
1356
1585
|
let total = 0, cnt = 0;
|
|
1357
|
-
|
|
1358
|
-
if (!m.mask[i]) continue;
|
|
1586
|
+
forEachSelected(sel, len2, (i) => {
|
|
1359
1587
|
const r = valueAt(avgRange, i).tryAsDouble();
|
|
1360
1588
|
if (r.ok) {
|
|
1361
1589
|
total += r.value;
|
|
1362
1590
|
cnt++;
|
|
1363
1591
|
}
|
|
1364
|
-
}
|
|
1592
|
+
});
|
|
1365
1593
|
return cnt === 0 ? FormulaValue.errorDiv0 : FormulaValue.number(total / cnt);
|
|
1366
1594
|
}
|
|
1367
1595
|
function sumSq(a, c) {
|
|
@@ -4185,39 +4413,43 @@ var FormulaHelper;
|
|
|
4185
4413
|
return result;
|
|
4186
4414
|
}
|
|
4187
4415
|
FormulaHelper2.flattenArgs = flattenArgs;
|
|
4188
|
-
function
|
|
4189
|
-
|
|
4190
|
-
const opMatch = /^(>=|<=|<>|>|<|=)(.*)$/.exec(
|
|
4416
|
+
function compileCriteria(criteria) {
|
|
4417
|
+
const trimmed = criteria.trim();
|
|
4418
|
+
const opMatch = /^(>=|<=|<>|>|<|=)(.*)$/.exec(trimmed);
|
|
4191
4419
|
if (opMatch) {
|
|
4192
4420
|
const op = opMatch[1];
|
|
4193
4421
|
const rhs = opMatch[2];
|
|
4194
4422
|
const rhsVal = parseFloat(rhs);
|
|
4195
4423
|
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));
|
|
4424
|
+
const rhsValue = rhsNum !== null ? FormulaValue.number(rhsNum) : FormulaValue.text(rhs);
|
|
4425
|
+
if (op === "=") {
|
|
4426
|
+
return rhsNum !== null ? numberEquality(rhsNum) : textEquality(rhs);
|
|
4210
4427
|
}
|
|
4428
|
+
if (op === "<>") {
|
|
4429
|
+
return { kind: "other", test: (v) => !FormulaValue.areEqual(v, rhsValue) };
|
|
4430
|
+
}
|
|
4431
|
+
const wanted = op === ">" ? (c) => c > 0 : op === ">=" ? (c) => c >= 0 : op === "<" ? (c) => c < 0 : (c) => c <= 0;
|
|
4432
|
+
const test = (v) => wanted(FormulaValue.compare(v, rhsValue));
|
|
4433
|
+
return rhsNum !== null ? { kind: "compareNumber", number: rhsNum, op, test } : { kind: "other", test };
|
|
4211
4434
|
}
|
|
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));
|
|
4435
|
+
if (trimmed.includes("*") || trimmed.includes("?")) {
|
|
4436
|
+
const pattern = wildcardToRegex(trimmed);
|
|
4437
|
+
return { kind: "other", test: (v) => pattern.test(v.asText()) };
|
|
4219
4438
|
}
|
|
4220
|
-
|
|
4439
|
+
const num5 = parseFloat(trimmed);
|
|
4440
|
+
return isNaN(num5) ? textEquality(trimmed) : numberEquality(num5);
|
|
4441
|
+
}
|
|
4442
|
+
FormulaHelper2.compileCriteria = compileCriteria;
|
|
4443
|
+
function numberEquality(n) {
|
|
4444
|
+
const target = FormulaValue.number(n);
|
|
4445
|
+
return { kind: "equalsNumber", number: n, test: (v) => FormulaValue.areEqual(v, target) };
|
|
4446
|
+
}
|
|
4447
|
+
function textEquality(s) {
|
|
4448
|
+
const upper2 = s.toUpperCase();
|
|
4449
|
+
return { kind: "equalsText", text: upper2, test: (v) => v.asText().toUpperCase() === upper2 };
|
|
4450
|
+
}
|
|
4451
|
+
function matchesCriteria(value2, criteria) {
|
|
4452
|
+
return compileCriteria(criteria).test(value2);
|
|
4221
4453
|
}
|
|
4222
4454
|
FormulaHelper2.matchesCriteria = matchesCriteria;
|
|
4223
4455
|
function wildcardToRegex(pattern) {
|
|
@@ -4227,7 +4459,7 @@ var FormulaHelper;
|
|
|
4227
4459
|
})(FormulaHelper || (FormulaHelper = {}));
|
|
4228
4460
|
|
|
4229
4461
|
// src/formula-context.ts
|
|
4230
|
-
var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn;
|
|
4462
|
+
var _sheet, _functions2, _evaluating, _scopes, _FormulaContext_instances, lookupScope_fn, forSheet_fn;
|
|
4231
4463
|
var _FormulaContext = class _FormulaContext {
|
|
4232
4464
|
constructor(sheet, formulaRow = 0, formulaCol = 0, evaluating, registry) {
|
|
4233
4465
|
__privateAdd(this, _FormulaContext_instances);
|
|
@@ -4272,24 +4504,70 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4272
4504
|
getRangeValues(startRef, endRef) {
|
|
4273
4505
|
const s = parseCellRef(startRef);
|
|
4274
4506
|
const e = parseCellRef(endRef);
|
|
4275
|
-
|
|
4276
|
-
|
|
4507
|
+
return this.getRangeValuesByIndex(s.row, s.column, e.row, e.column);
|
|
4508
|
+
}
|
|
4509
|
+
/**
|
|
4510
|
+
* Resolves a rectangle already known in coordinates. Split out from
|
|
4511
|
+
* {@link getRangeValues} because whole-column references and the range memo
|
|
4512
|
+
* both work in numbers, and formatting them back into `A1` strings only to
|
|
4513
|
+
* re-parse them is measurable at range scale.
|
|
4514
|
+
*/
|
|
4515
|
+
getRangeValuesByIndex(startRow, startCol, endRow, endCol) {
|
|
4516
|
+
const rows2 = endRow - startRow + 1;
|
|
4517
|
+
const cols = endCol - startCol + 1;
|
|
4518
|
+
const cache = rows2 * cols > 1 ? this.rangeCache : void 0;
|
|
4519
|
+
const sheetKey = cache ? __privateGet(this, _sheet).name.toUpperCase() : "";
|
|
4520
|
+
if (cache) {
|
|
4521
|
+
const hit = cache.get(sheetKey, startRow, startCol, endRow, endCol);
|
|
4522
|
+
if (hit !== void 0) return hit;
|
|
4523
|
+
}
|
|
4277
4524
|
const arr = new ArrayValue(rows2, cols);
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4525
|
+
const at = __privateGet(this, _sheet).getCellValueAt;
|
|
4526
|
+
if (at) {
|
|
4527
|
+
for (let r = 0; r < rows2; r++) {
|
|
4528
|
+
for (let c = 0; c < cols; c++) {
|
|
4529
|
+
arr.set(r, c, FormulaValue.fromObjectSync(at.call(__privateGet(this, _sheet), startRow + r, startCol + c)));
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
} else {
|
|
4533
|
+
for (let r = 0; r < rows2; r++) {
|
|
4534
|
+
for (let c = 0; c < cols; c++) {
|
|
4535
|
+
const ref = cellRefFromRowCol(startRow + r, startCol + c);
|
|
4536
|
+
arr.set(r, c, this.getCellValue(ref));
|
|
4537
|
+
}
|
|
4282
4538
|
}
|
|
4283
4539
|
}
|
|
4284
|
-
|
|
4540
|
+
const value2 = FormulaValue.array(arr);
|
|
4541
|
+
if (cache && rows2 > 0 && cols > 0) {
|
|
4542
|
+
cache.set(sheetKey, startRow, startCol, endRow, endCol, value2, rows2 * cols);
|
|
4543
|
+
}
|
|
4544
|
+
return value2;
|
|
4545
|
+
}
|
|
4546
|
+
/**
|
|
4547
|
+
* Resolves a whole-column (`A:A`) or whole-row (`1:1`) reference, bounded by
|
|
4548
|
+
* the sheet's used range the way Excel does. Without a bound this would
|
|
4549
|
+
* materialise a million-cell array for a sheet with twelve rows in it.
|
|
4550
|
+
*
|
|
4551
|
+
* A sheet that cannot report its extent yields `#REF!` rather than a guess:
|
|
4552
|
+
* silently reading nothing would make SUM(A:A) return 0 on a full column.
|
|
4553
|
+
*/
|
|
4554
|
+
getFullRangeValues(startCol, endCol, startRow, endRow) {
|
|
4555
|
+
const bounds = __privateGet(this, _sheet).usedBounds;
|
|
4556
|
+
if (!bounds) return FormulaValue.errorRef;
|
|
4557
|
+
if (bounds.rowCount === 0 || bounds.columnCount === 0) return FormulaValue.blank;
|
|
4558
|
+
const r0 = startRow ?? 1;
|
|
4559
|
+
const r1 = endRow ?? bounds.rowCount;
|
|
4560
|
+
const c0 = startCol ?? 1;
|
|
4561
|
+
const c1 = endCol ?? bounds.columnCount;
|
|
4562
|
+
const lastRow = Math.min(r1, bounds.rowCount);
|
|
4563
|
+
const lastCol = Math.min(c1, bounds.columnCount);
|
|
4564
|
+
if (lastRow < r0 || lastCol < c0) return FormulaValue.blank;
|
|
4565
|
+
return this.getRangeValuesByIndex(r0, c0, lastRow, lastCol);
|
|
4285
4566
|
}
|
|
4286
4567
|
getSheetCellValue(sheetName, cellReference) {
|
|
4287
4568
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4288
4569
|
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);
|
|
4570
|
+
return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getCellValue(cellReference);
|
|
4293
4571
|
} catch {
|
|
4294
4572
|
return FormulaValue.errorRef;
|
|
4295
4573
|
}
|
|
@@ -4297,10 +4575,16 @@ var _FormulaContext = class _FormulaContext {
|
|
|
4297
4575
|
getSheetRangeValues(sheetName, startRef, endRef) {
|
|
4298
4576
|
if (!this.workbook) return FormulaValue.errorRef;
|
|
4299
4577
|
try {
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4578
|
+
return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getRangeValues(startRef, endRef);
|
|
4579
|
+
} catch {
|
|
4580
|
+
return FormulaValue.errorRef;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
/** Whole-column/whole-row reference qualified by a sheet name. */
|
|
4584
|
+
getSheetFullRangeValues(sheetName, startCol, endCol, startRow, endRow) {
|
|
4585
|
+
if (!this.workbook) return FormulaValue.errorRef;
|
|
4586
|
+
try {
|
|
4587
|
+
return __privateMethod(this, _FormulaContext_instances, forSheet_fn).call(this, sheetName).getFullRangeValues(startCol, endCol, startRow, endRow);
|
|
4304
4588
|
} catch {
|
|
4305
4589
|
return FormulaValue.errorRef;
|
|
4306
4590
|
}
|
|
@@ -4355,6 +4639,21 @@ lookupScope_fn = function(name) {
|
|
|
4355
4639
|
}
|
|
4356
4640
|
return void 0;
|
|
4357
4641
|
};
|
|
4642
|
+
/**
|
|
4643
|
+
* A sibling context bound to another sheet, sharing this one's circular-
|
|
4644
|
+
* reference guard and range memo. These contexts only read values — they
|
|
4645
|
+
* never evaluate an AST — so the name resolver and recursion budget are
|
|
4646
|
+
* deliberately not carried over; passing them would suggest a scoping
|
|
4647
|
+
* relationship that does not exist. The memo, on the other hand, must be
|
|
4648
|
+
* carried: cross-sheet ranges are the most expensive reads in a workbook.
|
|
4649
|
+
*/
|
|
4650
|
+
forSheet_fn = function(sheetName) {
|
|
4651
|
+
const sheet = this.workbook.getWorksheet(sheetName);
|
|
4652
|
+
const ctx = new _FormulaContext(sheet, this.formulaRow, this.formulaCol, __privateGet(this, _evaluating), __privateGet(this, _functions2));
|
|
4653
|
+
ctx.workbook = this.workbook;
|
|
4654
|
+
ctx.rangeCache = this.rangeCache;
|
|
4655
|
+
return ctx;
|
|
4656
|
+
};
|
|
4358
4657
|
var FormulaContext = _FormulaContext;
|
|
4359
4658
|
|
|
4360
4659
|
// src/lru-cache.ts
|
|
@@ -4474,6 +4773,7 @@ function buildContext(sheet, opts) {
|
|
|
4474
4773
|
evaluating
|
|
4475
4774
|
);
|
|
4476
4775
|
ctx.recursionGuard = opts.recursionGuard ?? { depth: 0, max: DEFAULT_LAMBDA_DEPTH };
|
|
4776
|
+
ctx.rangeCache = opts.rangeCache;
|
|
4477
4777
|
const wb = opts.workbook;
|
|
4478
4778
|
if (wb) {
|
|
4479
4779
|
ctx.workbook = wb;
|
|
@@ -4525,7 +4825,8 @@ function evaluateAst(ast, sheet, options) {
|
|
|
4525
4825
|
const ctx = buildContext(sheet, {
|
|
4526
4826
|
workbook: options?.workbook,
|
|
4527
4827
|
formulaRow: options?.formulaRow,
|
|
4528
|
-
formulaCol: options?.formulaCol
|
|
4828
|
+
formulaCol: options?.formulaCol,
|
|
4829
|
+
rangeCache: options?.rangeCache
|
|
4529
4830
|
});
|
|
4530
4831
|
return ast.evaluate(ctx);
|
|
4531
4832
|
} catch {
|
|
@@ -4546,6 +4847,149 @@ function getAstCacheStats() {
|
|
|
4546
4847
|
function setAstCacheCapacity(capacity) {
|
|
4547
4848
|
astCache.resize(capacity);
|
|
4548
4849
|
}
|
|
4850
|
+
|
|
4851
|
+
// src/range-cache.ts
|
|
4852
|
+
var TILE_ROWS = 64;
|
|
4853
|
+
var TILE_COLS = 64;
|
|
4854
|
+
var MAX_TILES_PER_RANGE = 32;
|
|
4855
|
+
var DEFAULT_MAX_CELLS = 2e6;
|
|
4856
|
+
var _entries, _tiles, _wide, _placements, _sizes, _cells, _maxCells, _RangeCache_static, contains_fn, _RangeCache_instances, drop_fn, fileIn_fn;
|
|
4857
|
+
var _RangeCache = class _RangeCache {
|
|
4858
|
+
constructor(maxCells = DEFAULT_MAX_CELLS) {
|
|
4859
|
+
__privateAdd(this, _RangeCache_instances);
|
|
4860
|
+
/** `SHEET r0 c0 r1 c1` → the memoised array value. Insertion-ordered (FIFO eviction). */
|
|
4861
|
+
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
4862
|
+
/** `sheet tr tc` → entry keys whose rectangle overlaps that tile. */
|
|
4863
|
+
__privateAdd(this, _tiles, /* @__PURE__ */ new Map());
|
|
4864
|
+
/** sheet → entry keys whose rectangle is too broad to tile. */
|
|
4865
|
+
__privateAdd(this, _wide, /* @__PURE__ */ new Map());
|
|
4866
|
+
/** entry key → buckets it was filed under, for cheap removal. */
|
|
4867
|
+
__privateAdd(this, _placements, /* @__PURE__ */ new Map());
|
|
4868
|
+
/** entry key → its area, so eviction can give the budget back. */
|
|
4869
|
+
__privateAdd(this, _sizes, /* @__PURE__ */ new Map());
|
|
4870
|
+
/** Number of cells across all cached rectangles, kept under {@link #maxCells}. */
|
|
4871
|
+
__privateAdd(this, _cells, 0);
|
|
4872
|
+
__privateAdd(this, _maxCells);
|
|
4873
|
+
this.hits = 0;
|
|
4874
|
+
this.misses = 0;
|
|
4875
|
+
__privateSet(this, _maxCells, maxCells);
|
|
4876
|
+
}
|
|
4877
|
+
get size() {
|
|
4878
|
+
return __privateGet(this, _entries).size;
|
|
4879
|
+
}
|
|
4880
|
+
get cachedCells() {
|
|
4881
|
+
return __privateGet(this, _cells);
|
|
4882
|
+
}
|
|
4883
|
+
static key(sheet, r0, c0, r1, c1) {
|
|
4884
|
+
return `${sheet} ${r0} ${c0} ${r1} ${c1}`;
|
|
4885
|
+
}
|
|
4886
|
+
get(sheet, r0, c0, r1, c1) {
|
|
4887
|
+
const hit = __privateGet(this, _entries).get(_RangeCache.key(sheet, r0, c0, r1, c1));
|
|
4888
|
+
if (hit === void 0) this.misses++;
|
|
4889
|
+
else this.hits++;
|
|
4890
|
+
return hit;
|
|
4891
|
+
}
|
|
4892
|
+
/** Memoises a rectangle's value. `cells` is its area, for the budget. */
|
|
4893
|
+
set(sheet, r0, c0, r1, c1, value2, cells) {
|
|
4894
|
+
if (cells <= 0 || cells > __privateGet(this, _maxCells)) return;
|
|
4895
|
+
const key = _RangeCache.key(sheet, r0, c0, r1, c1);
|
|
4896
|
+
if (__privateGet(this, _entries).has(key)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
|
|
4897
|
+
__privateGet(this, _entries).set(key, value2);
|
|
4898
|
+
__privateSet(this, _cells, __privateGet(this, _cells) + cells);
|
|
4899
|
+
const buckets = [];
|
|
4900
|
+
const tileRow0 = r0 / TILE_ROWS | 0, tileRow1 = r1 / TILE_ROWS | 0;
|
|
4901
|
+
const tileCol0 = c0 / TILE_COLS | 0, tileCol1 = c1 / TILE_COLS | 0;
|
|
4902
|
+
const tileCount = (tileRow1 - tileRow0 + 1) * (tileCol1 - tileCol0 + 1);
|
|
4903
|
+
if (tileCount > MAX_TILES_PER_RANGE) {
|
|
4904
|
+
__privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _wide), sheet, key, buckets, `w ${sheet}`);
|
|
4905
|
+
} else {
|
|
4906
|
+
for (let tr = tileRow0; tr <= tileRow1; tr++) {
|
|
4907
|
+
for (let tc = tileCol0; tc <= tileCol1; tc++) {
|
|
4908
|
+
const bucket = `${sheet} ${tr} ${tc}`;
|
|
4909
|
+
__privateMethod(this, _RangeCache_instances, fileIn_fn).call(this, __privateGet(this, _tiles), bucket, key, buckets, `t ${bucket}`);
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
__privateGet(this, _placements).set(key, buckets);
|
|
4914
|
+
__privateGet(this, _sizes).set(key, cells);
|
|
4915
|
+
if (__privateGet(this, _cells) > __privateGet(this, _maxCells)) {
|
|
4916
|
+
for (const oldest of __privateGet(this, _entries).keys()) {
|
|
4917
|
+
if (__privateGet(this, _cells) <= __privateGet(this, _maxCells)) break;
|
|
4918
|
+
if (oldest === key) continue;
|
|
4919
|
+
__privateMethod(this, _RangeCache_instances, drop_fn).call(this, oldest);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
/** Drops every cached rectangle containing the given cell. */
|
|
4924
|
+
invalidateCell(sheet, row2, col) {
|
|
4925
|
+
var _a, _b;
|
|
4926
|
+
const tile = __privateGet(this, _tiles).get(`${sheet} ${row2 / TILE_ROWS | 0} ${col / TILE_COLS | 0}`);
|
|
4927
|
+
if (tile) {
|
|
4928
|
+
for (const key of [...tile]) {
|
|
4929
|
+
if (__privateMethod(_a = _RangeCache, _RangeCache_static, contains_fn).call(_a, key, sheet, row2, col)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4932
|
+
const wide = __privateGet(this, _wide).get(sheet);
|
|
4933
|
+
if (wide) {
|
|
4934
|
+
for (const key of [...wide]) {
|
|
4935
|
+
if (__privateMethod(_b = _RangeCache, _RangeCache_static, contains_fn).call(_b, key, sheet, row2, col)) __privateMethod(this, _RangeCache_instances, drop_fn).call(this, key);
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
clear() {
|
|
4940
|
+
__privateGet(this, _entries).clear();
|
|
4941
|
+
__privateGet(this, _tiles).clear();
|
|
4942
|
+
__privateGet(this, _wide).clear();
|
|
4943
|
+
__privateGet(this, _placements).clear();
|
|
4944
|
+
__privateGet(this, _sizes).clear();
|
|
4945
|
+
__privateSet(this, _cells, 0);
|
|
4946
|
+
}
|
|
4947
|
+
};
|
|
4948
|
+
_entries = new WeakMap();
|
|
4949
|
+
_tiles = new WeakMap();
|
|
4950
|
+
_wide = new WeakMap();
|
|
4951
|
+
_placements = new WeakMap();
|
|
4952
|
+
_sizes = new WeakMap();
|
|
4953
|
+
_cells = new WeakMap();
|
|
4954
|
+
_maxCells = new WeakMap();
|
|
4955
|
+
_RangeCache_static = new WeakSet();
|
|
4956
|
+
contains_fn = function(key, sheet, row2, col) {
|
|
4957
|
+
const parts = key.split(" ");
|
|
4958
|
+
const n = parts.length;
|
|
4959
|
+
const c1 = +parts[n - 1], r1 = +parts[n - 2], c0 = +parts[n - 3], r0 = +parts[n - 4];
|
|
4960
|
+
if (parts.slice(0, n - 4).join(" ") !== sheet) return false;
|
|
4961
|
+
return row2 >= r0 && row2 <= r1 && col >= c0 && col <= c1;
|
|
4962
|
+
};
|
|
4963
|
+
_RangeCache_instances = new WeakSet();
|
|
4964
|
+
drop_fn = function(key) {
|
|
4965
|
+
if (!__privateGet(this, _entries).delete(key)) return;
|
|
4966
|
+
__privateSet(this, _cells, __privateGet(this, _cells) - (__privateGet(this, _sizes).get(key) ?? 0));
|
|
4967
|
+
__privateGet(this, _sizes).delete(key);
|
|
4968
|
+
const buckets = __privateGet(this, _placements).get(key);
|
|
4969
|
+
if (buckets) {
|
|
4970
|
+
for (const bucket of buckets) {
|
|
4971
|
+
const store = bucket.charCodeAt(0) === 119 ? __privateGet(this, _wide) : __privateGet(this, _tiles);
|
|
4972
|
+
const bucketKey = bucket.slice(2);
|
|
4973
|
+
const set = store.get(bucketKey);
|
|
4974
|
+
if (!set) continue;
|
|
4975
|
+
set.delete(key);
|
|
4976
|
+
if (set.size === 0) store.delete(bucketKey);
|
|
4977
|
+
}
|
|
4978
|
+
__privateGet(this, _placements).delete(key);
|
|
4979
|
+
}
|
|
4980
|
+
};
|
|
4981
|
+
fileIn_fn = function(store, bucketKey, entryKey, buckets, bucketId) {
|
|
4982
|
+
let set = store.get(bucketKey);
|
|
4983
|
+
if (!set) {
|
|
4984
|
+
set = /* @__PURE__ */ new Set();
|
|
4985
|
+
store.set(bucketKey, set);
|
|
4986
|
+
}
|
|
4987
|
+
if (set.has(entryKey)) return;
|
|
4988
|
+
set.add(entryKey);
|
|
4989
|
+
buckets.push(bucketId);
|
|
4990
|
+
};
|
|
4991
|
+
__privateAdd(_RangeCache, _RangeCache_static);
|
|
4992
|
+
var RangeCache = _RangeCache;
|
|
4549
4993
|
function extractReferences(ast, formulaSheet, registry = FunctionRegistry.default, resolveName2) {
|
|
4550
4994
|
const refs = [];
|
|
4551
4995
|
let volatile = false;
|
|
@@ -4574,6 +5018,14 @@ function extractReferences(ast, formulaSheet, registry = FunctionRegistry.defaul
|
|
|
4574
5018
|
refs.push(cell(node.sheetName, node.cellReference));
|
|
4575
5019
|
} else if (node instanceof SheetRangeReferenceNode) {
|
|
4576
5020
|
refs.push(range(node.sheetName, node.startRef, node.endRef));
|
|
5021
|
+
} else if (node instanceof FullRangeReferenceNode) {
|
|
5022
|
+
refs.push({
|
|
5023
|
+
sheet: (node.sheetName ?? formulaSheet).toUpperCase(),
|
|
5024
|
+
startRow: node.startRow ?? 1,
|
|
5025
|
+
endRow: node.endRow ?? EXCEL_MAX_ROWS,
|
|
5026
|
+
startCol: node.startCol ?? 1,
|
|
5027
|
+
endCol: node.endCol ?? EXCEL_MAX_COLUMNS
|
|
5028
|
+
});
|
|
4577
5029
|
} else if (node instanceof NamedRangeNode) {
|
|
4578
5030
|
expandName(node.name);
|
|
4579
5031
|
} else if (node instanceof SpillReferenceNode) {
|
|
@@ -4610,19 +5062,30 @@ function refContains(ref, sheet, row2, col) {
|
|
|
4610
5062
|
}
|
|
4611
5063
|
|
|
4612
5064
|
// src/dependency-index.ts
|
|
4613
|
-
var
|
|
4614
|
-
var
|
|
5065
|
+
var TILE_ROWS2 = 64;
|
|
5066
|
+
var TILE_COLS2 = 64;
|
|
4615
5067
|
var MAX_TILES_PER_REF = 32;
|
|
4616
|
-
var
|
|
5068
|
+
var MAX_EXACT_SPAN = 16;
|
|
5069
|
+
var MAX_BUCKETS_PER_REF = 64;
|
|
5070
|
+
var tileOfRow = (row2) => row2 / TILE_ROWS2 | 0;
|
|
5071
|
+
var tileOfCol = (col) => col / TILE_COLS2 | 0;
|
|
5072
|
+
var _buckets, _placements2, _DependencyIndex_instances, visit_fn, file_fn;
|
|
4617
5073
|
var DependencyIndex = class {
|
|
4618
5074
|
constructor() {
|
|
4619
5075
|
__privateAdd(this, _DependencyIndex_instances);
|
|
4620
|
-
/**
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
5076
|
+
/**
|
|
5077
|
+
* All buckets in one map, keyed by a discriminating prefix:
|
|
5078
|
+
*
|
|
5079
|
+
* `t s tr tc` rectangle compact in both dimensions
|
|
5080
|
+
* `c s col tr` narrow columns, row tiles (the common case)
|
|
5081
|
+
* `C s col` narrow columns, every row (`A:A`)
|
|
5082
|
+
* `r s row tc` narrow rows, column tiles
|
|
5083
|
+
* `R s row` narrow rows, every column (`1:1`)
|
|
5084
|
+
* `w s` broad in both dimensions
|
|
5085
|
+
*/
|
|
5086
|
+
__privateAdd(this, _buckets, /* @__PURE__ */ new Map());
|
|
5087
|
+
/** formula key → the buckets it was filed under, for cheap removal. */
|
|
5088
|
+
__privateAdd(this, _placements2, /* @__PURE__ */ new Map());
|
|
4626
5089
|
}
|
|
4627
5090
|
/** Registers (or re-registers) a formula's precedent rectangles. */
|
|
4628
5091
|
add(formulaKey, refs) {
|
|
@@ -4630,78 +5093,117 @@ var DependencyIndex = class {
|
|
|
4630
5093
|
if (refs.length === 0) return;
|
|
4631
5094
|
const buckets = [];
|
|
4632
5095
|
for (const ref of refs) {
|
|
4633
|
-
const
|
|
4634
|
-
const
|
|
4635
|
-
const
|
|
4636
|
-
const
|
|
4637
|
-
const
|
|
4638
|
-
|
|
4639
|
-
|
|
5096
|
+
const rowSpan = ref.endRow - ref.startRow + 1;
|
|
5097
|
+
const colSpan = ref.endCol - ref.startCol + 1;
|
|
5098
|
+
const rowTile0 = tileOfRow(ref.startRow), rowTile1 = tileOfRow(ref.endRow);
|
|
5099
|
+
const colTile0 = tileOfCol(ref.startCol), colTile1 = tileOfCol(ref.endCol);
|
|
5100
|
+
const rowTiles = rowTile1 - rowTile0 + 1;
|
|
5101
|
+
const colTiles = colTile1 - colTile0 + 1;
|
|
5102
|
+
if (colSpan <= MAX_EXACT_SPAN && colSpan <= rowSpan) {
|
|
5103
|
+
if (colSpan * rowTiles <= MAX_BUCKETS_PER_REF && rowTiles <= MAX_TILES_PER_REF) {
|
|
5104
|
+
for (let col = ref.startCol; col <= ref.endCol; col++) {
|
|
5105
|
+
for (let tr = rowTile0; tr <= rowTile1; tr++) {
|
|
5106
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `c ${ref.sheet} ${col} ${tr}`, formulaKey, buckets);
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
} else {
|
|
5110
|
+
for (let col = ref.startCol; col <= ref.endCol; col++) {
|
|
5111
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `C ${ref.sheet} ${col}`, formulaKey, buckets);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
4640
5114
|
continue;
|
|
4641
5115
|
}
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
5116
|
+
if (rowSpan <= MAX_EXACT_SPAN) {
|
|
5117
|
+
if (rowSpan * colTiles <= MAX_BUCKETS_PER_REF && colTiles <= MAX_TILES_PER_REF) {
|
|
5118
|
+
for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
|
|
5119
|
+
for (let tc = colTile0; tc <= colTile1; tc++) {
|
|
5120
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `r ${ref.sheet} ${row2} ${tc}`, formulaKey, buckets);
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
} else {
|
|
5124
|
+
for (let row2 = ref.startRow; row2 <= ref.endRow; row2++) {
|
|
5125
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `R ${ref.sheet} ${row2}`, formulaKey, buckets);
|
|
5126
|
+
}
|
|
4646
5127
|
}
|
|
5128
|
+
continue;
|
|
4647
5129
|
}
|
|
5130
|
+
if (rowTiles * colTiles <= MAX_TILES_PER_REF) {
|
|
5131
|
+
for (let tr = rowTile0; tr <= rowTile1; tr++) {
|
|
5132
|
+
for (let tc = colTile0; tc <= colTile1; tc++) {
|
|
5133
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `t ${ref.sheet} ${tr} ${tc}`, formulaKey, buckets);
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
continue;
|
|
5137
|
+
}
|
|
5138
|
+
__privateMethod(this, _DependencyIndex_instances, file_fn).call(this, `w ${ref.sheet}`, formulaKey, buckets);
|
|
4648
5139
|
}
|
|
4649
|
-
__privateGet(this,
|
|
5140
|
+
__privateGet(this, _placements2).set(formulaKey, buckets);
|
|
4650
5141
|
}
|
|
4651
5142
|
/** Forgets a formula. */
|
|
4652
5143
|
remove(formulaKey) {
|
|
4653
|
-
const buckets = __privateGet(this,
|
|
5144
|
+
const buckets = __privateGet(this, _placements2).get(formulaKey);
|
|
4654
5145
|
if (!buckets) return;
|
|
4655
5146
|
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);
|
|
5147
|
+
const set = __privateGet(this, _buckets).get(bucket);
|
|
4660
5148
|
if (!set) continue;
|
|
4661
5149
|
set.delete(formulaKey);
|
|
4662
|
-
if (set.size === 0)
|
|
5150
|
+
if (set.size === 0) __privateGet(this, _buckets).delete(bucket);
|
|
4663
5151
|
}
|
|
4664
|
-
__privateGet(this,
|
|
5152
|
+
__privateGet(this, _placements2).delete(formulaKey);
|
|
4665
5153
|
}
|
|
4666
5154
|
/** Drops every registration. */
|
|
4667
5155
|
clear() {
|
|
4668
|
-
__privateGet(this,
|
|
4669
|
-
__privateGet(this,
|
|
4670
|
-
__privateGet(this, _placements).clear();
|
|
5156
|
+
__privateGet(this, _buckets).clear();
|
|
5157
|
+
__privateGet(this, _placements2).clear();
|
|
4671
5158
|
}
|
|
4672
5159
|
/**
|
|
4673
5160
|
* Calls `visit` with the key of each formula that *may* read the given cell.
|
|
4674
|
-
* Callers still confirm with {@link refContains}:
|
|
4675
|
-
*
|
|
5161
|
+
* Callers still confirm with {@link refContains}: buckets are coarser than
|
|
5162
|
+
* rectangles in the broad dimension, so this over-approximates.
|
|
5163
|
+
*
|
|
5164
|
+
* A fixed six lookups, whatever the workbook contains.
|
|
4676
5165
|
*/
|
|
4677
5166
|
candidates(sheet, row2, col, visit) {
|
|
4678
|
-
const
|
|
4679
|
-
|
|
4680
|
-
);
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
5167
|
+
const tr = tileOfRow(row2);
|
|
5168
|
+
const tc = tileOfCol(col);
|
|
5169
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `c ${sheet} ${col} ${tr}`, visit);
|
|
5170
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `C ${sheet} ${col}`, visit);
|
|
5171
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `r ${sheet} ${row2} ${tc}`, visit);
|
|
5172
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `R ${sheet} ${row2}`, visit);
|
|
5173
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `t ${sheet} ${tr} ${tc}`, visit);
|
|
5174
|
+
__privateMethod(this, _DependencyIndex_instances, visit_fn).call(this, `w ${sheet}`, visit);
|
|
4684
5175
|
}
|
|
4685
5176
|
};
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
_placements = new WeakMap();
|
|
5177
|
+
_buckets = new WeakMap();
|
|
5178
|
+
_placements2 = new WeakMap();
|
|
4689
5179
|
_DependencyIndex_instances = new WeakSet();
|
|
4690
|
-
|
|
4691
|
-
|
|
5180
|
+
visit_fn = function(bucket, visit) {
|
|
5181
|
+
const set = __privateGet(this, _buckets).get(bucket);
|
|
5182
|
+
if (set) for (const key of set) visit(key);
|
|
5183
|
+
};
|
|
5184
|
+
file_fn = function(bucket, formulaKey, buckets) {
|
|
5185
|
+
let set = __privateGet(this, _buckets).get(bucket);
|
|
4692
5186
|
if (!set) {
|
|
4693
5187
|
set = /* @__PURE__ */ new Set();
|
|
4694
|
-
|
|
5188
|
+
__privateGet(this, _buckets).set(bucket, set);
|
|
4695
5189
|
}
|
|
4696
5190
|
if (set.has(formulaKey)) return;
|
|
4697
5191
|
set.add(formulaKey);
|
|
4698
|
-
buckets.push(
|
|
5192
|
+
buckets.push(bucket);
|
|
4699
5193
|
};
|
|
4700
5194
|
|
|
4701
5195
|
// src/recalc-engine.ts
|
|
4702
5196
|
function createWorkbookRecalcModel(workbook) {
|
|
4703
5197
|
return {
|
|
4704
5198
|
getCellValue: (sheet, ref) => workbook.getWorksheet(sheet).getCellValue(ref),
|
|
5199
|
+
// Falls back to the reference-string read rather than reporting "no value":
|
|
5200
|
+
// an older core without getCellValueAt must still return the real value,
|
|
5201
|
+
// because a silent undefined here reads as a blank cell.
|
|
5202
|
+
getCellValueAt: (sheet, row2, column2) => {
|
|
5203
|
+
const ws = workbook.getWorksheet(sheet);
|
|
5204
|
+
return ws.getCellValueAt ? ws.getCellValueAt(row2, column2) : ws.getCellValue(cellRefFromRowCol(row2, column2));
|
|
5205
|
+
},
|
|
5206
|
+
getUsedBounds: (sheet) => workbook.getWorksheet(sheet).usedBounds,
|
|
4705
5207
|
setCellValue: (sheet, ref, value2) => {
|
|
4706
5208
|
workbook.getWorksheet(sheet).getCell(ref).setComputedValue(value2);
|
|
4707
5209
|
},
|
|
@@ -4730,7 +5232,7 @@ function parseKey(key) {
|
|
|
4730
5232
|
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
4731
5233
|
return { sheet, ref, row: row2, col: column2 };
|
|
4732
5234
|
}
|
|
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;
|
|
5235
|
+
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _dependents, _volatiles, _sheetLikes, _workbookLike, _rangeCache, _RecalcEngine_instances, writeCell_fn, register_fn, unregister_fn, rememberSheet_fn, recalcInOrder_fn, applyShift_fn, shiftPrecedents_fn, shiftSpillBookkeeping_fn, recalcFrom_fn, runFixpoint_fn, addVolatiles_fn, dependentClosure_fn, forEachDirectDependent_fn, evaluatePass_fn, writeResult_fn, isOccupied_fn, clearSpill_fn, evaluateFormulaValue_fn, sheetLike_fn, buildSheetLike_fn, buildWorkbookLike_fn;
|
|
4734
5236
|
var RecalcEngine = class {
|
|
4735
5237
|
constructor(model, registry = FunctionRegistry.default) {
|
|
4736
5238
|
__privateAdd(this, _RecalcEngine_instances);
|
|
@@ -4756,6 +5258,14 @@ var RecalcEngine = class {
|
|
|
4756
5258
|
/** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
|
|
4757
5259
|
__privateAdd(this, _sheetLikes, /* @__PURE__ */ new Map());
|
|
4758
5260
|
__privateAdd(this, _workbookLike);
|
|
5261
|
+
/**
|
|
5262
|
+
* Memo for range reads. The engine owns it because it owns the writes: every
|
|
5263
|
+
* value written goes through {@link #writeCell}, which drops the cached
|
|
5264
|
+
* rectangles containing that cell. Nothing else may write to the model behind
|
|
5265
|
+
* the engine's back without calling {@link onCellChanged} — that was already
|
|
5266
|
+
* true for dependency tracking, and now also keeps this memo honest.
|
|
5267
|
+
*/
|
|
5268
|
+
__privateAdd(this, _rangeCache, new RangeCache());
|
|
4759
5269
|
__privateSet(this, _model, model);
|
|
4760
5270
|
__privateSet(this, _registry, registry);
|
|
4761
5271
|
}
|
|
@@ -4793,8 +5303,63 @@ var RecalcEngine = class {
|
|
|
4793
5303
|
volatile
|
|
4794
5304
|
});
|
|
4795
5305
|
__privateGet(this, _model).setCellFormula?.(sheet, r, text);
|
|
5306
|
+
__privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
|
|
4796
5307
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }], key);
|
|
4797
5308
|
}
|
|
5309
|
+
/**
|
|
5310
|
+
* Indexes many formulas **without evaluating anything**.
|
|
5311
|
+
*
|
|
5312
|
+
* This is the path for loading a workbook. `setCellFormula` is built for an
|
|
5313
|
+
* edit: it recalculates the new cell and everything downstream of it, which is
|
|
5314
|
+
* right for one keystroke and quadratic for a file — registering N formulas one
|
|
5315
|
+
* by one evaluates the whole workbook N times. Here the formulas are only
|
|
5316
|
+
* parsed and filed into the dependency index; call {@link recalcAll} once
|
|
5317
|
+
* afterwards, or nothing at all if the cached values from the file will do.
|
|
5318
|
+
*
|
|
5319
|
+
* The model is deliberately **not** written to. These formulas are assumed to
|
|
5320
|
+
* be the ones the model already holds (they came from the file), and persisting
|
|
5321
|
+
* them again would clear each cell's cached value — leaving every formula cell
|
|
5322
|
+
* blank until a full recalculation had run. Use `setCellFormula` for formulas
|
|
5323
|
+
* the user is actually introducing.
|
|
5324
|
+
*
|
|
5325
|
+
* A formula that fails to parse is reported rather than thrown: one unsupported
|
|
5326
|
+
* construct in a large workbook should not abort the load.
|
|
5327
|
+
*/
|
|
5328
|
+
registerBulk(entries) {
|
|
5329
|
+
const resolveName2 = __privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0;
|
|
5330
|
+
let registered = 0;
|
|
5331
|
+
const failed = [];
|
|
5332
|
+
for (const entry of entries) {
|
|
5333
|
+
const { sheet, ref, formula } = entry;
|
|
5334
|
+
if (!formula) continue;
|
|
5335
|
+
const text = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
5336
|
+
const s = normSheet(sheet);
|
|
5337
|
+
const r = normRef(ref);
|
|
5338
|
+
try {
|
|
5339
|
+
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5340
|
+
const ast = parseFormulaUncached(text);
|
|
5341
|
+
const { refs, volatile } = extractReferences(ast, s, __privateGet(this, _registry), resolveName2);
|
|
5342
|
+
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
5343
|
+
__privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
|
|
5344
|
+
sheet: s,
|
|
5345
|
+
sheetName: sheet,
|
|
5346
|
+
ref: r,
|
|
5347
|
+
key: keyOf(s, r),
|
|
5348
|
+
row: row2,
|
|
5349
|
+
col: column2,
|
|
5350
|
+
formula: text,
|
|
5351
|
+
ast,
|
|
5352
|
+
refs,
|
|
5353
|
+
volatile
|
|
5354
|
+
});
|
|
5355
|
+
registered++;
|
|
5356
|
+
} catch (err) {
|
|
5357
|
+
failed.push({ sheet, ref, error: err instanceof Error ? err.message : String(err) });
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
if (registered > 0) __privateGet(this, _rangeCache).clear();
|
|
5361
|
+
return { registered, failed };
|
|
5362
|
+
}
|
|
4798
5363
|
/**
|
|
4799
5364
|
* Removes a formula from a cell and recalculates its dependents (which may now
|
|
4800
5365
|
* read a plain input value instead). Returns the recomputed dependent cells.
|
|
@@ -4807,6 +5372,7 @@ var RecalcEngine = class {
|
|
|
4807
5372
|
__privateMethod(this, _RecalcEngine_instances, unregister_fn).call(this, key);
|
|
4808
5373
|
__privateGet(this, _model).clearCell?.(sheet, r);
|
|
4809
5374
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5375
|
+
__privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
|
|
4810
5376
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4811
5377
|
}
|
|
4812
5378
|
/**
|
|
@@ -4817,8 +5383,8 @@ var RecalcEngine = class {
|
|
|
4817
5383
|
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4818
5384
|
const s = normSheet(sheet);
|
|
4819
5385
|
const r = normRef(ref);
|
|
4820
|
-
__privateGet(this, _model).setCellValue(sheet, r, value2);
|
|
4821
5386
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5387
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, sheet, r, value2, row2, column2);
|
|
4822
5388
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4823
5389
|
}
|
|
4824
5390
|
/**
|
|
@@ -4830,11 +5396,48 @@ var RecalcEngine = class {
|
|
|
4830
5396
|
const s = normSheet(sheet);
|
|
4831
5397
|
const r = normRef(ref);
|
|
4832
5398
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
5399
|
+
__privateGet(this, _rangeCache).invalidateCell(s, row2, column2);
|
|
4833
5400
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
4834
5401
|
}
|
|
4835
|
-
/**
|
|
4836
|
-
|
|
4837
|
-
|
|
5402
|
+
/**
|
|
5403
|
+
* Recalculates every registered formula.
|
|
5404
|
+
*
|
|
5405
|
+
* By default the order is derived here, with a topological sort over the
|
|
5406
|
+
* dependency index. `order` — typically `workbook.calcChain` — supplies one
|
|
5407
|
+
* instead, so the sort is not needed.
|
|
5408
|
+
*
|
|
5409
|
+
* **A supplied order is verified, not trusted.** Evaluating in a stale order
|
|
5410
|
+
* makes a formula read a precedent that has not been computed yet, and the
|
|
5411
|
+
* result is a plausible wrong number with nothing to distinguish it from a
|
|
5412
|
+
* right one. In a calculation engine that is the worst failure available, so
|
|
5413
|
+
* this checks as it goes: after each formula is written, the dependency index
|
|
5414
|
+
* says who reads it, and any reader that was already evaluated is an inversion.
|
|
5415
|
+
* On the first inversion the ordered attempt is abandoned and the whole
|
|
5416
|
+
* recalculation is redone through the sorted path, whose result is returned;
|
|
5417
|
+
* `onOrderConflict`, if given, is told which pair conflicted.
|
|
5418
|
+
*
|
|
5419
|
+
* The verification costs about what the sort it replaces costs, so `order` is
|
|
5420
|
+
* worth roughly nothing now (measured at ~9% of a full recalculation on a
|
|
5421
|
+
* 2 000-formula workbook, after the dependency index stopped being the
|
|
5422
|
+
* bottleneck). It is kept because reading an order Excel already computed is a
|
|
5423
|
+
* reasonable thing to want; prefer plain `recalcAll()` unless you have measured
|
|
5424
|
+
* a reason not to.
|
|
5425
|
+
*
|
|
5426
|
+
* Registered formulas the order does not mention are evaluated afterwards
|
|
5427
|
+
* through the sorted path, and a dynamic array that spills still triggers the
|
|
5428
|
+
* usual fixpoint, so neither silently misses cells.
|
|
5429
|
+
*/
|
|
5430
|
+
recalcAll(options) {
|
|
5431
|
+
const order = options?.order;
|
|
5432
|
+
if (!order || order.length === 0) {
|
|
5433
|
+
return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
|
|
5434
|
+
}
|
|
5435
|
+
const attempt = __privateMethod(this, _RecalcEngine_instances, recalcInOrder_fn).call(this, order);
|
|
5436
|
+
if (attempt.conflict) {
|
|
5437
|
+
options?.onOrderConflict?.(attempt.conflict);
|
|
5438
|
+
return __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, [...__privateGet(this, _formulas).values()]).results;
|
|
5439
|
+
}
|
|
5440
|
+
return attempt.results;
|
|
4838
5441
|
}
|
|
4839
5442
|
/** The precedents recorded for a formula cell (mainly for tests/inspection). */
|
|
4840
5443
|
getPrecedents(sheet, ref) {
|
|
@@ -4884,7 +5487,23 @@ _dependents = new WeakMap();
|
|
|
4884
5487
|
_volatiles = new WeakMap();
|
|
4885
5488
|
_sheetLikes = new WeakMap();
|
|
4886
5489
|
_workbookLike = new WeakMap();
|
|
5490
|
+
_rangeCache = new WeakMap();
|
|
4887
5491
|
_RecalcEngine_instances = new WeakSet();
|
|
5492
|
+
/**
|
|
5493
|
+
* The single write path into the model. Writing a value invalidates the
|
|
5494
|
+
* cached rectangles that contain the cell, so a formula evaluated later in the
|
|
5495
|
+
* same pass cannot read a pre-write copy of a range. Coordinates are passed in
|
|
5496
|
+
* where the caller already knows them, to avoid re-parsing the reference.
|
|
5497
|
+
*/
|
|
5498
|
+
writeCell_fn = function(sheetName, ref, value2, row2, col) {
|
|
5499
|
+
__privateGet(this, _model).setCellValue(sheetName, ref, value2);
|
|
5500
|
+
if (row2 === void 0 || col === void 0) {
|
|
5501
|
+
const parsed = parseCellRef(ref);
|
|
5502
|
+
row2 = parsed.row;
|
|
5503
|
+
col = parsed.column;
|
|
5504
|
+
}
|
|
5505
|
+
__privateGet(this, _rangeCache).invalidateCell(normSheet(sheetName), row2, col);
|
|
5506
|
+
};
|
|
4888
5507
|
/** Registers an entry in the map, the reverse index and the volatile set. */
|
|
4889
5508
|
register_fn = function(entry) {
|
|
4890
5509
|
__privateGet(this, _formulas).set(entry.key, entry);
|
|
@@ -4900,6 +5519,49 @@ unregister_fn = function(key) {
|
|
|
4900
5519
|
rememberSheet_fn = function(name) {
|
|
4901
5520
|
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
4902
5521
|
};
|
|
5522
|
+
/**
|
|
5523
|
+
* One pass in a caller-supplied order, watching for inversions.
|
|
5524
|
+
*
|
|
5525
|
+
* The check is the mirror image of the topological sort: instead of asking
|
|
5526
|
+
* "which of my precedents come first?", it asks, of each formula just
|
|
5527
|
+
* evaluated, "has anything that reads me already run?". The dependency index
|
|
5528
|
+
* answers that directly, and one positive answer is enough to know the order
|
|
5529
|
+
* cannot be trusted.
|
|
5530
|
+
*/
|
|
5531
|
+
recalcInOrder_fn = function(order) {
|
|
5532
|
+
const results = [];
|
|
5533
|
+
const changed = [];
|
|
5534
|
+
const done = /* @__PURE__ */ new Set();
|
|
5535
|
+
let spilled = false;
|
|
5536
|
+
for (const { sheet, ref } of order) {
|
|
5537
|
+
const key = keyOf(sheet, ref);
|
|
5538
|
+
if (done.has(key)) continue;
|
|
5539
|
+
const entry = __privateGet(this, _formulas).get(key);
|
|
5540
|
+
if (!entry) continue;
|
|
5541
|
+
done.add(key);
|
|
5542
|
+
const written = __privateMethod(this, _RecalcEngine_instances, writeResult_fn).call(this, entry);
|
|
5543
|
+
results.push(written.result);
|
|
5544
|
+
changed.push(...written.changed);
|
|
5545
|
+
if (written.result.spill) spilled = true;
|
|
5546
|
+
let conflict;
|
|
5547
|
+
__privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: entry.sheet, row: entry.row, col: entry.col }, (dep) => {
|
|
5548
|
+
if (conflict || dep.key === entry.key) return;
|
|
5549
|
+
if (done.has(dep.key)) conflict = { dependent: dep.key, precedent: entry.key };
|
|
5550
|
+
});
|
|
5551
|
+
if (conflict) return { results: [], conflict };
|
|
5552
|
+
}
|
|
5553
|
+
const rest = [...__privateGet(this, _formulas).values()].filter((e) => !done.has(e.key));
|
|
5554
|
+
if (rest.length > 0) {
|
|
5555
|
+
const pass = __privateMethod(this, _RecalcEngine_instances, evaluatePass_fn).call(this, rest);
|
|
5556
|
+
results.push(...pass.results);
|
|
5557
|
+
changed.push(...pass.changed);
|
|
5558
|
+
}
|
|
5559
|
+
if (spilled) {
|
|
5560
|
+
const evaluated = new Set(done);
|
|
5561
|
+
results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, changed, evaluated), evaluated));
|
|
5562
|
+
}
|
|
5563
|
+
return { results };
|
|
5564
|
+
};
|
|
4903
5565
|
applyShift_fn = function(shift) {
|
|
4904
5566
|
const editedSheet = normSheet(shift.sheetName);
|
|
4905
5567
|
const isRowShift = shift.dim === "row";
|
|
@@ -4941,6 +5603,7 @@ applyShift_fn = function(shift) {
|
|
|
4941
5603
|
__privateGet(this, _volatiles).clear();
|
|
4942
5604
|
for (const e of rekeyed) __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, e);
|
|
4943
5605
|
__privateMethod(this, _RecalcEngine_instances, shiftSpillBookkeeping_fn).call(this, shift, editedSheet);
|
|
5606
|
+
__privateGet(this, _rangeCache).clear();
|
|
4944
5607
|
const pending = /* @__PURE__ */ new Map();
|
|
4945
5608
|
for (const k of needsRecalc) {
|
|
4946
5609
|
const e = __privateGet(this, _formulas).get(k);
|
|
@@ -5011,7 +5674,7 @@ shiftSpillBookkeeping_fn = function(shift, editedSheet) {
|
|
|
5011
5674
|
const shifted = shiftCellKey(m);
|
|
5012
5675
|
if (!shifted) continue;
|
|
5013
5676
|
const cell = parseKey(shifted);
|
|
5014
|
-
|
|
5677
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
5015
5678
|
}
|
|
5016
5679
|
continue;
|
|
5017
5680
|
}
|
|
@@ -5152,7 +5815,7 @@ evaluatePass_fn = function(entries) {
|
|
|
5152
5815
|
if (emitted.has(k)) continue;
|
|
5153
5816
|
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, e.key, changed);
|
|
5154
5817
|
const value2 = FormulaValue.errorRef.toObject();
|
|
5155
|
-
|
|
5818
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
5156
5819
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
5157
5820
|
results.push({ sheet: e.sheetName, ref: e.ref, value: value2, circular: true });
|
|
5158
5821
|
}
|
|
@@ -5173,7 +5836,7 @@ writeResult_fn = function(e) {
|
|
|
5173
5836
|
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
5174
5837
|
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
5175
5838
|
const value2 = fv2.toObject();
|
|
5176
|
-
|
|
5839
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
5177
5840
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
5178
5841
|
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
5179
5842
|
}
|
|
@@ -5194,7 +5857,7 @@ writeResult_fn = function(e) {
|
|
|
5194
5857
|
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, anchorKey, changed);
|
|
5195
5858
|
__privateGet(this, _model).setCellArrayRef?.(e.sheetName, e.ref, null);
|
|
5196
5859
|
const value2 = FormulaValue.error(9 /* Spill */).toObject();
|
|
5197
|
-
|
|
5860
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, e.ref, value2, e.row, e.col);
|
|
5198
5861
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
5199
5862
|
return { result: { sheet: e.sheetName, ref: e.ref, value: value2 }, changed };
|
|
5200
5863
|
}
|
|
@@ -5203,7 +5866,7 @@ writeResult_fn = function(e) {
|
|
|
5203
5866
|
for (let dc = 0; dc < cols; dc++) {
|
|
5204
5867
|
const r = e.row + dr, c = e.col + dc;
|
|
5205
5868
|
const ref = cellRefFromRowCol(r, c);
|
|
5206
|
-
|
|
5869
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, e.sheetName, ref, arr.get(dr, dc).toObject(), r, c);
|
|
5207
5870
|
changed.push({ sheet: e.sheet, row: r, col: c });
|
|
5208
5871
|
if (dr !== 0 || dc !== 0) {
|
|
5209
5872
|
const cellKey2 = keyOf(e.sheet, ref);
|
|
@@ -5215,7 +5878,7 @@ writeResult_fn = function(e) {
|
|
|
5215
5878
|
for (const oldKey of prevSpill) {
|
|
5216
5879
|
if (newSpill.has(oldKey)) continue;
|
|
5217
5880
|
const cell = parseKey(oldKey);
|
|
5218
|
-
|
|
5881
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
5219
5882
|
__privateGet(this, _spillOwner).delete(oldKey);
|
|
5220
5883
|
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
5221
5884
|
}
|
|
@@ -5242,7 +5905,7 @@ clearSpill_fn = function(anchorKey, changed) {
|
|
|
5242
5905
|
if (!spill) return;
|
|
5243
5906
|
for (const cellKey2 of spill) {
|
|
5244
5907
|
const cell = parseKey(cellKey2);
|
|
5245
|
-
|
|
5908
|
+
__privateMethod(this, _RecalcEngine_instances, writeCell_fn).call(this, __privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null, cell.row, cell.col);
|
|
5246
5909
|
__privateGet(this, _spillOwner).delete(cellKey2);
|
|
5247
5910
|
changed.push({ sheet: cell.sheet, row: cell.row, col: cell.col });
|
|
5248
5911
|
}
|
|
@@ -5259,7 +5922,8 @@ evaluateFormulaValue_fn = function(e) {
|
|
|
5259
5922
|
return evaluateAst(e.ast, __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName), {
|
|
5260
5923
|
workbook: __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this),
|
|
5261
5924
|
formulaRow: e.row,
|
|
5262
|
-
formulaCol: e.col
|
|
5925
|
+
formulaCol: e.col,
|
|
5926
|
+
rangeCache: __privateGet(this, _rangeCache)
|
|
5263
5927
|
});
|
|
5264
5928
|
};
|
|
5265
5929
|
/**
|
|
@@ -5276,9 +5940,16 @@ sheetLike_fn = function(sheetName) {
|
|
|
5276
5940
|
return cached;
|
|
5277
5941
|
};
|
|
5278
5942
|
buildSheetLike_fn = function(sheetName) {
|
|
5943
|
+
const model = __privateGet(this, _model);
|
|
5279
5944
|
return {
|
|
5280
5945
|
name: sheetName,
|
|
5281
5946
|
getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
|
|
5947
|
+
getCellValueAt: model.getCellValueAt ? (row2, column2) => model.getCellValueAt(sheetName, row2, column2) : void 0,
|
|
5948
|
+
// A getter, not a snapshot: the extent grows as formulas write results,
|
|
5949
|
+
// and a whole-column reference read later in the same pass must see it.
|
|
5950
|
+
get usedBounds() {
|
|
5951
|
+
return model.getUsedBounds?.(sheetName);
|
|
5952
|
+
},
|
|
5282
5953
|
isRowHidden: __privateGet(this, _model).isRowHidden ? (row2) => __privateGet(this, _model).isRowHidden(sheetName, row2) : void 0,
|
|
5283
5954
|
getCellFormula: __privateGet(this, _model).getCellFormula ? (ref) => __privateGet(this, _model).getCellFormula(sheetName, ref) : void 0,
|
|
5284
5955
|
getSpillRange: __privateGet(this, _model).getSpillRange ? (ref) => __privateGet(this, _model).getSpillRange(sheetName, ref) : void 0
|
|
@@ -5302,6 +5973,6 @@ buildWorkbookLike_fn = function() {
|
|
|
5302
5973
|
});
|
|
5303
5974
|
};
|
|
5304
5975
|
|
|
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 };
|
|
5976
|
+
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
5977
|
//# sourceMappingURL=index.js.map
|
|
5307
5978
|
//# sourceMappingURL=index.js.map
|