ufud.org


preserving the golden age of computing

Adding a READ ... DATA like functionality to Forth

forth example code

Once you start coding in Forth for a while, you may come across a situation where you want to write a sequence of bytes to some memory location (e.g. if you want to store the sprite data for a game). Out of the box, Forth provides the word C, to store arrays of data which can then be written to the desired memory address using a DO…LOOP and MOVE in your code. However, I wanted to reduce overhead in my code and so I defined a custom word which is similar in function to the READ … DATA command you may know from the BASIC programming language. Here’s how it works.

The idea

For example, if we wanted the values 01, 02 and 03 sequentially written to addresses 8000, 8001 and 8002 in BASIC we’d do something like this:

10 FOR X = 0 TO 2
20 READ I
30 POKE 8000+X,I
40 NEXT X
100 DATA 01, 02, 03

The FOR … NEXT loop will read the values stored in line 100 and place them at the desired memory addresses. Now, my idea was to have things work in a similar manner with Forth just like this:

( Example 01 )

$01 $02 $03 $8000 data

Of course, it should be possible to place between 1 and n values to be written on the stack, where the maximum number of values our data command can process is only limited by the maximum number of elements the stack can take up. So far so good. But how would a data command eventually be implemented in Forth?

The code

The actual code for my custom data word can be seen below. Don’t worry if it looks rather cryptic to you, I’ll explain what it is doing in each line just below the code.

: data ( n1 ... nx addr -- )
  0 depth 3 - do
    dup i +
    rot swap c!
    -1 +loop 
  drop ;

How it works

: data ( n1 … nx addr – )

As you will know, this is how we begin a new word definition in Forth. The braces are used for a comment, detailing what needs to be on the stack before the word is being called.

0 depth 3 - do

Next we want a loop that is counting down the values (nx … n1) and writes them to memory. So we take the current depth of the stack and subtract 3 from the result. Why do we do that? Because the moment the word depth has been executed, the stack looks like this:

  Stack position        Value

(top)     x + 3         integer value returned by "depth"
          x + 2         0
          x + 1         addr
          x             nx
                  .
                  .
                  .
          2             n2          
(bottom)  1             n1

So we need to take into account that the first three values on the stack at that moment are:

  1. value returned by depth

  2. the number 0 which tells the do loop when it is required to stop counting (lower limit)

  3. the address addr at which we want to start placing the values n1 to nx (our start address)

As a result we’ll have to subtract 3 from the reported depth of the stack to get the total number of values on the stack (x). The do loop will then start counting backward to 0, beginning at x.

“Wait a minute! You said you want to start writing the values beginning at addr but why then is the loop couting down?” you may ask yourself. Well, I’ll explain in a moment. Once the do loop is set up, the stack will look like this:

Stack position          Value

(top)     x + 1         addr
          x             nx
                  .
                  .
                  .
          2             n2
(bottom)  1             n1

dup i +

We’re now inside the loop. As you can see above, our start address addr is now on top of the stack, followed by our values. So we duplicate the address and add the current loop counter i to it. This is because first value (n1) is at the bottom of the stack and the last value (nx) at the top.

If the do loop was counting forward from 0 to x we would actually be writing the values on the stack in reverse order !!

So now the stack looks like this:

Stack position          Value

(top)     x + 2         (addr + i)
          x + 1         addr
          x             nx
                  .
                  .
                  .
          2             n2
(bottom)  1             n1

rot swap c!

These three words do the actual work of picking the next value (as seen from the top of the stack) and writing it to its proper memory address.

After the rot command the value for nx is sitting on top of the stack:

Stack position          Value

(top)     x + 2         nx
          x + 1         (addr + i)
          x             addr
                  .
                  .
                  .
          2             n2
(bottom)  1             n1

Next, the swap command swaps the position of the top two values nx and (addr + i) which we need for the next step.

Stack position          Value

(top)     x + 2         (addr + i)
          x + 1         nx
          x             addr
                  .
                  .
                  .
          2             n2
(bottom)  1             n1

And finally we use the c! command to store nx at (addr + i), removing both from the stack, leaving the duplicate of addr on top followed by the next value (nx-1)

Stack position          Value

(top)     x + 1         addr
          x             nx-1
                  .
                  .
                  .
          2             n2
(bottom)  1             n1

-1 +loop

Count backwards by one and repeat the loop until we’ve reached 0. Once all values (n1 … nx) have been written, a last duplicate of addr remains on the stack:

Stack position          Value

(top = bottom)  1       addr

drop

This will remove the remaining addr duplicate from the stack. The program is finished.

What is stored in memory:

If we take Example 01 above and run it with our new word definition, the memory starting at the specified address $8000 will look like this:

Address  Value
 
 $8000    $01  
 $8001    $02  
 $8002    $03  

Conclusion

So this is my little article about how I implemented a READ … DATA functionality in Forth. The code above works with most Forth compilers and I have successfully tested it with the following:

  • GNU Forth (Linux)

  • DX-Forth (CP/M)

  • DurexForth (Commodore 64)

There are probably a few improvements that can be made to the code provided above. For one, it doesn’t check if there are at least two parameters on the stack (one value and the address it should be written to). Feel free to modify and adapt the code to your needs. If you have feedback, qustions or corrections regarding this article, contact me either via email or via Mastodon (see footer on this page).