Syntax Analysis — CFG, LL(1), FIRST/FOLLOW, Recursive Descent
1350 words
7 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Syntax Analysis — CFG, LL(1), FIRST/FOLLOW, Recursive Descent ## 🎯 Learning Objectives - Write context-free grammars for programming languages - Compute FIRST and FOLLOW sets - Construct LL(1) parsing tables - Implement recursive descent parsers - Identify and eliminate left recursion and left factoring * * * ##...

Syntax Analysis — CFG, LL(1), FIRST/FOLLOW, Recursive Descent
🎯 Learning Objectives
- Write context-free grammars for programming languages
- Compute FIRST and FOLLOW sets
- Construct LL(1) parsing tables
- Implement recursive descent parsers
- Identify and eliminate left recursion and left factoring
1. Context-Free Grammars
1.1 Intuition
A CFG is a set of rules describing how to form valid sentences in a language. Like English grammar rules (S → NP VP, NP → Det N, ...), programming language grammars define valid syntactic structures.
1.2 Formal Definition
A CFG G is a 4-tuple: G=(V,T,P,S)
- V: Non-terminals (syntactic variables)
- T: Terminals (tokens)
- P: Productions (rules of the form A→α)
- S: Start symbol Example: Arithmetic expressions
pseudoE → E + T | T // Addition T → T * F | F // Multiplication F → ( E ) | id // Primary
1.3 Derivations
A derivation is a sequence of replacements showing how a string is generated:
pseudoE ⇒ E + T ⇒ T + T ⇒ F + T ⇒ id + T ⇒ id + T * F ⇒ id + F * F ⇒ id + id * id
| Derivation Type | Description |
|---|---|
| Leftmost | Replace the leftmost non-terminal at each step |
| Rightmost | Replace the rightmost non-terminal at each step |
1.4 Parse Trees
(Diagram)
2. Eliminating Ambiguity and Left Recursion
2.1 Ambiguity
A grammar is ambiguous if a string has more than one parse tree.
Ambiguous grammar:
javaE → E + E | E * E | ( E ) | id // String "id + id * id" has two parse trees (implicit left vs right associativity)
Fix: Introduce precedence and associativity levels.
2.2 Left Recursion Elimination
A grammar is left-recursive if A⇒+Aα. This causes infinite loops in top-down parsers.
Direct left recursion: A→Aα∣β
Eliminated:
pseudoA → β A' A' → α A' | ε
Example:
pseudoBefore: E → E + T | T After: E → T E' E' → + T E' | ε
2.3 Left Factoring
When two productions for the same non-terminal share a common prefix, the parser can't decide which to choose.
Before:
pseudoS → if E then S S → if E then S else S
After:
pseudoS → if E then S S' S' → else S | ε
3. FIRST and FOLLOW Sets
3.1 FIRST(X)
The set of terminal symbols that can begin a string derived from X.
Algorithm:
pseudo1. If X is a terminal: FIRST(X) = {X} 2. If X → ε: add ε to FIRST(X) 3. If X → Y₁ Y₂ ... Yₖ: Add FIRST(Y₁) - {ε} If Y₁ ⇒* ε, add FIRST(Y₂) - {ε} If Y₁...Yₖ ⇒* ε, add ε
3.2 FOLLOW(X)
The set of terminals that can follow X in a derivation.
Algorithm:
sql1. Add $ to FOLLOW(S) 2. If A → αBβ: add FIRST(β) - {ε} to FOLLOW(B) 3. If A → αB or A → αBβ where β ⇒* ε: add FOLLOW(A) to FOLLOW(B)
3.3 Worked Example
Grammar:
pseudoE → T E' E' → + T E' | ε T → F T' T' → * F T' | ε F → ( E ) | id
| Non-terminal | FIRST | FOLLOW |
|---|---|---|
| E | { (, id } | { $, ) } |
| E' | { +, ε } | { $, ) } |
| T | { (, id } | { +, $, ) } |
| T' | { *, ε } | { +, $, ) } |
| F | { (, id } | { *, +, $, ) } |
FIRST(E) Calculation:
- E → T E': FIRST(T) = { (, id }
- Done: FIRST(E) = { (, id } FOLLOW(E) Calculation:
- $ in FOLLOW(E) (rule 1)
- F → ( E ) (rule 2): ) in FOLLOW(E)
- FOLLOW(E) = { $, ) }
4. LL(1) Parsing
4.1 LL(1) Parsing Table
Construct table M[A, a] where A is a non-terminal and a is a terminal:
pseudoFor each production A → α: 1. For each a in FIRST(α) - {ε}: add A → α to M[A, a] 2. If ε in FIRST(α): for each b in FOLLOW(A): add A → α to M[A, b]
If any cell has >1 production → grammar is NOT LL(1).
4.2 LL(1) Parsing Algorithm
pythonstack = ['$', S] # Start symbol with bottom marker input = tokens + ['$'] while stack not empty: X = stack.top() a = input[0] if X == a: pop(X); consume(a) # Match elif X is terminal: ERROR elif M[X, a] exists: pop(X) push M[X, a] in reverse order else: ERROR
4.3 Worked Example
Parse
id + id * id using the grammar above.| Step | Stack | Input | Action |
|---|---|---|---|
| 1 | $ E | id + id * id $ | Predict E → T E' |
| 2 | $ E' T | id + id * id $ | Predict T → F T' |
| 3 | $ E' T' F | id + id * id $ | Predict F → id |
| 4 | $ E' T' id | id + id * id $ | Match id |
| 5 | $ E' T' | + id * id $ | Predict T' → ε |
| 6 | $ E' | + id * id $ | Predict E' → + T E' |
| 7 | $ E' T + | + id * id $ | Match + |
| 8 | $ E' T | id * id $ | Predict T → F T' |
| 9 | $ E' T' F | id * id $ | Predict F → id |
| 10 | $ E' T' id | id * id $ | Match id |
| 11 | $ E' T' | * id $ | Predict T' → * F T' |
| 12 | $ E' T' F * | * id $ | Match * |
| 13 | $ E' T' F | id $ | Predict F → id |
| 14 | $ E' T' id | id $ | Match id |
| 15 | $ E' T' | $ | Predict T' → ε |
| 16 | $ E' | $ | Predict E' → ε |
| 17 | $ | $ | Accept! |
5. Common Pitfalls
Pitfall 1: Left recursion in LL(1) → infinite loop
Mistake: Using left-recursive grammar with recursive descent parser.
Fix: Always eliminate left recursion before building LL(1) parser.
Pitfall 2: FIRST set missing ε
Mistake: Forgetting that A→ε means ε is in FIRST(A).
Fix: If a production derives the empty string, add ε to FIRST.
Pitfall 3: Ambiguous grammar passes FIRST/FOLLOW check
Mistake: An ambiguous grammar can still pass LL(1) conditions.
Correction: LL(1) requires the grammar to be unambiguous AND not left-recursive AND left-factored. All three conditions are necessary.
6. 📝 Practice Questions
Q1: Compute FIRST and FOLLOW for: S → a S b | εAnswer:
- FIRST(S) = {a, ε}
- FOLLOW(S) = {$, b}
Derivation: FIRST(S): rule S→aSb adds a; S→ε adds ε. FOLLOW(S): $ added by rule 1. From S→a S b: b added by rule 2. Q2: Is the grammar S → a S | a LL(1)? Why or why not?Answer: No. FIRST(aS) = {a}, FIRST(a) = {a}. Cell M[S, a] has two productions → not LL(1). Fix with left factoring: S → a S', S' → S | ε. Q3: Eliminate left recursion from: A → A a | A b | c | dAnswer: A → c A' | d A', A' → a A' | b A' | ε Q4: Construct LL(1) table for: S → a B, B → b C | ε, C → cAnswer:
FIRST(S) = {a}, FIRST(B) = {b, ε}, FIRST(C) = {c} FOLLOW(S) = {}, FOLLOW(B) = {}, FOLLOW(C) = {$}
| a | b | c | $ | |
|---|---|---|---|---|
| S | S→aB | |||
| B | B→bC | B→ε | ||
| C | C→c |
Q5: What is the difference between a top-down and bottom-up parser?Answer: Top-down (LL) starts from the start symbol and tries to match the input by predicting productions (builds parse tree from root to leaves). Bottom-up (LR) starts from the input and tries to reduce to the start symbol (builds from leaves to root). LR can handle more grammars (including left-recursive ones) but is harder to construct by hand.
7. 🔗 Cross-References
- Week 2 - Lexical Analysis: Token stream from lexer feeds parser
- Week 4 - LR Parsing: More powerful bottom-up parsing
- BSCS3021 (ToC): Context-free grammars, derivation, Chomsky hierarchy Join Discord PreviousLexical AnalysisNextLR Parsing