GATE CS & DA Preparation Guide Module 7 – Theory of computation and compiler design (CS sections 6-7)
Context-free languages, PDAs, Turing machines
Design grammars and PDAs, use CNF, CYK and the pumping lemma for CFLs, and decide which problems are undecidable with reductions and Rice's theorem.
What you will learn
- Design CFGs and PDAs, detect ambiguity and convert grammars to normal forms
- Apply CFL closure properties and the pumping lemma for context-free languages
- Classify problems as decidable, semi-decidable or undecidable using known results and reductions
- Identify the level of a language in the Chomsky hierarchy
Before you start
What you’ll learn · Before you start
What you will learn
- Design CFGs and PDAs, detect ambiguity and convert grammars to normal forms
- Apply CFL closure properties and the pumping lemma for context-free languages
- Classify problems as decidable, semi-decidable or undecidable using known results and reductions
- Identify the level of a language in the Chomsky hierarchy
Before you start
On this page
A pushdown automaton (PDA), a finite automaton with a stack, checks that aabbbb has twice as many b’s as a’s in 7 moves: one push of two symbols for each a, one pop for each b, and a last move that reads nothing. A context-free grammar (CFG) generates strings by rewriting one variable at a time, whatever surrounds it, with rules such as S → aSb | ε, and the languages that grammars generate are the context-free languages (CFLs). Nondeterministic PDAs accept exactly the context-free languages, while deterministic PDAs accept fewer of them.
A Turing machine has an unbounded tape that it can read, write and move along in both directions. Turing machines compute everything that any computer can, and they define two classes: a language is decidable language: A language for which some Turing machine halts on every input and accepts exactly the strings of the language. Decidable and recursive mean the same., or recursive, when a machine halts on every input with the right answer, and recursively enumerable language: A language that some Turing machine accepts exactly, possibly running forever on strings outside it. Every decidable language is one; the halting problem is one that is not decidable. when a machine accepts exactly its strings but may run forever on the others. Each idea below comes with a program you can run.
Each machine adds one resource, and each class is strictly larger
The Chomsky hierarchy, with the machine and one example language at each level
Text description of the diagram
The diagram is a set of nested boxes. From the innermost box outwards:
1. Regular languages, recognised by finite automata; example: a*b*. 2. Deterministic context-free languages, recognised by deterministic pushdown automata; example: aⁿbⁿ. 3. Context-free languages, recognised by nondeterministic pushdown automata; example: the even palindromes ww^R. 4. Context-sensitive languages, recognised by linear-bounded automata; example: aⁿbⁿcⁿ. 5. Recursive (decidable) languages, recognised by Turing machines that halt on every input. 6. Recursively enumerable languages, recognised by Turing machines that may run forever on inputs they do not accept; example: HALT, the halting problem.
Outside all of them are the languages that are not recursively enumerable, for example the complement of HALT. Each box contains every box inside it, and every inclusion is strict.
Each class contains the classes drawn inside it, and each machine adds one resource. A finite automaton has only its states. A PDA adds a stack, which can match one count against another or a string against its reversal. A linear-bounded automaton may rewrite the tape cells of its input but no others, and Turing machines have an unbounded tape. Every inclusion is strict, and the examples show why: needs the stack, the even palindromes need a guess of the middle, needs more than one stack can do, and HALT is recognised but never decided.
Checkpoint
Check: The Chomsky hierarchy
A stack lets a PDA check what a grammar generates
The grammar S → aSbb | ε generates : each use of the first rule adds one a on the left and two b’s on the right. The PDA does the same work with its stack. It pushes two X’s for every a and pops one for every b, and the bottom marker Z tells it that every b has been matched. Step through its run on aabbbb:
The drawing shows the whole scene; its 9 steps are listed below.
Every step as text
- Start. The PDA starts in q0 with only the bottom marker Z on its stack and all of aabbbb to read. q0 accepts too, so the empty string, the case n = 0, is accepted without a move.
- The first a. Reading a with Z on top, the move a, Z → XXZ replaces Z by XXZ, two X's above the marker, and goes to q1.
- The second a. In q1, every further a with X on top pushes two more X's, so the stack grows to four X's.
- The first b. Reading b with X on top pops one X and moves to q2. From now on an a has no move, so an a after a b is rejected.
- The second b. Each b pops one X. Two X's are left for the two b's still to read, and the run stays in q2.
- The third and fourth b. Two more pops leave only the marker Z, and the input is used up. Four b's have matched the four X's that two a's pushed.
- Accept. With Z on top, the move ε, Z → Z reads nothing and goes to q3, which accepts, so aabbbb is accepted after 7 moves.
- One b short. On aabbb the input runs out in q2 with XZ still on the stack. Only a move on Z could lead to q3, and X is on top, so no move applies and the PDA rejects.
- Deterministic. In every configuration at most one move applies, and the move that reads nothing is never offered beside one that reads a symbol. So this PDA is deterministic.
The program prints the same configurations, then the run on aabbb, which uses up its input with an X still on the stack:
"""A deterministic PDA for a^n b^(2n), traced move by move and checked against the definition."""
from itertools import product
# (state, symbol read, top of stack) -> (next state, what replaces the top).
# A symbol of "" reads nothing; Z marks the bottom of the stack.
MOVES = {
("q0", "a", "Z"): ("q1", "XXZ"), # the first a pushes two X's
("q1", "a", "X"): ("q1", "XXX"), # every further a pushes two more
("q1", "b", "X"): ("q2", ""), # each b pops one X
("q2", "b", "X"): ("q2", ""),
("q2", "", "Z"): ("q3", "Z"), # every X matched: move to the accepting state
}
ACCEPTING = {"q0", "q3"} # q0 accepts the empty string
def run(word, show=False):
state, rest, stack = "q0", word, "Z" # the top of the stack is its first character
while True:
if show:
print(f" {state} input {rest or '-':7} stack {stack}")
top = stack[:1]
if rest and (state, rest[0], top) in MOVES:
state, push = MOVES[state, rest[0], top]
rest, stack = rest[1:], push + stack[1:]
elif (state, "", top) in MOVES:
state, push = MOVES[state, "", top]
stack = push + stack[1:]
else:
return not rest and state in ACCEPTING
for word in ("aabbbb", "aabbb"):
print(f"{word}:")
print(" accepted" if run(word, show=True) else " rejected: no move left")
words = ["".join(w) for n in range(10) for w in product("ab", repeat=n)]
same = all(run(w) == (w == "a" * (len(w) // 3) + "b" * (2 * len(w) // 3)) for w in words)
print(f"agrees with a^n b^(2n) on all {len(words)} strings up to length 9: {same}") Output
aabbbb: q0 input aabbbb stack Z q1 input abbbb stack XXZ q1 input bbbb stack XXXXZ q2 input bbb stack XXXZ q2 input bb stack XXZ q2 input b stack XZ q2 input - stack Z q3 input - stack Z accepted aabbb: q0 input aabbb stack Z q1 input abbb stack XXZ q1 input bbb stack XXXXZ q2 input bb stack XXXZ q2 input b stack XXZ q2 input - stack XZ rejected: no move left agrees with a^n b^(2n) on all 1023 strings up to length 9: True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pda_trace.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
This PDA is deterministic: in every configuration at most one move applies. Deterministic PDAs accept exactly the deterministic CFLs, which include but not the even palindromes, where a machine would have to guess where the middle is.
Checkpoint
Check: Grammars and PDAs for one language
Normal forms fix the length of every derivation
A grammar in Chomsky normal form: A grammar form that allows only rules A → BC and A → a, plus S → ε when the language contains ε. A derivation of a string of length n then takes exactly 2n − 1 steps. derives aabb in exactly 7 steps, in whatever order it applies its rules:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- The start variable. The grammar S → AB | AC, C → SB, A → a, B → b is in Chomsky normal form and generates aⁿbⁿ for n ≥ 1. A derivation of aabb starts from S.
- S → AC. A rule of the form A → BC replaces one variable by two, so the sentential form grows from one variable to two.
- A → a. A rule of the form A → a turns the leftmost variable into a terminal, which no later step changes.
- C → SB. C becomes S B, the second rule of the form A → BC. The S in the middle will produce the inner ab.
- S → AB. The third and last rule of the form A → BC. The form now has four symbols, the a already made and three variables, one for each terminal of aabb, so n − 1 = 3 such rules were needed.
- A → a. From here every step turns one variable into one terminal. The three variables left need three such steps, which with the earlier A → a make four, one per terminal.
- B → b. The third terminal rule. Only one variable is left.
- 7 steps in all. The last B becomes b. The derivation used 3 rules of the form A → BC and 4 of the form A → a, 2n − 1 = 7 steps for a string of length n = 4, whatever the order of the steps.
- Chomsky normal form (CNF) allows only rules A → BC and A → a, so a derivation of a string of length n takes exactly 2n − 1 steps.
- Greibach normal form (GNF) starts every right-hand side with a terminal, A → aα, so a string of length n takes exactly n steps.
- The CYK algorithm decides whether a grammar in CNF generates a string of length n in time, by filling a triangle of cells, one for each substring.
- The pumping lemma for CFLs splits every long enough string as uvxyz, with |vxy| ≤ p and |vy| ≥ 1, and pumps v and y together. fails it.
- Closure: CFLs are closed under union, concatenation, star and intersection with a regular language, but not under intersection or complement.
- Ambiguity: a grammar is ambiguous when some string has two parse trees, and no algorithm decides ambiguity for every grammar. A language is inherently ambiguous when every grammar for it is ambiguous.
Counting a CNF derivation as n steps
A derivation of aabb in Chomsky normal form takes 7 steps, not 4: three steps of the form A → BC grow one variable into four, and four steps of the form A → a turn them into terminals. Only Greibach normal form takes n steps.
Checkpoint
Check: Normal forms, CYK, pumping and closure
Decidability shrinks as the machines grow stronger
A problem is decidable when one Turing machine answers it correctly for every input and always halts. The table gives the standard results for each class; the input is a description of the language, such as a DFA, a grammar or a Turing machine.
| Criterion | Regular | DCFL | CFL | Recursive | RE |
|---|---|---|---|---|---|
| Is a given string in L? | Decidable | Decidable | Decidable | Decidable | Undecidable, semi-decidable |
| Is L empty? | Decidable | Decidable | Decidable | Undecidable | Undecidable |
| Is L finite? | Decidable | Decidable | Decidable | Undecidable | Undecidable |
| Is L every string? | Decidable | Decidable | Undecidable | Undecidable | Undecidable |
| Are two such languages equal? | Decidable | Decidable | Undecidable | Undecidable | Undecidable |
| Closed under complement? | Yes | Yes | No | Yes | No |
Two results explain most of the undecidable cells. Turing proved that no machine decides whether a given machine halts on a given input, the halting problem. Other problems are shown undecidable by reduction: if a decider for a problem P could be used to decide halting, P is undecidable. Rice’s theorem makes this general: every non-trivial property of the language that a Turing machine accepts is undecidable. Diagonalisation explains why such problems must exist: Turing machines can be listed one after another, while languages cannot.
Key idea
A reduction runs from the halting problem to P: if a decider for P would decide halting, no decider for P exists.
Checkpoint
Check: Decidable questions for each class
Practise: membership, closure and decidability
Exercise · Medium · Python
Decide membership with the CYK table
Write derives(binary, unit, start, word), which says whether a grammar in Chomsky normal form derives word from the variable start.
binarylists the rules of the form A → BC as triples, such as("S", "A", "C").unitmaps each terminal to the set of variables with a rule A → a, such as{"a": {"A"}, "b": {"B"}}.
Use the CYK algorithm. For every substring, from length 1 up to the whole word, work out the set of variables that derive it. A variable derives a substring of length 1 when it has a rule for that terminal. It derives a longer substring when one of its rules A → BC can split the substring into a first part that B derives and a rest that C derives. The word is in the language when start is in the set for the whole word. The empty word is never derived.
ANBN = [("S", "A", "B"), ("S", "A", "C"), ("C", "S", "B")]
derives(ANBN, {"a": {"A"}, "b": {"B"}}, "S", "aabb") # True
derives(ANBN, {"a": {"A"}, "b": {"B"}}, "S", "abab") # FalseStarter code · membership.py
def derives(binary, unit, start, word):
"""True when the grammar in Chomsky normal form derives `word` from `start`.
`binary` lists the rules A -> BC as triples ("A", "B", "C"); `unit` maps each terminal
to the set of variables that produce it (A -> a). The grammar has no rule for the empty
string, so the empty word is never derived.
"""
# Replace this line with your code.
return False The sample tests · test_membership.py
from itertools import product
from membership import derives
# a^n b^n, n >= 1: S -> AB | AC, C -> SB, A -> a, B -> b.
ANBN = [("S", "A", "B"), ("S", "A", "C"), ("C", "S", "B")]
ANBN_UNIT = {"a": {"A"}, "b": {"B"}}
# Balanced brackets: S -> SS | LR | LT, T -> SR, L -> (, R -> ).
BRACKETS = [("S", "S", "S"), ("S", "L", "R"), ("S", "L", "T"), ("T", "S", "R")]
BRACKETS_UNIT = {"(": {"L"}, ")": {"R"}}
def test_anbn_members():
"""ab, aabb and aaabbb are derived"""
assert [derives(ANBN, ANBN_UNIT, "S", w) for w in ("ab", "aabb", "aaabbb")] == [True, True, True]
def test_anbn_non_members():
"""aab, abab and ba are not derived"""
assert [derives(ANBN, ANBN_UNIT, "S", w) for w in ("aab", "abab", "ba")] == [False, False, False]
def test_empty_word():
"""a grammar in Chomsky normal form without an empty rule never derives the empty word"""
assert derives(ANBN, ANBN_UNIT, "S", "") is False
def test_unknown_symbol():
"""a symbol that no rule produces makes the word underivable"""
assert derives(ANBN, ANBN_UNIT, "S", "acb") is False
def test_brackets():
"""balanced brackets are derived, unbalanced ones are not"""
words = ["()", "(())()", "(()", ")(", "()()()"]
assert [derives(BRACKETS, BRACKETS_UNIT, "S", w) for w in words] == [True, True, False, False, True]
def test_anbn_against_definition():
"""the same answer as the definition on every string up to length 8"""
for n in range(9):
for w in map("".join, product("ab", repeat=n)):
k = len(w) // 2
assert derives(ANBN, ANBN_UNIT, "S", w) == (k > 0 and w == "a" * k + "b" * k), w A hint
Keep a dictionary from (start position, length) to a set of variables. Fill all the cells of length 1 from unit, then the cells of length 2, 3 and so on: for each cell try every split point and every rule A → BC, and add A when B is in the left cell and C is in the right one. The answer is whether start is in the cell (0, len(word)).
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Practice that adapts to you
Practise this lesson
10 questions about 10 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Grammars in depth: CYK, ambiguity and normal forms, worked Pro: opens with a Pro, Premium or Ultimate pass
Membership, ambiguity and normal forms each come with a count that is easy to get wrong. This part runs the CYK algorithm cell by cell, counts the parse trees of an ambiguous grammar, and works out what each normal form fixes about a derivation.
In this part:
- The CYK table, filled cell by cell
- Ambiguity reproduced: two trees and two values
- Normal forms by the numbers
2 runnable examples (Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Closure, pumping and deterministic PDAs, with the traps reproduced Pro: opens with a Pro, Premium or Ultimate pass
Closure properties and the pumping lemma settle most “is this context-free?” questions, and the gap between deterministic and nondeterministic PDAs settles the rest. This part tabulates closure with a counterexample for every failure, runs the pumping lemma on every split, and measures what nondeterminism costs a simulation.
In this part:
- Closure of CFLs and DCFLs, every “no” with its counterexample
- The pumping lemma for CFLs, checked on every split
- What nondeterminism costs, measured
2 runnable examples (Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Turing machines and undecidability, with reductions worked Pro: opens with a Pro, Premium or Ultimate pass
Decidability is settled by proofs, not runs, but a Turing machine you can trace makes the definitions concrete. This part runs a machine for , proves the halting problem undecidable by diagonalisation, and applies reductions and Rice’s theorem to eight typical properties.
In this part:
- A Turing machine for equal runs of a, b and c, traced and measured
- Why undecidable problems exist: a diagonal proof
- Reductions and Rice’s theorem, applied to eight properties
1 runnable example (Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher · Warm-up
What is the difference between a recursive and a recursively enumerable language?
A language is recursive, or decidable, when some Turing machine halts on every input and accepts exactly the strings of the language: it always answers yes or no. It is recursively enumerable when some Turing machine accepts exactly its strings but may run forever on a string outside it, so a no may never come. Every recursive language is recursively enumerable, but not the other way round: the halting problem is recursively enumerable and not recursive. The two classes meet in a useful theorem: when a language and its complement are both recursively enumerable, the language is recursive, because running the two machines in turns always ends. Recursive languages are closed under complement, and recursively enumerable ones are not.
-
Fresher to mid
Your team's expression grammar gives two parse trees for some inputs, and one input evaluates differently under each tree. Is there a tool that decides, for every context-free grammar, whether it is ambiguous? What do you do instead to make sure your grammar is not?
-
Fresher
The languages of the strings aⁿbⁿcᵐ and of the strings aᵐbⁿcⁿ, for n, m ≥ 0, are both context-free. Show that their intersection is not, and explain what this proves about closure of the context-free languages under intersection and under complement.
-
Fresher to mid
For a Turing machine M, which of these are decidable: (a) M has more than 10 states; (b) L(M) contains the string gate; (c) M halts on the blank tape within 100 steps; (d) L(M) is regular? Justify each one with Rice's theorem or a direct argument.
Key takeaways
- CFGs and nondeterministic PDAs describe the same languages; deterministic PDAs describe fewer, without the even palindromes.
- A derivation in Chomsky normal form takes 2n − 1 steps, one in Greibach normal form n steps, and CYK decides membership in time.
- CFLs are closed under union, concatenation and star and under intersection with a regular language, but not under intersection or complement.
- Membership, emptiness and finiteness are decidable for CFLs; equivalence and universality are not, and Rice’s theorem makes every non-trivial question about a Turing machine’s language undecidable.
Terms in this lesson
- Chomsky normal form
- A grammar form that allows only rules A → BC and A → a, plus S → ε when the language contains ε. A derivation of a string of length n then takes exactly 2n − 1 steps.
- context-free grammar (CFG)
- A grammar whose rules rewrite one variable at a time, whatever surrounds it, such as S → aSb | ε. The languages such grammars generate are the context-free languages.
- decidable language
- A language for which some Turing machine halts on every input and accepts exactly the strings of the language. Decidable and recursive mean the same.
- pushdown automaton (PDA)
- A finite automaton with a stack it can push and pop. Nondeterministic PDAs accept exactly the context-free languages; deterministic ones accept fewer of them.
- recursively enumerable language
- A language that some Turing machine accepts exactly, possibly running forever on strings outside it. Every decidable language is one; the halting problem is one that is not decidable.
References
- GATE 2027 syllabus, Computer Science and Information Technology (section 6, theory of computation) (Indian Institute of Technology Madras (GATE 2027))
- Three models for the description of language (N. Chomsky) (IRE Transactions on Information Theory)
- On certain formal properties of grammars (N. Chomsky) (Information and Control)
- A new normal-form theorem for context-free phrase structure grammars (S. A. Greibach) (Journal of the ACM)
- Recognition and parsing of context-free languages in time n³ (D. H. Younger) (Information and Control)
- On formal properties of simple phrase structure grammars (Y. Bar-Hillel, M. Perles and E. Shamir) (Zeitschrift für Phonetik, Sprachwissenschaft und Kommunikationsforschung)
- Deterministic context free languages (S. Ginsburg and S. Greibach) (Information and Control)
- L(A)=L(B)? Decidability results from complete formal systems (G. Sénizergues): DPDA equivalence (Theoretical Computer Science)
- On computable numbers, with an application to the Entscheidungsproblem (A. M. Turing) (Proceedings of the London Mathematical Society)
- Classes of recursively enumerable sets and their decision problems (H. G. Rice) (Transactions of the American Mathematical Society)
- functools.lru_cache: memoising a function (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress