Concrete syntax for programs and procedures #
Surface syntax for the imperative probabilistic language (StmtWithHoles /
ProcedureWithHoles from GaudisCrypt). Expression syntax (GaudiExpr[ ] and the $
sigil) is in ExpressionSyntax.lean; module syntax is in ModuleSyntax.lean. See
syntax-ideas.md for design notes.
Statements / programs — GaudiProg[ … ] #
A ;-terminated sequence of statements. The statement forms are:
skip;x <- e;— assignment;a, b <- e;/(a, b) <- e;— tuple assignment (the parentheses are optional);x <$ e;— samplexfrom distributione;x <- call p (e₁, …, eₙ);— call procedurep, storing the result inx;call p (e₁, …, eₙ);— callp, discarding the result;if (e) { … } else { … }— theelsebranch is optional;while (e) { … }{ … }— a nested block;let x := e;/have h : P := prf;/letI …;/haveI …;— an ordinary Lean binder (not a program statement), scoping over the statements that follow it in the same block.
The argument list ( … ) of a call is always required (write () for no arguments).
The sequence may start with var x : T, …; lines, GaudiProg[ var i : Nat; i <- 0; … ], which
declare local variables exactly as in a proc (below): each name stands for its slot in the
locals of the ProgramState.
Example (a b c : Lens Nat VariableAssignment globals, inc : Procedure …):
GaudiProg[
a <- $a + 1;
b, c <- ($a, $a * 2);
if ($a == 0) { a <- 1; } else { skip; }
while ($b == 0) { b <- $b + 1; }
a <- call inc ($a);
]
Procedures — proc (…) [uses (…)] [: R] { … } #
A procedure term:
proc (x : T, y : U) uses (A : (Nat) → Bool, B : (Bool) → Nat) : R {
var u : V, w : W; -- zero or more `var …;` lines of local variables
<statements>
return e
}
- parameters
(x : T, …)(possibly none). A binder may name several variables of one type,(x y : T, …), and stands for the same list as writing them out one by one — so may a local-variable binder, and a module parameter afterusing; - an optional
uses (…)clause declaring holes (abstract sub-procedures), each writtenname : (T₁, …, Tₙ) → R. Inside the body a hole is invoked with the ordinarycall A (…)syntax —Aresolves to a hole when it is one of the declared names, and to a concrete procedure otherwise; - an optional return type
: R(inferred fromreturn ewhen omitted); - local variables via one or more
var name : T, …;lines (var u w : V;declares two). A parameter or localx : Tstands for the slotlocalVarLens "x" Tof the locals; a fresh local starts at an unspecified value (VariableAssignment.init), so it should be written before it is read; - a body of statements ending in
return e.
A let/have/letI/haveI statement in the body scopes over the rest of the body and
over return e. Body and return value are two separate fields of ProcedureWithHoles, so
the binders on the body's spine — those of the procedure's own block, each carrying the
rest of it — are repeated around the return value. A binder nested inside an if/while/
block, on the other hand, ends with that block and does not reach return e.
Procedure types and signatures #
proctype (T, U, …) -> W— the type of a closed procedure;proctype (T, …) -> W uses ((T₁,…) → R, …)— the type of a procedure with holes;procsig (T, U, …) -> W— the bareProcedureSignature.
-> is used (rather than :) so these nest inside type ascriptions without extra
parentheses; they also pretty-print back into this form.
An argument of any of these may be named — procsig (h : G, m : F) -> Bool — which a
ProcedureSignature cannot record and which is therefore dropped, except by moduletype,
which keeps the names in the @[gaudiProcParamNames] attribute. See the Signature argument
lists section below.
Printing #
Statements, procedures and the two type forms all print in this syntax again, and do so
round-trip faithfully: parsing what was printed gives the same term back. A variable read
prints as §x, the printable spelling of $x. See the Printing section at the end of
this file for the delaborators and for the two places where the printed form differs from
what one would write by hand.
e.g. proctype (Nat, Bool) -> Nat, proctype (Nat) -> Nat uses ((Nat) → Bool, (Bool) → Nat),
procsig (Nat, Bool) -> Nat. Note Procedure (procsig (Nat) -> Nat) = proctype (Nat) -> Nat.
The module type of a procedure, procmod (…) -> R, is in ModuleSyntax.lean.
Syntax for programs (StmtWithHoles) #
Statement syntax over StmtWithHoles h. Each expression position (assignment
RHS, sampling distribution, if/while condition) is wrapped with GaudiExpr[ ]
so the $x sigil works. An l-value (assignment/sample LHS) is a lens, lifted
into the current full state ProgramState by liftLens — so a global Lens a VariableAssignment
may be written bare and is lifted with Lens.intoGlobal, and a local variable (a lens into
ProgramState) is used as it is.
Surface forms (gaudi_stmt):
skip;
x <- e; -- assignment
a, b <- e; (a,b) <- e; -- tuple l-value (parens optional), via `Lens.pair`
x <$ e; -- sampling (e : a distribution expression)
x <- call p (e₁, …, eₙ); -- procedure call, result stored in `x`
call p (e₁, …, eₙ); -- procedure call, result discarded (Lens.throwaway)
if (e) { … } else { … } -- the `else` branch is optional
while (e) { … }
{ … } -- a block (sequence)
let x := e; have h : P := prf; letI …; haveI …; -- a Lean binder
The call argument list ( … ) is always required (even ()); the arguments form a
tuple matching the callee's ParamType. (hole is still deferred.)
The four binder forms are not StmtWithHoles constructors: they are the ordinary Lean
term binders. Each carries the statements it scopes over as a nested statement list, so
let x := e; followed by more statements is a single gaudi_stmt holding them, and the
binder is visible exactly there — in the statements that follow it in its own block, and
nowhere else.
The @[gaudiProcParamNames] attribute #
A ProcedureSignature records parameter types only, so the names written in a
moduletype field or a module procedure would be lost. They are kept in this attribute
instead, on the declaration that names the procedure — a moduletype accessor MT.f, a
module's procedure module X.f and its X.f.procedure — so that notation about a
procedure can bind its parameters by name even when the procedure itself is abstract (a field
of an arbitrary module of a declared module type has no body to read names off).
The attribute's syntax has to be declared under Lean.Parser.Attr — that is the namespace
Lean strips to get an attribute's name from its syntax kind — and an initialize block cannot
be used in the file that declares it, which is why both live here rather than next to the
commands that write them (ModuleSyntax.lean).
@[gaudiProcParamNames x, m] records the parameter names of a procedure-valued
declaration. Written by the moduletype and module commands; may also be attached by hand
to an axiomatized procedure.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The parameter names recorded for a procedure-valued declaration by
@[gaudiProcParamNames …], if any. Read with
GaudisCrypt.gaudiProcParamNamesAttr.getParam? env declName.
Lift a program variable used as an l-value into a setter on the full current state
ProgramState. Dispatch is on the lens's container M: a global lens
(M = VariableAssignment, the globals)
is lifted with Lens.intoGlobal, a full-state lens (M = ProgramState, e.g. a local
variable localVarLens "x" T) is kept as-is. The content type A is deliberately
not a class parameter — resolution then only needs M (always concrete from the
argument), and the result's content unifies with the expected type as an ordinary,
postponable constraint. (That is what lets a call result l-value resolve even before
the callee's sig is known.)
- lift {A : Type} : Lens A M → Setter A ProgramState
Instances
Equations
- GaudisCrypt.instLiftLensVariableAssignment = { lift := fun {A : Type} (x : GaudisCrypt.Lens A GaudisCrypt.VariableAssignment) => x.intoGlobal.toSetter }
Equations
- GaudisCrypt.instLiftLensProgramState = { lift := fun {A : Type} (x : GaudisCrypt.Lens A GaudisCrypt.ProgramState) => x.toSetter }
User-facing l-value lift; the container M and the content A are inferred.
The result is a Setter (l-values only ever set).
Equations
Instances For
The raw (un-lifted) lens for an l-value: a tuple (x, y, …) becomes a nested
Lens.pair; a single term is itself. Pairing needs the components to be disjoint
lenses in the same container — the Lens.Disjoint instance is resolved at the concrete
lenses, so (a, b) requires Lens.Disjoint a b.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Raw nested Lens.pair of a comma-list of l-value components (each component
may itself be a paren-tuple, handled by [lvalRaw|]).
Equations
- One or more equations did not get rendered due to their size.
Instances For
lval% x: x as an l-value. A setter on the program state is one already and is used as
it is (resetSetter S, say: a setter that is not a lens); anything else
is a lens and goes through liftLens.
Equations
- GaudisCrypt.lvalElab = Lean.ParserDescr.node `GaudisCrypt.lvalElab 1022 (Lean.ParserDescr.binary `andthen (Lean.ParserDescr.symbol "lval% ") (Lean.ParserDescr.cat `term 1024))
Instances For
lval% x: x as an l-value. A setter on the program state is one already and is used as
it is (resetSetter S, say: a setter that is not a lens); anything else
is a lens and goes through liftLens.
Equations
- One or more equations did not get rendered due to their size.
Instances For
An l-value lifted into the current full state ProgramState. Accepts a single
lens, a parenthesised tuple (a, b), or a bare comma-list a, b (top-level
parens optional) — all interpreted via Lens.pair — or a single setter on the program state
(lval%).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Translate one statement to a StmtWithHoles term.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Translate a statement sequence (fold with seq; empty ↦ skip).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Top-level program bracket.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
The string name of a program variable: the identifier's name, which must be atomic.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Reject a name declared twice among the parameters, local variables and holes of one
proc (or the vars of one GaudiProg[ ]).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The letI binding of a program variable x : T: x stands for its local slot
localVarLens "x" T.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Wrap inner in the letI bindings bs (outermost first).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Lens.id followed by a chain of .ofst (true) / .osnd (false). Used for argument
tuples (hoare[ M (x, m) : … ]), no longer for local variables.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The names a binder declares, each paired with the (shared) declared type.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
A hole declaration A : (T₁, …, Tₙ) → R (an abstract procedure with no locals).
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Rewrite call A (…) → holecall A (…) for every callee A whose name is a hole
(recursing into if/while/block bodies); everything else is left untouched.
Wrap e in the Lean binders on the spine of the statement sequence ss: the
let/have/letI/haveI statements of the sequence itself, not those nested inside an
if/while/block. Each such statement carries the rest of its sequence, so the spine is a
chain — at most one binder per level, recursed into. proc uses this to repeat the body's
binders around the return value, which is a separate field of ProcedureWithHoles.
Equations
- One or more equations did not get rendered due to their size.
Instances For
GaudiProg[ var x : T, …; <stmts> ]: a statement sequence with local variables, each bound
to its local slot exactly as in a proc. (The form without var lines is the plain
GaudiProg[ … ] above.)
Equations
- One or more equations did not get rendered due to their size.
Instances For
Signature argument lists — proc_arg #
The argument list of a procsig/proctype/procmod, and of a proc field of a
moduletype, is a list of types, each of which may carry a name:
procsig (h : G, m : F) -> Bool. A ProcedureSignature records only the types, so the names
are dropped everywhere except by moduletype and module, which put them in
@[gaudiProcParamNames].
Deliberately not proc_binder (the named binder of proc/var/using). Serving both
from one category would mean adding a bare-type production to it, and a syntax category being
global, that production would also turn up in the five positions where a name is mandatory:
var Int;, proc (Int) { … } and module X using (Int) would then parse and fail late in
ProgramSyntax.parseBinder instead of at the parser.
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
A named argument slot, x : T. The name is documentation except in a moduletype.
Equations
- One or more equations did not get rendered due to their size.
Instances For
An unnamed argument slot: just the type.
Equations
- GaudisCrypt.proc_arg_ = Lean.ParserDescr.node `GaudisCrypt.proc_arg_ 1022 (Lean.ParserDescr.cat `term 0)
Instances For
The name (if one was written) and the declared type of one argument slot.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The declared types of an argument list, with the names dropped.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The names an argument list wrote, none for a slot that was left unnamed.
Equations
- One or more equations did not get rendered due to their size.
Instances For
An unnamed argument slot from its type — what the unexpanders build, a printed signature having no names to show.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Procedure type syntax #
proctype (T, U, V) -> W is the type Procedure { params := [T, U, V], ret := W }, and
proctype (…) -> W uses ((A₁,…) → R₁, …) is the corresponding ProcedureWithHoles, whose
hole context is built from the listed (nameless) procedure signatures. (Uses -> rather
than : so it needs no extra parentheses inside a type ascription.)
Equations
- One or more equations did not get rendered due to their size.
Instances For
A nameless hole signature (T₁, …, Tₙ) → R inside a proctype … uses (…) clause.
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
proctype unexpanders. A signature already prints as procsig (…) -> … (the
ProcedureSignature.mk unexpander), so we just rewrite Procedure (procsig …) and
ProcedureWithHoles … (procsig …) to proctype …. Parameter lists are read off the raw
procsig node (a category quotation can't match the sepBy inside the parens).
If s is a procsig ( … ) -> … node, return its parameter list and return type.
(Not private: ModuleSyntax.lean unexpands procmod with it.)
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Procedure signature syntax #
procsig (T, U, V) -> W is the bare ProcedureSignature.mk [T, U, V] W (the same surface
form as proctype, minus the holes — a signature has none). By construction
Procedure (procsig …) = proctype …. The unexpander is on ProcedureSignature.mk, so any
signature with a literal parameter list prints back as procsig (…) -> ….
Equations
- One or more equations did not get rendered due to their size.
Instances For
A signature holds no names, so it prints with unnamed argument slots.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Printing #
Delaborators that render elaborated terms back into the surface syntax above: a procedure
built by proc prints as proc (…) uses (…) : R { … }, a statement prints as
GaudiProg[ … ], and a Getter standing on its own prints as GaudiExpr[ … ] (with variable
reads as the §x sigil, the printable spelling of $x).
A Getter prints that way only over a ProgramState, the only carrier GaudiExpr[ ] can
build — a Getter a VariableAssignment (an Expr a) would print as something that does not
elaborate back. Inside a statement the expression slots are printed by delabGaudiExpr
directly, with no GaudiExpr[ ] wrapper, since $/§ already works there.
Printing is round-trip faithful: parsing what was printed yields the same term back
(ProgramSyntaxTest.lean checks this by printing, re-parsing and re-elaborating). Hence
the two places where the printed form deviates from what one would write by hand:
- a
seqin the left position of aseqis printed as a block{ … }, since re-parsing a flat sequence would re-associate it; - a hole call prints as
call A (…)only inside theprocthat declaresAin itsusesclause (which is where the macro turnscallback into a hole call), and as the internalholecall n (…)anywhere else; let/havestatements print with their type ascribed (let x : T := v;), an anonymous one under the_it was written as (Lean names it inaccessibly,x✝, which is not a name that parses back — so a statement that does refer to it makes the delaborators step aside);letI/haveIdo not print at all — they inline their value during elaboration, so no binder of theirs survives in the term to be printed. What is printed is then the inlined term, which is what re-parsing gives back, so the round trip still holds.
A statement the macros did not build (by hand, or left behind by a rewrite) prints too, in a spelling that re-elaborates to a defeq term:
- an expression slot that is not of the shape
GaudiExpr[ ]builds prints asf CurrentState.statewhen it is aGetter.mk f(getterViaState), and as the read§gotherwise (delabStmtExpr); - an l-value that is a lens used as a setter directly prints as that lens, which
liftLensturns back into the same setter; Stmt.callprints as thecallstatement;- a local slot written as what
localVarLens "x" Tunfolds to,(varLens ⟨"x", T, …⟩).intoLocal, is the variablexlike the folded one, and otherwise prints folded.
Whenever a term does not fit the surface syntax even so — an l-value that is a setter but not a
lens, lets that proc would not have generated — the delaborators fail and Lean falls back to
its default output. They also
step aside under pp.explicit and set_option pp.notation false, and under the dedicated
set_option pp.gaudisCrypt false, which switches only them off and leaves the rest of
Lean's notation alone — the way to look at the underlying term.
pp.gaudisCrypt reaches the delaborators above but not the app_unexpanders further up this
file (proctype/procsig, Procedure): UnexpandM is ReaderT Syntax (EStateM Unit Unit)
and carries no Options, so an unexpander cannot consult one. Those still print the type
as proctype (…) -> R under pp.gaudisCrypt false; pp.notation false turns them off.
Surface syntax is printed only when Lean is printing readably, and only while
pp.gaudisCrypt is on.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Getter.mk fun st => e — what GaudiExpr[ e ] builds — ↦ e. Fails when the state
binder really occurs in e, since then e is not expressible in the surface syntax.
Equations
- One or more equations did not get rendered due to their size.
Instances For
f : ProgramState → A ↦ the getter Getter.mk fun st => f CurrentState.state (with f
applied β-reduced), which delabGaudiExpr prints as f CurrentState.state. Its get is f up
to η, so this is how a function of the state that is not of the shape GaudiExpr[ ] builds is
printed faithfully. CurrentState.state is the state GaudiExpr[ ] reads at.
Before that, the reads in f are brought into printable form, each step up to defeq:
simplifyReads first; then, once the state is CurrentState.state, a read of a lens at it
becomes the sigil read §L (sigilReads); and a sigil read at any other state is pinned to
it (pinEvals), since §x would read at the current one.
CurrentState.state reads the innermost CurrentState, though, and a GaudiExpr[ ] nested in
f installs its own: written there, it would read the nested state. When f mentions its
argument inside one (occursNested), the state is bound by a Lean let instead, which nothing
captures: Getter.mk fun st => let σ := CurrentState.state; f σ, printed as
let σ := CurrentState.state; f σ, and the same up to ζ. The binder is named after f's own if
f is a lambda, unless that is the name of the getter's own state binder (delabGaudiExpr checks
that one is not mentioned).
Instances For
An expression slot of a statement. What GaudiExpr[ e ] builds prints as e; any other
Getter.mk f as f CurrentState.state (getterViaState); and any other getter g as the
read §g — §L for a lens L used as a getter. Each re-elaborates to the same getter up to
ζ, η and structure η.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Peel n let/have binders whose values satisfy isOk, then run k on the binder
names, inside the local context they introduce. With anon, an inaccessible binder is peeled
too and reported under its printed name (see binderPrintName).
Program variables #
A parameter or var of a proc (or of a GaudiProg[ var …; ]) is letI-bound, so the term
holds its slot localVarLens "x" T at every use and no binder. To
print it under its name again, the delaborators
- collect the slots of the frame — every such term with a literal name, outside nested
procliterals (which are frames of their own and print their own variables), together with the parameters; - decide which of them print under their name: exactly one type per name (up to reducible
defeq), a non-empty name without
», and no clash with a constant or a binder of the same name in the frame (a clashing name could not be told apart from the variable when parsing back); - bind each printable one to a let-variable of its name, replacing its slot by it, and print
that term with the ordinary machinery, which shows the variable as
x(§xwhen read).
A variable that is not printable keeps its slot, which prints as
§(localVarLens "x" T) and re-parses to the same term. A parameter
that is not printable makes the proc delaborator step aside: the header has to name it.
FrameVar, frameVars, withVarLocals, withExpr and varBinder live in ProgramSyntax and
are not private: the hoare[ ] delaborator prints the frame of a statement triple the same
way.
(varLens ⟨"x", T, …⟩).intoLocal — what localVarLens "x" T unfolds to, and what a lemma
stated over an arbitrary varLens slot leaves behind — ↦ ("x", T).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The program variables of the frame whose terms are es: the parameters params first (in
order), then every other variable whose slot occurs in es, in order of first occurrence.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Bind each variable of vars to a let-variable of its name, replace its slots in es by it,
and run k on the rewritten terms.
Run d on e in place of the current expression.
Equations
- GaudisCrypt.ProgramSyntax.withExpr e d = withTheReader Lean.SubExpr (fun (s : Lean.SubExpr) => { expr := e, pos := s.pos }) d
Instances For
The binder x : T of a printable variable, as written after var.
Equations
- One or more equations did not get rendered due to their size.
Instances For
A statement sequence: the right seq spine, flattened. A Lean binder wrapping the rest
of the sequence (what a let/have statement elaborates to) becomes one statement carrying
that rest; have is the nondependent let, which is the only thing that tells the two apart
in the term.
The names bound by the let/have statements on the spine of a printed statement
sequence, outermost first — exactly the binders wrapSpineBinders repeats around the return
value. (letI/haveI inline during elaboration, so no binder of theirs is in either term.)