Unit 3, Part 2 of 2 · CEUC302 Theory of Computation
Every grammar, one predictable shape
Chapter 4 designed grammars by hand, one rule at a time. This chapter makes them algorithm-ready: two standard shapes every CFG can be rewritten into (Chomsky and Greibach Normal Form), the CFL version of the Pumping Lemma (a genuinely different argument from Chapter 3's), the surprising limits of what closure buys you, and the parsing algorithm — CYK — that CNF was quietly designed to make possible. Gexpr-fixed from Chapter 4 carries the whole chapter; L3eq = {anbncn} returns for one more classification, its fourth and final proof of not-quite-fitting.
Chomsky Normal Form
A uniform shape, verified to preserve the language
Binary branching everywhere, or a single terminal — nothing else, ever.
Section 5.1 converts Gexpr-fixed into exactly this shape, step by step, checked against 19,531 test strings. CNF's uniformity is what makes Section 5.4's parsing algorithm possible at all.
Before you start
This chapter assumes Chapter 4's Gexpr-fixed (E→E+T|T; T→T*F|F; F→(E)|id) by name throughout, plus Chapter 3's Pumping Lemma structure (the new CFL version mirrors it, with a genuinely different mechanism) and Chapter 3's regular-language closure results (the contrast is the point of Section 5.3).
Two standard shapes, one language each time
An algorithm that processes grammars for a living needs to know exactly what shape a rule can take. Normal forms are that promise, kept.
Gexpr-fixed is a perfectly good grammar, but its rules come in several shapes: some length 1 (E→T), some length 3 (F→(E), with terminals on both sides of a non-terminal). An algorithm built to process "a rule" needs to know in advance what a rule looks like. Normal forms answer that, without changing what language results.
The intuition, no symbols yet
Chomsky Normal Form (CNF) makes every rule either "split into exactly two pieces" or "produce exactly one terminal and stop" — a parse tree in CNF is always binary-branching, which is exactly the shape a table-filling algorithm (Section 5.4) can process mechanically. Greibach Normal Form (GNF) instead makes every rule "emit one terminal right now, then hand off to some non-terminals" — useful because it means you always know the very next output symbol the moment you choose a rule, which is exactly the shape Unit 4's pushdown automata construction wants.
The formal treatment
| form | every rule looks like | why it matters |
|---|---|---|
| CNF | A → BC (two non-terminals) or A → a (one terminal) | binary trees → CYK parsing, Pumping Lemma proofs (5.2, 5.4) |
| GNF | A → aβ (one terminal, then zero or more non-terminals) | always know the next symbol → PDA construction (Unit 4) |
Worked example · Gexpr-fixed into CNF
Worked example
TERM, then BIN, then UNIT (no DEL needed here)Every rule is now exactly A→BC or A→a. Cross-checked against the original grammar's recognizer over all 19,531 token sequences up to length 6 built from {id,+,*,(,)}: zero disagreements. (F survives as its own non-terminal, even though its rules got copied up into E and T by the UNIT step, because X2→AstarF still needs it directly.)
A second, smaller example · into GNF
GNF conversion is most direct when the grammar has no left recursion. Take S→AB; A→aA|a; B→bB|b (generating a+b+). S→AB starts with a non-terminal — not GNF-legal — but A always starts with a, so substitute every way A could begin directly into S:
Every rule now starts with exactly one terminal. Verified against a direct a+b+ checker for every string up to length 8: zero mismatches.
When left recursion is present, GNF needs one more step first
Gexpr-fixed's E→EX1 (from the CNF conversion above) is left-recursive — E appears as the very first symbol of its own rule. Substitution alone loops forever here (replacing E's leading E with another EX1 just reproduces the same problem). The standard fix eliminates left recursion first (rewriting E→EX1|T into a right-recursive equivalent using a fresh non-terminal that absorbs the repeated X1's), then substitutes as above. That extra step is real algorithmic work, which is exactly why this section demonstrates GNF on a smaller, already-right-recursive example rather than forcing it through Gexpr-fixed.
Don't confuse these
- Normal form conversions change rule shape, never the language. Same theme as every restructuring in this book: L(CNF version) = L(original), always, or the conversion has a bug.
- Skipping DEL because one example didn't need it isn't a general rule. Any grammar with real ε-productions needs that step; Gexpr-fixed simply had none to remove.
- GNF's "β" is zero or more non-terminals, not "one or more." A→a alone (no non-terminals after the terminal) is perfectly valid GNF — it's the base case, not an exception.
Practice · 5.1
Seven problems
direct → variation → interpretation → synthesis5.1.1 · Is the rule X1 → AplusT in CNF? Is F → id?
Show solution
Both yes. X1→AplusT is A→BC (two non-terminals). F→id is A→a (one terminal, since "id" is treated as a single atomic terminal symbol).
5.1.2 · Convert S → aSb | ab into CNF (this is a simpler Leq-style grammar).
Show solution
TERM: Aa→a, Ab→b, giving S→AaSAb | AaAb. BIN: S→AaX, X→SAb. Final: S→AaX | AaAb; X→SAb; Aa→a; Ab→b. (No UNIT or DEL step needed — no unit or ε rules existed to begin with.)
5.1.3 · Variation: is S → AB; A → aA | a; B → b already close to GNF, or does it need the substitution step from this section's worked example?
Show solution
It still needs the same substitution: S→AB starts with a non-terminal, so it must become S→aAB|aB exactly as before. Only B's own rule changed (B→b instead of B→bB|b), which doesn't affect whether S needs fixing.
5.1.4 · Variation: why does S0 end up with exactly the same four rules as E in the finished CNF grammar?
Show solution
Because S0→E was itself a unit rule, and the UNIT-removal step for S0 copies all of E's (already non-unit) rules directly — S0's unit-closure is exactly {S0,E,T,F}, the same closure E itself sits inside, so they end up with identical rule sets, differing only in name.
5.1.5 · Interpretation: a classmate's CNF conversion drops F entirely, arguing "F's rules got copied into E and T, so F is redundant." Check this against X2→AstarF.
Show solution
Dropping F breaks the grammar — X2→AstarF still directly names F on its right side, and X2 is reachable (via T→TX2). UNIT-removal copies a target's rules upward to whoever pointed at it via a unit rule; it never deletes the target itself if something else still legitimately needs it.
5.1.6 · Interpretation: does converting to CNF ever change how many strings of a given length the grammar generates?
Show solution
No. Since L(CNF version) = L(original) exactly, the count of length-n strings in the language is identical before and after — only the internal machinery (number of non-terminals, rule shapes, even the parse tree's exact shape) changes, never the language itself or anything derived purely from the language.
5.1.7 · Synthesis (challenge) · Both CNF's UNIT-removal and Section 5.1's GNF substitution work by "copying rules from what a symbol points to, upward." Explain the one key difference: why does GNF substitution risk looping forever on left recursion while CNF's UNIT-removal never does.
Show solution
UNIT-removal only ever copies rules along unit chains (A→B, one non-terminal to another), and a grammar has only finitely many non-terminals, so the unit-closure computation (Section 5.1) always terminates — there's no way to "regenerate the same problem." GNF substitution, though, replaces a leading non-terminal with all of its right-hand sides, including ones that might start with the very same non-terminal being substituted (that's exactly what left recursion is) — so a naive substitution can reintroduce the identical leading non-terminal it just tried to remove, forever. That's precisely why left-recursion elimination has to happen as a separate, prior step.
Tall trees repeat non-terminals, not states
Chapter 3's Pumping Lemma found a repeated state along a string. This one finds a repeated non-terminal along a tree — a genuinely different mechanism, reused for a genuinely different purpose.
A DFA has no tree; it just has a path through states, one step per symbol, so a long string forces a repeated state. A CNF parse tree has no single path through states — but it does have root-to-leaf paths, and a long string forces a tall tree. With only finitely many non-terminals available, a tall enough path must repeat one of them. That repeat is this section's engine.
The intuition, no symbols yet
In a CNF tree, every internal node has exactly two children, so a tree with many leaves must have some path from root to leaf that's long — longer, in fact, than the number of distinct non-terminals available. Walk down that one long path: with more steps than non-terminals to choose from, some non-terminal A must appear twice. Everything hanging below the second A is a self-contained sub-tree rooted at A — and since the first A led, eventually, to that exact same sub-tree shape being viable, you can unplug it and replace it with nothing, or with another copy of the whole first-to-second-A chunk, without upsetting anything above.
a repeated non-terminal along one path
fig 5.2The formal treatment
The disproof pattern is identical in spirit to Chapter 3's: assume L is context-free, pick a suitable w∈L with |w|≥p, and show every valid (u,v,x,y,z) split fails condition (3) for some i — contradicting the assumption.
Worked example · L3eq is not context-free
Worked example
the fourth and final classification of {anbncn}Don't confuse these
- Two pieces are pumped, and always together. uv3xy3z is valid to consider; uv3xy1z is not — v and y always share the same exponent i, because they come from the same repeated non-terminal's two occurrences in one tree.
- |vxy|≤p bounds the combined middle, not v or y individually. x (the part between v and y) can be long; the constraint is on v, x, and y together.
- This is not the regular Pumping Lemma with new letters. The regular version pumps one piece via a repeated state along a string; this version pumps two pieces at once via a repeated non-terminal along a tree. Writing "xyz" instead of "uvxyz" here, or pumping only one piece, mixes the two lemmas up.
Practice · 5.2
Seven problems
direct → variation → interpretation → synthesis5.2.1 · State, from memory, why the CNF restriction (binary branching) is what makes the "tall tree forces a repeat" argument work.
Show solution
With branching factor exactly 2, a tree with n leaves must have height at least log2n — more leaves forces a taller tree, and a tall enough tree has a root-to-leaf path longer than the number of available non-terminals, forcing pigeonhole to bite along that specific path.
5.2.2 · For p=3, w=a3b3c3, give one valid (u,v,x,y,z) split with v="aa" and confirm pumping to i=0 breaks membership.
Show solution
u="a", v="aa" (from the remaining a's, |vxy|=2≤3), x="", y="" (or fold whatever remains appropriately) — concretely u=ε, v=aa, x=a, y=ε, z=b3c3 works: |vxy|=3≤p, |vy|=2>0. At i=0: uv0xy0z = xz = a b3c3, which has 1 a but 3 b's and 3 c's — not in L3eq.
5.2.3 · Variation: adapt the proof to show {anbncndn} is not context-free.
Show solution
Pick w=apbpcpdp. |vxy|≤p is still shorter than any single block (length p), so vxy touches at most 2 of the 4 blocks, leaving at least 2 blocks' counts completely unpumped. Pumping breaks the required 4-way equality. (Verified exhaustively at p=3: all 169 valid splits fail.)
5.2.4 · Variation: does this Pumping Lemma, by itself, tell you whether {anbncn} is context-sensitive?
Show solution
No — the CFL Pumping Lemma only ever disproves context-freeness (or, used positively, is consistent with it). Whether L3eq is context-sensitive is a separate question, answered instead by exhibiting an actual context-sensitive grammar for it (Chapter 1, Section 1.3) — a different kind of evidence entirely.
5.2.5 · Interpretation: a proof picks v and y from two different, unrelated substrings of w, not from a genuinely repeated non-terminal's two occurrences. What's wrong?
Show solution
v and y aren't free-floating choices — they're forced to be exactly the material between two occurrences of the same non-terminal along one root-to-leaf path (v to the left of the inner occurrence's subtree, y to the right, within the outer occurrence's span). Picking two unrelated chunks isn't a valid instance of the lemma's guarantee at all.
5.2.6 · Interpretation: does surviving every pumping attempt prove a language is context-free?
Show solution
No — exactly as in the regular case (Chapter 3), the lemma is necessary, not sufficient. Some non-context-free languages can still admit strings and splits that happen not to produce a contradiction under this particular test; only an actual CFG (or PDA, Unit 4) proves context-freeness.
5.2.7 · Synthesis (challenge) · Both Pumping Lemmas end with the same logical shape (assume a bound p exists, find one bad string, show every split fails). Name the one structural fact about the underlying machine that changes between them, and trace how it forces "one pumped piece" to become "two pumped pieces together."
Show solution
The regular machine processes a string as a single linear path through states, so a repeat gives exactly one loop, one pumped piece. The CNF machine processes a string as a tree, and a repeated non-terminal along one path splits the tree into three regions stacked vertically: above the first occurrence (u and z, outside), between the two occurrences (v on one side, y on the other, wrapping around the inner subtree), and the inner subtree itself (x). Because v and y are the two "wrapping" halves of the same loop-like structure, seen from a tree rather than a line, they must be pumped together — they're really one loop, just split by the branching structure into a left half and a right half.
Where the parallel with regular languages breaks
Union, concatenation, star: CFLs get all three, easily. Intersection and complement: they don't — and the reason is the same fact that just proved L3eq isn't context-free.
Chapter 3 proved regular languages closed under five operations: union, intersection, complement, concatenation, star. It's tempting to assume context-free languages inherit all five automatically. Three transfer cleanly. Two don't, and the counterexample doubles as one of this chapter's cleanest results.
The intuition, no symbols yet
Given two grammars, gluing them together with a fresh start symbol is easy: "generate from grammar 1 or grammar 2" (union) and "generate from grammar 1 then grammar 2" (concatenation) are both just new rules bolted on top, without touching either original grammar's internal logic. Intersection has no equivalent trick — there's no way to run "grammar 1's derivation and grammar 2's derivation, simultaneously, checking they agree" using only grammar rules, the way Chapter 3's product construction could run two automata's states side by side.
The formal treatment · three closures, quickly
None of this touches G1 or G2's existing rules — each closure is a small wrapper, which is exactly why they're easy.
Where it breaks · intersection and complement
Worked example
two context-free languages, one non-context-free intersectionThis is the same L1, L2 pattern as Section 4.4
Section 4.4's inherently ambiguous language was exactly L1∪L2 using this section's L1, L2. There, the union stayed context-free but became inherently ambiguous, because strings satisfying both conditions had two independent "reasons" for membership. Here, the intersection asks for both conditions to hold on the same string, which is a strictly harder demand — hard enough to leave the context-free class altogether. Union weakens a demand (either reason suffices); intersection tightens it (both must hold) — and tightening is what breaks closure entirely, not just cleanliness.
Complement doesn't survive either, and the proof is pure logic, no new construction needed: if CFLs were closed under complement, then by De Morgan's law (L1∩L2 = complement of ((L1)̄ ∪ (L2)̄)), closure under complement plus the already-proven closure under union would force closure under intersection too — contradicting the worked example just above. So complement closure must fail as well.
Decidability · three questions, all answerable
| question | decidable? | how |
|---|---|---|
| Membership: is w ∈ L(G)? | yes | CYK (Section 5.4), polynomial time in |w| |
| Emptiness: is L(G) = ∅? | yes | mark "generating" non-terminals bottom-up; empty iff the start symbol never gets marked |
| Finiteness: is L(G) finite? | yes | check whether any reachable, generating non-terminal has a productive cycle (A ⇒+ αAβ, αβ≠ε) |
Worked example
catching an empty languageFiniteness works similarly: S→AB; A→aA|a; B→b is infinite because A has a productive cycle (A⇒aA, adding a real "a" each time before optionally looping again) that's reachable from S and still generating — A alone already produces a,aa,aaa,… unboundedly, so S=AB inherits infinitely many strings. Change A's rule to A→a|aa (no self-reference at all) and the same-shaped grammar becomes finite: exactly {ab, aab}, two strings, because no non-terminal anywhere can ever regenerate itself.
Not everything about CFGs is decidable
Membership, emptiness, and finiteness are all decidable. Equivalence — given two CFGs, do they generate the exact same language? — is not. There's no general algorithm that always answers this correctly and halts. This is a genuine taste of Unit 5's territory (undecidability), arriving here only as a named fact to file away, not something this chapter proves.
Don't confuse these
- "Not closed under ∩" doesn't mean "always escapes." Some pairs of CFLs intersect to give another perfectly good CFL (or even a regular language). The claim is only that the class doesn't guarantee it — L3eq is a counterexample showing the guarantee can fail, not a claim that every intersection fails.
- Decidable doesn't mean fast, and undecidable doesn't mean "hasn't been solved yet." CYK is decidable and polynomial; equivalence-checking is provably, permanently undecidable — no amount of cleverness or future research fixes that, the same way no clever DFA fixes Leq's non-regularity.
Practice · 5.3
Seven problems
direct → variation → interpretation → synthesis5.3.1 · Using the union construction, write the single new rule needed to combine Gbal (Chapter 4) and Gexpr-fixed into one grammar for their union.
Show solution
S → Sbal | Sexpr (renaming each grammar's own start symbol so they don't collide), with both original rule sets left completely untouched.
5.3.2 · Run the generating-non-terminal marking algorithm on S→AB; A→a; B→b; C→SC (C is otherwise unused). Is L(G) empty?
Show solution
Round 1: A, B marked (both A→a, B→b are all-terminal). Round 2: S→AB has both A,B marked → S marked. Not empty — L(G)={ab}. (C never gets marked, and never needs to — it's unreachable from S, which the marking algorithm doesn't even need to notice, since it only asks whether S itself is generating.)
5.3.3 · Variation: is L1∩Leq (Section 5.3's L1={anbncm} intersected with Chapter 1's Leq={anbn}, both over compatible alphabets) guaranteed to be non-context-free, by the reasoning in this section?
Show solution
No such guarantee — and in fact this particular intersection is context-free: it's exactly {anbn : n≥0} (Leq itself has no c's, forcing m=0 in L1's pattern too). The trap box's first warning applies directly: failing to be closed under an operation means no guarantee, not a guaranteed failure every time.
5.3.4 · Variation: is S→AB; A→aA|ε; B→bB|ε finite or infinite? Identify the productive cycle(s) if any.
Show solution
Infinite. Both A (A⇒aA) and B (B⇒bB) have productive cycles on their own, and both are reachable and generating (both can bottom out via their ε option) — so S=AB already generates aibj for every i,j≥0, infinitely many strings.
5.3.5 · Interpretation: a classmate argues "CFLs must be closed under complement, since regular languages are and CFLs are 'bigger.'" What's the flaw?
Show solution
Being a larger class doesn't inherit smaller classes' closure properties — each closure property needs its own proof (or disproof) for each class. "Regular languages have property X" is evidence about regular languages specifically; CFLs sitting one tier up in Chapter 1's hierarchy says nothing by itself about which of X's proofs still go through.
5.3.6 · Interpretation: does the emptiness-checking algorithm ever need to actually enumerate strings of L(G)?
Show solution
No — it only ever inspects the grammar's rules directly (checking which non-terminals can eventually bottom out in terminals), never generates or tests any actual string. This is exactly why it terminates quickly regardless of whether L(G) would turn out to be small or unboundedly large.
5.3.7 · Synthesis (challenge) · Explain why "decidable" (membership, emptiness, finiteness) and "closed" (union, concatenation, star, but not intersection or complement) are answering two different kinds of question about CFGs, using this section's results as examples of each.
Show solution
Decidability asks "can an algorithm always correctly answer a yes/no question about a given grammar (or grammar plus string) in finite time" — a question about computability of grammar-level facts. Closure asks "if I combine languages from the class using some operation, must the result still belong to the same class" — a question about the class's structure under set operations. They're independent: a class can be closed under an operation while some decision problem about it remains hard or undecidable, or (as here) decidable in every individual case while still failing to be closed under a particular combination. CFLs happen to have nice decidability (membership, emptiness, finiteness) and only partial closure (three of five operations) — the two properties don't move together.
CNF's whole reason for existing
A table, filled from the shortest spans up to the longest. No guessing, no backtracking — just combining two already-solved smaller answers.
Every earlier section in this chapter was, in one way or another, preparing for this one. CNF's binary-branching shape (5.1) is precisely what lets a parsing algorithm ask "can this span split into two smaller already-solved spans?" and get a clean yes/no every time.
The intuition, no symbols yet
To know whether a long span of the input matches some non-terminal, first know the answer for every shorter span; a long span matches A exactly when it splits into two shorter, already-solved pieces that some rule A→BC can glue together. Build up from single tokens (trivial) to the whole string, and the last cell answers the entire question.
The formal treatment · the CYK recurrence
Every cell only ever looks at shorter spans already computed — filling order (shortest first) guarantees the pieces a cell needs are always ready before that cell is attempted.
Filling the table for "id+id*id"
CYK table, step by step
fig 5.4 · interactiveReading the finished table: span [0,5) — the whole string — ends up containing {E, S0}. Since S0 (the designated start symbol) is present, "id+id*id" is accepted. Every intermediate cell recorded exactly which non-terminals could explain that particular substring on its own, bottom-up, with no guessing about which rule to try first.
Top-down parsing, briefly
The opposite strategy starts at S and predicts which rule to expand next, consuming input as it goes — this is exactly the recursive-descent style used to verify every grammar in this chapter computationally. It works cleanly on Gexpr-fixed only because the left-recursive rules (E→E+T, T→T*F) were implemented as loops rather than direct recursive calls — "keep consuming +T while a + is next," not "recursively descend into E first." A naive recursive-descent call that dives straight into the left-recursive E before consuming anything loops forever, never reading a token — the exact practical shadow of Section 5.1's left-recursion problem for GNF. CYK sidesteps this entirely by never recursing at all; it only ever looks at strictly shorter spans already sitting in the table.
Don't confuse these
- CYK needs CNF specifically. The A→BC/A→a shape is what makes "try every split point" a well-defined, finite operation; a grammar with longer or differently-shaped rules doesn't fit the recurrence as written.
- A populated cell isn't automatically "accept." Only the single cell table[0][n] containing the start symbol specifically means the whole string is in the language; every other filled cell is just an intermediate fact about some substring.
- CYK doesn't count parse trees. If a cell ends up containing some non-terminal via two different splits, CYK notices only that the non-terminal belongs there at all — ambiguity (Chapter 4) is a separate question CYK's basic membership version doesn't answer without extra bookkeeping.
Practice · 5.4
Seven problems
direct → variation → interpretation → synthesis5.4.1 · What non-terminals populate table[2][3] (the span containing just the second "id")?
Show solution
{E, F, S0, T} — every non-terminal whose CNF rules include a direct A→id option, exactly the same set as every other single-"id" span.
5.4.2 · Why is table[0][2] (spanning "id+") empty?
Show solution
Combining table[0][1]={E,F,S0,T} with table[1][2]={Aplus} needs some rule A→X·Aplus with X in the first set — no CNF rule in this grammar ever ends with Aplus as its second symbol (Aplus only ever appears as the first symbol, in X1→AplusT). No match, cell stays empty.
5.4.3 · Variation: which cell would need to contain S0 for the string "id*id" (3 tokens) to be accepted, and what would populate it?
Show solution
table[0][3] (the full span). Following the same recurrence: table[0][1]={E,F,S0,T}, table[1][2]={Astar}, table[2][3]={E,F,S0,T}; table[1][3] combines the last two via X2→AstarF → {X2}; table[0][3] combines table[0][1] with table[1][3]={X2} via rules ending in X2: S0→T·X2, E→T·X2, T→T·X2 (all need T, present in table[0][1]) → {E, S0, T}. S0 present → accept.
5.4.4 · Variation: would CYK correctly reject "id+*id" (missing the second operand)? Which cell fails to ever contain anything useful?
Show solution
Yes, it rejects correctly. Tokens are id,+,*,id. table[1][2]={Aplus}, table[2][3]={Astar} — combining two "bare operator" spans matches no rule (no rule has the shape A→Aplus·Astar), so table[1][3] stays empty, and every larger span depending on it stays empty too. The full span table[0][4] never contains S0.
5.4.5 · Interpretation: a classmate reads table[2][5]={E,S0,T} (from the worked example) and declares "id*id is accepted." What's wrong?
Show solution
table[2][5] describes the substring from position 2 to 5, which is "id*id" — but that's a sub-span of the full string "id+id*id", not the whole input being parsed. Acceptance is only ever decided by table[0][n] (the full span, positions 0 to the very end); any other cell containing S0 just means that particular substring, considered on its own, would be a valid expression — interesting, but not what's actually being asked.
5.4.6 · Interpretation: why does CYK never need to backtrack, unlike some top-down strategies?
Show solution
Every cell's content is a complete, final answer about that exact span the moment it's computed — there's no "guess a rule, consume input, discover a dead end, undo" cycle, because CYK never consumes input in one direction while guessing; it only ever combines two answers that are already fully settled. There's nothing to walk back from.
5.4.7 · Synthesis (challenge) · CYK runs in O(n3) time (roughly: n2 cells, each checking up to n split points). Explain in your own words why moving to a grammar that isn't in CNF would break this time bound, not just the correctness of the recurrence.
Show solution
The O(n3) bound relies on each cell needing only one split point to check per candidate rule, because every CNF rule has exactly two symbols on the right — a single number (the split position) fully describes how to divide a span in two. A rule with three or more symbols on the right would need multiple split points chosen simultaneously (dividing a span into three or more pieces), multiplying the number of combinations to check at each cell and pushing the time bound higher for every extra symbol a rule shape is allowed to have. CNF isn't just a convenience for stating the recurrence cleanly; it's specifically what pins the split-search cost at "one number to try," which is what keeps the whole algorithm cubic instead of worse.
Cheat sheet
One line per idea. If a line doesn't ring a bell, that section needs a re-read before Chapter 6.
Normal forms · 5.1
CNF: A→BC or A→a, always
GNF: A→aβ, one terminal then ≥0 non-terminals
steps: START, TERM, BIN, UNIT, (DEL if ε-rules exist)
left recursion blocks naive GNF substitution — eliminate it first
CFL Pumping Lemma · 5.2
w=uvxyz, |vy|>0, |vxy|≤p, uvixyiz∈L for all i
engine: tall CNF tree ⇒ repeated non-terminal on some root-to-leaf path
v, y pumped together — two pieces, one shared exponent
necessary, not sufficient, same as the regular version
Closure & decidability · 5.3
closed: ∪, concatenation, star — each just a wrapper rule
not closed: ∩, complement — L3eq is the counterexample
decidable: membership, emptiness, finiteness
undecidable: CFG equivalence (a Unit 5 preview)
Parsing · 5.4
CYK: table[i][j] built from shorter spans, shortest-first, O(n3)
accept iff S0 ∈ table[0][n], the full span only
needs CNF specifically — the split search relies on exactly 2 symbols per rule
top-down: loops replace left-recursive calls; CYK never recurses at all
Ten questions, no section labels
Exams don't tell you which lecture a question came from. Neither does this.
ST1 · Which of these are valid CNF rules: A→BC, A→a, A→aB?
Show solution
A→BC and A→a only. A→aB mixes a terminal with a non-terminal — neither the "two non-terminals" nor the "one terminal alone" CNF shape.
ST2 · Is A → a (a lone terminal, no non-terminals after it) valid GNF?
Show solution
Yes. GNF requires a terminal followed by zero or more non-terminals — zero is allowed, this is the base case, not an exception.
ST3 · In the L3eq Pumping Lemma proof, why can't vxy ever span all three of the a-, b-, and c-blocks at once?
Show solution
Each block alone already has length p, and |vxy|≤p — touching parts of all three blocks would require passing entirely through the middle block, needing more than p symbols. vxy is confined to at most two adjacent blocks.
ST4 · True or false: context-free languages are closed under union. Under intersection?
Show solution
Union: true (a simple wrapper rule). Intersection: false (L1∩L2=L3eq, Section 5.3's counterexample).
ST5 · Using De Morgan's law and this chapter's results, explain in one line why CFLs can't be closed under complement.
Show solution
If complement were closed, union-closure (true) plus complement-closure would force intersection-closure via L1∩L2=complement(complement(L1)∪complement(L2)) — but intersection-closure is false, so complement-closure must be false too.
ST6 · For S→AB; A→a; B→Cb; C→Sc (nothing else), is L(G) empty?
Show solution
Yes, empty. A is generating (A→a). B needs C generating; C needs S generating; S needs A and B generating — B and S and C form a closed loop with no terminal-only escape, so none of them ever get marked. S is never generating ⇒ L(G)=∅.
ST7 · Is it decidable, given two arbitrary CFGs, whether they generate the same language?
Show solution
No — CFG equivalence is undecidable, unlike membership, emptiness, and finiteness, which all are.
ST8 · In a CYK table for input of length n, which single cell determines acceptance?
Show solution
table[0][n] — the span covering the entire input, checked for whether it contains the designated start symbol.
ST9 · What is a "productive cycle" in a grammar, and why does having one (on a reachable, generating non-terminal) make the language infinite?
Show solution
A non-terminal A has a productive cycle if A ⇒+ αAβ with α,β not both empty — A can derive a string containing itself, plus something extra. Repeating that derivation any number of times keeps adding more of α/β each time, generating unboundedly many distinct strings.
ST10 · Why does CYK's time complexity depend on the grammar being in CNF specifically, not just "some normal form or other"?
Show solution
CYK's per-cell cost comes from trying every single split point of a span — a search that only stays a simple "one number" search because CNF rules have exactly two symbols on the right. GNF, for instance, doesn't have this binary-split property and isn't the normal form CYK is built around.
Where this goes next
- Unit 4 adds a stack to the finite automaton — a pushdown automaton — built specifically to recognise exactly the context-free languages this whole unit studied.
- Gbal (Chapter 4's balanced-parentheses grammar) gets its promised PDA; GNF's "terminal, then non-terminals" shape (Section 5.1) maps almost directly onto PDA moves.
- Leq and L3eq, unrecognisable by any finite automaton (Chapters 2–3), finally get machines that can handle them — once those machines have unbounded memory of the right shape.
Further reading
- Sipser, Introduction to the Theory of Computation, 3rd ed. — the CNF conversion algorithm and CYK-style parsing follow essentially the same structure presented here.
- Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools. — the standard next stop for parsing at production scale: LL/LR parsing, parser generators, and error recovery well beyond this chapter's CYK/recursive-descent introduction.
Next: Chapter 6 · Pushdown Automata