Skip to content

Commit d5356d3

Browse files
author
Yannick Scherer
committed
Merge pull request #45 from xsc/ys/position-tracking-optional
make position-tracking zipper optional.
2 parents 2b2fcce + ca3fb01 commit d5356d3

23 files changed

+303
-203
lines changed

src/rewrite_clj/zip/zip.clj renamed to src/rewrite_clj/custom_zipper/core.clj

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,68 @@
1212
(ns ^{:doc "Functional hierarchical zipper, with navigation, editing,
1313
and enumeration. See Huet"
1414
:author "Rich Hickey"}
15-
rewrite-clj.zip.zip
15+
rewrite-clj.custom-zipper.core
1616
(: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]))
1819

19-
(defn ^:no-doc zipper
20-
"Creates a new zipper structure."
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+
(defn ^:no-doc custom-zipper
2126
[root]
22-
{:node root
27+
{::custom? true
28+
:node root
2329
:position [1 1]
24-
:parent nil
25-
:left []
26-
:right '()})
30+
:parent nil
31+
:left []
32+
:right '()})
33+
34+
(defn ^:no-doc zipper
35+
[root]
36+
(clj-zip/zipper
37+
node/inner?
38+
(comp seq node/children)
39+
node/replace-children
40+
root))
41+
42+
(defn ^:no-doc custom-zipper?
43+
[value]
44+
(::custom? value))
45+
46+
(defmacro ^:private defn-switchable
47+
[sym docstring params & body]
48+
(let [placeholders (repeatedly (count params) gensym)]
49+
`(defn ~sym
50+
~docstring
51+
[~@placeholders]
52+
(if (custom-zipper? ~(first placeholders))
53+
(let [~@(interleave params placeholders)]
54+
~@body)
55+
(~(symbol "clojure.zip" (name sym)) ~@placeholders)))))
56+
57+
;; ## Implementation
2758

28-
(defn node
59+
(defn-switchable node
2960
"Returns the node at loc"
3061
[{:keys [node]}]
3162
node)
3263

33-
(defn branch?
64+
(defn-switchable branch?
3465
"Returns true if the node at loc is a branch"
3566
[{:keys [node]}]
3667
(node/inner? node))
3768

38-
(defn children
69+
(defn-switchable children
3970
"Returns a seq of the children of node at loc, which must be a branch"
4071
[{:keys [node] :as loc}]
4172
(if (branch? loc)
4273
(seq (node/children node))
4374
(throw (Exception. "called children on a leaf node"))))
4475

45-
(defn ^:no-doc make-node
76+
(defn-switchable ^:no-doc make-node
4677
"Returns a new branch node, given an existing node and new
4778
children. The loc is only used to supply the constructor."
4879
[loc node children]
@@ -51,28 +82,35 @@
5182
(defn position
5283
"Returns the ones-based [row col] of the start of the current node"
5384
[loc]
54-
(:position loc))
85+
(if (custom-zipper? loc)
86+
(:position loc)
87+
(throw
88+
(IllegalStateException.
89+
(str
90+
"to use the 'position' function, please construct your zipper with "
91+
"':track-position?' set to true.")))))
5592

56-
(defn lefts
93+
(defn-switchable lefts
5794
"Returns a seq of the left siblings of this loc"
5895
[loc]
5996
(map first (:left loc)))
6097

61-
(defn down
98+
(defn-switchable down
6299
"Returns the loc of the leftmost child of the node at this loc, or
63100
nil if no children"
64101
[loc]
65102
(when (branch? loc)
66103
(let [{:keys [node path] [row col] :position} loc
67104
[c & cnext :as cs] (children loc)]
68105
(when cs
69-
{:node c
106+
{::custom? true
107+
:node c
70108
:position [row (+ col (node/leader-length node))]
71-
:parent loc
72-
:left []
73-
:right cnext}))))
109+
:parent loc
110+
:left []
111+
:right cnext}))))
74112

75-
(defn up
113+
(defn-switchable up
76114
"Returns the loc of the parent of the node at this loc, or nil if at
77115
the top"
78116
[loc]
@@ -86,7 +124,7 @@
86124
(concat (map first left) (cons node right))))
87125
parent))))
88126

89-
(defn root
127+
(defn-switchable root
90128
"zips all the way up and returns the root node, reflecting any changes."
91129
[{:keys [end?] :as loc}]
92130
(if end?
@@ -96,7 +134,7 @@
96134
(recur p)
97135
(node loc)))))
98136

99-
(defn right
137+
(defn-switchable right
100138
"Returns the loc of the right sibling of the node at this loc, or nil"
101139
[loc]
102140
(let [{:keys [node parent position left] [r & rnext :as right] :right} loc]
@@ -107,14 +145,14 @@
107145
:right rnext
108146
:position (node/+extent position (node/extent node))))))
109147

110-
(defn rightmost
148+
(defn-switchable rightmost
111149
"Returns the loc of the rightmost sibling of the node at this loc, or self"
112150
[loc]
113151
(if-let [next (right loc)]
114152
(recur next)
115153
loc))
116154

117-
(defn left
155+
(defn-switchable left
118156
"Returns the loc of the left sibling of the node at this loc, or nil"
119157
[loc]
120158
(let [{:keys [node parent left right]} loc]
@@ -126,7 +164,7 @@
126164
:left (pop left)
127165
:right (cons node right))))))
128166

129-
(defn leftmost
167+
(defn-switchable leftmost
130168
"Returns the loc of the leftmost sibling of the node at this loc, or self"
131169
[loc]
132170
(let [{:keys [node parent left right]} loc]
@@ -139,7 +177,7 @@
139177
:right (concat (map first (rest left)) [node] right)))
140178
loc)))
141179

142-
(defn insert-left
180+
(defn-switchable insert-left
143181
"Inserts the item as the left sibling of the node at this loc,
144182
without moving"
145183
[loc item]
@@ -151,7 +189,7 @@
151189
:left (conj left [item position])
152190
:position (node/+extent position (node/extent item))))))
153191

154-
(defn insert-right
192+
(defn-switchable insert-right
155193
"Inserts the item as the right sibling of the node at this loc,
156194
without moving"
157195
[loc item]
@@ -162,29 +200,31 @@
162200
:changed? true
163201
:right (cons item right)))))
164202

165-
(defn replace
203+
(defn-switchable replace
166204
"Replaces the node at this loc, without moving"
167205
[loc node]
168206
(assoc loc :changed? true :node node))
169207

170208
(defn edit
171209
"Replaces the node at this loc with the value of (f node args)"
172210
[loc f & args]
173-
(replace loc (apply f (node loc) args)))
211+
(if (custom-zipper? loc)
212+
(replace loc (apply f (node loc) args))
213+
(apply clj-zip/edit loc f args)))
174214

175-
(defn insert-child
215+
(defn-switchable insert-child
176216
"Inserts the item as the leftmost child of the node at this loc,
177217
without moving"
178218
[loc item]
179219
(replace loc (make-node loc (node loc) (cons item (children loc)))))
180220

181-
(defn append-child
221+
(defn-switchable append-child
182222
"Inserts the item as the rightmost child of the node at this loc,
183223
without moving"
184224
[loc item]
185225
(replace loc (make-node loc (node loc) (concat (children loc) [item]))))
186226

187-
(defn next
227+
(defn-switchable next
188228
"Moves to the next loc in the hierarchy, depth-first. When reaching
189229
the end, returns a distinguished loc detectable via end?. If already
190230
at the end, stays there."
@@ -199,7 +239,7 @@
199239
(or (right (up p)) (recur (up p)))
200240
(assoc p :end? true))))))
201241

202-
(defn prev
242+
(defn-switchable prev
203243
"Moves to the previous loc in the hierarchy, depth-first. If already
204244
at the root, returns nil."
205245
[loc]
@@ -210,12 +250,12 @@
210250
loc))
211251
(up loc)))
212252

213-
(defn end?
253+
(defn-switchable end?
214254
"Returns true if loc represents the end of a depth-first walk"
215255
[loc]
216256
(:end? loc))
217257

218-
(defn remove
258+
(defn-switchable remove
219259
"Removes the node at loc, returning the loc that would have preceded
220260
it in a depth-first walk."
221261
[loc]
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
(ns ^:no-doc rewrite-clj.custom-zipper.utils
2+
(:require [rewrite-clj.custom-zipper.core :as z]))
3+
4+
;; ## Remove
5+
6+
(defn- update-in-path
7+
[[node path :as loc] k f]
8+
(let [v (get path k)]
9+
(if (seq v)
10+
(with-meta
11+
[node (assoc path k (f v) :changed? true)]
12+
(meta loc))
13+
loc)))
14+
15+
(defn remove-right
16+
"Remove right sibling of the current node (if there is one)."
17+
[loc]
18+
(if (z/custom-zipper? loc)
19+
(let [{[r & rs] :right} loc]
20+
(assoc loc
21+
:right rs
22+
:changed? true))
23+
(update-in-path loc :r next)))
24+
25+
(defn remove-left
26+
"Remove left sibling of the current node (if there is one)."
27+
[loc]
28+
(if (z/custom-zipper? loc)
29+
(let [{:keys [left]} loc]
30+
(if-let [[_ lpos] (peek left)]
31+
(assoc loc
32+
:left (pop left)
33+
:position lpos
34+
:changed? true)
35+
loc))
36+
(update-in-path loc :l pop)))
37+
38+
(defn remove-right-while
39+
"Remove elements to the right of the current zipper location as long as
40+
the given predicate matches."
41+
[zloc p?]
42+
(loop [zloc zloc]
43+
(if-let [rloc (z/right zloc)]
44+
(if (p? rloc)
45+
(recur (remove-right zloc))
46+
zloc)
47+
zloc)))
48+
49+
(defn remove-left-while
50+
"Remove elements to the left of the current zipper location as long as
51+
the given predicate matches."
52+
[zloc p?]
53+
(loop [zloc zloc]
54+
(if-let [lloc (z/left zloc)]
55+
(if (p? lloc)
56+
(recur (remove-left zloc))
57+
zloc)
58+
zloc)))
59+
60+
;; ## Remove and Move
61+
62+
(defn remove-and-move-left
63+
"Remove current node and move left. If current node is at the leftmost
64+
location, returns `nil`."
65+
[loc]
66+
(if (z/custom-zipper? loc)
67+
(let [{:keys [position left]} loc]
68+
(if (seq left)
69+
(let [[lnode lpos] (peek left)]
70+
(assoc loc
71+
:changed? true
72+
:node lnode
73+
:position lpos
74+
:left (pop left)))))
75+
(let [[_ {:keys [l] :as path}] loc]
76+
(if (seq l)
77+
(with-meta
78+
[(peek l) (-> path
79+
(update-in [:l] pop)
80+
(assoc :changed? true))]
81+
(meta loc))))))
82+
83+
(defn remove-and-move-right
84+
"Remove current node and move right. If current node is at the rightmost
85+
location, returns `nil`."
86+
[loc]
87+
(if (z/custom-zipper? loc)
88+
(let [{:keys [position right]} loc]
89+
(if (seq right)
90+
(assoc loc
91+
:changed? true
92+
:node (first right)
93+
:right (next right))))
94+
95+
(let [[_ {:keys [r] :as path}] loc]
96+
(if (seq r)
97+
(with-meta
98+
[(first r) (-> path
99+
(update-in [:r] next)
100+
(assoc :changed? true))]
101+
(meta loc))))))

src/rewrite_clj/zip.clj

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
[rewrite-clj
1818
[parser :as p]
1919
[node :as node]]
20-
[rewrite-clj.zip.zip :as z]))
20+
[rewrite-clj.custom-zipper.core :as z]))
2121

2222
;; ## API Facade
2323

2424
(import-vars
25-
[rewrite-clj.zip.zip
25+
[rewrite-clj.custom-zipper.core
2626
node position root]
2727

2828
[rewrite-clj.zip.base
@@ -92,19 +92,19 @@
9292
:arglists `(quote ~arglists)})]
9393
`(def ~sym ~base)))
9494

95-
(defbase right* rewrite-clj.zip.zip/right)
96-
(defbase left* rewrite-clj.zip.zip/left)
97-
(defbase up* rewrite-clj.zip.zip/up)
98-
(defbase down* rewrite-clj.zip.zip/down)
99-
(defbase next* rewrite-clj.zip.zip/next)
100-
(defbase prev* rewrite-clj.zip.zip/prev)
101-
(defbase rightmost* rewrite-clj.zip.zip/rightmost)
102-
(defbase leftmost* rewrite-clj.zip.zip/leftmost)
103-
(defbase replace* rewrite-clj.zip.zip/replace)
104-
(defbase edit* rewrite-clj.zip.zip/edit)
105-
(defbase remove* rewrite-clj.zip.zip/remove)
106-
(defbase insert-left* rewrite-clj.zip.zip/insert-left)
107-
(defbase insert-right* rewrite-clj.zip.zip/insert-right)
95+
(defbase right* rewrite-clj.custom-zipper.core/right)
96+
(defbase left* rewrite-clj.custom-zipper.core/left)
97+
(defbase up* rewrite-clj.custom-zipper.core/up)
98+
(defbase down* rewrite-clj.custom-zipper.core/down)
99+
(defbase next* rewrite-clj.custom-zipper.core/next)
100+
(defbase prev* rewrite-clj.custom-zipper.core/prev)
101+
(defbase rightmost* rewrite-clj.custom-zipper.core/rightmost)
102+
(defbase leftmost* rewrite-clj.custom-zipper.core/leftmost)
103+
(defbase replace* rewrite-clj.custom-zipper.core/replace)
104+
(defbase edit* rewrite-clj.custom-zipper.core/edit)
105+
(defbase remove* rewrite-clj.custom-zipper.core/remove)
106+
(defbase insert-left* rewrite-clj.custom-zipper.core/insert-left)
107+
(defbase insert-right* rewrite-clj.custom-zipper.core/insert-right)
108108

109109
;; ## DEPRECATED
110110

0 commit comments

Comments
 (0)