Proofs: Yacc
These are the human proofs of Yacc, at full length.
Correctness of the yacc construction and of its parser full
This proof shows: correctness.
We prove three things about the algorithm on the yacc page: lines L1 to L4 build the LALR(1) table, lines L5 to L7 keep one action per cell by the stated rules, and for an LALR(1) grammar the parser with default reductions accepts exactly and reports its first error at the earliest possible token.
Notation and assumptions. is augmented with as production 0, and occurs on no right side. is reduced: every nonterminal is reachable from and derives some terminal string. An LR(1) item is with a dotted production and ; the LR(1) closure adds for every item , every production and every . The core of a set of LR(1) items is the set of its dotted productions. For an LR(0) state and a kernel item of , let
The LALR(1) table is, by definition, the table of the canonical LR(1) states merged by core.
Part 1: the lookaheads
Lemma 1 (closure is linear in the lookahead). Let be a symbol outside , used as a terminal. For a kernel item and a lookahead , the closure of is obtained from the closure of by replacing with in every item. The closure of a set of items is the union of the closures of its elements.
Proof. The closure rule acts on one item at a time and computes , which is , together with when is nullable. The symbol is used only as a terminal that is copied, so renaming to maps each application of the rule for to an application for and back; since is in no FIRST set, no other lookahead is affected. Because each rule application looks at one item only, a closure of a union is the union of the closures.
Lemma 1 justifies the classification of line L2: in the closure of , an item with contributes to the successor kernel item of whatever the lookahead of is (spontaneous), and an item with contributes exactly the lookaheads of (a link from to ).
Two standard facts about the canonical collections are used. (F1) The core of for an LR(1) state is the LR(0) state , because both GOTO functions move the dot over in the same dotted productions and close in the same way, the LR(1) closure adding a dotted production exactly when the LR(0) closure does: the lookahead set is never empty, since every symbol derives a terminal string. (F2) For the same reason every LR(0) state is the core of some LR(1) state, and in an LR(1) state every kernel dotted production carries at least one lookahead.
Let be the set that line L3 leaves for the kernel item of state .
Theorem 1. for every state and kernel item .
Proof of . By induction on the steps that add a lookahead to some .
- Start. is put on in state 0; the canonical start state contains .
- Spontaneous. Suppose is spontaneous for , coming from the kernel item of through an item in the closure of . By (F2) there is an LR(1) state with core and an item . By Lemma 1 the closure of , which is part of , contains , so contains , and its core is by (F1). Hence .
- Link. Suppose is copied along a link from to , with . By induction , so some LR(1) state with core has . By Lemma 1 its closure contains , and , of core , contains .
Proof of . By induction on the order in which the canonical LR(1) states are discovered, we show that every kernel item of an LR(1) state with core has . The start state has the single kernel item , and . Any other state is for an earlier state of core , with by (F1). A kernel item of comes from an item of , which by Lemma 1 lies in the closure of some single kernel item of . Again by Lemma 1, the closure of contains with either and , so that is spontaneous for , or and , so that is linked to . In the first case from the start of L3. In the second, by induction, and since L3 stops only when no link can enlarge its target, .
Corollary 2. The cells filled by line L4 are the cells of the canonical LR(1) table merged by core.
Proof. Shift entries depend only on the cores, and the LR(0) GOTO equals the merged LR(1) GOTO by (F1). For reductions, line L4 closes the kernel of with the sets . By Lemma 1 and Theorem 1 this closure is the union, over the LR(1) states with core , of the closures of their kernels, that is, of the states themselves. So a completed item gets lookahead in L4 exactly when it has in some LR(1) state of that core, which is the merged table. Accept is the reduction by production 0 on .
Part 2: resolution
Lemma 3. After L5 to L7 every cell holds at most one action, and:
- a cell with a single action keeps it;
- a cell with a shift on and one reduction by , where and have precedences and , keeps the shift if or if they are equal and declared
%right, keeps the reduction if or if they are equal and declared%left, and becomes an error if they are equal and declared%nonassoc; no conflict is reported; - a cell where a shift and a reduction remain after L5 keeps the shift and reports a shift-reduce conflict, and a cell with only reductions keeps the one of the smallest production number and reports a reduce-reduce conflict.
Proof. Single actions skip resolution. In L5 the loop over the reductions of the cell compares each one with the shift while the shift is present and both precedences are defined; the three branches implement the comparisons of item 2, dropping the reduction, dropping the shift, or dropping both. Equal precedence means the same declaration line, so the associativity is that of the token. Reductions without a precedence, or met after the shift was dropped, are kept. If a shift and at least one kept reduction remain, L6 returns the shift; otherwise if two or more reductions remain, L7 returns the first, and they are sorted by production number. Every branch returns one action or none.
Part 3: the parser with default reductions
Assume now that the table of Corollary 2 has no conflict, that is, is LALR(1), so Part 2 changes nothing. Write for the parser that uses only the table, and for the parser of lines L8 to L11, which takes the default reduction of a state whose only action, over all tokens, is one reduction .
Two facts are taken from the LALR(1) parsing page: halts on every input and accepts exactly . A third fact is the soundness of the driver proved on the parsing table page: any driver whose shifts and gotos follow the LR(0) automaton and whose reductions are by completed items of the top state accepts only strings of ; it applies to , since a default reduction is a table entry of its state.
Lemma 4. On every input, until reports an error, makes the same moves as .
Proof. In a configuration where has an action for the top state and the token , if has a default reduction then every action of row is , so , and also reduces by ; otherwise reads the same cell. The moves change the configuration in the same way.
Lemma 5. If or shifts the token of the input , then is a prefix of a sentence of .
Proof. At the shift the state stack spells, by the soundness invariant, symbols with , and the shift entry exists only if the LR(0) state reached on has an item . That state is the set of items valid for the viable prefix (LR item sets), so is a viable prefix: for some and . Since is reduced, derives some terminal string , so .
Theorem 6. accepts exactly . Let , and let be the largest number such that is a prefix of a sentence. Then shifts without reporting an error, never shifts , and never accepts; so if it reports an error, the first one is at position , counting positions from 0 and with position meaning the end marker.
Proof. If then accepts without an error, and by Lemma 4 makes the same moves and accepts. If , does not accept it, by soundness.
Now let and choose a sentence . On that sentence accepts, so it shifts each of without an error. Every decision that takes before shifting depends only on its stack and on the current token, which is one of ; so on it makes exactly the same moves up to and including the shift of , and reports no error before. If , Lemma 5 shows that never shifts , since is not a prefix of a sentence. From then on its only possible moves are reductions, an accept, which soundness excludes, and an error. So the first error, if one is reported, is reported while is the current token, at position . If , the same argument with the end marker in place of applies: cannot accept and can no longer shift, so the first error is at position .
Halting. Theorem 6 does not by itself say that reaches the error: after would report it, may still make default reductions. These reductions are table entries of their states, so each is a move that would make with some token, and in every run of the tests the parser halts, but the proof above does not bound their number. The implementation therefore stops with an exception after a fixed number of steps; for the same reason, the claims about error recovery on the page, which concern what happens after the first error, are checked by tests rather than proved here. ∎