Skip to content

Commit 0eb96c2

Browse files
committed
More work on compare functions.
1 parent b69abbd commit 0eb96c2

File tree

2 files changed

+171
-126
lines changed

2 files changed

+171
-126
lines changed

src/active/clojure/match.clj

Lines changed: 86 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -87,30 +87,6 @@
8787

8888
(def matcher? (some-fn constant-matcher? regex-matcher? existence-matcher? options-matcher? predicate-matcher?))
8989

90-
(defn matcher->value
91-
"Takes a `matcher` and returns the value/s it matches on.
92-
`::not-nil` symbolizes the existence matcher."
93-
[matcher]
94-
(fn [binding]
95-
(cond
96-
(constant-matcher? matcher)
97-
(constant-matcher-value matcher)
98-
99-
(regex-matcher? matcher)
100-
(regex-matcher-regex matcher)
101-
102-
(options-matcher? matcher)
103-
(cons :or (options-matcher-options matcher))
104-
105-
(existence-matcher? matcher) ; matches on everything.
106-
::not-nil
107-
108-
(predicate-matcher? matcher)
109-
(list binding :guard (predicate-matcher-predicate matcher))
110-
111-
:else
112-
(c/assertion-violation `matcher->value "not a matcher" matcher))))
113-
11490
(defn matcher-default-value
11591
"Returns the default value of a matcher, if any."
11692
[matcher]
@@ -280,7 +256,8 @@
280256

281257
(s/def ::regex regex?)
282258
(s/def ::compare-fn-token #{:compare-fn})
283-
(s/def ::compare-fn (s/cat :compare-fn ::compare-fn-token :fn ifn?))
259+
(s/def ::fn? #(or (ifn? %) (ifn? (eval %))))
260+
(s/def ::compare-fn (s/cat :compare-fn ::compare-fn-token :fn ::fn?))
284261

285262
(s/def ::or-token #{:or})
286263

@@ -423,71 +400,116 @@
423400
(if (optional? mode) (opt clause) clause))))))))
424401

425402
(defn parse-pattern
426-
"Parse the argument to `defpattern` as a [[Pattern]]"
427-
[name p]
428-
(if (pattern? p)
429-
p
430-
(make-pattern name (mapv parse-clause p))))
403+
"Parse the argument to `defpattern` as a [[Pattern]].
404+
Optionally accepts a `name` (String) that names the pattern. If none is
405+
provided, automatically assigns a name."
406+
([p]
407+
(parse-pattern (str "pattern-" (gensym)) p))
408+
([name p]
409+
(if (pattern? p)
410+
p
411+
(make-pattern name (mapv parse-clause p)))))
431412

432413
;; Match
433414

434415
(defn convert-path-element
435416
[path-element]
436417
(if (symbol? path-element) (str path-element) path-element))
437418

438-
(defn key-exists-clause->match
439-
[clause]
440-
(let [key (key-exists-clause-key clause)
441-
binding (key-exists-clause-binding clause)]
442-
;; ignore the binding if it is the same as the key
443-
{(convert-path-element key) binding}))
444-
445419
(defn key-exists-clause->rhs-match
446420
[message clause]
447421
(let [key (key-exists-clause-key clause)
448422
binding (key-exists-clause-binding clause)]
449423
`[~binding (get-in ~message [~(convert-path-element key)])]))
450424

451-
(defn path-exists-clause->match
452-
[clause]
453-
(let [path (path-exists-clause-path clause)
454-
binding (path-exists-clause-binding clause)]
455-
(assoc-in {} (map convert-path-element path) binding)))
456-
457425
(defn path-exists-clause->rhs-match
458426
[message clause]
459427
(let [key (path-exists-clause-path clause)
460428
binding (path-exists-clause-binding clause)]
461429
`[~binding (get-in ~message ~(mapv convert-path-element key))]))
462430

463-
(defn key-matches-clause->lhs-match
464-
[clause]
465-
(let [key (key-matches-clause-key clause)
466-
match-value (matcher->value (key-matches-clause-matcher clause))
467-
binding (key-matches-clause-binding clause)]
468-
{key (match-value binding)}))
469-
470431
(defn key-matches-clause->rhs-match
471432
[message clause]
472433
(let [key (key-matches-clause-key clause)
473434
match-value (matcher-default-value (key-matches-clause-matcher clause))
474435
binding (key-matches-clause-binding clause)]
475436
`[~binding (get-in ~message [~(convert-path-element key)] ~match-value)]))
476437

477-
(defn path-matches-clause->lhs-match
478-
[clause]
479-
(let [path (path-matches-clause-path clause)
480-
match-value (matcher->value (path-matches-clause-matcher clause))
481-
binding (path-matches-clause-binding clause)]
482-
(assoc-in {} (map convert-path-element path) (match-value binding))))
483-
484438
(defn path-matches-clause->rhs-match
485439
[message clause]
486440
(let [path (path-matches-clause-path clause)
487441
match-value (matcher-default-value (path-matches-clause-matcher clause))
488442
binding (path-matches-clause-binding clause)]
489443
`[~binding (get-in ~message ~(mapv convert-path-element path) ~match-value)]))
490444

445+
(defn matcher->value
446+
"Takes a `matcher` and returns the value/s it matches on.
447+
`::not-nil` symbolizes the existence matcher."
448+
[matcher]
449+
(fn [message path]
450+
(cond
451+
(constant-matcher? matcher)
452+
(constant-matcher-value matcher)
453+
454+
(regex-matcher? matcher)
455+
(regex-matcher-regex matcher)
456+
457+
(options-matcher? matcher)
458+
(cons :or (options-matcher-options matcher))
459+
460+
(existence-matcher? matcher) ; matches on everything.
461+
::not-nil
462+
463+
(predicate-matcher? matcher)
464+
[`(constantly (~(predicate-matcher-predicate matcher)
465+
(get-in ~message ~path)))]
466+
467+
:else
468+
(c/assertion-violation `matcher->value "not a matcher" matcher))))
469+
470+
(defn fold-path
471+
[path match]
472+
(let [path* (->> path
473+
(mapv convert-path-element)
474+
reverse)]
475+
(reduce (fn [m path-element]
476+
{path-element m})
477+
{(first path*) match}
478+
(rest path*))))
479+
480+
(defn clause->lhs
481+
[message clause]
482+
(cond
483+
(key-exists-clause? clause)
484+
(let [key (key-exists-clause-key clause)
485+
binding (key-exists-clause-binding clause)]
486+
;; ignore the binding if it is the same as the key
487+
{(convert-path-element key) binding})
488+
489+
(path-exists-clause? clause)
490+
(let [path (path-exists-clause-path clause)
491+
binding (path-exists-clause-binding clause)]
492+
(assoc-in {} (map convert-path-element path) binding))
493+
494+
(key-matches-clause? clause)
495+
(let [key (key-matches-clause-key clause)
496+
matcher (key-matches-clause-matcher clause)
497+
match-value (matcher->value matcher)]
498+
(if (predicate-matcher? matcher)
499+
`({~key ~'_} :guard ~(match-value message [key]))
500+
`{~key ~(match-value message [key])}))
501+
502+
(path-matches-clause? clause)
503+
(let [path (path-matches-clause-path clause)
504+
matcher (path-matches-clause-matcher clause)
505+
match-value (matcher->value matcher)]
506+
(if (predicate-matcher? matcher)
507+
`(~(fold-path path '_) :guard ~(match-value message path))
508+
(fold-path path (match-value message path))))
509+
510+
(optional-clause? clause)
511+
{}))
512+
491513
(defn deep-merge-with
492514
"Like merge-with, but merges maps recursively, applying the given fn
493515
only when there's a non-map at a particular level.
@@ -503,17 +525,12 @@
503525
maps))
504526

505527
(defn pattern->lhs
506-
[pattern]
528+
[message pattern]
507529
(let [clauses (pattern-clauses pattern)]
508-
(apply deep-merge-with merge
509-
(mapv (fn [clause]
510-
(cond
511-
(key-exists-clause? clause) (key-exists-clause->match clause)
512-
(path-exists-clause? clause) (path-exists-clause->match clause)
513-
(key-matches-clause? clause) (key-matches-clause->lhs-match clause)
514-
(path-matches-clause? clause) (path-matches-clause->lhs-match clause)
515-
(optional-clause? clause) {}))
516-
clauses))))
530+
(->> pattern
531+
pattern-clauses
532+
(mapv (partial clause->lhs message))
533+
(apply deep-merge-with merge))))
517534

518535
(defn clause->rhs
519536
[message bindings clause]
@@ -653,7 +670,7 @@
653670
(parse-pattern (gensym) lhs)
654671
lhs)]
655672
(concat code
656-
[(pattern->lhs pattern)
673+
[(pattern->lhs message pattern)
657674
(pattern->rhs message pattern rhs)])))))
658675
nil
659676
(partition 2 args))]
@@ -663,7 +680,7 @@
663680

664681
(defmacro defpattern
665682
[binding pattern]
666-
`(def ~binding ~(parse-pattern (gensym) pattern)))
683+
`(def ~binding ~(parse-pattern binding pattern)))
667684

668685
(defmacro matcher
669686
[& args]

0 commit comments

Comments
 (0)