@devmm/puredocs-excel-formula 1.0.0 → 1.0.1

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.d.cts CHANGED
@@ -10,7 +10,32 @@ declare const enum FormulaError {
10
10
  Calc = 8,
11
11
  Spill = 9
12
12
  }
13
- type FormulaValueKind = 'blank' | 'number' | 'text' | 'boolean' | 'error' | 'array';
13
+ type FormulaValueKind = 'blank' | 'number' | 'text' | 'boolean' | 'error' | 'array' | 'lambda';
14
+ /**
15
+ * A callable value produced by LAMBDA. Kept as an opaque interface so
16
+ * formula-value.ts stays decoupled from the AST/context modules.
17
+ */
18
+ interface LambdaLike {
19
+ /** Number of declared parameters. */
20
+ readonly arity: number;
21
+ /** Invokes the lambda with the given argument values (runs to completion). */
22
+ call(args: FormulaValue[]): FormulaValue;
23
+ /**
24
+ * Runs the lambda body ONCE in tail-aware mode. Returns either the final value
25
+ * or a TailCall describing the next tail call, so a trampoline can iterate
26
+ * without growing the JS stack. Optional; call() is the fallback.
27
+ */
28
+ step?(args: FormulaValue[]): FormulaValue | TailCall;
29
+ }
30
+ /**
31
+ * A pending tail call produced by evaluating a node in tail position. The
32
+ * trampoline in a lambda's call() loops on these instead of recursing.
33
+ */
34
+ declare class TailCall {
35
+ readonly lambda: LambdaLike;
36
+ readonly args: FormulaValue[];
37
+ constructor(lambda: LambdaLike, args: FormulaValue[]);
38
+ }
14
39
  declare class FormulaValue {
15
40
  #private;
16
41
  readonly kind: FormulaValueKind;
@@ -34,17 +59,20 @@ declare class FormulaValue {
34
59
  static boolean(v: boolean): FormulaValue;
35
60
  static error(e: FormulaError): FormulaValue;
36
61
  static array(a: ArrayValue): FormulaValue;
62
+ static lambda(fn: LambdaLike): FormulaValue;
37
63
  get isBlank(): boolean;
38
64
  get isNumber(): boolean;
39
65
  get isText(): boolean;
40
66
  get isBoolean(): boolean;
41
67
  get isError(): boolean;
42
68
  get isArray(): boolean;
69
+ get isLambda(): boolean;
43
70
  get isNumeric(): boolean;
44
71
  get numberValue(): number;
45
72
  get textValue(): string;
46
73
  get booleanValue(): boolean;
47
74
  get arrayVal(): ArrayValue;
75
+ get lambdaVal(): LambdaLike;
48
76
  coerceToNumber(): FormulaValue;
49
77
  tryAsDouble(): {
50
78
  ok: true;
@@ -121,10 +149,11 @@ declare const enum TokenType {
121
149
  GreaterThanOrEqual = 21,
122
150
  SheetReference = 22,
123
151
  NamedRange = 23,
124
- Exclamation = 24,
125
- AtSign = 25,
126
- ErrorLiteral = 26,
127
- EOF = 27
152
+ SpillReference = 24,
153
+ Exclamation = 25,
154
+ AtSign = 26,
155
+ ErrorLiteral = 27,
156
+ EOF = 28
128
157
  }
129
158
  interface Token {
130
159
  readonly type: TokenType;
@@ -220,10 +249,25 @@ declare namespace FormulaHelper {
220
249
  interface WorksheetLike {
221
250
  getCellValue(reference: string): unknown;
222
251
  name: string;
252
+ /** Optional: whether a 1-based row is hidden (used by SUBTOTAL/AGGREGATE). */
253
+ isRowHidden?(row: number): boolean;
254
+ /** Optional: formula text of a cell (used by SUBTOTAL to skip nested calls). */
255
+ getCellFormula?(reference: string): string | null;
256
+ /** Optional: spill range (e.g. "A1:A3") of a dynamic-array anchor, for A1#. */
257
+ getSpillRange?(reference: string): string | null;
223
258
  }
224
259
  interface WorkbookLike {
225
260
  worksheets: readonly WorksheetLike[];
226
261
  getWorksheet(name: string): WorksheetLike;
262
+ /**
263
+ * Optional resolver for defined names (named ranges). Returns the refers-to
264
+ * expression (without leading '='), following Excel scoping: a name scoped to
265
+ * `sheetName` wins over the workbook-global name. When this method is absent
266
+ * (or returns undefined), named-range references evaluate to #NAME?.
267
+ *
268
+ * @devmm/puredocs-excel's Workbook implements this.
269
+ */
270
+ getDefinedName?(name: string, sheetName?: string): string | undefined;
227
271
  }
228
272
  declare class FormulaContext {
229
273
  #private;
@@ -233,19 +277,46 @@ declare class FormulaContext {
233
277
  formulaCol: number;
234
278
  /** Optional workbook reference for cross-sheet references. */
235
279
  workbook?: WorkbookLike;
280
+ /**
281
+ * Optional named-range resolver, installed by the evaluator when the workbook
282
+ * exposes getDefinedName. Given a name, returns its evaluated value. Left
283
+ * undefined when no resolver is available, in which case named ranges are
284
+ * #NAME? (unchanged legacy behaviour).
285
+ */
286
+ nameResolver?: (name: string) => FormulaValue;
287
+ /**
288
+ * Shared recursion budget for LAMBDA calls, threaded across all contexts in a
289
+ * single evaluation. Guards non-tail recursion (which grows the JS stack);
290
+ * tail recursion is trampolined and does not count against it.
291
+ */
292
+ recursionGuard?: {
293
+ depth: number;
294
+ max: number;
295
+ };
296
+ /** Pushes a new binding frame (used by LET/LAMBDA). */
297
+ pushScope(bindings: Map<string, FormulaValue>): void;
298
+ /** Pops the innermost binding frame. */
299
+ popScope(): void;
236
300
  constructor(sheet: WorksheetLike, formulaRow?: number, formulaCol?: number, evaluating?: Set<string>, registry?: FunctionRegistry);
237
301
  get worksheet(): WorksheetLike;
238
302
  getCellValue(cellReference: string): FormulaValue;
239
303
  getRangeValues(startRef: string, endRef: string): FormulaValue;
240
304
  getSheetCellValue(sheetName: string, cellReference: string): FormulaValue;
241
305
  getSheetRangeValues(sheetName: string, startRef: string, endRef: string): FormulaValue;
242
- resolveNamedRange(_name: string): FormulaValue;
306
+ resolveNamedRange(name: string): FormulaValue;
307
+ /**
308
+ * Resolves a spill-range reference (A1#) to the array spilling from the anchor
309
+ * cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
310
+ */
311
+ resolveSpillRange(anchorRef: string): FormulaValue;
243
312
  getRangeBounds(startRef: string, endRef: string): {
244
313
  startRow: number;
245
314
  startCol: number;
246
315
  endRow: number;
247
316
  endCol: number;
248
317
  };
318
+ /** True if a built-in function with this name is registered. */
319
+ hasFunction(name: string): boolean;
249
320
  evaluateFunction(name: string, args: FormulaNode[]): FormulaValue;
250
321
  }
251
322
 
@@ -256,6 +327,13 @@ declare class FormulaContext {
256
327
 
257
328
  declare abstract class FormulaNode {
258
329
  abstract evaluate(ctx: FormulaContext): FormulaValue;
330
+ /**
331
+ * Evaluates this node in tail position. Returns a TailCall when the node's
332
+ * value is a call to a lambda in tail position (so the caller's trampoline can
333
+ * iterate without growing the stack); otherwise the final value. The default
334
+ * is a plain evaluate — only control-flow and call nodes override it.
335
+ */
336
+ evaluateTail(ctx: FormulaContext): FormulaValue | TailCall;
259
337
  }
260
338
  declare class NumberNode extends FormulaNode {
261
339
  readonly value: number;
@@ -310,6 +388,12 @@ declare class NamedRangeNode extends FormulaNode {
310
388
  constructor(name: string);
311
389
  evaluate(ctx: FormulaContext): FormulaValue;
312
390
  }
391
+ /** Spill-range reference (A1#): the array that spills from an anchor cell. */
392
+ declare class SpillReferenceNode extends FormulaNode {
393
+ readonly anchorRef: string;
394
+ constructor(anchorRef: string);
395
+ evaluate(ctx: FormulaContext): FormulaValue;
396
+ }
313
397
  declare const enum BinaryOperator {
314
398
  Add = 0,
315
399
  Subtract = 1,
@@ -354,6 +438,18 @@ declare class FunctionCallNode extends FormulaNode {
354
438
  readonly args: FormulaNode[];
355
439
  constructor(functionName: string, args: FormulaNode[]);
356
440
  evaluate(ctx: FormulaContext): FormulaValue;
441
+ evaluateTail(ctx: FormulaContext): FormulaValue | TailCall;
442
+ }
443
+ /**
444
+ * Calls the value produced by an expression (e.g. an inline LAMBDA:
445
+ * `LAMBDA(x,x+1)(5)`). The callee must evaluate to a lambda, else #VALUE!.
446
+ */
447
+ declare class CallNode extends FormulaNode {
448
+ readonly callee: FormulaNode;
449
+ readonly args: FormulaNode[];
450
+ constructor(callee: FormulaNode, args: FormulaNode[]);
451
+ evaluate(ctx: FormulaContext): FormulaValue;
452
+ evaluateTail(ctx: FormulaContext): FormulaValue | TailCall;
357
453
  }
358
454
 
359
455
  /**
@@ -438,6 +534,129 @@ declare class LruCache<K, V> {
438
534
  resize(newCapacity: number): void;
439
535
  }
440
536
 
537
+ /** A rectangular precedent region on a specific sheet (1-based, inclusive). */
538
+ interface PrecedentRef {
539
+ /** Upper-cased sheet name. */
540
+ readonly sheet: string;
541
+ readonly startRow: number;
542
+ readonly startCol: number;
543
+ readonly endRow: number;
544
+ readonly endCol: number;
545
+ }
546
+ interface ExtractResult {
547
+ readonly refs: PrecedentRef[];
548
+ readonly volatile: boolean;
549
+ }
550
+ /** Resolves a defined name to its refers-to expression, for name expansion. */
551
+ type NameResolver = (name: string, sheet: string) => string | undefined;
552
+ /**
553
+ * Extracts the precedents and volatility of a formula AST.
554
+ *
555
+ * @param ast The parsed formula.
556
+ * @param formulaSheet Upper-cased name of the sheet the formula lives on;
557
+ * unqualified references resolve against it.
558
+ * @param registry Function registry used to test volatility.
559
+ * @param resolveName Optional resolver so NamedRangeNode precedents expand to
560
+ * the references inside their refers-to expression.
561
+ */
562
+ declare function extractReferences(ast: FormulaNode, formulaSheet: string, registry?: FunctionRegistry, resolveName?: NameResolver): ExtractResult;
563
+ /** True if the 1-based cell (sheet,row,col) falls inside a precedent region. */
564
+ declare function refContains(ref: PrecedentRef, sheet: string, row: number, col: number): boolean;
565
+
566
+ interface CoreCellLike {
567
+ setComputedValue(value: unknown): unknown;
568
+ setFormula(formula: string): unknown;
569
+ setArrayRef(spillRef: string | null): unknown;
570
+ clear(): void;
571
+ }
572
+ interface CoreSheetLike {
573
+ getCellValue(ref: string): unknown;
574
+ getCell(ref: string): CoreCellLike;
575
+ getCellFormula?(ref: string): string | null;
576
+ getSpillRange?(ref: string): string | null;
577
+ isRowHidden?(row: number): boolean;
578
+ }
579
+ interface CoreWorkbookLike {
580
+ getWorksheet(name: string): CoreSheetLike;
581
+ getDefinedName(name: string, sheet?: string): string | undefined;
582
+ }
583
+ /**
584
+ * Adapts a @devmm/puredocs-excel Workbook to a RecalcModel so the engine can
585
+ * read cell values and write computed results back into the document (via
586
+ * Cell.setComputedValue, which preserves the formula). Named ranges defined on
587
+ * the workbook are resolved automatically.
588
+ */
589
+ declare function createWorkbookRecalcModel(workbook: CoreWorkbookLike): RecalcModel;
590
+ /** Minimal storage abstraction the engine reads inputs from and writes results to. */
591
+ interface RecalcModel {
592
+ /** Current value of a cell (raw input value, or last computed value). */
593
+ getCellValue(sheet: string, ref: string): unknown;
594
+ /** Store a computed formula result. Must NOT clear the cell's formula. */
595
+ setCellValue(sheet: string, ref: string, value: unknown): void;
596
+ /**
597
+ * Optional: persist a formula string on a cell so it serialises with the
598
+ * document. When present, the engine calls it from setCellFormula so the
599
+ * model stays the source of truth for the formula text.
600
+ */
601
+ setCellFormula?(sheet: string, ref: string, formula: string): void;
602
+ /** Optional: clear a cell (value + formula) — used by clearCellFormula. */
603
+ clearCell?(sheet: string, ref: string): void;
604
+ /** Optional: mark/unmark a cell as a dynamic-array anchor with its spill range. */
605
+ setCellArrayRef?(sheet: string, ref: string, spillRef: string | null): void;
606
+ /** Optional: formula text of a cell (lets SUBTOTAL skip nested calls). */
607
+ getCellFormula?(sheet: string, ref: string): string | null;
608
+ /** Optional: spill range of a dynamic-array anchor (enables A1#). */
609
+ getSpillRange?(sheet: string, ref: string): string | null;
610
+ /** Optional: whether a 1-based row is hidden (SUBTOTAL/AGGREGATE). */
611
+ isRowHidden?(sheet: string, row: number): boolean;
612
+ /** Optional defined-name resolver (enables named ranges in formulas). */
613
+ getDefinedName?(name: string, sheet?: string): string | undefined;
614
+ }
615
+ /** One recomputed cell, in evaluation order. */
616
+ interface RecalcResult {
617
+ readonly sheet: string;
618
+ readonly ref: string;
619
+ /** The written JS value (null/number/string/boolean). For a dynamic array,
620
+ * this is the anchor's top-left value. */
621
+ readonly value: unknown;
622
+ /** True when the cell is part of a circular reference. */
623
+ readonly circular?: boolean;
624
+ /** For a dynamic-array anchor: the spilled block size (rows × cols). */
625
+ readonly spill?: {
626
+ rows: number;
627
+ cols: number;
628
+ };
629
+ }
630
+ declare class RecalcEngine {
631
+ #private;
632
+ constructor(model: RecalcModel, registry?: FunctionRegistry);
633
+ /**
634
+ * Registers (or replaces) a formula for a cell and recalculates that cell and
635
+ * everything downstream of it. Returns the recomputed cells in the order they
636
+ * were evaluated. The leading '=' of the formula is optional.
637
+ */
638
+ setCellFormula(sheet: string, ref: string, formula: string): RecalcResult[];
639
+ /**
640
+ * Removes a formula from a cell and recalculates its dependents (which may now
641
+ * read a plain input value instead). Returns the recomputed dependent cells.
642
+ */
643
+ clearCellFormula(sheet: string, ref: string): RecalcResult[];
644
+ /**
645
+ * Writes a raw input value to a cell and recalculates every formula that
646
+ * depends on it. Use this for non-formula edits so dependents stay current.
647
+ */
648
+ setCellValue(sheet: string, ref: string, value: unknown): RecalcResult[];
649
+ /**
650
+ * Signals that a cell changed by some external means (the model was already
651
+ * updated) and recalculates its dependents.
652
+ */
653
+ onCellChanged(sheet: string, ref: string): RecalcResult[];
654
+ /** Recalculates every registered formula in dependency order. */
655
+ recalcAll(): RecalcResult[];
656
+ /** The precedents recorded for a formula cell (mainly for tests/inspection). */
657
+ getPrecedents(sheet: string, ref: string): readonly PrecedentRef[];
658
+ }
659
+
441
660
  declare function registerMath(r: FunctionRegistry): void;
442
661
 
443
662
  declare function registerText(r: FunctionRegistry): void;
@@ -452,4 +671,12 @@ declare function registerStatistical(r: FunctionRegistry): void;
452
671
 
453
672
  declare function registerInfo(r: FunctionRegistry): void;
454
673
 
455
- export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CellReferenceNode, ErrorNode, FormulaContext, FormulaError, FormulaException, type FormulaFunction, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, type FormulaValueKind, FunctionCallNode, type FunctionDef, FunctionRegistry, ImplicitIntersectionNode, LruCache, NamedRangeNode, NumberNode, RangeReferenceNode, SheetCellReferenceNode, SheetRangeReferenceNode, StringNode, type Token, TokenType, UnaryOpNode, UnaryOperator, type WorkbookLike, type WorksheetLike, clearAstCache, evaluateFormula, getAstCacheStats, parseFormula, registerDate, registerInfo, registerLogical, registerLookup, registerMath, registerStatistical, registerText, setAstCacheCapacity };
674
+ declare function registerFinance(r: FunctionRegistry): void;
675
+
676
+ declare function registerMeta(r: FunctionRegistry): void;
677
+
678
+ declare function registerAggregate(r: FunctionRegistry): void;
679
+
680
+ declare function registerArray(r: FunctionRegistry): void;
681
+
682
+ export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CallNode, CellReferenceNode, ErrorNode, type ExtractResult, FormulaContext, FormulaError, FormulaException, type FormulaFunction, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, type FormulaValueKind, FunctionCallNode, type FunctionDef, FunctionRegistry, ImplicitIntersectionNode, type LambdaLike, LruCache, type NameResolver, NamedRangeNode, NumberNode, type PrecedentRef, RangeReferenceNode, RecalcEngine, type RecalcModel, type RecalcResult, SheetCellReferenceNode, SheetRangeReferenceNode, SpillReferenceNode, StringNode, type Token, TokenType, UnaryOpNode, UnaryOperator, type WorkbookLike, type WorksheetLike, clearAstCache, createWorkbookRecalcModel, evaluateFormula, extractReferences, getAstCacheStats, parseFormula, refContains, registerAggregate, registerArray, registerDate, registerFinance, registerInfo, registerLogical, registerLookup, registerMath, registerMeta, registerStatistical, registerText, setAstCacheCapacity };
package/dist/index.d.ts CHANGED
@@ -10,7 +10,32 @@ declare const enum FormulaError {
10
10
  Calc = 8,
11
11
  Spill = 9
12
12
  }
13
- type FormulaValueKind = 'blank' | 'number' | 'text' | 'boolean' | 'error' | 'array';
13
+ type FormulaValueKind = 'blank' | 'number' | 'text' | 'boolean' | 'error' | 'array' | 'lambda';
14
+ /**
15
+ * A callable value produced by LAMBDA. Kept as an opaque interface so
16
+ * formula-value.ts stays decoupled from the AST/context modules.
17
+ */
18
+ interface LambdaLike {
19
+ /** Number of declared parameters. */
20
+ readonly arity: number;
21
+ /** Invokes the lambda with the given argument values (runs to completion). */
22
+ call(args: FormulaValue[]): FormulaValue;
23
+ /**
24
+ * Runs the lambda body ONCE in tail-aware mode. Returns either the final value
25
+ * or a TailCall describing the next tail call, so a trampoline can iterate
26
+ * without growing the JS stack. Optional; call() is the fallback.
27
+ */
28
+ step?(args: FormulaValue[]): FormulaValue | TailCall;
29
+ }
30
+ /**
31
+ * A pending tail call produced by evaluating a node in tail position. The
32
+ * trampoline in a lambda's call() loops on these instead of recursing.
33
+ */
34
+ declare class TailCall {
35
+ readonly lambda: LambdaLike;
36
+ readonly args: FormulaValue[];
37
+ constructor(lambda: LambdaLike, args: FormulaValue[]);
38
+ }
14
39
  declare class FormulaValue {
15
40
  #private;
16
41
  readonly kind: FormulaValueKind;
@@ -34,17 +59,20 @@ declare class FormulaValue {
34
59
  static boolean(v: boolean): FormulaValue;
35
60
  static error(e: FormulaError): FormulaValue;
36
61
  static array(a: ArrayValue): FormulaValue;
62
+ static lambda(fn: LambdaLike): FormulaValue;
37
63
  get isBlank(): boolean;
38
64
  get isNumber(): boolean;
39
65
  get isText(): boolean;
40
66
  get isBoolean(): boolean;
41
67
  get isError(): boolean;
42
68
  get isArray(): boolean;
69
+ get isLambda(): boolean;
43
70
  get isNumeric(): boolean;
44
71
  get numberValue(): number;
45
72
  get textValue(): string;
46
73
  get booleanValue(): boolean;
47
74
  get arrayVal(): ArrayValue;
75
+ get lambdaVal(): LambdaLike;
48
76
  coerceToNumber(): FormulaValue;
49
77
  tryAsDouble(): {
50
78
  ok: true;
@@ -121,10 +149,11 @@ declare const enum TokenType {
121
149
  GreaterThanOrEqual = 21,
122
150
  SheetReference = 22,
123
151
  NamedRange = 23,
124
- Exclamation = 24,
125
- AtSign = 25,
126
- ErrorLiteral = 26,
127
- EOF = 27
152
+ SpillReference = 24,
153
+ Exclamation = 25,
154
+ AtSign = 26,
155
+ ErrorLiteral = 27,
156
+ EOF = 28
128
157
  }
129
158
  interface Token {
130
159
  readonly type: TokenType;
@@ -220,10 +249,25 @@ declare namespace FormulaHelper {
220
249
  interface WorksheetLike {
221
250
  getCellValue(reference: string): unknown;
222
251
  name: string;
252
+ /** Optional: whether a 1-based row is hidden (used by SUBTOTAL/AGGREGATE). */
253
+ isRowHidden?(row: number): boolean;
254
+ /** Optional: formula text of a cell (used by SUBTOTAL to skip nested calls). */
255
+ getCellFormula?(reference: string): string | null;
256
+ /** Optional: spill range (e.g. "A1:A3") of a dynamic-array anchor, for A1#. */
257
+ getSpillRange?(reference: string): string | null;
223
258
  }
224
259
  interface WorkbookLike {
225
260
  worksheets: readonly WorksheetLike[];
226
261
  getWorksheet(name: string): WorksheetLike;
262
+ /**
263
+ * Optional resolver for defined names (named ranges). Returns the refers-to
264
+ * expression (without leading '='), following Excel scoping: a name scoped to
265
+ * `sheetName` wins over the workbook-global name. When this method is absent
266
+ * (or returns undefined), named-range references evaluate to #NAME?.
267
+ *
268
+ * @devmm/puredocs-excel's Workbook implements this.
269
+ */
270
+ getDefinedName?(name: string, sheetName?: string): string | undefined;
227
271
  }
228
272
  declare class FormulaContext {
229
273
  #private;
@@ -233,19 +277,46 @@ declare class FormulaContext {
233
277
  formulaCol: number;
234
278
  /** Optional workbook reference for cross-sheet references. */
235
279
  workbook?: WorkbookLike;
280
+ /**
281
+ * Optional named-range resolver, installed by the evaluator when the workbook
282
+ * exposes getDefinedName. Given a name, returns its evaluated value. Left
283
+ * undefined when no resolver is available, in which case named ranges are
284
+ * #NAME? (unchanged legacy behaviour).
285
+ */
286
+ nameResolver?: (name: string) => FormulaValue;
287
+ /**
288
+ * Shared recursion budget for LAMBDA calls, threaded across all contexts in a
289
+ * single evaluation. Guards non-tail recursion (which grows the JS stack);
290
+ * tail recursion is trampolined and does not count against it.
291
+ */
292
+ recursionGuard?: {
293
+ depth: number;
294
+ max: number;
295
+ };
296
+ /** Pushes a new binding frame (used by LET/LAMBDA). */
297
+ pushScope(bindings: Map<string, FormulaValue>): void;
298
+ /** Pops the innermost binding frame. */
299
+ popScope(): void;
236
300
  constructor(sheet: WorksheetLike, formulaRow?: number, formulaCol?: number, evaluating?: Set<string>, registry?: FunctionRegistry);
237
301
  get worksheet(): WorksheetLike;
238
302
  getCellValue(cellReference: string): FormulaValue;
239
303
  getRangeValues(startRef: string, endRef: string): FormulaValue;
240
304
  getSheetCellValue(sheetName: string, cellReference: string): FormulaValue;
241
305
  getSheetRangeValues(sheetName: string, startRef: string, endRef: string): FormulaValue;
242
- resolveNamedRange(_name: string): FormulaValue;
306
+ resolveNamedRange(name: string): FormulaValue;
307
+ /**
308
+ * Resolves a spill-range reference (A1#) to the array spilling from the anchor
309
+ * cell. Requires the worksheet to expose getSpillRange; otherwise #REF!.
310
+ */
311
+ resolveSpillRange(anchorRef: string): FormulaValue;
243
312
  getRangeBounds(startRef: string, endRef: string): {
244
313
  startRow: number;
245
314
  startCol: number;
246
315
  endRow: number;
247
316
  endCol: number;
248
317
  };
318
+ /** True if a built-in function with this name is registered. */
319
+ hasFunction(name: string): boolean;
249
320
  evaluateFunction(name: string, args: FormulaNode[]): FormulaValue;
250
321
  }
251
322
 
@@ -256,6 +327,13 @@ declare class FormulaContext {
256
327
 
257
328
  declare abstract class FormulaNode {
258
329
  abstract evaluate(ctx: FormulaContext): FormulaValue;
330
+ /**
331
+ * Evaluates this node in tail position. Returns a TailCall when the node's
332
+ * value is a call to a lambda in tail position (so the caller's trampoline can
333
+ * iterate without growing the stack); otherwise the final value. The default
334
+ * is a plain evaluate — only control-flow and call nodes override it.
335
+ */
336
+ evaluateTail(ctx: FormulaContext): FormulaValue | TailCall;
259
337
  }
260
338
  declare class NumberNode extends FormulaNode {
261
339
  readonly value: number;
@@ -310,6 +388,12 @@ declare class NamedRangeNode extends FormulaNode {
310
388
  constructor(name: string);
311
389
  evaluate(ctx: FormulaContext): FormulaValue;
312
390
  }
391
+ /** Spill-range reference (A1#): the array that spills from an anchor cell. */
392
+ declare class SpillReferenceNode extends FormulaNode {
393
+ readonly anchorRef: string;
394
+ constructor(anchorRef: string);
395
+ evaluate(ctx: FormulaContext): FormulaValue;
396
+ }
313
397
  declare const enum BinaryOperator {
314
398
  Add = 0,
315
399
  Subtract = 1,
@@ -354,6 +438,18 @@ declare class FunctionCallNode extends FormulaNode {
354
438
  readonly args: FormulaNode[];
355
439
  constructor(functionName: string, args: FormulaNode[]);
356
440
  evaluate(ctx: FormulaContext): FormulaValue;
441
+ evaluateTail(ctx: FormulaContext): FormulaValue | TailCall;
442
+ }
443
+ /**
444
+ * Calls the value produced by an expression (e.g. an inline LAMBDA:
445
+ * `LAMBDA(x,x+1)(5)`). The callee must evaluate to a lambda, else #VALUE!.
446
+ */
447
+ declare class CallNode extends FormulaNode {
448
+ readonly callee: FormulaNode;
449
+ readonly args: FormulaNode[];
450
+ constructor(callee: FormulaNode, args: FormulaNode[]);
451
+ evaluate(ctx: FormulaContext): FormulaValue;
452
+ evaluateTail(ctx: FormulaContext): FormulaValue | TailCall;
357
453
  }
358
454
 
359
455
  /**
@@ -438,6 +534,129 @@ declare class LruCache<K, V> {
438
534
  resize(newCapacity: number): void;
439
535
  }
440
536
 
537
+ /** A rectangular precedent region on a specific sheet (1-based, inclusive). */
538
+ interface PrecedentRef {
539
+ /** Upper-cased sheet name. */
540
+ readonly sheet: string;
541
+ readonly startRow: number;
542
+ readonly startCol: number;
543
+ readonly endRow: number;
544
+ readonly endCol: number;
545
+ }
546
+ interface ExtractResult {
547
+ readonly refs: PrecedentRef[];
548
+ readonly volatile: boolean;
549
+ }
550
+ /** Resolves a defined name to its refers-to expression, for name expansion. */
551
+ type NameResolver = (name: string, sheet: string) => string | undefined;
552
+ /**
553
+ * Extracts the precedents and volatility of a formula AST.
554
+ *
555
+ * @param ast The parsed formula.
556
+ * @param formulaSheet Upper-cased name of the sheet the formula lives on;
557
+ * unqualified references resolve against it.
558
+ * @param registry Function registry used to test volatility.
559
+ * @param resolveName Optional resolver so NamedRangeNode precedents expand to
560
+ * the references inside their refers-to expression.
561
+ */
562
+ declare function extractReferences(ast: FormulaNode, formulaSheet: string, registry?: FunctionRegistry, resolveName?: NameResolver): ExtractResult;
563
+ /** True if the 1-based cell (sheet,row,col) falls inside a precedent region. */
564
+ declare function refContains(ref: PrecedentRef, sheet: string, row: number, col: number): boolean;
565
+
566
+ interface CoreCellLike {
567
+ setComputedValue(value: unknown): unknown;
568
+ setFormula(formula: string): unknown;
569
+ setArrayRef(spillRef: string | null): unknown;
570
+ clear(): void;
571
+ }
572
+ interface CoreSheetLike {
573
+ getCellValue(ref: string): unknown;
574
+ getCell(ref: string): CoreCellLike;
575
+ getCellFormula?(ref: string): string | null;
576
+ getSpillRange?(ref: string): string | null;
577
+ isRowHidden?(row: number): boolean;
578
+ }
579
+ interface CoreWorkbookLike {
580
+ getWorksheet(name: string): CoreSheetLike;
581
+ getDefinedName(name: string, sheet?: string): string | undefined;
582
+ }
583
+ /**
584
+ * Adapts a @devmm/puredocs-excel Workbook to a RecalcModel so the engine can
585
+ * read cell values and write computed results back into the document (via
586
+ * Cell.setComputedValue, which preserves the formula). Named ranges defined on
587
+ * the workbook are resolved automatically.
588
+ */
589
+ declare function createWorkbookRecalcModel(workbook: CoreWorkbookLike): RecalcModel;
590
+ /** Minimal storage abstraction the engine reads inputs from and writes results to. */
591
+ interface RecalcModel {
592
+ /** Current value of a cell (raw input value, or last computed value). */
593
+ getCellValue(sheet: string, ref: string): unknown;
594
+ /** Store a computed formula result. Must NOT clear the cell's formula. */
595
+ setCellValue(sheet: string, ref: string, value: unknown): void;
596
+ /**
597
+ * Optional: persist a formula string on a cell so it serialises with the
598
+ * document. When present, the engine calls it from setCellFormula so the
599
+ * model stays the source of truth for the formula text.
600
+ */
601
+ setCellFormula?(sheet: string, ref: string, formula: string): void;
602
+ /** Optional: clear a cell (value + formula) — used by clearCellFormula. */
603
+ clearCell?(sheet: string, ref: string): void;
604
+ /** Optional: mark/unmark a cell as a dynamic-array anchor with its spill range. */
605
+ setCellArrayRef?(sheet: string, ref: string, spillRef: string | null): void;
606
+ /** Optional: formula text of a cell (lets SUBTOTAL skip nested calls). */
607
+ getCellFormula?(sheet: string, ref: string): string | null;
608
+ /** Optional: spill range of a dynamic-array anchor (enables A1#). */
609
+ getSpillRange?(sheet: string, ref: string): string | null;
610
+ /** Optional: whether a 1-based row is hidden (SUBTOTAL/AGGREGATE). */
611
+ isRowHidden?(sheet: string, row: number): boolean;
612
+ /** Optional defined-name resolver (enables named ranges in formulas). */
613
+ getDefinedName?(name: string, sheet?: string): string | undefined;
614
+ }
615
+ /** One recomputed cell, in evaluation order. */
616
+ interface RecalcResult {
617
+ readonly sheet: string;
618
+ readonly ref: string;
619
+ /** The written JS value (null/number/string/boolean). For a dynamic array,
620
+ * this is the anchor's top-left value. */
621
+ readonly value: unknown;
622
+ /** True when the cell is part of a circular reference. */
623
+ readonly circular?: boolean;
624
+ /** For a dynamic-array anchor: the spilled block size (rows × cols). */
625
+ readonly spill?: {
626
+ rows: number;
627
+ cols: number;
628
+ };
629
+ }
630
+ declare class RecalcEngine {
631
+ #private;
632
+ constructor(model: RecalcModel, registry?: FunctionRegistry);
633
+ /**
634
+ * Registers (or replaces) a formula for a cell and recalculates that cell and
635
+ * everything downstream of it. Returns the recomputed cells in the order they
636
+ * were evaluated. The leading '=' of the formula is optional.
637
+ */
638
+ setCellFormula(sheet: string, ref: string, formula: string): RecalcResult[];
639
+ /**
640
+ * Removes a formula from a cell and recalculates its dependents (which may now
641
+ * read a plain input value instead). Returns the recomputed dependent cells.
642
+ */
643
+ clearCellFormula(sheet: string, ref: string): RecalcResult[];
644
+ /**
645
+ * Writes a raw input value to a cell and recalculates every formula that
646
+ * depends on it. Use this for non-formula edits so dependents stay current.
647
+ */
648
+ setCellValue(sheet: string, ref: string, value: unknown): RecalcResult[];
649
+ /**
650
+ * Signals that a cell changed by some external means (the model was already
651
+ * updated) and recalculates its dependents.
652
+ */
653
+ onCellChanged(sheet: string, ref: string): RecalcResult[];
654
+ /** Recalculates every registered formula in dependency order. */
655
+ recalcAll(): RecalcResult[];
656
+ /** The precedents recorded for a formula cell (mainly for tests/inspection). */
657
+ getPrecedents(sheet: string, ref: string): readonly PrecedentRef[];
658
+ }
659
+
441
660
  declare function registerMath(r: FunctionRegistry): void;
442
661
 
443
662
  declare function registerText(r: FunctionRegistry): void;
@@ -452,4 +671,12 @@ declare function registerStatistical(r: FunctionRegistry): void;
452
671
 
453
672
  declare function registerInfo(r: FunctionRegistry): void;
454
673
 
455
- export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CellReferenceNode, ErrorNode, FormulaContext, FormulaError, FormulaException, type FormulaFunction, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, type FormulaValueKind, FunctionCallNode, type FunctionDef, FunctionRegistry, ImplicitIntersectionNode, LruCache, NamedRangeNode, NumberNode, RangeReferenceNode, SheetCellReferenceNode, SheetRangeReferenceNode, StringNode, type Token, TokenType, UnaryOpNode, UnaryOperator, type WorkbookLike, type WorksheetLike, clearAstCache, evaluateFormula, getAstCacheStats, parseFormula, registerDate, registerInfo, registerLogical, registerLookup, registerMath, registerStatistical, registerText, setAstCacheCapacity };
674
+ declare function registerFinance(r: FunctionRegistry): void;
675
+
676
+ declare function registerMeta(r: FunctionRegistry): void;
677
+
678
+ declare function registerAggregate(r: FunctionRegistry): void;
679
+
680
+ declare function registerArray(r: FunctionRegistry): void;
681
+
682
+ export { ArrayValue, BinaryOpNode, BinaryOperator, BlankNode, BooleanNode, CallNode, CellReferenceNode, ErrorNode, type ExtractResult, FormulaContext, FormulaError, FormulaException, type FormulaFunction, FormulaHelper, FormulaLexer, FormulaNode, FormulaParser, FormulaValue, type FormulaValueKind, FunctionCallNode, type FunctionDef, FunctionRegistry, ImplicitIntersectionNode, type LambdaLike, LruCache, type NameResolver, NamedRangeNode, NumberNode, type PrecedentRef, RangeReferenceNode, RecalcEngine, type RecalcModel, type RecalcResult, SheetCellReferenceNode, SheetRangeReferenceNode, SpillReferenceNode, StringNode, type Token, TokenType, UnaryOpNode, UnaryOperator, type WorkbookLike, type WorksheetLike, clearAstCache, createWorkbookRecalcModel, evaluateFormula, extractReferences, getAstCacheStats, parseFormula, refContains, registerAggregate, registerArray, registerDate, registerFinance, registerInfo, registerLogical, registerLookup, registerMath, registerMeta, registerStatistical, registerText, setAstCacheCapacity };