VARIABLE X
[The fourth in a series of posts on the evolution of TransForth]
We’re getting very close to the point at which we’ll need to start moving closer to the machine with things like direct memory access. Implementing IF/ELSE/THEN, which we’ll do in the next post, will practically require it.
Let’s see if we can stretch this experiment for one more post though and work out support for variables. Normally variables in Forth are just named chunks of memory. Allocating one is done by way of VARIABLE FOO. Much like CREATE, this creates a word (FOO) in the dictionary. Invoking FOO however doesn’t call the inner interpreter. Instead it pushes a memory address (containing its value) to the stack. Not wanting to introduce the idea of memory addresses yes, let's just have variable words push their own dictionary index.
define "VARIABLE" (fun () -> dict <-
{ Word with Name = token (); Code = fun () -> push (dict.Length + 1) } :: dict)
This is extremely similar to the CREATE we defined earlier:
define "CREATE" (fun () -> dict <- {
Word with Name = token (); Code = interpret (dict.Length + 1) } :: dict)
In fact, let’s refactor those and their essential difference is very clear:
let make word code = define word (fun () -> dict <-
{ Word with Name = token (); Code = code (dict.Length + 1) } :: dict)
make "CREATE" interpret
make "VARIABLE" (fun i () -> push i)
Load and Store
The normal Forth words for loading and storing values in a variable are @ and ! respectively. Saying FOO @ produces the value on the stack. That is, FOO pushes its own address, then @ pops the address and pushes back the value at that address. Saying 42 FOO ! stores 42 in FOO. That is, ! expects a value and an address on the stack and stores the value at that address. Of course, when we say “address” in our temporary solution we really mean dictionary index. We’ll add a temporary hack (removed next post) to store values:
type WordRecord = {
Name : string
Code : unit -> unit
Def : WordRecord list // secondary definition
Value : int ref // used for variables
Immediate : bool } // execute immediately even while compiling
let Word = { Name = ""; Code = id; Def = []; Value = ref 0; Immediate = false }
Load and store are simple now:
define "@" (fun () -> !(pop () |> index).Value |> push)
define "!" (fun () -> (pop () |> index).Value := pop ())
A couple of related standard words are useful too:
: ? @ . ; \ display contents of variable
: +! ( add a -- ) DUP @ ROT + SWAP ! ; \ add to variable
Tests
case "VARIABLE X 42 X ! X @ X ? FORGET X""42 "// variables
case "VARIABLE Y 40 Y ! Y ? 2 Y +! Y ? FORGET Y""40 42 "// add variable