Yasir Explains/Compiler Design/Parsing/Bottom-Up Parsing
Parsing

Bottom-Up Parsing

On this page

What is Bottom-Up Parsing?Handles — The Heart of Bottom-Up ParsingBottom-Up = Rightmost Derivation in ReverseBuilding the Parse Tree from the BottomWhy Bottom-Up? — Advantages and LimitationsCategories of Bottom-Up Parsers
Parsing

Bottom-Up Parsing

Learn how bottom-up parsers build the parse tree from the leaves to the root by reducing input tokens — and why they handle a much larger class of grammars than top-down parsers.

What is Bottom-Up Parsing?

In bottom-up parsing, the parser starts from the input tokens (leaves) and tries to reduce them, step by step, back to the start symbol (root) of the grammar.

It is the exact opposite of top-down parsing:

DirectionTop-DownBottom-Up
Starts atStart symbol SInput tokens
Builds treeRoot → leavesLeaves → root
Uses derivationLeftmostRightmost (in reverse)
OperationExpand a non-terminalReduce a substring to a non-terminal

The Core Idea

At each step, the parser looks at a chunk of the current sentence and asks:

"Is there a production whose right-hand side matches this chunk? If so, replace the chunk with the left-hand side."

That replacement is called a reduction. Reducing all the way back to the start symbol means the input is valid.


A First Example

Grammar:

Example
E → E + T | T
T → T * F | F
F → ( E ) | id

Input: id + id * id

We will reduce step by step. At each step we underline the substring we are about to reduce and show the production used.

Example
Step Current string Reduction
─────────────────────────────────────────────────────
0 id + id * id (start)
1 F + id * id F → id
2 T + id * id T → F
3 E + id * id E → T
4 E + F * id F → id
5 E + T * id T → F
6 E + T * F F → id
7 E + T T → T * F
8 E E → E + T ← reached start symbol ✓

The parser walked upward — from id + id * id (all leaves) to E (the root). This is why the technique is called bottom-up.

Handles — The Heart of Bottom-Up Parsing

At every step, the parser must decide which substring to reduce. That substring is called a handle.


Definition

A handle of a right-sentential form is a substring that:

  1. Matches the right-hand side of some production, and
  2. Reducing it produces the previous step of a rightmost derivation.

In other words, the handle is the correct place to reduce — the one that will lead the parser back to the start symbol.


Why Not Just Any Match?

Consider the string E + T * id from the grammar above. We could try to reduce in two places:

Example
E + T * id
─┬─
└─ E matches "E"? E → ... no usable reduction without context
E + T * id
──
└─ id matches F → id → safe reduction

Picking the wrong substring leads to a dead end. The handle is the unique correct substring.


Handle in Action

Let's reduce id + id using E → E + T, T → F, F → id.

StringHandleProductionResult
id + idid (first)F → idF + id
F + idFT → FT + id
T + idTE → TE + id
E + ididF → idE + F
E + FFT → FE + T
E + TE + TE → E + TE ✓

The handle is always at or near the right edge of what the parser has seen so far — that's why bottom-up parsers use a stack to track the current state.

Bottom-Up = Rightmost Derivation in Reverse

Bottom-up parsing produces a rightmost derivation in reverse.

A rightmost derivation expands the rightmost non-terminal at every step (top-down view):

Example
E ⟹ E + T (expand the only E, picking E → E + T)
⟹ E + T * F (expand the rightmost T → T * F)
⟹ E + T * id (F → id)
⟹ E + F * id (T → F)
⟹ E + id * id (F → id)
⟹ T + id * id (E → T)
⟹ F + id * id (T → F)
⟹ id + id * id (F → id)

The bottom-up parser performs the same sequence — read bottom to top:

Example
id + id * id ⟸ F → id
F + id * id ⟸ T → F
T + id * id ⟸ E → T
E + id * id ⟸ F → id
E + F * id ⟸ T → F
E + T * id ⟸ F → id
E + T * F ⟸ T → T * F
E + T ⟸ E → E + T
E ✓

Each reduction undoes one step of the rightmost derivation — the parser is literally running the derivation backwards.


Why Rightmost?

Because the handle of any right-sentential form is always located at or to the right of every other non-terminal that still needs to be reduced. Reducing it is equivalent to reversing the most recent step of a rightmost derivation. This is what gives bottom-up parsers their efficiency — they only ever need to look at the top of a stack, not the entire string.

Building the Parse Tree from the Bottom

Top-down parsing draws the tree from the root downward; bottom-up parsing grows the tree from the leaves upward.

Let's revisit the input id + id with grammar E → E + T, T → F, F → id.


Step 1 — Each input token becomes a leaf.

(forest)
id
+
id

Step 2 — Reduce each id using F → id. Two new internal nodes appear.

(forest)
F
id
+
F
id

Step 3 — Reduce each F using T → F.

(forest)
T
F
id
+
T
F
id

Step 4 — Reduce the leftmost T using E → T, then reduce E + T using E → E + T. The forest joins into one tree.

E
E
T
F
id
+
T
F
id

The parse tree is now complete. Notice how internal nodes were always created after their children existed — that is the defining trait of bottom-up parsing.

Why Bottom-Up? — Advantages and Limitations

Bottom-up parsing is the dominant technique in real-world compiler tools (yacc, bison, GNU Bison, Java CUP). Here is why.


Advantages

AdvantageDetail
Handles a larger grammar classLR(k) grammars include LL(k) grammars and many that LL cannot handle
No left-recursion problemLeft-recursive rules like E → E + T are natural for bottom-up parsers
No left-factoring requiredCommon prefixes do not cause trouble — the decision is made later, on lookahead
Better at expression grammarsOperator precedence and associativity fall out naturally

Limitations

LimitationDetail
Harder to implement by handTables are large; building them by hand is tedious
Less intuitive error messagesThe parser only knows what it has seen so far, not what it was trying to build
Requires parser-generator toolsAlmost always built with yacc/bison/ANTLR rather than handwritten
Shift/reduce and reduce/reduce conflictsMust be detected and resolved during table construction

Where It's Used

  • Yacc/Bison/GNU Bison — LALR(1) parser generators used by C, C++, awk, Perl, PHP, Ruby and many more.
  • GCC and Clang — historically used LALR; Clang now uses a hand-written predictive parser, but its grammar was originally designed against bottom-up techniques.
  • PostgreSQL and MySQL — their SQL parsers are LALR(1) generated by Bison.

Quick Comparison with Top-Down

Example
Top-Down (LL) Bottom-Up (LR)
─────────────────────────────────────────────────────────────────────
Direction Root → leaves Leaves → root
Derivation Leftmost Rightmost (reversed)
Grammar class LL(k) ⊂ LR(k) LR(k) — much larger
Left recursion Must be eliminated Handled naturally
Common prefixes Must be factored out Handled naturally
Implementation Easy by hand Use a parser generator
Error messages Predictive — knows Reactive — only knows
what's expected what it has seen

Categories of Bottom-Up Parsers

All bottom-up parsers share the same shift/reduce engine — they differ in how they decide which action to take.


Operator-Precedence Parser

Works only on operator grammars (no two non-terminals adjacent, no ε productions). Uses precedence relations between terminals to decide where the handle is. Simple to build by hand, but limited.

→ Covered in Operator Precedence Parsing.


LR(0)

Looks at no input to decide a reduction — uses only the current state of the parser. Very weak; many real grammars are not LR(0).


SLR(1) — Simple LR

LR(0) plus a one-token lookahead, where reductions are decided by the FOLLOW set of the non-terminal being reduced. Easy to build, handles most simple grammars.

→ Covered in Construction of SLR, CLR, LALR Parse Tables.


CLR(1) — Canonical LR

Full LR(1). Each parser state knows exactly which lookahead tokens justify each reduction. Most powerful, but produces very large tables (thousands of states for real languages).


LALR(1) — Look-Ahead LR

CLR(1) with states that share the same items merged together. Produces tables about the same size as SLR but is almost as powerful as CLR. This is the standard used by yacc and bison.


The Hierarchy

Example
LR(0) ⊂ SLR(1) ⊂ LALR(1) ⊂ CLR(1) ⊂ LR(k)

Each class strictly contains the one before it — i.e. every LR(0) grammar is SLR(1), every SLR(1) grammar is LALR(1), and so on. The practical sweet spot is LALR(1): it is almost as powerful as CLR(1) but with much smaller tables.