Proofs: One-pass regex matching
These are the human proofs of One-pass regex matching, at full length.
The one-pass matcher is correct, and its compile test is exact full
This proof shows: correctness, termination.
Let have positions and marked expression . Write for the map that replaces each position by , extended to sequences. A direct induction on shows , since marking and commute with union, concatenation and star. The sets Nullable, First, Last and Follow are computed by the usual rules: for they are unions; for , if is nullable, if is nullable, and each gets added to its Follow set; for , each gets added. A sequence with is a chain of when , and for each .
Lemma 1 (Glushkov). if and only if is nullable, and a nonempty sequence is in if and only if it is a chain of .
Proof. By induction on . The nullable part is the usual induction, so we treat nonempty sequences. For and there are no positions and no nonempty sequences in , and First is empty, so there are no chains. For a single position , , and since and , the only chain is .
For , the positions of and of are disjoint, and Follow never links them. So a chain of lies in or in and is then a chain of that part, and conversely. The claim follows from the induction hypothesis.
For , Follow adds only edges from to , and no edge leads from a position of to one of . Take a sequence with and , not both empty. If both are nonempty, and are chains of and , the last element of is in and the first of in , so is a chain of . If , then is nullable, so and the chain of is a chain of . The case is symmetric. Conversely, take a chain of and cut it before its first position of , as . Every step inside is in and every step inside is in . If is empty, then starts in at a position of , which needs nullable, so , and is a chain of . If is empty, then ends in at a position of , which needs nullable, and is a chain of . If both are nonempty, the step from to is an added edge, so ends in and starts in , and both are chains. In each case .
For , First and Last are those of , and Follow adds edges from to . A nonempty sequence of is with each a nonempty sequence of , so a chain of , and joining them with added edges gives a chain of . Conversely, cut a chain of at each step with . Such a step is an added edge, so and . Each piece starts in , ends in and steps inside , so it is a chain of and is in .
Let be the graph on with an edge for each , where , and let be the set of line L2. By Lemma 1, the sequences of are exactly the sequences such that is a path of ending in (for , the path is alone and exactly when is nullable). Call such a path accepting. The set of line L3 is the set of nodes that lie on some accepting path, apart from , which may or may not be in .
Lemma 2 (the table). If ONE-PASS-COMPILE returns , then for each and symbol , is defined exactly when some has , and then is that , which is unique.
Proof. Lines L5 to L8 insert each under the key , and line L7 returns before a second with the same symbol is inserted. So, when the compile ends at line L9, each key has exactly one such , and every such is a key.
Lemma 3 (one-pass runs follow accepting paths). Suppose the compile returns . Let and let be an accepting path with . Then the matcher, on , takes after reading for each , and accepts with .
Proof. Every lies on an accepting path, so . By induction on , with : before round the matcher has . Since and , Lemma 2 gives , so line L12 does not reject and line L13 sets and appends it to . After the loop (or when ), so line L14 accepts with .
Theorem 1 (the matcher). If the compile returns , then ONE-PASS-MATCH accepts if and only if . When it accepts, its list is in with , and it is the only such sequence.
Proof. If , then for some , and gives an accepting path with the symbols of , so the matcher accepts by Lemma 3. Conversely, if the matcher accepts with , then each , so by Lemma 2 and , and line L14 gives . So is an accepting path, , and . For uniqueness, any with gives an accepting path, and by Lemma 3 the matcher's list equals , so .
Theorem 2 (the test is exact). The compile returns NOT-ONE-PASS if and only if is not one-unambiguous, that is, if and only if there are and in with and .
Proof. Suppose line L7 fires at a node for two positions in with the same symbol. Because and are in , they are reachable from , and so , which has an edge to them, is reachable from too (if , it is in ). Take a path from to with the sequence of positions . Since is in , there is a path from to a node of with sequence , and in the same way a path from to with sequence . Then and are accepting paths, so and are in , which breaks one-unambiguity.
Conversely, suppose and are in with and . Their accepting paths share the prefix for , which ends at a node (the last element of , or if is empty). All nodes of both paths after lie on accepting paths, so they are in ; in particular and . When the loop of line L4 reaches , if it reaches it, line L6 meets both and with the same symbol, and line L7 returns NOT-ONE-PASS. If the loop never reaches , it has already returned NOT-ONE-PASS at an earlier node. Either way the compile reports NOT-ONE-PASS.
Termination. The Glushkov sets are computed by one walk over the nodes of . Line L3 runs two graph searches over the finite graph , each adding a node at most once. The loop at lines L4 to L8 runs over at most nodes and their finite Follow sets. The matcher loop runs once for each of the symbols of , with one table lookup each. So both procedures stop.
Conclusion. By Theorem 2, the compile succeeds exactly on one-unambiguous expressions, and by Theorem 1 the matcher then decides and returns the unique sequence of positions that spells .