(ns src.unify-matcher) (use '[clojure.set :only (union)]) (defn var? [s] (and (symbol? s) (= (nth (str s) 0) \?))) (defn wild-card? [s] (and (symbol? s) (>= (.length (str s)) 2) (= (take 2 (str s)) (seq "?*")))) (defn cons? [x] (and (coll? x) (not (empty? x)))) (def fail (gensym "fail__")) (defn fail? [s] (identical? s fail)) (declare unify-variable) (defn unify "See if x and y match with given bindings." ([x y] (unify x y {})) ([x y bind] (cond (fail? bind) fail (= x y) bind (var? x) (unify-variable x y bind) (var? y) (unify-variable y x bind) (and (cons? x) (wild-card? (first x))) (recur (first x) y bind) (and (cons? y) (wild-card? (first y))) (recur x (first y) bind) (and (cons? x) (cons? y)) (recur (rest x) (rest y) (unify (first x) (first y) bind)) :else fail))) (defn unify-variable "Unify var with x, using (and maybe extending) bindings." [var x bind] (if (contains? bind var) (unify (bind var) x bind) (assoc bind var x))) (defn subst "Substitute the value of variables in bindings into x, taking recursively bound variables into account." [bind x] (cond (fail? bind) fail (empty? bind) x (var? x) (if (contains? bind x) (recur bind (bind x)) x) (not (cons? x)) x :else (cons (subst bind (first x)) (subst bind (rest x))) )) (defn subst-tree [bind sexp] (if (cons? sexp) ((if (wild-card? (first sexp)) concat cons) (subst-tree bind (first sexp)) (subst-tree bind (rest sexp))) (subst bind sexp))) (defn vars-in ([sexp] (vars-in sexp (complement cons?))) ([sexp atom?] (if (atom? sexp) (if (var? sexp) #{sexp} #{}) (union (vars-in (first sexp) atom?) (vars-in (rest sexp) atom?) )))) (defmacro if-match ([pat seq then] `(if-match ~pat ~seq ~then nil)) ([pat seq then else] (let [gb (gensym "gb__")] `(let [~gb (unify ~pat ~seq)] (if (fail? ~gb) ~else (let [~@(apply concat (map (fn [v] `(~v (subst ~gb '~v))) (sort (vars-in then)) ))] ~then))))))