/** * Evaluator — Execute BoundExpr using the RuntimeValue system. * * The evaluator operates on BoundExpr (from the compile phase), * WorkbookSnapshot (from the snapshot phase), and RuntimeValue * (the value system). */ import type { BoundExpr } from "../compile/bound-ast.js"; import type { CompiledFormula } from "../compile/compiled-formula.js"; import type { WorkbookSnapshot } from "../integration/workbook-snapshot.js"; import type { FunctionDescriptor } from "./function-registry.js"; import type { RuntimeValue } from "./values.js"; /** * Cached formula evaluation result with both scalar and raw forms. * The scalar form is used by dependent formulas (implicit intersection applied). * The raw form preserves the full array for dynamic array / CSE materialization. */ interface CachedResult { /** Scalar (implicit-intersected, dereferenced) result. */ readonly scalar: RuntimeValue; /** Raw (full array) result. Only differs from scalar for array formulas. */ readonly raw: RuntimeValue; } /** * Per-calculation mutable state. */ export declare class EvalSession { /** Cells currently on the evaluation call stack (circular-ref detection). */ readonly evaluating: Set; /** * Unified formula result cache. * Each entry holds both the scalar form (for dependents) and the raw form * (for materialize). This replaces the previous separate cache/rawCache * pattern with a single, self-documenting structure. */ readonly resultCache: Map; /** Cache for runtime name resolution (defined names that need parsing). */ readonly nameCache: Map; /** Fallback values for circular references during iterative calculation. */ readonly circularFallback: Map; /** * Live spill map: cell key → (masterKey, row-offset, col-offset). * * Populated as soon as a dynamic-array formula is evaluated and yields * an array result. Downstream formulas that read a cell inside the * spill region look the master's cached array up via this map and * return the correct element — even before materialize has written * the ghost cells to the snapshot. * * This is the fix for "first-pass `=SUM(A1:A5)` over a `=SEQUENCE(5)` * spill" — without the live map, `getCellValue("S", 2, 1)` returned * BLANK and SUM only counted the master cell. */ readonly liveSpills: Map; /** * Runtime dependency recorder — tracks cell accesses made during evaluation. * * When a formula with `hasDynamicRefs` (INDIRECT/OFFSET) is being evaluated, * every `getCellValue` / `buildRangeArray` call records the accessed cell/range * key here. After evaluation, these dynamic edges can be merged with the * compiled static dependency set to produce a complete dependency graph. * * Key: formula cell key being evaluated → Set of accessed cell keys. * Only populated for formulas that have `hasDynamicRefs === true`. */ readonly dynamicDeps: Map>; /** * The formula cell key currently being recorded (null if recording is off). * Set before evaluating a formula with dynamic refs, cleared after. */ recordingKey: string | null; /** * Current LAMBDA invocation depth. Guards against unbounded recursion * (e.g. `LAMBDA(x, x(x))(LAMBDA(x, x(x)))`) that would otherwise overflow * the JS call stack. Excel documents a recursion limit of ~256. */ lambdaDepth: number; /** * AST cache for INDIRECT re-parsing. INDIRECT receives a runtime string * describing a reference; re-parsing it per invocation would be wasted * work, so we memoise the `bound` expression keyed on the reference * text. This belongs to the session (per-calculation lifetime) rather * than the snapshot because the bindings depend on the evaluation * context. */ readonly indirectAstCache: Map; makeKey(sheet: string, row: number, col: number): string; /** * Record a cell access for the currently-recording formula. */ recordAccess(sheet: string, row: number, col: number): void; } /** * The evaluation context. Carries the snapshot and compiled formula map * for the evaluator to access cell values and resolve names at runtime. * Short-lived per-calculation state (caches, iteration flags, etc.) lives * on `EvalSession` instead. */ export interface EvalContext { /** The workbook snapshot. */ readonly snapshot: WorkbookSnapshot; /** Map from formula cell key to CompiledFormula. */ readonly compiledFormulas: ReadonlyMap; /** The current sheet name (for relative references). */ currentSheet: string; /** Current cell address being evaluated. */ currentAddress?: { sheet: string; row: number; col: number; }; /** Local variable bindings from LET expressions. */ localBindings?: Map; /** * User-registered functions that take precedence over the built-in * registry. Lookup happens in `evaluateCall` — a matching * descriptor here shadows any built-in of the same name. Keys are * canonical uppercase names (prefix-stripped on register). */ readonly userFunctions?: ReadonlyMap; } /** * Evaluate a BoundExpr to produce a RuntimeValue. */ export declare function evaluate(expr: BoundExpr, ctx: EvalContext, session: EvalSession): RuntimeValue; /** * Evaluate a compiled formula and return its **scalar** result. * * This is the standard evaluation path for regular (non-array) formulas. * The result is: * 1. Evaluated from the bound expression tree * 2. Implicit-intersected to a single value/reference * 3. Dereferenced if it's a reference * 4. Cached for subsequent lookups by dependent formulas * * Use `evaluateFormulaRaw` instead when the full array result is needed * (dynamic array formulas, CSE formulas). */ export declare function evaluateFormula(compiled: CompiledFormula, ctx: EvalContext, session: EvalSession): RuntimeValue; /** * Evaluate a compiled formula and return the **raw** (possibly array) result. * * This is the evaluation path for dynamic array and CSE formulas where * the full array shape must be preserved for spill/distribution. * * Semantics: * - Both scalar and raw forms are stored in `session.resultCache` as a * `CachedResult{scalar, raw}`. Dependent scalar formulas see the scalar * form; the materialize layer retrieves the raw form. * - The return value is the full dereferenced result — may be an ArrayValue * with height > 1 or width > 1. */ export declare function evaluateFormulaRaw(compiled: CompiledFormula, ctx: EvalContext, session: EvalSession): RuntimeValue; export declare function implicitIntersect(val: RuntimeValue, ctx: EvalContext): RuntimeValue; export {};