Derivations, Parse Trees, and Ambiguity
Consider the string `id + id id` and the grammar rule `E → E + E | E E | id`. Does multiplication happen before addition, or after? The grammar alone does not say. You must watch how the string is built from the start symbol, step by step, to find out. This is the problem derivations solve. They give a precise, mechanical account of how a context-free grammar generates a string. Parse trees give the shape of that process a compiler can actually use. This matters because a compiler's front end must not only accept or reject a string. It must recover structure: which subexpression is the left operand of ``, which statement is the body of a loop, which `else` belongs to which `if`. Two different derivations of the same string can imply two different structures. If a grammar permits this for some string, it is called ambiguous. Ambiguity is not a cosmetic flaw. It is a genuine defect that can make a language's meaning depend on parser implementation details. That is exactly what a language specification must forbid. The big picture is a chain of three ideas. A derivation is a sequence of rewriting steps from the start symbol to a terminal string. A parse tree is the derivation's structure, stripped of the order in which non-terminals were expanded. Ambiguity is the situation where one parse tree corresponds to more than one distinct* derivation order in the same "direction" (leftmost or rightmost). Equivalently, it is where a string has more than one parse tree. Understanding these three ideas precisely is the prerequisite for every parsing algorithm that follows. This article builds the exact definitions of leftmost and rightmost derivations. It shows how a parse tree factors out irrelevant ordering choices. It proves the standard theorem linking derivations to trees. It also works through the classical ambiguous-grammar examples — dangling-else and expression precedence. This includes how compilers resolve them in practice.
- A derivation is a sequence of sentential forms obtained by repeatedly replacing one non-terminal using a production, starting from the start symbol.
- Leftmost derivations always expand the leftmost non-terminal; rightmost derivations always expand the rightmost one.
- A parse tree records only the shape of a derivation (which productions were used where), not the order of expansion.
- Theorem: a string has a parse tree iff it has a leftmost derivation iff it has a rightmost derivation — and these all correspond to the same set of trees.
- A grammar is ambiguous if some string has two distinct parse trees, equivalently two distinct leftmost derivations.
- Ambiguity from precedence/associativity is fixed by rewriting the grammar; ambiguity like dangling-else is often resolved by parser convention (disambiguation rules) instead.
A parse tree is a derivation with the irrelevant choice of which non-terminal to expand next thrown away — ambiguity is exactly when that discarded choice was hiding two genuinely different structures.
The idea
Let be a context-free grammar with non-terminals , terminals , productions , and start symbol . A sentential form is any string over derivable from . A derivation step rewrites one sentential form into another: if , where and . A derivation is a chain where ; we write . The set of strings so derivable is . At each step, a sentential form may contain several non-terminals, and the definition of does not specify which one to rewrite. Two canonical policies remove this freedom. In a leftmost derivation (written ), every step expands the leftmost non-terminal in the current sentential form. In a rightmost derivation (written , sometimes called a "canonical derivation"), every step expands the rightmost non-terminal. These are not different grammars or different languages — they are different strategies for exploring the same set of derivations, and they matter because Top-Down Parsing methods naturally construct leftmost derivations while Bottom-Up Parsing methods naturally construct rightmost derivations in reverse. A parse tree (or derivation tree) for is an ordered tree where: the root is labeled ; each internal node is labeled by some and its children, read left to right, spell out the right-hand side of some production (a node labeled with a single child labeled represents ); and each leaf is labeled by a terminal, or by . The yield of the tree is the concatenation of its leaf labels left to right, and a tree is a parse tree for string if its yield is . Crucially, a parse tree records which production was applied at which node, but not the order in which the productions were applied relative to each other — that ordering information is exactly what a derivation adds and a parse tree discards. A grammar is ambiguous if some string has two or more distinct parse trees. Because of the equivalence proved below, this is the same as saying has two distinct leftmost derivations (or, equivalently, two distinct rightmost derivations). Ambiguity is a property of the grammar, not of the language: the same language can often be generated by both an ambiguous grammar and an unambiguous one, and part of grammar engineering is finding the unambiguous version.
How it works
Constructing a leftmost derivation. The mechanism is a simple leftmost-rewrite loop: scan the current sentential form for the first (leftmost) non-terminal, pick a production for it, substitute, and repeat until only terminals remain. ``` function LEFTMOST-DERIVE(G, S): form ← [S] # sequence of symbols, sentential form steps ← [] while form contains a non-terminal: i ← index of leftmost non-terminal A in form choose a production A → γ (this choice is the nondeterminism that a real parser must resolve) form ← form[0 .. i-1] · γ · form[i+1 .. end] append form to steps return steps # the derivation, ending in a terminal string ``` Building the corresponding parse tree alongside the derivation is just as mechanical: maintain a tree whose current frontier (leaves not yet expanded) mirrors the sentential form, and whenever a non-terminal is rewritten by , attach children to 's node spelling . ``` function BUILD-TREE-FROM-DERIVATION(steps): root ← new node(S) frontier ← [root] # left-to-right list of open leaves for each step (A → γ) applied at position i in steps: node ← frontier[i] # the leaf currently labeled A if γ = ε: attach child(node, ε) remove node from expansion set # ε-leaf is terminal-like else: for each symbol X in γ (left to right): attach child(node, X) replace frontier[i] with the new children, in order return root ``` The key correctness fact used by both real parsers and this construction is:
Theorem (Derivation–Tree Correspondence). For a context-free grammar and string , the following are equivalent: 1. has a parse tree with yield and root . 2. (a leftmost derivation exists). 3. (a rightmost derivation exists). Moreover, distinct leftmost derivations correspond to distinct parse trees, and vice versa — so counting parse trees and counting leftmost derivations of give the same number. Proof sketch. (2) (1): induct on the number of derivation steps, using the tree-building procedure above; each leftmost expansion step corresponds to attaching one production's children to the current leftmost open leaf, and since expansion is always leftmost, the frontier ordering is unambiguous — there is exactly one "current leftmost open node" at every step, so the map from leftmost derivations to trees is well-defined. (1) (2): given a parse tree, perform a pre-order (left-to-right, depth-first) traversal, and at each internal node apply its production; this canonical traversal is a leftmost derivation, because pre-order always visits the leftmost unvisited subtree before any sibling to its right. The rightmost case (3) is symmetric, using a traversal that expands the rightmost open non-terminal at each step (equivalently, a reverse rightmost derivation corresponds to bottom-up reduction, the basis of shift-reduce parsing). Injectivity in both directions follows because the tree-to-derivation and derivation-to-tree maps constructed above are mutual inverses on the set of (production choice, position) sequences that respect leftmost order. This theorem is precisely what licenses parser designers to work with whichever of the three objects is most convenient: top-down predictive parsers build leftmost derivations directly; bottom-up LR parsers build rightmost derivations in reverse; but both are computing the same parse tree, and it's the tree — not the derivation order — that carries semantic structure (e.g., driving syntax-directed translation).
Complexity & correctness
Time and space to build one parse tree from a given derivation. If a derivation has steps and each step's production has right-hand side of length at most (a grammar constant), building the tree takes time and space, since each step attaches at most new children and the frontier list is updated once per step with an array/list splice of bounded local cost (amortized with a suitable representation, e.g., a doubly linked list of frontier nodes). A parse tree for a string of length has at most leaves, but can have more internal nodes if the grammar contains long unit-production chains (); in the worst case (a grammar with non-terminals and no bound on chain length) tree size is , since no non-terminal is expanded twice in a row on the same frontier position without producing a distinct sentential form. In practice, grammars for Top-Down Parsing and Bottom-Up Parsing are designed (or transformed) to avoid such chains, keeping parse trees in size.
Recognizing ambiguity is undecidable in general. There is no algorithm that takes an arbitrary context-free grammar and always correctly reports whether it is ambiguous — this is a classical undecidability result (reducible from the Post Correspondence Problem). What compilers rely on instead is sufficient, checkable conditions for unambiguity: a grammar is unambiguous if it can be parsed by a deterministic method such as LL(1) or LR(1), because the construction of those parsing tables itself either succeeds (proving no conflict, hence a unique parse per accepted string) or fails with a detected conflict. This is why parser-generator tools report "shift/reduce conflict" or "reduce/reduce conflict" rather than attempting a general ambiguity test — they are certifying unambiguity relative to their parsing strategy, which is decidable, rather than solving the undecidable general problem.
Correctness of the leftmost/rightmost equivalence rests on the induction sketched above: the key invariant is that at every prefix of a leftmost derivation, the sentential form splits cleanly into "fully terminal prefix already emitted to the left of the current point" and "one open non-terminal plus everything to its right still to be expanded," which mirrors exactly the frontier of a partially-built tree read left to right. This is why leftmost derivations correspond to pre-order tree construction and rightmost to post-order-reversed (i.e., a rightmost derivation read backward is the order in which a bottom-up parser reduces).
The honest tradeoff. Ambiguity is not always bad for writing a grammar — ambiguous grammars are often shorter, more natural, and closer to a language's informal description (e.g., "an if-statement, optionally followed by else"). The cost is that the compiler writer must supply extra information — precedence and associativity declarations, or a "match the else with the nearest unmatched if" rule — outside the bare grammar to pin down one tree per string. Rewriting a grammar to be unambiguous internalizes that rule into the productions themselves at the price of a taller, more layered grammar (as in the precedence-stratified example above), which is why parser generators support both approaches.
Worked example
Take : , and the string . We exhibit two distinct leftmost derivations, proving is ambiguous on .
Leftmost derivation 1 (yields tree A: `(id + id) * id`):
| Step | Sentential form | Production applied |
|---|---|---|
| 0 | — | |
| 1 | ||
| 2 | (leftmost ) | |
| 3 | ||
| 4 | ||
| 5 |
Leftmost derivation 2 (yields tree B: `id + (id * id)`):
| Step | Sentential form | Production applied |
|---|---|---|
| 0 | — | |
| 1 | ||
| 2 | (leftmost ) | |
| 3 | ||
| 4 | ||
| 5 |
Both are valid leftmost derivations of the same string from the same grammar, but they differ at step 1 (which top-level production is chosen), and this single early choice cascades into two structurally different trees — shown in the figure above. By the theorem, two distinct leftmost derivations means two distinct parse trees, which is the definition of being ambiguous. Semantically this matters enormously: tree A computes -style precedence (multiplication after addition), while tree B computes the conventional (multiplication first) — the same string would evaluate to different results depending on which tree a compiler happens to build.
The fix. Stratify the grammar by precedence level, forcing associativity and precedence into the productions:
Now trace again: any leftmost derivation of must start (since alone cannot yield a `+`), forcing the top-level split to occur at `+`; inside the right operand, is the only way to introduce ``. There is exactly one leftmost derivation, exactly one parse tree — tree A's shape, `(id) + (id id)` structurally at the / boundary — and the grammar is unambiguous on this string (and, one can verify, on every string in the language).
Dangling-else, a second worked case. Consider
and the string `if b then if b then a else a`. One parse attaches `else a` to the inner `if` (the conventional reading); another attaches it to the outer `if`. Both are valid parse trees under this grammar — a genuine ambiguity that, unlike operator precedence, is usually not removed by grammar rewriting in production compilers (though it can be, via an auxiliary non-terminal distinguishing "matched" from "unmatched" statements). Instead, most parser generators apply a disambiguation rule — "match each else with the closest unmatched then" — encoded directly in the parsing algorithm (e.g., by declaring `else` as taking precedence in a shift/reduce conflict). This shows the two available strategies side by side: precedence ambiguity is fixed in the grammar; dangling-else ambiguity is more often fixed in the parser's conflict-resolution policy.
Common pitfalls & traps
| Pitfall | Why it's wrong | Fix |
|---|---|---|
| Treating leftmost and rightmost derivations as generating different languages | They generate the same language and, up to reordering, the same set of trees — only the step order differs | Remember: derivation order is a strategy choice, not a grammar property |
| Confusing "the grammar is ambiguous" with "the string is invalid" | An ambiguous string is still in ; it just has trees | Check for multiple distinct parse trees, not non-existence of one |
| Assuming every language has some unambiguous grammar | Some context-free languages are inherently ambiguous (every grammar for them is ambiguous), e.g. classic examples built from | Know that unambiguous-grammar existence is a property of the language, not guaranteed in general |
| Believing an ambiguity-detection algorithm exists for arbitrary CFGs | Ambiguity is undecidable in general | Rely on decidable, weaker checks: LL(1)/LR(1) table construction success is sufficient for unambiguity |
| Building the parse tree in the order productions were listed rather than the order of the derivation | Silently produces the wrong tree shape, especially with left/right recursion mixed | Always use the sentential-form position (leftmost or rightmost non-terminal) to drive expansion, never source order |
| Forgetting -productions when computing tree yield | An internal node for contributes nothing to the yield but must still appear as a node (a common off-by-one in tree size counts) | Represent explicitly as a leaf child, then exclude it when concatenating the yield |
Pioneers & history
Origins · 1 minJohn Backus and how this was discovered
Backus led the team that built FORTRAN, the first widely used high-level programming language, and later co-developed the notation for specifying context-free grammars — Backus–Naur Form (BNF) — while working on ALGOL 60's formal syntax description. BNF turned "what strings are legal programs" into a precise, checkable mathematical object rather than an informal prose description, making derivations and parse trees well-defined artifacts a compiler could compute over.
Where this matters
Every production compiler front end — for C, Java, Python, Rust, or any language with an `if`/`else` construct or arithmetic expressions — must resolve exactly the ambiguities discussed here: expression-precedence ambiguity is designed away by grammar stratification (the `E`/`T`/`F` layering pattern generalizes to full operator-precedence tables), while dangling-else-style ambiguity is resolved by explicit disambiguation rules baked into parser generators like Yacc/Bison and ANTLR. Compiler front ends built with Top-Down Parsing techniques (recursive descent, LL parsers) construct leftmost derivations directly as they read input left to right, using exactly the frontier-expansion process described above; Bottom-Up Parsing techniques (LR, LALR, as used in Yacc/Bison and most industrial-strength parsers) construct rightmost derivations in reverse, reading shift/reduce and reduce/reduce conflicts as automated, decidable proxies for the underlying, otherwise-undecidable question of grammar ambiguity. Beyond compilers, the same theory underlies natural-language parsing (where ambiguity is the norm rather than the exception, and multiple parse trees are ranked probabilistically rather than eliminated) and configuration/data-format grammars (JSON, YAML schemas) where an unambiguous grammar is what guarantees a document parses to one unique structure regardless of which library reads it.
Ask the tutor: Can you show me why an LR(1) parser building a rightmost derivation "in reverse" actually recovers the same parse tree as a leftmost derivation would?
What exactly is the difference between a derivation and a parse tree?
A derivation is a sequence of rewriting steps from the start symbol to a string, recording the order in which non-terminals were expanded. A parse tree is the structural residue of a derivation — which production fired at which position — with the expansion order stripped away. Many different derivations (differing only in which non-terminal is expanded first, second, etc.) can correspond to the exact same parse tree, because tree-building only cares about the parent/child production relationships, not the traversal order used to discover them. Leftmost and rightmost derivations are two canonical ways of picking one representative derivation per tree, which is why the theorem in this article says "one tree ⟺ one leftmost derivation ⟺ one rightmost derivation" — restoring a unique correspondence once you fix a traversal convention.
Why do compilers care about leftmost derivations specifically, if trees are what matter?
Because constructing a parse tree requires some concrete algorithm, and top-down parsing algorithms (recursive descent, LL parsers) process input left to right while always trying to expand the leftmost unresolved non-terminal — this is literally what a leftmost derivation is. So while the tree is the semantically meaningful artifact, the leftmost derivation is the natural execution trace of a top-down parser, useful for reasoning about lookahead requirements, left recursion elimination, and predictive parsing table construction, all of which are properties of the derivation process rather than the finished tree.
Is every ambiguous grammar fixable by rewriting it into an equivalent unambiguous one?
No. Some context-free languages are inherently ambiguous: every context-free grammar that generates them is ambiguous, no rewriting can help. A standard example combines two overlapping conditions, such as , where any grammar must effectively "guess" which condition holds before seeing enough of the string, forcing overlap and hence ambiguity on strings satisfying both conditions (). Most practical programming-language constructs (arithmetic expressions, if-then-else, statement sequences) are not inherently ambiguous — their common ambiguous grammars can be rewritten unambiguously — but the general phenomenon of inherent ambiguity shows the limits of grammar engineering.
Why is ambiguity checking undecidable, intuitively?
The undecidability is proved by reduction from the Post Correspondence Problem (PCP): given two lists of strings, PCP asks whether some sequence of indices makes the concatenations from both lists equal. One can build a context-free grammar from a PCP instance such that the grammar is ambiguous exactly when the PCP instance has a solution. Since PCP is undecidable (no algorithm can always answer it correctly), and this reduction preserves answers, grammar ambiguity must be undecidable too. This is why real tools sidestep the general problem entirely, instead asking a strictly easier, decidable question: "does this specific parsing algorithm (LL(1), LR(1), etc.) succeed on this grammar without conflict?"
What is the "yield" of a parse tree, and why exclude non-terminal labels from it?
The yield is the string obtained by reading a parse tree's leaves left to right and concatenating their labels; since leaves are always terminals (or ε), the yield is automatically a string over the terminal alphabet, exactly the kind of string the grammar is meant to generate. Internal (non-terminal) node labels are excluded because they represent intermediate syntactic categories — like "this subtree is an expression" — that exist only to organize the derivation, not to appear in the final program text. This distinction is also why parse trees, not internal labels, are what gets consumed by later phases: a compiler's semantic analysis walks the tree structure using the non-terminal labels for dispatch, while the yield reconstructs exactly the original source text.
Does the dangling-else problem ever get fixed by changing the grammar instead of adding a parser rule?
Yes — it is possible, at the cost of a more complex grammar, by introducing two categories of statement: "matched" (every `then` has a paired `else`) and "unmatched" (the last `then` has no `else` yet). A production like `Matched → if b then Matched else Matched | a` alongside `Unmatched → if b then S | if b then Matched else Unmatched` forces every derivation to associate `else` with the nearest `then`, eliminating the ambiguity structurally. This is more verbose than a bare grammar plus a parser-level disambiguation rule, which is why most real compilers choose the simpler route: an ambiguous but concise grammar, paired with an explicit "closest unmatched if" convention enforced by the parsing algorithm.
How does this topic connect to what comes right after it in learning a compiler front end?
Leftmost derivations are the conceptual foundation for Top-Down Parsing: predictive and recursive-descent parsers are, at heart, algorithms that construct a leftmost derivation incrementally while consuming input left to right, and left recursion or ambiguity in the grammar shows up as concrete implementation obstacles there. Rightmost derivations (read in reverse) are the foundation for Bottom-Up Parsing: shift-reduce and LR parsers build a rightmost derivation backward, reducing substrings to non-terminals until only the start symbol remains. Both families of algorithms are, in the end, mechanical procedures for discovering one parse tree per input string efficiently — which is precisely why an unambiguous grammar (one tree per string) is a prerequisite for either method to behave deterministically.