Version: dev

This is the documentation for the development version. For the latest release, see v0.12.1.

Markdown documents: printing, parsing, and the round trip

Lean module Esm.Document.Source.MarkdownDoc, formal/Esm/Document/Source/MarkdownDoc.lean at 184aa13d35e5
Imports Esm.Document.Source.Markdown, Esm.Document.Source.Equation, Esm.Document.Source.MultiLine

printMd spells a canonical ESM document as Markdown, parseMd reads it back, and parseMd_printMd is the round trip. The layers compose:

  1. toDoc capsMd (the skeleton, with the path-keyed fallback for anything Markdown cannot spell natively) and ofDoc — ofDoc_toDoc ;
  2. rawDoc prMdx (each equation, and each reaction’s rate, as LaTeX under its component’s symbol table) and cookDoc ckMdx — cookDoc_rawDoc , with the Phase 2 theorem parseLatex_printLatex;
  3. items and unitems — unitems_items ;
  4. 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 ckMdx : Sym → MR → Option (List MC)

Read a math entry’s text.

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 proved source
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 printMd (d : Json) : String

Print a document as Markdown.

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 proved source
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 proved source
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 proved source
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 proved source
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 proved source
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 proved source
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 proved source
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 proved source
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 proved source
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.

This module also proves 9 lemmas without doc comments; they are in the source.