3450 lines
55 KiB
Text
3450 lines
55 KiB
Text
================================================================
|
|
|
|
,dPYb, ,dPYb,
|
|
IP'`Yb IP'`Yb
|
|
I8 8I gg I8 8I
|
|
I8 8bgg, "" I8 8'
|
|
I8 dP" "8 ,ggggg, ,ggg,,ggg, gg I8 dP ,ggggg,
|
|
I8d8bggP" dP" "Y8 ,8" "8P" "8, 88 I8dP dP" "Y8
|
|
I8P' "Yb, i8' ,8I d8 8I 8I 88 I8P i8' ,8I
|
|
,d8 `Yb,,d8, ,d8P8P 8I Yb,_,88,_,d8b,_ ,d8, ,d8'
|
|
88P Y8P"Y8888P" 8I `Y88P""Y88P'"Y88P"Y8888P"
|
|
|
|
================================================================
|
|
|
|
Copyright (c) charles childers
|
|
|
|
================================================================
|
|
|
|
This is a small, pragmatic forth running on the ilo virtual
|
|
computer. The source is written in unu (see unu.retroforth.org),
|
|
a format for literate programming.
|
|
|
|
Commentary is mixed throughout the code. I'll try to keep the
|
|
comments brief, describing the provided functions and adding in
|
|
notes where I think they are relevant.
|
|
|
|
Let's take a brief moment to look at the source format. The code
|
|
is placed between fences (the ~~~ / ~~~) pairs. Anything outside
|
|
of these fences is commentary. In addition, I have comment lines
|
|
(starting with "c ") inside the code blocks.
|
|
|
|
The code here is assembly, for the `pali` assembler.
|
|
|
|
Pali assembly takes the form of:
|
|
|
|
<directive> <data>
|
|
|
|
Directives are a single character. The length of the data part
|
|
varies.
|
|
|
|
Directives are:
|
|
|
|
+-----------+--------------------------------------------------+
|
|
| Directive | Action |
|
|
+===========+==================================================+
|
|
| : | data is a label |
|
|
| i | data is an instruction bundle |
|
|
| r | data is a reference to a label |
|
|
| - | data is a reference to a label |
|
|
| d | data is an integer (decimal) value |
|
|
| s | data is a string |
|
|
| c | data is a comment; ignored by pali |
|
|
| * | data is an integer, the number is the amount |
|
|
| | of space to allocate |
|
|
+-----------+--------------------------------------------------+
|
|
|
|
Instruction bundles are groups of four ilo instructions. The
|
|
instruction names are identified by two letter codes. These are
|
|
shown in the following tables:
|
|
|
|
Opode Instruction Names Data Stack Effects
|
|
===== ================= ====================================
|
|
00-05 .. li du dr sw pu - -n n-nn n- nm-mn n-
|
|
06-11 po ju ca cc cj re -n a- a- af- af- -
|
|
12-17 eq ne lt gt fe st nn-f nn-f nn-f nn-f a-n na-
|
|
18-23 ad su mu di an or nn-n nn-n nn-nn nn-n nn-n nn-n
|
|
24-29 xo sl sr cp cy io nn-n nn-n nn-n nnn- nnn- n-
|
|
===== ================= ====================================
|
|
|
|
An important note: instructions that modify the instruction
|
|
pointer must only be followed by a non-op. These instructions
|
|
are: ju ca cc cj re
|
|
|
|
================================================================
|
|
|
|
On startup ilo will load a memory image (the "rom") into ram and
|
|
jump to address 0. I call `setup:sigls` to setup the environment
|
|
and then jump into `forth` to begin actually handling the user
|
|
environment.
|
|
|
|
ilo provides 65,536 memory words. For Konilo, these are
|
|
organized as:
|
|
|
|
+---------------+----------------------------------------------+
|
|
| Range | Used For |
|
|
+===============+==============================================+
|
|
| 00000 - 59999 | forth & user code / data |
|
|
| 60000 - 61025 | block buffer |
|
|
| 61026 - 61091 | string evaluation buffers |
|
|
| 61092 - 61104 | loop indices |
|
|
| 61105 - 61139 | number conversion |
|
|
| 61140 - 61141 | lexical scope |
|
|
| 61142 - 61206 | needs buffer |
|
|
| 61207 - 61335 | sigils table |
|
|
| 61336 - 64377 | reserved for future system use |
|
|
| 64378 - 65406 | string buffers (8 x 127 chars) |
|
|
| 65407 - 65535 | text input buffer (127 chars) |
|
|
+---------------+----------------------------------------------+
|
|
|
|
I map names to these before proceeding. This will help keep
|
|
later code using these more readable.
|
|
|
|
~~~
|
|
c set to 1 after the actual start to leave room for a length
|
|
c cell
|
|
o 60001
|
|
: sys:buffers/block
|
|
|
|
o 60126
|
|
: sys:buffers/string-eval
|
|
|
|
o 61092
|
|
: sys:buffers/loops
|
|
|
|
o 61105
|
|
: sys:buffers/numeric-conversion
|
|
|
|
o 61140
|
|
: sys:buffers/scope
|
|
|
|
o 61142
|
|
: sys:buffers/needs
|
|
|
|
o 61207
|
|
: Sigils
|
|
* 128
|
|
|
|
o 61336
|
|
: sys:buffers/reserved
|
|
|
|
o 64378
|
|
: sys:buffers/strings+arrays
|
|
|
|
o 65407
|
|
: sys:buffers/input
|
|
o 65408
|
|
: sys:buffers/input/text
|
|
o 65409
|
|
: sys:buffers/input/text+1
|
|
~~~
|
|
|
|
After creating the labels for the system buffers, return to
|
|
address 0 to start the actual code.
|
|
|
|
On startup this calls a function to populate the default sigils,
|
|
then jumps into forth.
|
|
|
|
~~~
|
|
o 0
|
|
i lica....
|
|
r setup:sigils
|
|
i liju....
|
|
r forth
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
With the configuration out of the way, I move on to implementing
|
|
the various words.
|
|
|
|
Being a Forth, functions or subroutines are called `words`.
|
|
Information about these is stored in a `dictionary`.
|
|
|
|
The first set of words I'm defining map directly to the
|
|
instructions in the ilo computer.
|
|
|
|
`dup` duplicates the top item on the data stack.
|
|
|
|
+---+ +---+---+
|
|
| 1 | ==> | 1 | 1 |
|
|
+---+ +---+---+
|
|
|
|
~~~
|
|
: dup
|
|
c (n-nn)
|
|
i dure....
|
|
~~~
|
|
|
|
`drop` discards the top item on the data stack.
|
|
|
|
+---+---+ +---+
|
|
| 1 | 2 | ==> | 1 |
|
|
+---+---+ +---+
|
|
|
|
~~~
|
|
: drop
|
|
c (n-)
|
|
i drre....
|
|
~~~
|
|
|
|
`swap` exchanges the positions of the top two values on the
|
|
data stack.
|
|
|
|
+---+---+ +---+---+
|
|
| 1 | 2 | ==> | 2 | 1 |
|
|
+---+---+ +---+---+
|
|
|
|
~~~
|
|
: swap
|
|
c (nm-mn)
|
|
i swre....
|
|
~~~
|
|
|
|
`eq?` compares two items on the stack for equality. If they are
|
|
equal, it will push a true (-1) flag. If they are not equal, it
|
|
pushes false (0) instead.
|
|
|
|
+---+---+ +----+
|
|
| 1 | 1 | ==> | -1 |
|
|
+---+---+ +----+
|
|
|
|
+---+---+ +----+
|
|
| 2 | 1 | ==> | 0 |
|
|
+---+---+ +----+
|
|
|
|
~~~
|
|
: eq?
|
|
c (nn-f)
|
|
i eqre....
|
|
~~~
|
|
|
|
`-eq?` compares two items on the stack for inequality. If they
|
|
are not equal, it will push a true (-1) flag. If they are equal,
|
|
it pushes false (0) instead.
|
|
|
|
+---+---+ +----+
|
|
| 1 | 1 | ==> | 0 |
|
|
+---+---+ +----+
|
|
|
|
+---+---+ +----+
|
|
| 2 | 1 | ==> | -1 |
|
|
+---+---+ +----+
|
|
|
|
~~~
|
|
: neq?
|
|
c (nn-f)
|
|
i nere....
|
|
~~~
|
|
|
|
+---+---+ +----+
|
|
| 1 | 1 | ==> | 0 |
|
|
+---+---+ +----+
|
|
|
|
+---+---+ +----+
|
|
| 2 | 1 | ==> | 0 |
|
|
+---+---+ +----+
|
|
|
|
+---+---+ +----+
|
|
| 1 | 2 | ==> | -1 |
|
|
+---+---+ +----+
|
|
|
|
~~~
|
|
: lt?
|
|
c (nn-f)
|
|
i ltre....
|
|
~~~
|
|
|
|
+---+---+ +----+
|
|
| 1 | 1 | ==> | 0 |
|
|
+---+---+ +----+
|
|
|
|
+---+---+ +----+
|
|
| 2 | 1 | ==> | -1 |
|
|
+---+---+ +----+
|
|
|
|
+---+---+ +----+
|
|
| 1 | 2 | ==> | 0 |
|
|
+---+---+ +----+
|
|
|
|
~~~
|
|
: gt?
|
|
c (nn-f)
|
|
i gtre....
|
|
~~~
|
|
|
|
~~~
|
|
: fetch
|
|
c (p-n)
|
|
i fere....
|
|
~~~
|
|
|
|
~~~
|
|
: store
|
|
c (np-)
|
|
i stre....
|
|
~~~
|
|
|
|
`n:add` adds two values, returning the result.
|
|
|
|
+---+---+ +---+
|
|
| 1 | 2 | ==> | 3 |
|
|
+---+---+ +---+
|
|
|
|
~~~
|
|
: n:add
|
|
c (nn-n)
|
|
i adre....
|
|
~~~
|
|
|
|
`n:sub` subtracts two values, returning the result.
|
|
|
|
+---+---+ +---+
|
|
| 3 | 1 | ==> | 2 |
|
|
+---+---+ +---+
|
|
|
|
~~~
|
|
: n:sub
|
|
c (nn-n)
|
|
i sure....
|
|
~~~
|
|
|
|
`n:mul` multiplies two values, returning the result.
|
|
|
|
+---+---+ +---+
|
|
| 2 | 3 | ==> | 6 |
|
|
+---+---+ +---+
|
|
|
|
~~~
|
|
: n:mul
|
|
c (nn-n)
|
|
i mure....
|
|
~~~
|
|
|
|
`n:divmod` divides two values, returning the result and
|
|
remainer.
|
|
|
|
~~~
|
|
: n:divmod
|
|
c (nn-nn)
|
|
i dire....
|
|
~~~
|
|
|
|
~~~
|
|
: and
|
|
c (nn-n)
|
|
i anre....
|
|
~~~
|
|
|
|
~~~
|
|
: or
|
|
c (nn-n)
|
|
i orre....
|
|
~~~
|
|
|
|
~~~
|
|
: xor
|
|
c (nn-n)
|
|
i xore....
|
|
~~~
|
|
|
|
~~~
|
|
: shift-left
|
|
c (nn-n)
|
|
i slre....
|
|
~~~
|
|
|
|
~~~
|
|
: shift-right
|
|
c (nn-n)
|
|
i srre....
|
|
~~~
|
|
|
|
`io` triggers an i/o operation. The basic ilo system provides
|
|
just a few i/o devices.
|
|
|
|
Using this is a matter of pushing any need values to the stack,
|
|
pushing the i/o device number, then calling `io`. For a list of
|
|
devices:
|
|
|
|
+------------+--------------------------------------+----------+
|
|
| I/O Device | Action | Required |
|
|
+============+======================================+==========+
|
|
| 0 | Display a character. Consumes a | Yes |
|
|
| | character from the stack. | |
|
|
| 1 | Read a character from the input | Yes |
|
|
| | device. The character is pushed to | |
|
|
| | the stack. | |
|
|
| 2 | Read a block into memory. | Yes |
|
|
| 3 | Write memory to a block. | Yes |
|
|
| 4 | Write all memory to an image/rom. | No |
|
|
| 5 | Reload the image/rom, and jump to | Yes |
|
|
| | address 0. Also empty all stacks. | |
|
|
| 6 | End execution. On a hosted system, | No |
|
|
| | exit ilo. If native, suspend | |
|
|
| | execution. | |
|
|
| 7 | Obtain stack depths. Pushes the data | Yes |
|
|
| | depth then the address depth. | |
|
|
+------------+-------------------------------------------------+
|
|
|
|
~~~
|
|
: io
|
|
c (...n-?)
|
|
i iore....
|
|
~~~
|
|
|
|
`copy` copies "n" cells of memory starting at p1 to p2. For
|
|
example:
|
|
|
|
+---+---+---+---+---+---+---+---+---+
|
|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|
|
+---+---+---+---+---+---+---+---+---+
|
|
| A | B | C | D | D | E | A | B | C |
|
|
+---+---+---+---+---+---+---+---+---+
|
|
|
|
With a stack of:
|
|
|
|
#2 #6 #3
|
|
|
|
This would change memory to:
|
|
|
|
+---+---+---+---+---+---+---+---+---+
|
|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|
|
+---+---+---+---+---+---+---+---+---+
|
|
| A | B | C | D | D | B | C | D | C |
|
|
+---+---+---+---+---+---+---+---+---+
|
|
|
|
The ilo specification does not require the system to support
|
|
overlapping copies. If the memory areas would overlap, test
|
|
well and write a specific word to handle the overlap if needed.
|
|
|
|
~~~
|
|
: copy
|
|
c (ppn-)
|
|
i cyre....
|
|
~~~
|
|
|
|
`compare` will compare "n" cells of memory starting at p1 and
|
|
p2.
|
|
|
|
As an example:
|
|
|
|
+---+---+---+---+---+---+---+---+---+
|
|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|
|
+---+---+---+---+---+---+---+---+---+
|
|
| A | B | C | D | D | E | A | B | C |
|
|
+---+---+---+---+---+---+---+---+---+
|
|
|
|
With a stack of:
|
|
|
|
#1 #7 #3
|
|
|
|
This would return true (-1) as the ABC sequences at 1 & 7 match.
|
|
But a stack of:
|
|
|
|
#1 #5 #3
|
|
|
|
Would return false (0) since the sequences (ABC & DEA) do not
|
|
match.
|
|
|
|
~~~
|
|
: compare
|
|
c (ppn-)
|
|
i cpre....
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
The next set makes a copy of the second item on the stack, puts
|
|
a copy of the top item under the second item, discards the
|
|
second item, discards the top two items, and makes copies of the
|
|
top two items.
|
|
|
|
+---+---+ +---+---+---+
|
|
| 1 | 2 | ==> | 1 | 2 | 1 |
|
|
+---+---+ +---+---+---+
|
|
|
|
~~~
|
|
: over
|
|
c (xy-xyx)
|
|
i puduposw
|
|
i re......
|
|
~~~
|
|
|
|
+---+---+ +---+---+---+
|
|
| 1 | 2 | ==> | 2 | 1 | 2 |
|
|
+---+---+ +---+---+---+
|
|
|
|
~~~
|
|
: tuck
|
|
c (xy-yxy)
|
|
i dupuswpo
|
|
i re......
|
|
~~~
|
|
|
|
+---+---+ +---+
|
|
| 1 | 2 | ==> | 2 |
|
|
+---+---+ +---+
|
|
|
|
~~~
|
|
: nip
|
|
c (xy-y)
|
|
i swdrre..
|
|
~~~
|
|
|
|
`drop-pair` discards the top two stack items. (In some Forths
|
|
this is called "2drop")
|
|
|
|
+---+---+
|
|
| 1 | 2 | ==>
|
|
+---+---+
|
|
|
|
~~~
|
|
: drop-pair
|
|
c (xy-)
|
|
i drdrre..
|
|
~~~
|
|
|
|
`dup-pair` makes a copy of the top two stack items. (In some
|
|
Forths this is called "2dup")
|
|
|
|
+---+---+ +---+---+---+---+
|
|
| 1 | 2 | ==> | 1 | 2 | 1 | 2 |
|
|
+---+---+ +---+---+---+---+
|
|
|
|
~~~
|
|
: dup-pair
|
|
c (xy-xyxy)
|
|
i puduposw
|
|
i puduposw
|
|
i re......
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
There is a second stack, which holds return addresses. It's
|
|
used for subroutine calls, but you can temporarily store data
|
|
there as well. `push` and `pop` provide access to this stack.
|
|
|
|
Note that this is more complicated than simply pushing values.
|
|
Thes definitions take the direct threading implementation into
|
|
account to ensure that code does not crash when using them, as
|
|
long as the `push` and `pop` operations are paired correctly.
|
|
|
|
~~~
|
|
: push
|
|
c (n-) (A:-n)
|
|
i poswposw
|
|
i pupupure
|
|
|
|
: pop
|
|
c (-n) (A:n-)
|
|
i popoposw
|
|
i puswpure
|
|
~~~
|
|
|
|
A better way to work with data items on the address stack is to
|
|
use `dip` and `sip`. Using these ensures that the address stack
|
|
remains balanced, and is more idomatic.
|
|
|
|
In this case, `dip` removes the value before calling the
|
|
function, but `sip` does not. In both, the value is restored to
|
|
the stack after the function return.
|
|
|
|
~~~
|
|
: dip
|
|
c (np-n)
|
|
i swpuca..
|
|
i pore....
|
|
|
|
: sip
|
|
c (np-n)
|
|
i puduposw
|
|
i puca....
|
|
i pore....
|
|
~~~
|
|
|
|
For those familiar with traditional Forth systems, the `push`,
|
|
`pop`, `dip`, and `sip` are similar to:
|
|
|
|
+--------------+---------------+
|
|
| Konilo | Traditional |
|
|
+==============+===============+
|
|
| push ... pop | >R ... R> |
|
|
| [ ... ] dip | >R ... R> |
|
|
| [ ... ] sip | dup >R ... R> |
|
|
+--------------+---------------+
|
|
|
|
================================================================
|
|
|
|
Konilo provides a number of words for controlling the flow
|
|
of execution.
|
|
|
|
The initial building blocks are calls, jumps, and returns. A
|
|
call takes an address and jumps to it, saving the previous
|
|
execution address on the return (or address) stack. Return will
|
|
take the address from the return stack and jump back to it. A
|
|
jump is like call, but does not save a return address and thus
|
|
can not be used with return.
|
|
|
|
~~~
|
|
: jump
|
|
c (p-)
|
|
i popopodr
|
|
i drdrju..
|
|
|
|
: call
|
|
c (p-)
|
|
i ju......
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
`?jump` branches to a function if a passed flag is true
|
|
(non-zero).
|
|
|
|
~~~
|
|
: ?jump
|
|
i swline..
|
|
d 0
|
|
i licj....
|
|
r jump
|
|
i drre....
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
# Loops
|
|
|
|
The `times` combinator takes a count and an address. It then
|
|
runs the function the specified number of times.
|
|
|
|
An example:
|
|
|
|
#0 #10 [ dup n:put n:inc ] times drop
|
|
|
|
~~~
|
|
: times
|
|
c (np-)
|
|
i sw......
|
|
: times.loop
|
|
i dulieq..
|
|
d 0
|
|
i licj....
|
|
r times.done
|
|
i lisupudu
|
|
d 1
|
|
i puca....
|
|
i popoliju
|
|
r times.loop
|
|
: times.done
|
|
i drdrre..
|
|
~~~
|
|
|
|
The `until` combinator takes a function address. The function
|
|
must leave a flag on the stack. `until` runs the function
|
|
repeatedly until the flag is true (non-zero).
|
|
|
|
~~~
|
|
: until
|
|
c (p-)
|
|
c pointer must return a value to be used as a flag
|
|
i dupuca..
|
|
i poswlieq
|
|
d 0
|
|
i licj....
|
|
r until
|
|
i drre....
|
|
~~~
|
|
|
|
The `while` combinator takes a function address. The function
|
|
must leave a flag on the stack. `while` runs the function
|
|
repeatedly until the flag is false (zero).
|
|
|
|
~~~
|
|
: while
|
|
c (p-)
|
|
c pointer must return a value to be used as a flag
|
|
i dupuca..
|
|
i poswline
|
|
d 0
|
|
i licj....
|
|
r while
|
|
i drre....
|
|
~~~
|
|
|
|
`forever` runs a function repeatedly in an unending loop.
|
|
|
|
~~~
|
|
: forever
|
|
c (p-)
|
|
i dupuca..
|
|
i poliju..
|
|
r forever
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
`restart` triggers the reset functionality in ilo. This will
|
|
reload the image, reset the stack pointers to empty, and then
|
|
jump to address 0.
|
|
|
|
~~~
|
|
: restart
|
|
c (...-)
|
|
i liio....
|
|
d 5
|
|
~~~
|
|
|
|
`bye` triggers the shutdown functionality it ilo. On a hosted
|
|
system, this will normally return to the system shell.
|
|
|
|
~~~
|
|
: bye
|
|
c (-)
|
|
i liio....
|
|
d 6
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
# Block I/O
|
|
|
|
Block loading & saving is a frequent operation for me, so I have
|
|
these here to make them a little more efficient.
|
|
|
|
~~~
|
|
: block:load
|
|
c (np-)
|
|
i pulifead
|
|
r BaseBlock
|
|
i poliiore
|
|
d 2
|
|
|
|
: block:save
|
|
c (np-)
|
|
i pulifead
|
|
r BaseBlock
|
|
i poliiore
|
|
d 3
|
|
|
|
: BaseBlock
|
|
d 0
|
|
~~~
|
|
|
|
# Maths
|
|
|
|
`n:zero?` and `n:-zero?` compare a value to zero.
|
|
|
|
~~~
|
|
: n:zero?
|
|
c (n-f)
|
|
i lieqre..
|
|
d 0
|
|
|
|
: n:-zero?
|
|
c (n-f)
|
|
i linere..
|
|
d 0
|
|
~~~
|
|
|
|
`n:divmod` wraps the instruction that divides and returns the
|
|
quotient and remainder. These separate out the functionality.
|
|
|
|
~~~
|
|
: n:mod
|
|
c (n-n)
|
|
i didrre..
|
|
|
|
: n:div
|
|
c (n-n)
|
|
i diswdrre
|
|
~~~
|
|
|
|
`n:min` returns the lesser of two values; `n:max` returns the
|
|
greater.
|
|
|
|
~~~
|
|
: n:min
|
|
c (nn-n)
|
|
i lica....
|
|
r dup-pair
|
|
i ltlilili
|
|
r drop
|
|
r nip
|
|
r choose
|
|
i ju......
|
|
~~~
|
|
|
|
~~~
|
|
: n:max
|
|
c (nn-n)
|
|
i lica....
|
|
r dup-pair
|
|
i gtlilili
|
|
r drop
|
|
r nip
|
|
r choose
|
|
i ju......
|
|
~~~
|
|
|
|
I use `n:min` and `n:max` to implement `n:limit`, which takes a
|
|
value, and an upper and lower limit. It will return the value
|
|
if it is in range, or the closest value in range if outside it.
|
|
|
|
~~~
|
|
: n:limit
|
|
i swlilica
|
|
r n:min
|
|
r dip
|
|
i liju....
|
|
r n:max
|
|
~~~
|
|
|
|
~~~
|
|
: not
|
|
c (n-n)
|
|
i lixore..
|
|
d -1
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
# Memory
|
|
|
|
`fetch-next` takes an address. It fetches the value stored at
|
|
the address, and returns both this and the next address.
|
|
|
|
~~~
|
|
: fetch-next
|
|
c (p-pn)
|
|
i duliadsw
|
|
d 1
|
|
i fere....
|
|
~~~
|
|
|
|
`store-next` takes a value and an address. It stores the value
|
|
in the address, then returns the next address.
|
|
|
|
~~~
|
|
: store-next
|
|
c (np-p)
|
|
i duliadpu
|
|
d 1
|
|
i stpore..
|
|
~~~
|
|
|
|
`fill` is used to fill a region of memory with a specified
|
|
value.
|
|
|
|
Takes a value, and address, and a length.
|
|
|
|
~~~
|
|
: fill
|
|
c (npn-)
|
|
i pulica..
|
|
r dup-pair
|
|
i stliadpo
|
|
d 1
|
|
i lisuduli
|
|
d 1
|
|
d 0
|
|
i eqlicj..
|
|
r fill.done
|
|
i liju....
|
|
r fill
|
|
: fill.done
|
|
i drdrdrre
|
|
~~~
|
|
|
|
`gc` runs a function, saving and restoring the value of `Free`.
|
|
This replaces the pattern of `@Free [ ... ] dip !Free` with
|
|
`[ ... ] gc`, a shorter and more readable approach.
|
|
|
|
~~~
|
|
: gc
|
|
c (p-)
|
|
i lifepuca
|
|
r Free
|
|
i polistre
|
|
r Free
|
|
~~~
|
|
|
|
# Conditionals
|
|
|
|
`choose` takes a flag and two function addresses. If the flag
|
|
is true (non-zero), it runs the first function. If false (zero),
|
|
it will run the second.
|
|
|
|
This works by storing the pointers in a table. A pointer to the
|
|
second entry in the table (the false operation) is then placed
|
|
on the stack, and the flag is added to the pointer. If true (-1)
|
|
it will then point to the true function. I can then just fetch
|
|
and branch.
|
|
|
|
A small bit of logic is inserted to ensure the flags are either
|
|
-1 or 0.
|
|
|
|
~~~
|
|
: choice:true
|
|
d 0
|
|
|
|
: choice:false
|
|
d 0
|
|
|
|
: choose
|
|
c (fpp-)
|
|
c store possible branch targets
|
|
i listlist
|
|
r choice:false
|
|
r choice:true
|
|
c normalize the flag to 0 or -1, then add this to the "false"
|
|
c target address
|
|
i lineliad
|
|
d 0
|
|
r choice:false
|
|
c branch to the selected target
|
|
i feju....
|
|
~~~
|
|
|
|
`if` and `-if` each take a flag and a function pointer. `if`
|
|
will run the function if the flag is true. `-if` will run it if
|
|
the flag is false.
|
|
|
|
~~~
|
|
: -if
|
|
c (fp-)
|
|
i pulieqpo
|
|
d 0
|
|
: if
|
|
c (fp-)
|
|
i cj......
|
|
i re......
|
|
~~~
|
|
|
|
`lteq?` checks to see if a value is less than or equal to
|
|
another value. Given:
|
|
|
|
| 1 | 2 |
|
|
|
|
This would return true (-1) since 1 is less than or equal to 2.
|
|
|
|
~~~
|
|
: lteq?
|
|
c (nn-f)
|
|
i lica....
|
|
r dup-pair
|
|
i eqpultpo
|
|
i orre....
|
|
~~~
|
|
|
|
`gteq?` is like `lteq?`, but checks for greater than or equal
|
|
to.
|
|
|
|
~~~
|
|
: gteq?
|
|
c (nn-f)
|
|
i swliju..
|
|
r lteq?
|
|
~~~
|
|
|
|
# Dictionary
|
|
|
|
The dictionary maps names to functions. It is set up as a simple
|
|
linked list, with the following fields:
|
|
|
|
+---+-----------+---------------------------+
|
|
| # | Accessor | Description |
|
|
+===+===========+===========================+
|
|
| 0 | d:link | link to previous entry |
|
|
| 1 | d:hash | hash of word name |
|
|
| 2 | d:address | pointer to word start |
|
|
| 3 | d:flags | special flags (immediate) |
|
|
+---+-----------+---------------------------+
|
|
|
|
Note that the name of the word is not actually saved, just a
|
|
hash of it. This saves considerable space and makes lookups
|
|
fast, but comes at the cost of some flexibility.
|
|
|
|
Prior implementations of Konilo have used *word classes* to
|
|
handle different word behaviors. This implementation does not.
|
|
You have normal words or immediate ones, with all other cases
|
|
being handled by use of sigils.
|
|
|
|
Immediate words are kept to a minimum:
|
|
|
|
; [ ]
|
|
|
|
`d:lookup` takes a string with the word name and returns the
|
|
address of the dictionary header, or zero if the word is not
|
|
found in the dictionary.
|
|
|
|
~~~
|
|
: d:lookup
|
|
c (s-d)
|
|
|
|
c get the hash of the nam we are trying to find
|
|
i lica....
|
|
r a:hash
|
|
|
|
c push a pointer to the most recent entry to the stack,
|
|
c then run a loop looking for the end. When done, clean
|
|
c up the stack and return a pointer to the dictionary
|
|
c header (or 0 if not found)
|
|
i lifelica
|
|
r Latest
|
|
r d:lookup.next
|
|
i swdrre..
|
|
|
|
: d:lookup.next
|
|
c the lookup loop begins
|
|
|
|
c duplicate the top two entries on the stack
|
|
i puduposw
|
|
i puduposw
|
|
|
|
c get the contents of the `d:hash` field; compare
|
|
c to the desired hash. If they match, branch to
|
|
c "d:lookup.done".
|
|
i liadfene
|
|
d 1
|
|
i dulieqli
|
|
d 0
|
|
r d:lookup.done
|
|
i cj......
|
|
|
|
c if not a match, we fetch the next header. If it
|
|
c is zero, no other entries remain. Otherwise we
|
|
c then repeat the loop.
|
|
i drfedudu
|
|
i lieqlicj
|
|
d 0
|
|
r d:lookup.done
|
|
i drliju..
|
|
r d:lookup.next
|
|
|
|
c finish up and return to `d:lookup`
|
|
: d:lookup.done
|
|
i drre....
|
|
~~~
|
|
|
|
# Dictionary Fields
|
|
|
|
Given a dictionary pointer, these return the address of each
|
|
field.
|
|
|
|
~~~
|
|
: d:link
|
|
c (d-p)
|
|
i re......
|
|
|
|
: d:hash
|
|
c (d-p)
|
|
i liadre..
|
|
d 1
|
|
|
|
: d:address
|
|
c (d-p)
|
|
i liadre..
|
|
d 2
|
|
|
|
: d:flags
|
|
c (d-p)
|
|
c The flags field is currently only used to track
|
|
c immediate words.
|
|
i liadre..
|
|
d 3
|
|
~~~
|
|
|
|
`d:exists?` checks for the presence of a word. It uses
|
|
`d:lookup` to seach for a word, then returns either true (-1)
|
|
or false (0).
|
|
|
|
~~~
|
|
: d:exists?
|
|
c (s-f)
|
|
i lica....
|
|
r d:lookup
|
|
i linere..
|
|
d 0
|
|
~~~
|
|
|
|
# Arrays & Strings
|
|
|
|
Arrays are linear sequences of values. They start with a count
|
|
and are immediately followed by the values.
|
|
|
|
Strings are arrays where the values are the ASCII character
|
|
codes.
|
|
|
|
E.g.,
|
|
|
|
'hello
|
|
|
|
In memory is:
|
|
|
|
5 104 101 108 108 111
|
|
|
|
The array words are placed in the `a:` namespace. They are
|
|
mirrored under the `s:` namespace for strings.
|
|
|
|
`a:length` takes a pointer to an array and returns the length.
|
|
|
|
~~~
|
|
: a:length
|
|
c (a-n)
|
|
i fere....
|
|
~~~
|
|
|
|
`a:copy` makes a copy of an array. Provide a pointer to the
|
|
array and a pointer to the destination.
|
|
|
|
:a:copy over a:length n:inc copy ;
|
|
|
|
~~~
|
|
: a:copy
|
|
c (ap-)
|
|
i puduposw
|
|
i feliad..
|
|
d 1
|
|
i liju....
|
|
r copy
|
|
~~~
|
|
|
|
`a:dup` takes a pointer to an array. It makes a copy of this
|
|
and then returns a pointer to the new one.
|
|
|
|
:a:dup here [ dup a:length comma &comma a:for-each ] dip ;
|
|
|
|
The copy is made at `here`, so using this can generate garbage.
|
|
|
|
~~~
|
|
: a:dup
|
|
c (a-a)
|
|
c get a pointer to `here`, move it out of the way
|
|
i lifepu..
|
|
r Free
|
|
c get the length, comma it to the new array
|
|
i dulica..
|
|
r a:length
|
|
i lica....
|
|
r comma
|
|
c run `comma` against each value, copying them to the
|
|
c new array
|
|
i lilica..
|
|
r comma
|
|
r a:for-each
|
|
c restore the pointer to the new array, and we're done
|
|
i pore....
|
|
~~~
|
|
|
|
`a:th` takes a pointer to an array and an index. It then returns
|
|
the address that the index corresponds to.
|
|
|
|
~~~
|
|
: a:th
|
|
c (an-p)
|
|
i liadadre
|
|
d 1
|
|
~~~
|
|
|
|
`a:fetch` takes a pointer to an array and an index. It then
|
|
returns the value stored in the array at the specified index.
|
|
|
|
~~~
|
|
: a:fetch
|
|
c (an-n)
|
|
i liadadfe
|
|
d 1
|
|
i re......
|
|
~~~
|
|
|
|
`a:store` takes a value, a pointer to an array, and an index.
|
|
It stores the value into the array at the specified index.
|
|
|
|
~~~
|
|
: a:store
|
|
c (nan-)
|
|
i liadadst
|
|
d 1
|
|
i re......
|
|
~~~
|
|
|
|
`a:hash` returns a hash (using a modified djb2 model) for the
|
|
values in an array.
|
|
|
|
This is a very important function as the dictionary is stored
|
|
as hashes of the word names. If you alter this, please be aware
|
|
that you will need to update the dictionary hashes, and likely
|
|
some code in blocks that use hashes as well.
|
|
|
|
The hashes are constrained due to the use of 32-bit signed
|
|
integers. If using a host system with larger cells, you will
|
|
either need to update the dictionary and other places the hashes
|
|
are used, or ensure that your system handles overflow and wrap-
|
|
around as expected by ilo.
|
|
|
|
~~~
|
|
: a:hash
|
|
c (a-n)
|
|
i liswlili
|
|
d 5381
|
|
r a:hash.values
|
|
r a:for-each
|
|
i ju......
|
|
|
|
: a:hash.values
|
|
i swlimuad
|
|
d 33
|
|
i re......
|
|
~~~
|
|
|
|
`a:for-each` takes an array and a pointer to a function. It
|
|
then pushes each value from the array to the stack and runs
|
|
the code indicated by the pointer for each value.
|
|
|
|
Assuming an array of and a pointer to `[ n:put nl ]`:
|
|
|
|
here #3 comma $98 comma $99 comma $100 comma
|
|
[ n:put nl ] a:for-each
|
|
|
|
Is the same as doing:
|
|
|
|
$98 n:put nl
|
|
$99 n:put nl
|
|
$100 n:put nl
|
|
|
|
~~~
|
|
: a:for-each
|
|
c (ap-)
|
|
|
|
c get the length and a pointer to the first data element
|
|
c in the array
|
|
i swlica..
|
|
r fetch-next
|
|
|
|
c reorder the stack. We are now:
|
|
c pointer-to-data pointer-to-function length
|
|
i puswpo..
|
|
|
|
c run the actual loop over the data, then clean up
|
|
i lica....
|
|
r a:for-each.value
|
|
i drdrdrre
|
|
|
|
: a:for-each.value
|
|
c (ppn-ppn)
|
|
c is there remaining data?
|
|
i duline..
|
|
d 0
|
|
i dulieq..
|
|
d 0
|
|
c if not, branch to the exit point
|
|
i licj....
|
|
r a:for-each.value.done
|
|
c if so, move the length and function pointers out of the
|
|
c way and get a pointer to the next data item and the current
|
|
c item
|
|
i dr......
|
|
i pupulica
|
|
r fetch-next
|
|
c reorder the stack to: value ptr-function
|
|
c then call the function
|
|
i swpodupu
|
|
i swpuca..
|
|
c restore the various pointers (data, function) and length,
|
|
c then decrease the length by 1.
|
|
i popopoli
|
|
d 1
|
|
i suliju..
|
|
c and repeat until completed
|
|
r a:for-each.value
|
|
|
|
: a:for-each.value.done
|
|
i drre....
|
|
~~~
|
|
|
|
Use `a:prepend` to merge two arrays into a new one. Takes two
|
|
pointers, with the items from the second one being placed
|
|
before the items in the first.
|
|
|
|
:a:prepend
|
|
here
|
|
&swap dip swap
|
|
&swap dip swap
|
|
dup-pair a:length swap a:length n:add comma
|
|
&comma a:for-each &comma a:for-each ;
|
|
|
|
~~~
|
|
: a:prepend
|
|
c (aa-a)
|
|
i lica....
|
|
r dtc
|
|
- here
|
|
|
|
- lit
|
|
r swap
|
|
- dip
|
|
- swap
|
|
|
|
- lit
|
|
r swap
|
|
- dip
|
|
- swap
|
|
|
|
- dup-pair
|
|
- a:length
|
|
- swap
|
|
- a:length
|
|
- n:add
|
|
- comma
|
|
|
|
- lit
|
|
r comma
|
|
- a:for-each
|
|
- lit
|
|
r comma
|
|
- a:for-each
|
|
d 0
|
|
~~~
|
|
|
|
Use `a:append` to merge two arrays into a new one. Takes two
|
|
pointers, with the items from the first one being placed before
|
|
the items in the second.
|
|
|
|
~~~
|
|
: a:append
|
|
c (aa-a)
|
|
i swliju..
|
|
r a:prepend
|
|
~~~
|
|
|
|
To reverse the order of items in an array, use `a:reverse`. It
|
|
takes a pointer to an array and returns a new array.
|
|
|
|
A quick Forth take on this:
|
|
|
|
:a:reverse (a-a)
|
|
here [
|
|
dup fetch dup comma allot (length+space)
|
|
here n:dec swap [ over store n:dec ] a:for-each drop
|
|
] dip ;
|
|
|
|
~~~
|
|
: a:reverse
|
|
c (a-a)
|
|
i lifepu..
|
|
r Free
|
|
i dufedu..
|
|
i lica....
|
|
r comma
|
|
i lica....
|
|
r allot
|
|
i lifelisu
|
|
r Free
|
|
d 1
|
|
i swlilica
|
|
r a:reverse.values
|
|
r a:for-each
|
|
i drpo....
|
|
i dulica..
|
|
r s:temp
|
|
i swlistre
|
|
r Free
|
|
|
|
: a:reverse.values
|
|
i puduposw
|
|
i stlisure
|
|
d 1
|
|
~~~
|
|
|
|
`allot` reserves memory. Provide it a number and it'll reserve
|
|
that amount of memory. You can reclaim memory by passing in a
|
|
negative value.
|
|
|
|
Note that memory is allocated at `here`, in a linear fashion.
|
|
Expect issues if you reclaim memory after something else also
|
|
allocates memory.
|
|
|
|
:allot @Free n:add !Free ;
|
|
|
|
~~~
|
|
: allot
|
|
c (n-)
|
|
i lifead..
|
|
r Free
|
|
i listre..
|
|
r Free
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
# The main entry point
|
|
|
|
On startup this tries to find a word named `startup`. If found,
|
|
it then runs it. After this is complete, it drops into listen,
|
|
which runs the interactive listener (REPL).
|
|
|
|
~~~
|
|
: forth
|
|
c look for a word named `startup`
|
|
i lilica..
|
|
r $startup
|
|
r d:lookup
|
|
|
|
c if it was found, run it
|
|
i dulieq..
|
|
d 0
|
|
i lililica
|
|
r drop
|
|
r run-startup
|
|
r choose
|
|
|
|
c after checking for (and running if found) the latest `startup`
|
|
c word, we enter the listener loop.
|
|
|
|
: listen
|
|
c (-)
|
|
i lica....
|
|
r s:get/token
|
|
i lica....
|
|
r interpret
|
|
i liju....
|
|
r listen
|
|
~~~
|
|
|
|
~~~
|
|
: run-startup
|
|
i liadfeju
|
|
d 2
|
|
|
|
: $startup
|
|
s startup
|
|
~~~
|
|
|
|
get length
|
|
start address, dest address, length
|
|
copy
|
|
get length
|
|
decrement
|
|
store new length
|
|
|
|
~~~
|
|
: remove-sigil
|
|
c sigils receive a string with the input token sans the sigil.
|
|
c This removes the sigil by copying the rest of the string over
|
|
c the original data in the TIB and decrementing the count.
|
|
i lililicy
|
|
r sys:buffers/input/text+1
|
|
r sys:buffers/input/text
|
|
d 127
|
|
i liliju..
|
|
r sys:buffers/input
|
|
r v:dec
|
|
~~~
|
|
|
|
The `interpret` word takes a token and processes it.
|
|
|
|
If the token starts with a defined sigil, the sigil is removed
|
|
and the remainder of the token is passed to the sigil function
|
|
to deal with.
|
|
|
|
If it does not, the dictionary is searched for the word name.
|
|
|
|
Assuming it's found, the word type (normal or immediate) and
|
|
address are obtained, and the word is either called or compiled
|
|
into a definition as needed.
|
|
|
|
If the token was not found in the dictionary, a warning is
|
|
reported.
|
|
|
|
~~~
|
|
: interpret
|
|
c (s-)
|
|
|
|
c copy token to tib
|
|
i lilicyli
|
|
r sys:buffers/input
|
|
d 129
|
|
r sys:buffers/input
|
|
|
|
c check the first character in a string to see if a sigil has
|
|
c been defined for it. Returns a flag normalized to true (-1)
|
|
c or false (0)
|
|
i duliadfe
|
|
d 1
|
|
i lica....
|
|
r sigil:get
|
|
i line....
|
|
d 0
|
|
|
|
c then dispatch to appropriate handler
|
|
i lililica
|
|
r with-sigil
|
|
r without-sigil
|
|
r choose
|
|
i re......
|
|
~~~
|
|
|
|
~~~
|
|
: with-sigil
|
|
i lifelica
|
|
r sys:buffers/input/text
|
|
r remove-sigil
|
|
i lica....
|
|
r sigil:get
|
|
i ju......
|
|
~~~
|
|
|
|
~~~
|
|
: without-sigil
|
|
c see if the word is in the dictionary
|
|
i lica....
|
|
r d:lookup
|
|
i dulieq..
|
|
d 0
|
|
c if not found, branch to not-found
|
|
i licj....
|
|
r not-found
|
|
c if found, get the address & flags from the word
|
|
i duliadfe
|
|
d 2
|
|
i swliadfe
|
|
d 3
|
|
c then, based on the flags, either run it (immediate) or pass
|
|
c the address to handle-word for further processing
|
|
i lililiju
|
|
r handle-immediate
|
|
r handle-word
|
|
r choose
|
|
|
|
: not-found
|
|
i drlilica
|
|
r sys:buffers/input
|
|
r a:length
|
|
i lieqlicj
|
|
d 0
|
|
r empty-token
|
|
i lilica..
|
|
r $not-found
|
|
r s:put
|
|
i lilica..
|
|
r sys:buffers/input
|
|
r s:put
|
|
i lica....
|
|
r nl
|
|
: empty-token
|
|
i re......
|
|
|
|
: $not-found
|
|
s word not found:
|
|
c note: the above string ends with a space character. This may
|
|
c be hidden by your editor.
|
|
~~~
|
|
|
|
~~~
|
|
: handle-word
|
|
c (p-)
|
|
c if compiling (`Compiler` set to -1), comma the address into
|
|
c the current definition. If not, call the word by falling
|
|
c through into handle-immediate.
|
|
i lifelicj
|
|
r Compiler
|
|
r comma
|
|
|
|
: handle-immediate
|
|
c immediate words are always called.
|
|
i ju......
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
String evaluation. I am doing this by remapping `c:get` to read
|
|
input from a string instead of the keyboard. This is done to
|
|
ensure that evaluation and direct entry are treated the same.
|
|
|
|
~~~
|
|
: Source
|
|
d 0
|
|
|
|
: End
|
|
d 0
|
|
|
|
: At
|
|
d 0
|
|
~~~
|
|
|
|
If at the end of the input buffer, reset `c:get` to the default,
|
|
then return ASCII 32 (space).
|
|
|
|
~~~
|
|
: get/last
|
|
c (-c)
|
|
i lilist..
|
|
d 0
|
|
r c:get
|
|
i lililiad
|
|
d 0
|
|
r c:get
|
|
d 1
|
|
i stlire..
|
|
d 32
|
|
~~~
|
|
|
|
When not at the end, return the current character, and advance
|
|
the At pointer.
|
|
|
|
~~~
|
|
: get/next
|
|
c (-c)
|
|
i lifelife
|
|
r Source
|
|
r At
|
|
i lica....
|
|
r a:fetch
|
|
i liliju..
|
|
r At
|
|
r v:inc
|
|
~~~
|
|
|
|
A top level "get" decides which of the prior functions
|
|
(get/last and get/next) to use.
|
|
|
|
~~~
|
|
: get
|
|
c (-c)
|
|
i lifelife
|
|
r At
|
|
r End
|
|
i eqlili..
|
|
r get/last
|
|
r get/next
|
|
i liju....
|
|
r choose
|
|
~~~
|
|
|
|
The "process" function takes in a token and runs `interpret` on
|
|
it if the token is not empty.
|
|
|
|
~~~
|
|
: process
|
|
c (s-)
|
|
c check for non-empty token
|
|
|
|
c (s-sf)
|
|
i dulica..
|
|
r a:length
|
|
i line....
|
|
d 0
|
|
|
|
c process token if not empty
|
|
i lililiju
|
|
r interpret
|
|
r drop
|
|
r choose
|
|
~~~
|
|
|
|
~~~
|
|
: s:evaluate
|
|
c (s-)
|
|
|
|
c patch `c:get` to jump to "get" instead of reading keyboard
|
|
i lilist..
|
|
d 1793
|
|
r c:get
|
|
i lililiad
|
|
r get
|
|
r c:get
|
|
d 1
|
|
i st......
|
|
|
|
c obtain length of string to evaluate; set Source, End, and
|
|
c At
|
|
i dulica..
|
|
r a:length
|
|
i listlist
|
|
r End
|
|
r Source
|
|
i lilist..
|
|
d 0
|
|
r At
|
|
|
|
c begin the actual evaluation loop. Read in a token, then
|
|
c "process" it.
|
|
: s:evaluate/loop
|
|
i lica....
|
|
r s:get/token
|
|
i lica....
|
|
r process
|
|
|
|
c Check to see if we have reached the End of the string. If
|
|
c so, branch to the exit point. Otherwise, repeat the loop.
|
|
i lifelife
|
|
r At
|
|
r End
|
|
i eqlicj..
|
|
r s:evaluate/done
|
|
i liju....
|
|
r s:evaluate/loop
|
|
|
|
c This is the exit point.
|
|
: s:evaluate/done
|
|
i re......
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
Convert a string to a number with `s:to-n`.
|
|
|
|
The process used here is to fetch each character, determine the
|
|
value it represents, adding it to a placeholder on the stack.
|
|
The placeholder is then multiplied by ten before the next cycle.
|
|
Some care is taken to not do the multiplication if it would
|
|
cause the value to overflow. A sign value is factored in as
|
|
well. The `-` character changes the sign.
|
|
|
|
In higher level Forth:
|
|
|
|
'Length var
|
|
'Currently var
|
|
|
|
:scale &Length fetch &Currently fetch -eq?
|
|
[ #10 n:mul &Currently v:inc ] if ;
|
|
|
|
:s:to-n (s-n)
|
|
dup a:length &Length store #1 &Currently store
|
|
#1 swap #0 swap
|
|
[ dup #45 eq? [ drop swap n:negate swap ]
|
|
[ #48 n:sub n:add scale ] choose ] a:for-each
|
|
n:mul ;
|
|
|
|
'2147483647 s:to-n bye
|
|
|
|
~~~
|
|
: s:Length
|
|
d 0
|
|
|
|
: s:Current
|
|
d 0
|
|
|
|
: s:to-n.scale
|
|
c this will adjust the current sum to get it ready for adding
|
|
c the next value. Basically, just multiplying by the base (10).
|
|
|
|
c The scaling is only done if we aren't at the end of the input.
|
|
c This is done to avoid an overflow issue with large numbers.
|
|
i lifelife
|
|
r s:Length
|
|
r s:Current
|
|
i gtliliju
|
|
r s:to-n.adjust
|
|
r if
|
|
|
|
: s:to-n.adjust
|
|
i limulili
|
|
d 10
|
|
r s:Current
|
|
r v:inc
|
|
i ju......
|
|
|
|
: s:to-n
|
|
c (s-n)
|
|
i dufelist
|
|
r s:Length
|
|
i lilist..
|
|
d 1
|
|
r s:Current
|
|
i liswlisw
|
|
d 1
|
|
d 0
|
|
i lilica..
|
|
r s:to-n.digit
|
|
r a:for-each
|
|
i mure....
|
|
|
|
: s:to-n.digit
|
|
c check to see if the character is a "-" (indicating a negative
|
|
c value). If so, branch to the negative handler. Otherwise,
|
|
c branch to the general digit handler.
|
|
i dulieqli
|
|
d 45
|
|
r s:to-n.handle-negative
|
|
i liliju..
|
|
r s:to-n.process-digit
|
|
r choose
|
|
|
|
: s:to-n.handle-negative
|
|
c for negative values, we multiply the value by -1 and then
|
|
c return to process the next character.
|
|
i drswlimu
|
|
d -1
|
|
i swliliju
|
|
r s:Current
|
|
r v:inc
|
|
|
|
|
|
: s:to-n.process-digit
|
|
c processing digits is modestly involved. I start by checking
|
|
c for a few characters I want to ignore: . and ,
|
|
|
|
c ignore "."
|
|
i dulieq..
|
|
d 46
|
|
i licj....
|
|
r s:to-n.ignore
|
|
|
|
c ignore ","
|
|
i dulieq..
|
|
d 44
|
|
i licj....
|
|
r s:to-n.ignore
|
|
|
|
c if not an ignored character, process as digit. Convert the
|
|
c ASCII value to a simple decimal, add it to the running sum,
|
|
c and then setup for the next character.
|
|
i lisuad..
|
|
d 48
|
|
i liju....
|
|
r s:to-n.scale
|
|
|
|
: s:to-n.ignore
|
|
c handles ignored characters
|
|
i drliliju
|
|
r s:Current
|
|
r v:inc
|
|
~~~
|
|
|
|
The inverse is `n:to-s`, which converts a number into a string.
|
|
|
|
~~~
|
|
: n:to-s
|
|
c (n-s)
|
|
|
|
c get the current `here` and move it out of the way.
|
|
i lifepu..
|
|
r Free
|
|
|
|
c assign `Free` to the numeric conversion buffer.
|
|
i lilist..
|
|
r sys:buffers/numeric-conversion
|
|
r Free
|
|
|
|
c store an initial count (0)
|
|
i lilica..
|
|
d 0
|
|
r comma
|
|
|
|
c make a copy of the number, but ensure the copy is positive
|
|
i dulica..
|
|
r n:abs
|
|
|
|
c with these done, we are ready for the main loop. This will
|
|
c iterate over the number, dividing by the base, converting to
|
|
c ASCII, and then adding it to the string representation. We
|
|
c are at the end when the remainder is zero.
|
|
|
|
: n:to-s.internal
|
|
i lica....
|
|
r convert
|
|
i lica....
|
|
r record
|
|
i duline..
|
|
d 0
|
|
i licj....
|
|
r n:to-s.internal
|
|
|
|
c at this point there are no remaining values. Clean up the
|
|
c stack and check to see if the original value was negative.
|
|
c if so, call "add-negative-sign"
|
|
i drlilt..
|
|
d 0
|
|
i licc....
|
|
r add-negative-sign
|
|
|
|
c we now have a complete string, but it's in reversed order.
|
|
c This is corrected using `a:reverse`.
|
|
i lilica..
|
|
r sys:buffers/numeric-conversion
|
|
r a:reverse
|
|
|
|
c the last step is to reset `Free` to the saved value from the
|
|
c start.
|
|
i polistre
|
|
r Free
|
|
|
|
: add-negative-sign
|
|
i liliju..
|
|
d 45
|
|
r record
|
|
|
|
: convert
|
|
c divide the value by the base (hard coded to 10) to get a
|
|
c positional value
|
|
i lidisw..
|
|
d 10
|
|
c convert the positional value to an ASCII character
|
|
i liswliju
|
|
r $valid-digits
|
|
r a:fetch
|
|
|
|
: record
|
|
c add the ASCII character for the digit to the string and
|
|
c increment the length
|
|
i lica....
|
|
r comma
|
|
i liliju..
|
|
r sys:buffers/numeric-conversion
|
|
r v:inc
|
|
|
|
: $valid-digits
|
|
s 0123456789
|
|
~~~
|
|
|
|
================================================================
|
|
|
|
`v:inc` increments the value stored in a variable.
|
|
|
|
~~~
|
|
: v:inc
|
|
c (p-)
|
|
i dufeliad
|
|
d 1
|
|
i swstre..
|
|
~~~
|
|
|
|
`v:dec` decrements the value stored in a variable.
|
|
|
|
~~~
|
|
: v:dec
|
|
c (p-)
|
|
i dufelisu
|
|
d 1
|
|
i swstre..
|
|
~~~
|
|
|
|
`n:abs` returns the absolute value of a number.
|
|
|
|
~~~
|
|
: n:abs
|
|
c (n-n)
|
|
i duliltli
|
|
d 0
|
|
r n:negate
|
|
i liju....
|
|
r if
|
|
~~~
|
|
|
|
Use `n:negate` to invert the sign of a number.
|
|
|
|
~~~
|
|
: n:negate
|
|
c (n-n)
|
|
i limure..
|
|
d -1
|
|
~~~
|
|
|
|
`nl` displays a newline.
|
|
|
|
~~~
|
|
: nl
|
|
i liliju..
|
|
d 10
|
|
r c:put
|
|
~~~
|
|
|
|
`sp` displays a space.
|
|
|
|
~~~
|
|
: sp
|
|
i liliju..
|
|
d 32
|
|
r c:put
|
|
~~~
|
|
|
|
`tab` displays a tab.
|
|
|
|
~~~
|
|
: tab
|
|
i liliju..
|
|
d 9
|
|
r c:put
|
|
~~~
|
|
|
|
# Input
|
|
|
|
~~~
|
|
: c:get
|
|
c (-c)
|
|
c Leave two cells filled with nop's. This allows the definition
|
|
c to be temporarily replaced later. (Used by `s:evaluate`)
|
|
i ........
|
|
i ........
|
|
i liiore..
|
|
d 1
|
|
~~~
|
|
|
|
`s:get/token` reads a whitespace delimited token into the text
|
|
input buffer (tib).
|
|
|
|
If you use this and need to keep the token around, pass it to
|
|
`s:temp`.
|
|
|
|
~~~
|
|
: s:get/token
|
|
c (-s)
|
|
c clear tib (by seting length to zero)
|
|
i lilist..
|
|
d 0
|
|
r sys:buffers/input
|
|
|
|
c run an input loop
|
|
i lica....
|
|
r s:get.loop
|
|
c clean up the stack and push the TIB address before returning
|
|
i drlire..
|
|
r sys:buffers/input
|
|
|
|
: s:get.loop
|
|
c the actual input handling occurs here
|
|
i lica....
|
|
r c:get
|
|
|
|
c check to see if the character is an end of line/token (ASCII
|
|
c 10 [line feed] or 32 [space])
|
|
i dudulieq
|
|
d 10
|
|
i swlieqor
|
|
d 32
|
|
|
|
c if we are at end of input, branch to the end
|
|
i lixoduli
|
|
d -1
|
|
d 0
|
|
i eqlicj..
|
|
r s:get.loop.done
|
|
|
|
c if not, check to see if the user entered a backspace (ASCII 8
|
|
c or 127). If so, branch to a backspace handler.
|
|
i drdulieq
|
|
d 8
|
|
i licj....
|
|
r s:get.backspace
|
|
i dulieq..
|
|
d 127
|
|
i licj....
|
|
r s:get.backspace
|
|
|
|
c if not end of token or backspace, we add it to the TIB using
|
|
c a helper function, then repeat the loop.
|
|
i lica....
|
|
r add-to-tib
|
|
i liju....
|
|
r s:get.loop
|
|
|
|
c this is the exit point of the entry loop
|
|
: s:get.loop.done
|
|
i drre....
|
|
|
|
: s:get.backspace
|
|
c this handles backspacing. Note that this does not prevent
|
|
c writing to memory before the start of the TIB.
|
|
i dr......
|
|
i lifelisu
|
|
r sys:buffers/input
|
|
d 1
|
|
i listliju
|
|
r sys:buffers/input
|
|
r s:get.loop
|
|
|
|
: add-to-tib
|
|
c this adds a character to the TIB and increases the length
|
|
c counter.
|
|
i lidufead
|
|
r sys:buffers/input
|
|
i liadstli
|
|
d 1
|
|
r sys:buffers/input
|
|
i liju....
|
|
r v:inc
|
|
~~~
|
|
|
|
# Output
|
|
|
|
~~~
|
|
: c:put
|
|
c (c-)
|
|
c Leave two cells filled with nop's. This allows the definition
|
|
c to be temporarily replaced later.
|
|
i ........
|
|
i ........
|
|
i liiore..
|
|
d 0
|
|
~~~
|
|
|
|
Display a string.
|
|
|
|
~~~
|
|
: s:put
|
|
c (s-)
|
|
i liliju..
|
|
r c:put
|
|
r a:for-each
|
|
~~~
|
|
|
|
Display a number
|
|
|
|
~~~
|
|
: n:put
|
|
i lica....
|
|
r n:to-s
|
|
i liju....
|
|
r s:put
|
|
~~~
|
|
|
|
# Setup the System
|
|
|
|
# Sigils
|
|
|
|
~~~
|
|
: sigil:set
|
|
c (ac-)
|
|
i liadstre
|
|
r Sigils
|
|
|
|
: sigil:get
|
|
c (c-p)
|
|
i liadfere
|
|
r Sigils
|
|
~~~
|
|
|
|
`setup:sigils` populates the Sigils table with the default
|
|
implementations. This is called as part of the startup process.
|
|
|
|
~~~
|
|
: setup:sigils
|
|
c (-)
|
|
|
|
c # for numbers
|
|
i lililica
|
|
r sigil:#
|
|
d 35
|
|
r sigil:set
|
|
|
|
c : for colon definitions
|
|
i lililica
|
|
r sigil::
|
|
d 58
|
|
r sigil:set
|
|
|
|
c & for pointers
|
|
i lililica
|
|
r sigil:&
|
|
d 38
|
|
r sigil:set
|
|
|
|
c ' for strings
|
|
i lililica
|
|
r sigil:'
|
|
d 39
|
|
r sigil:set
|
|
|
|
i re......
|
|
~~~
|
|
|
|
# Stubs
|
|
|
|
~~~
|
|
: process-data
|
|
i lica....
|
|
r compiling?
|
|
i liliju..
|
|
r process-data.compile
|
|
r if
|
|
|
|
: process-data.compile
|
|
i lilica..
|
|
r lit
|
|
r comma
|
|
i liju....
|
|
r comma
|
|
~~~
|
|
|
|
New definitions start with a `:` sigil. This sigil creates a new
|
|
dictionary header (via `d:create`), then runs `compiler:dtc` to
|
|
setup a call to the `dtc` word, and finally turns the `Compiler`
|
|
on.
|
|
|
|
~~~
|
|
: sigil::
|
|
c (s-)
|
|
i lica....
|
|
r dtc
|
|
- d:create
|
|
- compiler:dtc
|
|
- compiler:on
|
|
d 0
|
|
~~~
|
|
|
|
Numbers are provided via the `#` sigil. This is done by
|
|
converting the token to a number, then calling `process-data`
|
|
to compile it into a definition if necessary.
|
|
|
|
~~~
|
|
: sigil:#
|
|
c (s-n)
|
|
i lica....
|
|
r s:to-n
|
|
i liju....
|
|
r process-data
|
|
~~~
|
|
|
|
Pointers are provided by the `&` sigil. This will lookup an
|
|
entry in the dictionary. It fetches the `d:address` field, then
|
|
uses `process-data` to compile it into a definition if
|
|
necessary.
|
|
|
|
~~~
|
|
: sigil:&
|
|
c (s-p)
|
|
i lica....
|
|
r d:lookup
|
|
i liadfe..
|
|
d 2
|
|
i liju....
|
|
r process-data
|
|
~~~
|
|
|
|
The `'` sigil processes strings. At compile time, it passes the
|
|
string to `s:keep`. At interpret time it passes it to `s:temp`
|
|
instead. Prior to this, it passes the string to `s:rewrite`
|
|
for preliminary processing (if defined).
|
|
|
|
~~~
|
|
: sigil:'
|
|
c (s-s)
|
|
|
|
c check to see if `s:rewrite` is in the dictionary
|
|
i lilica..
|
|
r $s:rewrite
|
|
r d:lookup
|
|
|
|
c if it is, call it to rewrite the string
|
|
i duline..
|
|
d 0
|
|
i lililica
|
|
r run.s:rewrite
|
|
r drop
|
|
r choose
|
|
|
|
c check to see if we are compiling or at the listener. Call
|
|
c either `s:keep` or `s:temp` based on the compiler status.
|
|
i lica....
|
|
r compiling?
|
|
i lililiju
|
|
r s:keep
|
|
r s:temp
|
|
r choose
|
|
|
|
c a string with the `s:rewrite` name.
|
|
: $s:rewrite
|
|
s s:rewrite
|
|
|
|
: run.s:rewrite
|
|
c this is a helper used to run `s:rewrite` if present.
|
|
i liadfeju
|
|
d 2
|
|
~~~
|
|
|
|
# Compiler
|
|
|
|
Compilation is mostly easy. To compile code, we needs to create
|
|
a new dictionary header with `d:create`, set a variable that
|
|
indicates we wish to compile to true (`Compiler`), lay down a
|
|
call to `dtc`, then just let `interpret` and the sigils handle
|
|
the actual details of laying down the addresses.
|
|
|
|
~~~
|
|
: Compiler
|
|
d 0
|
|
~~~
|
|
|
|
I define helper functions to enable and disable the `Compiler`.
|
|
|
|
~~~
|
|
: compiler:on
|
|
c (-)
|
|
i lilistre
|
|
d -1
|
|
r Compiler
|
|
~~~
|
|
|
|
~~~
|
|
: compiler:off
|
|
c (-)
|
|
i lilistre
|
|
d 0
|
|
r Compiler
|
|
~~~
|
|
|
|
And a `compiling?` to return the value of `Compiler`. This one
|
|
is just a readability aid.
|
|
|
|
~~~
|
|
: compiling?
|
|
c (-f)
|
|
i lifere..
|
|
r Compiler
|
|
~~~
|
|
|
|
The compiler needs to be able to store data in memory. This is
|
|
done with `comma` (called "," in most Forths). To implement
|
|
this I need a variable to track the next free memory address.
|
|
This variable is named `Free`. I also define a word (`here`) to
|
|
return the value of `Free`.
|
|
|
|
~~~
|
|
: Free
|
|
r FREE-SPACE
|
|
|
|
: here
|
|
c (-p)
|
|
i lifere..
|
|
r Free
|
|
~~~
|
|
|
|
With these, `comma` is easy. Fetch `Free`, use `store-next` to
|
|
write the value, then update `Free` with the next address.
|
|
|
|
~~~
|
|
: comma
|
|
c (n-)
|
|
i lifelica
|
|
r Free
|
|
r store-next
|
|
i listre..
|
|
r Free
|
|
~~~
|
|
|
|
Ending a word is done with `;`. This is one of a small number of
|
|
immediate words.
|
|
|
|
A DTC sequence ends with a NULL (0) value, so this uses `comma`
|
|
to write this value, then `compiler:off` to end compilation.
|
|
|
|
~~~
|
|
: ;
|
|
c (-)
|
|
i lilica..
|
|
d 0
|
|
r comma
|
|
i liju....
|
|
r compiler:off
|
|
~~~
|
|
|
|
`compiler:dtc` lays down a call to the `dtc` word at the start
|
|
of a compiled word.
|
|
|
|
~~~
|
|
: compiler:dtc
|
|
c (-)
|
|
c compile a "lica...." bundle
|
|
i lilica..
|
|
d 2049
|
|
r comma
|
|
c then a reference to `dtc`
|
|
i liliju..
|
|
r dtc
|
|
r comma
|
|
~~~
|
|
|
|
:d:create
|
|
here swap @Latest comma
|
|
a:hash comma
|
|
dup #4 n:add comma
|
|
#0 comma
|
|
!Latest ;
|
|
|
|
`d:create` makes a new dictionary header.
|
|
|
|
~~~
|
|
: d:create
|
|
c (s-)
|
|
|
|
c get a pointer to `here`, and a pointer to the previous
|
|
c entry. Store the pointer to the previous entry.
|
|
i lifeswli
|
|
r Free
|
|
r Latest
|
|
i felica..
|
|
r comma
|
|
|
|
c calculate the hash of the word name, store it
|
|
i lica....
|
|
r a:hash
|
|
i lica....
|
|
r comma
|
|
|
|
c calculate the starting address of the word and store it
|
|
i duliad..
|
|
d 4
|
|
i lica....
|
|
r comma
|
|
|
|
c store the default flags (0)
|
|
i lilica..
|
|
d 0
|
|
r comma
|
|
|
|
c finally, patch Latest (`Dictionary`) to point to the start
|
|
c of this entry.
|
|
i listre..
|
|
r Latest
|
|
~~~
|
|
|
|
# Quotations
|
|
|
|
Quotations are anonymous code blocks. They are basically words
|
|
with no names attached. Konilo uses them for a lot: loops,
|
|
conditionals, some stack flow, etc.
|
|
|
|
~~~
|
|
: [
|
|
c (-a)
|
|
i lica....
|
|
r dtc
|
|
c get the current Compiler state
|
|
- compiling?
|
|
c begin compiling
|
|
- compiler:on
|
|
c lay down a call to `internal:quote`
|
|
- lit
|
|
r quote
|
|
- comma
|
|
c get a pointer to `here` for later use
|
|
- here
|
|
- tuck
|
|
c store a dummy value for the length. This will be patched
|
|
c by `]`.
|
|
- lit
|
|
d 0
|
|
- comma
|
|
c use `compiler:dtc` to lay down code tostart a DTC code
|
|
c sequence
|
|
- compiler:dtc
|
|
d 0
|
|
|
|
|
|
: ]
|
|
c (a-a)
|
|
i lica....
|
|
r dtc
|
|
c lay down a 0 to end the quote definition
|
|
- lit
|
|
d 0
|
|
- comma
|
|
c determine the length of the quotation
|
|
- here
|
|
- over
|
|
- n:sub
|
|
- n:dec
|
|
c update the dummy length with the actual length
|
|
- swap
|
|
- store
|
|
c reset the compiler state to whatever it was prior
|
|
c to invoking `[`
|
|
- lit
|
|
r Compiler
|
|
- store
|
|
c if the compiler is still on, we are nested in a
|
|
c defintion. In this case, we can just drop the value
|
|
c on the stack.
|
|
c
|
|
c if at the interpreter, we can just increment the
|
|
c value on the stack to get a pointer to the actual
|
|
c code. This gets left on the stack.
|
|
- compiling?
|
|
- lit
|
|
r drop
|
|
- lit
|
|
r interpreting-quote
|
|
- choose
|
|
d 0
|
|
|
|
: interpreting-quote
|
|
i liju....
|
|
r n:inc
|
|
~~~
|
|
|
|
# Direct Threading
|
|
|
|
Most words in Konilo are direct threaded. This means that
|
|
they consist of a series of addresses, ending in a NULL (0).
|
|
A single word, `dtc`, walks through these lists, calling each
|
|
address in order.
|
|
|
|
The in memory representation of a word like:
|
|
|
|
:foo #1 #2 n:add ;
|
|
|
|
Would look like:
|
|
|
|
0000 i lica....
|
|
0001 r dtc
|
|
0002 - internal:lit
|
|
0003 d 1
|
|
0002 - internal:lit
|
|
0003 d 2
|
|
0004 - n:add
|
|
0005 d 0
|
|
|
|
The `internal:lit` pushes the value in the following cell to the
|
|
data stack and advances the instruction pointer.
|
|
|
|
DTC is slower than subroutine threading as it has an additional
|
|
layer of processing before each call. But it can be more dense,
|
|
and is easily disassembled/decompiled if you have name data.
|
|
|
|
~~~
|
|
: dtc
|
|
c (-)
|
|
c get the starting address of the DTC sequence. This will be
|
|
c the cell following the call to `dtc`.
|
|
i poliad..
|
|
d 1
|
|
c then walk through the list of addresses
|
|
i lica....
|
|
r dtc:walk
|
|
c when done, clean up and return to the parent of the caller
|
|
i drre....
|
|
|
|
: dtc:walk
|
|
c get the function address (and a pointer to the next address)
|
|
i lica....
|
|
r fetch-next
|
|
c check to see if we are at the end (function pointer set to
|
|
c zero). If so, exit the loop.
|
|
i dulieqli
|
|
d 0
|
|
r dtc:done
|
|
i cj......
|
|
c if not, move the pointer to the next function address out of
|
|
c the way and call the function
|
|
i swpuca..
|
|
c then restore the address of the next function and repeat the
|
|
c loop
|
|
i poliju..
|
|
r dtc:walk
|
|
|
|
: dtc:done
|
|
i drre....
|
|
~~~
|
|
|
|
This is exposed to the Forth dictionary as `internal:lit`. (We
|
|
had issues with mistyping `lit` and `list`, so moved both this
|
|
and the following "quote" under an `internal:` namespace)
|
|
|
|
~~~
|
|
: lit
|
|
c (-n)
|
|
i popolica
|
|
r fetch-next
|
|
i swpuswpu
|
|
i re......
|
|
~~~
|
|
|
|
This is exposed to the Forth dictionary as `internal:quote`.
|
|
|
|
~~~
|
|
: quote
|
|
c (-p)
|
|
i popolica
|
|
r fetch-next
|
|
i swdupuad
|
|
i poswpusw
|
|
i pure....
|
|
~~~
|
|
|
|
# Numbers
|
|
|
|
`n:inc` and `n:dec` increment and decrement numbers.
|
|
|
|
~~~
|
|
: n:inc
|
|
c (n-n)
|
|
i liadre..
|
|
d 1
|
|
|
|
: n:dec
|
|
c (n-n)
|
|
i lisure..
|
|
d 1
|
|
~~~
|
|
|
|
# Strings
|
|
|
|
In Konilo, strings are arrays containing character data.
|
|
|
|
I provide a rotating buffer of space for temporary strings (and
|
|
general arrays). In a default configuation this has space for 8
|
|
items.
|
|
|
|
It starts with a variable to track the next slot.
|
|
|
|
~~~
|
|
: Next-String
|
|
d 0
|
|
~~~
|
|
|
|
I use this along with the buffer address (the rather long
|
|
`sys:buffers/strings+arrays`) to calculate an address for
|
|
a temporary string.
|
|
|
|
Note here that strings/arrays are (somewhat) limited to 127
|
|
values. Longer ones can be made and used with `s:keep` or
|
|
moved to another buffer. A longer string/array will have some
|
|
degree of data corruption with later entries.
|
|
|
|
~~~
|
|
: s:temp-pointer
|
|
c (-p)
|
|
i lifelimu
|
|
r Next-String
|
|
d 128
|
|
i liadre..
|
|
r sys:buffers/strings+arrays
|
|
~~~
|
|
|
|
The next word updates the "Next-String" variable, constraining
|
|
it to a max of 8 entries.
|
|
|
|
~~~
|
|
: s:next
|
|
c (-)
|
|
i lica....
|
|
r dtc
|
|
c increment Next-String
|
|
- lit
|
|
r Next-String
|
|
- v:inc
|
|
c compare Next-String to 8
|
|
- lit
|
|
d 8
|
|
- lit
|
|
r Next-String
|
|
- fetch
|
|
- eq?
|
|
c if 8, use "s:adjust" to reset the counter
|
|
- lit
|
|
r s:adjust
|
|
- if
|
|
d 0
|
|
~~~
|
|
|
|
`s:adjust` resets the "Next-String" variable to zero. It is
|
|
called when by "s:next" when it reaches the end of the string
|
|
space.
|
|
|
|
~~~
|
|
: s:adjust
|
|
c (-)
|
|
i lilistre
|
|
d 0
|
|
r Next-String
|
|
~~~
|
|
|
|
`s:temp` moves a string into the temporary buffer pool.
|
|
|
|
~~~
|
|
: s:temp
|
|
c (s-s)
|
|
i lica....
|
|
r dtc
|
|
- s:temp-pointer
|
|
- tuck
|
|
- a:copy
|
|
- s:next
|
|
d 0
|
|
~~~
|
|
|
|
:data:string pop pop dup fetch-next n:add push swap push ;
|
|
|
|
~~~
|
|
: data:string
|
|
c (-s)
|
|
i popodu..
|
|
i lica....
|
|
r fetch-next
|
|
i lica....
|
|
r n:add
|
|
i puswpure
|
|
~~~
|
|
|
|
`s:keep` inlines a string to memory at `here`. It'll also add
|
|
code to skip over the string (in case of compilation being
|
|
active).
|
|
|
|
~~~
|
|
: s:keep
|
|
c (s-s)
|
|
i lica....
|
|
r dtc
|
|
|
|
c compile a call to `data:string`
|
|
- lit
|
|
- data:string
|
|
- comma
|
|
|
|
c get a reference to here, tuck it out of the way
|
|
- here
|
|
- swap
|
|
|
|
c get the string length, store it `here`
|
|
- dup
|
|
- a:length
|
|
- comma
|
|
|
|
c then use `a:for-each` to copy the characters to `here`
|
|
- lit
|
|
r comma
|
|
- a:for-each
|
|
|
|
c finally, if compiling, drop the pointer from the stack. (If
|
|
c not compiling, we just leave it on the stack)
|
|
- compiling?
|
|
- lit
|
|
r drop
|
|
- if
|
|
d 0
|
|
~~~
|
|
|
|
`s:append` is similar to `a:append`, but specifically uses the
|
|
temporary buffer memory.
|
|
|
|
~~~
|
|
: s:append
|
|
c (ss-s)
|
|
i lica....
|
|
r dtc
|
|
- here
|
|
- push
|
|
- s:temp-pointer
|
|
- lit
|
|
r Free
|
|
- store
|
|
- s:next
|
|
- a:append
|
|
- pop
|
|
- lit
|
|
r Free
|
|
- store
|
|
d 0
|
|
|
|
: s:prepend
|
|
c (ss-)
|
|
i swliju..
|
|
r s:append
|
|
~~~
|
|
================================================================
|
|
EOF
|
|
~~~
|
|
: ENTRY.0
|
|
d 0
|
|
d 2088204551
|
|
r neq?
|
|
d 0
|
|
: ENTRY.1
|
|
r ENTRY.0
|
|
d 193429569
|
|
r -if
|
|
d 0
|
|
: ENTRY.2
|
|
r ENTRY.1
|
|
d 177632
|
|
r ;
|
|
d -1
|
|
: ENTRY.3
|
|
r ENTRY.2
|
|
d 212805696
|
|
r ?jump
|
|
d 0
|
|
: ENTRY.4
|
|
r ENTRY.3
|
|
d 67966955
|
|
r BaseBlock
|
|
d 0
|
|
: ENTRY.5
|
|
r ENTRY.4
|
|
d -1210660288
|
|
r Compiler
|
|
d 0
|
|
: ENTRY.6
|
|
r ENTRY.5
|
|
d 1264838491
|
|
r Latest
|
|
d 0
|
|
: ENTRY.7
|
|
r ENTRY.6
|
|
d 2089116775
|
|
r Free
|
|
d 0
|
|
: ENTRY.8
|
|
r ENTRY.7
|
|
d -786332176
|
|
r Sigils
|
|
d 0
|
|
: ENTRY.9
|
|
r ENTRY.8
|
|
d 177664
|
|
r [
|
|
d -1
|
|
: ENTRY.10
|
|
r ENTRY.9
|
|
d 177666
|
|
r ]
|
|
d -1
|
|
: ENTRY.11
|
|
r ENTRY.10
|
|
d 1539635992
|
|
r a:append
|
|
d 0
|
|
: ENTRY.12
|
|
r ENTRY.11
|
|
d -294312037
|
|
r a:copy
|
|
d 0
|
|
: ENTRY.13
|
|
r ENTRY.12
|
|
d 251383785
|
|
r a:dup
|
|
d 0
|
|
: ENTRY.14
|
|
r ENTRY.13
|
|
d -1119160502
|
|
r a:fetch
|
|
d 0
|
|
: ENTRY.15
|
|
r ENTRY.14
|
|
d -1309732155
|
|
r a:for-each
|
|
d 0
|
|
: ENTRY.16
|
|
r ENTRY.15
|
|
d -294147516
|
|
r a:hash
|
|
d 0
|
|
: ENTRY.17
|
|
r ENTRY.16
|
|
d 1957010690
|
|
r a:length
|
|
d 0
|
|
: ENTRY.18
|
|
r ENTRY.17
|
|
d 1526142126
|
|
r a:prepend
|
|
d 0
|
|
: ENTRY.19
|
|
r ENTRY.18
|
|
d -674869668
|
|
r a:reverse
|
|
d 0
|
|
: ENTRY.20
|
|
r ENTRY.19
|
|
d -1103209427
|
|
r a:store
|
|
d 0
|
|
: ENTRY.21
|
|
r ENTRY.20
|
|
d -293712106
|
|
r s:temp
|
|
d 0
|
|
: ENTRY.22
|
|
r ENTRY.21
|
|
d 2090026588
|
|
r a:th
|
|
d 0
|
|
: ENTRY.23
|
|
r ENTRY.22
|
|
d 253189153
|
|
r allot
|
|
d 0
|
|
: ENTRY.24
|
|
r ENTRY.23
|
|
d 193486360
|
|
r and
|
|
d 0
|
|
: ENTRY.25
|
|
r ENTRY.24
|
|
d 427330826
|
|
r block:load
|
|
d 0
|
|
: ENTRY.26
|
|
r ENTRY.25
|
|
d 427567833
|
|
r block:save
|
|
d 0
|
|
: ENTRY.27
|
|
r ENTRY.26
|
|
d 193487813
|
|
r bye
|
|
d 0
|
|
: ENTRY.28
|
|
r ENTRY.27
|
|
d 253758370
|
|
r c:get
|
|
d 0
|
|
: ENTRY.29
|
|
r ENTRY.28
|
|
d 253768699
|
|
r c:put
|
|
d 0
|
|
: ENTRY.30
|
|
r ENTRY.29
|
|
d 2090140673
|
|
r call
|
|
d 0
|
|
: ENTRY.31
|
|
r ENTRY.30
|
|
d -161057562
|
|
r choose
|
|
d 0
|
|
: ENTRY.32
|
|
r ENTRY.31
|
|
d 255669810
|
|
r comma
|
|
d 0
|
|
: ENTRY.33
|
|
r ENTRY.32
|
|
d -748339476
|
|
r compare
|
|
d 0
|
|
: ENTRY.34
|
|
r ENTRY.33
|
|
d -1979274138
|
|
r compiling?
|
|
d 0
|
|
: ENTRY.35
|
|
r ENTRY.34
|
|
d 2090156064
|
|
r copy
|
|
d 0
|
|
: ENTRY.36
|
|
r ENTRY.35
|
|
d 352952457
|
|
r d:address
|
|
d 0
|
|
: ENTRY.37
|
|
r ENTRY.36
|
|
d 626189207
|
|
r d:create
|
|
d 0
|
|
: ENTRY.38
|
|
r ENTRY.37
|
|
d 2012546722
|
|
r d:exists?
|
|
d 0
|
|
: ENTRY.39
|
|
r ENTRY.38
|
|
d -1539492880
|
|
r d:flags
|
|
d 0
|
|
: ENTRY.40
|
|
r ENTRY.39
|
|
d -176741337
|
|
r d:hash
|
|
d 0
|
|
: ENTRY.41
|
|
r ENTRY.40
|
|
d -176589039
|
|
r d:link
|
|
d 0
|
|
: ENTRY.42
|
|
r ENTRY.41
|
|
d 975220285
|
|
r d:lookup
|
|
d 0
|
|
: ENTRY.43
|
|
r ENTRY.42
|
|
d 193489474
|
|
r dip
|
|
d 0
|
|
: ENTRY.44
|
|
r ENTRY.43
|
|
d 2090195226
|
|
r drop
|
|
d 0
|
|
: ENTRY.45
|
|
r ENTRY.44
|
|
d 288947475
|
|
r drop-pair
|
|
d 0
|
|
: ENTRY.46
|
|
r ENTRY.45
|
|
d 193489824
|
|
r dtc
|
|
d 0
|
|
: ENTRY.47
|
|
r ENTRY.46
|
|
d 193489870
|
|
r dup
|
|
d 0
|
|
: ENTRY.48
|
|
r ENTRY.47
|
|
d -59285433
|
|
r dup-pair
|
|
d 0
|
|
: ENTRY.49
|
|
r ENTRY.48
|
|
d 193490778
|
|
r eq?
|
|
d 0
|
|
: ENTRY.50
|
|
r ENTRY.49
|
|
d 258875503
|
|
r fetch
|
|
d 0
|
|
: ENTRY.51
|
|
r ENTRY.50
|
|
d -1885660229
|
|
r fetch-next
|
|
d 0
|
|
: ENTRY.52
|
|
r ENTRY.51
|
|
d 2090257196
|
|
r fill
|
|
d 0
|
|
: ENTRY.53
|
|
r ENTRY.52
|
|
d -1163346114
|
|
r forever
|
|
d 0
|
|
: ENTRY.54
|
|
r ENTRY.53
|
|
d 5863407
|
|
r gc
|
|
d 0
|
|
: ENTRY.55
|
|
r ENTRY.54
|
|
d 193493055
|
|
r gt?
|
|
d 0
|
|
: ENTRY.56
|
|
r ENTRY.55
|
|
d 260584565
|
|
r gteq?
|
|
d 0
|
|
: ENTRY.57
|
|
r ENTRY.56
|
|
d 2090324905
|
|
r here
|
|
d 0
|
|
: ENTRY.58
|
|
r ENTRY.57
|
|
d 5863476
|
|
r if
|
|
d 0
|
|
: ENTRY.59
|
|
r ENTRY.58
|
|
d 314257922
|
|
r interpret
|
|
d 0
|
|
: ENTRY.60
|
|
r ENTRY.59
|
|
d 5863485
|
|
r io
|
|
d 0
|
|
: ENTRY.61
|
|
r ENTRY.60
|
|
d 2090414049
|
|
r jump
|
|
d 0
|
|
: ENTRY.62
|
|
r ENTRY.61
|
|
d -1223977595
|
|
r lit
|
|
d 0
|
|
: ENTRY.63
|
|
r ENTRY.62
|
|
d -1465379862
|
|
r quote
|
|
d 0
|
|
: ENTRY.64
|
|
r ENTRY.63
|
|
d 193498500
|
|
r lt?
|
|
d 0
|
|
: ENTRY.65
|
|
r ENTRY.64
|
|
d 266514170
|
|
r lteq?
|
|
d 0
|
|
: ENTRY.66
|
|
r ENTRY.65
|
|
d 266796867
|
|
r n:abs
|
|
d 0
|
|
: ENTRY.67
|
|
r ENTRY.66
|
|
d 266796918
|
|
r n:add
|
|
d 0
|
|
: ENTRY.68
|
|
r ENTRY.67
|
|
d 266800217
|
|
r n:dec
|
|
d 0
|
|
: ENTRY.69
|
|
r ENTRY.68
|
|
d 266800368
|
|
r n:div
|
|
d 0
|
|
: ENTRY.70
|
|
r ENTRY.69
|
|
d 1637942608
|
|
r n:divmod
|
|
d 0
|
|
: ENTRY.71
|
|
r ENTRY.70
|
|
d 266805959
|
|
r n:inc
|
|
d 0
|
|
: ENTRY.72
|
|
r ENTRY.71
|
|
d -1502694228
|
|
r n:limit
|
|
d 0
|
|
: ENTRY.73
|
|
r ENTRY.72
|
|
d 266809907
|
|
r n:max
|
|
d 0
|
|
: ENTRY.74
|
|
r ENTRY.73
|
|
d 266810161
|
|
r n:min
|
|
d 0
|
|
: ENTRY.75
|
|
r ENTRY.74
|
|
d 266810349
|
|
r n:mod
|
|
d 0
|
|
: ENTRY.76
|
|
r ENTRY.75
|
|
d 266810555
|
|
r n:mul
|
|
d 0
|
|
: ENTRY.77
|
|
r ENTRY.76
|
|
d 2024000897
|
|
r n:negate
|
|
d 0
|
|
: ENTRY.78
|
|
r ENTRY.77
|
|
d 266813830
|
|
r n:put
|
|
d 0
|
|
: ENTRY.79
|
|
r ENTRY.78
|
|
d 266817079
|
|
r n:sub
|
|
d 0
|
|
: ENTRY.80
|
|
r ENTRY.79
|
|
d 215056784
|
|
r n:to-s
|
|
d 0
|
|
: ENTRY.81
|
|
r ENTRY.80
|
|
d -1486229492
|
|
r n:zero?
|
|
d 0
|
|
: ENTRY.82
|
|
r ENTRY.81
|
|
d -494948871
|
|
r n:-zero?
|
|
d 0
|
|
: ENTRY.83
|
|
r ENTRY.82
|
|
d 193500364
|
|
r nip
|
|
d 0
|
|
: ENTRY.84
|
|
r ENTRY.83
|
|
d 5863647
|
|
r nl
|
|
d 0
|
|
: ENTRY.85
|
|
r ENTRY.84
|
|
d 193500566
|
|
r not
|
|
d 0
|
|
: ENTRY.86
|
|
r ENTRY.85
|
|
d 5863686
|
|
r or
|
|
d 0
|
|
: ENTRY.87
|
|
r ENTRY.86
|
|
d 2090594561
|
|
r over
|
|
d 0
|
|
: ENTRY.88
|
|
r ENTRY.87
|
|
d 193502740
|
|
r pop
|
|
d 0
|
|
: ENTRY.89
|
|
r ENTRY.88
|
|
d -853324053
|
|
r process-data
|
|
d 0
|
|
: ENTRY.90
|
|
r ENTRY.89
|
|
d 2090629861
|
|
r push
|
|
d 0
|
|
: ENTRY.91
|
|
r ENTRY.90
|
|
d 1059716234
|
|
r restart
|
|
d 0
|
|
: ENTRY.92
|
|
r ENTRY.91
|
|
d -127536406
|
|
r s:append
|
|
d 0
|
|
: ENTRY.93
|
|
r ENTRY.92
|
|
d 410125037
|
|
r a:copy
|
|
d 0
|
|
: ENTRY.94
|
|
r ENTRY.93
|
|
d 272730363
|
|
r a:dup
|
|
d 0
|
|
: ENTRY.95
|
|
r ENTRY.94
|
|
d 102250697
|
|
r s:evaluate
|
|
d 0
|
|
: ENTRY.96
|
|
r ENTRY.95
|
|
d 652426460
|
|
r a:fetch
|
|
d 0
|
|
: ENTRY.97
|
|
r ENTRY.96
|
|
d -89307369
|
|
r a:for-each
|
|
d 0
|
|
: ENTRY.98
|
|
r ENTRY.97
|
|
d 704009186
|
|
r s:get/token
|
|
d 0
|
|
: ENTRY.99
|
|
r ENTRY.98
|
|
d 410289558
|
|
r a:hash
|
|
d 0
|
|
: ENTRY.100
|
|
r ENTRY.99
|
|
d 410401271
|
|
r s:keep
|
|
d 0
|
|
: ENTRY.101
|
|
r ENTRY.100
|
|
d 289838292
|
|
r a:length
|
|
d 0
|
|
: ENTRY.102
|
|
r ENTRY.101
|
|
d -1950939456
|
|
r s:prepend
|
|
d 0
|
|
: ENTRY.103
|
|
r ENTRY.102
|
|
d 272743435
|
|
r s:put
|
|
d 0
|
|
: ENTRY.104
|
|
r ENTRY.103
|
|
d 143016046
|
|
r a:reverse
|
|
d 0
|
|
: ENTRY.105
|
|
r ENTRY.104
|
|
d 668377535
|
|
r a:store
|
|
d 0
|
|
: ENTRY.106
|
|
r ENTRY.105
|
|
d 410724968
|
|
r s:temp
|
|
d 0
|
|
: ENTRY.107
|
|
r ENTRY.106
|
|
d 2090673454
|
|
r a:th
|
|
d 0
|
|
: ENTRY.108
|
|
r ENTRY.107
|
|
d 410733744
|
|
r s:to-n
|
|
d 0
|
|
: ENTRY.109
|
|
r ENTRY.108
|
|
d -38720901
|
|
r shift-left
|
|
d 0
|
|
: ENTRY.110
|
|
r ENTRY.109
|
|
d -1270529650
|
|
r shift-right
|
|
d 0
|
|
: ENTRY.111
|
|
r ENTRY.110
|
|
d -1801857825
|
|
r drop
|
|
d 0
|
|
: ENTRY.112
|
|
r ENTRY.111
|
|
d -1801857830
|
|
r sigil:#
|
|
d 0
|
|
: ENTRY.113
|
|
r ENTRY.112
|
|
d -1801857827
|
|
r sigil:&
|
|
d 0
|
|
: ENTRY.114
|
|
r ENTRY.113
|
|
d -1801857826
|
|
r sigil:'
|
|
d 0
|
|
: ENTRY.115
|
|
r ENTRY.114
|
|
d -1801857807
|
|
r sigil::
|
|
d 0
|
|
: ENTRY.116
|
|
r ENTRY.115
|
|
d 576954903
|
|
r sigil:get
|
|
d 0
|
|
: ENTRY.117
|
|
r ENTRY.116
|
|
d 576967971
|
|
r sigil:set
|
|
d 0
|
|
: ENTRY.118
|
|
r ENTRY.117
|
|
d 193505809
|
|
r sip
|
|
d 0
|
|
: ENTRY.119
|
|
r ENTRY.118
|
|
d 5863816
|
|
r sp
|
|
d 0
|
|
: ENTRY.120
|
|
r ENTRY.119
|
|
d 274826578
|
|
r store
|
|
d 0
|
|
: ENTRY.121
|
|
r ENTRY.120
|
|
d 1976567422
|
|
r store-next
|
|
d 0
|
|
: ENTRY.122
|
|
r ENTRY.121
|
|
d 2090739264
|
|
r swap
|
|
d 0
|
|
: ENTRY.123
|
|
r ENTRY.122
|
|
d -1371592987
|
|
r sys:buffers/block
|
|
d 0
|
|
: ENTRY.124
|
|
r ENTRY.123
|
|
d -1359625529
|
|
r sys:buffers/loops
|
|
d 0
|
|
: ENTRY.125
|
|
r ENTRY.124
|
|
d 942827360
|
|
r sys:buffers/numeric-conversion
|
|
d 0
|
|
: ENTRY.126
|
|
r ENTRY.125
|
|
d -1351755340
|
|
r sys:buffers/scope
|
|
d 0
|
|
: ENTRY.127
|
|
r ENTRY.126
|
|
d -1357624343
|
|
r sys:buffers/needs
|
|
d 0
|
|
: ENTRY.128
|
|
r ENTRY.127
|
|
d 1106725658
|
|
r sys:buffers/reserved
|
|
d 0
|
|
: ENTRY.129
|
|
r ENTRY.128
|
|
d 1438625409
|
|
r sys:buffers/strings+arrays
|
|
d 0
|
|
: ENTRY.130
|
|
r ENTRY.129
|
|
d -1363217974
|
|
r sys:buffers/input
|
|
d 0
|
|
: ENTRY.131
|
|
r ENTRY.130
|
|
d 193506620
|
|
r tab
|
|
d 0
|
|
: ENTRY.132
|
|
r ENTRY.131
|
|
d 275614599
|
|
r times
|
|
d 0
|
|
: ENTRY.133
|
|
r ENTRY.132
|
|
d 2090773084
|
|
r tuck
|
|
d 0
|
|
: ENTRY.134
|
|
r ENTRY.133
|
|
d 276987953
|
|
r until
|
|
d 0
|
|
: ENTRY.135
|
|
r ENTRY.134
|
|
d 276287585
|
|
r v:dec
|
|
d 0
|
|
: ENTRY.136
|
|
r ENTRY.135
|
|
d 276293327
|
|
r v:inc
|
|
d 0
|
|
: ENTRY.137
|
|
r ENTRY.136
|
|
d 279132286
|
|
r while
|
|
d 0
|
|
: ENTRY.138
|
|
r ENTRY.137
|
|
d 193511454
|
|
r xor
|
|
d 0
|
|
|
|
: Latest
|
|
r ENTRY.138
|
|
: FREE-SPACE
|
|
~~~
|
|
|