Algorithm Search⌘K

Proofs: LL(*) parsing

These are the human proofs of LL(*) parsing, at full length.

ALL-STAR-PARSE accepts exactly the sentences of a grammar without left recursion, returns their parse trees and reports errors at the first token that no sentence allows full

This proof shows: correctness.

Statement. Let G=(V,Σ,R,S)G = (V, \Sigma, R, S) be a grammar in which every nonterminal derives some terminal string and no nonterminal AA satisfies A⇒+AγA \Rightarrow^+ A \gamma (no left recursion, direct or hidden). For every input w=a1⋯anw = a_1 \cdots a_n, ALL-STAR-PARSE stops, and:

  1. it accepts if and only if w∈L(G)w \in L(G), and then it returns a parse tree of ww with root SS;
  2. if it reports an error at position ee (with e=n+1e = n + 1 meaning the end of the input), then a1⋯ae−1a_1 \cdots a_{e-1} is a prefix of a sentence, and a1⋯aea_1 \cdots a_e is not when e≤ne \le n.

The DFA cache does not change the result, so the statement holds for any contents of the cache built by earlier inputs.

Setup. Write ⇒\Rightarrow for one rewriting step and L(α)L(\alpha) for the terminal strings that a string of symbols α\alpha derives; L(α)≠∅L(\alpha) \neq \emptyset for every α\alpha because every symbol is productive. A head step on a string γ\gamma either replaces its first symbol B∈VB \in V by the right side of a production B→δB \to \delta, or removes its first symbol when it is the next input token (a match). This is the nondeterministic top-down parser: γ⇒∗x\gamma \Rightarrow^* x for a terminal string xx if and only if some sequence of head steps turns γ\gamma into ε\varepsilon while matching exactly xx, because a leftmost derivation rewrites the first nonterminal, and every terminal before it can be matched first. Call γ\gamma settled when it is ε\varepsilon or starts with a terminal.

Step 1: head expansions terminate. From a fixed string γ\gamma, there is no infinite sequence of head steps without a match. Suppose there were one. Its expansions form a forest of partial derivation trees whose roots are the symbols of γ\gamma, and an infinite sequence would expand infinitely many nodes of the tree of one symbol, so by König's lemma (each node has at most ∣G∣|G| children) that tree has an infinite path B0,B1,B2,…B_0, B_1, B_2, \ldots. Each Bj+1B_{j+1} is expanded only after all of its left siblings have been expanded to ε\varepsilon, since there was no match, so Bj⇒ηjBj+1⋯B_j \Rightarrow \eta_j B_{j+1} \cdots with ηj⇒∗ε\eta_j \Rightarrow^* \varepsilon. Some label repeats, Bj=BlB_j = B_l with j<lj < l, and then Bj⇒+Bjγ′B_j \Rightarrow^+ B_j \gamma', which is left recursion. By König's lemma again, the set of strings reachable from γ\gamma by head expansions is finite. So every CLOSURE call terminates, and the set it returns is the set of settled strings reachable by head expansions, with the stack entries of the parser read in when the local part becomes empty (line L27).

Step 2: what the configuration sets contain. Consider a call of ADAPTIVE-PREDICT for AA at position ii, with the parser stack X1⋯XqX_1 \cdots X_q below AA (top first), and write αp\alpha_p for the right side of the production pp of AA.

  • SLL. For t≥it \ge i, as long as the loop reaches the step tt, the set DD at that step is the set of pairs (p,γ)(p, \gamma) with γ\gamma settled such that head steps turn αp\alpha_p into γ\gamma while matching exactly ai⋯at−1a_i \cdots a_{t-1}. For t=it = i this is the definition of the start state. A step from tt to t+1t+1 happens only when no pair of DD has γ=ε\gamma = \varepsilon (line L15), so every pair is (p,atβ)(p, a_t \beta) or does not start with ata_t; line L18 keeps the pairs that can match ata_t and closes them, which by Step 1 gives exactly the settled strings reachable after matching ai⋯ata_i \cdots a_t. A cached edge was computed by the same function of (D,at)(D, a_t), so it is the same set.
  • LL. A configuration (p,γ,d)(p, \gamma, d) stands for the full stack γXq−d+1⋯Xq\gamma X_{q-d+1} \cdots X_q. By the same induction, the set CC at step tt represents exactly the settled full stacks reachable from αpX1⋯Xq\alpha_p X_1 \cdots X_q by head steps that match ai⋯at−1a_i \cdots a_{t-1}, and each such stack is represented at least once. The representation (ε,0)(\varepsilon, 0) is the only one of the empty stack.

Step 3: the choice of a production is safe. Say that a production pp of AA fits when αpX1⋯Xq⇒∗ai⋯an\alpha_p X_1 \cdots X_q \Rightarrow^* a_i \cdots a_n. Claim: if some production fits, ADAPTIVE-PREDICT returns a production that fits. Fix a fitting p∗p^* and a sequence of head steps from αp∗X1⋯Xq\alpha_{p^*} X_1 \cdots X_q that matches ai⋯ana_i \cdots a_n and ends with the empty stack. Let uu be the first position at which its local part, the part that comes from αp∗\alpha_{p^*}, is empty and settled; uu exists, at the latest at the end.

  • SLL, before uu. At every step t≤ut \le u the run has a pair (p∗,γt)(p^*, \gamma_t) in DD by Step 2, with γt≠ε\gamma_t \neq \varepsilon for t<ut < u, so the loop never reaches line L17 or L19 before uu. If it stops at line L14 at some t≤ut \le u, every pair has the alternative p∗p^*, which is returned. If it stops at line L16 at some tt, then the stacks of p∗p^* and of the returned alternative pp are the same nonempty strings γ\gamma, and since γt\gamma_t is one of them, the same head steps that finish the run of p∗p^* from γtX1⋯Xq\gamma_t X_1 \cdots X_q finish a run of pp: so pp fits. At step uu the pair (p∗,ε)(p^*, \varepsilon) is in DD, so the loop stops at line L14 with p∗p^* or calls LL-PREDICT at line L15.
  • LL. Now the run of p∗p^* gives, at every step t≤n+1t \le n + 1, a configuration of p∗p^* in CC, and at t=n+1t = n + 1 the configuration (p∗,ε,0)(p^*, \varepsilon, 0). So the loop never reports an error. If it stops at line L22, it returns p∗p^*. If it stops at line L23, the represented stacks of the returned pp include those of p∗p^*, and the run of p∗p^* continues from one of them, so pp fits. If it reaches line L24, CC holds (p,ε,0)(p, \varepsilon, 0) exactly for the productions pp whose head steps match all of ai⋯ana_i \cdots a_n and empty the stack, that is, the fitting ones; the smallest of them is returned.

Step 4: acceptance. Invariant J: at each test of line L2, with a1⋯ai−1a_1 \cdots a_{i-1} matched and the stack X1⋯XqX_1 \cdots X_q, the tree built so far is the tree of a leftmost derivation S⇒∗a1⋯ai−1X1⋯XqS \Rightarrow^* a_1 \cdots a_{i-1} X_1 \cdots X_q, with one empty slot for each stack entry; and if w∈L(G)w \in L(G), then X1⋯Xq⇒∗ai⋯anX_1 \cdots X_q \Rightarrow^* a_i \cdots a_n. Initially the stack is SS. A match keeps both parts. An expansion of X1X_1 by pp performs one leftmost derivation step, so the first part holds; for the second, if X1⋯Xq⇒∗ai⋯anX_1 \cdots X_q \Rightarrow^* a_i \cdots a_n, then some production of X1X_1 fits, and by Step 3 the production chosen fits too (when X1X_1 has a single production, it is the fitting one). So if w∈L(G)w \in L(G), line L6 is never reached (the top terminal is aia_i, by the second part and since a settled string that derives ai⋯ana_i \cdots a_n starts with aia_i), no prediction reports an error (Step 3), and when the stack is empty the second part says ai⋯an=εa_i \cdots a_n = \varepsilon: the parser accepts. Conversely, when it accepts, the stack is empty and i=n+1i = n + 1, so the first part gives a leftmost derivation S⇒∗wS \Rightarrow^* w whose tree has been returned, and w∈L(G)w \in L(G).

Step 5: termination. Every prediction loop advances tt by one at each round and stops at t=n+1t = n + 1 at the latest, and each CLOSURE call terminates (Step 1). In the main loop, each round matches a token or expands a nonterminal, and between two matches the expansions are head expansions of the stack, finitely many by Step 1. So the parser stops.

Step 6: errors. Say that a step of the parser inspects the tokens it compares, aia_i at line L5 and ai,…,ata_i, \ldots, a_t in the prediction loops, and the end of the input when it tests i=n+1i = n + 1 or t=n+1t = n + 1; let hh be the largest inspected position so far, with n+1n + 1 for the end. Invariant K: at each test of line L2, the stack X1⋯XqX_1 \cdots X_q derives a string that starts with ai⋯ah−1a_i \cdots a_{h-1} (empty when h≤ih \le i), and when the end has been inspected, it derives ai⋯ana_i \cdots a_n itself. A match keeps K. At a prediction that returns pp after inspecting up to the step tt, the set at step tt holds a configuration of pp (lines L14, L16, L22, L23) or, at line L24, (p,ε,0)(p, \varepsilon, 0); by Step 2, αpX2⋯Xq\alpha_p X_2 \cdots X_q derives, by productivity, a string that starts with ai⋯at−1a_i \cdots a_{t-1}, or ai⋯ana_i \cdots a_n exactly at line L24. If an earlier step had inspected up to h>th > t, then by K a run from the stack before the expansion matches ai⋯ah−1a_i \cdots a_{h-1}; it starts with some production of X1X_1, which is still alive at step tt, so it is pp when the loop stops at line L14 or L22, and at line L16 or L23 the same continuation works for pp, as in Step 3. So K holds after the expansion.

Now let the parser report an error at position ee. By K, e≥he \ge h before the failing step: if e<he < h, a run that matches past aea_e exists, so the top terminal is aea_e at line L5, and a prediction keeps a configuration alive past ee. The failing step inspects ee, and a1⋯ae−1a_1 \cdots a_{e-1} is a prefix of a sentence: the stack, or a configuration alive at step ee, derives a string that starts with ai⋯ae−1a_i \cdots a_{e-1}, and with the first part of J and productivity this extends to a sentence. Suppose that e≤ne \le n and some sentence w′w' starts with a1⋯aea_1 \cdots a_e. Every step of the parser up to the failing one inspects only positions up to ee, where ww and w′w' agree (the end of the input is never inspected, since e≤ne \le n and e≥he \ge h), and the DFA cache returns the same sets for the same arguments. So the parser on w′w' makes the same moves and reports the same error, which contradicts Step 4 for w′∈L(G)w' \in L(G). Hence a1⋯aea_1 \cdots a_e is not a prefix of a sentence. ■\blacksquare

Back to LL(*) parsing