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 be an operator grammar, without the productions that use a nonterminal deriving no terminal string, such that at most one of , , holds between any two symbols of , where the end markers are treated as the right side . Then for every input , OP-PARSE stops after at most iterations of its loop, and it accepts if and only if ; when it accepts, it returns a parse tree of .
Proof.
Step 1 (termination). Terminals enter the stack only by the shift at L4, which also advances the input, so there are at most shifts before the current token is , and is never shifted, because is not a relation and no or 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 reductions, and with the final step at L3 or L9 the loop runs at most times. The pop at L6 never removes the bottom : a terminal is pushed above the topmost terminal only if or , and the first terminal above was pushed with , since would need a right side with and a terminal next to it, and has none.
Step 2 (soundness). We claim that every nonterminal entry on the stack is a map from a set of nonterminals to trees, where the tree for is a parse tree with root 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 that matches the handle, the node whose children are the handle's terminals and, for each nonterminal , the tree for of the corresponding entry; this is a parse tree with yield the covered input. A unit production adds the node above the tree for . If OP-PARSE accepts at L3, the whole input has been shifted, the stack is with , and the tree for is a parse tree of . So acceptance implies .
Step 3 (contracted trees). Now let with a parse tree . A unit chain of is a maximal path of nodes in which each has the single nonterminal child for , by the production ; its bottom 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 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 in postorder, the children from left to right. After the first nodes have been processed, the cut is the string obtained from by replacing the yield of each maximal processed subtree by its root. Let be the -th node. All its children are leaves or processed, so its children are a substring of the cut, of the form with terminals , , and optional processed nodes . Everything to the right of in the cut is an unprocessed leaf, a terminal. Write the cut as .
Step 4 (the relations around ). Let be the last terminal of and the first symbol of , a terminal. Treat the two end markers as children of a virtual root above with the right side .
(a) , since both are children of with at most one nonterminal between them.
(b) . The parent of the leaf is not processed, since comes before it in postorder ( is not in the subtree of to the left of , and if were in the subtree of a child of to the left of , its yield would be to the left of ). If , then would be a terminal child of to the left of , which is impossible. So the subtree of is to the right of . Let be the child of right after . If is a terminal, it is the symbol right after in the cut, which is or ; is not a terminal, and would make . So is a nonterminal, and the subtree of starts right after . If that subtree contains , then with a single nonterminal, so and , using the unit chains and labels of . Otherwise the subtree of is exactly , which is processed; then the next child of after , if any, is a terminal adjacent to in the cut, that is, , which again gives ; and if is the last child of , all children of are processed or leaves to the left of 's yield, so comes before in postorder, a contradiction.
(c) . The parent of is not processed, because its leaf is not. If , would be a child of after , which is impossible. The child of right before is not the terminal (else ), and not a terminal at all, since the symbol right before in the cut is or . So is a nonterminal whose subtree ends right before and contains , since a subtree that is only would leave before it as a child of , again . Thus , so and .
(d) For two consecutive terminals of ( right after , possibly with one nonterminal between them), or . If this is (b). Otherwise the parent of is unprocessed, by the argument of (b), and the child of after is either the terminal , giving , or a nonterminal followed in by , giving , or a nonterminal whose subtree contains , giving and . The remaining cases, where is processed and is the last child of , or is the last child of , make processed before , 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 and do not hold.
Step 5 (the parser follows ). We show by induction on : when the parser has made all moves up to the -th reduction, its stack is 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 contains , the label its parent's production uses.
Before the reduction of , the parser is in the configuration where the stack is and the input is . As long as the topmost stack terminal is and the next token is or a terminal of , (b), (a) and (d) give or , so L4 shifts. When the stack holds and the next token is , (c) gives , so L5 reduces. The pops at L6 remove : after popping the topmost terminal is and is not , and after popping the topmost terminal is with . So the handle is exactly the children of . The production of the bottom of 's unit chain matches it, because each child entry contains the label used in that production, so it is in , and closing under unit productions adds the whole chain up to its top. This is the claim for .
After the root has been processed, the stack is with the root chain in , whose top is , and the input is at . L3 accepts. Since the moves before were all shifts and reductions with nonempty , no error was reported earlier. So implies acceptance.