CMPUT-474 · Lesson 6 of 12 · 31 min

Pushdown automata and limits of context-free languages

Grammars generate context-free languages; pushdown automata recognize them. A pushdown automaton is a finite automaton with one extra resource: a stack of unbounded size that it can push symbols onto and pop symbols from, seeing only the top. The stack is exactly the memory nested structure needs. To check \(a^nb^n\), push a marker for each a and pop one for each b. To check balanced parentheses, push on each opening parenthesis and pop on each closing one. This lesson defines pushdown automata, sketches why they recognize exactly the context-free languages, shows that determinism now matters, and then proves the limits of the class with a pumping lemma for context-free languages and the failure of closure under intersection and complement.

Pushdown automata

A pushdown automaton (PDA) is a 6-tuple \(P = (Q, \Sigma, \Gamma, \delta, q_0, F)\): states Q, input alphabet \(\Sigma\), stack alphabet \(\Gamma\), transition function \(\delta : Q \times \Sigma_\varepsilon \times \Gamma_\varepsilon \to \mathcal{P}(Q \times \Gamma_\varepsilon)\), start state \(q_0\) and accepting states F. A transition \((r, b) \in \delta(q, a, c)\) means: in state q, reading input a (or nothing if \(a = \varepsilon\)) with c on top of the stack (or ignoring the stack if \(c = \varepsilon\)), the machine may move to state r, popping c and pushing b. The PDA is nondeterministic by definition, and it accepts if some sequence of moves reads the whole input and ends in an accepting state. A common convention starts the stack with a special bottom marker, written \(\bot\) in the diagrams, so the machine can tell when the stack has been emptied; the simulator below uses a dollar sign for the same purpose.

A configuration records everything that determines the future: the current state, the remaining input and the whole stack contents. We write \((q, aw, c\gamma) \vdash (r, w, b\gamma)\) when one move takes the first configuration to the second. Because the stack is unbounded, a PDA has infinitely many configurations, unlike a DFA, and that is the source of its extra power. Some texts accept by empty stack instead of by final state; the two conventions recognize the same class of languages, because each machine can be converted to the other by adding a few states.

A PDA with three states p, q and f. p is the start state; a self-loop on p is labelled a, push A. An arrow from p to q is labelled epsilon, no stack change, meaning guess the middle. A self-loop on q is labelled b, pop A. An arrow from q to the accepting state f is labelled epsilon, bottom marker on top. Beside the diagram, a stack drawn as a vertical column shows A, A, A above the bottom marker after reading aaa.
A PDA for a^n b^n: push one A per a, guess the middle, pop one A per b, and accept when only the bottom marker remains

The program below simulates nondeterministic PDAs by breadth-first search over configurations, bounding the stack height so the search terminates. It runs the PDA in the figure on several strings, and a second PDA for \(\{ww^R : w \in \{a, b\}^*\}\), the even-length palindromes. The palindrome machine pushes the first half and pops while matching the second half, but it cannot know where the middle is. It guesses, using an \(\varepsilon\)-move at every position, and the guess succeeds for exactly one split when the input is a palindrome. That reliance on guessing is not a weakness of this particular design: no deterministic PDA recognizes \(ww^R\).

Simulating nondeterministic PDAs by breadth-first search over configurations; the palindrome PDA succeeds only through its guess of the middle
# A nondeterministic PDA simulator (breadth-first over configurations).
# Transitions: (state, input symbol or "", stack top or "") -> list of (state, string to push)
# The pushed string replaces the popped top; its first character becomes the new top.
from collections import deque

def accepts(pda, w, max_steps=10_000):
    start = (pda["start"], 0, "$")             # state, input position, stack (top first)
    seen, q = {start}, deque([start])
    steps = 0
    while q and steps < max_steps:
        state, i, stack = q.popleft(); steps += 1
        if i == len(w) and state in pda["accept"]:
            return True, steps
        for (s, a, top), moves in pda["delta"].items():
            if s != state: continue
            if a and (i >= len(w) or w[i] != a): continue
            if top and not stack.startswith(top): continue
            rest = stack[len(top):]
            for (t, push) in moves:
                cfg = (t, i + (1 if a else 0), push + rest)
                if len(cfg[2]) <= 2 * len(w) + 2 and cfg not in seen:
                    seen.add(cfg); q.append(cfg)
    return False, steps

anbn = {"start": "p", "accept": {"f"}, "delta": {
    ("p", "a", ""): [("p", "A")],        # push A for each a
    ("p", "", ""): [("q", "")],          # guess the middle
    ("q", "b", "A"): [("q", "")],        # pop A for each b
    ("q", "", "$"): [("f", "$")],        # bottom marker reached: accept
}}
evenpal = {"start": "p", "accept": {"f"}, "delta": {
    ("p", "a", ""): [("p", "a")], ("p", "b", ""): [("p", "b")],   # push the first half
    ("p", "", ""): [("q", "")],                                    # guess the middle
    ("q", "a", "a"): [("q", "")], ("q", "b", "b"): [("q", "")],    # match the second half
    ("q", "", "$"): [("f", "$")],
}}
for name, M, tests in [("a^n b^n", anbn, ["", "ab", "aabb", "aab", "abab", "aaabbb"]),
                       ("w w^R", evenpal, ["abba", "abab", "aabbaa", "baab", "aba", "bbbbbb"])]:
    print(name)
    for w in tests:
        ok, steps = accepts(M, w)
        print(f"  {w or 'eps':8s} {'accept' if ok else 'reject':7s} ({steps} configurations explored)")
Output
a^n b^n
  eps      accept  (3 configurations explored)
  ab       accept  (7 configurations explored)
  aabb     accept  (10 configurations explored)
  aab      reject  (8 configurations explored)
  abab     reject  (7 configurations explored)
  aaabbb   accept  (13 configurations explored)
w w^R
  abba     accept  (14 configurations explored)
  abab     reject  (11 configurations explored)
  aabbaa   accept  (22 configurations explored)
  baab     accept  (14 configurations explored)
  aba      reject  (9 configurations explored)
  bbbbbb   accept  (27 configurations explored)

PDAs and grammars are equivalent

Theorem. A language is context-free if and only if some PDA recognizes it. From grammar to PDA, the construction simulates leftmost derivations on the stack. Push the start variable. Then repeat: if the top of the stack is a variable A, nondeterministically replace it by the right side of some rule for A; if the top is a terminal, match it against the next input symbol and pop it. The PDA accepts when the input is consumed and the stack is empty, which happens exactly when some leftmost derivation produces the input. This is the idea behind top-down parsing in CMPUT-415, with the nondeterminism removed there by restricting to LL grammars.

From PDA to grammar is more intricate. First normalize the PDA so it has a single accepting state, empties its stack before accepting, and on every move either pushes or pops one symbol but not both. Then introduce a variable \(A_{pq}\) for each pair of states, intended to generate exactly the strings that take the PDA from state p with some stack to state q with the same stack, never dipping below it. The rules follow from how such a computation can unfold: either the symbol pushed first is popped at the very end, giving \(A_{pq} \to a A_{rs} b\), or the stack returns to its starting height somewhere in the middle, giving \(A_{pq} \to A_{pr}A_{rq}\). An induction on computation length shows the grammar is correct. The details are in standard texts; the important fact is that both directions are constructive.

Determinism matters for PDAs

A deterministic PDA (DPDA) has at most one move available in every configuration, counting \(\varepsilon\)-moves. The languages DPDAs accept by final state, the deterministic context-free languages (DCFLs), form a proper subclass of the context-free languages. They are exactly the languages with LR(1) grammars, a classic result of Knuth (1965), which is why LR parsers can process any deterministic context-free language in linear time. DCFLs are closed under complement, by a careful version of swapping accepting states, while CFLs are not; so any context-free language whose complement is not context-free, such as the complement of \(\{ww\}\), shows the inclusion is proper. The even palindromes \(ww^R\) are context-free but not deterministic: intuitively, a deterministic machine can never know when it has reached the middle. Unlike finite automata, PDAs genuinely need nondeterminism.

Machines with growing memory and the role of nondeterminism
ModelMemoryLanguagesNondeterminism adds power?
DFA / NFAFinite state onlyRegularNo (subset construction)
DPDAFinite state plus stack, deterministicDeterministic context-free (LR(1))-
PDAFinite state plus stack, nondeterministicContext-freeYes: ww^R needs it
Two-stack PDAFinite state plus two stacksRecognizable (same as Turing machines)No (lesson 7)
Turing machineUnbounded read-write tapeRecognizableNo for computability; open for polynomial time

The table also shows how fragile the class is. A PDA with two stacks is as powerful as a Turing machine, because two stacks can simulate a tape: one holds everything to the left of the head, the other everything to the right, and moving the head transfers a symbol from one stack to the other. So the context-free languages sit at a narrow point: one stack gives nested matching, two stacks give everything computable.

The pumping lemma for context-free languages

Pumping lemma for CFLs (Bar-Hillel, Perles and Shamir, 1961). If L is context-free, there is a number p such that every \(s \in L\) with \(|s| \ge p\) can be written \(s = uvxyz\) with \(|vy| \ge 1\), \(|vxy| \le p\), and \(uv^ixy^iz \in L\) for all \(i \ge 0\). Two pieces are pumped together, in step, and they lie within a window of length p. The proof looks at parse trees in a CNF grammar with b variables. A parse tree of height at most h has at most \(2^{h-1}\) leaves, so if s is longer than \(2^b\), its tree has a root-to-leaf path with more than b variables, and some variable R repeats on the lowest \(b + 1\) of them. The subtree under the upper R derives vxy and the subtree under the lower R derives x. Replacing the lower subtree by a copy of the upper one pumps v and y up; replacing the upper by the lower pumps them down. Choosing the repeat low on the path keeps \(|vxy| \le p = 2^b\).

Using the lemma is again a game, but the adversary has more freedom, so proofs must handle more cases. For \(L = \{a^nb^nc^n\}\), choose \(s = a^pb^pc^p\). Because \(|vxy| \le p\), the window vxy cannot touch all three blocks: it lies within at most two adjacent ones. Pumping up with \(i = 2\) increases the number of symbols of at most two kinds while the third stays at p, so the counts become unequal; and if v or y straddles a block boundary, pumping also creates symbols out of order. Either way \(uv^2xy^2z \notin L\). The program below checks every legal split for small p.

The context-free pumping game for a^n b^n c^n: every way of choosing a window vxy of length at most p fails when pumped down or up
# The pumping game for { a^n b^n c^n } with the context-free pumping lemma:
# s = u v x y z, |vxy| <= p, |vy| >= 1, and u v^i x y^i z must stay in L for all i.
def in_L(w):
    n = len(w) // 3
    return len(w) % 3 == 0 and w == "a" * n + "b" * n + "c" * n

for p in range(1, 7):
    s = "a" * p + "b" * p + "c" * p
    n, splits, beaten = len(s), 0, 0
    for start in range(n):                      # vxy = s[start:start+L]
        for L in range(1, p + 1):
            if start + L > n: break
            for lv in range(0, L + 1):
                for lx in range(0, L - lv + 1):
                    ly = L - lv - lx
                    if lv + ly == 0: continue
                    u, v = s[:start], s[start:start + lv]
                    x, y = s[start + lv:start + lv + lx], s[start + lv + lx:start + L]
                    z = s[start + L:]
                    splits += 1
                    if any(not in_L(u + v * i + x + y * i + z) for i in (0, 2)):
                        beaten += 1
    print(f"p = {p}: s = a^{p}b^{p}c^{p}, {splits:4d} legal splits, all beaten by i = 0 or 2: {beaten == splits}")
Output
p = 1: s = a^1b^1c^1,    6 legal splits, all beaten by i = 0 or 2: True
p = 2: s = a^2b^2c^2,   37 legal splits, all beaten by i = 0 or 2: True
p = 3: s = a^3b^3c^3,  121 legal splits, all beaten by i = 0 or 2: True
p = 4: s = a^4b^4c^4,  295 legal splits, all beaten by i = 0 or 2: True
p = 5: s = a^5b^5c^5,  605 legal splits, all beaten by i = 0 or 2: True
p = 6: s = a^6b^6c^6, 1106 legal splits, all beaten by i = 0 or 2: True

The same technique shows that \(\{ww : w \in \{a,b\}^*\}\) is not context-free, choosing \(s = a^pb^pa^pb^p\) and checking where the window can fall; that \(\{a^{n^2}\}\) is not context-free; and that \(\{a^ib^jc^k : i \le j \le k\}\) is not context-free. When the lemma is awkward, Ogden's lemma (1968) gives a stronger version in which you mark some positions of s, and the pumped pieces are guaranteed to contain marked positions; it handles languages such as \(\{a^ib^jc^kd^l : i = 0 \text{ or } j = k = l\}\) on which the plain lemma fails. As with regular languages, these lemmas are necessary conditions only, but unlike regular languages there is no simple complete characterization like Myhill–Nerode for context-free languages.

Closure properties

Context-free languages are closed under union (a new start variable \(S \to S_1 \mid S_2\)), concatenation (\(S \to S_1S_2\)), star (\(S \to SS_1 \mid \varepsilon\)), reversal (reverse every right side), homomorphism and inverse homomorphism. They are not closed under intersection. The languages \(L_1 = \{a^ib^ic^j\}\) and \(L_2 = \{a^ib^jc^j\}\) are each context-free, one matching as with bs and the other bs with cs, but their intersection is \(\{a^nb^nc^n\}\), which is not. They are therefore not closed under complement either, since closure under complement and union would give closure under intersection by De Morgan's law. The intersection of a context-free language with a regular language is always context-free: run the PDA and a DFA in parallel, product-style, using the single stack for the PDA alone.

Intersecting two context-free languages yields a^n b^n c^n, which is not context-free; intersecting with a regular language preserves context-freeness
# Closure facts checked on small strings.
# L1 = { a^i b^i c^j } and L2 = { a^i b^j c^j } are context-free; their intersection is a^n b^n c^n.
from itertools import product
def L1(w):
    i = len(w) - len(w.lstrip("a")); rest = w[i:]
    j = len(rest) - len(rest.lstrip("b")); rest2 = rest[j:]
    return set(rest2) <= {"c"} and i == j
def L2(w):
    i = len(w) - len(w.lstrip("a")); rest = w[i:]
    j = len(rest) - len(rest.lstrip("b")); rest2 = rest[j:]
    return set(rest2) <= {"c"} and j == len(rest2)
both = sorted((w for n in range(0, 10) for t in product("abc", repeat=n)
               for w in ["".join(t)] if L1(w) and L2(w)), key=len)
print("strings up to length 9 in L1 and L2:", [w or "eps" for w in both])

# Intersection with a regular language stays context-free: run a PDA and a DFA together.
# Example: balanced parentheses intersected with "length divisible by 4".
def balanced(w):
    d = 0
    for c in w:
        d += 1 if c == "(" else -1
        if d < 0: return False
    return d == 0
count = {n: sum(balanced("".join(t)) for t in product("()", repeat=n)) for n in range(0, 13, 2)}
print("balanced strings by length:", count)
print("kept by the intersection (length divisible by 4):", {n: c for n, c in count.items() if n % 4 == 0})
Output
strings up to length 9 in L1 and L2: ['eps', 'abc', 'aabbcc', 'aaabbbccc']
balanced strings by length: {0: 1, 2: 1, 4: 2, 6: 5, 8: 14, 10: 42, 12: 132}
kept by the intersection (length divisible by 4): {0: 1, 4: 2, 8: 14, 12: 132}

Closure under intersection with regular languages is a practical tool for non-context-freeness proofs. To show that the language of strings over \(\{a, b, c\}\) with equal numbers of each symbol is not context-free, intersect it with the regular language \(a^*b^*c^*\); the result is \(\{a^nb^nc^n\}\), so the original cannot be context-free. Decision properties also differ from the regular case. Membership is decidable (CYK) and so is emptiness (check whether the start variable can generate a terminal string) and finiteness. But equivalence of two CFGs, whether a CFG generates all of \(\Sigma^*\), and whether a CFG is ambiguous are all undecidable, which lesson 9 proves.

Worked examples

Worked problem

Design a PDA

Design a PDA for strings over {a, b} with equal numbers of a and b, in any order.

Show solution
  1. Use the stack to store the current surplus: push a symbol for each unmatched letter.
  2. Reading a: if the top is b, pop it (an a cancels a b); otherwise push a. Symmetrically for b.
  3. Accept when the input is consumed and only the bottom marker remains.
  4. This PDA is in fact deterministic once the empty-stack test uses the bottom marker.

Answer: A one-state stack machine that cancels opposite letters and accepts on an empty stack.

Worked problem

CFL pumping proof

Prove that \(\{a^n b^n c^n\}\) is not context-free (written proof).

Show solution
  1. Assume it is, with pumping length p, and choose \(s = a^pb^pc^p\).
  2. Let \(s = uvxyz\) with \(|vxy| \le p\) and \(|vy| \ge 1\). The window vxy contains no a or no c, because a and c blocks are p symbols apart.
  3. If v or y contains two kinds of symbol, \(uv^2xy^2z\) has symbols out of order.
  4. Otherwise v and y each contain one kind, and pumping with \(i = 2\) increases at most two of the three counts.
  5. In every case the pumped string is not in L, a contradiction.

Answer: Not context-free.

Worked problem

Non-closure in action

Is the set of strings over {a, b, c} with equal numbers of a, b and c context-free?

Show solution
  1. Intersect it with the regular language \(a^*b^*c^*\).
  2. The intersection is \(\{a^nb^nc^n\}\).
  3. If the original language were context-free, the intersection with a regular language would be context-free, which it is not.

Answer: No.

Common mistakes

  • MistakeAssume deterministic and nondeterministic PDAs are equivalent, as for DFAs.

    InsteadDPDAs recognize a proper subclass; ww^R needs nondeterminism.

  • MistakePump only one piece in the CFL lemma.

    Insteadv and y are pumped together, the same number of times.

  • MistakeForget the |vxy| <= p constraint.

    InsteadIt is what limits the window to at most two adjacent blocks in proofs like a^n b^n c^n.

  • MistakeClaim CFLs are closed under intersection.

    InsteadThey are not; only intersection with a regular language is safe.

  • MistakeUse the CFL lemma to prove a language IS context-free.

    InsteadIt only proves non-context-freeness; to show a language is context-free, give a grammar or PDA.

Summary and key terms

  • A PDA is a finite automaton with an unbounded stack; it is nondeterministic by definition.
  • PDAs recognize exactly the context-free languages; both conversions are constructive.
  • Deterministic PDAs recognize the deterministic CFLs, the LR(1) languages, a proper subclass.
  • Two stacks are as powerful as a Turing machine.
  • The CFL pumping lemma pumps two pieces v and y in step within a window of length p.
  • \(a^nb^nc^n\) and \(ww\) are not context-free.
  • CFLs are closed under union, concatenation, star, reversal and intersection with regular languages, but not intersection or complement.
Pushdown automaton
A finite automaton with an unbounded stack.
Stack alphabet
The symbols a PDA may push onto its stack.
Configuration
State, remaining input and stack contents at a moment of computation.
Deterministic PDA
A PDA with at most one possible move in every configuration.
Deterministic context-free language
A language accepted by a DPDA, equivalently one with an LR(1) grammar.
Pumping lemma for CFLs
A necessary condition for context-freeness based on pumping two pieces in step.
Ogden's lemma
A stronger pumping lemma in which chosen positions must be pumped.
Bottom marker
A special stack symbol that lets a PDA detect an empty stack.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Design a PDA for balanced parentheses in one sentence.

    Show answer

    Push on each opening parenthesis, pop on each closing one, reject if the stack is empty when popping, accept when it is empty at the end.

  2. Why can't a DPDA recognize the even palindromes?

    Show answer

    It cannot tell where the middle is without guessing.

  3. Give two context-free languages whose intersection is not context-free.

    Show answer

    \(\{a^ib^ic^j\}\) and \(\{a^ib^jc^j\}\).

  4. Which string would you pump to show {ww} is not context-free?

    Show answer

    \(a^pb^pa^pb^p\).

  5. Is the complement of a CFL always context-free?

    Show answer

    No; CFLs are not closed under complement.

  6. Is a CFL intersected with a regular language context-free?

    Show answer

    Yes, by running the PDA and the DFA in parallel.

  7. What extra memory makes a PDA as powerful as a Turing machine?

    Show answer

    A second stack.

  8. Which parsing method corresponds to the deterministic CFLs?

    Show answer

    LR(1) parsing.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz