-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathtree.scm
385 lines (359 loc) · 15.3 KB
/
tree.scm
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
;% %
;% This file is part of openLilyLib, %
;% =========== %
;% the community library project for GNU LilyPond %
;% (https://github.com/openlilylib) %
;% ----------- %
;% %
;% Library: oll-core %
;% ======== %
;% %
;% openLilyLib is free software: you can redistribute it and/or modify %
;% it under the terms of the GNU General Public License as published by %
;% the Free Software Foundation, either version 3 of the License, or %
;% (at your option) any later version. %
;% %
;% openLilyLib is distributed in the hope that it will be useful, %
;% but WITHOUT ANY WARRANTY; without even the implied warranty of %
;% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %
;% GNU General Public License for more details. %
;% %
;% You should have received a copy of the GNU General Public License %
;% along with openLilyLib. If not, see <http://www.gnu.org/licenses/>. %
;% %
;% openLilyLib is maintained by Urs Liska, ul@openlilylib.org %
;% and others. %
;% Copyright Jan-Peter Voigt, Urs Liska, 2016 %
;% %
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
(define-module (oll-core tree))
(use-modules
(oop goops)
(lily)
(srfi srfi-1)
(oll-core stack))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; tree
; a tree implementation
; every tree-node has a hashtable of children and a value
; main methods are:
; tree-set! <tree> path-list val: set a value in the tree
; tree-get <tree> path-list: get a value from the tree or #f if not present
(define-class <tree> ()
(children #:accessor children #:init-thunk make-hash-table)
(key #:accessor key #:init-keyword #:key #:init-value 'node)
(value #:accessor value #:setter set-value! #:init-value #f)
(has-value #:accessor has-value #:setter has-value! #:init-value #f)
(type #:accessor type #:setter set-type! #:init-value #f)
)
; set value at path
; if the node at path has a type first check against that
; if the path doesn't exist yet intermediate nodes are created implicitly
(define-method (tree-set! (tree <tree>) (path <list>) val)
(tree-set! #t tree path val))
; set value at path
; if create is #t missing intermediate nodes are created implicitly
; if the node at path has a type first check against that
(define-method (tree-set! (create <boolean>) (tree <tree>) (path <list>) val)
(if (= (length path) 0)
;; end of path reached: set value
(let ((pred? (type tree)))
(if pred?
;; if tree has a type defined check value against it before setting
(if (pred? val)
(begin
(set-value! tree val)
(has-value! tree #t))
(begin
(ly:input-warning (*location*)
(format #f "TODO: Format warning about typecheck error in tree-set!
Expected ~a, got ~a" (procedure-name pred?) val))
(set! val #f)))
;; if no typecheck is set simply set the value
(begin
(set-value! tree val)
(has-value! tree #t)
)))
;; determine child
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey)))
(if (not (tree? child))
;; create child node if option is set
(if create
(begin
(set! child (make <tree> #:key ckey))
(hash-set! (children tree) ckey child))))
(if (tree? child)
;; recursively walk path
(tree-set! create child cpath val)
(ly:input-warning (*location*)
(format #f "TODO: Format missing path warning in tree-set!
Path: ~a" path)))))
val)
; unset value at path
; set value = #f and has-value = #f at path
; if the path doesn't exist, the tree is left unchanged
(define-method (tree-unset! (tree <tree>) (path <list>))
(let ((val #f))
(if (= (length path) 0)
;; end of path reached: set value
(begin
(if (has-value tree) (set! val (value tree)))
(set-value! tree #f)
(has-value! tree #f)
)
;; determine child
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey)))
(if (tree? child)
;; recursively walk path
(tree-unset! child cpath))
))
val))
(define-method (tree-set-type! (tree <tree>) (path <list>)(predicate <procedure>))
(if (= (length path) 0)
;; end of path reached: register type
(begin
(set-type! tree predicate)
; TODO: What to do if there already is a value?
; probably: check type and issue an oll-warning
)
;; determine child
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey)))
(if (not (tree? child))
;; create child node if not present
(begin (set! child (make <tree> #:key ckey))
(hash-set! (children tree) ckey child)))
;; recursively walk path
(tree-set-type! child cpath predicate))
))
; merge value at path into tree
(define-method (tree-merge! (tree <tree>) (path <list>) (proc <procedure>) val)
(let ((ctree (tree-get-tree tree path)))
(if (tree? ctree)
(set! (value ctree)
(if (has-value ctree) (proc (value ctree) val) val))
(tree-set! tree path (proc #f val)))
))
; merge values of tree2 into tree1
(define-method (tree-merge! (tree1 <tree>) (proc <procedure>) (tree2 <tree>))
(tree-walk tree2 '()
(lambda (path nkey value)
(tree-merge! tree1 path proc value)
)))
; get value at path
; returns #f if path is not present or if its value is #f
; to discern use tree-get-node
(define-method (tree-get (tree <tree>) (path <list>))
(let ((ctree (tree-get-tree tree path)))
(if (tree? ctree) (value ctree) #f)))
; get the node at path
; returns '(key . value) pair - or #f if path is not present
; to be used if #f values are to be expected.
(define-method (tree-get-node (tree <tree>) (path <list>))
(let ((ctree (tree-get-tree tree path)))
(if (and (tree? ctree) (has-value ctree))
(cons (last path) (value ctree)) #f)))
; return the sub-tree with path as its root
; returns #f if path is not in the tree
(define-method (tree-get-tree (tree <tree>) (path <list>))
(if (= (length path) 0)
;; end of path reached: return sub-tree
tree
;; determine child node
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey)))
(if (tree? child)
;; recurse through path
(tree-get-tree child cpath)
;; return #f immediately if node is not present
#f))))
; get value with key <skey> from path
; if skey=global and path=music.momnt.brass.trumpet
; it looks for global, music.global, music.momnt.global, music.momnt.brass.global
; and music.momnt.brass.trumpet.global and returns the last value found
(define-method (tree-get-from-path (tree <tree>) (path <list>) skey)
(tree-get-from-path tree path skey #f))
(define-method (tree-get-from-path (tree <tree>) (path <list>) skey val)
(if (equal? skey (key tree))(set! val (value tree)))
(let ((child (hash-ref (children tree) skey)))
(if (tree? child)(set! val (value child))))
(if (= (length path) 0)
val
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey))
)
(if (tree? child)
(tree-get-from-path child cpath skey val)
val)
)))
; get key-value-pair with key <skey> from path
; if skey=global and path=music.momnt.brass.trumpet
; it looks for global, music.global, music.momnt.global, music.momnt.brass.global
; and music.momnt.brass.trumpet.global and returns the last value found
; TODO predicate?
(define-method (tree-get-node-from-path (tree <tree>) (path <list>) skey)
(tree-get-node-from-path tree path skey #f))
(define-method (tree-get-node-from-path (tree <tree>) (path <list>) skey val)
(if (and (equal? skey (key tree))(has-value tree))
(set! val (cons skey (value tree))))
(let ((child (hash-ref (children tree) skey)))
(if (and (tree? child)(has-value child))
(set! val (cons skey (value child)))))
(if (= (length path) 0)
val
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey))
)
(if (tree? child)
(tree-get-node-from-path child cpath skey val)
val)
)))
; return all sub-keys/nodes at path
(define-method (tree-get-keys (tree <tree>) (path <list>))
(if (= (length path) 0)
(hash-map->list (lambda (key value) key) (children tree))
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey))
)
(if (tree? child)
(tree-get-keys child cpath)
#f)
)))
; return pair with relative path to value
; if X is stored at 'a/b/c'
; (tree-dispatch tree '(a b c d e))
; returns: '((d e) . X)
(define-method (tree-dispatch (tree <tree>) (path <list>))
(tree-dispatch tree path '() #f))
; def = default value
(define-method (tree-dispatch (tree <tree>) (path <list>) def)
(tree-dispatch tree path '() def))
; relative = relative path to tree
(define-method (tree-dispatch (tree <tree>) (path <list>) (relative <list>) def)
(let ((val (value tree)))
(if (= (length path) 0)
(if (has-value tree) (cons '() val)(cons relative def)) ; return last element
(let* ((ckey (car path)) ; look deeper
(cpath (cdr path))
(child (hash-ref (children tree) ckey))
)
(if (or (has-value tree) (not (list? relative))) (set! relative '()))
(if (has-value tree) (set! def (value tree)))
(if (tree? child)
(tree-dispatch child cpath `(,@relative ,ckey) def)
`((,@relative ,@path) . ,def))
))))
; collect all values on path with optional predicate
(define-method (tree-collect (tree <tree>) (path <list>))
(tree-collect tree path (stack-create) (lambda (v) #t)))
(define-method (tree-collect (tree <tree>) (path <list>) (pred? <procedure>))
(tree-collect tree path (stack-create) pred?))
(define oll-stack (@@ (oll-core stack) <stack>)) ; there is also a <stack> class in (oop goops)
(define-method (tree-collect (tree <tree>) (path <list>) (vals oll-stack))
(tree-collect tree path vals (lambda (v) #t)))
(define-method (tree-collect (tree <tree>) (path <list>) (vals oll-stack) (pred? <procedure>))
(let ((val (value tree)))
(if (> (length path) 0)
(let* ((ckey (car path))
(cpath (cdr path))
(child (hash-ref (children tree) ckey))
)
(if (tree? child) (tree-collect child cpath vals pred?))
))
(if (and (has-value tree)(pred? val)) (push vals val))
(reverse (store vals))
))
; standard sort-function
(define (stdsort p1 p2)
(let ((v1 (car p1))
(v2 (car p2)))
(cond
((and (number? v1) (number? v2)) (< v1 v2))
((and (ly:moment? v1) (ly:moment? v2)) (ly:moment<? v1 v2))
(else (string-ci<? (format #f "~A" v1) (format #f "~A" v2)))
)))
; walk the tree and call callback for every node
(define-method (tree-walk (tree <tree>) (path <list>) (callback <procedure>) . opts)
(let ((dosort (assoc-get 'sort opts #f))
(sortby (assoc-get 'sortby opts stdsort))
(doempty (assoc-get 'empty opts #f)))
(if (or doempty (has-value tree))
(callback path (key tree) (value tree)))
(for-each (lambda (p)
(tree-walk (cdr p) `(,@path ,(car p)) callback `(sort . ,dosort) `(sortby . ,sortby) `(empty . ,doempty)))
(if dosort (sort (hash-table->alist (children tree)) sortby)
(hash-table->alist (children tree)) ))
))
; walk the tree and call callback for every node in sub-tree at path
(define-method (tree-walk-branch (tree <tree>) (path <list>) (callback <procedure>) . opts)
(let ((dosort (assoc-get 'sort opts))
(sortby (assoc-get 'sortby opts stdsort))
(doempty (assoc-get 'empty opts))
(ctree (tree-get-tree tree path)))
(if (tree? ctree)
(tree-walk ctree path callback `(sort . ,dosort) `(sortby . ,sortby) `(empty . ,doempty)))
))
; display tree
(define-public (tree-display tree . opt)
(let ((path (ly:assoc-get 'path opt '() #f)) ; path to display
(dosort (ly:assoc-get 'sort opt #t #f)) ; wether to sort by key
(sortby (assoc-get 'sortby opt stdsort)) ; sort-function
(empty (ly:assoc-get 'empty opt #f #f)) ; display empty nodes
(dval (ly:assoc-get 'value opt #t #f)) ; display value
(vformat (ly:assoc-get 'vformat opt (lambda (v)(format #f "~A" v)) #f)) ; format value
(pformat (ly:assoc-get 'pformat opt (lambda (v)(format #f "~A" v)) #f)) ; format path
(pathsep (ly:assoc-get 'pathsep opt "/" #f)) ; separator for path
(port (ly:assoc-get 'port opt (current-output-port)))) ; output-port
(tree-walk-branch tree path
(lambda (path k val)
(format port "[~A] ~A" (key tree) (string-join (map pformat path) pathsep 'infix))
(if dval
(begin
(display ": " port)
(display (vformat val) port)
))
(newline port)
) `(sort . ,dosort) `(sortby . ,sortby) `(empty . ,empty) )
))
; display tree to string
(define-public (tree->string tree . opt)
(call-with-output-string
(lambda (port)
(apply tree-display tree (assoc-set! opt 'port port))
)))
; display tree
(define-method (display (tree <tree>) port)
(let ((tkey (key tree)))
(tree-display tree `(port . ,port))))
; tree predicate
(define-public (tree? tree)(is-a? tree <tree>))
; create tree
(define-public (tree-create . key)
(let ((k (if (> (length key) 0)(car key) 'node)))
(make <tree> #:key k)
))
; export methods
(export tree-set!)
(export tree-unset!)
(export tree-set-type!)
(export tree-merge!)
(export tree-get-tree)
(export tree-get)
(export tree-get-node)
(export tree-get-from-path)
(export tree-get-node-from-path)
(export tree-get-keys)
(export tree-dispatch)
(export tree-collect)
(export tree-walk)
(export tree-walk-branch)