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