2.2. Artifact one: the readable spec
Elaboration writes Outputs/Decimal/spec.lean. Each production becomes a plain
predicate over strings. Here is the generated sign predicate:
#print Decimal.IsWf.Sign
The top-level predicate composes them along the concatenation structure — one existentially quantified variable per named capture, one conjunct per production:
#print Decimal.IsWf.Decimal
The root predicate composes the productions, and Decimal.IsValid conjoins shape with
the final-value range:
#print Decimal.IsValid
2.2.1. A typed field view
After parsing "-12.34", most code wants the useful pieces: the sign "-", the
natural digits "12", and the fraction digits "34". It should not need to understand
how the parser stores captures or records grammar choices.
The generated Decimal.View packages those pieces as named fields. Its input field
keeps the original string, while the other fields contain the text matched by the grammar's
named productions:
example : Decimal.View :=
{
input := "-12.34"
sign := "-"
natural := "12"
fraction := "34"
}
example (view : Decimal.View) : String :=
view.natural ++ "." ++ view.fraction
Code using a view can simply write view.natural or view.fraction. Without this
record, each caller would have to inspect the parser's generic capture map and know how the
grammar match was represented internally.
2.2.2. A structural derivation
A view deliberately forgets how the grammar matched. That keeps ordinary field access simple, but printer and roundtrip proofs need the missing structure: which alternative was selected, which optional pieces were present, and how repeated or referenced productions were built.
A derivation preserves that evidence as a tree. This gives a printer a grammar-shaped target:
construct a valid derivation for a domain value, then turn the derivation into text. Its
constructor records the chosen alternative, while its children record how each referenced
production matched. For Decimal, the root constructor has children for Sign,
Natural, and Fraction. The dot is a fixed literal, so it does not need its own
field:
example
(signTree : Decimal.Derivation.Sign)
(natural : Decimal.Derivation.Natural)
(fraction : Decimal.Derivation.Fraction) :
Decimal.Derivation.Decimal :=
.alt0 signTree natural fraction
2.2.3. Rendering for verified printers
Parsing moves from text to a derivation and its fields.
Decimal.Derivation.Decimal.render performs the tree-to-text direction: it concatenates
the terminal strings stored in a derivation and inserts the grammar's fixed literals.
example :
decimalDerivation =
.alt0
(.alt0 (some ()))
(.alt0 "12")
(.alt0 "34") := ⊢ decimalDerivation =
CedarExamples.Decimal.Decimal.Derivation.Decimal.alt0 (CedarExamples.Decimal.Decimal.Derivation.Sign.alt0 (some ()))
(CedarExamples.Decimal.Decimal.Derivation.Natural.alt0 "12")
(CedarExamples.Decimal.Decimal.Derivation.Fraction.alt0 "34")
All goals completed! 🐙
Evaluating rendering and validity together makes their separate roles visible:
#eval
(decimalDerivation.render,
decide decimalDerivation.Valid)
Crucially, Decimal.Derivation.Decimal.render does not validate the tree. Terminal nodes
store ordinary strings, so rendering also serializes a Natural node whose text violates the
grammar:
#eval
let tree := Decimal.Derivation.Natural.alt0 "abc"
(tree.render, decide tree.Valid)
The text "abc" is still rendered; the separate Decimal.Derivation.Natural.Valid
predicate rejects it.
Therefore Decimal.Derivation.Decimal.render is not itself a verified Decimal printer. A
printer certificate chooses a derivation for each domain value and proves that the tree is valid
and denotes that value; Decimal.Derivation.Decimal.render performs only the final
tree-to-string step. For valid derivations, later engine theorems prove that decoding the rendered
text recovers the tree's captures and typed view.
Note: Decimal.Derivation.Decimal.Valid checks only that the derivation is well-formed
according to the grammar, corresponding to the string-level Decimal.IsWf.
Decimal.IsValid checks both Decimal.IsWf and
Decimal.SatisfiesConstraints; for Decimal, the semantic constraint requires the computed
value to fit in Int64.