Derivatives of regular expressions are an elegant and algebraic technique, beloved by functional programmers, for implementing regex matching.
The technique originated from Janusz Brzozowski (1962). Brzozowski's insight was that given a regex
In 1995, Valentin Antimirov introduced an alternative to Brzozowski's method using partial derivatives. A partial derivative of
Moreover, we explored some recent results published in Romain Edelmann's PhD dissertation from EPFL. In his thesis, Edelmann shows how Brzozowski derivatives can be implemented using a purely functional data structure called a zipper, which is used to navigate tree-shaped datatypes (e.g. our regex AST). A zipper consists of a focused subtree
(Diagram inspired by Darragh & Adams's ICFP '20 talk)
When navigating through our regex AST, we can shift the zipper's focus to a different subtree, which creates a new zipper with an updated context. Edelmann demonstrates in his dissertation that Brzozowski derivatives can be computed using zippers! His idea was to move the zipper's focus and update the context every time the Brzozowski derivative function
Edelmann's insight was that the Brzozowski derivative only introduces 2 new kinds of AST constructors, namely
- When we encounter
$+$ , e.g. when computing$\delta_c(r_1 + r_2)$ , we need to split the focus between two subterms, so we use a set to keep track of different choices of focus. - When we encounter
$\cdot$ , e.g. when computing$\delta_c(r_1 \cdot r_2$ ), we have to keep the rest of the expression in the context before recursively calling$\delta_c$ on$r_1$ . Since order matters for$\cdot$ , we use lists to represent a sequence of$\cdot$ operations. - Edelmann's zipper data structure is thus a set of lists, where the elements in the set represent different choices of focus, and the elements of each list represent subterms to be concatenated. For example, the regex
$(r_1 \cdot r_2) + r_3$ corresponds to the zipper{ [r1, r2], [r3] }.
Now, Edelmann mentions in passing that his zipper representation is "reminiscent of Antimirov derivatives." We formally proved Edelmann's observation, i.e. that the set of terms contained within a Brzozowski zipper is the same as the set of terms contained in the set of Antimirov derivatives (modulo some rewrite rules).
In this project, we mechanize proofs that relate the two notions of derivatives and the zipper representation of Brzozowski derivatives.
-
Regex matchers based on Brzozowski and Antimirov derivatives are equivalent, in that they accept exactly the same set of strings. They are also equivalent to an inductively-defined matcher.
-
There are finitely many Antimirov partial derivatives for a given regex. (This means that we can write an algorithm to build a DFA using these derivatives.)
-
Michael Greenberg has a blogpost in which he investigates how the size of a regex grows as we take successive Brzozowski derivatives. In particular, the size (number of AST nodes) of Brzozowski derivatives grows exponentially, and the height (of the AST representation) of a Brzozowski derivative is at most twice the height of the original regex. We proved similar results for the Antimirov derivative:
a. The height of an Antimirov partial derivative is at most twice the height of the original regex.
b. The number of Antimirov partial deriatives of a regex is linear in the AST size of the original regex.
-
If we concatenate the elements within each context (list of regexes) contained in Edelmann’s zipper, we get the same set of regexes as the Antimirov derivative.
Thus, we have proved notions of equivalence for Brzozowski-based matchers, Antimirov-based matchers, and the zipper representation of the Brzozowski derivative. We also proved bounds on size, height, and finitude for these derivatives. Finally, we also implemented much of our work in OCaml to allow these derivative-based matchers to be executed. Our code is available on GitHub.
