Proofs: Parsing with derivatives
These are the human proofs of Parsing with derivatives, at full length.
Memoized derivatives of a cyclic grammar graph denote the Brzozowski derivatives, so the recognizer accepts exactly L(G), and every call stops full
This proof shows: correctness, termination.
We use the notation of the context-free grammar page: productions , for the empty string, for the start symbol, for the input length and for the grammar size. The line numbers are those of RECOGNIZE, and NULLABLE on the page. For a language and a token , the Brzozowski derivative is , and .
The meaning of a graph
A node is , , a token , a union or a concatenation , where and are nodes; the graph may have cycles. The language of every node is defined by the least sets that satisfy
Equivalently, when there is a finite membership proof: a tree whose root is the claim , where a claim about has one child, or , a claim about has two children and with , and the leaves are and . The size of a claim is the number of nodes of its smallest proof.
Lemma 0 (the grammar graph). For the graph built from , with one union node per nonterminal (a chain of unions over its productions, ended by ) and one right-nested chain of concatenations per production, for every nonterminal .
Proof. A membership proof for picks one production of in the union chain and splits along its right side, and each piece is again a membership proof for a symbol. Contracting the chains turns it into a parse tree of with root , and expanding a parse tree gives a membership proof.
Lemma 1: compaction
COMPACT (line L3) returns a node with the same language as , and if has size , then has size at most .
Proof. One step replaces or by , by , and returns for or . Each is an identity of languages, and in the first two cases a proof for is a proof for with one or two extra nodes on top, so sizes do not grow. If the steps come back to a node already seen, the nodes on the loop satisfy through equations with no other contribution, so a membership proof for any of them would contain a proof for the next one, then the next, without end; there is none, all of them denote , and returning is correct.
Lemma 2: nullability
After NULLABLE returns, every node reachable from is decided, and a node is marked nullable exactly when .
Proof. We have iff or , and iff both, since forces ; tokens and never contain . So the nullable nodes are the least solution of these Boolean equations. Assume, by induction over the calls, that the nodes decided before the call are decided correctly. Line L11 collects the undecided nodes reachable from ; all their children are collected or decided. For each collected node, the counter holds the number of collected children that must still be marked: 0 when the node is or a union with a child already known nullable, 1 for another union, and the number of undecided children for a concatenation whose children are not known to be non-nullable. Line L12 marks the nodes whose counter is 0 and propagates: a newly marked node decrements the counters of its users, and a counter reaching 0 marks its node. All counters are computed before any node is marked, so each child is counted once.
Soundness: by induction on the order of marking, every marked node has in its language, since its counter reached 0 only after the required children were marked or known. Completeness: if for a collected node, induction on the size of the claim shows that the children used by its smallest proof are marked or were known nullable before the call, so the counter of reaches 0. Line L13 then sets the rest to non-nullable, which is correct. Each collected node and each edge is handled times.
Lemma 3: one derivative
Let be called on a root during one token step. When it returns, every node returned by a call (line L4, L5, L6 or L10), in this step or an earlier step with the same token, satisfies .
Proof. The nodes created in this step are the memo nodes , one for each compacted union or concatenation node reached in this step (plus the inner concatenation node of line L9). Write for the value returned by : for and , or for a token, and otherwise the memo node of . When the stack is empty, every memo node has its children (lines L7 to L9):
- for ;
- for with not nullable;
- for with nullable.
The nodes , , are old: they were reachable from the root, and every node reachable from the root had its children before the step began, so NULLABLE is decided on a complete graph and is correct by Lemma 2. Memo nodes from earlier steps with the same token are correct by induction on the steps.
: the family satisfies the equations of the memo nodes, because , when , when , , and is for and otherwise; and by Lemma 1. The languages of the memo nodes are the least solution, so .
: we show that implies , by induction on the size of the claim . By Lemma 1 we may replace by without increasing the size. If is a token, then , and . If , then with a smaller claim, so . If , then with and . If , then by induction, so . If , then is nullable, has the branch , and with a smaller claim, so .
Theorem
RECOGNIZE stops on every input and returns true exactly when .
Proof. By Lemma 0 the initial node denotes . By Lemma 3 and induction on , after iterations of line L1 the current node denotes . So the final node denotes , which contains exactly when , and line L2 returns this by Lemma 2.
Termination: COMPACT stops because it never visits a node twice. One call of on a root pushes one stack entry per new memo key; the keys are pairs (old node, ), and there are finitely many old nodes, so the loop stops. NULLABLE handles each collected node and edge a constant number of times.
Remarks
The running time. Adams, Hollenbeck and Might name every node created during parsing by the node it was derived from and the tokens it was derived by, and show that the names have the form or with a node of the initial grammar and , substrings of the input. There are such names, so nodes are created; each creation costs apart from nullability, which is linear in the number of nodes in total by the counter method of Lemma 2. They note that the count holds with compaction too, since compaction only removes nodes.
Why memoization matters. Without the memo table, the rule for with nullable uses twice, once inside and once as , and repeated derivatives copy the copies; even an acyclic grammar can then make the graph grow exponentially, and a cyclic one makes the computation loop. The memo table both shares the copies and, because it stores before its children are computed, closes the cycles.