Imports
/- Copyright (c) 2026 Terence Rokop. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Terence Rokop -/ module public import Geb.Prototypes.ConcreteSyntax

A readable spelling of the rose syntax

Geb.Csexp.print and Geb.Ast.printViaRose are both [RFC9804] canonical: length-prefixed and whitespace-free, so neither has a readable form. This module spells the same Rose k as parenthesized text, a node's label followed by its children, so that fork (leaf 0) (fork (leaf 1) (leaf 2)) reads as (0 (1 2)).

The fragment lies inside [R7RS] <datum>: a reader conforming to that grammar accepts it without new code, and tools operating on parenthesized text apply to it directly. Labels are decimal numerals, which the [RFC9804] advanced form cannot spell as tokens — § 4.3 requires a token not begin with a digit — where [R7RS] and [EDN] read a digit-initial token as a number.

Main definitions

    Rsexp.print — the readable spelling of a Rose k.

    Rsexp.parse — the parser matching it, built from Rsexp.parseStep, Rsexp.parseAux and the shared Rose.parseChildren.

    Rsexp.skipWs — the whitespace skip the stripping discipline runs.

    Rsexp.printViaRose, Rsexp.parseViaRose — the composites with the rose bijection, spelling an Ast k.

Main statements

    Rsexp.parse_print, Rsexp.parseViaRose_printViaRose — the retraction law, on Rose and on Ast, with Rsexp.format_idem and Rsexp.print_injective instantiating the generic corollaries.

Implementation notes

The parser strips whitespace on return rather than on entry: parseStep is called on stripped input and returns a stripped remainder. That discipline is what lets Rose.parseChildren be reused verbatim — the loop tests its input's head against ')' immediately, so it must be called on already-stripped input — and what lets parse match some (r, []) syntactically rather than testing a remainder for whitespace.

A childless node prints as a bare numeral, so a printed tree can end in a digit and parseAux_print carries a delimiting side condition on the caller's remainder. Always parenthesizing would remove the obligation and the spelling; Csexp.readDigits_append carries the same condition for the same reason.

References

    [R7RS] §§ 4.1.3, 7.1.1, 7.1.2 — the datum grammar this fragment lies inside.

    [EDN] — the second readable format the spelling agrees with.

    [RFC9804] §§ 4.3, 6, 8 — the token rule that rules out the advanced form, and the conformance of declining it.

    [RFC8259] § 2 — the ws production this whitespace class matches.

Tags

concrete syntax, s-expression, parser, retraction, rose tree

@[expose] public sectionnamespace Gebnamespace Rsexp

Whitespace

The whitespace class this syntax admits: space, horizontal tab, carriage return and line feed. It is a subset of the class [R7RS] § 7.1.1 fixes, and admits the same four characters as [RFC8259] § 2's ws.

def isWs (c : Char) : Bool := c = ' ' || c = '\t' || c = '\r' || c = '\n'

Drop a leading run of whitespace. Carried by List.rec rather than by structural recursion, per the recursor rule.

def skipWs : List Char List Char := List.rec [] fun c cs ih if isWs c then ih else c :: cs
@[simp] theorem skipWs_nil : skipWs [] = [] := rfltheorem skipWs_cons (c : Char) (cs : List Char) : skipWs (c :: cs) = if isWs c then skipWs cs else c :: cs := rfl

Printer

The readable spelling: a childless node is its label, a node with children is its label and their spellings, parenthesized and each preceded by one space. The uniform space is what makes every element of the child block a cons, which the arity bound and the whitespace skip both use.

def print {k : Nat} : Rose k List Char := WType.elim (List Char) fun x match x with | (i, 0), _ => Csexp.decOf i.val | (i, _ + 1), ch => '(' :: (Csexp.decOf i.val ++ (((List.ofFn ch).map (fun s ' ' :: s)).flatten ++ [')']))
@[simp] theorem print_zero {k : Nat} (i : Fin k) (f : Fin 0 Rose k) : print (Rose.node i f) = Csexp.decOf i.val := rfltheorem print_succ {k n : Nat} (i : Fin k) (f : Fin (n + 1) Rose k) : print (Rose.node i f) = '(' :: (Csexp.decOf i.val ++ (((List.ofFn f).map (fun t ' ' :: print t)).flatten ++ [')'])) := k:n:i:Fin kf:Fin (n + 1) Rose kprint (Rose.node i f) = '(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn f)).flatten ++ [')'])) k:n:i:Fin kf:Fin (n + 1) Rose kWType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) (WType.mk (i, n + 1) f) = '(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) t) (List.ofFn f)).flatten ++ [')'])) k:n:i:Fin kf:Fin (n + 1) Rose k(match (i, n + 1), fun b WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) (f b) with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) = '(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) t) (List.ofFn f)).flatten ++ [')'])) -- reduce the match `WType.elim_mk` exposed, picking the `n.succ` arm k:n:i:Fin kf:Fin (n + 1) Rose k'(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn fun b WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) (f b))).flatten ++ [')'])) = '(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) t) (List.ofFn f)).flatten ++ [')'])) k:n:i:Fin kf:Fin (n + 1) Rose k'(' :: (Csexp.decOf i ++ ((List.ofFn ((fun s ' ' :: s) fun b WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) (f b))).flatten ++ [')'])) = '(' :: (Csexp.decOf i ++ ((List.ofFn ((fun t ' ' :: WType.elim (List Char) (fun x match x with | (i, 0), snd => Csexp.decOf i | (i, n.succ), ch => '(' :: (Csexp.decOf i ++ ((List.map (fun s ' ' :: s) (List.ofFn ch)).flatten ++ [')']))) t) f)).flatten ++ [')'])) All goals completed! 🐙

Character facts

The two parentheses are distinct characters.

theorem open_ne_close : ('(' : Char) ')' := '(' ')' All goals completed! 🐙

An opening parenthesis is not whitespace, so the whitespace skip stops at the head of a parenthesized spelling.

theorem open_not_ws : isWs '(' = false := isWs '(' = false All goals completed! 🐙

A closing parenthesis is not whitespace, so the whitespace skip stops at a child block's terminator.

theorem close_not_ws : isWs ')' = false := isWs ')' = false All goals completed! 🐙

A space is whitespace, so the whitespace skip consumes the separator the printer emits before each child.

theorem space_is_ws : isWs ' ' = true := isWs ' ' = true All goals completed! 🐙

The decimal layer

A printed numeral reads back whole, and leaves exactly what followed it. The delimiting hypothesis on the remainder is what a non-parenthesizing spelling of a childless node costs; it is the same condition Csexp.readDigits_append carries.

theorem readNat_append (n : Nat) (rest : List Char) (h : c cs, rest = c :: cs Csexp.charDigit c = none) : Csexp.readNat (Csexp.decOf n ++ rest) = some (n, rest) := n:rest:List Charh: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = noneCsexp.readNat (Csexp.decOf n ++ rest) = some (n, rest) n:rest:List Charh: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none(match Csexp.readDigits (Csexp.decOf n ++ rest) with | (ds, r) => if ds.isEmpty = true then none else Option.map (fun x (x, r)) (Csexp.digitsVal ds)) = some (n, rest) n:rest:List Charh: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none(match (Csexp.decOf n, rest) with | (ds, r) => if ds.isEmpty = true then none else Option.map (fun x (x, r)) (Csexp.digitsVal ds)) = some (n, rest) All goals completed! 🐙

A digit is not whitespace. The conclusion is isWs c = false rather than a disequality because the whitespace class has four members.

theorem digit_not_ws {c : Char} (h : (Csexp.charDigit c).isSome) : isWs c = false := c:Charh:(Csexp.charDigit c).isSome = trueisWs c = false c:Charh:(Csexp.charDigit c).isSome = true(decide (c = ' ') || decide (c = '\t') || decide (c = '\x0d') || decide (c = '\n')) = false c:Charh:(Csexp.charDigit c).isSome = trueh1:¬c = ' '(decide (c = ' ') || decide (c = '\t') || decide (c = '\x0d') || decide (c = '\n')) = false c:Charh:(Csexp.charDigit c).isSome = trueh1:¬c = ' 'h2:¬c = '\t'(decide (c = ' ') || decide (c = '\t') || decide (c = '\x0d') || decide (c = '\n')) = false c:Charh:(Csexp.charDigit c).isSome = trueh1:¬c = ' 'h2:¬c = '\t'h3:¬c = '\x0d'(decide (c = ' ') || decide (c = '\t') || decide (c = '\x0d') || decide (c = '\n')) = false c:Charh:(Csexp.charDigit c).isSome = trueh1:¬c = ' 'h2:¬c = '\t'h3:¬c = '\x0d'h4:¬c = '\n'(decide (c = ' ') || decide (c = '\t') || decide (c = '\x0d') || decide (c = '\n')) = false All goals completed! 🐙

A digit is not an opening parenthesis.

theorem digit_not_open {c : Char} (h : (Csexp.charDigit c).isSome) : c '(' := c:Charh:(Csexp.charDigit c).isSome = truec '(' h:(Csexp.charDigit '(').isSome = trueFalse exact absurd h (h:(Csexp.charDigit '(').isSome = true¬(Csexp.charDigit '(').isSome = true All goals completed! 🐙)

A digit is not a closing parenthesis.

theorem digit_not_close {c : Char} (h : (Csexp.charDigit c).isSome) : c ')' := c:Charh:(Csexp.charDigit c).isSome = truec ')' h:(Csexp.charDigit ')').isSome = trueFalse exact absurd h (h:(Csexp.charDigit ')').isSome = true¬(Csexp.charDigit ')').isSome = true All goals completed! 🐙)

Csexp.decOf_ne_nil in the form its consumers use: a shortest-form decimal has a head.

theorem decOf_eq_cons (n : Nat) : c cs, Csexp.decOf n = c :: cs := List.exists_cons_of_ne_nil (Csexp.decOf_ne_nil n)

The head of a shortest-form decimal is a digit.

theorem decOf_head_digit (n : Nat) : c cs, Csexp.decOf n = c :: cs (Csexp.charDigit c).isSome := n: (c : Char) (cs : List Char), Csexp.decOf n = c :: cs (Csexp.charDigit c).isSome = true n:c:Charcs:List Charhc:Csexp.decOf n = c :: cs(Csexp.charDigit c).isSome = true n:c:Charcs:List Charhc:Csexp.decOf n = c :: csc Csexp.decOf n n:c:Charcs:List Charhc:Csexp.decOf n = c :: csc c :: cs All goals completed! 🐙

A numeral is already stripped: the whitespace skip is the identity on a printed decimal and whatever follows it.

theorem skipWs_decOf_append (n : Nat) (rest : List Char) : skipWs (Csexp.decOf n ++ rest) = Csexp.decOf n ++ rest := n:rest:List CharskipWs (Csexp.decOf n ++ rest) = Csexp.decOf n ++ rest n:rest:List Charc:Charcs:List Charhc:Csexp.decOf n = c :: csskipWs (Csexp.decOf n ++ rest) = Csexp.decOf n ++ rest All goals completed! 🐙

The head of a spelling

Every spelling begins with an opening parenthesis or with a digit, according to whether the node has children. This is the readable counterpart of Rose.exists_print_eq_cons, which has only the parenthesized case.

theorem print_head {k : Nat} (r : Rose k) : c cs, print r = c :: cs (c = '(' (Csexp.charDigit c).isSome) := k:r:Rose k c cs, print r = c :: cs (c = '(' (Csexp.charDigit c).isSome = true) k:i:Fin kn:f:Rose.Arity (i, n) WType Rose.Arity c cs, print (WType.mk (i, n) f) = c :: cs (c = '(' (Csexp.charDigit c).isSome = true) cases n with k:i:Fin kf:Rose.Arity (i, 0) WType Rose.Arity c cs, print (WType.mk (i, 0) f) = c :: cs (c = '(' (Csexp.charDigit c).isSome = true) k:i:Fin kf:Rose.Arity (i, 0) WType Rose.Arityc:Charcs:List Charhc:Csexp.decOf i = c :: cs c cs, print (WType.mk (i, 0) f) = c :: cs (c = '(' (Csexp.charDigit c).isSome = true) All goals completed! 🐙 k:i:Fin kn:f:Rose.Arity (i, n + 1) WType Rose.Arity c cs, print (WType.mk (i, n + 1) f) = c :: cs (c = '(' (Csexp.charDigit c).isSome = true) All goals completed! 🐙

A spelling is already stripped: neither possible head is whitespace, so the skip is the identity on a spelling followed by anything.

theorem skipWs_print_append {k : Nat} (r : Rose k) (rest : List Char) : skipWs (print r ++ rest) = print r ++ rest := k:r:Rose krest:List CharskipWs (print r ++ rest) = print r ++ rest k:r:Rose krest:List Charc:Charcs:List Charhc:print r = c :: cshd:c = '(' (Csexp.charDigit c).isSome = trueskipWs (print r ++ rest) = print r ++ rest k:r:Rose krest:List Charc:Charcs:List Charhc:print r = c :: cshd:c = '(' (Csexp.charDigit c).isSome = truehw:isWs c = falseskipWs (print r ++ rest) = print r ++ rest All goals completed! 🐙

The rest = [] instance of skipWs_print_append, which is the form the entry point uses.

theorem skipWs_print {k : Nat} (r : Rose k) : skipWs (print r) = print r := k:r:Rose kskipWs (print r) = print r k:r:Rose kh:skipWs (print r ++ []) = print r ++ []skipWs (print r) = print r rwa [k:r:Rose kh:skipWs (print r) = print rskipWs (print r) = print rk:r:Rose kh:skipWs (print r) = print rskipWs (print r) = print r at h

A child block with its terminator never begins with a digit: it begins with the terminator when empty and with a separating space otherwise. Stating it over the terminated block is what makes the empty case true.

theorem block_append_head_not_digit {k : Nat} (ts : List (Rose k)) (rest : List Char) : c cs, (ts.map (fun t ' ' :: print t)).flatten ++ ')' :: rest = c :: cs Csexp.charDigit c = none := k:ts:List (Rose k)rest:List Char (c : Char) (cs : List Char), (List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest = c :: cs Csexp.charDigit c = none k:ts:List (Rose k)rest:List Charc:Charcs:List Charh:(List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest = c :: csCsexp.charDigit c = none cases ts with k:rest:List Charc:Charcs:List Charh:(List.map (fun t ' ' :: print t) []).flatten ++ ')' :: rest = c :: csCsexp.charDigit c = none k:rest:List Charc:Charcs:List Charh:')' :: rest = c :: csCsexp.charDigit c = none k:rest:List Charc:Charcs:List Charh1:')' = ctail_eq✝:rest = csCsexp.charDigit c = none k:rest:List Charcs:List Chartail_eq✝:rest = csCsexp.charDigit ')' = none All goals completed! 🐙 k:rest:List Charc:Charcs:List Chart:Rose kts:List (Rose k)h:(List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest = c :: csCsexp.charDigit c = none k:rest:List Charc:Charcs:List Chart:Rose kts:List (Rose k)h:' ' :: (print t ++ (List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest) = c :: csCsexp.charDigit c = none k:rest:List Charc:Charcs:List Chart:Rose kts:List (Rose k)h1:' ' = ctail_eq✝:print t ++ (List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest = csCsexp.charDigit c = none k:rest:List Charcs:List Chart:Rose kts:List (Rose k)tail_eq✝:print t ++ (List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest = csCsexp.charDigit ' ' = none All goals completed! 🐙

Parser

One layer of the recursive descent. A tree is a bare numeral or a parenthesized list, so this branches on the first character. It strips whitespace at four sites: after (, after the label in each branch, and after the child list. The strip after the parenthesized branch's label is the one the grammar makes least obvious: Rose.parseChildren tests its input's head against ')' immediately, so it must be called on stripped input or (0 1 2) fails at its first child. parseStep is called on stripped input and returns a stripped remainder; that invariant is what lets the shared loop be reused.

def parseStep (k : Nat) (childParse : List Char Option (Rose k × List Char)) (loopFuel : Nat) : List Char Option (Rose k × List Char) | [] => none | c :: cs => if c = '(' then match Csexp.readNat (skipWs cs) with | some (m, cs1) => if h : m < k then (Rose.parseChildren childParse loopFuel (skipWs cs1)).map fun p (Rose.ofList m, h p.1, skipWs p.2) else none | none => none else match Csexp.readNat (c :: cs) with | some (m, cs1) => if h : m < k then some (Rose.node m, h Fin.elim0, skipWs cs1) else none | none => none

Recursive descent over the readable spelling. The Nat bounds the recursion and serves in two roles at each layer: undecremented as the child loop's bound, and decremented as the child parser's fuel.

def parseAux (k : Nat) : Nat List Char Option (Rose k × List Char) := Nat.rec (motive := fun _ List Char Option (Rose k × List Char)) (fun _ none) fun f ih parseStep k ih (f + 1)
@[simp] theorem parseAux_succ (k f : Nat) : parseAux k (f + 1) = parseStep k (parseAux k f) (f + 1) := rfl

The parser of the readable spelling, rejecting trailing input. The leading strip and the stripping invariant together are what let a leading indent and a trailing newline parse.

def parse (k : Nat) (cs : List Char) : Option (Rose k) := match parseAux k cs.length (skipWs cs) with | some (r, []) => some r | _ => none
theorem parseStep_open (k : Nat) (childParse : List Char Option (Rose k × List Char)) (loopFuel : Nat) (cs : List Char) : parseStep k childParse loopFuel ('(' :: cs) = match Csexp.readNat (skipWs cs) with | some (m, cs1) => if h : m < k then (Rose.parseChildren childParse loopFuel (skipWs cs1)).map fun p (Rose.ofList m, h p.1, skipWs p.2) else none | none => none := rfltheorem parseStep_other (k : Nat) (childParse : List Char Option (Rose k × List Char)) (loopFuel : Nat) (c : Char) (cs : List Char) (hc : c '(') : parseStep k childParse loopFuel (c :: cs) = match Csexp.readNat (c :: cs) with | some (m, cs1) => if h : m < k then some (Rose.node m, h Fin.elim0, skipWs cs1) else none | none => none := ite_eq_right hc

The retraction law

The child loop reads back a printed child sequence, given a child parser that reads back each child from its own spelling and returns the stripped remainder, and one unit of fuel per child plus one for the closing parenthesis. The loop's input is stripped, as every call site's is; the remainder it returns is not, since parseStep strips what it returns.

theorem parseChildren_print {k : Nat} (childParse : List Char Option (Rose k × List Char)) : (ts : List (Rose k)) (fuel : Nat) (rest : List Char), ( t ts, r : List Char, ( c cs, r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((ts.map (fun t ' ' :: print t)).flatten ++ ')' :: rest)) = some (ts, rest) := List.rec (motive := fun ts (fuel : Nat) (rest : List Char), ( t ts, r : List Char, ( c cs, r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((ts.map (fun t ' ' :: print t)).flatten ++ ')' :: rest)) = some (ts, rest)) (fun fuel rest _ hfuel k:childParse:List Char Option (Rose k × List Char)fuel:rest:List Charx✝: t [], (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)hfuel:[].length < fuelRose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) []).flatten ++ ')' :: rest)) = some ([], rest) k:childParse:List Char Option (Rose k × List Char)rest:List Charx✝: t [], (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)g:hfuel:[].length < g + 1Rose.parseChildren childParse (g + 1) (skipWs ((List.map (fun t ' ' :: print t) []).flatten ++ ')' :: rest)) = some ([], rest) All goals completed! 🐙) (fun t ts ih fuel rest hchild hfuel k:childParse:List Char Option (Rose k × List Char)t:Rose kts:List (Rose k)ih: (fuel : ) (rest : List Char), (∀ t ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest)) = some (ts, rest)fuel:rest:List Charhchild: t_1 t :: ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t_1 ++ r) = some (t_1, skipWs r)hfuel:(t :: ts).length < fuelRose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest)) = some (t :: ts, rest) k:childParse:List Char Option (Rose k × List Char)t:Rose kts:List (Rose k)ih: (fuel : ) (rest : List Char), (∀ t ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest)) = some (ts, rest)rest:List Charhchild: t_1 t :: ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t_1 ++ r) = some (t_1, skipWs r)g:hfuel:(t :: ts).length < g + 1Rose.parseChildren childParse (g + 1) (skipWs ((List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest)) = some (t :: ts, rest) k:childParse:List Char Option (Rose k × List Char)t:Rose kts:List (Rose k)ih: (fuel : ) (rest : List Char), (∀ t ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest)) = some (ts, rest)rest:List Charhchild: t_1 t :: ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t_1 ++ r) = some (t_1, skipWs r)g:hfuel:(t :: ts).length < g + 1c:Charcs:List Charhc:print t = c :: cshd:c = '(' (Csexp.charDigit c).isSome = trueRose.parseChildren childParse (g + 1) (skipWs ((List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest)) = some (t :: ts, rest) k:childParse:List Char Option (Rose k × List Char)t:Rose kts:List (Rose k)ih: (fuel : ) (rest : List Char), (∀ t ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest)) = some (ts, rest)rest:List Charhchild: t_1 t :: ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t_1 ++ r) = some (t_1, skipWs r)g:hfuel:(t :: ts).length < g + 1c:Charcs:List Charhc:print t = c :: cshd:c = '(' (Csexp.charDigit c).isSome = truehlt:ts.length < gRose.parseChildren childParse (g + 1) (skipWs ((List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest)) = some (t :: ts, rest) k:childParse:List Char Option (Rose k × List Char)t:Rose kts:List (Rose k)ih: (fuel : ) (rest : List Char), (∀ t ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest)) = some (ts, rest)rest:List Charhchild: t_1 t :: ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t_1 ++ r) = some (t_1, skipWs r)g:hfuel:(t :: ts).length < g + 1c:Charcs:List Charhc:print t = c :: cshd:c = '(' (Csexp.charDigit c).isSome = truehlt:ts.length < ghne:c ')'Rose.parseChildren childParse (g + 1) (skipWs ((List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest)) = some (t :: ts, rest) k:childParse:List Char Option (Rose k × List Char)t:Rose kts:List (Rose k)ih: (fuel : ) (rest : List Char), (∀ t ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t ++ r) = some (t, skipWs r)) ts.length < fuel Rose.parseChildren childParse fuel (skipWs ((List.map (fun t ' ' :: print t) ts).flatten ++ ')' :: rest)) = some (ts, rest)rest:List Charhchild: t_1 t :: ts, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) childParse (print t_1 ++ r) = some (t_1, skipWs r)g:hfuel:(t :: ts).length < g + 1c:Charcs:List Charhc:print t = c :: cshd:c = '(' (Csexp.charDigit c).isSome = truehlt:ts.length < ghne:c ')'hcons: (u : List Char), print t ++ u = c :: (cs ++ u)Rose.parseChildren childParse (g + 1) (skipWs ((List.map (fun t ' ' :: print t) (t :: ts)).flatten ++ ')' :: rest)) = some (t :: ts, rest) All goals completed! 🐙)

The parser inverts the printer on printed input, given fuel at least the printed length. The remainder returned is the caller's stripped: a childless node's spelling ends in a digit, so the delimiting hypothesis on the remainder is what keeps the label from running on, and the stripping invariant makes what comes back skipWs rest rather than rest.

theorem parseAux_print {k : Nat} (r : Rose k) : (f : Nat) (rest : List Char), (print r).length f ( c cs, rest = c :: cs Csexp.charDigit c = none) parseAux k f (print r ++ rest) = some (r, skipWs rest) := WType.rec (motive := fun r (f : Nat) (rest : List Char), (print r).length f ( c cs, rest = c :: cs Csexp.charDigit c = none) parseAux k f (print r ++ rest) = some (r, skipWs rest)) (fun s ch ih f rest hf hrest k:r:Rose ks:Rose.Shape kch:Rose.Arity s WType Rose.Arityih: (a : Rose.Arity s) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)f:rest:List Charhf:(print (WType.mk s ch)).length fhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = noneparseAux k f (print (WType.mk s ch) ++ rest) = some (WType.mk s ch, skipWs rest) k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kn:ch:Rose.Arity (i, n) WType Rose.Arityih: (a : Rose.Arity (i, n)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(print (WType.mk (i, n) ch)).length fparseAux k f (print (WType.mk (i, n) ch) ++ rest) = some (WType.mk (i, n) ch, skipWs rest) k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kn:ch:Rose.Arity (i, n) WType Rose.Arityih: (a : Rose.Arity (i, n)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(print (Rose.node i ch)).length fparseAux k f (print (WType.mk (i, n) ch) ++ rest) = some (WType.mk (i, n) ch, skipWs rest) k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kn:ch:Rose.Arity (i, n) WType Rose.Arityih: (a : Rose.Arity (i, n)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(print (Rose.node i ch)).length fparseAux k f (print (Rose.node i ch) ++ rest) = some (Rose.node i ch, skipWs rest) cases n with k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(print (Rose.node i ch)).length fparseAux k f (print (Rose.node i ch) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(Csexp.decOf i).length fparseAux k f (Csexp.decOf i ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(Csexp.decOf i).length fc:Charcs:List Charhc:Csexp.decOf i = c :: csparseAux k f (Csexp.decOf i ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)c:Charcs:List Charhc:Csexp.decOf i = c :: csg:hf:(Csexp.decOf i).length g + 1parseAux k (g + 1) (Csexp.decOf i ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)c:Charcs:List Charhc:Csexp.decOf i = c :: csg:hf:(Csexp.decOf i).length g + 1(match some (i, rest) with | some (m, cs1) => if h : m < k then some (Rose.node m, h Fin.elim0, skipWs cs1) else none | none => none) = some (Rose.node i ch, skipWs rest) -- reduce the match on the `some` just produced, exposing the label-bound check k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)c:Charcs:List Charhc:Csexp.decOf i = c :: csg:hf:(Csexp.decOf i).length g + 1(if h : i < k then some (Rose.node i, h Fin.elim0, skipWs rest) else none) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin kch:Rose.Arity (i, 0) WType Rose.Arityih: (a : Rose.Arity (i, 0)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)c:Charcs:List Charhc:Csexp.decOf i = c :: csg:hf:(Csexp.decOf i).length g + 1some (Rose.node i, Fin.elim0, skipWs rest) = some (Rose.node i ch, skipWs rest) All goals completed! 🐙 k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:(print (Rose.node i ch)).length fparseAux k f (print (Rose.node i ch) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose kf:rest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length fparseAux k f ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) cases f with k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length 0parseAux k 0 ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) All goals completed! 🐙 k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1parseAux k (g + 1) ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1parseAux k (g + 1) ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length gparseAux k (g + 1) ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length ghchild: t List.ofFn ch, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) parseAux k g (print t ++ r) = some (t, skipWs r)parseAux k (g + 1) ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length ghchild: t List.ofFn ch, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) parseAux k g (print t ++ r) = some (t, skipWs r)hfuel:(List.ofFn ch).length < g + 1parseAux k (g + 1) ('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')'])) ++ rest) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length ghchild: t List.ofFn ch, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) parseAux k g (print t ++ r) = some (t, skipWs r)hfuel:(List.ofFn ch).length < g + 1(match Csexp.readNat (skipWs (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']) ++ rest)) with | some (m, cs1) => if h : m < k then Option.map (fun p (Rose.ofList m, h p.1, skipWs p.2)) (Rose.parseChildren (parseAux k g) (g + 1) (skipWs cs1)) else none | none => none) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length ghchild: t List.ofFn ch, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) parseAux k g (print t ++ r) = some (t, skipWs r)hfuel:(List.ofFn ch).length < g + 1(match Csexp.readNat (skipWs (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ ')' :: rest))) with | some (m, cs1) => if h : m < k then Option.map (fun p (Rose.ofList m, h p.1, skipWs p.2)) (Rose.parseChildren (parseAux k g) (g + 1) (skipWs cs1)) else none | none => none) = some (Rose.node i ch, skipWs rest) k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length ghchild: t List.ofFn ch, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) parseAux k g (print t ++ r) = some (t, skipWs r)hfuel:(List.ofFn ch).length < g + 1(match some (i, (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ ')' :: rest) with | some (m, cs1) => if h : m < k then Option.map (fun p (Rose.ofList m, h p.1, skipWs p.2)) (Rose.parseChildren (parseAux k g) (g + 1) (skipWs cs1)) else none | none => none) = some (Rose.node i ch, skipWs rest) -- reduce the match on the `some` just produced, exposing the label-bound check k:r:Rose krest:List Charhrest: (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = nonei:Fin km:ch:Rose.Arity (i, m + 1) WType Rose.Arityih: (a : Rose.Arity (i, m + 1)) (f : ) (rest : List Char), (print (ch a)).length f (∀ (c : Char) (cs : List Char), rest = c :: cs Csexp.charDigit c = none) parseAux k f (print (ch a) ++ rest) = some (ch a, skipWs rest)g:hf:('(' :: (Csexp.decOf i ++ ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ [')']))).length g + 1hlen:(Csexp.decOf i).length + (List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length + 2 g + 1hL:(List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten.length ghchild: t List.ofFn ch, (r : List Char), (∀ (c : Char) (cs : List Char), r = c :: cs Csexp.charDigit c = none) parseAux k g (print t ++ r) = some (t, skipWs r)hfuel:(List.ofFn ch).length < g + 1(if h : i < k then Option.map (fun p (Rose.ofList i, p.1, skipWs p.2)) (Rose.parseChildren (parseAux k g) (g + 1) (skipWs ((List.map (fun t ' ' :: print t) (List.ofFn ch)).flatten ++ ')' :: rest))) else none) = some (Rose.node i ch, skipWs rest) All goals completed! 🐙) r

The retraction law for the readable spelling: printing a rose tree and parsing the result returns that tree.

theorem parse_print {k : Nat} (r : Rose k) : parse k (print r) = some r := k:r:Rose kparse k (print r) = some r k:r:Rose k(match parseAux k (print r).length (skipWs (print r)) with | some (r, []) => some r | x => none) = some r All goals completed! 🐙

The generic corollaries, instantiated here

Geb.Retraction at the readable spelling.

theorem retraction (k : Nat) : Retraction (parse k) (print (k := k)) := parse_print

Geb.format_idem at the readable spelling.

theorem format_idem (k : Nat) (c : List Char) : (format (parse k) print c).bind (format (parse k) print) = format (parse k) print c := Geb.format_idem _ _ (retraction k) c

Geb.print_injective at the readable spelling.

theorem print_injective (k : Nat) : Function.Injective (print (k := k)) := Geb.print_injective _ _ (retraction k)

The Ast composites

The readable spelling of an Ast k, through the rose bijection.

def printViaRose {k : Nat} (a : Ast k) : List Char := print a.toRose

The parser matching printViaRose.

def parseViaRose (k : Nat) (cs : List Char) : Option (Ast k) := (parse k cs).map Ast.ofRose

The retraction law for the Ast composites, transported along the rose retraction by Ast.ofRose_toRose.

theorem parseViaRose_printViaRose {k : Nat} (a : Ast k) : parseViaRose k (printViaRose a) = some a := k:a:Ast kparseViaRose k (printViaRose a) = some a All goals completed! 🐙
end Rsexpend Geb