forked from ruricolist/serapeum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrees.lisp
197 lines (176 loc) · 7.04 KB
/
trees.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
(in-package :serapeum)
(declaim (inline reuse-cons))
(defun reuse-cons (x y x-y)
"If X and Y are the car and cdr of X-Y, return X-Y.
Otherwise, return a fresh cons of X and Y."
(if (and (eq x (car x-y))
(eq y (cdr x-y)))
x-y
(cons x y)))
(declaim (ftype
(function (list) (values t t &optional))
car+cdr))
(defun car+cdr (list)
"Given LIST, return its car and cdr as two values."
(values (car list)
(cdr list)))
(defun walk-tree (fun tree &key (tag nil tagp) (traversal :preorder))
"Call FUN in turn over each atom and cons of TREE.
FUN can skip the current subtree with (throw TAG nil)."
(let ((fun (ensure-function fun)))
;; NB map-tree only conses if you change something.
(multiple-value-call #'map-tree
(lambda (tree)
(funcall fun tree)
tree)
tree
:traversal traversal
(if tagp (values :tag tag) (values)))
(values)))
(defun map-tree (fun tree &key (tag nil tagp)
(traversal :preorder))
"Walk FUN over TREE and build a tree from the results.
The new tree may share structure with the old tree.
(eq tree (map-tree #'identity tree)) => T
FUN can skip the current subtree with (throw TAG SUBTREE), in which
case SUBTREE will be used as the value of the subtree.
TRAVERSE can be one of `:preorder', `:postorder', or `:inorder'. The
default is `:preorder'."
(ecase traversal
(:preorder (map-tree/preorder fun tree tag tagp))
(:postorder (map-tree/postorder fun tree tag tagp))
(:inorder (map-tree/inorder fun tree tag tagp))))
(defun map-tree/preorder (fun tree tag tagp)
#.+merge-tail-calls+
(let ((fun (ensure-function fun)))
(labels ((map-tree (tree)
(let ((tree2 (funcall fun tree)))
(if (atom tree2)
tree2
(reuse-cons (map-tree (car tree2))
(map-tree (cdr tree2))
tree2))))
(map-tree/tag (tree tag)
(catch tag
(let ((tree2 (funcall fun tree)))
(if (atom tree2)
tree2
(reuse-cons (map-tree/tag (car tree2) tag)
(map-tree/tag (cdr tree2) tag)
tree2))))))
(if tagp
(map-tree/tag tree tag)
(map-tree tree)))))
(defun map-tree/postorder (fun tree tag tagp)
#.+merge-tail-calls+
(let ((fun (ensure-function fun)))
(labels ((map-tree (tree)
(if (atom tree)
(funcall fun tree)
(let* ((left (map-tree (car tree)))
(right (map-tree (cdr tree)))
(tree2 (reuse-cons left right tree)))
(funcall fun tree2))))
(map-tree/tag (tree tag)
(catch tag
(if (atom tree)
(funcall fun tree)
(let* ((left (map-tree/tag (car tree) tag))
(right (map-tree/tag (cdr tree) tag))
(tree2 (reuse-cons left right tree)))
(funcall fun tree2))))))
(if tagp
(map-tree/tag tree tag)
(map-tree tree)))))
(defun map-tree/inorder (fun tree tag tagp)
#.+merge-tail-calls+
(let ((fun (ensure-function fun)))
(labels ((map-tree (tree)
(if (atom tree)
(funcall fun tree)
(let* ((left (map-tree (car tree)))
(tree2 (funcall fun (reuse-cons left (cdr tree) tree))))
(reuse-cons (car tree2)
(map-tree (cdr tree2))
tree2))))
(map-tree/tag (tree tag)
(catch tag
(if (atom tree)
(funcall fun tree)
(let* ((left (map-tree/tag (car tree) tag))
(tree2 (funcall fun (reuse-cons left (cdr tree) tree))))
(reuse-cons (car tree2)
(map-tree/tag (cdr tree2) tag)
tree2))))))
(if tagp
(map-tree/tag tree tag)
(map-tree tree)))))
(defun leaf-walk (fun tree)
"Call FUN on each leaf of TREE."
(let ((fun (ensure-function fun)))
(labels ((leaf-walk (fun tree)
(declare (function fun))
(cond ((atom tree)
(funcall fun tree))
(t (leaf-walk fun (car tree))
(leaf-walk fun (cdr tree))))))
#.+merge-tail-calls+
(leaf-walk fun tree)))
(values))
;;; https://code.google.com/p/sparser/source/browse/trunk/util/util.lisp?spec=svn737&r=737
(defun leaf-map (fn tree)
"Call FN on each leaf of TREE.
Return a new tree possibly sharing structure with TREE."
(let ((fn (ensure-function fn)))
(flet ((map-fn (x)
(if (listp x)
x
(funcall fn x))))
#.+merge-tail-calls+
(declare (dynamic-extent #'map-fn))
(map-tree #'map-fn tree))))
(defun occurs-if (test tree &key (key #'identity) (traversal :preorder))
"Is there a node (leaf or cons) in TREE that satisfies TEST?"
(ensuring-functions (key test)
;; SBCL wants the walker to be fbound and dynamic-extent.
(flet ((walker (node)
(when (funcall test (funcall key node))
(return-from occurs-if (values node t)))))
(declare (dynamic-extent #'walker))
(walk-tree #'walker tree :traversal traversal))))
(defun prune-if (test tree &key (key #'identity))
"Remove any atoms satisfying TEST from TREE.
Pruning is defined \"modulo flatten\": you should get the same result
from pruning, and then flattening, that you would get from flattening,
and then filtering.
Also note that pruning is not defined for trees containing improper
lists."
(ensuring-functions (key test)
(labels ((cons* (car cdr)
(if (funcall test (funcall key car))
cdr
(cons car cdr)))
(prune (tree acc)
(cond ((null tree)
(nreverse acc))
((consp (car tree))
(prune (cdr tree)
(cons* (prune (car tree) nil) acc)))
(t (prune (cdr tree)
(cons* (car tree) acc))))))
#.+merge-tail-calls+
(prune tree nil))))
(defun occurs (node tree &key (key #'identity) (test #'eql) (traversal :preorder))
"Is NODE present in TREE?"
(nth-value 1
(ensuring-functions (test)
(flet ((test (x) (funcall test node x)))
(declare (dynamic-extent #'test))
(occurs-if #'test tree :key key :traversal traversal)))))
(defun prune (leaf tree &key (key #'identity) (test #'eql))
"Remove LEAF from TREE wherever it occurs.
See `prune-if' for more information."
(ensuring-functions (test)
(flet ((test (x) (funcall test leaf x)))
(declare (dynamic-extent #'test))
(prune-if #'test tree :key key))))