Context-Free Grammars for Parsing
Consider a compiler trying to check whether `if (x > 0) { y = 1; } else { y = -1; }` is syntactically legal C. A scanner can hand back a stream of tokens — `IF`, `LPAREN`, `ID`, `GT`, `NUM`, `RPAREN`, `LBRACE`, `...`. But no finite automaton can decide whether the parentheses and braces are balanced. It also cannot check proper nesting. Balancing arbitrarily deep nesting requires unbounded memory of "how many opens are still unclosed." Regular languages provably cannot count without bound. This is exactly the gap that context-free grammars (CFGs) fill in the compiler pipeline. They describe recursive, nested structure that lexical analysis cannot. CFGs matter because almost every programming language's syntax is nested and recursive by nature. This includes expressions, statements, blocks, and function definitions. An `if` can contain another `if`. An expression can contain a parenthesized sub-expression that itself contains an expression. Real systems from `javac` to `clang` to every JSON or XML parser you have ever used rely on a CFG. This CFG, explicit or implicit, serves as the specification of "what counts as valid input." They also rely on a parsing algorithm that turns that specification into a working procedure or table. Getting the grammar right is the difference between a parser that accepts exactly the intended language. The alternative is one riddled with ambiguity, dangling-else bugs, or accidental exponential blowup. The big picture: a CFG is a generative device. It is a small set of rewriting rules that can produce every string in a language by repeated substitution. A parser, meanwhile, is a recognizer. It runs those rules in reverse to decide membership and recover structure. Grammars sit at the boundary between informal syntax notation (BNF you see in language manuals). They also border the formal machinery (derivations, parse trees, parsing algorithms) that a compiler front end actually executes. Understanding grammars precisely is the prerequisite for everything that follows. Without a clean grammar there is no principled way to build a parse tree. There is also no way to choose a parsing strategy, or diagnose why a language is ambiguous. This article gives the precise definition of a context-free grammar. It works through concrete grammars for arithmetic expressions and balanced brackets. It explains why CFGs can express nesting that regular expressions cannot, with a real argument, not a hand-wave. It also surveys the pitfalls that every grammar designer runs into. These include ambiguity, left recursion pitfalls in practice, and the classic dangling-else. It sets up, but does not yet perform, the derivations, parse trees, and top-down parsing techniques covered in later material.
- A context-free grammar (CFG) is a 4-tuple : nonterminals, terminals, production rules, and a start symbol.
- Productions rewrite a single nonterminal into a string of terminals and nonterminals — this "one nonterminal at a time" restriction is exactly what "context-free" means.
- CFGs generate nested/recursive structure (balanced parentheses, matching tags) that finite automata and regular expressions cannot express.
- The language of a grammar is the set of all terminal strings derivable from the start symbol by repeated rewriting.
- A grammar can be ambiguous — some string has two different structural interpretations — which is a property of the grammar, not (necessarily) of the language.
- CFGs are the declarative specification a parser implements; the grammar defines what is legal, the parsing algorithm defines how to check it efficiently.
A context-free grammar generates nested, recursive language structure by rewriting one nonterminal at a time, independent of surrounding context — which is precisely the expressive power a finite automaton lacks.
The idea
A context-free grammar is formally a 4-tuple where is a finite set of nonterminals (syntactic categories such as `Expr` or `Stmt`), is a finite set of terminals (the actual tokens produced by the scanner, disjoint from ), is a finite set of production rules, and is the designated start symbol. Each production has the form
where is a single nonterminal and is any string of terminals and nonterminals (possibly empty, written ). The grammar is called context-free precisely because the left-hand side of every rule is a single, bare nonterminal — the rule "fires" regardless of what surrounds . This contrasts with more general phrase-structure grammars where the left-hand side can be a whole string, and the rewrite is legal only in specific surrounding context. A derivation starts from a string containing only and repeatedly replaces some nonterminal occurring in the current string with the right-hand side of a production , writing for one rewrite step and for zero or more steps. The language generated by , written , is the set of all terminal-only strings reachable from :
Two grammars are equivalent if they generate the same language, even though their rule sets and derivation structures may differ completely — a fact that matters enormously when a compiler writer rewrites a grammar to remove ambiguity or make it suitable for a particular parsing algorithm, without changing the language it describes. This full apparatus — derivations as sequences of rewrites and the trees they induce — is developed properly under Derivations and Parse Trees; here the focus is on the grammar as a static object: what it is, what it can express, and why that expressive power is exactly what parsing needs.
How it works
Building and reading a CFG is mechanical once the pieces are fixed. The workflow a compiler front end (or a student writing a grammar by hand) follows is: 1. Identify terminals — these are exactly the token classes your scanner already emits (`ID`, `NUM`, `PLUS`, `LPAREN`, ...), per Building Scanners with Finite Automata. 2. Identify nonterminals — one per syntactic category you want to name (`Expr`, `Term`, `Factor`, `Stmt`, `Block`). 3. Write productions for each nonterminal, describing every way an instance of that category can be built from smaller pieces or terminals. 4. Designate the start symbol — the nonterminal representing "a complete program" or "a complete expression," whichever the grammar is meant to check. 5. Verify informally (or later, formally via parsing) that every intended string is derivable and no unintended string is. A canonical running example — arithmetic expressions with `+`, ``, parentheses, and identifiers — illustrates the whole process: ``` Grammar for arithmetic expressions: Nonterminals V = { Expr, Term, Factor } Terminals Σ = { id, num, '+', '', '(', ')' } Start symbol S = Expr Productions R: Expr -> Expr '+' Term Expr -> Term Term -> Term '' Factor Term -> Factor Factor -> '(' Expr ')' Factor -> id Factor -> num ``` Reading this as a generator: starting from `Expr`, you may repeatedly rewrite any nonterminal in the current string using a matching production, until only terminals remain. The layering `Expr → Term → Factor` is not decorative — it encodes that `` binds tighter than `+`, because a `Term` (product) can never directly contain a top-level `+`, while an `Expr` (sum) can contain `Term`s that internally contain ``. The following pseudocode is a generator — it nondeterministically expands the start symbol and demonstrates precisely what "belongs to the language" means, mirroring how a parser will later be asked to invert this process: ``` function generate(grammar): frontier <- [ grammar.start ] // list of symbols, terminals+nonterminals while frontier contains a nonterminal: choose (nondeterministically) some nonterminal A at position i in frontier choose (nondeterministically) some production A -> alpha in grammar.rules[A] replace frontier[i] with the sequence of symbols alpha // alpha may be empty (epsilon production): removes A with nothing return frontier // now a string of terminals only, i.e. a sentence function inLanguage(grammar, w): // membership by exhaustive/backtracking search over derivations return exists a sequence of choices in generate(grammar) that yields exactly w ``` `generate` is intentionally nondeterministic and, taken literally, exponential or non-terminating — it is a specification* of , not an efficient recognizer. The entire enterprise of Top-Down Parsing (and its bottom-up counterparts) exists to replace this brute-force search with a deterministic or low-backtracking algorithm that decides `inLanguage` in linear or near-linear time for well-behaved grammars. The figure below shows the grammar as a small rewriting system: nonterminals as rule-boxes, productions as labeled arrows into alternative right-hand sides.
Complexity & correctness
Expressive power, argued precisely. Every regular language is context-free (any finite automaton converts directly into a right-linear grammar with productions of the form or ), so CFGs are at least as powerful as regular expressions. The strict inclusion is what matters for parsing: the balanced-parenthesis language is context-free — generated by — but not regular. This is provable directly from the pumping lemma for regular languages: any regular language has a pumping length such that every string of length can be split with , , and for all . Take ; any nonempty inside the first symbols consists only of 's, so pumping (say ) produces more 's than 's, leaving the resulting string outside . Since no valid split exists, is not regular. Because real programming-language syntax needs unboundedly nested brackets, blocks, and expressions — exactly this pattern — no scanner alone can check syntactic well-formedness; a CFG (and a parser for it) is structurally necessary, not a convenience.
Grammar size vs. language size. A CFG is a finite object — finitely many nonterminals and productions — yet it can generate an infinite language, because recursive productions (a nonterminal appearing, directly or indirectly, in its own right-hand side) allow unbounded reuse of the same finite rule set. This is the same compression idea behind regular expressions and DFAs, taken up a level of expressive power: a constant-size description implicitly encodes infinitely many strings.
Membership and ambiguity are properties of the grammar, checked algorithmically later. Determining whether for a general CFG (with tokens) is decidable in time via the CYK or Earley algorithms — a fact whose algorithmic proof belongs to parsing proper, previewed here only to be honest that recognizing membership from the raw definition (`generate`/`inLanguage` above) is not by itself efficient. What belongs squarely at the grammar level is a subtler correctness notion: ambiguity. A grammar is ambiguous if some string has two distinct parse trees (equivalently, two distinct leftmost derivations). Ambiguity is undecidable in general — there is no algorithm that, given an arbitrary CFG, always correctly reports whether it is ambiguous — but for concrete grammars we can often exhibit two derivations directly, which is a fully rigorous proof that the grammar (not necessarily the language) is ambiguous, since some other equivalent unambiguous grammar may exist for the same language.
Tradeoffs. The expressive gain over regular languages is not free: CFG parsing is asymptotically more expensive ( worst case for arbitrary CFGs, versus for regular languages via a DFA), and grammar design introduces failure modes — ambiguity, left recursion, unreachable or unproductive nonterminals — that regular expressions simply cannot exhibit. Restricting to well-behaved grammar subclasses (LL(1), LR(1)) recovers linear-time parsing, at the cost of grammar-writing discipline covered under Top-Down Parsing.
Worked example
Trace the grammar above on the concrete token string `id + id * id` (already lexed — three `id` tokens and the operators), producing a leftmost derivation (always rewrite the leftmost nonterminal):
| Step | Current string | Rule applied |
|---|---|---|
| 0 | `Expr` | — (start) |
| 1 | `Expr '+' Term` | `Expr → Expr '+' Term` |
| 2 | `Term '+' Term` | `Expr → Term` |
| 3 | `Factor '+' Term` | `Term → Factor` |
| 4 | `id '+' Term` | `Factor → id` |
| 5 | `id '+' Term '*' Factor` | `Term → Term '*' Factor` |
| 6 | `id '+' Factor '*' Factor` | `Term → Factor` |
| 7 | `id '+' id '*' Factor` | `Factor → id` |
| 8 | `id '+' id '*' id` | `Factor → id` |
Every step rewrites exactly one nonterminal into the right-hand side of one production, and after 8 steps every symbol is a terminal, matching the input exactly — so `id + id id ∈ L(G)`, proved constructively by exhibiting the derivation. Notice how the grammar's structure, not just the string, encodes precedence: the `` ends up nested two levels deeper (inside `Term`) than the `+` (at `Expr` level), which is exactly why, when this derivation is later turned into a parse tree, multiplication groups tighter than addition without a single explicit "precedence number" anywhere in the grammar. Now contrast with a naively unified grammar that collapses `Expr`, `Term`, `Factor` into one nonterminal: ``` Expr -> Expr '+' Expr | Expr '' Expr | '(' Expr ')' | id | num ``` This generates the exact same language — every string derivable in the layered grammar is derivable here too, and vice versa — but for `id + id id` it admits two distinct leftmost derivations, one that groups as `id + (id id)` and one that groups as `(id + id) id`. Both are legitimate parse structures for the same token string under this grammar, which is precisely the definition of ambiguity: the layered three-nonterminal grammar was designed exactly to eliminate this by construction, using nonterminal layering as a substitute for explicit precedence declarations.
Common pitfalls & traps
| Pitfall | What goes wrong | Fix |
|---|---|---|
| Ambiguous grammar | Same string has ≥2 parse trees (e.g., unlayered expression grammar, dangling-`else`) | Layer nonterminals by precedence; add disambiguating rules (e.g., "match else to nearest unmatched if") |
| Direct left recursion, unnoticed | looks natural but breaks naive recursive-descent parsers (infinite loop) | Rewrite via standard left-recursion elimination before top-down parsing |
| Useless nonterminals | A nonterminal that can never derive a terminal string, or is never reachable from , silently bloats the grammar | Compute reachable & generating nonterminal sets; prune |
| Confusing terminals with nonterminals | Writing `id` as if it can be expanded further, or forgetting quotes around literal tokens | Keep strictly; name terminals exactly as the scanner emits them |
| Forgetting -productions | Grammar cannot express "optional" constructs (empty statement list, optional `else`) | Add explicit `X → ε` where the language truly allows an empty case |
| Grammar accepts too much or too little | E.g., allowing `a + + b` by a loose rule, or rejecting valid nested expressions via overly rigid rules | Test against both accepted and rejected example strings, not just happy-path input |
Pioneers & history
Origins · 1 minNoam Chomsky and how this was discovered
Chomsky's 1956 work on formal grammars for describing natural language syntax introduced the hierarchy of grammar classes — regular, context-free, context-sensitive, unrestricted — that gives the field its name and its rigor. His context-free grammars were devised to model human language structure, but their real, lasting impact turned out to be as the exact right tool for describing the syntax of programming languages.
Backus co-developed the notation used to specify ALGOL 60's syntax at the 1959 ALGOL conference; Peter Naur refined it into what is now called Backus–Naur Form (BNF), the standard concrete notation for writing context-free grammars in language manuals and compiler front ends, essentially unchanged in spirit to this day.
Where this matters
Every production compiler front end — GCC, Clang, `javac`, the Rust and Go compilers, V8's JavaScript parser — begins with a context-free grammar (explicit or embedded in hand-written parser code) that defines legal program syntax before any semantic analysis happens. Language specifications (the C standard, the Java Language Specification, ECMAScript) publish their syntax as BNF or EBNF grammars directly, making the CFG the literal, citable legal definition of "valid syntax" for the language. Parser-generator tools such as Yacc/Bison, ANTLR, and Menhir take a CFG as direct textual input and emit a working parser, meaning grammar design is not academic exercise but the actual engineering artifact compiler teams maintain and version. Beyond compilers, CFGs underlie configuration-file formats (JSON, YAML, TOML each have a published CFG), markup languages (HTML/XML's nesting structure), query languages (SQL grammars), and even parts of computational linguistics, where Chomsky's original motivation — modeling natural language syntax — persists in modern syntactic parsers used in NLP pipelines. Any system that must recognize nested structure rather than merely a flat pattern of tokens reaches for a CFG.
Ask the tutor: How do I mechanically eliminate left recursion from a grammar before writing a recursive-descent parser?
Is every context-free grammar ambiguous, or is ambiguity rare?
Ambiguity is neither universal nor rare — it depends entirely on how the grammar is written, not on the language it generates. Many practical grammars (arithmetic expressions with layered precedence nonterminals, well-formed parenthesis matching) are unambiguous by careful design. Others, like the naive single-nonterminal expression grammar or the classic dangling-`else` grammar, are ambiguous by an easy oversight. Crucially, ambiguity is a property of a specific grammar, not necessarily of the underlying language: some inherently ambiguous languages exist where every equivalent grammar is ambiguous, but most languages used in real compilers admit an unambiguous grammar if written carefully — which is exactly the discipline grammar designers practice.
How is a CFG different from a regular expression, given both describe sets of strings?
A regular expression (equivalently, a finite automaton) has no memory of unbounded depth: it processes input with only finitely many internal states, so it cannot "count" arbitrarily high, which balanced nesting requires. A context-free grammar adds recursion — a nonterminal can (in)directly refer back to itself — giving it an implicit unbounded stack of "things still expected to close." That extra stack-like power is exactly the pumping-lemma argument in this article: it is what lets a CFG generate matched parentheses, nested blocks, and recursive expressions that no regular language can capture.
Why do compilers layer nonterminals (Expr, Term, Factor) instead of just writing one big rule with all operators?
A single flat rule like `Expr → Expr '+' Expr | Expr '' Expr | ...` generates the correct language but is ambiguous, since it gives no way to decide whether `a + b c` groups as `(a+b)c` or `a+(bc)`. Layering nonterminals by precedence level bakes the grouping decision directly into the grammar's structure: because `Term` (built from `*`) sits strictly beneath `Expr` (built from `+`) in the rule hierarchy, every valid derivation is forced to nest multiplication inside addition, resolving precedence without any auxiliary numeric precedence table.
Can every context-free language be described by an unambiguous grammar?
No — there exist inherently ambiguous context-free languages for which no equivalent unambiguous grammar exists at all; a standard example involves the union of two related sublanguages sharing an overlapping middle case (such as ). These are rare in practice but establish a hard theoretical limit: unlike ambiguity in a specific grammar, which can usually be fixed by rewriting, inherent ambiguity is a property of the language itself and cannot be engineered away.
What is the relationship between a CFG and the parse tree a parser eventually builds?
The CFG is the static specification: a finite list of rewrite rules describing legal structure. A parse tree is the record of one particular derivation — internal nodes labeled by nonterminals, each with children corresponding to the symbols on the right-hand side of the production used to expand it, and leaves reading out the original terminal string left to right. The grammar defines what trees are possible; the parsing algorithm (covered under Derivations and Parse Trees and Top-Down Parsing) is the procedure that, given an input string, efficiently reconstructs the one tree (or reports there is none, or that there are several, if ambiguous) that the grammar assigns to it.
Why can't I just test my grammar by generating random strings and checking they look right?
Generation only demonstrates that the grammar produces some valid strings — it cannot show the grammar rejects everything it should, nor reveal ambiguity, since two different generation paths might silently produce the identical output string without you noticing the duplication. Rigorous grammar validation instead checks: (1) every intended construct is derivable, via explicit derivation traces like the worked example here; (2) clearly illegal strings are not derivable; and (3) for suspected ambiguity, you actively search for two distinct leftmost derivations of the same string, as done for the dangling-else and unlayered expression cases above.
Does grammar choice affect parser efficiency, or only correctness?
Both. An arbitrary CFG can always be recognized in time by general algorithms like CYK or Earley, but production compilers demand linear or near-linear parsing, which requires the grammar to satisfy extra structural conditions — no left recursion and bounded lookahead for top-down LL parsing, or the LR conditions for bottom-up parsing. Two grammars can generate the exact same language yet differ enormously in how easily (or whether at all) a particular parsing algorithm can be built for them, which is why grammar transformation (left-recursion elimination, left-factoring) is a standard preprocessing step covered under Top-Down Parsing.