-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
viewers.clj
152 lines (125 loc) · 4.44 KB
/
viewers.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
(ns mentat.clerk-utils.viewers
"Functions useful for writing custom Clerk viewers."
(:require [clojure.string :as-alias s]))
(defn map-kv
"Returns a map of identical type and key set to `m`, with each value `v`
transformed by the supplied function `vf` into `(vf v)` and each key `k`
transformed into `(kf k)`.
In the 2-arity case, only values are transformed."
([vf m]
(map-kv identity vf m))
([kf vf m]
(persistent!
(reduce-kv (fn [acc k v]
(assoc! acc (kf k) (vf v)))
(transient (empty m))
m))))
(defn unquote?
"Returns true if `form` is a form that should be included with no quoting into
the returned form, false otherwise."
[form]
(and (sequential? form)
(= (first form)
'clojure.core/unquote)))
(defn unquote-splice?
"Returns true if `form` is a sequence form that should be spliced directly into
the returned form, false otherwise."
[form]
(and (sequential? form)
(= (first form)
'clojure.core/unquote-splicing)))
(defn unquoted-form
"Given a `form` that responds `true` to [[unquote?]] or [[unquote-splice?]],
returns the unquoted body."
[form]
(second form))
(defn splice-reduce
"Helper function for reducing over a sequence that might contain forms that need
to be spliced into the resulting sequence. This is a sort of helper for a
guarded `mapcat`.
Takes a sequence `xs` and mapping function `f` and returns a sequence of
sequences that, if concatenated together, would be identical to
```clojure
(map f xs)
```
Except that any `x` such that `(unquote-splice? x)` returns true would have
its sequential value `x` spliced into the result instead of `(f x)`."
[f xs]
(let [[acc pending] (reduce
(fn [[acc pending] x]
(if (unquote-splice? x)
(let [form (unquoted-form x)]
(if (empty? pending)
[(conj acc form) []]
[(conj acc pending form) []]))
[acc (conj pending (f x))]))
[[] []]
xs)]
(if (empty? pending)
acc
(conj acc pending))))
(defn compile-sym
"Given a map `aliases` of `<symbol> => <namespace object>` and a symbol `sym`,
returns `sym` with its namespace expanded if that namespace is present in
`aliases`, `sym` otherwise.
For example:
```clj
(require '[clojure.core :as c])
(compile-sym (ns-aliases *ns*) 'c/cake)
;; => 'clojure.core/cake
```"
[aliases sym]
(list
'quote
(if-let [ns (namespace sym)]
(if-let [full-ns (aliases (symbol ns))]
(symbol (str full-ns) (name sym))
sym)
sym)))
(defn compile-form
"Given a map `aliases` of `<symbol> => <namespace object>` and a Clojure
expression tree `skel`, returns `skel` with
- any splices or unquote-splices resolved from the environment
- any symbol namespaced by an alias substituted for a symbol with namespace
expanded.
Used by [[q]]."
[aliases skel]
(letfn [(compile-sequential [xs]
(let [acc (splice-reduce compile xs)]
(cond (empty? acc) ()
(= 1 (count acc)) (first acc)
:else `(concat ~@acc))))
(compile [form]
(cond (symbol? form)
(compile-sym aliases form)
(unquote? form)
(unquoted-form form)
(unquote-splice? form)
(into [] (unquoted-form form))
(map? form)
(map-kv compile compile form)
(vector? form)
`(vec ~(compile-sequential form))
(set? form)
`(set ~(compile-sequential form))
(sequential? form)
(if (empty? form)
form
`(seq ~(compile-sequential form)))
:else form))]
(compile skel)))
(defmacro q
"[[q]] is similar to Clojure's `unquote` facility, except that
- symbols are not automatically prefixed by namespace,
- splices and unquote splices are respected, and
- any symbol namespaced by an alias will have its namespace expanded.
For example:
```clojure
(require '[clojure.core :as c])
(let [x 10]
(q (+ ~x c/y z ~@[4 5])))
;;=> (+ 10 clojure.core/y z 4 5)
```"
[form]
(let [alias-m (ns-aliases *ns*)]
(compile-form alias-m form)))