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* ^

NONE
(def ^:dynamic *handler* nil)
link

+base+ ^

NONE
(defonce +base+)
link

+nil-handler+ ^

NONE
(def +nil-handler+)
link

+types+ ^

NONE
(defonce +types+ (atom {}))
link

at-end? ^

[{:keys [context], :as zip}]
Added 3.0

replace element right of the current status

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?))

at-inside-most-left? ^

[zip]
Added 3.0

check if at inside-most left point of a container

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

at-inside-most? ^

[zip]
Added 3.0

check if at inside-most point of a container

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

at-left-most? ^

[zip]
Added 3.0

check if at left-most point of a container

v 3.0
(defn at-left-most?
  ([zip]
   (empty? (:left zip))))
link
(-> (from-status [1 2 ['| 3 4] 5 6]) (at-left-most?)) => true

at-outside-most? ^

[zip]
Added 3.0

check if at outside-most point of the tree

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

at-right-most? ^

[zip]
Added 3.0

check if at right-most point of a container

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}]
Added 3.0

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}]
Added 3.0

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}]
Added 3.0

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}]
Added 3.0

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}]
Added 3.0

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

check-context ^

[context]
Added 3.0

checks that the zipper contains valid functions

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)

check-optional ^

[context]
Added 3.0

checks that the meta contains valid functions

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}]
Added 3.0

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]
Added 3.0

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]
Added 3.0

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 |])

display-zipper ^

[{:keys [parent context], :as zip}]
Added 4.0

displays the zipper

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

find ^

[zip move pred]
Added 3.0

helper function for the rest of the `find` series

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

find-left ^

[zip pred]
Added 3.0

steps status left to search predicate

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]
Added 3.0

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]
Added 3.0

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]])

find-right ^

[zip pred]
Added 3.0

steps status right to search for predicate

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])

form-zip ^

[root] [root opts]
Added 4.0

creates a form zip

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})

form-zip-unwrap ^

[form]
Added 4.0

unwraps nils for the zipper

v 4.0
(defn form-zip-unwrap
  [form]
  (walk/prewalk
   (fn [x]
     (if (h/wrapped? x)
       @x
       x))
   form))
link

form-zip-wrap ^

[form]
Added 4.0

wraps nils for the zipper

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]
Added 3.0

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)})

get ^

[zip] [zip arg] [zip func step]
Added 3.0

gets the value of the zipper

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

hierarchy ^

[{:keys [context], :as zip}]
Added 3.0

replace element right of the current status

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]
Added 3.0

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]
Added 4.0

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

is ^

[zip pred] [zip pred step]
Added 3.0

checks zip given a predicate

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]
Added 3.0

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]
Added 3.0

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

left-element ^

[zip]
Added 3.0

element directly left of current position

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

left-elements ^

[zip]
Added 3.0

all elements left of current position

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}]
Added 3.0

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]
Added 3.0

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}]
Added 3.0

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]]]]

postwalk ^

[zip f]
Added 3.0

emulates std.lib.walk/postwalk behavior with zipper

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]
Added 3.0

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]

register-type ^

[type context]
Added 4.0

registers a zip type

v 4.0
(defn register-type
  [type context]
  (swap! +types+ assoc type context))
link
(resolve 'register-type) => var?

replace-left ^

[zip data]
Added 3.0

replace element left of the current status

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])

replace-right ^

[zip data]
Added 3.0

replace element right of the current status

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"])

right-element ^

[zip]
Added 3.0

element directly right of current position

v 3.0
(defn right-element
  ([zip]
   (first (:right zip))))
link
(-> (from-status '[1 2 3 | 4]) (right-element)) => 4

right-elements ^

[zip]
Added 3.0

all elements right of current position

v 3.0
(defn right-elements
  ([zip]
   (:right zip)))
link
(-> (from-status '[1 2 | 3 4]) (right-elements)) => '(3 4)

root-element ^

[zip]
Added 3.0

accesses the top level node

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]

seq-zip ^

[root] [root opts]
Added 3.0

constructs a sequence zipper

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]]
Added 3.0

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]])

status-string ^

[zip]
Added 3.0

returns the string form of the status

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]]"

step-end ^

[{:keys [context], :as zip}]
Added 3.0

steps status to container directly at end

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 [[|]]])

step-inside ^

[{:keys [context], :as zip}] [zip n]
Added 3.0

step down from current status

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]
Added 3.0

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}]
Added 3.0

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}]
Added 3.0

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]
Added 3.0

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}]
Added 3.0

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}]
Added 3.0

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)

step-outside ^

[zip] [zip n]
Added 3.0

step out to the current container

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}]
Added 3.0

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}]
Added 3.0

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]] |)

step-outside-right ^

[zip] [zip n]
Added 3.0

the right of the current container

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}]
Added 3.0

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]
Added 3.0

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}]
Added 3.0

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}]
Added 3.0

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]]])

unregister-type ^

[type context]
Added 4.0

unregisters a zip type

v 4.0
(defn unregister-type
  [type context]
  (swap! +types+ dissoc type))
link
(resolve 'unregister-type) => var?

unwrap-element ^

[{:keys [context], :as zip} element]
Added 4.0

unwraps an element

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]
Added 3.0

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]

vector-zip ^

[root] [root opts]
Added 3.0

constructs a vector based zipper

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])})

zipper ^

[root context] [root context opts]
Added 3.0

constructs a zipper

v 3.0
(defn zipper
  ([root context]
   (zipper root context {}))
  ([root context opts]
   (let [{:keys [wrap-data]
          :or {wrap-data identity}} context]
     (map->Zipper (merge opts
                         {:depth   0
                          :left    ()
                          :right   (list (wrap-data root))
                          :context (check-context context)})))))
link
(resolve 'zipper) => var?

zipper? ^

[x]
Added 3.0

checks to see if an object is a zipper

v 3.0
(defn zipper?
  ([x]
   (instance? Zipper x)))
link
(zipper? 1) => false

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:

  1. 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 of h/NIL functions for various handler points, serving as a default.n Zipper Record: The core data structure for the zipper. It holds:n context: A map of functions defining how the zipper interacts with the underlying data structure (e.g., is-container?, list-elements).n left: A sequence of elements to the left of the current position.n right: A sequence of elements to the right of the current position.n parent: The parent zipper node, forming the hierarchical context.n prefix, display: Optional fields for custom string representation.n check-context [context]: Validates that a zipper context map contains all required functions.n check-optional [context]: Validates that a zipper context map contains all optional functions.n zipper? [x]: A predicate to check if an object is a Zipper.n zipper [root context & [opts]]: Constructs a new Zipper instance from a root data structure and a context map.nn2. Element Access and Inspection:n left-element [zip]: Returns the element immediately to the left of the current position.n right-element [zip]: Returns the element immediately to the right of the current position.n left-elements [zip]: Returns all elements to the left of the current position.n right-elements [zip]: Returns all elements to the right of the current position.n current-elements [zip]: Returns all elements at the current level (left and right).n is [zip pred & [step]]: Checks if the element at the current position (or a specified step) satisfies a predicate.n get [zip & [func step]]: Retrieves the element at the current position (or a specified step), optionally applying a func.n is-container? [zip & [step]]: Checks if the element at the current position (or a specified step) is a container.n is-empty-container? [zip & [step]]: Checks if the element at the current position (or a specified step) is an empty container.nn3. Navigation (Step Operations):n at-left-most? [zip]: Checks if the cursor is at the left-most position in the current container.n at-right-most? [zip]: Checks if the cursor is at the right-most position in the current container.n at-inside-most? [zip]: Checks if the cursor is at the deepest possible position to the right within the current container.n at-inside-most-left? [zip]: Checks if the cursor is at the deepest possible position to the left within the current container.n at-outside-most? [zip]: Checks if the cursor is at the top-most level of the entire tree.n can-step-left? [zip]: Checks if it's possible to move left.n can-step-right? [zip]: Checks if it's possible to move right.n can-step-inside? [zip]: Checks if it's possible to move down into a child.n can-step-inside-left? [zip]: Checks if it's possible to move down into a child on the left.n can-step-outside? [zip]: Checks if it's possible to move up to the parent.n step-left [zip & [n]]: Moves the cursor n steps to the left.n step-right [zip & [n]]: Moves the cursor n steps to the right.n step-inside [zip & [n]]: Moves the cursor n steps down into the right-most child.n step-inside-left [zip & [n]]: Moves the cursor n steps down into the left-most child.n step-outside [zip & [n]]: Moves the cursor n steps up to the parent.n step-outside-right [zip & [n]]: Moves the cursor n steps up and then right.n step-left-most [zip]: Moves the cursor to the left-most position in the current container.n step-right-most [zip]: Moves the cursor to the right-most position in the current container.n step-inside-most [zip]: Moves the cursor to the deepest possible position to the right within the current container.n step-inside-most-left [zip]: Moves the cursor to the deepest possible position to the left within the current container.n step-outside-most [zip]: Moves the cursor to the top-most level of the entire tree.n step-outside-most-right [zip]: Moves the cursor to the top-most level and then to the right-most position.n step-end [zip]: Moves the cursor to the end of the tree (deepest right-most element).nn4. Editing Operations:n insert-left [zip data & more]: Inserts one or more elements to the left of the current position.n insert-token-to-right [zip data & more]: Inserts one or more elements to the right of the current position.n delete-left [zip & [n]]: Deletes n elements to the left of the current position.n delete-right [zip & [n]]: Deletes n elements to the right of the current position.n replace-left [zip data]: Replaces the element to the left of the current position.n replace-right [zip data]: Replaces the element to the right of the current position.n surround [zip]: Nests the current elements within a new container.nn5. Tree Traversal and Search:n hierarchy [zip]: Returns a sequence of zipper nodes from the current position up to the root.n at-end? [zip]: Checks if the cursor is at the end of the tree (deepest right-most element).n root-element [zip]: Returns the root element of the tree.n status [zip]: Returns the data structure with a marker (|) indicating the current cursor position.n status-string [zip]: Returns a string representation of the data structure with the cursor marker.n step-next [zip]: Moves the cursor to the next element in depth-first order.n step-prev [zip]: Moves the cursor to the previous element in reverse depth-first order.n find [zip move pred]: Finds the first element satisfying a predicate by repeatedly applying a move function.n find-left [zip pred]: Finds the first element satisfying pred by moving left.n find-right [zip pred]: Finds the first element satisfying pred by moving right.n find-next [zip pred]: Finds the first element satisfying pred by moving in depth-first order.n find-prev [zip pred]: Finds the first element satisfying pred by moving in reverse depth-first order.n from-status [data & [zipper-fn]]: Constructs a zipper from a data structure that includes a | marker for the initial cursor position.n prewalk [zip f]: Emulates std.lib.walk/prewalk behavior using the zipper.n postwalk [zip f]: Emulates std.lib.walk/postwalk behavior using the zipper.n matchwalk [zip matchers f & [matchwalk opts]]: Performs a walk, applying f when a matcher is found, and can recurse into sub-trees.n levelwalk [zip [pred] f & [levelwalk opts]]: Performs a walk at the current level, applying f when a predicate is met.nn6. Zipper Types:n seq-zip [root & [opts]]: Constructs a zipper for sequence-like data structures.n vector-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 context allows 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 like prewalk and postwalk (implemented with the zipper) provide familiar traversal patterns for complex transformations.n Debugging and Visualization: The status and status-string functions 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.