Registry Synced

Context-Free Grammars

1971 words
10 min read

Reading compass

Now ยท ๐ŸŽฏ Learning Objectives

Context-Free Grammars

๐ŸŽฏ Learning Objectives

  • Design context-free grammars for various languages
  • Generate parse trees and derivations (leftmost, rightmost)
  • Detect and eliminate ambiguity in grammars
  • Convert CFGs to Chomsky Normal Form
  • Apply closure properties of context-free languages

1. Introduction to CFGs

1.1 Intuition

A context-free grammar is like a set of LEGO building instructions. You start with one piece (the start symbol) and repeatedly replace pieces with more detailed assemblies using rules (productions). The rules are "context-free" because you can apply them regardless of what's around the symbol โ€” just like LEGO instructions don't depend on what other bricks are nearby. CFGs describe programming languages, natural language syntax, and protocols. They're more powerful than regular expressions/DFAs โ€” they can handle nested structures like parentheses, HTML tags, and mathematical expressions.

1.2 Formal Definition

A CFG is a 4-tuple G=(V,ฮฃ,R,S)G = (V, \Sigma, R, S) where:
  • VV: Finite set of variables (nonterminals)
  • ฮฃ\Sigma: Finite set of terminals (alphabet)
  • RR: Finite set of rules (productions) of form Aโ†’ฮฑA \to \alpha where AโˆˆV,ฮฑโˆˆ(Vโˆชฮฃ)โˆ—A \in V, \alpha \in (V \cup \Sigma)^*
  • SโˆˆVS \in V: Start symbol

1.3 Derivation Example

Grammar for balanced parentheses: Sโ†’SSโˆฃ(S)โˆฃฮตS \to SS \mid (S) \mid \varepsilon Derivation of "( ) ( )":
StepSentential FormRule Applied
1SStart
2SSS โ†’ SS
3(S)SS โ†’ (S)
4()SS โ†’ ฮต
5()(S)S โ†’ (S)
6()()S โ†’ ฮต
(Diagram)

2. Derivation Types

2.1 Leftmost vs. Rightmost Derivation

TypeDescriptionExample (for Sโ†’0S1โˆฃฮตS \to 0S1 \mid \varepsilon )
LeftmostReplace leftmost variable firstSโ‡’0S1โ‡’00S11โ‡’0011S \Rightarrow 0S1 \Rightarrow 00S11 \Rightarrow 0011
RightmostReplace rightmost variable firstSโ‡’0S1โ‡’0S01โ‡’0011S \Rightarrow 0S1 \Rightarrow 0S01 \Rightarrow 0011

2.2 Worked Example

Grammar: Eโ†’E+EโˆฃEโˆ—Eโˆฃ(E)โˆฃaE \to E+E \mid E*E \mid (E) \mid a Leftmost derivation of a+aโˆ—aa+a*a: Eโ‡’E+Eโ‡’a+Eโ‡’a+Eโˆ—Eโ‡’a+aโˆ—Eโ‡’a+aโˆ—aE \Rightarrow E+E \Rightarrow a+E \Rightarrow a+E*E \Rightarrow a+a*E \Rightarrow a+a*a Rightmost derivation of a+aโˆ—aa+a*a: Eโ‡’E+Eโ‡’E+Eโˆ—Eโ‡’E+Eโˆ—aโ‡’E+aโˆ—aโ‡’a+aโˆ—aE \Rightarrow E+E \Rightarrow E+E*E \Rightarrow E+E*a \Rightarrow E+a*a \Rightarrow a+a*a Note: The parse tree for both derivations is the same: (Diagram)

3. Ambiguity

3.1 Intuition

A grammar is ambiguous if a string has more than one parse tree. This is bad for programming languages โ€” if a compiler can parse 3 + 4 ร— 5 in two ways, it gives different results (either 35 or 23).

3.2 Ambiguous Grammar

The expression grammar Eโ†’E+EโˆฃEโˆ—Eโˆฃ(E)โˆฃaE \to E+E \mid E*E \mid (E) \mid a is ambiguous. Two parse trees for a+aโˆ—aa+a*a: Tree 1 (addition first): (a+a)โˆ—a(a+a)*a โ†’ evaluates as (a+a)โˆ—a(a+a)*a Tree 2 (multiplication first): a+(aโˆ—a)a+(a*a) โ†’ evaluates as a+(aโˆ—a)a+(a*a)

3.3 Eliminating Ambiguity

Unambiguous grammar (respects precedence):
  • Eโ†’E+TโˆฃTE \to E + T \mid T
  • Tโ†’Tโˆ—FโˆฃFT \to T * F \mid F
  • Fโ†’(E)โˆฃaF \to (E) \mid a Now a+aโˆ—aa+a*a has only one parse tree (multiplication binds tighter).
RuleLevelAssociativity
Eโ†’E+TE \to E + TLowest precedenceLeft-associative
Tโ†’Tโˆ—FT \to T * FMedium precedenceLeft-associative
Fโ†’(E)โˆฃaF \to (E) \mid aHighest precedenceโ€”

4. Chomsky Normal Form (CNF)

4.1 Intuition

CNF is a simplified form where every rule has exactly two nonterminals or one terminal. This restriction makes parsing algorithms (like CYK) simpler โ€” they only need to consider combining two adjacent sub-results, like binary tree operations.

4.2 CNF Rules

Every production is of the form:
  1. Aโ†’BCA \to BC (two nonterminals)
  2. Aโ†’aA \to a (one terminal)
  3. Sโ†’ฮตS \to \varepsilon (only if ฮต is in the language, and S never appears on RHS)

4.3 Conversion Algorithm

Step 1: Add new start variable S0S_0 Step 2: Eliminate ฮต-rules (Aโ†’ฮตA \to \varepsilon) Step 3: Eliminate unit rules (Aโ†’BA \to B) Step 4: Convert remaining rules to CNF

4.4 Worked Example

Original grammar: Sโ†’ASAโˆฃaBS \to ASA \mid aB Aโ†’BโˆฃSA \to B \mid S Bโ†’bโˆฃฮตB \to b \mid \varepsilon Step 1 โ€” New start: S0โ†’SS_0 \to S Sโ†’ASAโˆฃaBS \to ASA \mid aB Aโ†’BโˆฃSA \to B \mid S Bโ†’bโˆฃฮตB \to b \mid \varepsilon Step 2 โ€” Eliminate ฮต-rules (Bโ†’ฮตB \to \varepsilon): Remove Bโ†’ฮตB \to \varepsilon. For each rule with B on RHS, add version without B: Sโ†’aBS \to aB becomes Sโ†’aBโˆฃaS \to aB \mid a Aโ†’BA \to B becomes Aโ†’BโˆฃฮตA \to B \mid \varepsilon (but we also need to handle ฮต in A) Step 3 โ€” Eliminate unit rules: S0โ†’SS_0 \to S, Aโ†’BA \to B, Aโ†’SA \to S are unit rules. Replace: Aโ†’BA \to B (and Bโ†’bB \to b) with Aโ†’bA \to b. Etc. Step 4 โ€” Convert to CNF: S0โ†’AS1โˆฃaBโˆฃaS_0 \to AS_1 \mid aB \mid a (where S1=SAS_1 = SA) ... Final CNF: S0โ†’AS1โˆฃaBโˆฃaS_0 \to AS_1 \mid aB \mid a S1โ†’SAS_1 \to SA Sโ†’AS1โˆฃaBโˆฃaS \to AS_1 \mid aB \mid a Aโ†’bโˆฃAS1โˆฃaBโˆฃaA \to b \mid AS_1 \mid aB \mid a Bโ†’bB \to b

5. Common Pitfalls

Pitfall 1: Left Recursion in Top-Down Parsing

The mistake: Using Aโ†’AฮฑA \to A\alpha (left recursion) in a grammar for top-down parsing. Why students make it: Left recursion is natural for left-associative operations (Eโ†’E+TE \to E + T). How to catch it: Top-down parsers (like LL parsers) infinite loop on left recursion. Correct approach: Use right recursion or EBNF for top-down grammars, or use table-driven parsers (LR) that handle left recursion.

Pitfall 2: Confusing Derivation with Parse Tree

The mistake: Thinking two different derivations mean the grammar is ambiguous. Why students make it: Different derivation orders (leftmost vs. rightmost) give different derivations. How to catch it: Ambiguity means different parse trees, not different derivation orders. Leftmost and rightmost derivations of the same string may differ but have the same parse tree. Correct approach: Two derivations with the same parse tree โ‰  ambiguity. Different parse trees = ambiguity.

Pitfall 3: Incorrect ฮต-Rule Elimination

The mistake: Removing ฮต-rules without adding replacement rules for all affected productions. Why students make it: The algorithm seems simple โ€” just remove ฮต and add new versions. How to catch it: Some derivations that were valid before elimination become invalid because required replacements were missed. Correct approach: Find all nullable variables systematically. For every rule Aโ†’X1X2...XnA \to X_1X_2...X_n, generate all combinations of replacing nullable XiX_i with ฮต.

6. Key Concepts Reference

ConceptDefinitionApplication
CFGG=(V,ฮฃ,R,S)G = (V, \Sigma, R, S)Describes context-free languages
DerivationStep-by-step replacement of variablesGenerating strings
Parse treeTree representation of derivationSyntax analysis
AmbiguityMultiple parse trees for same stringProblem for compilers
CNFAโ†’BCA \to BC or Aโ†’aA \to a onlyCYK parsing algorithm
Leftmost/RightmostOrder of variable replacementDerivation strategies

7. ๐Ÿ“ Practice Questions

Q1: Design a CFG for L = {0^n1^n | n โ‰ฅ 0}.
Answer: S โ†’ 0S1 | ฮต
Derivation for 0011: S โ‡’ 0S1 โ‡’ 0(0S1)1 โ‡’ 00ฮต11 = 0011 Q2: Is the grammar S โ†’ 0S1 | 0S | ฮต ambiguous?
Answer: Yes. The string "01" can be derived in two ways: (1) S โ‡’ 0S1 โ‡’ 0(ฮต)1 = 01 (2) S โ‡’ 0S โ‡’ 0(0S1) โ‡’ 0(0ฮต1) = 001 โ€” wait that's "001", not "01".
Actually let me re-check. For grammar S โ†’ 0S1 | 0S | ฮต: String "01": S โ‡’ 0S1 โ‡’ 0ฮต1 = 01 (only this way) String "0": S โ‡’ 0S โ‡’ 0ฮต = 0 (only this way) String "00": S โ‡’ 0S โ‡’ 0(0S) โ‡’ 0(0ฮต) = 00 OR S โ‡’ 0S โ‡’ 0(0ฮต) = 00 (same) This grammar is actually unambiguous. Q3: Convert S โ†’ aSa | bSb | ฮต to CNF.
Answer:
Step 1: New start Sโ‚€ โ†’ S. Step 2: No ฮต-rules (except S โ†’ ฮต, which is already in CNF form). Step 3: No unit rules. Step 4: Convert: S โ†’ aSa becomes S โ†’ Aโ‚Aโ‚‚ where Aโ‚ โ†’ a, Aโ‚‚ โ†’ Sa, and Sa โ†’ S Aโ‚ Similarly for bSb.
Final CNF: Sโ‚€ โ†’ Aโ‚Aโ‚ƒ | Aโ‚„Aโ‚† | ฮต S โ†’ Aโ‚Aโ‚ƒ | Aโ‚„Aโ‚† | ฮต Aโ‚ โ†’ a Aโ‚ƒ โ†’ SAโ‚ Aโ‚„ โ†’ b Aโ‚† โ†’ SAโ‚„
(where Aโ‚ƒ = Sa, Aโ‚† = Sb) Q4: Why are CFGs called "context-free"?
Answer: They are "context-free" because the replacement rule for a variable A โ†’ ฮฑ can be applied regardless of the surrounding symbols (context). Contrast with context-sensitive grammars where the rule may depend on neighboring symbols (e.g., aA โ†’ bc means "replace A with c only if preceded by a"). Q5: Design a CFG for L = {a^i b^j c^k | i โ‰  j}.
Answer: This is tricky because we can't directly compare. Approach: generate a^i b^j where i โ‰  j, then add matching c's.
S โ†’ AC | BC A โ†’ aA | a (at least one a) C โ†’ cC | ฮต (any number of c's) B โ†’ bB | b (at least one b)
Wait, this doesn't capture i โ‰  j properly. Better:
S โ†’ aSc | aXc | Y (where X generates more a's, Y generates more b's) X โ†’ aX | a Y โ†’ bYc | bY | b
Actually let me think again:
L = {a^i b^j c^k | i โ‰  j}
S โ†’ aSc | T T generates {a^i b^j | i โ‰  j} (with extra c's? No, c's handled by S)
Better approach: S โ†’ aSc | U U โ†’ aU | aUb | Ub | ab (generate strings where a count โ‰  b count)
Hmm, this is complex. Let me simplify:
S โ†’ aSc | T T โ†’ aT | aTb | Tb | ab But T needs to ensure i โ‰  j.
The standard approach: generate three regions separately: S โ†’ aSc | T (S matches c's with outer a's) T โ†’ aTb | A | B (T generates inner a's and b's) A โ†’ aA | a (more a's) B โ†’ bB | b (more b's)
This generates a^i b^j c^k where i = (k + #inner a's), j = #inner b's, and if inner a's โ‰  inner b's then i โ‰  j. Q6: What is the Pumping Lemma for context-free languages?
Answer: If L is a CFL, there exists p > 0 such that any string s โˆˆ L with |s| โ‰ฅ p can be written as s = uvxyz where:
  1. |vy| โ‰ฅ 1 (at least one of v or y is non-empty)
  2. |vxy| โ‰ค p (the pumped region is bounded)
  3. uv^i xy^i z โˆˆ L for all i โ‰ฅ 0
This differs from the regular pumping lemma: we're pumping TWO parts (v and y) simultaneously, reflecting the binary branching structure of CFG parse trees. Q7: Design a CFG for the language of palindromes over {0,1}.
Answer: S โ†’ 0S0 | 1S1 | 0 | 1 | ฮต
This generates strings that read the same forwards and backwards. For "0110": S โ‡’ 0S0 โ‡’ 0(1S1)0 โ‡’ 0(1ฮต1)0 = 0110 Q8: Why is the grammar E โ†’ E + T | T, T โ†’ T * F | F, F โ†’ (E) | a unambiguous for expressions?
Answer: It enforces precedence: + is at the top level (E), * is one level lower (T), and atoms/literals are innermost (F). Since each string forces a specific derivation order (you must expand E first to add, T to multiply, F for parentheses), there's exactly one parse tree. Left recursion in E โ†’ E + T forces left associativity โ€” there's no alternative way to group the same string.

8. ๐Ÿ”— Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of contents

No note selected

Open a chapter to see its outline, backlinks, and sources here.