Parser Tooling
For a while now, I’ve been working on a variant/subset of LaTeX which I call MiniLaTeX. The idea is to be able write MiniLaTeX documents in the browser and also to have them rendered there … indeed, to have them rendered in real-time as the user types. Good for writing class notes, problem sets, etc. You can see MiniLaTeX in action
- here, in a little demo app
- and here, in a more capable document-management app
Both apps are written in Elm, with the second also using Lamdera, a technology which enables one to write both the front- and backend in Elm, with the two communicating via websockets (read that last word as fast).
As I’ve begun to devote more attention to expanding the scope of MinLaTeX, I realized that I needed a convenient tool for testing changes to the compiler. The compiler can be thought of as the composition of two functions, one of which consumes MiniLaTeX source text and produces a syntax tree, the other which consumes syntax trees and produces HTML, with formulas rendered by either MathJax or KaTeX. The Parser Tool you see in the figure above responds to this need. Enter MiniLaTeX source text in the top left pane and press “Parse.” The syntax tree in “raw” form is displayed in the middle left panel and the rendered text is displayed in the bottom left one.
The fun part is the graphical representation of the tree which you see on the right. It is an SVG image constructed using the library alex-tan/elm-tree-diagram. There is only one non-trivial step, which is in fact quite easy to carry out. This is implementing a function
transform : LaTeXExpression -> Tree String
which consumes a syntax tree and produces a tree whose nodes are labeled by strings. The latter is the kind of tree that alex-tan/elm-tree-diagram can further transform into an SVG image. The transform
function looks like what you see below. At top level, it is a case analysis of the variants which make up aLaTeXExpression
, the type of the syntax tree. The first three cases handle terminal nodes, whereas the next two handle non-terminal nodes using recursive calls to transform
.
transform : LatexExpression -> Tree String
transform expr =
case expr of
LXString str -> TreeDiagram.node ("S: " ++ str) []
InlineMath str -> TreeDiagram.node ("M1: " ++ str) []
DisplayMath str -> TreeDiagram.node ("M2: " ++ str) []
Macro name args optargs ->
TreeDiagram.node ("MA: " ++ name)
[
TreeDiagram.node "args" (List.map transform optargs)
, TreeDiagram.node "optargs" (List.map transform args)
]
LatexList list ->
TreeDiagram.node "LaTeXList" (List.map transform list)
...
That is it — quite simple!