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 item with a skip rail bypassing it
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 arching a separate bypass over a nested choice.

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 drawn at a lower Oklch lightness so they read as shades of the same hue.

VariableDefaultEffect
rrbase#dde8fbbase fill colour for the boxes
rrtermdark0.08how far to darken (lower Oklch L) a terminal box
rrccdark0.17how far to darken a character-class box
rrlinecolorblackink for the rails and box outlines
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