Markdown documents: printing, parsing, and the round trip
printMd
spells a canonical ESM document as Markdown, parseMd
reads it back, and
parseMd_printMd
is the round trip. The layers compose:
toDoc capsMd(the skeleton, with the path-keyed fallback for anything Markdown cannot spell natively) andofDoc—ofDoc_toDoc;rawDoc prMdx(each equation, and each reaction’s rate, as LaTeX under its component’s symbol table) andcookDoc ckMdx—cookDoc_rawDoc, with the Phase 2 theoremparseLatex_printLatex;itemsandunitems—unitems_items;- the text: front matter, then the constructs separated by blank lines —
lex_render.
Equations in LaTeX
def latexOf (S : Sym) (e : Eqn) : List Char
An equation’s LaTeX under the symbol table S.
def prMd (S : Sym) (e : Eqn) : String
An equation’s math text: its _comment as a LaTeX comment line, then the equation.
def envs : List (String × Bool)
The multi-line environments: true when all rows are one equation.
def envOf (body : List Char) : Option (Bool × List Char)
A display that is one multi-line environment: whether its rows are one equation, and the environment’s body.
def withComment (c : Option String) : List Eqn → List Eqn
The comment goes to the first equation of a display.
def oneMd (S : Sym) (c : Option String) (body : List Char) : Option (List Eqn)
One display in LaTeX: a multi-line environment (an accepted variant), or one equation.
def ckMd (S : Sym) (raw : String) : Option (List Eqn)
Read the math text of a display equation under the symbol table S: an
optional leading comment line % … (the _comment), then the equation.
def prMdx : Sym → MC → MR
A math entry’s text: an equation’s LaTeX (prMd
), or a reaction with its rate as
LaTeX.
def commentOK : Option String → Bool
A comment Markdown can carry on its LaTeX comment line.
What Markdown spells natively
def dqOK : Json → Bool
A value remark-directive reads as written in a double-quoted attribute: a string
whose characters the YAML printer writes as themselves (no ", which would end the
quoted value; no backslash, tab, newline, carriage return or other character written as
an escape, which remark-directive would not decode) and without & (a character
reference would be decoded); any other value.
def capsMd : Caps
The Markdown container’s predicates. A description must not contain a line that
would close its fence (noStop
; CommonMark also closes a fence indented by up to three
spaces, and treats a carriage return as a line end). An equation’s LaTeX must be one
line without $ and not start with %.
theorem ckMd_prMd (S : Sym) (e : Eqn) (hc : capsMd.eqn S e = true) (hl : isE e.lhs = true)
(hr : isE e.rhs = true) : ckMd S (prMd S e) = some [e]
An equation reads back from its LaTeX, comment included.
The document
def frontMd (front : List (String × Json)) : List Char
The front matter: ---, the YAML of the entries, ---.
def canonSegs (its : List RItem) : List Seg
The canonical layout: each construct after a blank line, a declaration on a line of its own.
def lexDoc (s : List Char) : Option (List (String × Json) × List RItem)
Read the front matter and the constructs.
def parseMd (s : String) : Option Json
Read a Markdown document, after dropping a byte-order mark and reading CRLF line
endings as LF (decodeText
).
theorem lexDoc_front (front : List (String × Json)) (hf : jwf (.obj front) = true)
(segs : List Seg) (hv : Valid true segs) :
lexDoc (frontMd front ++ render segs) = some (front, segItems segs)
The front matter and the body read back.
theorem decode_front (front : List (String × Json)) (X : List Char) :
decodeText (frontMd front ++ X) = frontMd front ++ X
A document with front matter is read as written: decodeText
leaves it alone.
theorem items_ok (D : Doc MC) (h : DocOK capsMd D) :
∀ it ∈ items (rawDoc prMdx D), itemOK it = true
Every construct of the printed skeleton satisfies itemOK
.
theorem parseMd_printMd (d : Json) (hd : canonDoc d = true) : parseMd (printMd d) = some d
The Markdown round trip. Every ESM document in canonical form reads back from its Markdown spelling.
Prose and labels do not change the parse
def parseItemsMd (front : List (String × Json)) (its : List RItem) : Option Json
What a document says, given its front matter and its constructs.
theorem parseMd_body (front : List (String × Json)) (hf : jwf (.obj front) = true)
(segs : List Seg) (hv : Valid true segs) :
parseMd (String.ofList (frontMd front ++ render segs)) = parseItemsMd front (segItems segs)
The parse of a Markdown document depends only on its front matter and its sequence of constructs: not on the prose between them, and not on equation labels.
theorem parseMd_eq_of_items (front : List (String × Json)) (hf : jwf (.obj front) = true)
(s₁ s₂ : List Seg) (h₁ : Valid true s₁) (h₂ : Valid true s₂) (h : segItems s₁ = segItems s₂) :
parseMd (String.ofList (frontMd front ++ render s₁)) =
parseMd (String.ofList (frontMd front ++ render s₂))
Two valid documents with the same constructs parse alike.
theorem segItems_prose (a b : List Seg) (p : List Char) :
segItems (a ++ .prose p :: b) = segItems (a ++ b)
Inserting prose leaves the constructs as they were.
theorem segItems_label (a b : List Seg) (raw : String) (l : List Char) :
segItems (a ++ .labeled raw l :: b) = segItems (a ++ .item (.eqn raw) :: b)
Labelling an equation leaves the constructs as they were.
theorem parseMd_insert_prose (front : List (String × Json)) (hf : jwf (.obj front) = true)
(a b : List Seg) (p : List Char) (h₁ : Valid true (a ++ b)) (h₂ : Valid true (a ++ .prose p :: b)) :
parseMd (String.ofList (frontMd front ++ render (a ++ .prose p :: b))) =
parseMd (String.ofList (frontMd front ++ render (a ++ b)))
Inserting prose does not change the parse (where the result is still valid: the
prose is Inert
and block constructs still start a line).
theorem parseMd_label (front : List (String × Json)) (hf : jwf (.obj front) = true)
(a b : List Seg) (raw : String) (l : List Char) (h₁ : Valid true (a ++ .item (.eqn raw) :: b))
(h₂ : Valid true (a ++ .labeled raw l :: b)) :
parseMd (String.ofList (frontMd front ++ render (a ++ .labeled raw l :: b))) =
parseMd (String.ofList (frontMd front ++ render (a ++ .item (.eqn raw) :: b)))
Adding an equation label does not change the parse.
Multi-line equations
theorem ckMd_aligned (S : Sym) (Ls : List Layout) (es : List Eqn)
(hL : ∀ L ∈ Ls, L.OK) (hne : Ls ≠ [])
(h : List.Forall₂ (fun L e => L.toks = print .latex S (eqExpr e) ∧ isE e.lhs = true ∧
isE e.rhs = true) Ls es) :
ckMd S (String.ofList ("\\begin{aligned}".toList ++ Latex.render (rowsToks Ls) ++
"\\end{aligned}".toList)) = some (es.map (fun e => ⟨none, e.lhs, e.rhs⟩))
The aligned variant reads as the equations it lays out: a display
\begin{aligned} … \end{aligned} whose rows lay out the canonical LaTeX of equations
(Layout
) reads back as exactly those equations.