LL(1) Predictive Parsing Tables

Everything so far has been preparation. We took a grammar, stripped its left recursion and common prefixes, and computed its FIRST and FOLLOW sets. Now we cash it all in. An LL(1) parser is a beautifully simple machine: an explicit stack, a pointer into the input, and a two-dimensional lookup table that tells it, deterministically, exactly what to do next. No backtracking, no guessing, linear time. The table is the compiler's crystallised knowledge of the grammar.

The name decodes as: L — scan the input Left to right; L — produce a Leftmost derivation; (1) — using one token of lookahead. That single lookahead token, matched against the top-of-stack nonterminal, indexes the table and picks the production. If one token always suffices to choose, the grammar is LL(1).

Building the table M[A, a]

The table has a row per nonterminal A and a column per terminal a (including the end-marker \$). Each cell M[A, a] holds the production to expand A by when the next input token is a — or is blank, meaning "error". The construction rule is two lines, and it is entirely driven by FIRST and FOLLOW:

The intuition is exactly the two questions FIRST and FOLLOW answer. FIRST tells you "this production can start with token a, so use it on a". FOLLOW handles the vanishing case: if \alpha can derive \varepsilon, then choosing A \to \alpha makes A disappear, which is the right move precisely when the next token is one that can legally follow A.

Here is the finished table for the expression grammar. Blank cells are errors:

M[A,a]\mathbf{id}+*()\$
EE \to T E'E \to T E'
E'E' \to + T E'E' \to \varepsilonE' \to \varepsilon
TT \to F T'T \to F T'
T'T' \to \varepsilonT' \to * F T'T' \to \varepsilonT' \to \varepsilon
FF \to \mathbf{id}F \to (\,E\,)

Every cell holds at most one production. That is the signature of an LL(1) grammar — and the whole point.

The table-driven predictive parser

The engine that drives the table never changes — only the table does. Push the start symbol and \$ onto a stack, append \$ to the input, then loop on the top-of-stack symbol X against the current input token a:

The picture to keep in your head — stack on one side, input buffer on the other, the table as an oracle in the middle emitting productions:

Watch it parse \mathbf{id} + \mathbf{id} * \mathbf{id}. The stack is shown with its top on the left; each row is one loop iteration:

Stack (top left)InputAction
E\,\$\mathbf{id}+\mathbf{id}*\mathbf{id}\,\$output E \to T E'
T\,E'\,\$\mathbf{id}+\mathbf{id}*\mathbf{id}\,\$output T \to F T'
F\,T'\,E'\,\$\mathbf{id}+\dotsoutput F \to \mathbf{id}
\mathbf{id}\,T'\,E'\,\$\mathbf{id}+\dotsmatch \mathbf{id}
T'\,E'\,\$+\,\mathbf{id}*\mathbf{id}\,\$output T' \to \varepsilon
E'\,\$+\,\mathbf{id}*\mathbf{id}\,\$output E' \to + T E'
+\,T\,E'\,\$+\,\mathbf{id}*\mathbf{id}\,\$match +
… (parse the \mathbf{id}*\mathbf{id} term) …
E'\,\$\$output E' \to \varepsilon
\$\$accept

Read the sequence of "output" lines top to bottom and you have reconstructed the leftmost derivation — which is exactly what the two Ls in "LL" promised.

The parser, in code

The whole engine is about twenty lines. The table below is the one we built above; the driver is grammar- independent.

type Prod = string[]; // right-hand side; "eps" = ε type Table = Record<string, Record<string, Prod>>; // M[A][a] = production const M: Table = { E: { id: ["T", "E'"], "(": ["T", "E'"] }, "E'": { "+": ["+", "T", "E'"], ")": ["eps"], "$": ["eps"] }, T: { id: ["F", "T'"], "(": ["F", "T'"] }, "T'": { "+": ["eps"], "*": ["*", "F", "T'"], ")": ["eps"], "$": ["eps"] }, F: { id: ["id"], "(": ["(", "E", ")"] }, }; const isNT = (X: string) => X in M; function parse(input: string[]): boolean { const inp = [...input, "$"]; const stack: string[] = ["$", "E"]; // bottom $, top E let i = 0; while (stack.length) { const X = stack.pop()!; // top of stack const a = inp[i]; if (X === "$") { return a === "$"; } // accept iff input also exhausted if (!isNT(X)) { // terminal: must match if (X === a) { i++; continue; } console.log(`error: expected '${X}', saw '${a}'`); return false; } const prod = M[X]?.[a]; // nonterminal: consult the table if (!prod) { console.log(`error: no rule M[${X}, ${a}]`); return false; } console.log(`${X} -> ${prod.join(" ")}`); if (prod[0] !== "eps") { for (let k = prod.length - 1; k >= 0; k--) stack.push(prod[k]); // push reversed } } return false; } console.log("Parsing id + id * id"); console.log("accepted:", parse(["id", "+", "id", "*", "id"])); console.log("---"); console.log("Parsing id + (malformed)"); console.log("accepted:", parse(["id", "+"]));

The good input prints its leftmost derivation and accepts; the malformed input hits a blank cell / early \$ and is rejected with a precise message. That precision — knowing exactly which token was unexpected — is a real virtue of predictive parsing.

When a cell holds two productions: a conflict

The table construction says "put the production in the cell". What if two different productions both want the same cell M[A, a]? That is a conflict — a multiply-defined entry — and it means the grammar is not LL(1): one token of lookahead cannot decide between the two rules. It happens exactly when two productions for A have overlapping FIRST sets, or when a nullable production's FIRST overlaps A's FOLLOW.

The classic non-LL(1) offender is the dangling-\mathbf{else} grammar: even after left factoring, the helper S' \to \mathbf{else}\ S \mid \varepsilon puts both \mathbf{else} (from FIRST) and — because it is nullable — \mathbf{else} again (from FOLLOW) into the same cell. Real tools break that tie by preferring the non-\varepsilon rule ("shift the \mathbf{else}"), which is precisely the "nearest \mathbf{if}" convention.

One token is a sweet spot: the table is small (nonterminals × terminals) and each step is a single array lookup, so parsing is genuinely linear with a tiny constant. Peeking further ahead is possible — LL(k) indexes the table by the next k tokens, growing its power but blowing the table up combinatorially (columns become k-tuples of terminals). Modern recursive-descent tools like ANTLR go further with LL(*), using an on-the-fly sub-automaton to look ahead a variable, unbounded distance when one token is not enough, falling back to cheap LL(1) decisions when it is. But the humble LL(1) table remains the thing every compiler course builds by hand, because it exposes the entire mechanism — FIRST, FOLLOW, table, stack — with nothing hidden.

Do not expect to feed a raw grammar to the LL(1) construction. Two preconditions are mandatory: the grammar must have no left recursion (or the derivation loops forever) and must be left factored (or two productions share a FIRST prefix and collide in a cell). Those transformations are prerequisites, not optional polish. But here is the sting in the tail: even a grammar that is both non-left-recursive and left-factored can still fail to be LL(1). Left factoring only removes overlaps caused by identical prefixes; overlaps caused by two productions whose FIRST sets happen to share a token further in, or by the nullable-FIRST-meets-FOLLOW case, survive and produce conflicts anyway. LL(1) is a genuinely restricted class. When a grammar resists every rewrite, the honest move is to step up to a more powerful method — bottom-up LR / LALR parsing — rather than to keep torturing the grammar.