|
1 |
| -(ns active.clojure.pattern |
| 1 | +(ns active.clojure.match |
| 2 | + "Syntactic sugar for map matching around `core.match`." |
2 | 3 | (:require [active.clojure.condition :as c]
|
3 | 4 | [active.clojure.functions :as f]
|
4 | 5 | [active.clojure.lens :as lens]
|
|
8 | 9 | [clojure.core.match :as match]
|
9 | 10 | [clojure.core.match.regex]))
|
10 | 11 |
|
| 12 | +(defmethod match/to-source ::match/regex |
| 13 | + [pat ocr] |
| 14 | + `(and (not= ~ocr ::match/not-found) (re-matches ~(:regex pat) ~ocr))) |
| 15 | + |
11 | 16 | (define-record-type Pattern
|
12 | 17 | (make-pattern name clauses) pattern?
|
13 | 18 | [name pattern-name
|
|
521 | 526 | ~rhs)))
|
522 | 527 |
|
523 | 528 | (defmacro map-matcher
|
| 529 | + "Construct a map matcher. Syntactic sugar for `core.match`. |
| 530 | +
|
| 531 | + `map-matcher´ accepts two kinds of inputs: |
| 532 | +
|
| 533 | + 1. A sequence of alternating patterns and consequents (see below). |
| 534 | + 2. A sequence of alternating Pattern objects and consequents. |
| 535 | +
|
| 536 | + The syntax is `(map-matcher <pattern> <consequent> ... :else <alternative>)` where |
| 537 | + `<pattern>` is a vector of clauses `[<clause>+]` where `clause` is one of the following: |
| 538 | +
|
| 539 | + - `(<key> <value> :as <name>)` which requires the key `<key>` to be |
| 540 | + mapped to `<value>` in the map and binds `<name>` to `<value>`. |
| 541 | +
|
| 542 | + - `(<key-and-name> <value>)` which requires the key `<key-and-name>` |
| 543 | + to be mapped to `<value>` in the map and binds `<key-and-name>` to |
| 544 | + `<value>`. |
| 545 | +
|
| 546 | + - `(<key> :as <name>)` which requires `<key>` to be present in the map |
| 547 | + and binds `<name>` to its value. |
| 548 | +
|
| 549 | + - `<key-and-name>` which requires `<key-and-name>` to be present in |
| 550 | + the map and binds `<key-and-name>` to its value |
| 551 | +
|
| 552 | + The map matcher also supports optional keys: |
| 553 | +
|
| 554 | + - `(? <key> <default> :as <name>)` binds `<name>` to to the value of |
| 555 | + `<key>` in the map or to `<default>` if `<key>` is not in the map. |
| 556 | +
|
| 557 | + - `(? <key-and-name> <default>)` binds `<key-and-name>` to the value of |
| 558 | + `<key-and-name>` in the map or to `<default>` if `<key-and-name>` is not |
| 559 | + in the map. |
| 560 | +
|
| 561 | + - `(? <key> :as <name>)` binds `<name>` to to the value of `<key>` |
| 562 | + in the map or to `nil` if `<key>` is not in the map. |
| 563 | +
|
| 564 | + - `(? <key-and-name>)` binds `<key-and-name>` to the value of |
| 565 | + `<key-and-name>` in the map or to `nil` if `<key-and-name>` is not |
| 566 | + in the map. |
| 567 | +
|
| 568 | + Access to nested values is also possible. Use `[<key>+]` to access |
| 569 | + a nested value, where `[<key>+]` is a sequence of keys. When no |
| 570 | + `:as <name>` clause is given, the last `<key>` of the sequence of |
| 571 | + keys is used as a name to bind the value. |
| 572 | +
|
| 573 | + `<key>` and `<key-and-name>` can be either a symbol or a keyword. |
| 574 | + If `<key-and-name>` is a symbol, it is converted to a string when |
| 575 | + used as a key (and used as symbol for binding the value). If |
| 576 | + `<key-and-name>` is a keyword, it is converted to a name for binding |
| 577 | + the value (and usesd as keyword when used as a key). |
| 578 | +
|
| 579 | + `<value>` can be: |
| 580 | +
|
| 581 | + - any value, regular expressions are also possible (only in Clojure, though, |
| 582 | + `core.match` does not support regex matching in ClojureScript). |
| 583 | +
|
| 584 | + - a list of alternative values in the form of: `(:or <value> <value>*)`. |
| 585 | +
|
| 586 | + - a custom compare function in the form of: |
| 587 | + `(:compare-fn <compare-fn>)` where `<compare-fn>` accepts the value that |
| 588 | + is mapped to `<key>` or `<key-and-name>`. |
| 589 | +
|
| 590 | + `<value>` also can be a list of alternative values in the form of: |
| 591 | + `(:or <value> <value>*)`. |
| 592 | +
|
| 593 | + `map-matcher` returns a function that accepts a map and evaluates |
| 594 | + `<consequent>` with all the `<name>`s bound when the message matches |
| 595 | + the given `<clause>`s, otherwise it evaluates `<alternative>`. or |
| 596 | + throws `IllegalArgumentException` if `<clause>` matches and no |
| 597 | + `<alternative>` is given. |
| 598 | +
|
| 599 | + Example: |
| 600 | +
|
| 601 | + (def example-map-matcher |
| 602 | + (map-matcher |
| 603 | + [(:x \"x\" :as x) |
| 604 | + (:y \"y\") |
| 605 | + (:z :as z) |
| 606 | + :w] |
| 607 | + (println x y z w) |
| 608 | + [(:a \"a\" :as a) |
| 609 | + (:b \"b\") |
| 610 | + (:c :as c) |
| 611 | + ([:d Z] 42 :as Z) |
| 612 | + ([:d Y] :as Y) |
| 613 | + ([:d X] 65) |
| 614 | + [:d W foo]] |
| 615 | + (println a b c Z Y X foo) |
| 616 | + :else false)) |
| 617 | +
|
| 618 | + (example-map-matcher {:a \"a\" :b \"b\" :c \"c\" |
| 619 | + :d {\"Z\" 42 \"Y\" 23 \"X\" 65 |
| 620 | + \"W\" {\"foo\" \"bar\"}}}) |
| 621 | +
|
| 622 | + prints |
| 623 | +
|
| 624 | + \"a b c d 42 23 65 bar\"" |
524 | 625 | [& args]
|
| 626 | + (when-not (even? (count args)) |
| 627 | + (throw (IllegalArgumentException. (str "expecting an even number of arguments " *ns* " " (meta &form))))) |
525 | 628 | (let [message `message#
|
526 | 629 | patterns+consequents (reduce
|
527 | 630 | (fn [code [lhs* rhs]]
|
|
544 | 647 | [binding pattern]
|
545 | 648 | `(def ~binding ~(parse-pattern (gensym) pattern)))
|
546 | 649 |
|
| 650 | +(defmacro matcher |
| 651 | + [& args] |
| 652 | + (let [event `event#] |
| 653 | + `(fn [~event] |
| 654 | + ((map-matcher ~@args) ~event)))) |
| 655 | + |
| 656 | +(defmacro match |
| 657 | + [event & args] |
| 658 | + `((matcher ~@args) ~event)) |
| 659 | + |
547 | 660 | (define-record-type Dependency
|
548 | 661 | (make-dependency path matcher for-pattern) dependency?
|
549 | 662 | [^{:doc "The path that this [[Dependency]] has a restriction on."}
|
|
0 commit comments