rewrite-clj icon indicating copy to clipboard operation
rewrite-clj copied to clipboard

Zipper end state behaves differently for skip vs raw movement

Open lread opened this issue 4 years ago • 1 comments

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

lread avatar May 10 '21 20:05 lread

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

lread avatar May 12 '21 19:05 lread