This repository has been archived by the owner on Mar 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
match.clj
220 lines (188 loc) · 7.62 KB
/
match.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
(ns mimir.match
(:use [clojure.set :only (intersection map-invert rename-keys difference union join)]
[clojure.tools.logging :only (debug info warn error spy enabled?)]
[clojure.walk :only (postwalk prewalk walk postwalk-replace)])
(:import [java.util.regex Pattern]
[clojure.lang IPersistentMap IPersistentSet Sequential Symbol Fn Keyword]))
(defprotocol MatchAny (match-any [this x acc]))
(defprotocol MatchMap (match-map [this x acc]))
(defprotocol MatchSeq (match-seq [this x acc]))
(defn filter-walk
[pred coll]
(let [acc (transient [])]
(postwalk #(when (pred %) (conj! acc %)) coll)
(distinct (persistent! acc))))
(defn singleton-coll? [x]
(and (coll? (first x)) (not (next x))))
(defn maybe-singleton-coll [x]
(if (singleton-coll? x) (first x) x))
(def ^:dynamic *match-var?* #(and (symbol? %)
(not (or (resolve %) ('#{do fn* let* if} %)
(re-matches #".*/.*"(str %)) (re-matches #"\..*"(name %))
(re-matches #".*\."(name %)) (re-matches #".*#"(name %))))))
(def ^:dynamic *var-symbol* symbol)
(defn bind-vars [x pattern acc]
(if-let [var (if (*match-var?* pattern)
pattern
(-> pattern meta :tag))]
(if-let [v (acc var)]
(if-not (= v var)
(if (= (acc v) var)
(assoc acc var x)
(match-any v x acc))
acc)
(assoc acc var x))
acc))
(defn preserve-meta [form meta]
(if (and (instance? clojure.lang.IMeta form)
(not (and (list? form)
(= 'quote (first form))
(symbol (second form)))))
(list 'if (list 'instance? 'clojure.lang.IMeta form)
(list 'with-meta form (list 'quote meta))
form)
form))
(defn meta-walk [form]
(let [m (dissoc (meta form) :line)]
(if (seq m)
(preserve-meta (walk meta-walk identity form) m)
(if (*match-var?* form)
(list 'quote form)
(walk meta-walk identity form)))))
(defn bound-vars [x]
(let [vars (transient [])
var-walk (fn this [form]
(let [v (or (-> form meta :tag) form)]
(when (*match-var?* v)
(conj! vars v)))
form)]
(prewalk var-walk x)
(distinct (persistent! vars))))
(defn regex-vars [x]
(let [vars (transient [])
regex-walk (fn this [form]
(when (instance? Pattern form)
(reduce conj! vars
(map (comp symbol second)
(re-seq #"\(\?<(.+?)>.*?\)" (str form)))))
form)]
(postwalk regex-walk x)
(distinct (persistent! vars))))
(extend-type Object
MatchAny (match-any [this x acc] (when (= this x) acc))
MatchMap (match-map [this x acc])
MatchSeq (match-seq [this x acc]))
(extend-type nil
MatchAny (match-any [this x acc] (when (nil? x) acc))
MatchMap (match-map [this x acc])
MatchSeq (match-seq [this x acc]))
(extend-type IPersistentMap
MatchAny
(match-any [this x acc] (match-map x this acc))
MatchMap
(match-map [x this acc] (loop [[k & ks] (keys this)
acc acc]
(if-not k
(bind-vars x this acc)
(when (contains? x k)
(when-let [acc (match-any (this k) (x k) acc)]
(recur ks (bind-vars (x k) (this k) acc))))))))
(extend-type Symbol
MatchAny
(match-any [this x acc] (if (*match-var?* this)
(bind-vars x this acc)
(when (= this x) acc))))
(extend-type Pattern
MatchAny
(match-any [this x acc] (let [re (re-matcher this (str x))
groups (regex-vars this)]
(when (.matches re)
(reduce #(assoc % (*var-symbol* %2)
(.group re (str %2)))
acc groups)))))
(extend-type Class
MatchAny
(match-any [this x acc] (when (instance? this x) acc)))
(extend-type Fn
MatchAny
(match-any [this x acc] (when (try (this x) (catch RuntimeException _))
(bind-vars x this acc))))
(extend-type Keyword
MatchAny
(match-any [this x acc] (when (or (contains? x this) (= x this))
(bind-vars x this acc))))
(extend-type IPersistentSet
MatchAny
(match-any [this x acc] (loop [[k & ks] (seq this)
acc acc]
(when k
(if-let [acc (match-any k x acc)]
(bind-vars x this acc)
(recur ks acc))))))
(extend-type Sequential
MatchAny
(match-any [this x acc] (match-seq x this acc))
MatchSeq
(match-seq [x this acc] (loop [[p & ps] this
[y & ys] x
acc acc]
(if (and (not p) (not y))
(bind-vars x this acc)
(if ('#{& .} p)
(let [rst (when y (vec (cons y ys)))]
(when-let [acc (if (*match-var?* (first ps))
acc
(match-seq rst (repeat (count rst)
(first ps)) acc))]
(bind-vars rst (first ps) acc)))
(when-let [acc (match-any p y acc)]
(recur ps ys (bind-vars y p acc))))))))
(defn truth [& _] true)
(defn unquote-vars-in-scope [&env form]
(if &env
(postwalk #(if (and (list? %)
(= 'quote (first %))
(&env (second %)))
(second %) %) form)
form))
(defn prepare-matcher [m &env]
(->> (preserve-meta (walk identity meta-walk m) (meta m))
(postwalk-replace {'_ truth :else truth})
(unquote-vars-in-scope &env)))
(defn match* [x pattern] (match-any pattern x {}))
(defmacro match [x m]
`(match* ~x ~(prepare-matcher m &env)))
(defn all-vars [lhs]
(vec (concat (bound-vars lhs)
(map *var-symbol* (regex-vars lhs)))))
(defmacro condm* [match-var [lhs rhs & ms]]
`(if-let [{:syms ~(remove (set (keys &env)) (all-vars lhs))}
(mimir.match/match ~match-var ~lhs)]
~rhs
~(when ms
`(condm* ~match-var ~ms))))
(defmacro condm [x & ms]
(let [match-var (if-let [v (-> x meta :tag)] v '*match*)]
`(let [~match-var ~(if (and (instance? clojure.lang.IMeta x)
(not (and (list? x)
(= 'quote (first x))
(symbol? (second x)))))
(with-meta x {})
x)]
(condm* ~match-var ~ms))))
(defn single-arg? [ms]
(not-any? coll? (take-nth 2 ms)))
(defmacro fm [& ms]
`(fn ~'this [& ~'args]
(condm (if ~(single-arg? ms) (first ~'args) ~'args) ~@ms)))
(defmacro defm [name args & ms]
(let [[doc ms] (split-with string? ms)
[_ _ [match-var & _ ]] (partition-by '#{&} args)]
`(do
(defn ~name ~args
~(when (seq ms)
`(condm ~(list 'first (if (single-arg? ms)
(list 'first match-var)
match-var)) ~@ms)))
(alter-meta! (var ~name) merge {:doc (apply str ~doc)})
~name)))