The pattern matcher is easily extensible by customizing several multimethods.
First, we need to add a new method for
emit-pattern is used in the initial traversal of the pattern matrix, converting normal Clojure forms into patterns.
emit-pattern dispatches on the
class of a form. When the conversion process comes across a regex pattern, we'd like to drop in our customized pattern.
What to dispatch on?
match.regex=> (type #"asdf") java.util.regex.Pattern
So we add a simple dispatch that returns a RegexPattern record.
(defrecord RegexPattern [regex]) (defmethod emit-pattern java.util.regex.Pattern [pat] (RegexPattern. pat))
We use the multimethod
pattern-compare to compare patterns in the compilation phase.
The default method of
pattern-compare says that the two arguments are not equal.
For our extension this is fine, except for the case where two
RegexPatterns have equal regular expressions.
Let's define that case.
(defmethod pattern-compare [RegexPattern RegexPattern] [a b] (if (and (= (.pattern (:regex a)) (.pattern (:regex b))) (= (.flags (:regex a)) (.flags (:regex b)))) 0 -1))
If we attempt to pattern match currently, the regex syntax will be treated as a normal
LiteralPattern, which simply uses
= to test a branch.
match.core.debug=> (m-to-clj [x] [#"a"] 1 :else 2) (cond (= x #"a") 1 :else 2) nil
What we really want is this:
(cond (re-matches #"a" x) 1 :else 2)
To accomplish this we customize the multimethod
(defmethod to-source RegexPattern [pat ocr] `(re-matches ~(:regex pat) ~ocr))
to-source dispatches on the
type of its first argument. This method will be chosen when the pattern is a
The second argument is the "occurance" we're currently matching. In the example above, the occurance is "x".
We can run
to-source directly to see what we get.
match.regex=> (def r (RegexPattern. #"a")) ;=> #'match.regex/r match.regex=> (to-source r 'x) ;=> (clojure.core/re-matches #"a" x)
We simply need to
use the namespace containing the customizations to include them in pattern matching.
match.core.debug=> (use 'match.regex) nil match.core.debug=> (m-to-clj [x] [#"a"] 1 :else 2) (cond (re-matches #"a" x) 1 :else 2) nil