Zipper end state behaves differently for skip vs raw movement
Not sure if this is a bug or a behavior to document, just recording some notes for now.
Version Pre-existing in v0
Platform All
Behaviour
Let's explore what happens with next (whitespace aware) vs next* (raw) in rewrite-clj.
These tests were carried out on v0.6.1 just to make sure this behaviour was not something v1 code introduced.
-- Variation 1 next on underlying clojure.zip zipper
stays on the last node when end is hit, zipper is still traversable:
(require '[clojure.zip :as z])
(->> "[1 2 3]"
z/of-string
(iterate z/next)
(take 10)
(map (juxt z/string z/end?)))
;; => (["[1 2 3]" nil]
;; ["1" nil]
;; ["2" nil]
;; ["3" nil]
;; ["3" true]
;; ["3" true]
;; ["3" true]
;; ["3" true]
;; ["3" true]
;; ["3" true])
(->> "[1 2 3]"
z/of-string
(iterate z/next)
(take 10)
last
z/prev
z/string)
;; => "2"
-- Variation 2 next* on underlying clojure.zip zipper
hits an end state, node becomes root node, zipper is no longer traversable:
(->> "[1 2 3]"
z/of-string
(iterate z/next*)
(take 10)
(map (juxt z/string z/end?)))
;; => (["[1 2 3]" nil]
;; ["1" nil]
;; [" " nil]
;; ["2" nil]
;; [" " nil]
;; ["3" nil]
;; ["[1 2 3]" true]
;; ["[1 2 3]" true]
;; ["[1 2 3]" true]
;; ["[1 2 3]" true]
-- Variation 3 next on underlying "custom zipper"
rewrite-clj's zipper that supports position tracking, behaviour is same as next on underlying clojure.zip zipper.
(->> (z/of-string "[1 2 3]" {:track-position? true})
(iterate z/next)
(take 10)
(map (juxt z/string z/position z/end?)))
;; => (["[1 2 3]" [1 1] nil]
;; ["1" [1 2] nil]
;; ["2" [1 4] nil]
;; ["3" [1 6] nil]
;; ["3" [1 6] true]
;; ["3" [1 6] true]
;; ["3" [1 6] true]
;; ["3" [1 6] true]
;; ["3" [1 6] true]
;; ["3" [1 6] true])
(->> (z/of-string "[1 2 3]" {:track-position? true})
(iterate z/next)
(take 10)
last
z/prev
((juxt z/string z/position)))
;; => ["2" [1 4]]
-- Variation 4 next* on underlying position tracking zipper
behaviour is same as next* on underlying clojure.zip zipper, we end up at root node and zipper is no longer traversable:
(->> (z/of-string "[1 2 3]" {:track-position? true})
(iterate z/next*)
(take 10)
(map (juxt z/string z/position z/end?)))
;; => (["[1 2 3]" [1 1] nil]
;; ["1" [1 2] nil]
;; [" " [1 3] nil]
;; ["2" [1 4] nil]
;; [" " [1 5] nil]
;; ["3" [1 6] nil]
;; ["[1 2 3]" [1 1] true]
;; ["[1 2 3]" [1 1] true]
;; ["[1 2 3]" [1 1] true]
;; ["[1 2 3]" [1 1] true])
So at least we are consistent for next* and next for custom and clojure zipper.
-- Variation 5 - Sanity check on clojure.zip zipper
And just a quick check on using a clojure.zip zipper outside of rewrite-clj (yeah, same behaviour as next* in rewrite-clj):
(require '[clojure.zip :as czip]
(->> '[1 2 3]
czip/vector-zip
(iterate czip/next)
(take 10)
(map (juxt czip/node czip/end?)))
;; => ([[1 2 3] false]
;; [1 false]
;; [2 false]
;; [3 false]
;; [[1 2 3] true]
;; [[1 2 3] true]
;; [[1 2 3] true]
;; [[1 2 3] true]
;; [[1 2 3] true]
;; [[1 2 3] true])
(->> '[1 2 3]
czip/vector-zip
(iterate czip/next)
(take 10)
last
;; we are at root can we navigate down?
czip/down)
;; => Execution error (NullPointerException) at fiddle/eval10795 (REPL:514).
;; null
Current perceived impact:
Zipper end state is confusing:
- for skip movement, end is past last node but we stay on last node, zipper is still usable
- for raw movement, end is past last node, we state in this state, zipper is no longer usable