/** * Shared structural traversal for the unified {@link Expression} AST. * * Before this module existed, the recursion over an expression's children was * hand-coded in every analysis (`isRollfree`, free-variable collection, * constant folding, formatting, …). Adding an AST node meant hunting down each * copy, and the copies silently drifted (e.g. some walked `comprehension.by`, * some did not). This module is the single place that knows the child shape of * each node: * * - {@link mapChildren} / {@link mapStatementChildren} — structural rewrites * (returns a new node with each child replaced by `f(child)`). * - {@link forEachChild} — side-effecting visit of each child. * - {@link foldChildren} — accumulate a value across children. * - {@link everyChild} / {@link someChild} — short-circuiting predicates. * * All of the read-only helpers visit exactly the same children that * {@link mapChildren} rewrites, so an analysis built on {@link foldChildren} * can never disagree with a rewrite built on {@link mapChildren} about what a * node's children are. * * ## Scope-blindness contract * * This layer is intentionally SCOPE-BLIND: it descends into the bodies of * `comprehension-expr` / `fold-expr` / `repeat-expr` without tracking the * binders those nodes introduce. It is therefore safe for transformations and * analyses that depend only on syntactic structure (constant folding, * commutative re-sorting, the `isRollfree` predicate, …). An analysis whose * result depends on *which binding* a `variable-ref` resolves to MUST NOT use * this layer to recurse into binder bodies; it has to mirror an explicit * push/pop scope walk (see `renameExpr` / `freeVarsExpr` in * `program-canonical.ts`). * * ## Dice nodes are leaves * * The dice atoms (`die`, `n-dice`, `custom-die`, `structured-dice-roll`, * `dice-reduce`) are treated as opaque leaves here: their inner * `DiceReduceable` / `DiceFilterable` / modifier-argument expressions are NOT * visited. Passes that need to rewrite dice internals run a dedicated dice * pass alongside this one (see `pass2NormalizeDice` in `program-canonical.ts`). */ import type { Expression, Statement } from './program'; /** * Rewrite every immediate {@link Expression} child of `expr` through `f`, * returning a new node (the input is never mutated). Leaf nodes — literals, * variable references, and the dice atoms — are returned unchanged. * * `repeat-expr` recurses into the expression children of its body statements * via {@link mapStatementChildren}. */ export declare function mapChildren(expr: Expression, f: (e: Expression) => Expression): Expression; /** * Rewrite every immediate {@link Expression} child of a {@link Statement} * through `f`. Parameter and dice declarations carry no `Expression` children * at this layer, so they are returned unchanged. */ export declare function mapStatementChildren(stmt: Statement, f: (e: Expression) => Expression): Statement; /** Visit each immediate {@link Expression} child of `expr`. */ export declare function forEachChild(expr: Expression, visit: (e: Expression) => void): void; /** Accumulate `acc` across each immediate {@link Expression} child of `expr`. */ export declare function foldChildren(expr: Expression, init: A, combine: (acc: A, child: Expression) => A): A; /** * True iff `pred` holds for every immediate {@link Expression} child of * `expr` (vacuously true for leaves). Short-circuits on the first child that * fails. */ export declare function everyChild(expr: Expression, pred: (e: Expression) => boolean): boolean; /** * True iff `pred` holds for at least one immediate {@link Expression} child of * `expr`. Short-circuits on the first child that matches. */ export declare function someChild(expr: Expression, pred: (e: Expression) => boolean): boolean;