Railroad diagrams
The railroad package turns a grammar written in W3C-style EBNF into railroad (syntax) diagrams — one picture per rule, drawn over the vector-graphics layer. Load it with:
The railroad package turns a grammar written in W3C-style EBNF into railroad (syntax)
diagrams — one picture per rule, drawn over the vector-graphics layer.
Load it with:
\use{railroad}
Drawing a grammar
\railroad takes its grammar verbatim (like \verb or \url), so the EBNF specials —
::=, |, ?, *, +, (, ), ", ' — pass through untouched. Each rule becomes its
own self-contained diagram, complete with a bold rule-name title, so it needs no surrounding
spacing or paragraph commands:
\railroad{
greeting ::= "hello" name ("," name)*
name ::= [A-Za-z] [A-Za-z0-9]*
}
Grammar syntax
A grammar is a list of rules, each name ::= expression. An expression is built from:
| Notation | Meaning | Drawn as |
|---|---|---|
name | a nonterminal | a square box |
"…" or '…' | a terminal (literal text) | a rounded stadium, in bold |
[…] | a character class | a pointed hexagon, in bold |
a b c | juxtaposition (sequence) | boxes in a row |
a \| b \| c | alternation (choice) | stacked alternatives joined by a fork |
( … ) | grouping | (transparent — no box of its own) |
x? | optional | the skip on the main line, the item on a track below |
x* | zero or more | a skip rail and a repeat loop |
x+ | one or more | a repeat loop |
A terminal that itself contains a quote is wrapped in the other quote: '"' is a literal
double-quote, "'" a literal single-quote. Spaces inside a terminal are kept ("end of line" is one box). Labels are set in JetBrains Mono.
Readable idioms
Two common grammar shapes are recognised and drawn the intuitive way rather than literally:
- Separated lists —
A ( sep A )*(withAa single symbol), the usual “one or moreAseparated bysep“ pattern, is drawn asAon the line withsepriding a return loop above it, instead of “Athen zero-or-more of (sep A)”. Soexpression ::= term ( "|" term )*shows a singletermbox with|on the loop. - Optional choices —
( A | B | C )?folds the skip into the choice itself, as an empty top branch on the main line with the alternatives forked below, rather than nesting the choice inside a separate optional. A plainx?draws the same way — the skip on the main line, the item on a single track below — so one optional and an optional choice match.
Customising the look
Set any of these with \set before calling \railroad. Colours are the most useful: the
whole palette is derived from one base colour, with terminals and character classes shaded from
it in Oklch lightness so they read as shades of
the same hue.
The base and the ink default to auto, so a railroad diagram takes its colours from the document
around it: the rails are drawn in the pen, and the boxes in a tint of the page. The shading of
terminals and character classes is toward the ink, which is darker on white paper and lighter
on a dark page — always-darker would push a terminal past the page and out of sight exactly where
it is meant to stand out.
| Variable | Default | Effect |
|---|---|---|
rrbase | auto | base fill colour for the boxes; auto derives a tint of the page |
rrtintl / rrtintc / rrtinth | 0.0716 / 0.0284 / 261.5 | the auto base: how far its lightness moves from the page toward the ink, and the tint’s Oklch chroma and hue |
rrtermdark | 0.08 | how far to shade a terminal box from the base toward the ink |
rrccdark | 0.17 | how far to shade a character-class box toward the ink |
rrlinecolor | auto | ink for the rails and box outlines; auto follows the document’s pen |
rrface | jetbrains | typeface for the labels |
rrfont / rrtitlefont | 9 / 10 | label and rule-title point sizes |
rrboxh / rrpadx | 20 / 11 | box height and horizontal padding |
rrhgap / rrvgap | 15 / 11 | gaps between boxes in a row / stacked alternatives |
\set rrbase {#ffe6cc} // a warm palette instead of the default blue
\railroad{ digit ::= [0-9] }