Algorithm Search⌘K

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 L(G)L(G) and reports its first error at the earliest possible token.

Notation and assumptions. G=(V,Σ,P,S)G = (V, \Sigma, P, S) is augmented with S′→SS' \to S as production 0, and S′S' occurs on no right side. GG is reduced: every nonterminal is reachable from SS and derives some terminal string. An LR(1) item is [i,a][i, a] with ii a dotted production and a∈Σ∪{$}a \in \Sigma \cup \{\$\}; the LR(1) closure adds [B→⋅η, b][B \to \cdot \eta,\ b] for every item [A→α⋅Bβ, a][A \to \alpha \cdot B \beta,\ a], every production B→ηB \to \eta and every b∈FIRST(βa)b \in \mathrm{FIRST}(\beta a). The core of a set of LR(1) items is the set of its dotted productions. For an LR(0) state ss and a kernel item ii of ss, let

LA(s,i)={a:[i,a]∈J for some canonical LR(1) state J whose core is the closure of s}.\mathrm{LA}(s, i) = \{ a : [i, a] \in J \text{ for some canonical LR(1) state } J \text{ whose core is the closure of } s \}.

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 V∪Σ∪{$}V \cup \Sigma \cup \{\$\}, used as a terminal. For a kernel item ii and a lookahead bb, the closure of {[i,b]}\{[i, b]\} is obtained from the closure of {[i,#]}\{[i, \#]\} by replacing #\# with bb 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 FIRST(βa)\mathrm{FIRST}(\beta a), which is FIRST(β)\mathrm{FIRST}(\beta), together with aa when β\beta is nullable. The symbol aa is used only as a terminal that is copied, so renaming #\# to bb maps each application of the rule for #\# to an application for bb 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. □\square

Lemma 1 justifies the classification of line L2: in the closure of {[i,#]}\{[i, \#]\}, an item [B→γ⋅Xδ, c][B \to \gamma \cdot X \delta,\ c] with c≠#c \ne \# contributes cc to the successor kernel item j=[B→γX⋅δ]j = [B \to \gamma X \cdot \delta] of t=GOTO(s,X)t = \mathrm{GOTO}(s, X) whatever the lookahead of ii is (spontaneous), and an item with c=#c = \# contributes exactly the lookaheads of ii (a link from (s,i)(s, i) to (t,j)(t, j)).

Two standard facts about the canonical collections are used. (F1) The core of GOTO1(J,X)\mathrm{GOTO}_1(J, X) for an LR(1) state JJ is the LR(0) state GOTO(core(J),X)\mathrm{GOTO}(\mathrm{core}(J), X), because both GOTO functions move the dot over XX 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 FIRST(βa)\mathrm{FIRST}(\beta a) 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 JJ every kernel dotted production carries at least one lookahead.

Let ℓ(s,i)\ell(s, i) be the set that line L3 leaves for the kernel item ii of state ss.

Theorem 1. ℓ(s,i)=LA(s,i)\ell(s, i) = \mathrm{LA}(s, i) for every state ss and kernel item ii.

Proof of ⊆\subseteq. By induction on the steps that add a lookahead to some ℓ\ell.

  • Start. $\$ is put on [S′→⋅S][S' \to \cdot S] in state 0; the canonical start state contains [S′→⋅S, $][S' \to \cdot S,\ \$].
  • Spontaneous. Suppose cc is spontaneous for (t,j)(t, j), coming from the kernel item ii of ss through an item [B→γ⋅Xδ, c][B \to \gamma \cdot X \delta,\ c] in the closure of {[i,#]}\{[i, \#]\}. By (F2) there is an LR(1) state JJ with core ss and an item [i,b]∈J[i, b] \in J. By Lemma 1 the closure of {[i,b]}\{[i, b]\}, which is part of JJ, contains [B→γ⋅Xδ, c][B \to \gamma \cdot X \delta,\ c], so GOTO1(J,X)\mathrm{GOTO}_1(J, X) contains [j,c][j, c], and its core is tt by (F1). Hence c∈LA(t,j)c \in \mathrm{LA}(t, j).
  • Link. Suppose aa is copied along a link from (s,i)(s, i) to (t,j)(t, j), with a∈ℓ(s,i)a \in \ell(s, i). By induction a∈LA(s,i)a \in \mathrm{LA}(s, i), so some LR(1) state JJ with core ss has [i,a][i, a]. By Lemma 1 its closure contains [B→γ⋅Xδ, a][B \to \gamma \cdot X \delta,\ a], and GOTO1(J,X)\mathrm{GOTO}_1(J, X), of core tt, contains [j,a][j, a].

Proof of ⊇\supseteq. By induction on the order in which the canonical LR(1) states are discovered, we show that every kernel item [j,a][j, a] of an LR(1) state KK with core tt has a∈ℓ(t,j)a \in \ell(t, j). The start state has the single kernel item [S′→⋅S, $][S' \to \cdot S,\ \$], and $∈ℓ(0,[S′→⋅S])\$ \in \ell(0, [S' \to \cdot S]). Any other state is K=GOTO1(J,X)K = \mathrm{GOTO}_1(J, X) for an earlier state JJ of core ss, with t=GOTO(s,X)t = \mathrm{GOTO}(s, X) by (F1). A kernel item [j,a][j, a] of KK comes from an item [B→γ⋅Xδ, a][B \to \gamma \cdot X \delta,\ a] of JJ, which by Lemma 1 lies in the closure of some single kernel item [i,b][i, b] of JJ. Again by Lemma 1, the closure of {[i,#]}\{[i, \#]\} contains [B→γ⋅Xδ, c][B \to \gamma \cdot X \delta,\ c] with either c=ac = a and a≠#a \ne \#, so that aa is spontaneous for (t,j)(t, j), or c=#c = \# and a=ba = b, so that (s,i)(s, i) is linked to (t,j)(t, j). In the first case a∈ℓ(t,j)a \in \ell(t, j) from the start of L3. In the second, b∈ℓ(s,i)b \in \ell(s, i) by induction, and since L3 stops only when no link can enlarge its target, b∈ℓ(t,j)b \in \ell(t, j). □\square

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 ss with the sets ℓ(s,i)\ell(s, i). By Lemma 1 and Theorem 1 this closure is the union, over the LR(1) states JJ with core ss, of the closures of their kernels, that is, of the states JJ themselves. So a completed item gets lookahead aa in L4 exactly when it has aa in some LR(1) state of that core, which is the merged table. Accept is the reduction by production 0 on $\$. □\square

Part 2: resolution

Lemma 3. After L5 to L7 every cell holds at most one action, and:

  1. a cell with a single action keeps it;
  2. a cell with a shift on aa and one reduction by pp, where aa and pp have precedences π(a)\pi(a) and π(p)\pi(p), keeps the shift if π(a)>π(p)\pi(a) > \pi(p) or if they are equal and declared %right, keeps the reduction if π(a)<π(p)\pi(a) < \pi(p) or if they are equal and declared %left, and becomes an error if they are equal and declared %nonassoc; no conflict is reported;
  3. 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. □\square

Part 3: the parser with default reductions

Assume now that the table of Corollary 2 has no conflict, that is, GG is LALR(1), so Part 2 changes nothing. Write P\mathcal{P} for the parser that uses only the table, and D\mathcal{D} 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 rr.

Two facts are taken from the LALR(1) parsing page: P\mathcal{P} halts on every input and accepts exactly L(G)L(G). 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 L(G)L(G); it applies to D\mathcal{D}, since a default reduction is a table entry of its state.

Lemma 4. On every input, until P\mathcal{P} reports an error, D\mathcal{D} makes the same moves as P\mathcal{P}.

Proof. In a configuration where P\mathcal{P} has an action xx for the top state qq and the token aa, if qq has a default reduction rr then every action of row qq is rr, so x=rx = r, and D\mathcal{D} also reduces by rr; otherwise D\mathcal{D} reads the same cell. The moves change the configuration in the same way. □\square

Lemma 5. If D\mathcal{D} or P\mathcal{P} shifts the token aja_j of the input a1⋯ana_1 \cdots a_n, then a1⋯aja_1 \cdots a_j is a prefix of a sentence of GG.

Proof. At the shift the state stack spells, by the soundness invariant, symbols X1⋯XmX_1 \cdots X_m with X1⋯Xm⇒∗a1⋯aj−1X_1 \cdots X_m \Rightarrow^* a_1 \cdots a_{j-1}, and the shift entry exists only if the LR(0) state reached on X1⋯XmX_1 \cdots X_m has an item [A→α⋅ajβ][A \to \alpha \cdot a_j \beta]. That state is the set of items valid for the viable prefix X1⋯XmX_1 \cdots X_m (LR item sets), so X1⋯XmajX_1 \cdots X_m a_j is a viable prefix: S′⇒rm∗X1⋯XmajβwS' \Rightarrow_{rm}^* X_1 \cdots X_m a_j \beta w for some β\beta and ww. Since GG is reduced, β\beta derives some terminal string vv, so a1⋯ajvw∈L(G)a_1 \cdots a_j v w \in L(G). □\square

Theorem 6. D\mathcal{D} accepts exactly L(G)L(G). Let w=a1⋯an∉L(G)w = a_1 \cdots a_n \notin L(G), and let kk be the largest number such that a1⋯aka_1 \cdots a_k is a prefix of a sentence. Then D\mathcal{D} shifts a1,…,aka_1, \ldots, a_k without reporting an error, never shifts ak+1a_{k+1}, and never accepts; so if it reports an error, the first one is at position kk, counting positions from 0 and with position nn meaning the end marker.

Proof. If w∈L(G)w \in L(G) then P\mathcal{P} accepts ww without an error, and by Lemma 4 D\mathcal{D} makes the same moves and accepts. If w∉L(G)w \notin L(G), D\mathcal{D} does not accept it, by soundness.

Now let w∉L(G)w \notin L(G) and choose a sentence a1⋯akv∈L(G)a_1 \cdots a_k v \in L(G). On that sentence D\mathcal{D} accepts, so it shifts each of a1,…,aka_1, \ldots, a_k without an error. Every decision that D\mathcal{D} takes before shifting aka_k depends only on its stack and on the current token, which is one of a1,…,aka_1, \ldots, a_k; so on ww it makes exactly the same moves up to and including the shift of aka_k, and reports no error before. If k<nk < n, Lemma 5 shows that D\mathcal{D} never shifts ak+1a_{k+1}, since a1⋯ak+1a_1 \cdots a_{k+1} 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 ak+1a_{k+1} is the current token, at position kk. If k=nk = n, the same argument with the end marker in place of ak+1a_{k+1} applies: D\mathcal{D} cannot accept and can no longer shift, so the first error is at position nn. □\square

Halting. Theorem 6 does not by itself say that D\mathcal{D} reaches the error: after P\mathcal{P} would report it, D\mathcal{D} may still make default reductions. These reductions are table entries of their states, so each is a move that P\mathcal{P} 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. ∎

Back to Yacc