1 # A doubly linked list permits bidirectional traversal.
3 container duplex-list:_elem [
5 next:&:duplex-list:_elem
6 prev:&:duplex-list:_elem
9 def push x:_elem, in:&:duplex-list:_elem/contained-in:result -> result:&:duplex-list:_elem [
12 result:&:duplex-list:_elem <- new {(duplex-list _elem): type}
13 *result <- merge x, in, null
15 put *in, prev:offset, result
18 def first in:&:duplex-list:_elem -> result:_elem [
23 zero:&:_elem <- new _elem:type
24 zero-result:_elem <- copy *zero
28 result <- get *in, value:offset
31 def next in:&:duplex-list:_elem -> result:&:duplex-list:_elem/contained-in:in [
34 return-unless in, null
35 result <- get *in, next:offset
38 def prev in:&:duplex-list:_elem -> result:&:duplex-list:_elem/contained-in:in [
41 return-unless in, null
42 result <- get *in, prev:offset
46 scenario duplex-list-handling [
49 # reserve locations 0-9 to check for missing null check
52 list:&:duplex-list:num <- push 3, null
55 list2:&:duplex-list:num <- copy list
56 20:num/raw <- first list2
58 21:num/raw <- first list2
60 22:num/raw <- first list2
61 30:&:duplex-list:num/raw <- next list2
62 31:num/raw <- first 30:&:duplex-list:num/raw
63 32:&:duplex-list:num/raw <- next 30:&:duplex-list:num/raw
64 33:&:duplex-list:num/raw <- prev 30:&:duplex-list:num/raw
66 40:num/raw <- first list2
68 41:num/raw <- first list2
69 50:bool/raw <- equal list, list2
71 memory-should-contain [
72 0 <- 0 # no modifications to null pointers
75 20 <- 5 # scanning next
79 31 <- 0 # first of null
80 32 <- 0 # next of null
81 33 <- 0 # prev of null
82 40 <- 4 # then start scanning prev
84 50 <- 1 # list back at start
88 def length l:&:duplex-list:_elem -> result:num [
94 result <- add result, 1
100 # insert 'x' after 'in'
101 def insert x:_elem, in:&:duplex-list:_elem -> in:&:duplex-list:_elem [
104 new-node:&:duplex-list:_elem <- new {(duplex-list _elem): type}
105 *new-node <- put *new-node, value:offset, x
106 # save old next before changing it
107 next-node:&:duplex-list:_elem <- get *in, next:offset
108 *in <- put *in, next:offset, new-node
109 *new-node <- put *new-node, prev:offset, in
110 *new-node <- put *new-node, next:offset, next-node
111 return-unless next-node
112 *next-node <- put *next-node, prev:offset, new-node
115 scenario inserting-into-duplex-list [
117 list:&:duplex-list:num <- push 3, null
121 list2:&:duplex-list:num <- next list # inside list
122 list2 <- insert 6, list2
123 # check structure like before
125 10:num/raw <- first list2
127 11:num/raw <- first list2
129 12:num/raw <- first list2
131 13:num/raw <- first list2
133 20:num/raw <- first list2
135 21:num/raw <- first list2
137 22:num/raw <- first list2
138 30:bool/raw <- equal list, list2
140 memory-should-contain [
141 10 <- 5 # scanning next
143 12 <- 6 # inserted element
148 30 <- 1 # list back at start
152 scenario inserting-at-end-of-duplex-list [
154 list:&:duplex-list:num <- push 3, null
158 list2:&:duplex-list:num <- next list # inside list
159 list2 <- next list2 # now at end of list
160 list2 <- insert 6, list2
161 # check structure like before
163 10:num/raw <- first list2
165 11:num/raw <- first list2
167 12:num/raw <- first list2
169 13:num/raw <- first list2
171 20:num/raw <- first list2
173 21:num/raw <- first list2
175 22:num/raw <- first list2
176 30:bool/raw <- equal list, list2
178 memory-should-contain [
179 10 <- 5 # scanning next
182 13 <- 6 # inserted element
186 30 <- 1 # list back at start
190 scenario inserting-after-start-of-duplex-list [
192 list:&:duplex-list:num <- push 3, null
196 list <- insert 6, list
197 # check structure like before
198 list2:&:duplex-list:num <- copy list
199 10:num/raw <- first list2
201 11:num/raw <- first list2
203 12:num/raw <- first list2
205 13:num/raw <- first list2
207 20:num/raw <- first list2
209 21:num/raw <- first list2
211 22:num/raw <- first list2
212 30:bool/raw <- equal list, list2
214 memory-should-contain [
215 10 <- 5 # scanning next
216 11 <- 6 # inserted element
222 30 <- 1 # list back at start
226 # remove 'x' from its surrounding list 'in'
228 # Returns null if and only if list is empty. Beware: in that case any other
229 # pointers to the head are now invalid.
230 def remove x:&:duplex-list:_elem/contained-in:in, in:&:duplex-list:_elem -> in:&:duplex-list:_elem [
233 # if 'x' is null, return
235 next-node:&:duplex-list:_elem <- get *x, next:offset
236 prev-node:&:duplex-list:_elem <- get *x, prev:offset
238 *x <- put *x, next:offset, null
239 *x <- put *x, prev:offset, null
240 # if next-node is not null, set its prev pointer
242 break-unless next-node
243 *next-node <- put *next-node, prev:offset, prev-node
245 # if prev-node is not null, set its next pointer and return
247 break-unless prev-node
248 *prev-node <- put *prev-node, next:offset, next-node
251 # if prev-node is null, then we removed the head node at 'in'
252 # return the new head rather than the old 'in'
256 scenario removing-from-duplex-list [
258 list:&:duplex-list:num <- push 3, null
262 list2:&:duplex-list:num <- next list # second element
263 list <- remove list2, list
264 10:bool/raw <- equal list2, null
265 # check structure like before
267 11:num/raw <- first list2
269 12:num/raw <- first list2
270 20:&:duplex-list:num/raw <- next list2
272 30:num/raw <- first list2
273 40:bool/raw <- equal list, list2
275 memory-should-contain [
276 10 <- 0 # remove returned non-null
277 11 <- 5 # scanning next, skipping deleted element
279 20 <- 0 # no more elements
280 30 <- 5 # prev of final element
281 40 <- 1 # list back at start
285 scenario removing-from-start-of-duplex-list [
287 list:&:duplex-list:num <- push 3, null
291 list <- remove list, list
292 # check structure like before
293 list2:&:duplex-list:num <- copy list
294 10:num/raw <- first list2
296 11:num/raw <- first list2
297 20:&:duplex-list:num/raw <- next list2
299 30:num/raw <- first list2
300 40:bool/raw <- equal list, list2
302 memory-should-contain [
303 10 <- 4 # scanning next, skipping deleted element
305 20 <- 0 # no more elements
306 30 <- 4 # prev of final element
307 40 <- 1 # list back at start
311 scenario removing-from-end-of-duplex-list [
313 list:&:duplex-list:num <- push 3, null
317 # delete last element
318 list2:&:duplex-list:num <- next list
320 list <- remove list2, list
321 10:bool/raw <- equal list2, null
322 # check structure like before
324 11:num/raw <- first list2
326 12:num/raw <- first list2
327 20:&:duplex-list:num/raw <- next list2
329 30:num/raw <- first list2
330 40:bool/raw <- equal list, list2
332 memory-should-contain [
333 10 <- 0 # remove returned non-null
334 11 <- 5 # scanning next, skipping deleted element
336 20 <- 0 # no more elements
337 30 <- 5 # prev of final element
338 40 <- 1 # list back at start
342 scenario removing-from-singleton-duplex-list [
344 list:&:duplex-list:num <- push 3, null
346 list <- remove list, list
347 1:num/raw <- deaddress list
349 memory-should-contain [
350 1 <- 0 # back to an empty list
354 def remove x:&:duplex-list:_elem/contained-in:in, n:num, in:&:duplex-list:_elem -> in:&:duplex-list:_elem [
358 curr:&:duplex-list:_elem <- copy x
360 done?:bool <- greater-or-equal i, n
363 next:&:duplex-list:_elem <- next curr
364 in <- remove curr, in
371 scenario removing-multiple-from-duplex-list [
373 list:&:duplex-list:num <- push 3, null
377 list2:&:duplex-list:num <- next list # second element
378 list <- remove list2, 2, list
381 trace-should-contain [
386 # remove values between 'start' and 'end' (both exclusive).
387 # also clear pointers back out from start/end for hygiene.
388 # set end to 0 to delete everything past start.
389 # can't set start to 0 to delete everything before end, because there's no
390 # clean way to return the new head pointer.
391 def remove-between start:&:duplex-list:_elem, end:&:duplex-list:_elem/contained-in:start -> start:&:duplex-list:_elem [
394 next:&:duplex-list:_elem <- get *start, next:offset
395 nothing-to-delete?:bool <- equal next, end
396 return-if nothing-to-delete?
397 assert next, [malformed duplex list]
398 # start->next->prev = 0
400 *next <- put *next, prev:offset, null
401 *start <- put *start, next:offset, end
404 stash [spliced:] next
407 # end->prev->next = 0
409 prev:&:duplex-list:_elem <- get *end, prev:offset
410 assert prev, [malformed duplex list - 2]
411 *prev <- put *prev, next:offset, null
412 stash [spliced:] next
413 *end <- put *end, prev:offset, start
416 scenario remove-range [
417 # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
419 list:&:duplex-list:num <- push 18, null
420 list <- push 17, list
421 list <- push 16, list
422 list <- push 15, list
423 list <- push 14, list
424 list <- push 13, list
427 # first pointer: to the third element
428 list2:&:duplex-list:num <- next list
430 list2 <- remove-between list2, null
432 10:num/raw <- get *list, value:offset
434 11:num/raw <- get *list, value:offset
436 12:num/raw <- get *list, value:offset
437 20:&:duplex-list:num/raw <- next list
439 memory-should-contain [
445 trace-should-contain [
446 app: spliced: 16 <-> 17 <-> 18
450 scenario remove-range-to-final [
452 # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
453 list:&:duplex-list:num <- push 18, null
454 list <- push 17, list
455 list <- push 16, list
456 list <- push 15, list
457 list <- push 14, list
458 list <- push 13, list
460 # delete 15, 16 and 17
461 # start pointer: to the second element
462 list2:&:duplex-list:num <- next list
463 # end pointer: to the last (sixth) element
464 end:&:duplex-list:num <- next list2
468 remove-between list2, end
470 10:num/raw <- get *list, value:offset
472 11:num/raw <- get *list, value:offset
474 12:num/raw <- get *list, value:offset
475 20:&:duplex-list:num/raw <- next list
477 memory-should-contain [
481 20 <- 0 # no more elements
483 trace-should-contain [
484 app: spliced: 15 <-> 16 <-> 17
488 scenario remove-range-to-penultimate [
490 # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
491 list:&:duplex-list:num <- push 18, null
492 list <- push 17, list
493 list <- push 16, list
494 list <- push 15, list
495 list <- push 14, list
496 list <- push 13, list
499 # start pointer: to the second element
500 list2:&:duplex-list:num <- next list
501 # end pointer: to the last (sixth) element
502 end:&:duplex-list:num <- next list2
505 remove-between list2, end
507 10:num/raw <- get *list, value:offset
509 11:num/raw <- get *list, value:offset
511 12:num/raw <- get *list, value:offset
513 13:num/raw <- get *list, value:offset
514 20:&:duplex-list:num/raw <- next list
516 memory-should-contain [
521 20 <- 0 # no more elements
523 trace-should-contain [
524 app: spliced: 15 <-> 16
528 scenario remove-range-empty [
530 # construct a duplex list with three elements [13, 14, 15]
531 list:&:duplex-list:num <- push 15, null
532 list <- push 14, list
533 list <- push 13, list
535 # delete between first and second element (i.e. nothing)
536 list2:&:duplex-list:num <- next list
537 remove-between list, list2
539 10:num/raw <- get *list, value:offset
541 11:num/raw <- get *list, value:offset
543 12:num/raw <- get *list, value:offset
544 20:&:duplex-list:num/raw <- next list
547 memory-should-contain [
555 scenario remove-range-to-end [
557 # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
558 list:&:duplex-list:num <- push 18, null
559 list <- push 17, list
560 list <- push 16, list
561 list <- push 15, list
562 list <- push 14, list
563 list <- push 13, list
565 # remove the third element and beyond
566 list2:&:duplex-list:num <- next list
567 remove-between list2, null
569 10:num/raw <- get *list, value:offset
571 11:num/raw <- get *list, value:offset
572 20:&:duplex-list:num/raw <- next list
574 memory-should-contain [
581 # insert list beginning at 'start' after 'in'
582 def splice in:&:duplex-list:_elem, start:&:duplex-list:_elem/contained-in:in -> in:&:duplex-list:_elem [
587 end:&:duplex-list:_elem <- last start
588 next:&:duplex-list:_elem <- next in
591 *end <- put *end, next:offset, next
592 *next <- put *next, prev:offset, end
594 *in <- put *in, next:offset, start
595 *start <- put *start, prev:offset, in
598 # insert contents of 'new' after 'in'
599 def insert in:&:duplex-list:_elem, new:&:@:_elem -> in:&:duplex-list:_elem [
604 len:num <- length *new
606 curr:&:duplex-list:_elem <- copy in
609 done?:bool <- greater-or-equal idx, len
611 c:_elem <- index *new, idx
620 def append in:&:duplex-list:_elem, new:&:duplex-list:_elem/contained-in:in -> in:&:duplex-list:_elem [
623 last:&:duplex-list:_elem <- last in
624 *last <- put *last, next:offset, new
626 *new <- put *new, prev:offset, last
629 def last in:&:duplex-list:_elem -> result:&:duplex-list:_elem [
634 next:&:duplex-list:_elem <- next result
641 # does a duplex list start with a certain sequence of elements?
642 def match x:&:duplex-list:_elem, y:&:@:_elem -> result:bool [
648 done?:bool <- greater-or-equal i, max
650 expected:_elem <- index *y, i
651 return-unless x, false/no-match
652 curr:_elem <- first x
653 curr-matches?:bool <- equal curr, expected
654 return-unless curr-matches?, false/no-match
659 return true/successful-match
662 scenario duplex-list-match [
664 list:&:duplex-list:char <- push 97/a, null
665 list <- push 98/b, list
666 list <- push 99/c, list
667 list <- push 100/d, list
669 10:bool/raw <- match list, []
670 11:bool/raw <- match list, [d]
671 12:bool/raw <- match list, [dc]
672 13:bool/raw <- match list, [dcba]
673 14:bool/raw <- match list, [dd]
674 15:bool/raw <- match list, [dcbax]
676 memory-should-contain [
678 11 <- 1 # matches [d]
679 12 <- 1 # matches [dc]
680 13 <- 1 # matches [dcba]
681 14 <- 0 # does not match [dd]
682 15 <- 0 # does not match [dcbax]
686 # helper for debugging
687 def dump-from x:&:duplex-list:_elem [
693 c:_elem <- get *x, value:offset
697 is-newline?:bool <- equal c, 10/newline
698 break-unless is-newline?
704 $print 10/newline, [---], 10/newline
707 scenario stash-duplex-list [
709 list:&:duplex-list:num <- push 1, null
715 trace-should-contain [
716 app: list: 3 <-> 2 <-> 1
720 def to-text in:&:duplex-list:_elem -> result:text [
723 buf:&:buffer:char <- new-buffer 80
724 buf <- to-buffer in, buf
725 result <- buffer-to-array buf
728 # variant of 'to-text' which stops printing after a few elements (and so is robust to cycles)
729 def to-text-line in:&:duplex-list:_elem -> result:text [
732 buf:&:buffer:char <- new-buffer 80
733 buf <- to-buffer in, buf, 6 # max elements to display
734 result <- buffer-to-array buf
737 def to-buffer in:&:duplex-list:_elem, buf:&:buffer:char -> buf:&:buffer:char [
742 buf <- append buf, [[]]
745 # append in.value to buf
746 val:_elem <- get *in, value:offset
747 buf <- append buf, val
749 next:&:duplex-list:_elem <- next in
750 nextn:num <- deaddress next
752 buf <- append buf, [ <-> ]
754 remaining:num, optional-input-found?:bool <- next-input
756 break-if optional-input-found?
757 # unlimited recursion
758 buf <- to-buffer next, buf
762 break-unless remaining
764 remaining <- subtract remaining, 1
765 buf <- to-buffer next, buf, remaining
768 # past recursion depth; insert ellipses and stop
772 scenario stash-empty-duplex-list [
774 x:&:duplex-list:num <- copy null
778 trace-should-contain [