Proofs: Shift-reduce parsing
These are the human proofs of Shift-reduce parsing, at full length.
The shift-reduce driver builds a reverse rightmost derivation, and the exact decision procedure parses every grammar full
This proof shows: correctness, termination.
We use the pseudocode SHIFT-REDUCE of the page and the notation of the context-free grammar page. The input is . A configuration is the stack, a sequence of trees with roots , together with pos. A legal move is a shift (if ) or a reduction by a production whose right side is the sequence of the top roots. The augmented grammar adds a new start symbol and the production .
Claim 1: whatever DECIDE does, an accepted tree is right
Invariant. After every move, (i) the leaves of , left to right, are ; (ii) each is a parse tree with root ; (iii) if are the productions of the reductions so far, then applying to , each to the rightmost nonterminal, gives a rightmost derivation .
Proof. At the start the stack is empty, pos is 1, and the derivation of from has no step. A shift appends the leaf and increases pos, which changes neither the string nor the derivation. A reduction by replaces the top trees, whose roots spell , by a node over them, which keeps (i) and (ii). The string becomes , where is the rest of the stack, and rewrites , which is the rightmost nonterminal because everything after it is terminal. So this step, followed by the old derivation, is a rightmost derivation, which is (iii).
When the driver accepts, the stack is the single tree with root and pos is , so by (i) and (ii) it is a parse tree of , and by (iii) the reductions read backwards are a rightmost derivation .
Goodness and item chains
A configuration is good if some sequence of legal moves leads from it to the stack with pos . Legal moves depend only on the roots, so goodness depends only on and pos.
Lemma 2. The configuration with roots at position pos is good if and only if there are productions , , ..., and of the augmented grammar such that and .
Proof. (Only if.) Take a sequence of moves that ends in acceptance, and let be the final tree, with a root added above . The current trees are subtrees of , because later moves only put new nodes above them; call them old, and call the nodes made by later reductions new. A new node is pushed on top of the stack, to the right of every old tree still there, so in the order of children, old trees always come before new nodes that contain no old tree. Hence the children of a new node are: some old trees, then at most one new node that contains old trees, then new nodes and future leaves that contain no old tree. Start at the root and walk down: at a new node with production , are the roots of its old children, is the child that contains old trees, and are the rest; stop at the first node that has no child containing old trees, with production . The 's are all the old trees, in order, so their concatenation is . The trees of the 's cover the leaves , from the deepest node up, so . (If the stack is already at , take , , .)
(If.) Split into parts derived by the symbols of and choose a parse tree for each part. Build from the bottom: a node over the old trees of and the trees of , then a node over the old trees of , the node and the trees of , and so on up to . Then is a parse tree of that contains , and its new nodes are the nodes on the chain and the nodes of the trees. Do the moves of the postorder of the new nodes and future leaves: shift each future leaf when it comes, and reduce each new node when it comes. By induction along the postorder, when a node comes, all its children are finished and are the top entries of the stack in order, since everything later in the postorder is to their right. So each move is legal, and the last one makes the node over everything, at pos .
The implementation tests the condition of Lemma 2 by running the items of the chains over . It keeps pairs (item , ), where is the position where the continuations of the ancestors, , must start to derive . Reading a symbol moves the dot over ; at an item with end , the pairs are added for every with . The configuration is good when some pair after has . The relation on substrings is computed once by a least fixpoint.
Claim 2: the exact procedure accepts exactly and stops
Lemma 3. From a good configuration that is not an acceptance, the procedure returns a legal move to a good configuration, and the number of moves before the next shift or acceptance is finite.
Proof. Fix an accepting continuation from the current configuration . Every is good. Let be the configuration just before its first shift, or its final acceptance; the moves are good reductions, and is a target: shifting from it is good, or it is the acceptance. If is a target the procedure shifts or accepts. Otherwise the breadth-first search over good reductions from explores level by level, each stack has at most reductions, and a target lies at depth at most ; so the search stops, and returns the first move of a shortest path to a target, which leads to a good configuration. Let be the length of a shortest path of good reductions from to a target. The move goes along a shortest path, so decreases by one at each reduction. When the procedure shifts or accepts.
Theorem. With the exact procedure, the driver accepts if and only if , and it always stops.
Proof. The start configuration, empty stack at pos 1, is good exactly when : by Lemma 2 with , the condition reads . If it is not good, the procedure reports an error at once. If it is good, by Lemma 3 every move keeps the configuration good, there are at most shifts, and between two shifts there are finitely many reductions, so the run reaches the acceptance; by Claim 1 the returned tree is a parse tree of .