@devmm/puredocs-excel-formula 1.0.4 → 1.0.6
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/LICENSE +21 -0
- package/dist/index.cjs +385 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +385 -53
- package/dist/index.js.map +1 -1
- package/package.json +45 -45
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol } from '@devmm/puredocs-excel';
|
|
1
|
+
import { toOADate, fromOADate, columnLetter, parseCellRef, cellRefFromRowCol, shiftIndex, shiftFormulaRefs, shiftSpan, EXCEL_MAX_ROWS, EXCEL_MAX_COLUMNS } from '@devmm/puredocs-excel';
|
|
2
2
|
|
|
3
3
|
var __typeError = (msg) => {
|
|
4
4
|
throw TypeError(msg);
|
|
@@ -4455,12 +4455,15 @@ function parseFormula(formula) {
|
|
|
4455
4455
|
const stripped = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
4456
4456
|
const cached = astCache.get(stripped);
|
|
4457
4457
|
if (cached !== void 0) return cached;
|
|
4458
|
-
const
|
|
4459
|
-
const tokens = lexer.tokenize();
|
|
4460
|
-
const ast = new FormulaParser(tokens).parse();
|
|
4458
|
+
const ast = parseFormulaUncached(stripped);
|
|
4461
4459
|
astCache.set(stripped, ast);
|
|
4462
4460
|
return ast;
|
|
4463
4461
|
}
|
|
4462
|
+
function parseFormulaUncached(formula) {
|
|
4463
|
+
const stripped = formula.startsWith("=") ? formula.slice(1) : formula;
|
|
4464
|
+
const tokens = new FormulaLexer(stripped).tokenize();
|
|
4465
|
+
return new FormulaParser(tokens).parse();
|
|
4466
|
+
}
|
|
4464
4467
|
var DEFAULT_LAMBDA_DEPTH = 1024;
|
|
4465
4468
|
function buildContext(sheet, opts) {
|
|
4466
4469
|
const evaluating = opts.evaluating ?? /* @__PURE__ */ new Set();
|
|
@@ -4484,7 +4487,7 @@ function buildContext(sheet, opts) {
|
|
|
4484
4487
|
return ctx;
|
|
4485
4488
|
}
|
|
4486
4489
|
function resolveName(name, sheet, workbook, evaluating, resolving, resolved, recursionGuard) {
|
|
4487
|
-
const key = `${sheet.name}
|
|
4490
|
+
const key = `${sheet.name} ${name.toUpperCase()}`;
|
|
4488
4491
|
const cached = resolved.get(key);
|
|
4489
4492
|
if (cached !== void 0) return cached;
|
|
4490
4493
|
if (resolving.has(key)) return FormulaValue.errorRef;
|
|
@@ -4512,7 +4515,13 @@ function resolveName(name, sheet, workbook, evaluating, resolving, resolved, rec
|
|
|
4512
4515
|
function evaluateFormula(formula, sheet, options) {
|
|
4513
4516
|
if (!formula) return FormulaValue.blank;
|
|
4514
4517
|
try {
|
|
4515
|
-
|
|
4518
|
+
return evaluateAst(parseFormula(formula), sheet, options);
|
|
4519
|
+
} catch {
|
|
4520
|
+
return FormulaValue.errorValue;
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
function evaluateAst(ast, sheet, options) {
|
|
4524
|
+
try {
|
|
4516
4525
|
const ctx = buildContext(sheet, {
|
|
4517
4526
|
workbook: options?.workbook,
|
|
4518
4527
|
formulaRow: options?.formulaRow,
|
|
@@ -4600,6 +4609,95 @@ function refContains(ref, sheet, row2, col) {
|
|
|
4600
4609
|
return ref.sheet === sheet && row2 >= ref.startRow && row2 <= ref.endRow && col >= ref.startCol && col <= ref.endCol;
|
|
4601
4610
|
}
|
|
4602
4611
|
|
|
4612
|
+
// src/dependency-index.ts
|
|
4613
|
+
var TILE_ROWS = 64;
|
|
4614
|
+
var TILE_COLS = 64;
|
|
4615
|
+
var MAX_TILES_PER_REF = 32;
|
|
4616
|
+
var _tiles, _wide, _placements, _DependencyIndex_instances, fileIn_fn;
|
|
4617
|
+
var DependencyIndex = class {
|
|
4618
|
+
constructor() {
|
|
4619
|
+
__privateAdd(this, _DependencyIndex_instances);
|
|
4620
|
+
/** `sheet|tileRow|tileCol` → formula keys whose precedents overlap that tile. */
|
|
4621
|
+
__privateAdd(this, _tiles, /* @__PURE__ */ new Map());
|
|
4622
|
+
/** sheet → formula keys with a precedent too broad to tile. */
|
|
4623
|
+
__privateAdd(this, _wide, /* @__PURE__ */ new Map());
|
|
4624
|
+
/** formula key → the bucket keys it was filed under, for cheap removal. */
|
|
4625
|
+
__privateAdd(this, _placements, /* @__PURE__ */ new Map());
|
|
4626
|
+
}
|
|
4627
|
+
/** Registers (or re-registers) a formula's precedent rectangles. */
|
|
4628
|
+
add(formulaKey, refs) {
|
|
4629
|
+
this.remove(formulaKey);
|
|
4630
|
+
if (refs.length === 0) return;
|
|
4631
|
+
const buckets = [];
|
|
4632
|
+
for (const ref of refs) {
|
|
4633
|
+
const tileRow0 = ref.startRow / TILE_ROWS | 0;
|
|
4634
|
+
const tileRow1 = ref.endRow / TILE_ROWS | 0;
|
|
4635
|
+
const tileCol0 = ref.startCol / TILE_COLS | 0;
|
|
4636
|
+
const tileCol1 = ref.endCol / TILE_COLS | 0;
|
|
4637
|
+
const tileCount = (tileRow1 - tileRow0 + 1) * (tileCol1 - tileCol0 + 1);
|
|
4638
|
+
if (tileCount > MAX_TILES_PER_REF) {
|
|
4639
|
+
__privateMethod(this, _DependencyIndex_instances, fileIn_fn).call(this, __privateGet(this, _wide), ref.sheet, formulaKey, buckets, `w ${ref.sheet}`);
|
|
4640
|
+
continue;
|
|
4641
|
+
}
|
|
4642
|
+
for (let tr = tileRow0; tr <= tileRow1; tr++) {
|
|
4643
|
+
for (let tc = tileCol0; tc <= tileCol1; tc++) {
|
|
4644
|
+
const key = `${ref.sheet} ${tr} ${tc}`;
|
|
4645
|
+
__privateMethod(this, _DependencyIndex_instances, fileIn_fn).call(this, __privateGet(this, _tiles), key, formulaKey, buckets, `t ${key}`);
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
__privateGet(this, _placements).set(formulaKey, buckets);
|
|
4650
|
+
}
|
|
4651
|
+
/** Forgets a formula. */
|
|
4652
|
+
remove(formulaKey) {
|
|
4653
|
+
const buckets = __privateGet(this, _placements).get(formulaKey);
|
|
4654
|
+
if (!buckets) return;
|
|
4655
|
+
for (const bucket of buckets) {
|
|
4656
|
+
const wide = bucket.charCodeAt(0) === 119;
|
|
4657
|
+
const store = wide ? __privateGet(this, _wide) : __privateGet(this, _tiles);
|
|
4658
|
+
const key = bucket.slice(2);
|
|
4659
|
+
const set = store.get(key);
|
|
4660
|
+
if (!set) continue;
|
|
4661
|
+
set.delete(formulaKey);
|
|
4662
|
+
if (set.size === 0) store.delete(key);
|
|
4663
|
+
}
|
|
4664
|
+
__privateGet(this, _placements).delete(formulaKey);
|
|
4665
|
+
}
|
|
4666
|
+
/** Drops every registration. */
|
|
4667
|
+
clear() {
|
|
4668
|
+
__privateGet(this, _tiles).clear();
|
|
4669
|
+
__privateGet(this, _wide).clear();
|
|
4670
|
+
__privateGet(this, _placements).clear();
|
|
4671
|
+
}
|
|
4672
|
+
/**
|
|
4673
|
+
* Calls `visit` with the key of each formula that *may* read the given cell.
|
|
4674
|
+
* Callers still confirm with {@link refContains}: a tile is coarser than a
|
|
4675
|
+
* rectangle, so this over-approximates by at most a tile's worth.
|
|
4676
|
+
*/
|
|
4677
|
+
candidates(sheet, row2, col, visit) {
|
|
4678
|
+
const tile = __privateGet(this, _tiles).get(
|
|
4679
|
+
`${sheet} ${row2 / TILE_ROWS | 0} ${col / TILE_COLS | 0}`
|
|
4680
|
+
);
|
|
4681
|
+
if (tile) for (const key of tile) visit(key);
|
|
4682
|
+
const wide = __privateGet(this, _wide).get(sheet);
|
|
4683
|
+
if (wide) for (const key of wide) visit(key);
|
|
4684
|
+
}
|
|
4685
|
+
};
|
|
4686
|
+
_tiles = new WeakMap();
|
|
4687
|
+
_wide = new WeakMap();
|
|
4688
|
+
_placements = new WeakMap();
|
|
4689
|
+
_DependencyIndex_instances = new WeakSet();
|
|
4690
|
+
fileIn_fn = function(store, key, formulaKey, buckets, bucketId) {
|
|
4691
|
+
let set = store.get(key);
|
|
4692
|
+
if (!set) {
|
|
4693
|
+
set = /* @__PURE__ */ new Set();
|
|
4694
|
+
store.set(key, set);
|
|
4695
|
+
}
|
|
4696
|
+
if (set.has(formulaKey)) return;
|
|
4697
|
+
set.add(formulaKey);
|
|
4698
|
+
buckets.push(bucketId);
|
|
4699
|
+
};
|
|
4700
|
+
|
|
4603
4701
|
// src/recalc-engine.ts
|
|
4604
4702
|
function createWorkbookRecalcModel(workbook) {
|
|
4605
4703
|
return {
|
|
@@ -4632,7 +4730,7 @@ function parseKey(key) {
|
|
|
4632
4730
|
const { row: row2, column: column2 } = parseCellRef(ref);
|
|
4633
4731
|
return { sheet, ref, row: row2, col: column2 };
|
|
4634
4732
|
}
|
|
4635
|
-
var _model, _registry, _formulas, _sheetNames, _spills, _spillOwner, _RecalcEngine_instances, rememberSheet_fn, recalcFrom_fn, dependentClosure_fn,
|
|
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;
|
|
4636
4734
|
var RecalcEngine = class {
|
|
4637
4735
|
constructor(model, registry = FunctionRegistry.default) {
|
|
4638
4736
|
__privateAdd(this, _RecalcEngine_instances);
|
|
@@ -4645,6 +4743,19 @@ var RecalcEngine = class {
|
|
|
4645
4743
|
__privateAdd(this, _spills, /* @__PURE__ */ new Map());
|
|
4646
4744
|
/** spilled cellKey → the anchor cellKey that owns it. */
|
|
4647
4745
|
__privateAdd(this, _spillOwner, /* @__PURE__ */ new Map());
|
|
4746
|
+
/**
|
|
4747
|
+
* Reverse dependency index, so finding the formulas that read a cell does not
|
|
4748
|
+
* mean testing every registered formula.
|
|
4749
|
+
*/
|
|
4750
|
+
__privateAdd(this, _dependents, new DependencyIndex());
|
|
4751
|
+
/**
|
|
4752
|
+
* Keys of the volatile formulas. Maintained incrementally: scanning every
|
|
4753
|
+
* formula for volatility on each edit is O(F) per keystroke on its own.
|
|
4754
|
+
*/
|
|
4755
|
+
__privateAdd(this, _volatiles, /* @__PURE__ */ new Set());
|
|
4756
|
+
/** Memoised per-sheet adapters — these are stateless w.r.t. the formula. */
|
|
4757
|
+
__privateAdd(this, _sheetLikes, /* @__PURE__ */ new Map());
|
|
4758
|
+
__privateAdd(this, _workbookLike);
|
|
4648
4759
|
__privateSet(this, _model, model);
|
|
4649
4760
|
__privateSet(this, _registry, registry);
|
|
4650
4761
|
}
|
|
@@ -4660,7 +4771,7 @@ var RecalcEngine = class {
|
|
|
4660
4771
|
const s = normSheet(sheet);
|
|
4661
4772
|
const r = normRef(ref);
|
|
4662
4773
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
4663
|
-
const ast =
|
|
4774
|
+
const ast = parseFormulaUncached(text);
|
|
4664
4775
|
const { refs, volatile } = extractReferences(
|
|
4665
4776
|
ast,
|
|
4666
4777
|
s,
|
|
@@ -4668,18 +4779,21 @@ var RecalcEngine = class {
|
|
|
4668
4779
|
__privateGet(this, _model).getDefinedName ? (n, sh) => __privateGet(this, _model).getDefinedName(n, sh) : void 0
|
|
4669
4780
|
);
|
|
4670
4781
|
__privateMethod(this, _RecalcEngine_instances, rememberSheet_fn).call(this, sheet);
|
|
4671
|
-
|
|
4782
|
+
const key = keyOf(s, r);
|
|
4783
|
+
__privateMethod(this, _RecalcEngine_instances, register_fn).call(this, {
|
|
4672
4784
|
sheet: s,
|
|
4673
4785
|
sheetName: sheet,
|
|
4674
4786
|
ref: r,
|
|
4787
|
+
key,
|
|
4675
4788
|
row: row2,
|
|
4676
4789
|
col: column2,
|
|
4677
4790
|
formula: text,
|
|
4791
|
+
ast,
|
|
4678
4792
|
refs,
|
|
4679
4793
|
volatile
|
|
4680
4794
|
});
|
|
4681
4795
|
__privateGet(this, _model).setCellFormula?.(sheet, r, text);
|
|
4682
|
-
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }],
|
|
4796
|
+
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }], key);
|
|
4683
4797
|
}
|
|
4684
4798
|
/**
|
|
4685
4799
|
* Removes a formula from a cell and recalculates its dependents (which may now
|
|
@@ -4688,8 +4802,9 @@ var RecalcEngine = class {
|
|
|
4688
4802
|
clearCellFormula(sheet, ref) {
|
|
4689
4803
|
const s = normSheet(sheet);
|
|
4690
4804
|
const r = normRef(ref);
|
|
4691
|
-
const
|
|
4692
|
-
if (!
|
|
4805
|
+
const key = keyOf(s, r);
|
|
4806
|
+
if (!__privateGet(this, _formulas).has(key)) return [];
|
|
4807
|
+
__privateMethod(this, _RecalcEngine_instances, unregister_fn).call(this, key);
|
|
4693
4808
|
__privateGet(this, _model).clearCell?.(sheet, r);
|
|
4694
4809
|
const { row: row2, column: column2 } = parseCellRef(r);
|
|
4695
4810
|
return __privateMethod(this, _RecalcEngine_instances, recalcFrom_fn).call(this, [{ sheet: s, row: row2, col: column2 }]);
|
|
@@ -4725,6 +4840,39 @@ var RecalcEngine = class {
|
|
|
4725
4840
|
getPrecedents(sheet, ref) {
|
|
4726
4841
|
return __privateGet(this, _formulas).get(keyOf(sheet, ref))?.refs ?? [];
|
|
4727
4842
|
}
|
|
4843
|
+
// ── Structural edits ─────────────────────────────────────────────────────────
|
|
4844
|
+
/**
|
|
4845
|
+
* Tells the engine rows were inserted on a sheet. Call AFTER the
|
|
4846
|
+
* corresponding `Worksheet.insertRows` (the model must already reflect the
|
|
4847
|
+
* edit). The dependency graph is translated in place — registrations,
|
|
4848
|
+
* formula text and spill bookkeeping all move — and only volatile formulas
|
|
4849
|
+
* are recomputed, because a pure translation changes no values.
|
|
4850
|
+
*
|
|
4851
|
+
* @example
|
|
4852
|
+
* ws.insertRows(3, 2);
|
|
4853
|
+
* engine.onRowsInserted('S1', 3, 2);
|
|
4854
|
+
*/
|
|
4855
|
+
onRowsInserted(sheet, at, count2 = 1) {
|
|
4856
|
+
return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "row", kind: "insert", at, count: count2, sheetName: sheet });
|
|
4857
|
+
}
|
|
4858
|
+
/**
|
|
4859
|
+
* Tells the engine rows were deleted on a sheet. Call AFTER the
|
|
4860
|
+
* corresponding `Worksheet.deleteRows`. Registrations inside the deleted
|
|
4861
|
+
* band are dropped; the rest of the graph is translated, and only formulas
|
|
4862
|
+
* whose precedents touched the deleted band (plus their dependents and
|
|
4863
|
+
* volatiles) are recomputed.
|
|
4864
|
+
*/
|
|
4865
|
+
onRowsDeleted(sheet, at, count2 = 1) {
|
|
4866
|
+
return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "row", kind: "delete", at, count: count2, sheetName: sheet });
|
|
4867
|
+
}
|
|
4868
|
+
/** Column counterpart to {@link onRowsInserted}. */
|
|
4869
|
+
onColumnsInserted(sheet, at, count2 = 1) {
|
|
4870
|
+
return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "col", kind: "insert", at, count: count2, sheetName: sheet });
|
|
4871
|
+
}
|
|
4872
|
+
/** Column counterpart to {@link onRowsDeleted}. */
|
|
4873
|
+
onColumnsDeleted(sheet, at, count2 = 1) {
|
|
4874
|
+
return __privateMethod(this, _RecalcEngine_instances, applyShift_fn).call(this, { dim: "col", kind: "delete", at, count: count2, sheetName: sheet });
|
|
4875
|
+
}
|
|
4728
4876
|
};
|
|
4729
4877
|
_model = new WeakMap();
|
|
4730
4878
|
_registry = new WeakMap();
|
|
@@ -4732,10 +4880,152 @@ _formulas = new WeakMap();
|
|
|
4732
4880
|
_sheetNames = new WeakMap();
|
|
4733
4881
|
_spills = new WeakMap();
|
|
4734
4882
|
_spillOwner = new WeakMap();
|
|
4883
|
+
_dependents = new WeakMap();
|
|
4884
|
+
_volatiles = new WeakMap();
|
|
4885
|
+
_sheetLikes = new WeakMap();
|
|
4886
|
+
_workbookLike = new WeakMap();
|
|
4735
4887
|
_RecalcEngine_instances = new WeakSet();
|
|
4888
|
+
/** Registers an entry in the map, the reverse index and the volatile set. */
|
|
4889
|
+
register_fn = function(entry) {
|
|
4890
|
+
__privateGet(this, _formulas).set(entry.key, entry);
|
|
4891
|
+
__privateGet(this, _dependents).add(entry.key, entry.refs);
|
|
4892
|
+
if (entry.volatile) __privateGet(this, _volatiles).add(entry.key);
|
|
4893
|
+
else __privateGet(this, _volatiles).delete(entry.key);
|
|
4894
|
+
};
|
|
4895
|
+
unregister_fn = function(key) {
|
|
4896
|
+
__privateGet(this, _formulas).delete(key);
|
|
4897
|
+
__privateGet(this, _dependents).remove(key);
|
|
4898
|
+
__privateGet(this, _volatiles).delete(key);
|
|
4899
|
+
};
|
|
4736
4900
|
rememberSheet_fn = function(name) {
|
|
4737
4901
|
__privateGet(this, _sheetNames).set(normSheet(name), name);
|
|
4738
4902
|
};
|
|
4903
|
+
applyShift_fn = function(shift) {
|
|
4904
|
+
const editedSheet = normSheet(shift.sheetName);
|
|
4905
|
+
const isRowShift = shift.dim === "row";
|
|
4906
|
+
const bandEnd = shift.at + shift.count - 1;
|
|
4907
|
+
const rekeyed = [];
|
|
4908
|
+
const needsRecalc = /* @__PURE__ */ new Set();
|
|
4909
|
+
for (const e of __privateGet(this, _formulas).values()) {
|
|
4910
|
+
let row2 = e.row;
|
|
4911
|
+
let col = e.col;
|
|
4912
|
+
if (e.sheet === editedSheet) {
|
|
4913
|
+
const shifted = shiftIndex(isRowShift ? row2 : col, shift);
|
|
4914
|
+
if (shifted === null) continue;
|
|
4915
|
+
if (isRowShift) row2 = shifted;
|
|
4916
|
+
else col = shifted;
|
|
4917
|
+
}
|
|
4918
|
+
const touchedBand = shift.kind === "delete" && e.refs.some((r) => r.sheet === editedSheet && (isRowShift ? r.endRow >= shift.at && r.startRow <= bandEnd : r.endCol >= shift.at && r.startCol <= bandEnd));
|
|
4919
|
+
const rewritten = shiftFormulaRefs(e.formula, shift, e.sheetName);
|
|
4920
|
+
const moved = row2 !== e.row || col !== e.col;
|
|
4921
|
+
const ref = moved ? cellRefFromRowCol(row2, col) : e.ref;
|
|
4922
|
+
const key = moved ? keyOf(e.sheet, ref) : e.key;
|
|
4923
|
+
rekeyed.push({
|
|
4924
|
+
...e,
|
|
4925
|
+
ref,
|
|
4926
|
+
key,
|
|
4927
|
+
row: row2,
|
|
4928
|
+
col,
|
|
4929
|
+
formula: rewritten.changed ? rewritten.text : e.formula,
|
|
4930
|
+
// A rewritten formula's cached AST no longer matches its text; it is
|
|
4931
|
+
// re-parsed lazily, and only if the cell is actually evaluated again.
|
|
4932
|
+
ast: rewritten.changed ? void 0 : e.ast,
|
|
4933
|
+
refs: __privateMethod(this, _RecalcEngine_instances, shiftPrecedents_fn).call(this, e.refs, shift, editedSheet)
|
|
4934
|
+
// Volatility is a property of which *functions* a formula calls, and
|
|
4935
|
+
// rewriting references cannot change that.
|
|
4936
|
+
});
|
|
4937
|
+
if (touchedBand || rewritten.hasRefError) needsRecalc.add(key);
|
|
4938
|
+
}
|
|
4939
|
+
__privateSet(this, _formulas, /* @__PURE__ */ new Map());
|
|
4940
|
+
__privateGet(this, _dependents).clear();
|
|
4941
|
+
__privateGet(this, _volatiles).clear();
|
|
4942
|
+
for (const e of rekeyed) __privateMethod(this, _RecalcEngine_instances, register_fn).call(this, e);
|
|
4943
|
+
__privateMethod(this, _RecalcEngine_instances, shiftSpillBookkeeping_fn).call(this, shift, editedSheet);
|
|
4944
|
+
const pending = /* @__PURE__ */ new Map();
|
|
4945
|
+
for (const k of needsRecalc) {
|
|
4946
|
+
const e = __privateGet(this, _formulas).get(k);
|
|
4947
|
+
if (e) pending.set(k, e);
|
|
4948
|
+
}
|
|
4949
|
+
for (const e of __privateGet(this, _formulas).values()) {
|
|
4950
|
+
if (e.volatile) pending.set(e.key, e);
|
|
4951
|
+
}
|
|
4952
|
+
return __privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, /* @__PURE__ */ new Set());
|
|
4953
|
+
};
|
|
4954
|
+
/**
|
|
4955
|
+
* Translates precedent rectangles across a structural edit.
|
|
4956
|
+
*
|
|
4957
|
+
* This is arithmetic rather than a re-parse of the rewritten formula, and the
|
|
4958
|
+
* two are equivalent by construction: `shiftFormulaRefs` derives the new
|
|
4959
|
+
* reference text by calling `shiftSpan`/`shiftIndex` on exactly these numbers,
|
|
4960
|
+
* so applying the same functions to the stored rectangle yields the same
|
|
4961
|
+
* region. A rectangle that is entirely deleted disappears, matching the
|
|
4962
|
+
* `#REF!` the text rewrite produces.
|
|
4963
|
+
*/
|
|
4964
|
+
shiftPrecedents_fn = function(refs, shift, editedSheet) {
|
|
4965
|
+
const out = [];
|
|
4966
|
+
for (const r of refs) {
|
|
4967
|
+
if (r.sheet !== editedSheet) {
|
|
4968
|
+
out.push(r);
|
|
4969
|
+
continue;
|
|
4970
|
+
}
|
|
4971
|
+
if (shift.dim === "row") {
|
|
4972
|
+
const span = shiftSpan(r.startRow, r.endRow, shift);
|
|
4973
|
+
if (!span) continue;
|
|
4974
|
+
out.push({
|
|
4975
|
+
sheet: r.sheet,
|
|
4976
|
+
startRow: span.start,
|
|
4977
|
+
endRow: Math.min(span.end, EXCEL_MAX_ROWS),
|
|
4978
|
+
startCol: r.startCol,
|
|
4979
|
+
endCol: r.endCol
|
|
4980
|
+
});
|
|
4981
|
+
} else {
|
|
4982
|
+
const span = shiftSpan(r.startCol, r.endCol, shift);
|
|
4983
|
+
if (!span) continue;
|
|
4984
|
+
out.push({
|
|
4985
|
+
sheet: r.sheet,
|
|
4986
|
+
startRow: r.startRow,
|
|
4987
|
+
endRow: r.endRow,
|
|
4988
|
+
startCol: span.start,
|
|
4989
|
+
endCol: Math.min(span.end, EXCEL_MAX_COLUMNS)
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
return out;
|
|
4994
|
+
};
|
|
4995
|
+
/** Translates spill ownership across a structural edit on `editedSheet`. */
|
|
4996
|
+
shiftSpillBookkeeping_fn = function(shift, editedSheet) {
|
|
4997
|
+
const shiftCellKey = (key) => {
|
|
4998
|
+
const { sheet, row: row2, col } = parseKey(key);
|
|
4999
|
+
if (sheet !== editedSheet) return key;
|
|
5000
|
+
const r = shift.dim === "row" ? shiftIndex(row2, shift) : row2;
|
|
5001
|
+
const c = shift.dim === "col" ? shiftIndex(col, shift) : col;
|
|
5002
|
+
if (r === null || c === null) return null;
|
|
5003
|
+
return keyOf(sheet, cellRefFromRowCol(r, c));
|
|
5004
|
+
};
|
|
5005
|
+
const newSpills = /* @__PURE__ */ new Map();
|
|
5006
|
+
__privateGet(this, _spillOwner).clear();
|
|
5007
|
+
for (const [anchor, members] of __privateGet(this, _spills)) {
|
|
5008
|
+
const newAnchor = shiftCellKey(anchor);
|
|
5009
|
+
if (newAnchor === null) {
|
|
5010
|
+
for (const m of members) {
|
|
5011
|
+
const shifted = shiftCellKey(m);
|
|
5012
|
+
if (!shifted) continue;
|
|
5013
|
+
const cell = parseKey(shifted);
|
|
5014
|
+
__privateGet(this, _model).setCellValue(__privateGet(this, _sheetNames).get(cell.sheet) ?? cell.sheet, cell.ref, null);
|
|
5015
|
+
}
|
|
5016
|
+
continue;
|
|
5017
|
+
}
|
|
5018
|
+
const newMembers = /* @__PURE__ */ new Set();
|
|
5019
|
+
for (const m of members) {
|
|
5020
|
+
const shifted = shiftCellKey(m);
|
|
5021
|
+
if (!shifted) continue;
|
|
5022
|
+
newMembers.add(shifted);
|
|
5023
|
+
__privateGet(this, _spillOwner).set(shifted, newAnchor);
|
|
5024
|
+
}
|
|
5025
|
+
newSpills.set(newAnchor, newMembers);
|
|
5026
|
+
}
|
|
5027
|
+
__privateSet(this, _spills, newSpills);
|
|
5028
|
+
};
|
|
4739
5029
|
// ── Internals ────────────────────────────────────────────────────────────────
|
|
4740
5030
|
/**
|
|
4741
5031
|
* Recomputes formulas affected by the given changed cells, to a fixpoint.
|
|
@@ -4751,13 +5041,17 @@ recalcFrom_fn = function(seeds, includeKey) {
|
|
|
4751
5041
|
const results = [];
|
|
4752
5042
|
const evaluated = /* @__PURE__ */ new Set();
|
|
4753
5043
|
let pending = __privateMethod(this, _RecalcEngine_instances, dependentClosure_fn).call(this, seeds, evaluated);
|
|
4754
|
-
|
|
4755
|
-
if (e.volatile) pending.set(keyOf(e.sheet, e.ref), e);
|
|
4756
|
-
}
|
|
5044
|
+
__privateMethod(this, _RecalcEngine_instances, addVolatiles_fn).call(this, pending);
|
|
4757
5045
|
if (includeKey) {
|
|
4758
5046
|
const self = __privateGet(this, _formulas).get(includeKey);
|
|
4759
5047
|
if (self) pending.set(includeKey, self);
|
|
4760
5048
|
}
|
|
5049
|
+
results.push(...__privateMethod(this, _RecalcEngine_instances, runFixpoint_fn).call(this, pending, evaluated));
|
|
5050
|
+
return results;
|
|
5051
|
+
};
|
|
5052
|
+
/** Evaluates `pending` entries to a fixpoint, following spill-induced changes. */
|
|
5053
|
+
runFixpoint_fn = function(pending, evaluated) {
|
|
5054
|
+
const results = [];
|
|
4761
5055
|
let guard = 0;
|
|
4762
5056
|
const maxIterations = __privateGet(this, _formulas).size + 8;
|
|
4763
5057
|
while (pending.size > 0 && guard++ <= maxIterations) {
|
|
@@ -4768,28 +5062,47 @@ recalcFrom_fn = function(seeds, includeKey) {
|
|
|
4768
5062
|
}
|
|
4769
5063
|
return results;
|
|
4770
5064
|
};
|
|
4771
|
-
/**
|
|
5065
|
+
/** Adds every volatile formula to a pending set. */
|
|
5066
|
+
addVolatiles_fn = function(pending) {
|
|
5067
|
+
for (const key of __privateGet(this, _volatiles)) {
|
|
5068
|
+
const e = __privateGet(this, _formulas).get(key);
|
|
5069
|
+
if (e) pending.set(key, e);
|
|
5070
|
+
}
|
|
5071
|
+
};
|
|
5072
|
+
/**
|
|
5073
|
+
* Formula entries (not yet evaluated) whose precedents include one of the
|
|
5074
|
+
* cells, transitively. The queue is walked with a head index — Array.shift()
|
|
5075
|
+
* is O(n) per dequeue, which turns a long dependency chain quadratic.
|
|
5076
|
+
*/
|
|
4772
5077
|
dependentClosure_fn = function(cells, evaluated) {
|
|
4773
5078
|
const out = /* @__PURE__ */ new Map();
|
|
4774
5079
|
const queue = [...cells];
|
|
4775
|
-
|
|
4776
|
-
const cell = queue
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
out.set(k, dep);
|
|
5080
|
+
for (let head = 0; head < queue.length; head++) {
|
|
5081
|
+
const cell = queue[head];
|
|
5082
|
+
__privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, cell, (dep) => {
|
|
5083
|
+
if (out.has(dep.key) || evaluated.has(dep.key)) return;
|
|
5084
|
+
out.set(dep.key, dep);
|
|
4781
5085
|
queue.push({ sheet: dep.sheet, row: dep.row, col: dep.col });
|
|
4782
|
-
}
|
|
5086
|
+
});
|
|
4783
5087
|
}
|
|
4784
5088
|
return out;
|
|
4785
5089
|
};
|
|
4786
|
-
/**
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
5090
|
+
/**
|
|
5091
|
+
* Visits the formula entries whose precedents include the given cell, via the
|
|
5092
|
+
* reverse index. The index over-approximates (it buckets rectangles into
|
|
5093
|
+
* tiles), so each candidate is still confirmed against its real rectangles.
|
|
5094
|
+
*/
|
|
5095
|
+
forEachDirectDependent_fn = function(cell, visit) {
|
|
5096
|
+
__privateGet(this, _dependents).candidates(cell.sheet, cell.row, cell.col, (key) => {
|
|
5097
|
+
const e = __privateGet(this, _formulas).get(key);
|
|
5098
|
+
if (!e) return;
|
|
5099
|
+
for (const ref of e.refs) {
|
|
5100
|
+
if (refContains(ref, cell.sheet, cell.row, cell.col)) {
|
|
5101
|
+
visit(e);
|
|
5102
|
+
return;
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
});
|
|
4793
5106
|
};
|
|
4794
5107
|
/**
|
|
4795
5108
|
* Topologically sorts the given entries and evaluates them, writing results
|
|
@@ -4798,32 +5111,32 @@ directDependents_fn = function(cell) {
|
|
|
4798
5111
|
*/
|
|
4799
5112
|
evaluatePass_fn = function(entries) {
|
|
4800
5113
|
const inSet = /* @__PURE__ */ new Map();
|
|
4801
|
-
for (const e of entries) inSet.set(
|
|
5114
|
+
for (const e of entries) inSet.set(e.key, e);
|
|
4802
5115
|
const indegree = /* @__PURE__ */ new Map();
|
|
4803
5116
|
const dependents = /* @__PURE__ */ new Map();
|
|
4804
5117
|
for (const k of inSet.keys()) {
|
|
4805
5118
|
indegree.set(k, 0);
|
|
4806
5119
|
dependents.set(k, []);
|
|
4807
5120
|
}
|
|
4808
|
-
for (const
|
|
4809
|
-
const
|
|
4810
|
-
|
|
4811
|
-
if (
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
}
|
|
5121
|
+
for (const pe of entries) {
|
|
5122
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5123
|
+
__privateMethod(this, _RecalcEngine_instances, forEachDirectDependent_fn).call(this, { sheet: pe.sheet, row: pe.row, col: pe.col }, (dep) => {
|
|
5124
|
+
if (dep.key === pe.key || !inSet.has(dep.key) || seen.has(dep.key)) return;
|
|
5125
|
+
seen.add(dep.key);
|
|
5126
|
+
dependents.get(pe.key).push(dep.key);
|
|
5127
|
+
indegree.set(dep.key, indegree.get(dep.key) + 1);
|
|
5128
|
+
});
|
|
4817
5129
|
}
|
|
4818
5130
|
const ready = [];
|
|
4819
5131
|
for (const [k, d] of indegree) if (d === 0) ready.push(k);
|
|
4820
5132
|
const order = [];
|
|
4821
|
-
|
|
4822
|
-
const k = ready
|
|
5133
|
+
for (let head = 0; head < ready.length; head++) {
|
|
5134
|
+
const k = ready[head];
|
|
4823
5135
|
order.push(k);
|
|
4824
5136
|
for (const d of dependents.get(k)) {
|
|
4825
|
-
|
|
4826
|
-
|
|
5137
|
+
const left2 = indegree.get(d) - 1;
|
|
5138
|
+
indegree.set(d, left2);
|
|
5139
|
+
if (left2 === 0) ready.push(d);
|
|
4827
5140
|
}
|
|
4828
5141
|
}
|
|
4829
5142
|
const results = [];
|
|
@@ -4837,7 +5150,7 @@ evaluatePass_fn = function(entries) {
|
|
|
4837
5150
|
}
|
|
4838
5151
|
for (const [k, e] of inSet) {
|
|
4839
5152
|
if (emitted.has(k)) continue;
|
|
4840
|
-
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this,
|
|
5153
|
+
__privateMethod(this, _RecalcEngine_instances, clearSpill_fn).call(this, e.key, changed);
|
|
4841
5154
|
const value2 = FormulaValue.errorRef.toObject();
|
|
4842
5155
|
__privateGet(this, _model).setCellValue(e.sheetName, e.ref, value2);
|
|
4843
5156
|
changed.push({ sheet: e.sheet, row: e.row, col: e.col });
|
|
@@ -4852,7 +5165,7 @@ evaluatePass_fn = function(entries) {
|
|
|
4852
5165
|
* an array shrinks, previously-spilled cells it no longer covers are cleared.
|
|
4853
5166
|
*/
|
|
4854
5167
|
writeResult_fn = function(e) {
|
|
4855
|
-
const anchorKey =
|
|
5168
|
+
const anchorKey = e.key;
|
|
4856
5169
|
const fv2 = __privateMethod(this, _RecalcEngine_instances, evaluateFormulaValue_fn).call(this, e);
|
|
4857
5170
|
const changed = [];
|
|
4858
5171
|
const prevSpill = __privateGet(this, _spills).get(anchorKey) ?? /* @__PURE__ */ new Set();
|
|
@@ -4936,15 +5249,33 @@ clearSpill_fn = function(anchorKey, changed) {
|
|
|
4936
5249
|
__privateGet(this, _spills).delete(anchorKey);
|
|
4937
5250
|
};
|
|
4938
5251
|
evaluateFormulaValue_fn = function(e) {
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
5252
|
+
if (!e.ast) {
|
|
5253
|
+
try {
|
|
5254
|
+
e.ast = parseFormulaUncached(e.formula);
|
|
5255
|
+
} catch {
|
|
5256
|
+
return FormulaValue.errorValue;
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
return evaluateAst(e.ast, __privateMethod(this, _RecalcEngine_instances, sheetLike_fn).call(this, e.sheetName), {
|
|
5260
|
+
workbook: __privateMethod(this, _RecalcEngine_instances, buildWorkbookLike_fn).call(this),
|
|
4943
5261
|
formulaRow: e.row,
|
|
4944
5262
|
formulaCol: e.col
|
|
4945
5263
|
});
|
|
4946
5264
|
};
|
|
5265
|
+
/**
|
|
5266
|
+
* Per-sheet evaluator adapter. Memoised: these hold no per-formula state, and
|
|
5267
|
+
* rebuilding one (with its four closures) for every formula evaluated made a
|
|
5268
|
+
* full recalc allocate several objects per cell.
|
|
5269
|
+
*/
|
|
4947
5270
|
sheetLike_fn = function(sheetName) {
|
|
5271
|
+
let cached = __privateGet(this, _sheetLikes).get(sheetName);
|
|
5272
|
+
if (!cached) {
|
|
5273
|
+
cached = __privateMethod(this, _RecalcEngine_instances, buildSheetLike_fn).call(this, sheetName);
|
|
5274
|
+
__privateGet(this, _sheetLikes).set(sheetName, cached);
|
|
5275
|
+
}
|
|
5276
|
+
return cached;
|
|
5277
|
+
};
|
|
5278
|
+
buildSheetLike_fn = function(sheetName) {
|
|
4948
5279
|
return {
|
|
4949
5280
|
name: sheetName,
|
|
4950
5281
|
getCellValue: (ref) => __privateGet(this, _model).getCellValue(sheetName, ref),
|
|
@@ -4954,8 +5285,9 @@ sheetLike_fn = function(sheetName) {
|
|
|
4954
5285
|
};
|
|
4955
5286
|
};
|
|
4956
5287
|
buildWorkbookLike_fn = function() {
|
|
5288
|
+
if (__privateGet(this, _workbookLike)) return __privateGet(this, _workbookLike);
|
|
4957
5289
|
const self = this;
|
|
4958
|
-
return {
|
|
5290
|
+
return __privateSet(this, _workbookLike, {
|
|
4959
5291
|
get worksheets() {
|
|
4960
5292
|
return [...__privateGet(self, _sheetNames).values()].map((n) => {
|
|
4961
5293
|
var _a;
|
|
@@ -4967,9 +5299,9 @@ buildWorkbookLike_fn = function() {
|
|
|
4967
5299
|
return __privateMethod(_a = self, _RecalcEngine_instances, sheetLike_fn).call(_a, name);
|
|
4968
5300
|
},
|
|
4969
5301
|
getDefinedName: __privateGet(this, _model).getDefinedName ? (name, sheet) => __privateGet(this, _model).getDefinedName(name, sheet) : void 0
|
|
4970
|
-
};
|
|
5302
|
+
});
|
|
4971
5303
|
};
|
|
4972
5304
|
|
|
4973
|
-
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, evaluateFormula, extractReferences, getAstCacheStats, parseFormula, refContains, registerAggregate, registerArray, registerDate, registerFinance, registerInfo, registerLogical, registerLookup, registerMath, registerMeta, registerStatistical, registerText, setAstCacheCapacity };
|
|
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 };
|
|
4974
5306
|
//# sourceMappingURL=index.js.map
|
|
4975
5307
|
//# sourceMappingURL=index.js.map
|