texish

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:

NotationMeaningDrawn as
namea nonterminala square box
"…" or '…'a terminal (literal text)a rounded stadium, in bold
[…]a character classa pointed hexagon, in bold
a b cjuxtaposition (sequence)boxes in a row
a \| b \| calternation (choice)stacked alternatives joined by a fork
( … )grouping(transparent — no box of its own)
x?optionalthe skip on the main line, the item on a track below
x*zero or morea skip rail and a repeat loop
x+one or morea 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 listsA ( sep A )* (with A a single symbol), the usual “one or more A separated by sep“ pattern, is drawn as A on the line with sep riding a return loop above it, instead of “A then zero-or-more of (sep A)”. So expression ::= term ( "|" term )* shows a single term box 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 plain x? 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.

VariableDefaultEffect
rrbaseautobase fill colour for the boxes; auto derives a tint of the page
rrtintl / rrtintc / rrtinth0.0716 / 0.0284 / 261.5the auto base: how far its lightness moves from the page toward the ink, and the tint’s Oklch chroma and hue
rrtermdark0.08how far to shade a terminal box from the base toward the ink
rrccdark0.17how far to shade a character-class box toward the ink
rrlinecolorautoink for the rails and box outlines; auto follows the document’s pen
rrfacejetbrainstypeface for the labels
rrfont / rrtitlefont9 / 10label and rule-title point sizes
rrboxh / rrpadx20 / 11box height and horizontal padding
rrhgap / rrvgap15 / 11gaps between boxes in a row / stacked alternatives
\set rrbase {#ffe6cc}      // a warm palette instead of the default blue
\railroad{ digit ::= [0-9] }

Search

Esc
to navigate to open Esc to close