zaterdag 12 december 2020

Using Forth on my ELF II

Using Forth on my ELF II computer (including a CREATE DOES> explanation ) is the second article in a series about me and using Forth that started with my blogpost “My first computer and Forth” 
With Forth running on my ELF II computer with its RCA 1802 processor now real high level coding was possible.
I used virtual disk blocks with code stored in memory (loaded and saved to my system using audio cassettes). For coding in assembler an 1802 assembler in Forth was available. Also Forth text editor was published in a Forth magazine. I controlled hardware with Forth (an AY-3-8910 audio chip that I had added to my system). Forth became one the most important work horse on my system

In Forth you can new own words in vocabularies using : ; Do (complex) math in RPN using a (Forth) stack. Add extensions for floating point and strings. In my opinion one of the most powerful features of Forth is CREATE DOES> ( in the fig-Forth version when i started with Forth  <BUILD DOES> ).

Explanation of CREATE DOES>

Before i can explain CREATE DOES> (or <BUILD DOES> as it was called when i started with Forth) some fundamentals of Forth must be known. I do not give an in depth explanation however it is always possible to ask questions after reading this article.
Parameters (numbers) in Forth are passed using a stack.
(New) Forth words are stored in memory in a dictionary.
Things you type in using your keyboard are placed in a special memory area, the terminal input buffer. Space or spaces are used as separators between words and there is a character defining the end of line.
The Forth interpreter looks at the first word from the terminal input buffer and tries to find it in its dictionary. If found, there is also code to be executed in the dictionary. If not found Forth tries to convert it to a number and places the number on the stack. If not found Forth shows an error message.
After that (when there was no error) it continues with the next word until the end of line character in the terminal input buffer. The end of line character is just a special Forth word that prints an oke prompt and lets you enter a new line of text in the terminal input buffer.
Defining a new Forth word is placing a new word in the Forth dictionary together with the code that needs to be executed.
Example 1 : ; ( or colon semi-colon ) construction.
: My1stWord DUP 2 + . ;
The : puts My1stWord in the dictionary. DUP 2 + is the Forth code that belongs to My1stWord
; ( semi-colon) indicates that this is the end of the definition made by : ( colon )
Example 2 VARIABLE ( I do not encourage the use variables as often it is better to store things on the stack than in a variables)
15 VARIABLE My1stVar
15 is put on the stack. A variable with the name My1stVar is created containing the initial value 15 . ( I assume you have a Forth system where the initial value is on the stack. it is also possible you have a system where no initial value is needed/used.)
When the new Forth word My1stVar is executed it gives a memory pointer to the address where the value of the variable is stored. To get the value ( 15 ) we can use the Forth word @ This word uses the address (that is on the top of the stack) and fetches the value.
The magic of creating new words is CREATE DOES>
CREATE creates a new word in the dictionary and the DOES> part tells what to do.
Let make the Forth word JVAR to create a variable This is relative simple:
: JVAR CREATE , DOES> ;
: defines the word JVAR
CREATE is a word that creates the dictionary entry. The dictionary entry contains a pointer to the DOES> part. So when the word is found in the dictionary it knows what to do.
, ( comma ) is just a simple word that gets a number from the stack and puts it in the dictionary.
And then there is the DOES> part, in this case seems to do nothing.
When we now enter 12 JVAR My2ndVar The variable with the name My2ndVar is created and available in the Forth Dictionary.
When the new Forth word My2ndVar is executed it gives a memory pointer to the address where the value of the variable My2ndVar is stored. To get the value ( 12 ) we can use the Forth word @
To change this to the behaviour of a “constant” where, when executed not a pointer but the value is on the stack, we could do something like
: My1stCONST My2ndVar @ ;
However this would be an uncommon way to create a constant as this is actually fetching a variable. Creating multiple constants would require for each constant also a variable.
Let us make a word JCONST to create a ‘real’ constant.
: JCONST CREATE , DOES> @ ;
The @ is now in the DOES> part.
32 JCONST My2ndCONST
creates the word My2ndCONST
The DOES> part uses the address and fetches ( @ ) the value.
A very powerful way to make defining words at your own flavor as everything can be adapted in this CREATE DOES> concept.
For example create double size variables initialized with the value 0
: JDVAR CREATE 0 , 0 , DOES> ;
or do you want something else like a constant with a value that increments each time with one when a new constant word is created?

Forth has advantages and disadvantages. Floating point math, use of text strings. It was always a bit different from other computer languages. Most times with what I would call ‘more contact to what is really happening in the system’. I liked using less variables and using the data stack.

In my brain came the idea that the most beautiful programs are the smallest ones. When I was repeatedly typing (almost) the same code (copy and paste was that time not as easy as nowadays) programs could be done differently (and better). When you expect to find here a lot of real Forth code that you can enter in any Forth system I must disappoint you. However I present some ideas and parts of code that you can use in your Forth system if you know some of the internal working of your Forth system and add some code to adapt it to your Forth system.

A (lazy) simple program to control your hardware in Forth only needs
1 :LAMP LAMP_CHILDROOM
2 :LAMP LAMP_LIVING
4 :LAMP LAMP_B
8 :LAMP LAMP_BEDROOM
If you have created a smart defining word :LAMP with your system suddenly can understand what to do when you type
OFF LAMP_LIVING
ON LAMP_BEDROOM
TOGGLE LAMP_CHILDROOM
TEST? LAMP_CHILDROOM
I used TOGGLE to switch it ON when the light was OFF or switch of OFF when it was ON.
TEST? to leave TRUE or FALSE on the stack depending on the current state.
OFF ON TOGGLE and TEST? can be implemented as constants that each push a different value on the stack.
:LAMP is used to define LAMP_CHILDROOM LAMP_LIVING LAMP_B LAMP_BEDROOM
The 1 2 4 or 8 are stored in these words so later on FORTH knows which hardware bit controls which lamp. The DOES> part is executed when the new defined words are executed.
It gives a pointer to the defined word so the correct hardware bit (and/or other parameters) that was stored during the <BUILD can be fetched.
OFF ON TOGGLE and TEST? pushed a value on the stack so the DOES> part can use it to determine what to do.
So on the stack are the bit/hardware parameters and what you like to do ( OFF ON TOGGLE or TEST? )
SWAP these, so what you want to do is on top of the stack. Using a CASE statement you can continue with the code to turn the bit/(hardware) high, low, toggle it or test the current state (and leave that as a flag on the stack).
To turn all the lights of you need something like
OFF LAMP_CHILDROOM
OFF LAMP_LIVING
OFF LAMP_B
OFF LAMP_BEDROOM
or
OFF OFF OFF OFF LAMP_CHILDROOM LAMP_LIVING LAMP_B LAMP_BEDROOM
This as each device needs its own control parameter when coded the way described above.
Some people don't like that.
This can be solved in different ways:
Method 2 could be OFF ON TOGGLE and TEST? are not constants that were pushed on the stack. Implement OFF ON TOGGLE and TEST? as words that change a variable
Method 3 could be don’t eat the value of OFF OF TOGGLE or TEST? from the stack.
Change the code so it is after (successful) execution still on the stack. (Otherwise leave a special number indicating there was an error)
Another option could be that you don’t like the spaces between the command and word at all.
2 :LAMP LAMP_LIVING
You do not want to create one word LAMP_LIVING
It should create multiple words (without spaces):
LAMP_LIVING_OFF
LAMP_LIVING_ON
LAMP_LIVING_TOGGLE
LAMP_LIVING_TEST?
This is also possible because :LAMP can define multiple words e.g.
2 2 2 2 :LAMP LAMP_LIVING_OFF LAMP_LIVING_ON LAMP_LIVING_TOGGLE LAMP_LIVING_TEST?
This as you can make :LAMP consisting of multiple defining words like
: :LAMP :LAMP_OFF :LAMP_ON :LAMP_TOGGLE :LAMP_TEST? ;
:LAMP_OFF has a CREATE DOES> to turn a bit off
:LAMP_ON has a CREATE DOES> to turn a bit on
:LAMP_TOGGLE has a CREATE DOES> to toggle a bit
:LAMP_TEST? has a CREATE DOES> to test a bit
Each of the defining words needs to know the hardware bit (in this case 2)
It is strange and can give errors if you need to do it as described before repeating 2 2 2 2.
Again this can be solved multiple ways:
One method is by duplicating the hardware bit (Except the last time)
: :LAMP DUP :LAMP_OFF DUP :LAMP_ON DUP :LAMP_TOGGLE :LAMP_TEST? ;
Another method is to make the code of the defining words :LAMP_OFF :LAMP_ON :LAMP_TOGGLE :LAMP_TEST? keeping the hardware bit on the stack
: :LAMP :LAMP_OFF :LAMP_ON :LAMP_TOGGLE :LAMP_TEST? DROP ;
The last word does not need to maintain the bit on the stack. However for consistency it let all words keep the hardware bit and DROP it at the end.
This way if you later-on add a new thing like :LAMP_BLINK you do not need to check which words change the stack.
This all to reduce the code from
2 2 2 2 :LAMP LAMP_LIVING_OFF LAMP_LIVING_ON LAMP_LIVING_TOGGLE LAMP_LIVING_TEST?
to
2 :LAMP LAMP_LIVING_OFF LAMP_LIVING_ON LAMP_LIVING_TOGGLE LAMP_LIVING_TEST?
What about improving the code so
2 :LAMP LAMP_LIVING
would create in the Forth dictionary
LAMP_LIVING_OFF LAMP_LIVING_ON LAMP_LIVING_TOGGLE LAMP_LIVING_TEST?
This is not very difficult. You need to know in detail how (your) Forth creates new words.
CREATE (or <BUILD) gets the new word that needs to come in the dictionary from the terminal input buffer (TIB). Adapt this so you start each time again at the same word LAMP_LIVING and add the _OFF _ON _TOGGLE _TEST?
You need to DIG in your system to check how this is implemented. (Perhaps you have the word DIG to check Forth words).
So modify Forths so not the next word from the terminal input buffer is fetched (and eaten) but a word is defined including _OFF _ON _TOGGLE or _TEST?

Unfortunate i learned that in eForth there is no CREATE DOES>.  However i expect some of the ideas presented still can be used. For example modifying the defining words so not only the word comes in the dictionary, also with suffixes appended as _OFF _ON _TOGGLE _TEST?

Geen opmerkingen: