Yasir Explains/Compiler Design/Parsing/Construction of SLR
Parsing

Construction of SLR

On this page

What is SLR(1)?Running GrammarStep 1 — The LR(0) Item DFAStep 2 — Filling the ACTION and GOTO TablesThe Complete SLR(1) Parse TableStep 3 — Tracing a ParseWhere SLR(1) Fails — The Classic Example
Parsing

Construction of SLR

Build an SLR(1) parse table step by step on the same expression grammar from the LR Parsers topic — reuse the LR(0) item DFA, compute FOLLOW sets, then fill the ACTION and GOTO tables with shift, reduce, and accept entries.

What is SLR(1)?

SLR(1) stands for Simple LR with 1 token of lookahead. It is the easiest LR table-construction technique to learn and the cheapest to implement — a great starting point before tackling CLR(1) and LALR(1).

The "simple" comes from how it decides reductions: it uses FOLLOW sets computed from the grammar, rather than carrying detailed per-state lookahead information.


How SLR Differs from LR(0)

LR(0) parsers reduce as soon as they reach an item with the dot at the end (A → α ·), regardless of the next input token. That works for very few grammars — most real grammars produce shift/reduce conflicts at LR(0).

SLR(1) is one small but powerful improvement:

Example
LR(0) : reduce A → α on every terminal (no lookahead)
SLR(1): reduce A → α only when the lookahead is in FOLLOW(A)

By restricting the reduction to FOLLOW(A), many LR(0) conflicts disappear — and any grammar where that single rule is enough is called SLR(1).


The Construction Pipeline

Example
1. Augment the grammar → S' → S
2. Build the LR(0) item DFA → CLOSURE, GOTO, canonical collection
3. Compute FIRST and FOLLOW sets
4. Fill ACTION and GOTO tables

The first two steps are shared with every LR variant — they were covered in the LR Parsers topic. Step 4 is what makes the parser SLR.

Running Grammar

We reuse the same expression grammar from the LR Parsers topic, so the DFA we already built carries over directly — only the reduce entries change.

Example
Original:
E → E + T
E → T
T → id
Augmented:
(0) E' → E
(1) E → E + T
(2) E → T
(3) T → id

It generates simple additions of identifiers: id, id + id, id + id + id, and so on. It is left-recursive (E → E + T) — which LR handles naturally — and it is genuinely LR(0). We will see that SLR(1), by consulting FOLLOW, still produces a tighter table than the blind LR(0) one.


FIRST and FOLLOW

We will need the FOLLOW sets in step 2 — they drive every reduce entry.

Example
FIRST(E) = FIRST(T) = { id }
FIRST(T) = { id }
FOLLOW(E') = { $ }
FOLLOW(E) = ? → E' → E gives FOLLOW(E') ⊆ FOLLOW(E) → { $ }
E → E + T puts '+' right after E → { + }
FOLLOW(E) = { +, $ }
FOLLOW(T) = ? → E → E + T T sits at the end → FOLLOW(E) ⊆ FOLLOW(T)
E → T T sits at the end → FOLLOW(E) ⊆ FOLLOW(T)
FOLLOW(T) = { +, $ }

Keep these handy: FOLLOW(E) = FOLLOW(T) = { +, $ } and FOLLOW(E') = { $ }.

Step 1 — The LR(0) Item DFA

Augmenting and building the LR(0) item DFA are identical for every LR variant — SLR adds nothing here. We constructed this exact DFA state by state in the LR Parsers topic, so we only restate the result.

Starting from the kernel E' → · E and applying CLOSURE and GOTO until no new states appear gives 6 states:

Example
State Items Outgoing transitions
─────────────────────────────────────────────────────────────────
I₀ E' → · E, E → · E + T, E → I₁, T → I₂, id → I₃
E → · T, T → · id
I₁ E' → E ·, E → E · + T + → I₄
I₂ E → T · (none — reduce only)
I₃ T → id · (none — reduce only)
I₄ E → E + · T, T → · id T → I₅, id → I₃
I₅ E → E + T · (none — reduce only)

The complete (reduce/accept) items live in I₁ (accept on $), I₂, I₃, and I₅. Note that both I₀ and I₄ transition to the single state I₃ on id — identical item sets share one state.

Step 2 — Filling the ACTION and GOTO Tables

Now apply the SLR(1) rules to fill in every entry of ACTION and GOTO.


Shift Entries (ACTION)

For every transition I_s — a → I_t where a is a terminal:

Example
ACTION[s][a] = "shift t"

From our DFA:

Example
I₀ — id → I₃ ⇒ ACTION[0][id] = s3
I₁ — + → I₄ ⇒ ACTION[1][+] = s4
I₄ — id → I₃ ⇒ ACTION[4][id] = s3

GOTO Entries

For every transition I_s — A → I_t where A is a non-terminal:

Example
GOTO[s][A] = t

From our DFA:

Example
I₀ — E → I₁ ⇒ GOTO[0][E] = 1
I₀ — T → I₂ ⇒ GOTO[0][T] = 2
I₄ — T → I₅ ⇒ GOTO[4][T] = 5

Accept Entry

The state containing E' → E · accepts on $:

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

Reduce Entries — The SLR(1) Rule

This is the one rule that defines SLR.

Example
For every state Iₛ containing a complete item A → α · (where A ≠ E') :
for every a ∈ FOLLOW(A) :
ACTION[s][a] = reduce A → α

Walk through each reduce state, using FOLLOW(E) = FOLLOW(T) = { +, $ }:

Example
I₂ contains E → T · FOLLOW(E) = { +, $ }
⇒ ACTION[2][+] = r2, ACTION[2][$] = r2
I₃ contains T → id · FOLLOW(T) = { +, $ }
⇒ ACTION[3][+] = r3, ACTION[3][$] = r3
I₅ contains E → E + T · FOLLOW(E) = { +, $ }
⇒ ACTION[5][+] = r1, ACTION[5][$] = r1

(Production numbers: 1 = E → E + T, 2 = E → T, 3 = T → id.)


SLR Is Tighter Than LR(0)

In the LR Parsers topic we built the LR(0) table for this same grammar, where every complete item reduces on every terminal — including id. SLR consults FOLLOW instead, and since id ∉ FOLLOW(E) and id ∉ FOLLOW(T), the id column for states 2, 3, and 5 becomes error rather than reduce:

Example
LR(0) ACTION[2][id] = r2 ← reduces blindly on id
SLR(1) ACTION[2][id] = (error) ← id can never follow an E, so reject early

Same DFA, same shifts and gotos — SLR just rejects bad input one step sooner.

The Complete SLR(1) Parse Table

Putting all the entries together:

Example
ACTION GOTO
State | id + $ | E T
──────┼────────────────────────────────────────
0 | s3 | 1 2
1 | s4 acc |
2 | r2 r2 |
3 | r3 r3 |
4 | s3 | 5
5 | r1 r1 |

Reading conventions:

  • s3 — shift, then go to state 3.
  • r2 — reduce by production (2): E → T.
  • acc — accept.
  • Empty cells — syntax error.

Every cell has at most one entry. That confirms the grammar is SLR(1) — no conflicts. Compare with the LR(0) table from the LR Parsers topic: there the id column of states 2, 3, and 5 held r2/r3/r1; here those cells are empty, because id is not in any FOLLOW set.


Quick Reading of Each Row

StateWhat it means
0Initial: shift id; on E or T use GOTO
1We have parsed E — shift + to continue, or accept on $
2We have parsed a T — reduce E → T (only when + or $ follows)
3We have shifted id — reduce T → id
4We have shifted E + — shift the right operand's id
5We have parsed E + T — reduce E → E + T

Step 3 — Tracing a Parse

Let's run the SLR(1) parser on the input id + id — the same string traced in the LR Parsers topic, so you can compare the two engines directly.

The stack stores (symbol, state) pairs. We show only the state numbers for compactness — the symbols are easy to recover from context.

Example
Stack (states) Input Action
──────────────────────────────────────────────────────────────────
0 id + id $ ACTION[0][id] = s3 → push id, push 3
0 id 3 + id $ ACTION[3][+] = r3 → reduce T → id
pop 2 items, top state = 0
push T, push GOTO[0][T] = 2
0 T 2 + id $ ACTION[2][+] = r2 → reduce E → T
pop 2 items, top state = 0
push E, push GOTO[0][E] = 1
0 E 1 + id $ ACTION[1][+] = s4 → push +, push 4
0 E 1 + 4 id $ ACTION[4][id] = s3 → push id, push 3
0 E 1 + 4 id 3 $ ACTION[3][$] = r3 → reduce T → id
pop 2 items, top state = 4
push T, push GOTO[4][T] = 5
0 E 1 + 4 T 5 $ ACTION[5][$] = r1 → reduce E → E + T
pop 6 items, top state = 0
push E, push GOTO[0][E] = 1
0 E 1 $ ACTION[1][$] = acc → done ✓

Notice the discipline:

  • Shift pushes (token, new_state).
  • Reduce by A → β pops 2·|β| entries, then pushes (A, GOTO[top][A]). The final reduction E → E + T has three symbols, so it pops 6 entries at once.

No backtracking, no guessing — every action is a direct table lookup. Reading the reductions in order (T → id, E → T, T → id, E → E + T) and reversing them recovers the rightmost derivation of id + id. That is the entire SLR(1) algorithm in action.

Where SLR(1) Fails — The Classic Example

SLR(1) is easy but not powerful. Some unambiguous grammars cause shift/reduce conflicts in SLR even though they are perfectly parseable by stronger LR variants.


The Famous L = R Grammar

Example
(0) S' → S
(1) S → L = R
(2) S → R
(3) L → * R
(4) L → id
(5) R → L

This grammar describes a tiny pointer-assignment language: *x = y, x = y, x, etc. It is unambiguous — every valid input has exactly one parse tree.


The Conflict Appears

When we build its LR(0) DFA, one state ends up with both:

Example
S → L · = R ← would shift '=' next
R → L · ← could reduce R → L

To pick the right action, SLR asks: is = in FOLLOW(R)?

Walk through FOLLOW(R):

Example
S → L = R ⇒ R is at the end of an S production → FOLLOW(S) ⊆ FOLLOW(R)
L → * R ⇒ same effect
FOLLOW(S) = { $ }

But also: R → L and S → R mean R can replace S, and L can replace R, so:

Example
FOLLOW(R) ⊇ FOLLOW(L)

And S → L = R puts = in FOLLOW(L). Hence = ends up in FOLLOW(R) too.

So SLR concludes: on lookahead =, both reduce R → L and shift are valid:

Example
ACTION[state][=] = { shift to next state, reduce R → L } ← conflict!

Why SLR Was Wrong

In this context, = is the next token only when we are inside an S → L = R derivation — we definitely should shift. But SLR cannot tell that, because FOLLOW(R) is computed globally over the grammar and ignores the state the parser is in.

CLR(1) fixes this by attaching lookaheads to individual items, giving per-context precision. That is the topic of the next chapter.


Summary

StrengthWeakness
Tiny tables, easy to buildFOLLOW is too coarse for some grammars
Good for teaching and small DSLsMany real-language grammars are not SLR(1)
Same driver as every other LR variantPushes correct cases into LALR/CLR territory

Despite the limitations, most teaching examples are SLR(1) — and learning the mechanics here makes CLR and LALR almost free.