Compiler DesignUnit 513 min read
Bottom-Up Parsing: LR, SLR, LALR, CLR Parsers & Shift-Reduce
Unit 5 of Compiler Design explores bottom-up parsing techniques—LR(0), SLR, LALR, and CLR parsers—covering their definitions, parsing tables, shift-reduce conflicts, and how they handle grammars. It includes worked examples, parsing table construction, and comparisons with top-down methods.
TAKEAWAYS:
- Bottom-up parsers use shift-reduce operations to build the parse tree from the bottom up, guided by a parsing table.
- LR(0), SLR, LALR, and CLR parsers differ in how they resolve conflicts and handle lookaheads.
- Parsing tables (action and goto) are constructed using the LR(0) automaton and augmented grammar.
- Shift-reduce conflicts (shift-shift or reduce-reduce) must be resolved manually or via lookaheads.
- LALR parsers are widely used because they balance efficiency and correctness.
- Real-world applications include compilers for programming languages (e.g., GCC, Java compilers) and syntax validation in IDEs.
1. Introduction to Bottom-Up Parsing
Bottom-up parsing constructs the parse tree starting from the leaves (terminals) and builds upward toward the root (start symbol). Unlike top-down parsing (e.g., recursive descent), it does not guess and backtrack; instead, it uses a stack to keep track of partially parsed input.
Key Concepts:
- Shift: Push the next input symbol onto the stack.
- Reduce: Replace the top of the stack with the right-hand side of a production (if the top matches the left-hand side).
- Accept: If the stack contains only the start symbol after processing all input, the input is valid.
- Error: If no valid shift/reduce/accept action exists, the input is invalid.
Example Grammar (for illustration):
E → E + T | T
T → T * F | F
F → ( E ) | id
This grammar is LR(1) (no conflicts), meaning it can be parsed unambiguously by an LR parser.
2. LR(0) Parsing
LR(0) parsing is the simplest bottom-up parsing technique. It uses an LR(0) automaton (a finite-state machine) to guide parsing decisions.
How LR(0) Works:
- Augment the grammar by adding a new start symbol (for our example).
- Construct the LR(0) automaton using the closure and goto operations.
- Build the parsing table (action and goto) from the automaton.
- Parse the input using the stack and parsing table.
LR(0) Automaton Construction:
The automaton is built using:
- Closure(I): Adds all productions that can derive the first symbol of any item in .
- Goto(I, X): Moves from state on symbol , adding new items to the closure.
Example: LR(0) Automaton for
stateDiagram-v2
I0: [E' → •E]
I1: [E → •E + T, E' → E•]
I2: [E → E + •T, E' → E•]
I3: [T → •T * F, E → E + T•]
I4: [T → T * •F, E → E + T•]
I5: [F → •( E ), T → T * F•]
I6: [F → ( •E ), T → T * F•]
I7: [F → ( E •), T → T * F•]
I8: [E → •T, F → ( E )•]
I9: [T → •F, F → ( E )•]
I10: [F → •id, T → T * F•]
I11: [T → •id, F → id•]
I0 -->|E| I1
I1 -->|+| I2
I2 -->|T| I3
I3 -->|*| I4
I4 -->|F| I5
I5 -->|(| I6
I6 -->|E| I7
I7 -->|)| I8
I8 -->|T| I9
I9 -->|F| I10
I10 -->|id| I11
I11 -->|id| I11Parsing Table for LR(0):
| State | Action (input) | Goto (non-terminal) |
|---|---|---|
| I0 | shift(E) → 1 | E' → 2 |
| I1 | shift(+) → 3 | E → 4 |
| I2 | shift(T) → 5 | |
| I3 | reduce(E → E + T) | |
| I4 | shift(*) → 6 | |
| I5 | shift(F) → 7 | |
| ... | ... | ... |
Parsing Example: Input id + id * id
| Stack | Input | Action |
|---|---|---|
| [0, E'] | id + id * id $ | shift(id) → 1 |
| [0, 1, id] | + id * id $ | shift(+) → 3 |
| [0, 1, id, 3, +] | id * id $ | shift(id) → 5 |
| [0, 1, id, 3, 5, id] | * id $ | shift(*) → 6 |
| [0, 1, id, 3, 5, id, 6, *] | id $ | shift(id) → 10 |
| [0, 1, id, 3, 5, id, 6, 10, id] | $ | reduce(F → id) |
| ... | ... | ... |
| [0, 2, E] | $ | accept |
Final Parse Tree:
E
/ | \
E + T
/ \ \
id T F
/ \ \
T * id
/ \
id F
|
id
3. Shift-Reduce Conflicts
LR(0) parsers may encounter conflicts:
- Shift-Shift Conflict: Two states suggest shifting on the same input symbol.
- Reduce-Reduce Conflict: Two states suggest reducing by different productions.
stateDiagram-v2
[*] --> Shift
Shift --> Reduce: Conflict
Reduce --> [*]
Shift --> Shift: Conflict
Shift --> Accept
note right of Shift
Shift-Shift
Reduce-Reduce
end noteTypes of shift-reduce conflicts in LR parsingExample Conflict:
Consider the grammar:
S → A a | b
A → B a
B → b
- Conflict: On input
b a, LR(0) may suggest:- Shift
b(from ). - Reduce (from ).
- Shift
Resolving Conflicts:
- SLR(1): Use FOLLOW sets to resolve conflicts.
- LALR(1): Merge states with identical cores (more efficient than SLR).
- CLR(1): Uses look-ahead sets (most powerful but computationally expensive).
4. SLR(1) Parsing
SLR(1) resolves conflicts using FOLLOW sets. It is more powerful than LR(0) but may still have conflicts.
FOLLOW Set Construction:
For a non-terminal , contains all terminals that can appear immediately after in any derivation.
Example FOLLOW Sets:
For :
SLR(1) Parsing Table:
- If a shift-shift conflict occurs, resolve by checking which shift is valid based on FOLLOW sets.
- If a reduce-reduce conflict occurs, it is unresolvable (grammar is not SLR(1)).
5. LALR(1) Parsing
LALR(1) merges LR(0) states with identical cores (sets of items without lookaheads). It is more efficient than SLR(1) and resolves more conflicts.
LALR(1) vs. SLR(1):
| Feature | LR(0) | SLR(1) | LALR(1) | CLR(1) |
|---|---|---|---|---|
| Lookahead | None | FOLLOW sets | Lookahead | Full lookahead |
| Conflict Resolution | None | FOLLOW sets | Merged states | Full lookahead |
| Power | Weakest | Stronger | Stronger | Strongest |
| Efficiency | High | Medium | High | Low |
Example: LALR(1) State Merging
Two LR(0) states with the same core (items without lookaheads) are merged in LALR(1). This reduces table size.
6. CLR(1) Parsing
CLR(1) uses full lookahead sets (including one token of lookahead). It is the most powerful but computationally expensive.
When to Use CLR(1):
- When SLR(1) and LALR(1) fail to resolve conflicts.
- For highly ambiguous grammars.
7. Parsing Table Construction Steps
- Augment the grammar with .
- Compute the LR(0) automaton using closure and goto.
- Resolve conflicts (for SLR/LALR/CLR).
- Build the parsing table (action and goto).
- Parse the input using the stack and table.
Example: Parsing Table for
| State | Action (input) | Goto (non-terminal) |
|---|---|---|
| 0 | shift(E) → 1 | E' → 2 |
| 1 | shift(+) → 3 | E → 4 |
| 2 | accept | |
| 3 | shift(T) → 5 | |
| 4 | reduce(E → E + T) | |
| 5 | shift(*) → 6 | T → 7 |
| 6 | shift(F) → 8 | |
| 7 | reduce(T → T * F) | |
| 8 | reduce(F → (E)) |
8. Real-World Applications
In the Real World:
GCC (GNU Compiler Collection):
- Uses LALR(1) parsers for C, C++, and other languages.
- Efficiently handles complex grammars while minimizing conflicts.
Java Compiler (javac):
- Employs LR parsing for syntax analysis.
- Resolves ambiguities using look-ahead techniques (similar to CLR(1)).
SQL Parsers (e.g., MySQL, PostgreSQL):
- Uses bottom-up parsing to validate SQL queries.
- Example: Parsing
SELECT * FROM users WHERE age > 25involves shift-reduce operations to build the abstract syntax tree (AST).
eSewa (Nepalese Government Service):
- The backend parser for API request validation (e.g., JSON/XML) uses LR techniques to ensure correct syntax before processing payments or license renewals.
Khalti (Digital Payment System):
- Parses transaction requests (e.g.,
transfer(amount, from, to)) using LR parsers to validate input before executing payments.
- Parses transaction requests (e.g.,
9. Worked Example: Parsing id * id + id with LALR(1)
Input: id * id + id $
Grammar:
E → E + T | T
T → T * F | F
F → id
LALR(1) Parsing Steps:
| Stack | Input | Action |
|---|---|---|
| [0, E'] | id * id + id $ | shift(id) → 1 |
| [0, 1, id] | * id + id $ | reduce(F → id) → 2 |
| [0, 2, F] | * id + id $ | shift(*) → 3 |
| [0, 2, 3, *] | id + id $ | shift(id) → 4 |
| [0, 2, 3, 4, id] | + id $ | reduce(F → id) → 5 |
| [0, 2, 5, F] | + id $ | reduce(T → T * F) → 6 |
| [0, 6, T] | + id $ | shift(+) → 7 |
| [0, 6, 7, +] | id $ | shift(id) → 8 |
| [0, 6, 7, 8, id] | $ | reduce(F → id) → 9 |
| [0, 6, 9, F] | $ | reduce(T → T * F) → 10 |
| [0, 10, T] | $ | reduce(E → E + T) → 11 |
| [0, 11, E] | $ | reduce(E' → E) → 12 |
| [0, 12, E'] | $ | accept |
Parse Tree:
E
/ | \
E + T
/ \ \
T T F
/ \ / \
T * T id
/ \ / \
F id id F
| | |
id id id
10. Advantages and Disadvantages
| Parser Type | Advantages | Disadvantages |
|---|---|---|
| LR(0) | Simple, no lookahead needed | Many conflicts, weak grammar handling |
| SLR(1) | Uses FOLLOW sets for resolution | Still may have conflicts |
| LALR(1) | More efficient than SLR(1) | May merge states incorrectly |
| CLR(1) | Handles all LR(1) grammars | Computationally expensive |
11. Exam Tip
Understand LR(0) Automaton Construction:
- Know how to compute closure and goto.
- Practice building automata for given grammars.
Parsing Table Questions:
- Expect questions on action/goto table construction.
- Be able to resolve conflicts using FOLLOW/lookahead sets.
Shift-Reduce Conflicts:
- Identify shift-shift and reduce-reduce conflicts.
- Explain how SLR/LALR/CLR resolve them.
Worked Examples:
- Trace parsing steps for given inputs.
- Draw parse trees for valid inputs.
Real-World Links:
- Relate LR parsers to compilers (GCC, javac) and API parsers (eSewa, Khalti).
- Explain why LALR(1) is preferred in practice.
Common Pitfalls:
- Forgetting to augment the grammar ().
- Miscomputing FOLLOW sets or lookaheads.
- Not handling error recovery (though not in syllabus, useful for exams).
Final Note: Bottom-up parsing is fundamental to compiler design. Mastering LR parsers will help you understand how real compilers (like those in GCC or Java) validate and process code. Practice constructing automata and parsing tables—this is where most exam marks lie!
In the real world
- GCC Compiler (GNU Compiler Collection) uses LALR(1) parsing tables to validate C/C++ syntax. When you write
int x = 5 + 3 * 2;, the compiler’s parser resolves operator precedence via LALR(1) states, ensuring*is evaluated before+without conflicts. - WhatsApp’s Message Parser employs bottom-up shift-reduce parsing (similar to LR(1)) to validate emoji sequences like
😊👍🔥. Conflicts (e.g., invalid emoji combos) trigger error messages. - Nepali Banking Apps (e.g., NMB Bank’s mobile app) use SLR(1) parsers to validate transaction formats like
account: 123456789, amount: 5000. The parser checks if the input matches the grammarTransaction → AccountNumber Amountbefore processing.
Based on the PU BE Computer (PU) syllabus for Compiler Design (CMP360), unit 5.
Discussion
Loading…