Proofs: Recursive ascent parsing
These are the human proofs of Recursive ascent parsing, at full length.
The recursive ascent parser makes exactly the moves of the table-driven LR parser, so it accepts L(G), builds the right tree, and stops full
This proof shows: correctness, termination.
We use the notation of the context-free grammar page and the pseudocode of the page: the driver RECURSIVE-ASCENT-PARSE (lines L1 and L2) and the procedures STATE (lines L3 to L13). The grammar is SLR(1), so its ACTION table has at most one action per entry, and GOTO is the goto table of its LR(0) automaton. The reference is the table-driven parser SLR-PARSE of SLR parsing, which uses the same two tables.
Configurations
A configuration of the table-driven parser is a stack of states with , a list of trees (the tree belongs to the symbol on the transition into ), and an input position . A move is a shift, a reduction followed by a goto, an accept, or an error.
At any moment of the recursive ascent parser, the active procedure calls form a chain: STATE called by the driver, STATE called by it, and so on up to the running call STATE. Call received the argument (for , nothing). Every call except the last is suspended at one of the two call sites, line L4 or line L13, and when it resumes it continues at line L10. We say that the recursive ascent parser is in configuration when this is the chain, the input position is , and the call STATE is about to execute line L3.
Lemma 1: one move
Suppose the recursive ascent parser is in configuration , and let be the token at position .
- If ACTION = shift , then it next reaches the configuration .
- If ACTION = reduce with , then , and it next reaches the configuration with and the node with the children .
- If ACTION = accept, then every call returns ACCEPT and the driver returns .
- If the entry is empty, line L9 reports an error at position .
These are exactly the next configurations of the table-driven parser.
Proof. Case 1: line L4 advances the input and calls STATE with the leaf ; the new call is about to execute line L3. Case 4 is line L9.
Case 2 with : line L7 sets = REDUCE, and the loop of line L10 starts in the call . Line L11 does nothing, and since , line L13 calls STATE with the node over no children, which is .
Case 2 with : we show by induction on that the call resumes at line L10 with = REDUCE. For : line L6 of call returns REDUCE, and its caller was suspended at a call site, so it resumes at line L10 with this value. The call exists because . For : in call , , so line L12 returns REDUCE, and call resumes with it. For , the call has and the children in order, and line L13 calls STATE with . The calls have returned, so the chain is , and the new call is about to execute line L3.
Case 3: line L8 returns ACCEPT. Each suspended call resumes at line L10, and line L11 returns the same value, so the value reaches the driver, and line L2 returns .
The bound holds because the table-driven parser in the same configuration pops states above : by Lemma 2 of the SLR proof, its stack symbols form a viable prefix whose last symbols are .
Theorem
On every input , the recursive ascent parser makes the same sequence of moves as the table-driven SLR(1) parser with the same tables. So it stops, it accepts exactly the sentences of , on acceptance it returns the same parse tree and the same sequence of reductions (a reverse rightmost derivation of ), and on rejection it reports the error at the same position.
Proof. Initially, line L1 calls STATE with nothing, and the input position is 0: the recursive ascent parser is in configuration , the initial configuration of the table-driven parser. By Lemma 1 and induction on the number of moves, after each move both parsers are in the same configuration, and both make the same next move. The reductions are recorded at lines L6 and L7, at the same moves as at line L7 of SLR-PARSE.
The table-driven parser accepts exactly , with a correct tree, and stops after moves for a fixed grammar; these are Theorems 4 and 7 of the SLR parsing correctness proof. Each move of the recursive ascent parser costs calls and returns, plus returns for a reduction by a production with right-side symbols, the same as the pops of SLR-PARSE. So it stops after steps too.
Remarks
Other tables. The proof uses only that ACTION has at most one action per entry and that reductions find their right side on the stack. So the same procedures work with LR(0), LALR(1) or canonical LR(1) tables.
The depth of the calls. The chain of active calls is the LR stack, so its length is at most the largest stack height, which is for a fixed grammar and can reach (for right recursion or nested parentheses). The implementation keeps the chain in a list of Python generators instead of on the machine stack, which changes nothing in the argument above: a yield is a call, and a return is a return.