Skip to content

Commit bd33a3f

Browse files
committed
Merge pull request #428 from mpenet/feature/walk
add pixie.walk
2 parents 9f52918 + b7db173 commit bd33a3f

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

pixie/walk.pxi

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
(ns pixie.walk)
2+
3+
(defprotocol IWalk
4+
(-walk [x f]))
5+
6+
(extend-protocol IWalk
7+
PersistentList
8+
(-walk [x f]
9+
(apply list (map f x)))
10+
11+
Cons
12+
(-walk [x f]
13+
(cons (f (first x)) (map f (next x))))
14+
15+
IMapEntry
16+
(-walk [x f]
17+
(map-entry (f (key x)) (f (val x))))
18+
19+
PersistentVector
20+
(-walk [x f]
21+
(into [] (map f) x))
22+
23+
PersistentHashSet
24+
(-walk [x f]
25+
(into #{} (map f) x))
26+
27+
PersistentHashMap
28+
(-walk [x f]
29+
(into {} (map f) x))
30+
31+
IRecord
32+
(-walk [x f]
33+
(into x (map f) x))
34+
35+
ISeqable
36+
(-walk [x f]
37+
(map f x))
38+
39+
IObject
40+
(-walk [x f] x)
41+
42+
Nil
43+
(-walk [x f] nil))
44+
45+
(defn walk
46+
{:doc "Traverses form, an arbitrary data structure. f is a
47+
function. Applies f to each element of form, building up a data
48+
structure of the same type. Recognizes all Pixie data
49+
structures. Consumes seqs."
50+
:signatures [[f x]]
51+
:added "0.1"}
52+
[f x]
53+
(-walk x f))
54+
55+
(defn postwalk
56+
{:doc "Performs a depth-first, post-order traversal of form. Calls f on
57+
each sub-form, uses f's return value in place of the original.
58+
Recognizes all Pixie data structures."
59+
:signatures [[f x]]
60+
:added "0.1"}
61+
[f x]
62+
(f (walk (partial postwalk f) x)))
63+
64+
(defn prewalk
65+
{:doc "Like postwalk, but does pre-order traversal."
66+
:added "0.1"}
67+
[f x]
68+
(walk (partial prewalk f) (f x)))
69+
70+
(defn prewalk-replace
71+
{:doc "Recursively transforms form by replacing
72+
keys in smap with their values. Like `replace` but works on
73+
any data structure. Does replacement at the root of the tree
74+
first."
75+
:signatures [[f x]]
76+
:added "0.1"}
77+
[smap x]
78+
(prewalk (fn [x] (if (contains? smap x) (smap x) x)) x))
79+
80+
(defn postwalk-replace
81+
{:doc "Recursively transforms form by replacing keys in smap with
82+
their values. Like `replace` but works on any data structure.
83+
Does replacement at the leaves of the tree first."
84+
:signatures [[smap x]]
85+
:added "0.1"}
86+
[smap x]
87+
(postwalk (fn [x] (if (contains? smap x) (smap x) x)) x))
88+
89+
(defn keywordize-keys
90+
{:doc "Recursively transforms all map keys from strings to keywords."
91+
:signatures [[m]]
92+
:added "0.1"}
93+
[m]
94+
(let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
95+
;; only apply to maps
96+
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
97+
98+
(defn stringify-keys
99+
{:doc "Recursively transforms all map keys from keywords to strings."
100+
:signatures [[m]]
101+
:added "0.1"}
102+
[m]
103+
(let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
104+
;; only apply to maps
105+
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
106+
107+
(defn macroexpand-all
108+
{:doc "Recursively performs all possible macroexpansions in
109+
form. For development use only."
110+
:added "0.1"
111+
:signatures [[x]]}
112+
[x]
113+
(prewalk (fn [x] (if (seq? x) (macroexpand-1 x) x)) x))

tests/pixie/tests/test-walk.pxi

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
(ns pixie.tests.test-walk
2+
(:require [pixie.walk :as w]
3+
[pixie.test :as t]))
4+
5+
(t/deftest t-prewalk-replace
6+
(t/assert (= (w/prewalk-replace {:a :b} [:a {:a :a} (list 3 :c :a)])
7+
[:b {:b :b} (list 3 :c :b)])))
8+
9+
(t/deftest t-postwalk-replace
10+
(t/assert (= (w/postwalk-replace {:a :b} [:a {:a :a} (list 3 :c :a)])
11+
[:b {:b :b} (list 3 :c :b)])))
12+
13+
(t/deftest t-prewalk-order
14+
(t/assert (= (let [a (atom [])]
15+
(w/prewalk (fn [form] (swap! a conj form) form)
16+
[1 2 {:a 3} (list 4 [5])])
17+
@a)
18+
[[1 2 {:a 3} (list 4 [5])]
19+
1 2 {:a 3} [:a 3] :a 3 (list 4 [5])
20+
4 [5] 5])))
21+
22+
(t/deftest t-postwalk-order
23+
(t/assert (= (let [a (atom [])]
24+
(w/postwalk (fn [form] (swap! a conj form) form)
25+
[1 2 {:a 3} (list 4 [5])])
26+
@a)
27+
[1 2
28+
:a 3 [:a 3] {:a 3}
29+
4 5 [5] (list 4 [5])
30+
[1 2 {:a 3} (list 4 [5])]])))
31+
32+
(defrecord Foo [a b c])
33+
34+
(t/deftest walk
35+
"Checks that walk returns the correct result and type of collection"
36+
(let [colls ['(1 2 3)
37+
[1 2 3]
38+
#{1 2 3}
39+
{:a 1, :b 2, :c 3}
40+
(->Foo 1 2 3)]]
41+
(doseq [c colls]
42+
(let [walked (w/walk identity c)]
43+
(t/assert (= c walked))
44+
(t/assert (= (type c) (type walked)))
45+
(if (or (map? c)
46+
(record? c))
47+
(do
48+
(t/assert (= (reduce + (vals (w/walk
49+
#(map-entry
50+
(key %)
51+
(inc (val %)))
52+
c)))
53+
(reduce + (map (comp inc val) c)))))
54+
(t/assert (= (reduce + (w/walk inc c))
55+
(reduce + (map inc c)))))))))
56+
57+
(t/deftest t-stringify-keys
58+
(t/assert (= (w/stringify-keys {:a 1, nil {:b 2 :c 3}, :d 4})
59+
{"a" 1, nil {"b" 2 "c" 3}, "d" 4})))

0 commit comments

Comments
 (0)