retroforth/interface/ll.retro

144 lines
3.4 KiB
Forth
Raw Normal View History

# Forward Linked Lists
(c) Arland Childers
~~~
:comma , ;
~~~
The basic building block of our lists is a *cons cell*. This is
a tuple with two values. The first of these is the *car*, and
the second is the *cdr*. (Naming of these comes from LISP, and
some very old computer processors)
In memory a cons cell with two values (1, 2) would look like:
+---+-----+
| 1 | car |
+---+-----+
| 2 | cdr |
+---+-----+
We provide `cons` to make a cons cell, `car` to get a pointer to
the first element, and `cdr` to get a pointer to the second.
We also provide `car@` and `car!` to read or modify the car
value, and `cdr@` and `cdr!` for doing the same to the cdr.
~~~
:cons (car,cdr-ptr) here [ swap comma comma ] dip ;
:car (cons-ptr) ;
:cdr (cons-ptr) n:inc ;
:car@ (cons-value) car fetch ;
:car! (value,cons-) car store ;
:cdr@ (cons-value) cdr fetch ;
:cdr! (value,cons-) cdr store ;
~~~
With this out of the way, we turn to lists. A list, in this
model, is a series of cons cells. We are working with forward
linked lists, so each cons cell points to the next one in the
chain. (Back linked lists, or doubly linked lists, are not
implemented at this time). A list of values 1, 2, and 3 would
look like:
+---+---+ +---+---+ +---+---+
| 1 | =====> | 2 | =====> | 3 | =====> END
+---+---+ +---+---+ +---+---+
`END` is an empty word that we use to denote the end of a list.
We'll compare the contents of the CDR field to this to determine
if we've reached the end. This could equally well be zero, but
we decided it was better for readability to have a named element.
~~~
:END (-) ;
~~~
Creation of a list begins with `fll:create`. This takes in a
value, and creates a cons cell with the value, and a pointer
to END in the cdr.
~~~
:fll:create (v-p) &END cons ;
~~~
Many operations will need to access the end of the list. We have
`fll:to-end` to seek this. It'll walk through the list, returning
a pointer to the final cons cell.
~~~
{{
'r var
---reveal---
:fll:to-end (p-p)
dup !r [ cdr@ dup &END -eq? dup
[ over !r ] [ nip ] choose ] while @r ;
}}
~~~
Appening a value is simple. Create a new cons with the value in
the car and a pointer to END in the cdr. Find the end of the
list, and update the cdr to point to the new cons.
~~~
:fll:append/value (pv-) &END cons swap fll:to-end cdr! ;
~~~
Given a specific node in the list, follow the cdr chain until
the node number is reached. Starts at zero.
An issue with this: it does not currently check the length to
make sure that the index is valid.
~~~
:fll:to-index (pn-p) [ cdr@ ] times ;
~~~
~~~
:fll:del
dup-pair n:dec fll:to-index [ n:inc fll:to-index ] dip cdr! ;
~~~
The `fll:for-each` runs a quote once for each value in a list.
~~~
{{
'Action var
---reveal---
:fll:for-each (aq-)
!Action [ [ car@ @Action call ] sip cdr@ dup &END -eq? ]
while drop ;
}}
~~~
To obtain the length of a list (in number of cons) use
`fll:length`.
~~~
:fll:length (p-n) #0 swap [ drop n:inc ] fll:for-each n:dec ;
~~~
`fll:drop` removes the a specified cons from a list.
~~~
:fll:drop dup fll:length n:dec fll:to-index &END swap cdr! ;
~~~
`fll:inject` adds a new cons to a list, inserting at a specified
point.
~~~
{{
'i var
---reveal---
:fll:inject (piv-)
fll:create !i dup-pair n:dec fll:to-index
&fll:to-index dip @i swap cdr! @i cdr! ;
:fll:put (a-) [ n:put sp ] fll:for-each ;
}}
~~~