/** * Frame types for the trampoline evaluator. * * Each frame type represents a continuation — "what to do with the result of a * sub-evaluation." When the trampoline evaluates a sub-expression, it pushes a * frame onto the continuation stack (k). When the sub-expression produces a * value, the trampoline pops the frame and calls `applyFrame(frame, value, k)` * to determine the next step. * * All frame types are plain serializable objects — no functions, no closures. * This enables continuation serialization in Phase 4 (suspension & resume). * * The `env` field uses `ContextStack` for Phase 1 runtime use. In Phase 4, * this will be replaced with a serializable representation (the `Context[]` * chain without host bindings, which are re-injected on resume). */ import type { Any, Arr, Obj } from '../interface'; import type { DvalaModule } from '../builtin/modules/interface'; import type { BindingSlot } from '../builtin/bindingSlot'; import type { MatchSlot } from '../builtin/matchSlot'; import type { AstNode, BindingTarget, FunctionLike, HandlerFunction, NormalExpressionNode, UserDefinedFunction } from '../parser/types'; import type { MatchCase } from '../builtin/specialExpressions/match'; import type { LoopBindingNode } from '../builtin/specialExpressions/loops'; import type { SourceCodeInfo } from '../tokenizer/token'; import type { ContextStack } from './ContextStack'; import type { Snapshot } from './effectTypes'; import type { Context } from './interface'; /** * Evaluate a sequence of AST nodes in order, returning the last value. * * Used by: `do...end` (block), top-level program evaluation, function body. * * The trampoline evaluates `nodes[index]`. When the value comes back it * advances `index`. When all nodes are done, the last value propagates up. */ export interface SequenceFrame { type: 'Sequence'; nodes: AstNode[]; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Conditional branch (`if`). * * Pushed when the condition expression is being evaluated. When the condition * value arrives, the trampoline picks `thenNode` or `elseNode` (or returns * `null` if there is no else branch). */ export interface IfBranchFrame { type: 'IfBranch'; thenNode: AstNode; elseNode: AstNode | undefined; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Pattern matching (`match`). * * Phase `'matchValue'`: the match-value expression is being evaluated. * Phase `'guard'`: a pattern matched and boundary bindings were created; * the guard expression is being evaluated. * Phase `'body'`: the body for the matched case is being evaluated. * * `matchValue` is `null` during the `'matchValue'` phase and set once known. * `bindings` holds the names captured by `tryMatch` (empty until a pattern * matches). */ export interface MatchFrame { type: 'Match'; phase: 'matchValue' | 'guard' | 'body'; matchValue: Any | null; cases: MatchCase[]; index: number; bindings: Record; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Short-circuit `&&` — evaluates nodes sequentially, returning the first * falsy value or the last value if all are truthy. */ export interface AndFrame { type: 'And'; nodes: AstNode[]; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Short-circuit `||` — evaluates nodes sequentially, returning the first * truthy value or the last value if all are falsy. */ export interface OrFrame { type: 'Or'; nodes: AstNode[]; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Nullish coalescing `??` — evaluates nodes sequentially, returning the * first non-null value. Undefined user-defined symbols are treated as null * (skipped without throwing `UndefinedSymbolError`). */ export interface QqFrame { type: 'Qq'; nodes: AstNode[]; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Template string construction — evaluates interpolated segments sequentially * and concatenates them into a string result. * * Structurally identical to ArrayBuildFrame but accumulates a string * via String() coercion instead of an array. */ export interface TemplateStringBuildFrame { type: 'TemplateStringBuild'; segments: AstNode[]; index: number; result: string; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Array literal construction (`[]` / `array`). * * Evaluates elements sequentially. Spread nodes (`...expr`) evaluate the * inner expression and flatten the resulting array into `result`. */ export interface ArrayBuildFrame { type: 'ArrayBuild'; nodes: AstNode[]; index: number; result: Arr; isSpread: boolean; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Object literal construction (`{}` / `object`). * * Evaluates entries sequentially. Each entry is either a `[keyNode, valueNode]` * pair or a `SpreadNode`. For pairs, evaluates key then value. For spreads, * evaluates the spread expression and merges the result into `result`. * * `currentKey` holds the evaluated key string when we're between key and * value evaluation within a pair (null otherwise). */ export interface ObjectBuildFrame { type: 'ObjectBuild'; entries: (AstNode[] | AstNode)[]; index: number; result: Obj; currentKey: string | null; isSpread: boolean; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `let` binding — evaluate the value expression, then process destructuring. * * The trampoline evaluates the value expression. When it completes, * `applyFrame` processes `evaluateBindingNodeValues(target, value, ...)` * and adds the resulting bindings to the context. The result of the let * expression is the evaluated value itself. */ export interface LetBindFrame { type: 'LetBind'; target: BindingTarget; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `let` binding completion — receive destructured record and add to env. * * After `BindingSlotFrame` completes processing all slots, this frame * receives the resulting record, adds it to the environment, and returns * the original value (the RHS of the let expression). */ export interface LetBindCompleteFrame { type: 'LetBindComplete'; originalValue: Any; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `loop` binding setup — evaluate binding values sequentially. * * Each binding's value expression is evaluated in a context that includes * all previously bound values (bindings can depend on earlier bindings). * * Phase `'value'`: evaluating a binding's value expression. * Phase `'destructure'`: value evaluated, processing destructuring defaults. */ export interface LoopBindFrame { type: 'LoopBind'; phase: 'value' | 'destructure'; bindings: [BindingTarget, AstNode][]; index: number; context: Context; body: AstNode; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `loop` body iteration — evaluate body with `recur` handling. * * In the trampoline, `recur` does NOT throw `RecurSignal`. Instead, when * recur args are collected, the trampoline pops back to this frame, rebinds * the variables, and re-evaluates the body. This gives proper tail-call * elimination without stack growth. */ export interface LoopIterateFrame { type: 'LoopIterate'; bindings: [BindingTarget, AstNode][]; bindingContext: Context; body: AstNode; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `loop` binding completion — receive destructured record and add to context. * * After `BindingSlotFrame` completes processing all slots, this frame * receives the resulting record, adds it to the loop context, and either * continues to the next binding or starts the loop body. */ export interface LoopBindCompleteFrame { type: 'LoopBindComplete'; bindings: [BindingTarget, AstNode][]; index: number; context: Context; body: AstNode; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * State for a single binding level in a `for` loop. * * Each level iterates over a `collection`. Inner-level collections are * re-evaluated when outer-level bindings change. */ export interface ForBindingLevelState { collection: Arr; index: number; } /** * `for` multi-binding nested iteration. * * Multi-level nested loop with optional let-bindings, when-guards, and * while-guards at each binding level. Collects body results into an array. * * Phase describes what sub-expression is currently being evaluated: * - `'evalCollection'`: evaluating the collection expression for a level * - `'evalWhen'`: evaluating the when-guard * - `'evalWhile'`: evaluating the while-guard * - `'evalBody'`: evaluating the loop body */ export interface ForLoopFrame { type: 'ForLoop'; bindingNodes: LoopBindingNode[]; body: AstNode; result: Arr; phase: 'evalCollection' | 'evalWhen' | 'evalWhile' | 'evalBody'; bindingLevel: number; levelStates: ForBindingLevelState[]; context: Context; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `for` element binding completion. * * After `BindingSlotFrame` completes processing element destructuring, * this frame receives the resulting record, adds it to the context, * and continues with let-bindings or guards. */ export interface ForElementBindCompleteFrame { type: 'ForElementBindComplete'; forFrame: ForLoopFrame; levelStates: ForBindingLevelState[]; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `for` let-binding evaluation. * * Evaluates let-bindings at the current level sequentially. * Each let-binding's value is evaluated, then destructured. */ export interface ForLetBindFrame { type: 'ForLetBind'; phase: 'evalValue' | 'destructure'; forFrame: ForLoopFrame; levelStates: ForBindingLevelState[]; letBindings: [BindingTarget, AstNode][]; letIndex: number; currentValue?: Any; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `perform` argument collection — evaluate effect ref + args sequentially. * * First evaluates the effect expression (index 0) to get an EffectRef. * Then evaluates each argument expression. When all are collected, produces * a `PerformStep` with the resolved EffectRef and argument values. */ export interface PerformArgsFrame { type: 'PerformArgs'; argNodes: AstNode[]; index: number; params: Arr; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * `recur` — evaluate parameters sequentially, then signal tail-call. * * In the trampoline, instead of throwing `RecurSignal`, the completed * recur frame pops the continuation stack to the nearest `LoopIterateFrame` * or `FnBodyFrame`, rebinds parameters, and re-enters the loop/body. * This eliminates the exception-based control flow used by the recursive * evaluator. */ export interface RecurFrame { type: 'Recur'; nodes: AstNode[]; index: number; params: Arr; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Handle recur's loop rebinding using slot-based binding. * * When recur is called inside a loop, each binding node needs to be * rebound to the new param value. This frame tracks progress through * the binding nodes. * * After binding completes for one node, the record is merged into * bindingContext and we move to the next node. */ export interface RecurLoopRebindFrame { type: 'RecurLoopRebind'; bindings: [BindingTarget, AstNode][]; bindingIndex: number; params: Arr; bindingContext: Context; body: AstNode; env: ContextStack; remainingK: ContinuationStack; sourceCodeInfo?: SourceCodeInfo; } /** * Algebraic effect handler boundary (new system). * * Installed by `h(-> body)` where `h` is a HandlerFunction value. * When `perform` fires, the effect name is looked up in the handler's clauseMap. * If found, the clause body runs with `resume` bound in scope. * If not found, the effect propagates to the next handler on the stack. * * On normal body completion, the transform clause is applied (if present). * On abort (clause returns without calling resume), transform is bypassed. */ /** * Cleanup callback registered by a host effect handler via * `ctx.onScopeExit`. Fires in LIFO order when the enclosing * `AlgebraicHandleFrame` terminally exits. * * `effectName` records which host effect registered this cleanup, * so the runtime's restriction error messages can say which * resources are held (e.g. "2 × file.open, 1 × db.connect"). */ export interface CleanupEntry { callback: () => void | Promise; effectName: string; } export interface AlgebraicHandleFrame { type: 'AlgebraicHandle'; handler: HandlerFunction; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; /** * Host-registered cleanup callbacks. Accumulated via * `ctx.onScopeExit` inside host effect handlers whose effect * propagated into this frame's scope. Fires in LIFO on * terminal exit (normal completion, abort, or snapshot discard). * * When non-empty, the frame is "resource-holding" and the * runtime refuses snapshot capture (see design doc * `design/archive/2026-04-19_host-scoped-resources.md`). * * Stored as a mutable array deliberately — `onScopeExit` pushes * to this list from the host-handler's synchronous call path, * and per-frame identity is preserved because `AlgebraicHandleFrame` * values are never deeply copied during normal evaluation (only * referenced from the continuation stack). */ cleanups?: CleanupEntry[]; /** * Set to true once this frame's cleanups have fired (via * HandlerCleanupFrame discharge). Continuations captured from * within this frame become invalid at that point — attempting to * invoke them after discharge is the multi-shot restriction * described in the design doc. */ cleanupsFired?: boolean; } /** * Runs `cleanups` in LIFO after a handler frame has terminally * exited. Inserted into the continuation by * `applyAlgebraicHandleNormalCompletion` and `applyHandlerClauseAbort` * so the cleanups fire after the user-visible result is computed * (post-transform for normal completion; post-abort-value for * abort paths). */ export interface HandlerCleanupFrame { type: 'HandlerCleanup'; cleanups: CleanupEntry[]; /** The AlgebraicHandleFrame these cleanups belong to; marked `cleanupsFired` * once this frame's callbacks have drained so the multi-shot restriction * can detect continuations re-entering a discharged frame. */ handleFrame: AlgebraicHandleFrame; sourceCodeInfo?: SourceCodeInfo; } /** * Transform application frame for the new handler system. * * Pushed when: * 1. Body completes normally — applies the handler's transform clause * 2. `resume(value)` is called — the reinstalled handler's normal completion * goes through transform, and the result is what `resume` returns * * The transform clause's param is bound to `value`, and the body is evaluated. * If no transform exists, this frame is not pushed (identity transform). */ export interface HandlerTransformFrame { type: 'HandlerTransform'; handler: HandlerFunction; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Clause execution frame for the new handler system. * * When a handler clause matches a performed effect: * 1. The clause body runs with params bound + `resume` in scope * 2. If `resume` is called: continuation runs (handler reinstalled, deep semantics) * 3. If `resume` is NOT called: clause result becomes the handle block result (abort) * * When the clause body completes (via `applyHandlerClauseAbort`), the result * propagates past the enclosing AlgebraicHandleFrame regardless of whether * `resume` was called. Multi-shot is safe because this frame is never mutated * after being pushed onto the continuation stack. */ export interface HandlerClauseFrame { type: 'HandlerClause'; /** Continuation from the perform site to the AlgebraicHandleFrame. * Used by resume to continue execution with the handler reinstalled. */ performK: ContinuationStack; /** The handler to reinstall on resume (deep handler semantics). */ handler: HandlerFunction; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Setup frame for `with h;` — evaluates the handler expression, * then pushes an AlgebraicHandleFrame and evaluates the body. * * When the handler value arrives, this frame pushes AlgebraicHandleFrame * and starts body evaluation as a sequence — no function boundary, * preserving `recur` behavior. */ export interface WithHandlerSetupFrame { type: 'WithHandlerSetup'; bodyExprs: AstNode[]; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Collects the evaluated argument for `resume(value)` and dispatches * the resume function call. */ export interface ResumeCallFrame { type: 'ResumeCall'; resumeFn: Any; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Resume a `parallel(...)` expression after suspension. * * When a `parallel` has some branches that suspended and some that completed, * the continuation is suspended with this frame at the top. On resume, the * host provides a value for the first pending suspended branch. * * `applyFrame` converts this to a `ParallelResumeStep` so that `tick` can * handle it with access to `handlers` and `signal`. * * Fields: * - `branchCount`: total number of branches (for ordered result array) * - `completedBranches`: branches that already finished `{ index, value }` * - `suspendedBranches`: remaining suspended branches `{ index, snapshot }` * The first entry is the one being resumed — its snapshot is NOT used because * the value was already provided by the host. Subsequent entries are pending. */ export interface ParallelResumeFrame { type: 'ParallelResume'; branchCount: number; completedBranches: { index: number; value: unknown; }[]; suspendedBranches: { index: number; snapshot: Snapshot; }[]; } /** * Context describing which parallel/race expression a branch belongs to. * Carried by the `ParallelBranchBarrierFrame` during live execution, and * used to construct `ReRunParallelFrame` or `ResumeParallelFrame` during * checkpoint serialization and final suspension composition. */ /** * Intermediate frame that evaluates the array argument of parallel/race/settled. * After the value arrives (should be an array of functions), creates branches * and dispatches to the concurrent execution machinery. */ export interface ConcurrentArgFrame { type: 'ConcurrentArg'; mode: 'parallel' | 'race' | 'settled'; env: ContextStack; } export interface ParallelBranchContext { /** Index of this branch within the parallel/race expression */ branchIndex: number; /** Total number of branches */ branchCount: number; /** Function values for ALL branches (needed for re-run on resume). Typed as unknown[] * to avoid circular dependency — actual values are DvalaFunction instances. */ branches: unknown[]; /** Environment at the parallel/race/settled call site */ env: ContextStack; /** Result collection strategy: parallel collects all, race picks first */ mode: 'parallel' | 'race' | 'settled'; } /** * Barrier frame sitting between a branch continuation and the outer program. * * Three roles: * 1. **Completion sentinel**: when the trampoline hits this with a value, * the branch is complete — returns a `BranchComplete` step instead of * flowing into outerK. * 2. **Effect boundary**: `dispatchPerform` and `tryDispatchDvalaError` stop * walking `k` at this frame, preserving effect isolation between branches * and the outer scope. This prevents algebraic effects from inside a branch * propagating to outer handlers through outerK. * 3. **Context carrier**: holds `branchCtx` for checkpoint composition — * checkpoint serialization replaces this frame with a `ReRunParallelFrame`. * * Never serialized directly — always replaced before serialization. */ export interface ParallelBranchBarrierFrame { type: 'ParallelBranchBarrier'; branchCtx: ParallelBranchContext; } /** * Replaces `ParallelBranchBarrierFrame` in serialized mid-execution checkpoints. * * When a checkpoint is taken inside a running branch (Tier 1), siblings are * still running concurrently and cannot be snapshotted. On resume, this frame * re-runs all sibling branches from their original AST. * * On resume: the branch continues from its `branchK`. When the value reaches * this frame, it re-evaluates all other branches from scratch, collects results * (parallel: array in order, race: first wins), and continues with outerK. */ export interface ReRunParallelFrame { type: 'ReRunParallel'; branchIndex: number; branchCount: number; /** Function values for all branches */ branches: unknown[]; /** Environment at the parallel/race/settled call site */ env: ContextStack; mode: 'parallel' | 'race' | 'settled'; } /** * Replaces `ParallelBranchBarrierFrame` in the serialized final suspension. * * When all branches have settled (completed, errored, or force-suspended), * we know their full state. On resume, this frame resumes suspended siblings * from their abort-point continuations and uses completed siblings' cached values. * * Sibling continuations are stored **truncated at the BarrierFrame** — the * barrier and outerK tail are stripped because outerK is the same as the * continuation after this frame. On resume, each sibling gets a fresh * BarrierFrame + outerK reconstructed from the frame's context. */ export interface ResumeParallelFrame { type: 'ResumeParallel'; branchIndex: number; branchCount: number; /** Function values for all branches */ branches: unknown[]; /** Environment at the parallel/race/settled call site */ env: ContextStack; completedBranches: { index: number; value: unknown; }[]; /** Sibling continuations truncated at the BarrierFrame (no barrier or outerK tail), * plus captured effect info for re-triggering on resume */ suspendedBranches: { index: number; k: ContinuationStack; effectName?: string; effectArg?: Any; }[]; mode: 'parallel' | 'race' | 'settled'; } /** * Evaluate function call arguments. * * Evaluates argument expressions sequentially, collecting results into * `params`. Handles spread nodes (flatten arrays) and placeholder `_` * nodes (record indices for partial application). * * `fnNode` is the first element of the `NormalExpressionNode` payload — * either a symbol node (for named calls) or an expression node (for * anonymous calls like `((fn [x] x) 5)`). * * When all arguments are collected: * - Named builtin symbol → dispatch to builtin's evaluate * - Named user symbol → look up value, push `CallFnFrame`, dispatch * - Anonymous expression → push `CallFnFrame`, evaluate function expression */ export interface EvalArgsFrame { type: 'EvalArgs'; node: NormalExpressionNode; index: number; params: Arr; placeholders: number[]; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Dispatch a function call after the function value has been resolved. * * Pushed when an anonymous function expression needs to be evaluated before * calling, or when a compound function type (Comp, Juxt, etc.) needs to * chain sub-calls. Receives the resolved function value and dispatches * using `executeFunction`. */ export interface CallFnFrame { type: 'CallFn'; fnName?: string; params: Arr; placeholders: number[]; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * User-defined function body evaluation. * * After parameter setup (binding destructuring, defaults, rest args), the * function body is evaluated as a sequence. This frame also serves as the * target for `recur` — when recur params are collected, the trampoline * pops to this frame, rebinds parameters, and re-evaluates the body. * * `fn` is stored for recur handling: the parameter definitions * (`fn.evaluatedfunction[0]`) and captured environment * (`fn.evaluatedfunction[2]`) are needed to rebind. */ export interface FnBodyFrame { type: 'FnBody'; fn: UserDefinedFunction; bodyIndex: number; env: ContextStack; outerEnv: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Function argument binding — incrementally bind parameters with defaults. * * When calling a user-defined function, parameters are bound to arguments. * If an argument has a default value that needs evaluation, this frame * captures the binding state so it can resume after the default is evaluated. * * Phases: * - `'default'`: Evaluating a default value expression. When complete, * `applyFrame` continues binding with the evaluated value. * - `'rest-default'`: Evaluating a default in rest argument destructuring. * * The frame stores: * - `fn`: The function being called (for its parameter definitions) * - `params`: Original call arguments (needed for rest calculation) * - `argIndex`: Which argument we're currently binding (0-based) * - `context`: Accumulated bindings so far * - `outerEnv`: The calling environment (for body evaluation later) */ export interface FnArgBindFrame { type: 'FnArgBind'; phase: 'default' | 'rest-default'; fn: UserDefinedFunction; params: Arr; argIndex: number; context: Context; outerEnv: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Frame for completing one function argument's slot-based binding. * * After `startBindingSlots` finishes binding one argument, this frame * merges the result into the accumulated context and continues with * the next argument (or body if all args are bound). * * Fields: * - `fn`: The user-defined function being called * - `params`: Original call params (for calculating rest) * - `argIndex`: Which argument just completed binding * - `nbrOfNonRestArgs`: Total non-rest args count * - `context`: Accumulated bindings (will be mutated) * - `outerEnv`: Calling environment */ export interface FnArgSlotCompleteFrame { type: 'FnArgSlotComplete'; fn: UserDefinedFunction; params: Arr; argIndex: number; nbrOfNonRestArgs: number; context: Context; outerEnv: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Frame for completing rest argument slot-based binding. * * After `startBindingSlots` finishes binding the rest argument, this frame * merges the bindings into the context and proceeds to evaluate the body. */ export interface FnRestArgCompleteFrame { type: 'FnRestArgComplete'; fn: UserDefinedFunction; context: Context; outerEnv: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Linearized binding slot processing frame. * * Used for frame-based destructuring without callbacks. The binding pattern * is pre-flattened into a linear list of slots, and this frame tracks * sequential processing through those slots. * * When a slot has a default that needs evaluation: * 1. Push this frame with current state * 2. Evaluate the default expression * 3. Resume: store evaluated value, continue to next slot * * Nested bindings with intermediate defaults (e.g., `{ a: { b } = default }`) * are handled via the `contexts` stack. When encountering such a slot: * 1. Resolve the intermediate value (from extraction or default) * 2. Push a new context for the nested structure * 3. Process nested slots, then pop and continue with parent * * Fields: * - `contexts`: Stack of binding contexts (last is current) * - `record`: Accumulated name→value bindings (shared across all contexts) */ export interface BindingSlotContext { slots: BindingSlot[]; index: number; rootValue: Any; } export interface BindingSlotFrame { type: 'BindingSlot'; contexts: BindingSlotContext[]; record: Record; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Context for pattern matching slot processing. * Tracks position within a flattened match pattern. */ export interface MatchSlotContext { slots: MatchSlot[]; index: number; rootValue: Any; } /** * Frame for evaluating pattern match slots. * * Similar to BindingSlotFrame but supports: * - Match failure (pattern doesn't match) * - Literal evaluation and comparison * - Type checking at each path * * When a slot needs evaluation (literal or default), this frame is pushed * and the node is evaluated. On resume: * - For literals: compare result with value; if mismatch, fail * - For defaults: bind the evaluated value and continue * * Fields: * - `contexts`: Stack of match contexts for nested patterns * - `record`: Accumulated bindings so far * - `matchFrame`: The parent MatchFrame to resume on failure or success * - `phase`: 'literal' when evaluating a literal for comparison, * 'default' when evaluating a default value * - `currentSlot`: The slot being processed (for reference after eval) * - `env`: Environment for evaluation */ export interface MatchSlotFrame { type: 'MatchSlot'; contexts: MatchSlotContext[]; record: Record; matchFrame: MatchFrame; phase: 'literal' | 'default'; currentSlot: MatchSlot; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Complement function wrapper — negates the result of the wrapped function. * * Created by `(complement fn)`. When the wrapped function returns, * this frame applies `!` to the result. */ export interface ComplementFrame { type: 'Complement'; sourceCodeInfo?: SourceCodeInfo; } /** * Comp function iteration — chains function calls right-to-left. * * `(comp f g h)` called with `x` evaluates as `f(g(h(x)))`. * We iterate from right to left (index starts at len-1, decrements). * Each step wraps the result in an array for the next function call. */ export interface CompFrame { type: 'Comp'; fns: Arr; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Juxt function iteration — calls each function with same params, collects results. * * `(juxt f g h)` called with `x` returns `[f(x), g(x), h(x)]`. * Each step adds the result to the accumulated array. */ export interface JuxtFrame { type: 'Juxt'; fns: Arr; params: Arr; index: number; results: Arr; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * EveryPred function iteration — short-circuit AND across predicates. * * `(every-pred p1 p2)` returns a function that returns true iff all * predicates return truthy for all arguments. * Precomputes all (fn, param) pairs and iterates with early exit on falsy. */ export interface EveryPredFrame { type: 'EveryPred'; checks: { fn: FunctionLike; param: Any; }[]; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * SomePred function iteration — short-circuit OR across predicates. * * `(some-pred p1 p2)` returns a function that returns true if any * predicate returns truthy for any argument. * Precomputes all (fn, param) pairs and iterates with early exit on truthy. */ export interface SomePredFrame { type: 'SomePred'; checks: { fn: FunctionLike; param: Any; }[]; index: number; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } /** * Finite-number guard after evaluating a normal expression. * * Normal expressions go through a finiteness check: if the result is `NaN` * or `Infinity`, an `ArithmeticError` is thrown. This frame wraps that check. */ export interface FiniteCheckFrame { type: 'FiniteCheck'; sourceCodeInfo?: SourceCodeInfo; } /** * Discriminated union of all frame types. * * Each frame captures the continuation state for one recursive call pattern * in the evaluator. The `type` field serves as the discriminant. * * Frame categories: * - **Program flow**: SequenceFrame * - **Branching**: IfBranchFrame, MatchFrame * - **Short-circuit**: AndFrame, OrFrame, QqFrame * - **Collection construction**: ArrayBuildFrame, ObjectBuildFrame * - **Binding**: LetBindFrame, LoopBindFrame, LoopIterateFrame, ForLoopFrame * - **Control flow**: RecurFrame * - **Exception & effect handling**: AlgebraicHandleFrame * - **Function calls**: EvalArgsFrame, CallFnFrame, FnBodyFrame * - **Destructuring**: FnArgBindFrame, BindingSlotFrame, MatchSlotFrame * - **Post-processing**: FiniteCheckFrame */ /** * Merges the result of evaluating a module's Dvala source with the module's * TypeScript functions. The source must evaluate to an object; its entries * are spread over the TS functions map and the combined object is returned * as the import result. The merged result is cached in `env` so subsequent * imports of the same module reuse the evaluated closures. */ export interface ImportMergeFrame { type: 'ImportMerge'; tsFunctions: Obj; moduleName: string; module: DvalaModule; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } export interface FileResolveFrame { type: 'FileResolve'; moduleName: string; previousFileDir: string; env: ContextStack; } export type Frame = SequenceFrame | IfBranchFrame | MatchFrame | AndFrame | OrFrame | QqFrame | TemplateStringBuildFrame | ArrayBuildFrame | ObjectBuildFrame | LetBindFrame | LetBindCompleteFrame | LoopBindFrame | LoopBindCompleteFrame | LoopIterateFrame | ForLoopFrame | ForElementBindCompleteFrame | ForLetBindFrame | RecurFrame | RecurLoopRebindFrame | PerformArgsFrame | AlgebraicHandleFrame | HandlerTransformFrame | HandlerClauseFrame | HandlerCleanupFrame | ResumeCallFrame | WithHandlerSetupFrame | ComplementFrame | CompFrame | JuxtFrame | EveryPredFrame | SomePredFrame | ParallelResumeFrame | ConcurrentArgFrame | ParallelBranchBarrierFrame | ReRunParallelFrame | ResumeParallelFrame | EvalArgsFrame | CallFnFrame | FnBodyFrame | FnArgBindFrame | FnArgSlotCompleteFrame | FnRestArgCompleteFrame | BindingSlotFrame | MatchSlotFrame | FiniteCheckFrame | ImportMergeFrame | FileResolveFrame | MacroEvalFrame | CodeTemplateBuildFrame; /** * Macro expansion result (`macro`). * * Pushed when a macro function call returns its result (new AST). * The trampoline evaluates the returned AST in the original calling scope. */ export interface MacroEvalFrame { type: 'MacroEval'; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; /** When true, the frame is a pass-through marker for error location only. */ expanded?: boolean; } /** * Code template splice collection (``` ```.```). * * Evaluates splice expressions sequentially. When all are collected, * walks the body AST and replaces Splice nodes with the evaluated values, * returning the assembled AST as plain Dvala data. */ export interface CodeTemplateBuildFrame { type: 'CodeTemplateBuild'; bodyAst: AstNode[]; spliceExprs: AstNode[]; index: number; values: Any[]; renameMap: Map; env: ContextStack; sourceCodeInfo?: SourceCodeInfo; } import type { PersistentList } from '../utils/persistent/PersistentList'; /** * The continuation stack — a persistent linked list of frames. * Head = top of stack (next frame to apply). Tail = rest of continuation. * * PersistentList gives O(1) push (cons) and pop (tail), and O(1) forking: * to take a multi-shot continuation, just keep the reference. Two resumptions * from the same snapshot share the same immutable list structure. */ export type ContinuationStack = PersistentList;