Algorithm Search⌘K

Proofs: Operator-precedence parsing

These are the human proofs of Operator-precedence parsing, at full length.

OP-PARSE accepts exactly the sentences of an operator precedence grammar and stops after O(n) steps full

This proof shows: correctness.

Statement. Let GG be an operator grammar, without the productions that use a nonterminal deriving no terminal string, such that at most one of ⋖\lessdot, ≐\doteq, ⋗\gtrdot holds between any two symbols of Σ∪{$}\Sigma \cup \{\$\}, where the end markers are treated as the right side $S$\$ S \$. Then for every input w=a1⋯anw = a_1 \cdots a_n, OP-PARSE stops after at most 2n+22n + 2 iterations of its loop, and it accepts if and only if w∈L(G)w \in L(G); when it accepts, it returns a parse tree of ww.

Proof.

Step 1 (termination). Terminals enter the stack only by the shift at L4, which also advances the input, so there are at most nn shifts before the current token is $\$, and $\$ is never shifted, because $≐$\$ \doteq \$ is not a relation and no $⋖$\$ \lessdot \$ or $⋗$\$ \gtrdot \$ can be derived from right sides that do not contain $\$. Each reduction removes at least one terminal from the stack: L6 pops at least one. So there are at most nn reductions, and with the final step at L3 or L9 the loop runs at most 2n+22n + 2 times. The pop at L6 never removes the bottom $\$: a terminal xx is pushed above the topmost terminal yy only if y⋖xy \lessdot x or y≐xy \doteq x, and the first terminal above $\$ was pushed with $⋖x\$ \lessdot x, since $≐x\$ \doteq x would need a right side with $\$ and a terminal xx next to it, and $S$\$ S \$ has none.

Step 2 (soundness). We claim that every nonterminal entry on the stack is a map from a set NN of nonterminals to trees, where the tree for A∈NA \in N is a parse tree with root AA whose yield is the part of the input covered by the entry, and that the entries and terminals on the stack cover the shifted input from left to right. A shift adds a terminal. A reduction at L8 builds, for a production A→X1⋯XmA \to X_1 \cdots X_m that matches the handle, the node AA whose children are the handle's terminals and, for each nonterminal XiX_i, the tree for XiX_i of the corresponding entry; this is a parse tree with yield the covered input. A unit production B→AB \to A adds the node BB above the tree for AA. If OP-PARSE accepts at L3, the whole input has been shifted, the stack is $ N\$ \, N with S∈NS \in N, and the tree for SS is a parse tree of ww. So acceptance implies w∈L(G)w \in L(G).

Step 3 (contracted trees). Now let w∈L(G)w \in L(G) with a parse tree tt. A unit chain of tt is a maximal path of nodes B1,B2,…,BrB_1, B_2, \ldots, B_r in which each BiB_i has the single nonterminal child Bi+1B_{i+1} for i<ri < r, by the production Bi→Bi+1B_i \to B_{i+1}; its bottom BrB_r uses a production that is not a unit production. Contract each unit chain to one node, labeled with the chain. Every node of the contracted tree t^\hat t uses a production whose right side has a terminal: a right side that is not a unit production and not empty either is one terminal or has length at least 2 with no two adjacent nonterminals.

Process the nodes of t^\hat t in postorder, the children from left to right. After the first kk nodes have been processed, the cut is the string obtained from ww by replacing the yield of each maximal processed subtree by its root. Let hh be the (k+1)(k+1)-th node. All its children are leaves or processed, so its children are a substring of the cut, of the form [X0] c1[X1]⋯cm[Xm][X_0] \, c_1 [X_1] \cdots c_m [X_m] with terminals c1,…,cmc_1, \ldots, c_m, m≥1m \ge 1, and optional processed nodes XiX_i. Everything to the right of hh in the cut is an unprocessed leaf, a terminal. Write the cut as $ π [X0]c1⋯cm[Xm] ρ $\$ \, \pi \, [X_0] c_1 \cdots c_m [X_m] \, \rho \, \$.

Step 4 (the relations around hh). Let ℓ\ell be the last terminal of $π\$ \pi and rr the first symbol of ρ$\rho \$, a terminal. Treat the two end markers as children of a virtual root above SS with the right side $S$\$ S \$.

(a) ci≐ci+1c_i \doteq c_{i+1}, since both are children of hh with at most one nonterminal between them.

(b) ℓ⋖c1\ell \lessdot c_1. The parent pp of the leaf ℓ\ell is not processed, since hh comes before it in postorder (hh is not in the subtree of pp to the left of ℓ\ell, and if hh were in the subtree of a child of pp to the left of ℓ\ell, its yield would be to the left of ℓ\ell). If p=hp = h, then ℓ\ell would be a terminal child of hh to the left of c1c_1, which is impossible. So the subtree of hh is to the right of ℓ\ell. Let YY be the child of pp right after ℓ\ell. If YY is a terminal, it is the symbol right after ℓ\ell in the cut, which is X0X_0 or c1c_1; X0X_0 is not a terminal, and Y=c1Y = c_1 would make p=hp = h. So YY is a nonterminal, and the subtree of YY starts right after ℓ\ell. If that subtree contains c1c_1, then Y⇒+[X0]c1⋯Y \Rightarrow^+ [X_0] c_1 \cdots with X0X_0 a single nonterminal, so c1∈LEADING(Y)c_1 \in \mathrm{LEADING}(Y) and ℓ⋖c1\ell \lessdot c_1, using the unit chains and labels of tt. Otherwise the subtree of YY is exactly X0X_0, which is processed; then the next child of pp after YY, if any, is a terminal adjacent to X0X_0 in the cut, that is, c1c_1, which again gives p=hp = h; and if YY is the last child of pp, all children of pp are processed or leaves to the left of hh's yield, so pp comes before hh in postorder, a contradiction.

(c) cm⋗rc_m \gtrdot r. The parent qq of rr is not processed, because its leaf rr is not. If q=hq = h, rr would be a child of hh after cmc_m, which is impossible. The child ZZ of qq right before rr is not the terminal cmc_m (else q=hq = h), and not a terminal at all, since the symbol right before rr in the cut is cmc_m or XmX_m. So ZZ is a nonterminal whose subtree ends right before rr and contains cmc_m, since a subtree that is only XmX_m would leave cmc_m before it as a child of qq, again q=hq = h. Thus Z⇒+⋯cm[Xm]Z \Rightarrow^+ \cdots c_m [X_m], so cm∈TRAILING(Z)c_m \in \mathrm{TRAILING}(Z) and cm⋗rc_m \gtrdot r.

(d) For two consecutive terminals x,yx, y of $π c1\$ \pi \, c_1 (yy right after xx, possibly with one nonterminal between them), x⋖yx \lessdot y or x≐yx \doteq y. If y=c1y = c_1 this is (b). Otherwise the parent pp of xx is unprocessed, by the argument of (b), and the child YY of pp after xx is either the terminal yy, giving x≐yx \doteq y, or a nonterminal followed in pp by yy, giving x≐yx \doteq y, or a nonterminal whose subtree contains yy, giving y∈LEADING(Y)y \in \mathrm{LEADING}(Y) and x⋖yx \lessdot y. The remaining cases, where YY is processed and is the last child of pp, or xx is the last child of pp, make pp processed before hh, as in (b).

Since at most one relation holds between any two terminals, (a) to (d) determine the relation of every pair used below, and in particular ci⋖ci+1c_i \lessdot c_{i+1} and ℓ≐c1\ell \doteq c_1 do not hold.

Step 5 (the parser follows t^\hat t). We show by induction on kk: when the parser has made all moves up to the kk-th reduction, its stack is $π\$ \pi with the same terminals, nonterminal entries at the places of the processed nodes, the input pointer is at the first unprocessed leaf, and the entry of each processed node with unit chain B1,…,BrB_1, \ldots, B_r contains B1B_1, the label its parent's production uses.

Before the reduction of hh, the parser is in the configuration where the stack is $π\$ \pi and the input is […]c1⋯[\ldots] c_1 \cdots. As long as the topmost stack terminal is xx and the next token yy is c1c_1 or a terminal of hh, (b), (a) and (d) give ⋖\lessdot or ≐\doteq, so L4 shifts. When the stack holds …ℓ[X0]c1[X1]⋯cm[Xm]\ldots \ell [X_0] c_1 [X_1] \cdots c_m [X_m] and the next token is rr, (c) gives ⋗\gtrdot, so L5 reduces. The pops at L6 remove cm,…,c1c_m, \ldots, c_1: after popping ci+1c_{i+1} the topmost terminal is cic_i and ci≐ci+1c_i \doteq c_{i+1} is not ⋖\lessdot, and after popping c1c_1 the topmost terminal is ℓ\ell with ℓ⋖c1\ell \lessdot c_1. So the handle is exactly the children of hh. The production of the bottom of hh's unit chain matches it, because each child entry contains the label used in that production, so it is in NN, and closing under unit productions adds the whole chain up to its top. This is the claim for k+1k + 1.

After the root has been processed, the stack is $ N\$ \, N with the root chain in NN, whose top is SS, and the input is at $\$. L3 accepts. Since the moves before were all shifts and reductions with nonempty NN, no error was reported earlier. So w∈L(G)w \in L(G) implies acceptance. ■\blacksquare

Back to Operator-precedence parsing