Yasir Explains/Compiler Design/Parsing/CLR
Parsing

CLR

On this page

What is CLR(1)?LR(1) Items, CLOSURE, and GOTORunning GrammarBuilding the LR(1) Item DFAFilling the CLR(1) Parse TableTracing a CLR(1) ParseAdvantages and Cost
Parsing

CLR

Construct a Canonical LR(1) parse table — the most powerful LR parser. Attach a precise lookahead to every item, build a fine-grained DFA, and resolve conflicts that defeat SLR(1).

What is CLR(1)?

CLR(1) stands for Canonical LR with 1 token of lookahead. It is the most powerful parser in the LR family — every deterministic context-free grammar that can be parsed with one lookahead token can be parsed by a CLR(1) parser.

The price is table size: CLR(1) often produces 5–10× more states than SLR(1) or LALR(1) for the same grammar. For decades that was too expensive on real hardware, so LALR(1) became the practical default. But CLR(1) is the theoretical maximum — and the right starting point for understanding LALR(1) too.


The Core Idea — Lookaheads on Items

In LR(0)/SLR, every item is of the form A → α · β. CLR adds a lookahead to each item:

Example
[ A → α · β , a ]

Read it as: "we have parsed α, expect β next, and after reducing this production we expect to see terminal a." That terminal a is what makes the item LR(1).

A complete item [A → α · , a] triggers reduce A → α only when the next input token is exactly a — never on any other terminal.


Why This Beats SLR

SLR's FOLLOW set is computed globally over the grammar — it lumps together every context where a non-terminal might appear. A particular state of the parser usually doesn't allow all of FOLLOW(A) to follow — only a context-dependent subset.

CLR avoids the overshoot by storing exact lookaheads inside each item. Two parser states that contain the same LR(0) item but with different lookaheads become different CLR states. That fine granularity is precisely what resolves grammars like S → L = R that SLR cannot handle.


The Construction Pipeline

Example
1. Augment the grammar → S' → S
2. Build the LR(1) item DFA → CLOSURE / GOTO with lookaheads
3. Fill ACTION and GOTO tables → reduce only on each item's lookahead

Same overall shape as SLR — only the items, the CLOSURE, and the reduce rule change.

LR(1) Items, CLOSURE, and GOTO

Before building the table we need three definitions: the item format, CLOSURE for LR(1), and GOTO for LR(1).


LR(1) Item Format

Example
[ A → α · β , a ]
A → α · β production with a dot showing the parsing position
a the LOOKAHEAD — a single terminal (or $)

When we write [A → α · β, a/b] (with a slash) it is shorthand for two items, one with lookahead a and one with lookahead b.


CLOSURE for LR(1)

If a state already contains [A → α · B β , a] (dot before a non-terminal B), then somewhere "soon" the parser will start matching a B production. So for every production B → γ we must add:

Example
[ B → · γ , b ] for every b ∈ FIRST(β a)

FIRST(β a) is the set of terminals that can appear right after the new B. If β can derive ε, the inherited a is included.

Keep applying the rule until no new items are added — that is the closure.


Worked CLOSURE Example

Grammar fragment: A → · B C , $, productions B → b and B → c, with C deriving a non-ε string.

Example
Start:
[ A → · B C , $ ]
Apply closure (dot before B):
for each B-production add [B → · γ , b] where b ∈ FIRST(C $)
FIRST(C $) = FIRST(C) (since C doesn't derive ε)
Suppose FIRST(C) = { x, y }
add [ B → · b , x ]
add [ B → · b , y ]
add [ B → · c , x ]
add [ B → · c , y ]

The lookahead chains — the things that follow the new non-terminal become the lookaheads of the items added by closure.


GOTO for LR(1)

GOTO works the same way as in LR(0), but lookaheads are carried along:

Example
GOTO( state I, symbol X ) =
CLOSURE({ [ A → α X · β , a ] | [ A → α · X β , a ] ∈ I })

In words: take every item in I whose dot precedes X, shift the dot one position past X, keep the same lookahead, then close the result.


Why More States?

Because two parser states with the same LR(0) items but different lookaheads are now considered different. CLR splits states that LR(0) would have merged — that is exactly where the extra precision comes from, and also where the extra states come from.

Running Grammar

We use the same grammar as the SLR topic so the two table constructions can be compared side by side.

Example
(0) S' → S
(1) S → C C
(2) C → c C
(3) C → d

For SLR, this grammar produced 7 states. For CLR, we will see it explode to 10 states — three more than SLR — because the lookaheads split some states apart.

(For the L = R grammar where SLR fails outright, CLR succeeds — but the example above is small enough to write the entire DFA on one page.)

Building the LR(1) Item DFA

Start from the kernel of the augmented start production and apply closure repeatedly.


State I₀

Kernel:

Example
[ S' → · S , $ ]

Closure (dot before S, so add S productions with lookahead = FIRST($) = {$}):

Example
[ S → · C C , $ ]

Closure again (dot before C, lookahead = FIRST(C $) = FIRST(C) = {c, d}):

Example
[ C → · c C , c/d ]
[ C → · d , c/d ]

So I₀ is:

Example
I₀:
[ S' → · S , $ ]
[ S → · C C , $ ]
[ C → · c C , c/d ]
[ C → · d , c/d ]

Transitions out of I₀

Example
GOTO(I₀, S) → I₁:
[ S' → S · , $ ] ← future ACCEPT
GOTO(I₀, C) → I₂:
[ S → C · C , $ ] ← kernel
closure (dot before C, lookahead = FIRST($) = {$}):
[ C → · c C , $ ]
[ C → · d , $ ] ← note: $ only, NOT c/d
GOTO(I₀, c) → I₃:
[ C → c · C , c/d ] ← lookaheads inherited
closure:
[ C → · c C , c/d ]
[ C → · d , c/d ]
GOTO(I₀, d) → I₄:
[ C → d · , c/d ] ← REDUCE C → d on c or d

Notice already: states I₂ and I₃ share the same LR(0) core (C → · c C, C → · d) but different lookaheads ($ vs c/d). CLR keeps them separate. LALR will eventually merge them — but that is the next topic.


Transitions out of I₂

Example
GOTO(I₂, C) → I₅:
[ S → C C · , $ ] ← REDUCE S → C C on $
GOTO(I₂, c) → I₆:
[ C → c · C , $ ]
closure:
[ C → · c C , $ ]
[ C → · d , $ ]
GOTO(I₂, d) → I₇:
[ C → d · , $ ] ← REDUCE C → d on $

I₆ and I₃ share their LR(0) core but differ in lookaheads ($ vs c/d) — another future merge.


Transitions out of I₃

Example
GOTO(I₃, C) → I₈:
[ C → c C · , c/d ] ← REDUCE C → c C on c or d
GOTO(I₃, c) → I₃ (self-loop, same state)
GOTO(I₃, d) → I₄ (existing state)

Transitions out of I₆

Example
GOTO(I₆, C) → I₉:
[ C → c C · , $ ] ← REDUCE C → c C on $
GOTO(I₆, c) → I₆ (self-loop)
GOTO(I₆, d) → I₇ (existing)

I₄/I₇ share an LR(0) core, and I₈/I₉ share an LR(0) core — three pairs total that LALR will eventually merge.


Summary — All 10 States

Example
I₀ [S'→·S,$] [S→·CC,$] [C→·cC,c/d] [C→·d,c/d]
I₁ [S'→S·,$] ← accept
I₂ [S→C·C,$] [C→·cC,$] [C→·d,$]
I₃ [C→c·C,c/d] [C→·cC,c/d] [C→·d,c/d]
I₄ [C→d·,c/d] ← reduce 3 on c/d
I₅ [S→CC·,$] ← reduce 1 on $
I₆ [C→c·C,$] [C→·cC,$] [C→·d,$]
I₇ [C→d·,$] ← reduce 3 on $
I₈ [C→cC·,c/d] ← reduce 2 on c/d
I₉ [C→cC·,$] ← reduce 2 on $

Visual Diagram of the DFA

Each box is a state with its LR(1) items inside — the lookahead follows the comma. Arrows are transitions labelled by the symbol that triggers them.

How to read it:

  • Blue — the start state (I₀).
  • Cyan — the accept state (I₁, on lookahead $).
  • Amber — reduce states (I₄, I₅, I₇, I₈, I₉).
  • Arrow labels — the grammar symbol that triggers the transition.

Notice the lookahead-driven splits: I₂/I₃ and I₆/I₃ share the LR(0) core {C → ·cC, C → ·d} but differ only in lookahead ($ vs c/d); likewise I₄/I₇ and I₈/I₉. Those splits are exactly why CLR has 10 states where SLR had 7 — and exactly the states LALR will merge back in the next topic.

Filling the CLR(1) Parse Table

With the DFA built, the table follows the standard LR recipe — but the reduce rule uses the lookahead carried in each item.


Shift and GOTO Entries

Identical to SLR — read straight off the DFA transitions.

Example
Terminal transitions → shift entries in ACTION
Non-terminal transitions → GOTO entries

Accept Entry

Example
I₁ contains [S' → S · , $] ⇒ ACTION[1][$] = accept

Reduce Entries — The CLR(1) Rule

Example
For every state Iₛ containing a complete item [ A → α · , a ] where A ≠ S' :
ACTION[s][a] = reduce A → α

That's it — one cell per item, not one cell per terminal in FOLLOW. Going through our reduce states:

Example
I₄ : [C → d · , c] → ACTION[4][c] = r3
[C → d · , d] → ACTION[4][d] = r3
(NOT on $ — that's I₇'s job)
I₅ : [S → C C · , $] → ACTION[5][$] = r1
I₇ : [C → d · , $] → ACTION[7][$] = r3
I₈ : [C → c C · , c] → ACTION[8][c] = r2
[C → c C · , d] → ACTION[8][d] = r2
I₉ : [C → c C · , $] → ACTION[9][$] = r2

Each reduction fires on a specific subset of terminals — never the full FOLLOW set.


The Complete CLR(1) Parse Table

Example
ACTION GOTO
State | c d $ | S C
──────┼─────────────────────────────────────────────
0 | s3 s4 | 1 2
1 | acc |
2 | s6 s7 | 5
3 | s3 s4 | 8
4 | r3 r3 |
5 | r1 |
6 | s6 s7 | 9
7 | r3 |
8 | r2 r2 |
9 | r2 |

Every cell has at most one entry — the grammar is CLR(1).


Compare With SLR

AspectSLR(1)CLR(1)
States710
Reduce in state 4r3 on c, d, $r3 only on c, d
Reduce in state 7(state 7 didn't exist)r3 only on $
PowerSome grammars failMaximum LR power

Both parsers accept the same language for this grammar, but CLR distinguishes contexts that SLR collapses together. That extra precision is exactly what saves grammars like S → L = R from rejection.

Tracing a CLR(1) Parse

Same input as the SLR trace — input c d d — but using the CLR(1) table.

Example
Stack (states) Input Action
─────────────────────────────────────────────────
0 c d d $ ACTION[0][c] = s3 → shift, goto 3
0 c 3 d d $ ACTION[3][d] = s4 → shift, goto 4
0 c 3 d 4 d $ ACTION[4][d] = r3 → reduce C → d
pop 2; top state = 3
push C, goto GOTO[3][C] = 8
0 c 3 C 8 d $ ACTION[8][d] = r2 → reduce C → cC
pop 4; top state = 0
push C, goto GOTO[0][C] = 2
0 C 2 d $ ACTION[2][d] = s7 → shift, goto 7
0 C 2 d 7 $ ACTION[7][$] = r3 → reduce C → d
pop 2; top state = 2
push C, goto GOTO[2][C] = 5
0 C 2 C 5 $ ACTION[5][$] = r1 → reduce S → CC
pop 4; top state = 0
push S, goto GOTO[0][S] = 1
0 S 1 $ ACTION[1][$] = accept → done ✓

The parser took the same sequence of shifts and reduces as the SLR trace, but went through different intermediate states (3 ➝ 8 ➝ 2 ➝ 7) instead of (3 ➝ 6 ➝ 2 ➝ 4). The CLR DFA encodes more information per state — and that is the entire trade-off.

Advantages and Cost

CLR(1) is the gold standard of deterministic parsing — at a price.


Advantages

AdvantageDetail
Most powerful in the LR familyHandles every grammar parseable with 1 token lookahead
No false conflictsEvery reduction fires only when contextually justified
Theoretical foundation for LALR(1)LALR is just "CLR with compatible states merged"
Same O(n) parse timeDriver is identical to SLR — only tables differ

Cost

CostDetail
Table explosion5×–10× more states than SLR/LALR on real grammars
Memory pressureHistorically prohibitive on small machines
Slower table generationThe lookahead chase is expensive at construction time

When to Reach for CLR(1)

In practice, almost never — LALR(1) does the same job with a fraction of the table size. CLR's role is mostly:

  1. A teaching tool to introduce lookahead-bearing items.
  2. A theoretical reference when proving things about LR languages.
  3. A starting point for LALR(1) — every LALR(1) table can be derived by merging CLR(1) states.

If you understand CLR, LALR is one merge step away — which is exactly the topic of the next lesson.