Triptych

2.3. Artifact two: the executable parser🔗

The readable specification says which strings are valid and what they mean. The generated parser.lean turns that interface into a function that returns a domain value.

2.3.1. The generated parser🔗

def Decimal.parse : String → Option Int64 := fun s => scannerParse Decimal.grammar Decimal.constraints Decimal.valueExpr Int64.ofInt s#print Decimal.parse
def Decimal.parse : String → Option Int64 :=
fun s => scannerParse Decimal.grammar Decimal.constraints Decimal.valueExpr Int64.ofInt s

Read this definition as a pipeline:

  1. scannerParse scans the input once and obtains the named captures.

  2. It checks both grammar-derived and semantic constraints against that same capture map.

  3. For an accepted input, it evaluates Decimal.valueExpr to the specification-level Int.

  4. Int64.ofInt converts that value to the parser's Int64 result; a rejected input returns Option.none.

The generated Decimal.parse_eq_gated theorem proves that this single-scan implementation has exactly the readable behavior “accept when Decimal.IsValid, then return Decimal.computeValue converted by Int64.ofInt.”

The first two inputs below succeed. The third violates the grammar, while the fourth has the right shape but denotes a value outside the Int64 range:

(some 15000, some (-1500), none, none)#eval (Decimal.parse "1.5", Decimal.parse "-0.15", Decimal.parse "1.x", Decimal.parse "922337203685477.5808")
(some 15000, some (-1500), none, none)

2.3.2. Executing validity🔗

The generated decision procedure makes the readable predicates directly executable with decide. This separates a grammar failure from a semantic-constraint failure:

(false, true, false, false)#eval let tooLarge := "922337203685477.5808" (decide (Decimal.IsWf "3.14159"), decide (Decimal.IsWf tooLarge), decide (Decimal.SatisfiesConstraints tooLarge), decide (Decimal.IsValid tooLarge))
(false, true, false, false)

The first string fails Decimal.IsWf because it has five fraction digits. The second string is well-formed, but it fails Decimal.SatisfiesConstraints, so Decimal.IsValid also returns false.

2.3.3. Scanning the grammar🔗

Before a value can be computed, the generated scanner extracts the grammar's named captures:

some [("Sign", ""), ("Natural", "1"), ("Fraction", "5")]#eval scan Decimal.grammar "1.5"
some [("Sign", ""), ("Natural", "1"), ("Fraction", "5")]

This capture map is an engine representation. Ordinary clients use the typed Decimal.View introduced in §2.2 instead of looking up these string keys.

The scanner performs ordered search directly and stops at the first complete parse. The archived Triptych.decode definition instead constructs lists of candidate splits; it is kept as a transparent reference semantics. The checked theorem Triptych.scan_eq_decode proves that both select exactly the same capture map on every input, so replacing the runtime engine does not change the readable specification, computed value, or parser contracts.

2.3.4. Computing the specification value🔗

Decimal.computeValue uses the scanner and evaluates the DSL's value expression. Its result is still the specification-level Int; it neither applies the semantic constraint nor converts the result to Int64:

def Decimal.computeValue : String → Option Int := fun s => scannerComputeValue Decimal.grammar Decimal.valueExpr s#print Decimal.computeValue
def Decimal.computeValue : String → Option Int :=
fun s => scannerComputeValue Decimal.grammar Decimal.valueExpr s

The distinction is visible in these three results. A valid decimal computes normally, the well-formed but out-of-range decimal still computes an Int, and malformed text cannot be decoded:

(some 15000, some 9223372036854775808, none)#eval (Decimal.computeValue "1.5", Decimal.computeValue "922337203685477.5808", Decimal.computeValue "1.x")
(some 15000, some 9223372036854775808, none)

2.3.5. The typed executable view🔗

The engine stores captures in a generic string-keyed map. Without a typed view, every client proof would have to unfold that representation and reason about lookups such as "Natural" ↦ "12" and "Fraction" ↦ "34". The view theorems perform that translation once: client code obtains a Decimal.View and works directly with fields such as v.natural, v.fraction, and v.denotation.

Surface validity is then equivalent to the existence of a valid decoded view, and value computation and parser results factor through that same record:

@Decimal.decodeView_input : ∀ {s : String} {v : Decimal.View}, Decimal.decodeView s = some v → v.input = s#check @Decimal.decodeView_input Decimal.IsValid_view : ∀ (s : String), Decimal.IsValid s ↔ ∃ v, Decimal.decodeView s = some v ∧ v.Valid#check @Decimal.IsValid_view Decimal.computeValue_view : ∀ (s : String), Decimal.computeValue s = Option.map Decimal.View.denotation (Decimal.decodeView s)#check @Decimal.computeValue_view Decimal.parse_eq_some_iff_view : ∀ (s : String) (i : Int64), Decimal.parse s = some i ↔ ∃ decodedView, Decimal.decodeView s = some decodedView ∧ decodedView.Valid ∧ Int64.ofInt decodedView.denotation = i#check @Decimal.parse_eq_some_iff_view Decimal.parse_eq_none_iff_view : ∀ (s : String), Decimal.parse s = none ↔ ¬∃ v, Decimal.decodeView s = some v ∧ v.Valid#check @Decimal.parse_eq_none_iff_view

The generated engine also connects structural derivations to executable decoding. Decimal.Derivation.Decimal.matches proves that a valid derivation matches the root production, and Decimal.Derivation.Decimal.mem_fullParses records its captures as a complete parse of the grammar. The Decimal grammar passes the conservative all-input uniqueness checker, so Decimal.Derivation.Decimal.decode_render proves that the decoder recovers those captures without an ambiguity premise. Finally, Decimal.Derivation.Decimal.decodeView_render proves that decoding recovers the same typed view:

Decimal.Derivation.Decimal.matches : ∀ (qual : String) (fuel : Nat) (d : Decimal.Derivation.Decimal), d.Valid → ProdMatch Decimal.grammar qual (fuel + 2) { name := "Decimal", alts := [[{ sym := Sym.ref "Sign" }, { sym := Sym.ref "Natural" }, { sym := Sym.lit "." }, { sym := Sym.ref "Fraction" }]] } d.render (Decimal.Derivation.Decimal.capturesWith qual d)#check @Decimal.Derivation.Decimal.matches Decimal.Derivation.Decimal.mem_fullParses : ∀ (d : Decimal.Derivation.Decimal), d.Valid → Decimal.Derivation.Decimal.capturesWith "" d ∈ fullParses Decimal.grammar d.render#check @Decimal.Derivation.Decimal.mem_fullParses Decimal.Derivation.Decimal.decode_render : ∀ (d : Decimal.Derivation.Decimal), d.Valid → decode Decimal.grammar d.render = some (Decimal.Derivation.Decimal.capturesWith "" d)#check @Decimal.Derivation.Decimal.decode_render Decimal.Derivation.Decimal.decodeView_render : ∀ (d : Decimal.Derivation.Decimal), d.Valid → Decimal.decodeView d.render = some d.toView#check @Decimal.Derivation.Decimal.decodeView_render "-12.34"#eval CedarExamples.Decimal.Decimal.Derivation.Decimal.render CedarExamples.Decimal.decimalDerivation

2.3.6. Generated parser contracts🔗

Beyond the executable function, parser.lean contains machine-checked contracts emitted and proved automatically. Every success is valid and correctly valued, every valid input with a matching value succeeds, and rejection is exactly invalidity:

Decimal.parse_sound : ∀ (s : String) (i : Int64), Decimal.parse s = some i → Decimal.IsValid s ∧ Option.map Int64.ofInt (Decimal.computeValue s) = some i#check @Decimal.parse_sound Decimal.parse_complete : ∀ (s : String) (i : Int64), Decimal.IsValid s → Option.map Int64.ofInt (Decimal.computeValue s) = some i → Decimal.parse s = some i#check @Decimal.parse_complete Decimal.parse_reject : ∀ (s : String), Decimal.parse s = none ↔ ¬Decimal.IsValid s#check @Decimal.parse_reject

Compiler-generated theorems use only propext, Classical.choice, and Quot.sound.

2.3.7. Why the parser is correct by construction🔗

Triptych generates the grammar, constraints, value expression, and scanner parser from the same DSL description. The generic theorem Triptych.scannerParse_eq_surfaceGatedParseOfSpec proves that the one-scan implementation equals the readable Triptych.gatedParseOfSpec contract.

The generated contracts rewrite through Decimal.parse_eq_gated and then apply generic theorems about that contract. For example, the complete soundness proof has this form:

example (s : String) (i : Int64) : Decimal.parse s = some i → Decimal.IsValid s ∧ (Decimal.computeValue s).map Int64.ofInt = some i := s:Stringi:Int64⊢ Decimal.parse s = some i → Decimal.IsValid s ∧ Option.map Int64.ofInt (Decimal.computeValue s) = some i s:Stringi:Int64⊢ gatedParseOfSpec Decimal.IsValid Decimal.computeValue Int64.ofInt s = some i → Decimal.IsValid s ∧ Option.map Int64.ofInt (Decimal.computeValue s) = some i All goals completed! 🐙

The same construction supplies completeness and rejection. When the generated file builds, Lean checks the parser definition and each proof term together. If the emitted parser no longer has the behavior required by a generic theorem, the proof does not typecheck.

Here, correct by construction has a precise boundary: for every input, the generated parser implements the authored Triptych validity and value specification. It does not by itself prove that the human-authored grammar is an accurate transcription of Cedar's intended format. The independent corpus check at the end of this chapter addresses that separate question.

2.3.8. The runtime-checked Cedar parser🔗

Before any format-specific proof about Cedar's implementation, parser.lean generates Decimal.checkedExtParse. It first runs Cedar's parser. If Cedar returns a value, the checked parser keeps that result only when Decimal.IsValid accepts the same input and Decimal.computeValue computes the same value after conversion; otherwise it returns Option.none:

Decimal.checkedExtParse : String → Option Cedar.Spec.Ext.Decimal#check @Decimal.checkedExtParse Decimal.checkedExtParse_eq_some_iff : ∀ (s : String) (d : Cedar.Spec.Ext.Decimal), Decimal.checkedExtParse s = some d ↔ Cedar.Spec.Ext.Decimal.parse s = some d ∧ Decimal.IsValid s ∧ Decimal.computeValue s = some (Int64.toInt d)#check @Decimal.checkedExtParse_eq_some_iff Decimal.checkedExtParse_sound_view : ∀ (s : String) (d : Cedar.Spec.Ext.Decimal), Decimal.checkedExtParse s = some d → ∃ v, Decimal.decodeView s = some v ∧ v.Valid ∧ v.denotation = Int64.toInt d#check @Decimal.checkedExtParse_sound_view

This provides runtime soundness without first proving Cedar's parser correct. It does not repair a false rejection: if Cedar returns Option.none, the checked parser also returns Option.none.