|
14 | 14 | :author "Rich Hickey"}
|
15 | 15 | rewrite-clj.zip.zip
|
16 | 16 | (:refer-clojure :exclude (replace remove next))
|
17 |
| - (:require [rewrite-clj.node.protocols :as node])) |
| 17 | + (:require [rewrite-clj.node.protocols :as node] |
| 18 | + [clojure.zip :as clj-zip])) |
| 19 | + |
| 20 | +;; ## Switch |
| 21 | +;; |
| 22 | +;; To not force users into using this custom zipper, the following flag |
| 23 | +;; is used to dispatch to `clojure.zip` when set to `false`. |
| 24 | + |
| 25 | +(def ^:dynamic *active?* |
| 26 | + "Set this to true to activate the custom, position-tracking zipper |
| 27 | + implementation." |
| 28 | + false) |
| 29 | + |
| 30 | +(defmacro ^:private defn-switchable |
| 31 | + [sym docstring params & body] |
| 32 | + (let [placeholders (repeatedly (count params) gensym)] |
| 33 | + `(defn ~sym |
| 34 | + ~docstring |
| 35 | + [~@placeholders] |
| 36 | + (if *active?* |
| 37 | + (let [~@(interleave params placeholders)] |
| 38 | + ~@body) |
| 39 | + (~(symbol "clojure.zip" (name sym)) ~@placeholders))))) |
| 40 | + |
| 41 | +(defmacro with-positional-zipper |
| 42 | + "Do not use `clojure.zip` to evaluate any rewrite-clj zipper operations in |
| 43 | + `body` but a custom position-tracking zipper that offers the function |
| 44 | + `position` to return row and column of a node." |
| 45 | + [& body] |
| 46 | + `(binding [*active?* true] |
| 47 | + ~@body)) |
| 48 | + |
| 49 | +;; ## Implementation |
18 | 50 |
|
19 | 51 | (defn ^:no-doc zipper
|
20 | 52 | "Creates a new zipper structure."
|
21 | 53 | [root]
|
22 |
| - {:node root |
23 |
| - :position [1 1] |
24 |
| - :parent nil |
25 |
| - :left [] |
26 |
| - :right '()}) |
| 54 | + (if *active?* |
| 55 | + {:node root |
| 56 | + :position [1 1] |
| 57 | + :parent nil |
| 58 | + :left [] |
| 59 | + :right '()} |
| 60 | + (clj-zip/zipper |
| 61 | + node/inner? |
| 62 | + (comp seq node/children) |
| 63 | + node/replace-children |
| 64 | + root))) |
27 | 65 |
|
28 |
| -(defn node |
| 66 | +(defn-switchable node |
29 | 67 | "Returns the node at loc"
|
30 | 68 | [{:keys [node]}]
|
31 | 69 | node)
|
32 | 70 |
|
33 |
| -(defn branch? |
| 71 | +(defn-switchable branch? |
34 | 72 | "Returns true if the node at loc is a branch"
|
35 | 73 | [{:keys [node]}]
|
36 | 74 | (node/inner? node))
|
37 | 75 |
|
38 |
| -(defn children |
| 76 | +(defn-switchable children |
39 | 77 | "Returns a seq of the children of node at loc, which must be a branch"
|
40 | 78 | [{:keys [node] :as loc}]
|
41 | 79 | (if (branch? loc)
|
42 | 80 | (seq (node/children node))
|
43 | 81 | (throw (Exception. "called children on a leaf node"))))
|
44 | 82 |
|
45 |
| -(defn ^:no-doc make-node |
| 83 | +(defn-switchable ^:no-doc make-node |
46 | 84 | "Returns a new branch node, given an existing node and new
|
47 | 85 | children. The loc is only used to supply the constructor."
|
48 | 86 | [loc node children]
|
|
51 | 89 | (defn position
|
52 | 90 | "Returns the ones-based [row col] of the start of the current node"
|
53 | 91 | [loc]
|
54 |
| - (:position loc)) |
| 92 | + (if *active?* |
| 93 | + (:position loc) |
| 94 | + (throw |
| 95 | + (IllegalStateException. |
| 96 | + (str "to use the positional zipper functions, please use " |
| 97 | + "`rewrite-clj.zip.zip/with-positional-zipper`."))))) |
55 | 98 |
|
56 |
| -(defn lefts |
| 99 | +(defn-switchable lefts |
57 | 100 | "Returns a seq of the left siblings of this loc"
|
58 | 101 | [loc]
|
59 | 102 | (map first (:left loc)))
|
60 | 103 |
|
61 |
| -(defn down |
| 104 | +(defn-switchable down |
62 | 105 | "Returns the loc of the leftmost child of the node at this loc, or
|
63 | 106 | nil if no children"
|
64 | 107 | [loc]
|
|
72 | 115 | :left []
|
73 | 116 | :right cnext}))))
|
74 | 117 |
|
75 |
| -(defn up |
| 118 | +(defn-switchable up |
76 | 119 | "Returns the loc of the parent of the node at this loc, or nil if at
|
77 | 120 | the top"
|
78 | 121 | [loc]
|
|
86 | 129 | (concat (map first left) (cons node right))))
|
87 | 130 | parent))))
|
88 | 131 |
|
89 |
| -(defn root |
| 132 | +(defn-switchable root |
90 | 133 | "zips all the way up and returns the root node, reflecting any changes."
|
91 | 134 | [{:keys [end?] :as loc}]
|
92 | 135 | (if end?
|
|
96 | 139 | (recur p)
|
97 | 140 | (node loc)))))
|
98 | 141 |
|
99 |
| -(defn right |
| 142 | +(defn-switchable right |
100 | 143 | "Returns the loc of the right sibling of the node at this loc, or nil"
|
101 | 144 | [loc]
|
102 | 145 | (let [{:keys [node parent position left] [r & rnext :as right] :right} loc]
|
|
107 | 150 | :right rnext
|
108 | 151 | :position (node/+extent position (node/extent node))))))
|
109 | 152 |
|
110 |
| -(defn rightmost |
| 153 | +(defn-switchable rightmost |
111 | 154 | "Returns the loc of the rightmost sibling of the node at this loc, or self"
|
112 | 155 | [loc]
|
113 | 156 | (if-let [next (right loc)]
|
114 | 157 | (recur next)
|
115 | 158 | loc))
|
116 | 159 |
|
117 |
| -(defn left |
| 160 | +(defn-switchable left |
118 | 161 | "Returns the loc of the left sibling of the node at this loc, or nil"
|
119 | 162 | [loc]
|
120 | 163 | (let [{:keys [node parent left right]} loc]
|
|
126 | 169 | :left (pop left)
|
127 | 170 | :right (cons node right))))))
|
128 | 171 |
|
129 |
| -(defn leftmost |
| 172 | +(defn-switchable leftmost |
130 | 173 | "Returns the loc of the leftmost sibling of the node at this loc, or self"
|
131 | 174 | [loc]
|
132 | 175 | (let [{:keys [node parent left right]} loc]
|
|
139 | 182 | :right (concat (map first (rest left)) [node] right)))
|
140 | 183 | loc)))
|
141 | 184 |
|
142 |
| -(defn insert-left |
| 185 | +(defn-switchable insert-left |
143 | 186 | "Inserts the item as the left sibling of the node at this loc,
|
144 | 187 | without moving"
|
145 | 188 | [loc item]
|
|
151 | 194 | :left (conj left [item position])
|
152 | 195 | :position (node/+extent position (node/extent item))))))
|
153 | 196 |
|
154 |
| -(defn insert-right |
| 197 | +(defn-switchable insert-right |
155 | 198 | "Inserts the item as the right sibling of the node at this loc,
|
156 | 199 | without moving"
|
157 | 200 | [loc item]
|
|
162 | 205 | :changed? true
|
163 | 206 | :right (cons item right)))))
|
164 | 207 |
|
165 |
| -(defn replace |
| 208 | +(defn-switchable replace |
166 | 209 | "Replaces the node at this loc, without moving"
|
167 | 210 | [loc node]
|
168 | 211 | (assoc loc :changed? true :node node))
|
169 | 212 |
|
170 | 213 | (defn edit
|
171 | 214 | "Replaces the node at this loc with the value of (f node args)"
|
172 | 215 | [loc f & args]
|
173 |
| - (replace loc (apply f (node loc) args))) |
| 216 | + (if *active?* |
| 217 | + (replace loc (apply f (node loc) args)) |
| 218 | + (apply clj-zip/edit loc f args))) |
174 | 219 |
|
175 |
| -(defn insert-child |
| 220 | +(defn-switchable insert-child |
176 | 221 | "Inserts the item as the leftmost child of the node at this loc,
|
177 | 222 | without moving"
|
178 | 223 | [loc item]
|
179 | 224 | (replace loc (make-node loc (node loc) (cons item (children loc)))))
|
180 | 225 |
|
181 |
| -(defn append-child |
| 226 | +(defn-switchable append-child |
182 | 227 | "Inserts the item as the rightmost child of the node at this loc,
|
183 | 228 | without moving"
|
184 | 229 | [loc item]
|
185 | 230 | (replace loc (make-node loc (node loc) (concat (children loc) [item]))))
|
186 | 231 |
|
187 |
| -(defn next |
| 232 | +(defn-switchable next |
188 | 233 | "Moves to the next loc in the hierarchy, depth-first. When reaching
|
189 | 234 | the end, returns a distinguished loc detectable via end?. If already
|
190 | 235 | at the end, stays there."
|
|
199 | 244 | (or (right (up p)) (recur (up p)))
|
200 | 245 | (assoc p :end? true))))))
|
201 | 246 |
|
202 |
| -(defn prev |
| 247 | +(defn-switchable prev |
203 | 248 | "Moves to the previous loc in the hierarchy, depth-first. If already
|
204 | 249 | at the root, returns nil."
|
205 | 250 | [loc]
|
|
210 | 255 | loc))
|
211 | 256 | (up loc)))
|
212 | 257 |
|
213 |
| -(defn end? |
| 258 | +(defn-switchable end? |
214 | 259 | "Returns true if loc represents the end of a depth-first walk"
|
215 | 260 | [loc]
|
216 | 261 | (:end? loc))
|
217 | 262 |
|
218 |
| -(defn remove |
| 263 | +(defn-switchable remove |
219 | 264 | "Removes the node at loc, returning the loc that would have preceded
|
220 | 265 | it in a depth-first walk."
|
221 | 266 | [loc]
|
|
0 commit comments