Free variables of, and de Bruijn index manipulation for, a ComputableTLAPlus.Expression under
locally-nameless binding.
Expression.freeVars collects every .var _ (.free name) node: a Memory-keyed name (PlusCal
variable/channel/fifo, self, a statement with). .bound occurrences resolve to an
enclosing expression binder and .module/.intrinsic occurrences resolve through Ξ/Ω — none
of them read memory, so none contribute.
Origin.bound uses standard de Bruijn indices: .bound 0 is the nearest enclosing
expression-level binder. liftBound/openVar/close/subst/instantiate are all one
depth-tracking traversal, mapVars. .bound and .free are disjoint namespaces, so subst of
a free name captures nothing and needs no freshness side condition.
Every name a target reads from memory: every .var _ (.free name) node. .bound and
.module/.intrinsic occurrences resolve elsewhere and contribute nothing.
Equations
- One or more equations did not get rendered due to their size.
- (ComputableTLAPlus.Expression.var a (TypedTLAPlus.Origin.free n)).freeVars = {n}
- (ComputableTLAPlus.Expression.var a a_1).freeVars = ∅
- (ComputableTLAPlus.Expression.forall a a_1 dom body).freeVars = dom.freeVars ∪ body.freeVars
- (ComputableTLAPlus.Expression.exists a a_1 dom body).freeVars = dom.freeVars ∪ body.freeVars
- (ComputableTLAPlus.Expression.choose a a_1 dom body).freeVars = dom.freeVars ∪ body.freeVars
- (ComputableTLAPlus.Expression.collect a a_1 dom pred).freeVars = dom.freeVars ∪ pred.freeVars
- (body.map' a ann cod dom).freeVars = dom.freeVars ∪ body.freeVars
- (f.fnCall fnTyp e').freeVars = f.freeVars ∪ e'.freeVars
- (ComputableTLAPlus.Expression.fn a ann cod dom body).freeVars = dom.freeVars ∪ body.freeVars
- (f.recordAccess a).freeVars = f.freeVars
- (e₁.if e₂ e₃ τ).freeVars = e₁.freeVars ∪ e₂.freeVars ∪ e₃.freeVars
- (ComputableTLAPlus.Expression.nat a).freeVars = ∅
- (ComputableTLAPlus.Expression.str a).freeVars = ∅
- ComputableTLAPlus.Expression.true.freeVars = ∅
- ComputableTLAPlus.Expression.false.freeVars = ∅
Instances For
Rebuild every .var node knowing the number of expression-level binders enclosing it: f k τ o pos is the replacement for a .var τ o at binder depth k. Each binder arm recurses into its
scoped body at k + 1; domain and annotation positions stay at k.
Equations
- One or more equations did not get rendered due to their size.
- ComputableTLAPlus.Expression.mapVars f k (ComputableTLAPlus.Expression.var τ o) = f k τ o (posOf (ComputableTLAPlus.Expression.var τ o))
- ComputableTLAPlus.Expression.mapVars f k (g.recordAccess nm) = (ComputableTLAPlus.Expression.mapVars f k g).recordAccess nm @@ posOf (g.recordAccess nm)
- ComputableTLAPlus.Expression.mapVars f k (ComputableTLAPlus.Expression.nat n) = ComputableTLAPlus.Expression.nat n @@ posOf (ComputableTLAPlus.Expression.nat n)
- ComputableTLAPlus.Expression.mapVars f k (ComputableTLAPlus.Expression.str s) = ComputableTLAPlus.Expression.str s @@ posOf (ComputableTLAPlus.Expression.str s)
- ComputableTLAPlus.Expression.mapVars f k ComputableTLAPlus.Expression.true = ComputableTLAPlus.Expression.true @@ posOf ComputableTLAPlus.Expression.true
- ComputableTLAPlus.Expression.mapVars f k ComputableTLAPlus.Expression.false = ComputableTLAPlus.Expression.false @@ posOf ComputableTLAPlus.Expression.false
Instances For
The mapVars action liftBound d runs at every .var node: add d to a .bound index that
sits at or past the current binder depth, leave everything else. Named so lemmas can talk about the
traversal without re-inlining it.
Equations
- ComputableTLAPlus.Expression.liftBoundLam d k τ (TypedTLAPlus.Origin.bound i) pos = ComputableTLAPlus.Expression.var τ (TypedTLAPlus.Origin.bound (if k ≤ i then i + d else i)) @@ pos
- ComputableTLAPlus.Expression.liftBoundLam d k τ o pos = ComputableTLAPlus.Expression.var τ o @@ pos
Instances For
Add d to every .bound index that refers past e's own binders.
Equations
Instances For
The mapVars action openVar name runs at every .var node: the .bound index equal to the
current depth becomes .free name, deeper indices shift down by one, shallower ones stay. Named so
lemmas can talk about the traversal without re-inlining it.
Equations
- One or more equations did not get rendered due to their size.
- ComputableTLAPlus.Expression.openVarLam name k τ o pos = ComputableTLAPlus.Expression.var τ o @@ pos
Instances For
In a binder's body — already stripped of that binder — turn the reference to the removed binder
into the free name name, and shift every deeper free index down by one.
Equations
Instances For
Bind every free occurrence of name as a new outermost .bound, shifting every deeper free
index up by one. Inverse of openVar.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Collapse the outer attach ∘ map a second mapVars (or applyComputable) traversal wraps
around a list arm: (l.attach.map f).attach.map g matches the single-layer l.attach.map h when
g on each element f a agrees with h a. The applyComputable list arms
(tuple/record) need this — the source term carries one attach.map, the traversed term two.
Two mapVars traversals at the same depth that undo one another at every .var node cancel
over the whole term. Used to state .liftBound/.openVar cancellations.
Two mapVars traversals commute when the left one runs n + 1 binder levels deeper than the
right: applying f at depth j then g at n + 1 + j equals applying fg at n + j then gf
at j, provided the four .var-node actions satisfy that identity pointwise. The n offset is
what a splice under n + 1 binders (an operator/function body) needs — e.g. openVar at depth
n + 1 past a liftBound 1 at depth 0 matches liftBound 1 past openVar at depth n
(openVar_liftBound_one_comm). applyComputable never builds except/case, but those arms are
discharged too so the lemma holds for every term.
Two mapVars traversals at the same depth commute when the four .var-node actions commute
pointwise. The zero-offset companion of mapVars_shift_comm — needed where a splice happens
directly under the binder being opened, not one level deeper (subst_openVar_comm).
Opening the binder n + 1 levels out (openVar name at depth n + 1) commutes with the
liftBound 1 a splice under that binder carries: doing the lift first and opening at n + 1
matches opening at n first and lifting. The instance of mapVars_shift_comm that
openVar_applyComputable needs for every arm of applyComputable that puts e under a binder.
Lifting every de Bruijn index up by one and then opening the (now vacant) innermost binder
with name is the identity: the lift moves every index past the cutoff, so openVar finds
nothing bound at depth 0 to rename and shifts everything back down. No freshness needed.
Resolve every .bound occurrence in e to the free name its binding hint carries. outer
names the binders enclosing e that are not nodes within it — operator/function parameters, a
multicast filter's recipient — in source order, outermost first. Afterwards e holds no .bound
node: every former bound occurrence reads as .free of the string its binder recorded. The inverse
the elaborator performs is close; consumers that only read a term (code generation, pretty
printing) use this to work with names instead of indices.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The mapVars action subst x e runs at every .var node: a .var _ (.free x) becomes e
lifted by the current binder depth; everything else is left. Named so lemmas compose without
re-inlining it.
Equations
- One or more equations did not get rendered due to their size.
- ComputableTLAPlus.Expression.substLam x e k τ o pos = ComputableTLAPlus.Expression.var τ o @@ pos
Instances For
Substitute e for every free occurrence of the free name x. Captures nothing — .free and
.bound are disjoint — with e liftBound-ed by the binder depth it is spliced under. Sound only
when e is locally closed (Expression.LC); the liftBound is meaningless otherwise.
Equations
- ComputableTLAPlus.Expression.subst x e target = ComputableTLAPlus.Expression.mapVars (ComputableTLAPlus.Expression.substLam x e) 0 target
Instances For
e has no dangling de Bruijn index: every .bound i node sits under more than i enclosing
binders. Phrased so that any depth-tracking traversal that is the identity on .bound i below its
own depth (and on non-.bound origins) leaves e untouched, from any base depth — which is what
liftBound/openVar/subst all are, on their .bound arms, once i is genuinely bound.
Equations
- One or more equations did not get rendered due to their size.
Instances For
A locally-closed term is fixed by liftBound at any amount.
A locally-closed term is fixed by an openVar traversal at any base depth: there is no
outermost .bound for it to rename.
The converse of LC.mapVars_openVarLam_eq: openVar fixing a term certifies it locally closed.
Substituting a free name and opening a binder body commute, provided the opened name is
distinct from the substituted one and the substituend is locally closed. The binder case of
evalSubst'.
The mapVars action instantiate args runs at every .var node: a .bound index at or past
the current binder depth is replaced by the corresponding arg (lifted past the crossed binders)
or shifted down by args.length when it points past them. Named so lemmas compose without
re-inlining it.
Equations
- One or more equations did not get rendered due to their size.
- ComputableTLAPlus.Expression.instLam args k τ o pos = ComputableTLAPlus.Expression.var τ o @@ pos
Instances For
Instantiate the outermost de Bruijn binders with args (args[0] for .bound 0, …), shifting
every deeper index down by args.length. Operator/function parameter substitution.
Equations
- ComputableTLAPlus.Expression.instantiate args target = ComputableTLAPlus.Expression.mapVars (ComputableTLAPlus.Expression.instLam args) 0 target
Instances For
Bumping the base depth of a mapVars by one is unobservable when the per-node action f's
answer at depth d + 1 matches its answer at d.
Iterated mapVars_succ_base: a depth-agnostic action's mapVars is depth-independent.
A locally-closed term is fixed by a liftBound traversal at any base depth.
A locally-closed term is fixed by an instantiate traversal at any base depth: it has no free
.bound for instLam to replace.
For a locally-closed substituend, substLam ignores the depth it runs at, so subst reads off
a mapVars of substLam at any base depth.
subst, structurally #
subst pushes through every constructor. The binder arms descend the body at depth 1; under
e'.LC that mapVars _ 1 is subst again (LC.mapVars_substLam_eq), so LC.subst_* state the
fully-recursive shape evalSubst' reads off.
Opening a binder body one level out commutes with a subst that descended one level into it,
when the opened name is distinct from the substituted one and the substituend is locally closed.
The .forall/.exists/… case of evalSubst'.
Opening a substituted body and substituting an opened body agree, once the opened name avoids
the substituted one and the substituend is locally closed. The evalSubst' binder step, phrased
directly on subst/openVar.
A .var at a non-.bound origin is locally closed.
An .opCall of locally-closed parts is locally closed.
A one-entry except of locally-closed parts is locally closed.
liftBound of a locally-closed term is locally closed (it is the term itself).
A .fnCall of locally-closed parts is locally closed.
A .recordAccess of a locally-closed part is locally closed.
e'[e\r]: substitutes a preceding r≔e assignment's effect into a later expression e'. A
bare-variable r (no .args) substitutes the name directly; a compound r (with field/index
segments) instead substitutes the whole variable with [var(r) EXCEPT !path = e]. r.args is
already the List (String ⊕ Expression α) shape Expression.except takes, so it is a one-entry
except list, no reshaping needed.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The two branches of substRef, named. Both readings get used — ExprSemantics.evalSubstRef
(Core/ComputableTLAPlus/Semantics/Interface.lean) proves one case of its ↔ from each — and
neither states the @@ tags, which are definitionally transparent (Common/Position.lean) and only
noise in a consumer's goal.
At a bare reference, substRef is plain substitution of the right-hand side.
At a compound reference, substRef substitutes the base variable by a one-entry EXCEPT
rebuilding it along the reference's own path.