Compiler Design and ConstructionUnit 310 min read
Syntax Analysis (Parsing): Grammars, Parsing Tables, and Translation
Unit 3 of Compiler Design and Construction covers the core techniques of syntax analysis, including grammar classification (LL, LR, LALR), parsing table construction (LL(1), SLR, LR(1)), syntax-directed translation, and annotated parse trees—essential for building compilers that correctly interpret programming language
Core Concepts: Grammars and Parsing
1. Grammars and Language Classification
A grammar defines a formal language:
- : Non-terminals (e.g.,
S,E,T). - : Terminals (e.g.,
+,*,id). - : Production rules (e.g.,
E → E + T | T). - : Start symbol.
Grammars are classified by Chomsky hierarchy:
- Type 0: Unrestricted (Turing-complete).
- Type 1: Context-sensitive (e.g.,
S → aSb | ε). - Type 2: Context-free (used in compilers, e.g.,
E → E + T | T). - Type 3: Regular (used in lexical analysis).
Classification of formal grammars (Image: TinyTedDanson, CC BY-SA 4.0, via Wikimedia Commons)
2. Parsing Methods: LL vs. LR
Parsing converts input strings into parse trees using grammars. Two dominant approaches:
| Method | Direction | Lookahead | Grammar Type | Example |
|---|---|---|---|---|
| LL(1) | Left-to-right | 1 token | LL(1) grammars | Recursive descent parsers |
| LR(0) | Left-to-right | 0 tokens | LR(0) grammars | SLR, LR(1) parsers |
| LALR(1) | Left-to-right | 1 token | LALR(1) grammars | Optimized LR(0) with lookahead |
Key Idea:
- LL(1): Leftmost derivation, left-to-right, leftmost expansion.
Example:
S → aSb | ε→aab(leftmostSexpanded first). - LR(1): Rightmost derivation, right-to-left, rightmost expansion.
Example:
S → aSb | ε→aab(rightmostSexpanded first).
3. FIRST and FOLLOW Sets
Used to construct LL(1) parsing tables.
FIRST(X)
Set of terminals that can appear as the first symbol in any string derived from . Rules:
- If , add to FIRST(X).
- If , add FIRST(Y₁) to FIRST(X).
- If FIRST(Y₁) contains , add FIRST(Y₂) (and so on).
- If no terminal can derive , FIRST(X) = {}.
FOLLOW(X)
Set of terminals that can appear immediately after in any sentential form. Rules:
- If is the start symbol, add
$(end-of-input) to FOLLOW(S). - If , add FIRST(β) to FOLLOW(B).
- If FIRST(β) contains , add FOLLOW(A) to FOLLOW(B).
Worked Example: FIRST and FOLLOW
Grammar:
S → ACB | CbB | Ba
A → da | BC
B → g | ε
C → h | ε
Compute FIRST and FOLLOW:
Step 1: FIRST Sets
| Non-terminal | FIRST(X) |
|---|---|
| S | {A, C, B} |
| A | {d, B} |
| B | {g, ε} |
| C | {h, ε} |
Trace:
- FIRST(S) = FIRST(A) ∪ FIRST(C) = {d, B, h}.
- FIRST(A) = {d} ∪ FIRST(B) = {d, g, ε}.
- FIRST(B) = {g, ε} (given).
- FIRST(C) = {h, ε} (given).
Step 2: FOLLOW Sets
| Non-terminal | FOLLOW(X) |
|---|---|
| S | {$} |
| A | {C, b, $} |
| B | {B, b, $} |
| C | {B, b, $} |
Trace:
- FOLLOW(S) = {$} (start symbol).
- For
S → ACB:- FOLLOW(A) = FIRST(CB) = {h, g, ε} → {h, g, B, b, $}.
- FOLLOW(C) = FIRST(B) = {g, ε} → {g, B, b, $}.
- FOLLOW(B) = {$} (end of production).
4. LL(1) Parsing Table Construction
Algorithm:
- For each production :
- For each terminal , add to
M[A, a]. - If , add to
M[A, b]for all .
- For each terminal , add to
Example Table for Above Grammar:
| a | b | d | g | h | $ | |
|---|---|---|---|---|---|---|
| S | S→CbB | S→ACB | ||||
| A | A→da | |||||
| A | A→BC | |||||
| B | B→g | B→ε | ||||
| C | C→h | C→ε |
Check for LL(1) Conflict:
- No two entries in the same cell → LL(1).
5. LR Parsing: SLR(1) and LR(1)
SLR(1) Parsing Table
Uses FOLLOW sets to resolve shifts/reduces. Steps:
- Build LR(0) items (augmented grammar + dot positions).
- Construct LR(0) automaton (states = sets of items).
- Add FOLLOW lookahead to resolve conflicts.
LR(1) Parsing Table
Extends LR(0) with look-ahead symbols in items.
Item Format: [A → α • β, a] (where a is lookahead).
Worked Example: SLR(1) Table
Grammar:
S → aAa | bAb | ba
LR(0) Items:
[S' → •S, $][S → •aAa, $][S → a•Aa, $][S → aA•a, $][S → •bAb, $][S → b•Ab, $][S → bA•b, $][A → •a, $][A → a•, $]
SLR(1) Table:
| State | Action on a |
Action on b |
Goto on A |
Goto on S |
|---|---|---|---|---|
| 0 | shift 2 | shift 5 | 1 | |
| 1 | reduce S→ba | accept | ||
| 2 | shift 8 | 3 | ||
| 3 | shift 4 | |||
| 4 | reduce S→aAa | |||
| 5 | shift 6 | 7 | ||
| 6 | reduce S→bAb | |||
| 7 | reduce A→a | |||
| 8 | reduce A→a |
Trace for Input aab:
- Start in state 0, read
a→ shift to state 2. - Read
a→ shift to state 8. - Read
b→ reduceA → a(state 8 → state 7). - Read
$→ reduceS → aAa→ accept.
6. Syntax-Directed Translation
Definition: Attach semantic actions (e.g., code generation) to grammar productions. Example: Evaluate arithmetic expressions.
Grammar:
E → E + T | T
T → T * F | F
F → (E) | id
Annotated Parse Tree for (5*3+2)*5:
graph TD
A["*"] --> B[(5*3+2)]
A --> C["5"]
B --> D["+"]
D --> E[(5*3)]
D --> F["2"]
E --> G["*"]
G --> H["5"]
G --> I["3"]Semantic Actions:
def E(node):
if node.left and node.right: # E + T
return E(node.left) + T(node.right)
else: # T
return T(node)
def T(node):
if node.left and node.right: # T * F
return T(node.left) * F(node.right)
else: # F
return F(node)
Trace for (5*3+2)*5:
- Parse
(5*3+2)→5*3+2=15+2=17. - Multiply by
5→17*5=85.
In the Real World
eSewa (Nepal):
- Uses LR parsing to validate user inputs (e.g.,
payment = amount + tax). - Grammar ensures correct syntax for transaction rules.
- Uses LR parsing to validate user inputs (e.g.,
Khalti API (Nepal):
- LL(1) parsers validate JSON requests (e.g.,
{"amount": 1000, "currency": "NPR"}). - Rejects malformed queries early (e.g., missing
currency).
- LL(1) parsers validate JSON requests (e.g.,
YouTube’s Algorithm (Global):
- Syntax-directed translation ranks videos by parsing user watch history as a grammar:
WatchHistory → Video+ Video → (Category | Tags)+ - Generates recommendations via semantic actions (e.g.,
if Category(X) matches then suggest Y).
- Syntax-directed translation ranks videos by parsing user watch history as a grammar:
Ncell Billing System:
- LR(1) parsers process calls in the format:
Call → Duration Unit Unit → "min" | "sec" - Computes charges as
cost = Duration * Rate(Unit).
- LR(1) parsers process calls in the format:
Exam Tip
Grammar Classification:
- Always check if a grammar is LL(1) or LR(1) before building tables.
- LL(1) Conflict: Two productions for the same non-terminal and lookahead.
- LR Conflict: Shift-reduce or reduce-reduce in the same state.
Parsing Tables:
- LL(1): Use FIRST/FOLLOW to fill the table.
- LR(1): Draw the automaton and resolve conflicts with lookahead.
- Common Mistake: Forgetting to add
$to FOLLOW(S).
Syntax-Directed Translation:
- Annotate parse trees with semantic rules (e.g.,
E → E + T { = $1 + $3 }). - Exam Trick: For expressions like
(5*3+2)*5, show both the parse tree and the evaluation steps.
- Annotate parse trees with semantic rules (e.g.,
Real-World Tie-Ins:
- Relate grammars to APIs (Khalti), billing (Ncell), or validation (eSewa).
- Example: "How would you parse a Daraz order confirmation email?" → Use LR(1) for structured data.
Comparison Table: LL vs. LR Parsing
| Feature | LL(1) | LR(1) |
|---|---|---|
| Direction | Leftmost derivation | Rightmost derivation |
| Grammar | Must be LL(1) | Must be LR(1) |
| Table Size | Smaller (FIRST/FOLLOW-based) | Larger (automaton-based) |
| Error Handling | Detects errors late | Detects errors early |
| Use Case | Simple languages (e.g., C) | Complex languages (e.g., Java) |
Mermaid: LR(0) Automaton Construction
flowchart TD
A["Start: [S'→•S, ]"] --> B["State 0"]
B --> C["Action: shift S → [S→•aAa, ]"]
C --> D["State 1: [S→a•Aa, ]"]
D --> E["Action: shift A → [A→•a, ]"]
E --> F["State 2: [A→a•, ], [S→aA•a, ]"]
F --> G["Action: reduce A→a"]
G --> H["State 3: [S→aAa•, ]"]
H --> I["Action: accept"]Code Example: Recursive Descent Parser (LL(1))
def parse_E():
parse_T()
while lookahead == '+':
match('+')
parse_T()
def parse_T():
parse_F()
while lookahead == '*':
match('*')
parse_F()
def parse_F():
if lookahead == '(':
match('(')
parse_E()
match(')')
else:
match('id')
Trace for Input 5*3+2:
| Step | Action | Stack | Input |
|---|---|---|---|
| 1 | parse_E() | E | 5*3+2$ |
| 2 | parse_T() | E T | 5*3+2$ |
| 3 | parse_F() → match(5) | E T F | *3+2$ |
| 4 | lookahead = * | E T | *3+2$ |
| 5 | parse_T() → parse_F() | E T T F | 3+2$ |
| ... | ... | ... | ... |
Key Takeaways
- Grammars define the syntax; parsers validate and translate them.
- LL(1) is simpler but restrictive; LR(1) handles more grammars.
- FIRST/FOLLOW are critical for LL(1) tables; LR(0) automata for LR(1).
- Syntax-directed translation attaches meaning to parse trees (e.g., code generation).
- Real-world: APIs, billing systems, and validation tools rely on parsing techniques.
Based on the TU BSc CSIT syllabus for Compiler Design and Construction (CSC365), unit 3.
Discussion
Loading…