Quiz 2

Syntax Analysis — CFG, LL(1), FIRST/FOLLOW, Recursive Descent

1350 words
7 min read
Python Week 1: the first filter for runtime behavior
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)G = (V, T, P, S)
  • V: Non-terminals (syntactic variables)
  • T: Terminals (tokens)
  • P: Productions (rules of the form AαA \to \alpha)
  • S: Start symbol Example: Arithmetic expressions
pseudo
E  → 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:
pseudo
E ⇒ E + T ⇒ T + T ⇒ F + T ⇒ id + T
  ⇒ id + T * F ⇒ id + F * F ⇒ id + id * id
Derivation TypeDescription
LeftmostReplace the leftmost non-terminal at each step
RightmostReplace 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:
java
E → 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αA \Rightarrow^+ A\alpha. This causes infinite loops in top-down parsers. Direct left recursion: AAαβA \to A\alpha | \beta Eliminated:
pseudo
A  → β A'
A' → α A' | ε
Example:
pseudo
Before: 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:
pseudo
S → if E then S
S → if E then S else S
After:
pseudo
S  → 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:
pseudo
1. 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:
sql
1. 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:
pseudo
E  → T E'
E' → + T E' | ε
T  → F T'
T' → * F T' | ε
F  → ( E ) | id
Non-terminalFIRSTFOLLOW
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:
pseudo
For 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

python
stack = ['$', 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.
StepStackInputAction
1$ Eid + id * id $Predict E → T E'
2$ E' Tid + id * id $Predict T → F T'
3$ E' T' Fid + id * id $Predict F → id
4$ E' T' idid + 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' Tid * id $Predict T → F T'
9$ E' T' Fid * id $Predict F → id
10$ E' T' idid * id $Match id
11$ E' T'* id $Predict T' → * F T'
12$ E' T' F ** id $Match *
13$ E' T' Fid $Predict F → id
14$ E' T' idid $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εA \to \varepsilon 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 | d
Answer: A → c A' | d A', A' → a A' | b A' | ε Q4: Construct LL(1) table for: S → a B, B → b C | ε, C → c
Answer:
  • FIRST(S) = {a}, FIRST(B) = {b, ε}, FIRST(C) = {c}
  • FOLLOW(S) = {}, FOLLOW(B) = {}, FOLLOW(C) = {$}
abc$
SS→aB
BB→bCB→ε
CC→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

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.