Recursive descent works top-down: it starts at the start symbol and guesses its way toward the
leaves. Bottom-up parsing runs the film backwards. It starts with the raw tokens — the leaves —
and repeatedly recognises little completed phrases, gluing them into ever-larger subtrees until the whole
input has been folded up into the start symbol at the root. Where top-down parsing predicts,
bottom-up parsing waits and confirms: it never commits to a production until it has seen the
entire right-hand side. That patience is exactly what makes bottom-up parsers strictly more powerful, and
it is the foundation of every
The dominant bottom-up technique is shift-reduce parsing. Picture a stack on the left and the unread input on the right. At every step you make one of two basic moves: push the next input token onto the stack (shift), or recognise that the top few stack symbols form the right-hand side of some production and replace them with its left-hand nonterminal (reduce). Keep going until the stack holds just the start symbol and the input is empty — that is accept.
Write the configuration as stack | remaining input, with a marker
(
A shift defers a decision ("I'll gather more before I judge"); a reduce is the moment of commitment where a subtree is finally built. A run of the parser is therefore a sequence of shifts and reduces, and the reduces — read in the order they happen — spell out the parse.
Not every substring that matches a production body may be reduced. Reducing the wrong one leads
to a dead end. The substring you are actually permitted to fold at a given step is called the
handle. Formally, for a right-sentential form
This connects to a deep symmetry. A shift-reduce parse traces a rightmost derivation in reverse. A top-down parser expands the start symbol forward down to the tokens; a bottom-up parser, by pruning handles, walks a rightmost derivation backward from the tokens up to the start symbol. Each reduce undoes one step of that rightmost derivation, which is why bottom-up parsers are also called LR — Left-to-right scan, Rightmost derivation.
Take the classic ambiguity-free expression grammar
| Step | Stack | Input | Action |
|---|---|---|---|
| 1 | shift | ||
| 2 | reduce | ||
| 3 | reduce | ||
| 4 | reduce | ||
| 5 | shift | ||
| 6 | shift | ||
| 7 | reduce | ||
| 8 | reduce | ||
| 9 | shift | ||
| 10 | shift | ||
| 11 | reduce | ||
| 12 | reduce | ||
| 13 | reduce | ||
| 14 | accept |
Read the reduce lines from bottom to top:
The mechanics of shift-reduce parsing are trivial once someone tells you which action to take at
each step. Below, an oracle plays the role of the (as-yet-unbuilt) LR table: it looks at the
stack and the lookahead and returns the action. The driver just obeys — shifting, reducing, and printing
the trace. This is deliberately the whole point of the LR machinery to come: build a real
oracle from the grammar.
The trace it prints matches the table above line for line. The driver contains no intelligence at all —
every genuinely hard decision lives inside oracle. Replace that ad-hoc oracle with a
table computed from the grammar and you have a real, general parser. Building that table honestly is the
entire subject of the next few pages.
You could, and that is essentially what a backtracking bottom-up parser does — with disastrous cost.
Several stack tops may look reducible at once, but only one choice keeps you on the path to a
valid parse; the others lead to eventual failure and a forced rewind. The genius of LR parsing, due to
Donald Knuth in 1965, is that a deterministic finite automaton run over the stack can
identify the handle in constant time per step, with no guessing or backtracking, for a
remarkably large class of grammars. The DFA's states summarise "everything about the stack that could
matter" — the set of viable prefixes seen so far — so the parser always knows whether the top of the
stack is a complete handle or merely a prefix of one. That automaton is the star of the
A shift-reduce parser is only as good as its oracle, and for some grammar-and-lookahead situations no single correct action exists — the grammar itself is at fault (or too weak for the parsing method). Two kinds of deadlock arise:
if C then S with an else waiting, should you reduce the
if-then now, or shift the else to attach it to this if? Tools
resolve this by a default (usually: prefer shift, binding else to the nearest
if), but the conflict is real.
The whole hierarchy of LR parsers — SLR, LALR, canonical LR(1) — is a graded answer to how much lookahead information you spend to avoid these conflicts. Finding the handle deterministically is the problem; conflicts are what it looks like when you can't.