THEORY OF COMPUTATION / 3. CFG & PUSHDOWN AUTOMATA
Context-Free Grammars & Pushdown Automata
The math behind programming language syntax — parsers and grammars
EXPLANATION
Context-Free Grammars (CFGs) and Pushdown Automata (PDAs) describe the next level of the Chomsky hierarchy — Context-Free Languages. This is the class that captures the structure of most programming languages. Context-Free Grammar — formal definition: A CFG is a 4-tuple (V, Σ, R, S): - V — variables (non-terminals): symbols that can be replaced. Written in UPPERCASE or <angle brackets> - Σ — terminals: the actual symbols in strings (alphabet). Written in lowercase - R — production rules: V → (V ∪ Σ)* — each variable maps to a string of variables and terminals - S — start variable Derivation: start with S, repeatedly replace any variable using a production rule, until only terminals remain. The set of all strings derivable from S is the language L(G). Example: grammar for balanced parentheses: S → ε | SS | (S) - ε (empty string) — zero pairs - SS — two balanced groups concatenated - (S) — a balanced group wrapped in parens Derives: ε, (), (()), ()(), ((())), etc. Parse trees: derivations can be visualized as trees. The leaves (read left to right) give the derived string. Parse trees expose the STRUCTURE of the string (precedence, association) — this is what compilers build. Ambiguity: a grammar is AMBIGUOUS if some string has two different parse trees. This is bad for compilers — two parses mean two interpretations! Classic example: E → E+E | E*E | id. The string "id+id*id" has two parse trees (add first, or multiply first?). Fix: rewrite grammar to encode precedence. CFL Pumping Lemma: if L is context-free, long enough strings have the form uvwxy where: - |vwx| ≤ p (pumping length) - |vx| ≥ 1 - For all i ≥ 0: uvⁱwxⁱy ∈ L Used to prove languages are NOT context-free. Example: aⁿbⁿcⁿ is not CFL. Pushdown Automaton (PDA) — FA + stack: A PDA is an NFA with an unlimited stack. On each transition: - Read input symbol (or ε) - Pop a symbol from stack (or ε) - Push a string of symbols onto stack (or ε) - Move to next state The stack provides the "counting" ability DFAs lack. To recognize aⁿbⁿ: ① Push 'a' onto stack for each 'a' read ② Pop one 'a' for each 'b' read ③ Accept if stack is empty at end Every CFL has a PDA and every PDA recognizes a CFL — they are equivalent. Chomsky Normal Form (CNF): every CFG can be converted to CNF where every rule is either: - A → BC (two variables) - A → a (one terminal) CNF is used by the CYK parsing algorithm — O(n³) — can parse ANY CFG.
DIAGRAM
CFG for arithmetic: E → E+T | T, T → T*F | F, F → (E) | id
Parse tree for "id + id * id":
E
/ | E + T
| / | T T * F
| | |
F F id
| |
id id
Precedence encoded: * binds tighter than +
(because * is deeper in the grammar hierarchy)
PDA for aⁿbⁿ:
States: q0 (reading a's), q1 (reading b's), q2 (accept)
Stack alphabet: {A, Z} where Z = bottom marker
q0 --a, Z/AZ--> q0 (push A, keep Z)
q0 --a, A/AA--> q0 (push another A)
q0 --b, A/ε --> q1 (pop A for each b)
q1 --b, A/ε --> q1
q1 --ε, Z/Z --> q2 (stack has only Z = balanced)
Trace "aabb":
State Input Stack
q0 aabb Z
q0 abb AZ (push A)
q0 bb AAZ (push A)
q1 b AZ (pop A)
q1 ε Z (pop A)
q2 ε Z ACCEPT ✓CODE