1 Introduction
1.1 Overview
std.lib.zip is part of the standard foundation library set. This page collects the public API reference for the namespace.
2 Walkthrough
2.1 Creating zippers
vector-zip and seq-zip construct zippers over vectors and sequences. from-status parses a form that already contains a | cursor marker.
create zippers from collections
(vector-zip [1 2 3 4 5])
=> (contains {:left ()
:right '([1 2 3 4 5])})
(seq-zip '(1 2 3 4 5))
=> (contains {:left ()
:right '((1 2 3 4 5))})
parse a form with a cursor
(from-status '[1 2 3 | 4])
=> (contains {:left '(3 2 1)
:right '(4)})
2.2 Moving around
Step into, out of, left, and right within the tree. status renders the zipper back into a readable form with the cursor visible.
step inside and right
(-> (from-status '[1 2 | [3 4]])
(step-inside)
(status))
=> '([1 2 [| 3 4]])
step left and right
(-> (from-status '[1 2 [3 4 |]])
(step-left)
(status))
=> '([1 2 [3 | 4]])
walk to the outside-most point
(-> (from-status '[1 2 [| 3 4]])
(step-outside-most)
(status))
=> '(| [1 2 [3 4]])
2.3 Editing
Insert, delete, and replace elements around the cursor, then use root-element to read the modified tree.
insert elements left of the cursor
(-> (from-status '[1 2 [[| 3] 4]])
(insert-left 1 2 3)
(status))
=> '([1 2 [[1 2 3 | 3] 4]])
delete the element right of the cursor
(-> (from-status '[1 2 | 3])
(delete-right)
(status))
=> '([1 2 |])
replace the element right of the cursor
(-> (from-status '[1 2 | 3])
(replace-right "10")
(status))
=> '([1 2 | "10"])
2.4 Searching
find-next, find-prev, find-right, and find-left move the cursor to the first element that satisfies a predicate.
find a nested value
(-> (vector-zip [1 [2 [6 7] 3] [4 5]])
(find-next #{7})
(status))
=> '([1 [2 [6 | 7] 3] [4 5]])
find right for an even number
(-> (from-status '[0 | 1 [2 3] [4 5] 6])
(find-right even?)
(status))
=> '([0 1 [2 3] [4 5] | 6])
2.5 Walking
prewalk and postwalk apply a function to every node of the zipper, similar to std.lib.walk but with explicit navigation.
prewalk over a vector zipper
(-> (vector-zip [[1 2] [3 4]])
(prewalk (fn [v] (if (vector? v)
(conj v 100)
(+ v 100))))
(root-element))
=> [[101 102 200] [103 104 200] 200]
postwalk over a vector zipper
(-> (vector-zip [[1 2] [3 4]])
(postwalk (fn [v] (if (vector? v)
(conj v 100)
(+ v 100))))
(root-element))
=> [[101 102 100] [103 104 100] 100]
2.6 End-to-end: navigate, edit, and read back
A common workflow: create a zipper, navigate to a specific node, replace it, and extract the modified root.
replace the first odd number in a nested vector
(-> (vector-zip [2 [4 [5 6]] 8])
(find-next odd?)
(replace-right 99)
(root-element))
=> [2 [4 [99 6]] 8]
3 API
- *handler*
- +base+
- +nil-handler+
- +types+
- at-end?
- at-inside-most-left?
- at-inside-most?
- at-left-most?
- at-outside-most?
- at-right-most?
- can-step-inside-left?
- can-step-inside?
- can-step-left?
- can-step-outside?
- can-step-right?
- check-context
- check-optional
- current-elements
- delete-left
- delete-right
- display-zipper
- find
- find-left
- find-next
- find-prev
- find-right
- form-zip
- form-zip-unwrap
- form-zip-wrap
- from-status
- get
- hierarchy
- insert-left
- insert-right
- is
- is-container?
- is-empty-container?
- left-element
- left-elements
- levelwalk
- list-child-elements
- matchwalk
- postwalk
- prewalk
- register-type
- replace-left
- replace-right
- right-element
- right-elements
- root-element
- seq-zip
- status
- status-string
- step-end
- step-inside
- step-inside-left
- step-inside-most
- step-inside-most-left
- step-left
- step-left-most
- step-next
- step-outside
- step-outside-most
- step-outside-most-right
- step-outside-right
- step-prev
- step-right
- step-right-most
- surround
- unregister-type
- unwrap-element
- update-child-elements
- vector-zip
- zipper
- zipper?
v 3.0
(defn at-end?
([{:keys [context] :as zip}]
(and ((:at-right-most? context) zip)
(->> (hierarchy zip)
(map step-right)
(every? (:at-right-most? context))))))
link
(->> (from-status '[1 [[|]]]) (at-end?)) => true (->> (from-status '[1 [[[2 |]] 3]]) (at-end?)) => false (->> (from-status '[1 [[[2 |]] 3]]) (at-end?))
v 3.0
(defn at-inside-most-left?
([zip]
(or (empty? (:left zip))
(not (is-container? zip :left)))))
link
(-> (from-status '[1 2 [| 1 2]]) (at-inside-most-left?)) => true
v 3.0
(defn at-inside-most?
([zip]
(or (empty? (:right zip))
(not (is-container? zip :right)))))
link
(-> (from-status '[1 2 [3 4 |]]) (at-inside-most?)) => true
v 3.0
(defn at-left-most?
([zip]
(empty? (:left zip))))
link
(-> (from-status [1 2 ['| 3 4] 5 6]) (at-left-most?)) => true
v 3.0
(defn at-outside-most?
([zip]
(nil? (:parent zip))))
link
(-> (from-status [1 2 [3 4 '|]]) (at-outside-most?)) => false (-> (from-status '[1 2 [3 4 |]]) (step-outside) (step-outside) (at-outside-most?)) => true
v 3.0
(defn at-right-most?
([zip]
(empty? (:right zip))))
link
(-> (from-status '[1 2 [3 4 |]]) (at-right-most?)) => true
can-step-inside-left? ^
[{:keys [context], :as zip}]
check if can step left inside a container
v 3.0
(defn can-step-inside-left?
([{:keys [context] :as zip}]
(not ((:at-inside-most-left? context) zip))))
link
(-> (from-status '[[3 4] |]) (can-step-inside-left?)) => true
can-step-inside? ^
[{:keys [context], :as zip}]
check if can step down from current status
v 3.0
(defn can-step-inside?
([{:keys [context] :as zip}]
(not ((:at-inside-most? context) zip))))
link
(-> (from-status '[1 2 [3 4 |]]) (can-step-inside?)) => false (-> (from-status '[1 2 | [3 4]]) (can-step-inside?)) => true
can-step-left? ^
[{:keys [context], :as zip}]
check if can step left from current status
v 3.0
(defn can-step-left?
([{:keys [context] :as zip}]
(not ((:at-left-most? context) zip))))
link
(-> (from-status '[1 2 [3 | 4]]) (can-step-left?)) => true (-> (from-status '[1 2 [| 3 4]]) (can-step-left?)) => false
can-step-outside? ^
[{:keys [context], :as zip}]
check if can step up from current status
v 3.0
(defn can-step-outside?
([{:keys [context] :as zip}]
(not ((:at-outside-most? context) zip))))
link
(-> (from-status '[1 2 [3 4 |]]) (can-step-outside?)) => true
can-step-right? ^
[{:keys [context], :as zip}]
check if can step right from current status
v 3.0
(defn can-step-right?
([{:keys [context] :as zip}]
(not ((:at-right-most? context) zip))))
link
(-> (from-status '[1 2 [3 | 4]]) (can-step-right?)) => true (-> (from-status '[1 2 [3 4 |]]) (can-step-right?)) => false
v 3.0
(defn check-context
([context]
(let [missing (->> [:create-container
:create-element
:cursor
:is-container?
:is-empty-container?
:is-element?
:list-elements
:update-elements
:add-element
:at-left-most?
:at-right-most?
:at-inside-most?
:at-inside-most-left?
:at-outside-most?]
(remove context))]
(if (seq missing)
(throw (ex-info "Missing keys." {:keys missing}))
context))))
link
(check-context {}) => (throws)
v 3.0
(defn check-optional
([context]
(let [missing (->> [:update-step-left
:update-step-right
:update-step-inside
:update-step-inside-left
:update-step-outside
:update-delete-left
:update-delete-right
:update-insert-left
:update-insert-right
:wrap-data
:unwrap-data]
(remove context))]
(if (seq missing)
(throw (ex-info "Missing keys." {:keys missing}))
context))))
link
(check-optional {}) => (throws)
current-elements ^
[{:keys [left right], :as zip}]
all elements left and right of current position
v 3.0
(defn current-elements
([{:keys [left right] :as zip}]
(concat (reverse left) right)))
link
(-> (from-status '[1 2 | 3 4]) (current-elements)) => '(1 2 3 4) (-> (from-status '[1 [2 | 3] 4]) (current-elements)) => '(2 3)
delete-left ^
[{:keys [context left], :as zip}] [{:keys [context], :as zip} n]
delete element/s left of the current status
v 3.0
(defn delete-left
([{:keys [context left] :as zip}]
(cond ((:at-left-most? context) zip)
(if-let [zip-fn (get-in *handler* [:delete :at-left-most])]
(zip-fn zip)
zip)
:else
(let [elem (first left)]
(-> zip
(update-in [:left] rest)
(assoc :changed? true)
(h/call (:update-delete-left context) elem)))))
([{:keys [context] :as zip} n]
(nth (iterate delete-left zip) n)))
link
(-> (from-status '[1 2 | 3]) (delete-left) (status)) => '([1 | 3])
delete-right ^
[{:keys [context right], :as zip}] [{:keys [context], :as zip} n]
delete element/s right of the current status
v 3.0
(defn delete-right
([{:keys [context right] :as zip}]
(cond ((:at-right-most? context) zip)
(if-let [zip-fn (get-in *handler* [:delete :at-right-most])]
(zip-fn zip)
zip)
:else
(let [elem (first right)]
(-> zip
(update-in [:right] rest)
(assoc :changed? true)
(h/call (:update-delete-right context) elem)))))
([{:keys [context] :as zip} n]
(nth (iterate delete-right zip) n)))
link
(-> (from-status '[1 2 | 3]) (delete-right) (status)) => '([1 2 |])
v 4.0
(defn display-zipper
[{:keys [parent context] :as zip}]
#_(dissoc zip :context :prefix :display :changed?)
(cond-> zip
:then (dissoc :context :prefix :display :changed?)
parent (assoc :parent
(root-element
(replace-right parent
((:update-elements context)
(first (:right parent))
[((:create-element context)
'...)]))))))
link
v 3.0
(defn find
([zip move pred]
(->> (iterate move zip)
(drop 1)
(take-while right-element)
(filter #(try (pred (right-element %))
(catch Throwable t)))
(first))))
link
v 3.0
(defn find-left
([zip pred]
(binding [*handler* (assoc-in *handler*
[:step :at-left-most]
(fn [_] nil))]
(find zip step-left pred))))
link
(-> (from-status '[0 1 [2 3] [4 5] 6 |]) (find-left odd?) (status)) => '([0 | 1 [2 3] [4 5] 6]) (-> (from-status '[0 1 [2 3] [4 5] 6 |]) (find-left keyword?)) => nil
find-next ^
[zip pred]
step status through the tree in depth first order to the first matching element
v 3.0
(defn find-next
([zip pred]
(find zip step-next pred)))
link
(-> (vector-zip [1 [2 [6 7] 3] [4 5]]) (find-next #{7}) (status)) => '([1 [2 [6 | 7] 3] [4 5]]) (-> (vector-zip [1 [2 [6 7] 3] [4 5]]) (find-next #{3}) (status)) (-> (vector-zip [1 [2 [6 7] 3] [4 5]]) (find-next keyword)) => nil
find-prev ^
[zip pred]
step status through the tree in reverse order to the last matching element
v 3.0
(defn find-prev
([zip pred]
(find zip step-prev pred)))
link
(-> (from-status '[1 [2 [6 | 7] 3] [4 5]]) (find-prev even?) (status)) => '([1 [2 [| 6 7] 3] [4 5]]) (-> (from-status '[1 [2 [[[6 7]]] [3]] | [4 5]]) (find-prev even?) (status)) => '([1 [2 [[[| 6 7]]] [3]] [4 5]])
v 3.0
(defn find-right
([zip pred]
(binding [*handler* (assoc-in *handler*
[:step :at-right-most]
(fn [_] nil))]
(find zip step-right pred))))
link
(-> (from-status '[0 | 1 [2 3] [4 5] 6]) (find-right even?) (status)) => '([0 1 [2 3] [4 5] | 6])
v 4.0
(defn form-zip
([root]
(form-zip root nil))
([root opts]
(zipper root
(merge {:wrap-data form-zip-wrap
:unwrap-data form-zip-unwrap
:create-container (fn [] '()) ; Default to an empty list for new containers
:create-element identity
:is-container? (fn [x] (or (list? x) (vector? x) (map? x) (set? x)))
:is-empty-container? empty?
:is-element? (complement nil?)
:list-elements seq
:update-elements (fn [container new-elements]
;; Reconstructs the container, preserving its original type
(cond
(list? container) (apply list new-elements)
(vector? container) (vec new-elements)
(map? container) (into {} new-elements)
(set? container) (into #{} new-elements)
:else (throw (ex-info "Unsupported container type for update" {:type (type container)}))))
:add-element (fn [container element]
;; Adds an element, preserving container type.
;; Note: conj behavior varies by collection type (front for lists, end for vectors).
(cond
(list? container) (concat container [element])
(vector? container) (conj container element)
(map? container) (conj container element)
(set? container) (conj container element)
:else (throw (ex-info "Unsupported container type for add" {:type (type container)}))))}
+base+)
opts)))
link
(form-zip :hello) (-> (form-zip '([x] 1)) (step-next) (status)) => '((| [x] 1)) (-> (form-zip '([x] 1)) (step-next) (step-next) (step-next) (status)) => '(([x] | 1)) (-> (form-zip '([nil] 1)) (step-next) (step-next) (step-next) (status)) (-> (form-zip '([nil] 1)) (step-next) (step-next) (step-next) (status)) => '(([nil] | 1)) (-> (form-zip '(1 nil 2 3)) (step-next) (step-next) (step-next) (status)) => '((1 nil | 2 3)) (-> (form-zip '(defn hello [x] (+ 1 2 3))) (find-next #{3}) (status)) => '((defn hello [x] (+ 1 2 | 3))) (-> (form-zip {:b 2 :a 1 }) (step-inside) (insert-left [:c 4]) (step-outside) (status)) => '(| {:c 4, :b 2, :a 1}) (-> (form-zip #{:b :c :a}) (step-inside) (insert-left :e) (step-outside) (status)) => '(| #{:e :c :b :a})
v 4.0
(defn form-zip-wrap
[form]
(walk/prewalk
(fn [x]
(if (nil? x)
(h/wrapped x nil nil pr-str)
x))
form))
link
from-status ^
[data] [data zipper-fn]
returns a zipper given a data structure with | as the status
v 3.0
(defn from-status
([data]
(from-status data vector-zip))
([data zipper-fn]
(let [{:keys [context] :as zip} (zipper-fn data)]
(if-let [nzip (-> zip
(find-next #(zero? (compare (:cursor context) %))))]
(delete-right nzip)
zip))))
link
(from-status '[1 2 3 | 4]) => (contains {:left '(3 2 1), :right '(4)})
v 3.0
(defn get
([zip]
(get zip identity :right))
([zip arg]
(if (or (= arg :left)
(= arg :right))
(get zip identity arg)
(get zip arg :right)))
([zip func step]
(let [elem (first (clojure.core/get zip step))]
(func elem))))
link
(-> (vector-zip [0 1 2 3 4]) (step-inside) (get)) => 0
v 3.0
(defn hierarchy
([{:keys [context] :as zip}]
(loop [out []
zip (step-outside zip)]
(if ((:at-outside-most? context) zip)
(conj out zip)
(recur (conj out zip) (step-outside zip))))))
link
(->> (from-status '[1 [[|]]]) (hierarchy) (map right-element)) => [[] [[]] [1 [[]]]]
insert-left ^
[{:keys [context], :as zip} data] [{:keys [context], :as zip} data & more]
insert element/s left of the current status
v 3.0
(defn insert-left
([{:keys [context] :as zip} data]
(let [create-fn (:create-element context)
elem (create-fn data)]
(-> zip
(update-in [:left] #(cons elem %))
(assoc :changed? true)
(h/call (:update-insert-left context) elem))))
([{:keys [context] :as zip} data & more]
(apply insert-left (insert-left zip data) more)))
link
(-> (from-status '[1 2 [[| 3] 4]]) (insert-left 1 2 3) (status)) => '([1 2 [[1 2 3 | 3] 4]])
insert-right ^
[{:keys [context], :as zip} data] [{:keys [context], :as zip} data & more]
inserts element/s right of the current status
v 4.0
(defn insert-right
([{:keys [context] :as zip} data]
(let [create-fn (:create-element context)
elem (create-fn data)]
(-> zip
(update-in [:right] #(cons elem %))
(assoc :changed? true)
(h/call (:update-insert-right context) elem))))
([{:keys [context] :as zip} data & more]
(apply insert-right (insert-right zip data) more)))
link
v 3.0
(defn is
([zip pred]
(is zip pred :right))
([zip pred step]
(let [elem (first (clojure.core/get zip step))]
(try
(or (= elem pred)
(if (and (ifn? pred)
(not (coll? pred)))
(pred elem))
(zero? (compare elem pred)))
(catch Throwable t
false)))))
link
(-> (vector-zip [0 1 2 3 4]) (step-inside) (is zero?)) => true
is-container? ^
[zip] [{:keys [context], :as zip} step]
checks if node on either side is a container
v 3.0
(defn is-container?
([zip]
(is-container? zip :right))
([{:keys [context] :as zip} step]
(is zip (-> context :is-container?) step)))
link
(-> (vector-zip [1 2 3]) (is-container? :right)) => true (-> (vector-zip [1 2 3]) (is-container? :left)) => false
is-empty-container? ^
[zip] [{:keys [context], :as zip} step]
check if current container is empty
v 3.0
(defn is-empty-container?
([zip]
(is-empty-container? zip :right))
([{:keys [context] :as zip} step]
(is zip (-> context :is-empty-container?) step)))
link
(-> (vector-zip []) (is-empty-container?)) => true
v 3.0
(defn left-element
([zip]
(first (:left zip))))
link
(-> (from-status '[1 2 3 | 4]) (left-element)) => 3 (-> (from-status '[1 [2 [| 3]]]) (left-element)) => nil (-> (from-status '[1 [2 [nil | 3]]]) (left-element)) => nil
v 3.0
(defn left-elements
([zip]
(reverse (:left zip))))
link
(-> (from-status '[1 2 | 3 4]) (left-elements)) => '(1 2)
levelwalk ^
[zip [pred] f] [zip [pred] f levelwalk {:keys [move-right can-move-right?], :as opts}]
performs a match at the same level
v 3.0
(defn levelwalk
([zip [pred] f]
(levelwalk zip [pred] f levelwalk {}))
([zip [pred] f levelwalk {:keys [move-right
can-move-right?]
:as opts}]
(let [zip (if (try (pred zip)
(catch Throwable t))
(f zip)
zip)
zip (if ((or can-move-right?
can-step-right?) zip)
(levelwalk ((or move-right
step-right) zip) [pred] f levelwalk opts)
zip)]
zip)))
link
(-> (vector-zip [1 2 3 4]) (step-inside) (levelwalk [(fn [zip] (odd? (right-element zip)))] delete-right) (root-element)) => [2 4]
list-child-elements ^
[zip] [{:keys [context], :as zip} direction]
lists elements of a container
v 3.0
(defn list-child-elements
([zip]
(list-child-elements zip :right))
([{:keys [context] :as zip} direction]
(let [elem (first (clojure.core/get zip direction))
check-fn (:is-container? context)
list-fn (:list-elements context)]
(if (check-fn elem)
(list-fn elem)
(throw (ex-info "Not a org." {:element elem}))))))
link
(-> (vector-zip [1 2 3]) (list-child-elements :right)) => '(1 2 3) (-> (vector-zip 1) (list-child-elements :right)) => (throws)
matchwalk ^
[zip matchers f] [zip [pred & more :as matchers] f matchwalk {:keys [move-right can-move-right?], :as opts}]
performs a match at each level
v 3.0
(defn matchwalk
([zip matchers f]
(matchwalk zip matchers f matchwalk {}))
([zip [pred & more :as matchers] f matchwalk {:keys [move-right
can-move-right?]
:as opts}]
(let [zip (if (try (pred zip)
(catch Throwable t))
(cond (empty? more)
(f zip)
(can-step-inside? zip)
(step-outside (matchwalk (step-inside zip) more f matchwalk opts))
:else
zip)
zip)
zip (if (can-step-inside? zip)
(step-outside (matchwalk (step-inside zip) matchers f matchwalk opts))
zip)
zip (if ((or can-move-right?
can-step-right?) zip)
(matchwalk ((or move-right
step-right) zip) matchers f matchwalk opts)
zip)]
zip)))
link
(-> (matchwalk (vector-zip [1 [2 [3 [4]]]]) [(fn [zip] (= 2 (first (right-element zip)))) (fn [zip] (= 4 (first (right-element zip))))] delete-left) (root-element)) => [1 [2 [[4]]]]
v 3.0
(defn postwalk
([zip f]
(let [zip (cond (can-step-inside? zip)
(loop [zip (step-inside zip)]
(let [zip (postwalk zip f)]
(cond (can-step-right? zip)
(recur (step-right zip))
:else
(step-outside zip))))
:else zip)]
(if (can-step-right? zip)
(let [elem (right-element zip)]
(replace-right zip (f elem)))
zip))))
link
(-> (vector-zip [[1 2] [3 4]]) (postwalk (fn [v] (if (vector? v) (conj v 100) (+ v 100)))) (root-element)) => [[101 102 100] [103 104 100] 100]
prewalk ^
[{:keys [context], :as zip} f]
emulates std.lib.walk/prewalk behavior with zipper
v 3.0
(defn prewalk
([{:keys [context] :as zip} f]
(let [elem (right-element zip)
zip (replace-right zip (f elem))]
(cond (can-step-inside? zip)
(loop [zip (step-inside zip)]
(let [zip (-> (prewalk zip f)
(step-right))]
(cond (can-step-right? zip)
(recur zip)
:else
(step-outside zip))))
:else zip))))
link
(-> (vector-zip [[1 2] [3 4]]) (prewalk (fn [v] (if (vector? v) (conj v 100) (+ v 100)))) (root-element)) => [[101 102 200] [103 104 200] 200]
v 4.0
(defn register-type
[type context]
(swap! +types+ assoc type context))
link
(resolve 'register-type) => var?
v 3.0
(defn replace-left
([zip data]
(-> zip
(delete-left)
(insert-left data))))
link
(-> (from-status '[1 2 | 3]) (replace-left "10") (status)) => '([1 "10" | 3])
v 3.0
(defn replace-right
([zip data]
(-> zip
(delete-right)
(insert-right data))))
link
(-> (from-status '[1 2 | 3]) (replace-right "10") (status)) => '([1 2 | "10"])
v 3.0
(defn right-element
([zip]
(first (:right zip))))
link
(-> (from-status '[1 2 3 | 4]) (right-element)) => 4
v 3.0
(defn right-elements
([zip]
(:right zip)))
link
(-> (from-status '[1 2 | 3 4]) (right-elements)) => '(3 4)
v 3.0
(defn root-element
([zip]
(-> zip
(step-outside-most)
(step-left-most)
(right-element))))
link
(-> (vector-zip [[[3] 2] 1]) (step-inside-most) (root-element)) => [[[3] 2] 1]
v 3.0
(defn seq-zip
([root]
(seq-zip root nil))
([root opts]
(zipper root
(merge {:create-container list
:create-element identity
:is-container? seq?
:is-empty-container? empty?
:is-element? (complement nil?)
:list-elements identity
:update-elements (fn [container new-elements] (apply list new-elements))
:add-element (fn [container element] (concat container [element]))}
+base+)
opts)))
link
(seq-zip '(1 2 3 4 5)) => (contains {:left (), :right '((1 2 3 4 5))})
status ^
[{:keys [context], :as zip} & [no-cursor]]
returns the form with the status showing
v 3.0
(defn status
([{:keys [context] :as zip} & [no-cursor]]
(->> (if no-cursor
zip
(insert-left zip (:cursor context)))
(step-outside-most)
(current-elements)
(apply list)
(unwrap-element zip))))
link
(-> (vector-zip [1 [[2] 3]]) (step-inside) (step-right) (step-inside) (step-inside) (status)) => '([1 [[| 2] 3]])
v 3.0
(defn status-string
([zip]
(->> (status zip)
(apply pr-str))))
link
(-> (vector-zip [1 [[2] 3]]) (step-inside) (step-right) (status-string)) => "[1 | [[2] 3]]"
v 3.0
(defn step-end
([{:keys [context] :as zip}]
(-> zip
(step-outside-most)
(step-right-most)
(step-inside-most-left))))
link
(->> (from-status '[1 | [[]]]) (step-end) (status)) => '([1 [[|]]])
v 3.0
(defn step-inside
([{:keys [context] :as zip}]
(cond ((:at-right-most? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-right-most])]
(zip-fn zip)
zip)
((:at-inside-most? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-inside-most])]
(zip-fn zip)
zip)
:else
(let [elem (right-element zip)
children (list-child-elements zip :right)]
(-> zip
(assoc :depth (inc (:depth zip))
:left ()
:right children
:parent zip)
(h/call (:update-step-inside context) elem)))))
([zip n]
(nth (iterate step-inside zip) n)))
link
(-> (from-status '[1 2 | [3 4]]) (step-inside) (status)) => '([1 2 [| 3 4]])
step-inside-left ^
[{:keys [context], :as zip}] [zip n]
steps into the form on the left side
v 3.0
(defn step-inside-left
([{:keys [context] :as zip}]
(cond ((:at-left-most? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-left-most])]
(zip-fn zip)
zip)
((:at-inside-most-left? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-inside-most-left])]
(zip-fn zip)
zip)
:else
(let [elem (left-element zip)
children (list-child-elements zip :left)
{:keys [left right depth]} zip
parent (assoc zip
:left (rest left)
:right (cons (first left) right))]
(-> zip
(assoc :depth (inc depth) :left (reverse children) :right () :parent parent)
(h/call (:update-step-inside-left context) elem)))))
([zip n]
(nth (iterate step-inside-left zip) n)))
link
(-> (from-status '[[1 2] |]) (step-inside-left) (status)) => '([[1 2 |]])
step-inside-most ^
[{:keys [context], :as zip}]
step to at-inside-most point of current container
v 3.0
(defn step-inside-most
([{:keys [context] :as zip}]
(if ((:at-inside-most? context) zip)
zip
(recur (step-inside zip)))))
link
(-> (from-status '[1 2 | [[3] 4]]) (step-inside-most) (status)) => '([1 2 [[| 3] 4]])
step-inside-most-left ^
[{:keys [context], :as zip}]
steps all the way inside to the left side
v 3.0
(defn step-inside-most-left
([{:keys [context] :as zip}]
(if ((:at-inside-most-left? context) zip)
zip
(recur (step-inside-left zip)))))
link
(-> (from-status '[[1 [2]] | 3 4]) (step-inside-most-left) (status)) => '([[1 [2 |]] 3 4])
step-left ^
[{:keys [left context right], :as zip}] [zip n]
step left from current status
v 3.0
(defn step-left
([{:keys [left context right] :as zip}]
(cond ((:at-left-most? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-left-most])]
(zip-fn zip)
zip)
:else
(let [elem (first left)]
(-> zip
(assoc :left (rest left))
(assoc :right (cons elem right))
(h/call (:update-step-left context) elem)))))
([zip n]
(nth (iterate step-left zip) n)))
link
(-> (from-status '[1 2 [3 4 |]]) (step-left) (status)) => '([1 2 [3 | 4]])
step-left-most ^
[{:keys [context], :as zip}]
step to left-most point of current container
v 3.0
(defn step-left-most
([{:keys [context] :as zip}]
(if ((:at-left-most? context) zip)
zip
(recur (step-left zip)))))
link
(-> (from-status '[1 2 [3 4 |]]) (step-left-most) (status)) => '([1 2 [| 3 4]])
step-next ^
[{:keys [context], :as zip}]
step status through the tree in depth first order
v 3.0
(defn step-next
([{:keys [context] :as zip}]
(cond (nil? zip) nil
(and (can-step-inside? zip)
(not (is-empty-container? zip)))
(let [zip (step-inside zip)
check-fn (:is-element? context)]
(if (check-fn (right-element zip))
zip
(recur (step-right zip))))
(can-step-right? (step-right zip))
(let [zip (step-right zip)]
(if ((:is-element? context) (right-element zip))
zip
(recur zip)))
:else
(loop [zip (step-outside-right zip)]
(cond ((:at-outside-most? context) zip)
nil
((:is-element? context) (right-element zip))
zip
(can-step-right? (step-right zip))
(step-next (step-right zip))
:else
(recur (step-outside-right zip)))))))
link
(->> (from-status '[| 1 [2 [6 7] 3] [4 5]]) (iterate step-next) (take-while identity) (map right-element)) => '(1 [2 [6 7] 3] 2 [6 7] 6 7 3 [4 5] 4 5)
v 3.0
(defn step-outside
([zip]
(let [{:keys [context left right parent depth]} zip]
(cond ((:at-outside-most? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-outside-most])]
(zip-fn zip)
zip)
:else
(let [elements (concat (reverse left) right)
body {:left (:left parent)
:right (:right parent)
:parent (:parent parent)
:depth (dec depth)}]
(cond-> (merge zip body)
(:changed? zip) (update-child-elements elements)
:then (h/call (:update-step-outside context) left))))))
([zip n]
(nth (iterate step-outside zip) n)))
link
(-> (from-status '[1 2 [| 3 4]]) (step-outside) (status)) => '([1 2 | [3 4]])
step-outside-most ^
[{:keys [context], :as zip}]
step to outside-most point of the tree
v 3.0
(defn step-outside-most
([{:keys [context] :as zip}]
(if ((:at-outside-most? context) zip)
zip
(recur (step-outside zip)))))
link
(-> (from-status '[1 2 [| 3 4]]) (step-outside-most) (status)) => '(| [1 2 [3 4]])
step-outside-most-right ^
[{:keys [context], :as zip}]
step to outside-most point of the tree to the right
v 3.0
(defn step-outside-most-right
([{:keys [context] :as zip}]
(if ((:at-outside-most? context) zip)
zip
(step-right (step-outside-most zip)))))
link
(-> (from-status '[1 2 [| 3 4]]) (step-outside-most-right) (status)) => '([1 2 [3 4]] |)
v 3.0
(defn step-outside-right
([zip]
(-> zip
(step-outside)
(step-right)))
([zip n]
(nth (iterate step-outside-right zip) n)))
link
(-> (from-status '[1 2 [| 3 4]]) (step-outside-right) (status)) => '([1 2 [3 4] |])
step-prev ^
[{:keys [context], :as zip}]
step status in reverse through the tree in depth first order
v 3.0
(defn step-prev
([{:keys [context] :as zip}]
(cond (nil? zip) nil
((:at-outside-most? context) zip)
nil
(can-step-left? zip)
(cond (can-step-inside-left? zip)
(recur (step-inside-left zip))
((:is-element? context) (left-element zip))
(step-left zip)
:else
(recur (step-left zip)))
:else
(step-outside zip))))
link
(->> (from-status '[1 [2 [6 7] 3] [4 | 5]]) (iterate step-prev) (take 10) (map right-element)) => '(5 4 [4 5] 3 7 6 [6 7] 2 [2 [6 7] 3] 1)
step-right ^
[{:keys [left right context], :as zip}] [zip n]
step right from current status
v 3.0
(defn step-right
([{:keys [left right context] :as zip}]
(cond ((:at-right-most? context) zip)
(if-let [zip-fn (get-in *handler* [:step :at-right-most])]
(zip-fn zip)
zip)
:else
(let [elem (first right)
res (-> zip
(assoc :left (cons elem left))
(assoc :right (rest right))
(h/call (:update-step-right context) elem))]
res)))
([zip n]
(nth (iterate step-right zip) n)))
link
(-> (from-status '[1 2 [| 3 4]]) (step-right) (status)) => '([1 2 [3 | 4]])
step-right-most ^
[{:keys [context], :as zip}]
step to right-most point of current container
v 3.0
(defn step-right-most
([{:keys [context] :as zip}]
(if ((:at-right-most? context) zip)
zip
(recur (step-right zip)))))
link
(-> (from-status '[1 2 [| 3 4]]) (step-right-most) (status)) => '([1 2 [3 4 |]])
surround ^
[{:keys [context parent left], :as zip}]
nests elements in current block within another container
v 3.0
(defn surround
([{:keys [context parent left] :as zip}]
(let [list-fn (:list-elements context)
update-fn (:update-elements context)
add-fn (:add-element context)
empty-elem ((:create-container context))
new-elem (->> (update-fn empty-elem (current-elements zip))
(add-fn empty-elem))]
(cond (nil? parent)
(let [elem (list-fn new-elem)]
(-> zip
(assoc :left ()
:right elem
:changed? true)
(h/call (:update-step-outside context) left)))
:else
(-> (step-outside zip)
(replace-right new-elem)
(step-inside))))))
link
(-> (vector-zip 3) (insert-left 1 2) (surround) (status)) => '(| [1 2 3]) (->> (from-status '[1 [1 2 | 3 4]]) (surround) (status)) => '([1 [| [1 2 3 4]]])
v 4.0
(defn unregister-type
[type context]
(swap! +types+ dissoc type))
link
(resolve 'unregister-type) => var?
v 4.0
(defn unwrap-element
([{:keys [context]
:as zip}
element]
(let [{:keys [unwrap-data]
:or {unwrap-data identity}} context]
(unwrap-data element))))
link
update-child-elements ^
[zip child-elements] [{:keys [context], :as zip} child-elements direction]
updates elements of a container
v 3.0
(defn update-child-elements
([zip child-elements]
(update-child-elements zip child-elements :right))
([{:keys [context] :as zip} child-elements direction]
(update-in zip
[direction]
(fn [elements]
(let [check-fn (:is-container? context)
update-fn (:update-elements context)
old (first elements)
_ (if-not (check-fn old)
(throw (ex-info "Not a container." {:element old})))
new (update-fn old
child-elements)]
(cons new (rest elements)))))))
link
(-> (vector-zip [1 2]) (update-child-elements [1 2 3 4] :right) (right-element)) => [1 2 3 4]
v 3.0
(defn vector-zip
([root]
(vector-zip root nil))
([root opts]
(zipper root
(merge {:create-container vector
:create-element identity
:is-container? vector?
:is-empty-container? empty?
:is-element? (complement nil?)
:list-elements seq
:update-elements (fn [_ new-elements] (vec new-elements))
:add-element conj}
+base+)
opts)))
link
(vector-zip [1 2 3 4 5]) => (contains {:left (), :right '([1 2 3 4 5])})
4 std.lib.zip: A Comprehensive Summary
The std.lib.zip namespace provides a powerful and flexible implementation of a zipper data structure, enabling efficient traversal, inspection, and modification of hierarchical data. It extends the core zipper concept with custom context functions, allowing it to operate on various data structures (sequences, vectors, etc.) and offering a rich set of navigation and editing operations. This module is crucial for tasks involving tree-like data manipulation, such as AST transformations, XML/HTML processing, or UI component tree management.
Key Features and Concepts:
- Zipper Structure and Context:n
*handler*: A dynamic var that can be bound to a map of custom handler functions for specific zipper operations (e.g.,:step :at-left-most).n+nil-handler+: A map ofh/NILfunctions for various handler points, serving as a default.nZipperRecord: The core data structure for the zipper. It holds:ncontext: A map of functions defining how the zipper interacts with the underlying data structure (e.g.,is-container?,list-elements).nleft: A sequence of elements to the left of the current position.nright: A sequence of elements to the right of the current position.nparent: The parent zipper node, forming the hierarchical context.nprefix,display: Optional fields for custom string representation.ncheck-context [context]: Validates that a zippercontextmap contains all required functions.ncheck-optional [context]: Validates that a zippercontextmap contains all optional functions.nzipper? [x]: A predicate to check if an object is aZipper.nzipper [root context & [opts]]: Constructs a newZipperinstance from arootdata structure and acontextmap.nn2. Element Access and Inspection:nleft-element [zip]: Returns the element immediately to the left of the current position.nright-element [zip]: Returns the element immediately to the right of the current position.nleft-elements [zip]: Returns all elements to the left of the current position.nright-elements [zip]: Returns all elements to the right of the current position.ncurrent-elements [zip]: Returns all elements at the current level (left and right).nis [zip pred & [step]]: Checks if the element at the current position (or a specifiedstep) satisfies apredicate.nget [zip & [func step]]: Retrieves the element at the current position (or a specifiedstep), optionally applying afunc.nis-container? [zip & [step]]: Checks if the element at the current position (or a specifiedstep) is a container.nis-empty-container? [zip & [step]]: Checks if the element at the current position (or a specifiedstep) is an empty container.nn3. Navigation (Step Operations):nat-left-most? [zip]: Checks if the cursor is at the left-most position in the current container.nat-right-most? [zip]: Checks if the cursor is at the right-most position in the current container.nat-inside-most? [zip]: Checks if the cursor is at the deepest possible position to the right within the current container.nat-inside-most-left? [zip]: Checks if the cursor is at the deepest possible position to the left within the current container.nat-outside-most? [zip]: Checks if the cursor is at the top-most level of the entire tree.ncan-step-left? [zip]: Checks if it's possible to move left.ncan-step-right? [zip]: Checks if it's possible to move right.ncan-step-inside? [zip]: Checks if it's possible to move down into a child.ncan-step-inside-left? [zip]: Checks if it's possible to move down into a child on the left.ncan-step-outside? [zip]: Checks if it's possible to move up to the parent.nstep-left [zip & [n]]: Moves the cursornsteps to the left.nstep-right [zip & [n]]: Moves the cursornsteps to the right.nstep-inside [zip & [n]]: Moves the cursornsteps down into the right-most child.nstep-inside-left [zip & [n]]: Moves the cursornsteps down into the left-most child.nstep-outside [zip & [n]]: Moves the cursornsteps up to the parent.nstep-outside-right [zip & [n]]: Moves the cursornsteps up and then right.nstep-left-most [zip]: Moves the cursor to the left-most position in the current container.nstep-right-most [zip]: Moves the cursor to the right-most position in the current container.nstep-inside-most [zip]: Moves the cursor to the deepest possible position to the right within the current container.nstep-inside-most-left [zip]: Moves the cursor to the deepest possible position to the left within the current container.nstep-outside-most [zip]: Moves the cursor to the top-most level of the entire tree.nstep-outside-most-right [zip]: Moves the cursor to the top-most level and then to the right-most position.nstep-end [zip]: Moves the cursor to the end of the tree (deepest right-most element).nn4. Editing Operations:ninsert-left [zip data & more]: Inserts one or more elements to the left of the current position.ninsert-token-to-right [zip data & more]: Inserts one or more elements to the right of the current position.ndelete-left [zip & [n]]: Deletesnelements to the left of the current position.ndelete-right [zip & [n]]: Deletesnelements to the right of the current position.nreplace-left [zip data]: Replaces the element to the left of the current position.nreplace-right [zip data]: Replaces the element to the right of the current position.nsurround [zip]: Nests the current elements within a new container.nn5. Tree Traversal and Search:nhierarchy [zip]: Returns a sequence of zipper nodes from the current position up to the root.nat-end? [zip]: Checks if the cursor is at the end of the tree (deepest right-most element).nroot-element [zip]: Returns the root element of the tree.nstatus [zip]: Returns the data structure with a marker (|) indicating the current cursor position.nstatus-string [zip]: Returns a string representation of the data structure with the cursor marker.nstep-next [zip]: Moves the cursor to the next element in depth-first order.nstep-prev [zip]: Moves the cursor to the previous element in reverse depth-first order.nfind [zip move pred]: Finds the first element satisfying apredicate by repeatedly applying amovefunction.nfind-left [zip pred]: Finds the first element satisfyingpredby moving left.nfind-right [zip pred]: Finds the first element satisfyingpredby moving right.nfind-next [zip pred]: Finds the first element satisfyingpredby moving in depth-first order.nfind-prev [zip pred]: Finds the first element satisfyingpredby moving in reverse depth-first order.nfrom-status [data & [zipper-fn]]: Constructs a zipper from a data structure that includes a|marker for the initial cursor position.nprewalk [zip f]: Emulatesstd.lib.walk/prewalkbehavior using the zipper.npostwalk [zip f]: Emulatesstd.lib.walk/postwalkbehavior using the zipper.nmatchwalk [zip matchers f & [matchwalk opts]]: Performs a walk, applyingfwhen a matcher is found, and can recurse into sub-trees.nlevelwalk [zip [pred] f & [levelwalk opts]]: Performs a walk at the current level, applyingfwhen a predicate is met.nn6. Zipper Types:nseq-zip [root & [opts]]: Constructs a zipper for sequence-like data structures.nvector-zip [root & [opts]]: Constructs a zipper for vector-like data structures.
Overall Importance:
The std.lib.zip module is a powerful tool for manipulating hierarchical data within the foundation-base project. Its key contributions include:
- Efficient Tree Manipulation: Provides a functional and efficient way to navigate and modify tree-like data structures without explicit recursion.n Abstract Data Structure Operations: The customizable
contextallows the zipper to operate on various underlying data representations (sequences, vectors, custom trees).n Precise Cursor Control: Offers fine-grained control over the cursor's position within the data structure, enabling targeted modifications.n Simplified Transformations: Functions likeprewalkandpostwalk(implemented with the zipper) provide familiar traversal patterns for complex transformations.n Debugging and Visualization: Thestatusandstatus-stringfunctions are invaluable for visualizing the zipper's current position and the state of the data.
By offering these advanced tree manipulation capabilities, std.lib.zip significantly enhances the foundation-base project's ability to process and transform complex data models, which is vital for its multi-language development ecosystem.