Algorithm Search⌘K

Proofs: Backtracking regex matching

These are the human proofs of Backtracking regex matching, at full length.

Backtracking regex matching stops and returns true exactly for the strings of the expression full

This proof shows: correctness, termination.

Let rr be a regular expression with mm nodes and let w=w1⋯wnw = w_1 \cdots w_n. For 0≤i≤i′≤n0 \le i \le i' \le n, write w[i:i′]=wi+1⋯wi′w[i:i'] = w_{i+1} \cdots w_{i'}. The size ∣x∣|x| of an expression is the number of nodes of its syntax tree, and ∣CHECK(s∗,j)∣=0|\mathrm{CHECK}(s^*, j)| = 0.

The tree of configurations. For a configuration c=(i,k)c = (i, k), its children are the configurations that lines L9 to L14 push when cc is popped. There are at most two. The configuration (i,[ ])(i, [\,]) has no children, nor has (i,∅::k′)(i, \emptyset :: k'), a symbol that does not fit, or a marker CHECK(s∗,j)\mathrm{CHECK}(s^*, j) with i≤ji \le j. A path is a sequence of configurations in which each one is a child of the one before. The configurations reachable from (0,[r])(0, [r]) form a tree TT (a configuration reachable on two paths appears twice). A configuration is successful if a path leads from it to (n,[ ])(n, [\,]).

Lemma 1 (search). If TT is finite, BACKTRACK returns true if and only if (0,[r])(0, [r]) is successful.

Proof. Line L1 pushes the root. Each pop at line L3 removes one node of TT from KK and pushes its children. By induction on the number of pops, KK always holds nodes of TT that have not been popped, and every node of TT that has not been popped is a descendant of a node in KK. Each node is pushed once, as a child of its parent, so if TT is finite the loop ends after at most ∣T∣|T| pops. The algorithm returns true only at line L5, when it pops (n,[ ])(n, [\,]), which is a node of TT; then the root is successful. Conversely, if the root is successful, (n,[ ])(n, [\,]) is a node of TT. While it has not been popped it is a descendant of a node in KK, so KK is not empty and the loop goes on; since the loop ends, it ends at line L5. □\square

Lemma 2 (termination). TT is finite.

Proof. Each node has at most two children, so by König's lemma it suffices to show that no path is infinite.

Now take an infinite path. Its positions never decrease and are at most nn, so from some configuration c0c_0 on they are constant, equal to ii. Every marker made after c0c_0 has j=ij = i and fails at line L14. The list of c0c_0 is finite, so it has finitely many markers with j<ij < i, and each step of line L14 that pushes a child removes one of them from the head of the list. So after some configuration c1c_1 of the path, line L14 never pushes a child. From c1c_1 on, each step is one of lines L9, L11, L12 or L13, and each lowers ∑x∈k∣x∣\sum_{x \in k} |x| by at least 1: line L9 removes ε\varepsilon, line L11 replaces 1+∣s∣+∣t∣1 + |s| + |t| by ∣s∣|s| or ∣t∣|t|, line L12 replaces 1+∣s∣+∣t∣1 + |s| + |t| by ∣s∣+∣t∣|s| + |t|, and line L13 replaces 1+∣s∣1 + |s| by ∣s∣+0|s| + 0. This sum is a natural number, so the path is finite, which is a contradiction. □\square

Lemma 3 (soundness). For an item xx, let L(x)L(x) be the language of xx if xx is an expression, and L(CHECK(s∗,j))=L(s∗)L(\mathrm{CHECK}(s^*, j)) = L(s^*). For a list, L([ ])={ε}L([\,]) = \{\varepsilon\} and L(x::k)=L(x)L(k)L(x :: k) = L(x) L(k). If (i,k)(i, k) is successful, then w[i:n]∈L(k)w[i:n] \in L(k).

Proof. By induction on the length of a path from (i,k)(i, k) to (n,[ ])(n, [\,]). For length 0, (i,k)=(n,[ ])(i, k) = (n, [\,]) and w[n:n]=ε∈L([ ])w[n:n] = \varepsilon \in L([\,]). Otherwise let (i′,k′)(i', k') be the next configuration, with w[i′:n]∈L(k′)w[i':n] \in L(k') by induction, and let xx be the head of kk and k′′k'' its tail.

  • L9: x=εx = \varepsilon, i′=ii' = i, k′=k′′k' = k'', and L(k′′)=L(ε)L(k′′)L(k'') = L(\varepsilon) L(k'').
  • L10: x=a=wi+1x = a = w_{i+1}, i′=i+1i' = i + 1, k′=k′′k' = k'', so w[i:n]=a⋅w[i+1:n]∈L(a)L(k′′)w[i:n] = a \cdot w[i+1:n] \in L(a) L(k'').
  • L11: k′=s::k′′k' = s :: k'' or t::k′′t :: k'', and L(s),L(t)⊆L(s∣t)L(s), L(t) \subseteq L(s \mid t).
  • L12: L(s::t::k′′)=L(st)L(k′′)L(s :: t :: k'') = L(st) L(k'').
  • L13: k′=k′′k' = k'', and L(k′′)⊆L(s∗)L(k′′)L(k'') \subseteq L(s^*) L(k'') since ε∈L(s∗)\varepsilon \in L(s^*); or k′=s::CHECK(s∗,i)::k′′k' = s :: \mathrm{CHECK}(s^*, i) :: k'', and L(s)L(s∗)L(k′′)⊆L(s∗)L(k′′)L(s) L(s^*) L(k'') \subseteq L(s^*) L(k'').
  • L14: k′=s∗::k′′k' = s^* :: k'' and L(k′)=L(CHECK(s∗,j))L(k′′)=L(k)L(k') = L(\mathrm{CHECK}(s^*, j)) L(k'') = L(k).

In each case L(k′)⊆L(k)L(k') \subseteq L(k) with i′=ii' = i, or the step reads wi+1w_{i+1} as shown, so w[i:n]∈L(k)w[i:n] \in L(k). □\square

Lemma 4 (completeness). Let xx be an expression and kk a list. If w[i:i′]∈L(x)w[i:i'] \in L(x) and (i′,k)(i', k) is successful, then (i,x::k)(i, x :: k) is successful.

Proof. By induction on the pair (i′−i,∣x∣)(i' - i, |x|) in lexicographic order.

  • x=∅x = \emptyset: L(x)L(x) is empty, so there is nothing to prove.
  • x=εx = \varepsilon: then i′=ii' = i, and line L9 makes (i,k)(i, k) a child.
  • x=ax = a: then i′=i+1i' = i + 1 and wi+1=aw_{i+1} = a, and line L10 makes (i+1,k)(i + 1, k) a child.
  • x=s∣tx = s \mid t: w[i:i′]w[i:i'] is in L(s)L(s) or in L(t)L(t); by induction (∣s∣,∣t∣<∣x∣|s|, |t| < |x|), (i,s::k)(i, s :: k) or (i,t::k)(i, t :: k) is successful, and both are children by line L11.
  • x=stx = st: choose pp with w[i:p]∈L(s)w[i:p] \in L(s) and w[p:i′]∈L(t)w[p:i'] \in L(t). By induction on tt (with i′−p≤i′−ii' - p \le i' - i and ∣t∣<∣x∣|t| < |x|), (p,t::k)(p, t :: k) is successful. By induction on ss with the list t::kt :: k (with p−i≤i′−ip - i \le i' - i and ∣s∣<∣x∣|s| < |x|), (i,s::t::k)(i, s :: t :: k) is successful, and it is the child of (i,x::k)(i, x :: k) by line L12.
  • x=s∗x = s^*: if i′=ii' = i, the child (i,k)(i, k) of line L13 is successful. Otherwise w[i:i′]=u1⋯uqw[i:i'] = u_1 \cdots u_q with q≥1q \ge 1 and u1,…,uq∈L(s)u_1, \ldots, u_q \in L(s). Drop the empty factors; since w[i:i′]w[i:i'] is not empty, at least one factor is left, so w[i:i′]=v1⋯vpw[i:i'] = v_1 \cdots v_p with p≥1p \ge 1 and each vlv_l nonempty and in L(s)L(s). Let e=i+∣v1∣e = i + |v_1|, so i<e≤i′i < e \le i' and w[e:i′]∈L(s∗)w[e:i'] \in L(s^*). By induction (i′−e<i′−ii' - e < i' - i), (e,s∗::k)(e, s^* :: k) is successful. Since e>ie > i, line L14 makes (e,s∗::k)(e, s^* :: k) a child of (e,CHECK(s∗,i)::k)(e, \mathrm{CHECK}(s^*, i) :: k), which is therefore successful. By induction on ss with the list CHECK(s∗,i)::k\mathrm{CHECK}(s^*, i) :: k (with e−i≤i′−ie - i \le i' - i and ∣s∣<∣x∣|s| < |x|), (i,s::CHECK(s∗,i)::k)(i, s :: \mathrm{CHECK}(s^*, i) :: k) is successful, and it is a child of (i,s∗::k)(i, s^* :: k) by line L13. □\square

Theorem. BACKTRACK(r,w)(r, w) stops, and it returns true if and only if w∈L(r)w \in L(r).

Proof. By Lemma 2, TT is finite, so by Lemma 1 the algorithm stops and returns true if and only if (0,[r])(0, [r]) is successful. If it is, Lemma 3 gives w=w[0:n]∈L([r])=L(r)w = w[0:n] \in L([r]) = L(r). If w∈L(r)w \in L(r), then w[0:n]∈L(r)w[0:n] \in L(r) and (n,[ ])(n, [\,]) is successful with a path of length 0, so Lemma 4 with i=0i = 0, i′=ni' = n and k=[ ]k = [\,] shows that (0,[r])(0, [r]) is successful. ■\blacksquare

Back to Backtracking regex matching