knitr::opts_chunk$set(echo = TRUE) library(froth, quietly = TRUE)
Forth-like languages are nice because they don't require a lot of variables. However, sometimes there's a problem that requires saving a particular value for later usage that can't be on the stack. This is where variables, constants, and arrays come in.
Let's say we want to store a value somewhere outside of the stack. We can initialize a variable using the VARIABLE
word.
fr> VARIABLE testvar ok.
Nothing happened! That's because this word only allocates space for a variable without doing any other actions. We can now call the name of the variable at any time to push its address onto the stack. In Forth, this would be an actual memory address. froth
, however, uses an abstracted memory map.
fr> test .s [[1]] [1] "addr <test, cell 0>" [[2]] NULL ok.
We can see the address of the variable at the top of the stack. We'll come back to the cell 0
bit later.
To store a value in a variable, we use !
. We can copy the value in an address to the stick using @
.
fr> variable newvar ok. fr> 20 newvar ! ok. fr> newvar @ . 20 ok.
Notice that for !
we push the value first, then the address. It's also important to note that the variable retains its value even after calling @
.
fr> variable newvar ok. fr> 20 newvar ! ok. fr> newvar dup @ . dup @ . @ . 20 20 20 ok.
@ .
is a pretty common call to inspect the value of a variable. In fact, it's so common that there's a shortcut word to perform both, ?
.
fr> variable newvar 20 newvar ! ok. fr> newvar ? 20 ok.
Variables are often useful as counters, using the +!
command. +!
is similar to !
, except that it adds to the variable rather than replacing it.
fr> variable newvar ok. fr> 0 newvar ! ok. fr> 5 0 do 1 newvar +! loop newvar ? 5 ok.
Sometimes we just need a name for a value. Variables that will never change values are called constants, and can be fittingly be defined with the CONSTANT
word.
fr> 100 CONSTANT hundred ok. fr> hundred . 100 ok.
Constants avoid the hassle of dealing with addresses, and can be accessed slightly faster than variables. Calling the name of the variable copies its value directly onto the stack.
Remember when I said we'd come back to that whole "cell 0
" thing? By default, initializing a variable creates space for one "thing" at an address. Unlike Forth, froth
creates this space at an abstracted memory location, and that space is big enough for any arbitrary R object. However, sometimes it's nice to have a variable that can store more than one value, like in the case of vectors or lists in R.
Let's start by initializing an array. The definition proceeds like in normal variables, we just have to further specify that we want the variable to be larger than a single value:
fr> VARIABLE myarray 5 CELLS ALLOT ok.
This initializes a variable with 5 slots for things. Internally, this is the same as a length 5 list. The word ALLOT
allots memory for the preceding variable, and we'll come back to CELLS
in a moment.
Previously, we saw this behavior:
fr> myarray .s [[1]] [1] "addr <myarray, cell 0>" [[2]] NULL ok.
cell 0
means that this address is pointing to the first value of the array myarray
. Cells are 0-indexed in froth
, so this is the first entry in the array. Now as before, we can store a value at the first cell. I'm assuming the address is still on the stack, so remember we need to call swap
to ensure the top value is the address.
fr> 1 swap ! ok. fr> myarray ? 1 ok.
Ok, now we can look at the word CELLS
. Let's start by inspecting what we get by pushing CELLS
to the stack:
fr> 4 CELLS .s clear [[1]] [1] "addr <, cell 4>" [[2]] NULL ok.
It's the fourth cell of...nothing? CELLS
creates a unique kind of address--it stores cell number, but it doesn't point to any variable. Words like ALLOT
expect one of these objects to tell it how much space to allocate. While CELLS
calls don't point to anything, we can add them to variable addresses to get to other values in the array.
fr> myarray ok. fr> 4 CELLS + .s [[1]] [1] "addr <myarray, cell 4>" [[2]] NULL ok.
Notice how we've now gotten an address to myarray
that's offset by 4 cells. Since the size of myarray
is 5, this is the final entry in the array (indexing starts at 0, so the 5 entries are 0-4). This address works like the examples in the Variables section, so we can use !
, @
, +!
, and ?
on it:
fr> VARIABLE myarray 5 CELLS ALLOT ok. fr> 0 BEGIN dup 5 < WHILE dup dup myarray swap CELLS + ! 1 + REPEAT drop ok.
Here we're putting it all together. See if you can guess what the contents of myarray
now look like before reading the next section.
We start by initializing an array myarray
of size 5. Then, we put 0
on the stack and begin
a loop. At each iteration of the loop we:
myarray
, swap it with the top duplicatemyarray
So what happens? When the stack is 0, we store 0 at the 0th position of myarray
, then 1 at the 1st position, 2 at the second, etc. Let's print out the entire array:
fr> 5 0 do myarray i cells + ? loop 0 1 2 3 4 ok.
It's also possible to initialize an array to a specific value using the CREATE
and ,
words. Pay very close attention to this syntax!
fr> CREATE myarray 0 , 1 , 2 , 3 , 4 , ok. fr> 5 0 do myarray i cells + ? loop 0 1 2 3 4 ok.
Did you notice how we used ,
? Unlike English, ,
is a froth
word, meaning that it needs to be space separated and it acts upon the stack. This means that we must include a comma after the last element to ensure it's added to the array.
It's often useful to be able to quickly change the values or the size of allocated arrays.
To change the values in an array, we can use the FILL
word. FILL
expects three items on the stack: a variable address, a CELLS
offset n
, and a value x
. It then fills the first n
cells with x
.
fr> variable filltest 3 cells allot ok. fr> 3 0 do i filltest i cells + ! loop ok. fr> 3 0 do filltest i cells + ? loop 0 1 2 ok. fr> filltest 3 cells 10 fill ok. fr> 3 0 do filltest i cells + ? loop 10 10 10 ok.
There's also the shortcut word ERASE
, which is equivalent to 0 FILL
.
fr> filltest 3 cells erase ok. fr> 3 0 do filltest i cells + ? loop 0 0 0 ok.
That changes values, but what if we end up with the wrong size array altogether? For that, we have two options: EXTEND
and REALLOT
. Both of these words have the same form; they expect the stack to be comprised of a variable address v
and a value n
. EXTEND
will make the array at v
larger by n
cells, and REALLOT
will change v
to have exactly n
cells (copying the contents of the first n
cells, if any).
fr> variable myarray 3 cells allot ok. fr> myarray 5 cells + ? Error: array accessed out of bounds! fr> myarray 2 cells extend ok. fr> myarray 5 cells + ? 0 ok. fr> 10 myarray 1 cells + ! ok. fr> myarray 3 cells reallot ok. fr> myarray 5 cells + ? Error: array accessed out of bounds! fr> myarray 1 cells + ? 10 ok.
If you're ever unsure about how large your array is, use the LENGTH
word to copy the length of an address to the stack, and LENGTH?
to print it out.
fr> variable myarray 10 cells allot ok. fr> myarray LENGTH . 10 ok. fr> myarray 20 cells reallot ok. fr> myarray LENGTH? 20 ok.
Variables storing values is nice, but sometimes it's useful to be able to store a function inside a variable. This is a common practice in R (e.g., any of the *apply
functions), and also exists in C in the form of function pointers.
Forth also incorporates a way to do this, using a unique word to put an "execution token" on the stack. An execution token is essentially just the location of a particular type of code. When you want to execute that token, Forth runs the code at the location referenced by the execution token.
This capability is implemented in froth
, but it does have a few differences to classic Forths due to how the memory is abstracted. Let's start with a simple example:
fr> : GREET ." Hello!" ; ok. fr> GREET Hello! ok. fr> ' GREET . executable token < greet > ok. fr> ' GREET EXECUTE Hello! ok.
We have two new words here: '
and EXECUTE
. The first of these, '
(pronounced "tick"), creates an execution token for the word that follows it, then pushes it to the stack. If we print this value out, we get executable token < greet >
. Forth languages would traditionally return memory location, but froth
just shows the word that the execution token corresponds to. The second word, EXECUTE
, does just that: it executes the execution token at the top of the stack.
In a way, ' GREET EXECUTE
is just a really roundabout way of accomplishing the same thing as just calling GREET
directly. Naturally, one might wonder what the purpose of this is. We can use '
to store functions in variables so we can indirectly execute them later on. Let's see how to do this with an example:
fr> : HELLO ." Hello" ; ok. fr> : GOODBYE ." Goodbye" ; ok. fr> VARIABLE 'aloha ' HELLO 'aloha ! ok. fr> : ALOHA 'aloha @ EXECUTE ; ok.
I'll show what happened in a minute, but I'd encourage readers to first think about this code. What's going on here? What would running 'aloha
do? Read through the code, then continue on to the explanation below.
The first two lines should be simple by now. We've defined two functions, HELLO
and GOODBYE
, that simply print out Hello
and Goodbye
(respectively). Then, we initialize a variable called 'aloha
, and store an execution token for HELLO
inside that variable. Finally, we define a function ALOHA
that executes the execution token stored in 'aloha
.
This implies two things. First, if we run ALOHA
, we should expect it to call HELLO
:
fr> ALOHA Hello ok.
And indeed it does. Secondly, if we store a different function pointer in 'aloha
and then call ALOHA
, we should expect to see a different result:
fr> ' GOODBYE 'aloha ! ok. fr> ALOHA Goodbye ok.
Here lies the power of execution tokens. Using a single variable, we can call multiple functions!
I'll also note that we called our variable 'aloha
("tick-aloha"). This convention of preceding the variable name with a '
is used in Forth to denote variables that store execution pointers.
There is a one major difference here between Forth and froth
. froth
is completely interpreted, and thus does not distinguish between variable definition lines and non-definition lines. What this means for Forth programmers is that the froth
word '
is equivalent to the Forth word [']
when used in a definition context. froth
cannot look at the input stream, and will instead always look at the next word on the execution stack. I'm thinking of changing this to be more consistent with Forth in the future, but I haven't gotten to it yet.
This implications of this are demonstrated by the following:
fr> : SAY ' 'aloha ! ; ok. fr> SAY HELLO Error: can't find 'aloha fr> : COMING ' HELLO 'aloha ! ; ok. fr> : GOING ' GOODBYE 'aloha ! ; ok. fr> COMING ALOHA GOING ALOHA Hello Goodbye ok.
In traditional Forth, the GOING
definition would be : GOING ['] GOODBYE 'aloha ! ;
, and SAY HELLO
would not produce an error. However, this isn't the case here. [']
is also provided as an alias for '
. Again, I plan to change this eventually.
VARIABLE xxx
: creates a variable named xxx
! ( n addr -- )
: stores the value n
at address addr
@ ( addr -- n )
: copies the value at addr
to the stack? ( addr -- )
: prints the value of addr
+! ( n addr -- )
: adds the value n
to the value at addr
CONSTANT xxx (n -- )
: creates a constant called xxx
that stores n
; xxx
returns n
when calledALLOT ( addr ncells -- )
: allocates ncells
cells at addr
CREATE xxx y1 , y2 , ... yn ,
: creates an array xxx
with values y1, y2, ... yn
CELLS ( n -- )
: creates a memory address offset for arraysFILL ( addr ncells val -- )
: fills ncells
cells of memory beginning at addr
with val
ERASE ( addr ncells -- )
: fills ncells
cells of memory beginning at addr
with 0REALLOT ( addr ncells -- )
: reallots array at addr
to have size ncells
.EXTEND ( addr ncells -- )
: extends the array at addr
by ncells
cellsLENGTH ( addr -- len )
: pushes the length of the array at addr
onto the stackLENGTH? ( addr -- )
: prints the length of the array at addr
' xxx ( -- addr )
: attempts to find xxx
in the dictionary, and pushes an execution token for xxx
to the stack if foundEXECUTE ( xt -- )
: executes an execution token on top of the stack['] xxx ( -- addr )
: currently equivalent to '
for froth
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.