Skip to main content

Not what Man takes, will profane 
Him, but what leaves Him.
Jesus Nazarenus (0-33 a. C.)

                        STRENGTH IN FORTH
                             part 6             

                A CLOSER LOOK AT FORTH'S DIGESTION (1)

I haven't  the slightest idea how and when this course  will  end. 
One day I started typing and it didn't stop until now.  Just luck! 
I've  been  just lucky,  putting in things in part  two,  which  I 
could  put out in part four.  It's like buying  music  today,  for 
which to play I'll find the right instrument next week.  Or buying 
cattle  without having a farm and - lucky - three days later  your 
old but very rich American uncle perishes and you are the one  and 
only  inheritor of his farm and fortune !  It's just that kind  of 
luck,  that is always with me.  My misfortune is not having a rich 
uncle.  So  I'll   continue this story,  happy-go-lucky as  I  did 

before.  Putting in words, which I will have put out at some later 
date,  as if I really had  thought of it. Let's see what we can do 
with input and output in FORTH, its digestion.

SHOWING SOME CHARACTER

We've already met some output operations:  {.},  {."} and  {EMIT}, 
which  display a number,  a literal character string and a  ascii-
code  respectively.  And  all input so far has used  the  keyboard 
interpreter.  So  you have some experience with input and  output. 
Let's start from the very beginning with character input. 
A  single character may be input by the use of  {KEY}.  This  word 
waits for a key to be pressed.  If that event has taken place,  it 
leaves  the  ascii-code  of the corresponding  key  TOS.  In  many 
computer  applications the user is expected to hit a key in  order 
to start some subprogramm: HIT ANY KEY TO CONTINUE has become very 
famous for that reason.  In FORTH the following sequence will wait 
for any key to be pressed:  :  STARTGAME   KEY DROP ;  . Of course 
you  can use the ascii-code of a series of keys to cause  branches 
to  separate subprogramms.  You could do that with the use of  the 
control structures of part 5.  Like this for  instance:  :  TWIGGY  
KEY  DUP 65 = IF (SUB1) ELSE DUP 66 = IF (SUB2) ELSE DUP 67  =  IF 
(SUB3)  THEN THEN THEN DROP ;.  ( (SUB1),  (SUB2) and  (SUB3)  are 
dummies !!). Now there are better ways to implement this  multiple 
branching.  In  FORTH there is a whole family of multiple  branche 
control  structures.  This  family is known by the name  of  CASE-
statement.  In fact there are two families,  both closely related. 
The first is the POSITIONAL-CASE-statement family,  the second the 
KEY-CASE-statement  family.  Here and now I will discuss the  last 
one. The KEY-CASE-statement mostly takes after this form:
: TWIGGY   KEY 
           CASE
           DUP 65 OF (SUB1) ENDOF
           DUP 66 OF (SUB2) ENDOF
           DUP 67 OF (SUB3) ENDOF
           DUP 68 OF (SUB4) ENDOF
           ENDCASE
           DROP
;
First   a  value is supplied by {KEY}.  Then that value is  tested 
against a sequence of explicit values.   If the testing gives TRUE 
the words after {OF} are executed and the following test is  made. 

The  same  value  may  be used several  times  in  that  sequence, 
although there are no logical or programming needs to do  so.  The 
values need not to be in numerical order. If none of the following 
tests  gives TRUE,  programm-control goes to {ENDCASE} and  hereby 
the  case  ends.  For  the testing I used  a  {DUP}ed  value,  the 
original  value  is dropped after the case ended.  Mind  that  the 
testing-value need not be necessarily provided by the word  {KEY}. 
Any  method of generating such a value is merrily  excepted,  even 
random  values.  When  we  will discuss  the  subject  of  forward 
references, I'll introduce a positional-CASE-statement.

EXPECTING ...

The word {EXPECT} is also related to the input of characters.  You 
can use {EXPECT} to specify the input of characters to your  exact 
need. The word {EXPECT} itself stops execution and waits for input 
from your terminal. {EXPECT} really expects something from you. It 
always expects a carriage return.  (You needn't specify that.) And
it  expects a given number of keystrokes,  whichever comes  first. 

Furthermore  it  expects an address.  The incoming  text  will  be 
stored in memory beginning at the address supplied.  The  Standard 
also requires the word {QUERY}. This word will accept any sequence 
of  characters  typed on your keyboard.  There is a  limit  of  80 
characters.   Pressing  RETURN  also  indicates  the  end  of  the 
sequence.  A  special  region of memory is allocated  for  storing 
these input characters temporarely. This region is called Terminal
Input Buffer, {TIB}. The  last character of every input will be at 
least  one  zero.  More zeroes are provided by  FORTH,  until  all 
"places"  in the {TIB} are filled.  Text may be  transported  from 
{TIB}  to  elsewhere in memory.  Now {QUERY} can be  described  by 
means of {EXPECT}. Here it is: : QUERY   SP0 @ 80 EXPECT   ;. It's 
worth analysing this definition.  At first the appearance of {SP0} 
may seem very strange. Of course it isn't. As you will undoubtedly 
remember  the  beginning address of the compu-stack is  stored  at 
{SP0}.  Secondly you were told,  that the stack grows downward  in 
memory.  You  were NOT told what was on the upside of  the  {SP0}-
address.  Well,  it is {TIB}.  One pointer suffices to control two 
memory-regions.  Going down from the address stored at {SP0},  you 
enter the compu-stack,  going up will bring you into the  terminal 
input  buffer.  So {SP0} {@} supplies {EXPECT} with  the  expected 
address,  the address of {TIB}.  The number following the  address 
sets the limit of characters which may be entered (80) and is used 
by  {EXPECT},  as  this  word consists of a  number  of  words  as 
keyboard  scanning  and character  transferring  primitives.  Most 
FORTH-programmers prefer {QUERY} to {EXPECT},  because {QUERY} can 
be used in relation with the following word:  {WORD}.  Furthermore 
you should't presume,  that the definition of {QUERY} given above, 
is the exact description of the word.  It's not that  simple.  The 
definition  given above only served to explain the main action  of 
{EXPECT}. At the end of this section, you'll find some examples of 
the use of {EXPECT},  {QUERY} and the words to follow.   The  word 
most frequently used for transferring characters is the only  word 
which  itself  is  called  {WORD}.  The  buffer  to  which  {WORD} 
transfers  characterstrings  is the  Wordbuffer.  In  most  FORTH-
systems the buffer used by {WORD} starts at {HERE}.  {HERE} is NOT 
a fixed bufferaddress as is the address stored at {SP0}.  Entering 
{HERE}   will place an address on the stack.  That address is  the 
first free memoryspace above the dictionary. New words are entered 
...yes just HERE..!!  {HERE} fetches the address of the Dictionary 
Pointer,  {DP}.  {HERE} is simply defined as :  HERE   DP @ ;.  As 
{DP}  changes each time words are entered in or deleted  from  the 
dictionary,  the  starting-address of the Wordbuffer will  equally 
change.  From the name of {WORD} you could tell its  function.  It 
scans  the  terminal input buffer,  reaching out for a  so  called 
"delimiter",  a signal by which it recognises, that you - the user 
- (or anyone else)  has indicated the end of a  charactersequence, 
such as a word. A delimiter is the ascii-code of a character, e.g. 
32 {WORD} tells FORTH to take a space for a delimiter. When {WORD} 
has  accomplished  its  task,  it has transferred a  word  to  its 
buffer, has counted how many characters that word consists of, has 
placed that count in the first byte of its buffer and has left the 
address  of its buffer on the stack.  Keep in mind that the  count 
doesn't count itself,  but only the characters of the word. {WORD} 
ignores   the  occurence  of  leading  delimiters.   The   leading 
delimiters   are  ignored  until  {WORD}  encounters   any   other 
character. Here are two strings in ascii-code: 
String A: 32 32 32 32 32 81 74 81 80  -- 5 spaces PIPO .
String B: 81 74 81 80                 -- 0 spaces PIPO .
Now  {WORD} will handle both strings in exactly the same  way.  In 
its  buffer both strings are placed as 4 81 74 81 80 0.  Note  the 
count  (4)  and the terminating zero.  Such a string is  called  a 
packed string.

PERVERSE DEMANDS

Three  words  are very close related to {WORD}.  Their  names  are 
almost  selfexplanatory, {-TRAILING}, {COUNT} and {TYPE}.
Before we have a look at {-TRAILING}, {COUNT} and {TYPE},  we will 
demonstrate a further feature  of {WORD}.  To  do so,  you have to
enter the  following  definition:
: PIPOPRINT QUERY 32 WORD  COUNT -TRAILING TYPE ;.
My experience with {WORD} is,  that one can't trust {WORD} by  its 
word.  I  used  {WORD}  on four different computers  and  on  each 
computer  {WORD}  has  had pecularities  of  its  own.  The  above 
definition  of {PIPOPRINT} will work on a BBC-B-computer or  Acorn 
ELECTRON,  but not with Wycove-FORTH on the good-old TI 99/4a,  as 
it won't on my ST with at least one FORTH.  With that FORTH I have 
to erase {QUERY} in the definition of {PIPOPRINT},  to achieve the 
same effect.  To execute {PIPOPRINT} you have to enter {PIPOPRINT}
OK  and then type  DEMOSTRING   and press ENTER.  FORTH will  type  
back DEMOSTRING  OK.  We used {WORD} inside a definition.  Now  we 
will use it directly from the keyboard as QUERY 32 WORD COUNT TYPE 
and see what happens. Not much good, isn't it ? It is important to 
realise  that  the keyboard interpreter itself  uses  {WORD}.  All 
keyboard input is transferred - word by word - to the  wordbuffer, 
overwriting the previous contents.  So you better use {WORD}  from 
within  a  definition.   Now,   what  happened  when  we  executed 
{PIPOPRINT}  ?  The  word {QUERY} stops execution  and  waits  for 
input. After the characterinput {WORD} checks the input buffer for 
a  ascii-code  32  ( a blank ) and then  transfers  the  foregoing 
characters  to  {HERE},as  described  above.   The  blanks   which 
antecipate  the first non-blank character are neglected by {WORD}. 
When {WORD} reaches {HERE}, it inserts the count in the first free 
byte,  places the string in the following free memory and puts the 
address of the string - in fact the address of the count-byte - on
the stack. Then {COUNT} finds the address - the count-byte address 
- ,  fetches the contents of that byte,  increments the address  - 
which  is  of  course the address of the first  character  of  the 
string  we put there - ,  puts the address onto the stack and  the 
count on top of it.  Now {TYPE} comes in action.  We could  define 
{TYPE} in high-level FORTH as:  :  TYP  0 DO DUP I + C@ EMIT  LOOP 
DROP ;.  By now you should be able to read that last definition of 
{TYP},  as all words used have been explained earlier. It's fun to 
modify {TYP},  so you can shape the character-output to your  most 
perverse demands.  An example:  :  TYP  0 DO DUP I + C@ EMIT SPACE 
LOOP DROP ;.  The output is now intermitted with  spaces.  Another 
definition of {TYP}, with some improvement and some tricky output, 
could be: : TYP   DUP 0 > IF 0 DO DUP I + C@ CR I SPACES EMIT LOOP 
THEN ;.  The main difference with the first {TYP} above is that  a 
test  is made to see if there IS a string to type  i.  e.  if  the 
count-byte is >0.  Otherwise there isn't anything to type.

MOVING AROUND

So  far we have been using existing buffers for  storing  strings. 
You can create your own buffers for that purpose.  Before  telling 
you  how  to  DO so,   you should be  told how  to  USE  your  own 
buffers. In other words: how does FORTH manage to transfer strings 
from one region to another. Very simple of course !! With the word 
{CMOVE}  for  instance  (Character MOVE).  This  word  transfers a 
specified  number of characters from address1 to  address2.  There 
are  some pitfalls however.  At first:  no problem arises  if  the 
destination address is less then the source address.  But there is 
a  difficulty,  if the destination address is higher than that  of 
the  source and the two regions overlap.  Why ?  Because the  byte 
with  the lowest address is moved first and the transfer  proceeds 
in  the  order of increasing addresses.  Let's draw a  picture  to 
show,  what  I'm  trying to explain to you.  Suppose  we  have  to 
transfer 7 characters.  The first character is at addresss 10  and 
the  last  one  at  address  16.  Furthermore,  suppose  that  the 
destination  address  of the first character  is  13.  This  would 
happen: ADDR10  ADDR13 7 {CMOVE}.

 10  11  12  13  14  15  16     13  14  15  16  17  18  19
 ___________________________    ___________________________
| S | T | - | N | E | W | S |  | N | E | W | S |   |   |   |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                                ___________________________
First move: S from 10 to 13.   | S | E | W | S |   |   |   |
                                ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The  result is,  that the 'N' at address 13 is overwritten by  the 
'S'  moved  there from address 10.   
                                ___________________________
Second move: T from 11 to 14.  | S | T | W | S |   |   |   |
                                ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                                ___________________________
Third move: -  from 12 to 15.  | S | T | - | S |   |   |   |
                                ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Fourth move: By that time the situation looks like beneath.

 10  11  12  13  14  15  16     13  14  15  16  17  18  19
 ___________________________    ___________________________
| S | T | - | S | T | - | S |  | S | T | - | S |   |   |   |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The fourth move will move the 'S' from 13 to 16,  and successively 
the fifth move will place the 'T' from 14 at 17, and the next move 
puts the '-' from 15 at 18.  The last move brings the 'S' from  16 
to 19. The result of all that wiggling is rubbish.

 10   11  12  13  14  15  16    13  14  15  16  17  18  19
 ___________________________    ___________________________
| S | T | - | S | T | - | S |  | S | T | - | S | T | - | S |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Now you should try a {CMOVE}, given the following situation.
ADDR13 ADDR10 7 {CMOVE}. 

 10  11  12  13  14  15  16     13  14  15  16  17  18  19
 ___________________________    ___________________________
|   |   |   | S | T | - | N |  | S | T | - | N | E | W | S |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The result should be

 10   11  12  13  14  15  16    13  14  15  16  17  18  19
 ___________________________    ___________________________
| S | T | - | N | E | W | S |  | N | E | W | S | E | W | S |
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
MORE MOVES

This was perfectly right and it would be nice,  if  {CMOVE}  would 
always perform like that,  but it won't.  To avoid the  unrequired 
effects as depicted above, we could define a {<CMOVE}. Its overall 
action is identical to that of {CMOVE}.  Except that the byte with 
the  highest address is moved first and the transfer proceeeds  in 
the order of decreasing addresses. A {<CMOVE} may be included with 
your system, but if not, here is a high-level definition.
: <CMOVE  1- 0 SWAP DO ( from/to/count --- )
                      OVER I + C@
                      OVER I + C!
                 -1 +LOOP
;

Now we have two versions of a character-moving word.  The decision 
which  of the two is to be used,  has to be made by you each  time 
you have to transfer bytes.  And if you aren't going to like that, 
you could define a word that makes that decision for you.  Here it 
is: : <MOVE>  ( from/to/count ---  )
        >R 2DUP  R> -ROT - IF <CMOVE ELSE CMOVE THEN  ;
As you may well notice,  you can't define {<MOVE>} without  having 
defined {<CMOVE}.  Before we can give some examples of the use  of 
all the words above,  we have to explain still some  words,  which 
may  be  usefull  when  you are  going  to  play  charactermoving. 
Sometimes it may be very smart to clear a buffer,  before  storing 
anything in it.  {FILL} is the perfect word to do so.  But first I 
will show you, how to create a buffer, as I promised  you earlier. 
Here we go.  CREATE  MYBUFF  20 ALLOT OK. I used the word {CREATE} 
to  place  the  word  {MYBUFF} into  the  dictionary.  That  is: a 
namefield,containing the name MYBUFF,  a linkfield,  a  codefield, 
which   will  be  a  pointer  to  the  code   of   {VARIABLE},   a 
parameterfield containing nothing. The sequence 20 ALLOT allocates 
the first 20 bytes of free memory. Assuming that {MYBUFF}   is the 
last  word in the dictionary,  executing {MYBUFF} will  leave  the 
address of the first free byte in the dictionary, the same address 
as  returned by {HERE}.  When we typed 20 ALLOT,  we expanded  the 
parameterfield   to   hold   more   items.   For   instance,   the 
parameterfield could hold 20 characters,or 5 32-bits numbers, now.
To  store a single character in the first byte of the  buffer,  65 
{MYBUFF} {!} will do.  To store a character into the 12th byte  of 
the buffer, you will have to count that byte with {MYBUFF} 12 {+}. 
So  112 {MYBUFF} 12 {+} {!} will place the character  with  ascii-
code  112  in  the 12th byte of  the  buffer  {MYBUFF}.  When  you 
reserved  space with 20 {ALLOT},  that space was not  initialised. 
Now here comes {FILL}. It goes like this: 
{MYBUFF}  20  32  {FILL} OK.  The addresses from  {MYBUFF}  up  to 
{MYBUFF}  20 {+} are filled with ascii-code 32 (a blank).  If  you 
like  you  could fill the buffer with any  character.  Earlier  we 
tried to avoid the overlaying action of {CMOVE}.  Now {FILL}  just 
takes advantage of that feature. Look at its definition.
:  FILL   SWAP >R OVER C!  DUP 1+ R> 1- CMOVE ( addr n b --- )  ;. 
This definition is a beauty,  using simple words,  to gain a  fast 
effective action.  Two common ways of clearing a certain amount of 
memory  are 1) filling the region with ascii-code 32 - a space  -, 
or  2) filling the region with ascii-code 30 - a  null.  For  each 
method FORTH has a seperate word.  The first method uses the  word 
{BLANKS},  the second {ERASE}.  Instead of {MYBUFF} 20 32  {FILL}, 
you  can  use {MYBUFF} 20 {ERASE} or  {MYBUFF}  20  {BLANKS}.  And 
instead of the number 32 you can use {BL},  which is a constant of 
the value 32,  the ascii-code for a space.  The bell rings for the 
second round,  the examples.  I explained,  how to create a buffer 
for  storing  a ascii-string. (Perhaps you already jumped  to  the 
conclusion,  that a buffer created in such a way can also serve as 
an (very simple) array.) Let's put all (most) words  together.  In 
many  a  computer-application  you  will  be  asked  some  oblique 
questions,  which  you'll  have to answer in order to  persue  the 
thing to come to action.  We are going to do that in FORTH and  we 
are going to keep things simple, only using words we met already.

NOT WATERPROOF

The  first thing to resolve is,  what should the computer do as  a 
reaction on me answering its questions ? Let's assume we are going 
to protect a piece of software against malicious intruders and the 
computer  will give further information of what to do  next,  when 
and only when a certain name,  address and place are typed at  the 
keyboard. So three questions are to be put. The application has to 
compare  the  input with its 'inside' information.  We  won't  use 
stringcomparising words,  as those words - if any in your system - 
may  differ as in action as in namegiving.  And I am not a  clair-
voyant. The solution to that problem is to extract some characters 
from  each  response  we get and compare those with  the  ones  we 
determined  previously.  We will best choose  some  characteristic 
ones  at  characteristic places.  We will discuss a  version  with 
{QUERY} and {WORD},  and another version using {EXPECT}. First the 
{QUERY}-version.
Let's  create  3 string-buffers.  The length will  depend  on  the 
actual input of your name, address and residence.

CREATE NAME 14 ALLOT   CREATE ADDRESS 13 ALLOT
CREATE PLACE 11 ALLOT

To be neat, we clear the buffers.

: CLEARALL  NAME 14 ERASE  ADDRESS 13 ERASE  PLACE 11 ERASE ;

Now we get ready for input.

: INPUTNAME CR ." Your name: " QUERY 32 WORD COUNT
            SWAP NAME CMOVE ;
: INPUTADDRESS CR ." Your address: " QUERY 32 WORD COUNT
               SWAP ADDRESS CMOVE ;
: INPUTPLACE CR ." Your residence: " QUERY 32 WORD COUNT 
             SWAP PLACE CMOVE ;
: INPUT  CLEARALL INPUTNAME INPUTADDRESS INPUTPLACE ;

The following definition checks 1 byte per buffer,  to see if  the 
typist is entitled to more information. You may change that byte.

: ?CHEATING  NAME    8 + C@ 74 =  ( --- t/f )
             ADDRESS 6 + C@ 65 = 
             PLACE   9 + C@ 68 = AND AND ;
: ALLRIGHT CR  ." You passed the check. Now type LOVE to read    
                today's fan-mail." ;
: YES  NOT ;

The grand finale is now to come.

: TEST  BEGIN INPUT ?CHEATING YES WHILE REPEAT ALLRIGHT ( -- ) ;

This  is not waterproof for sure .  But I've managed (using  words 
still to come) to develop quite a nice protecting application. You 
will hear from me on that subject later. Now the {EXPECT}-version, 
which will proof to be far more protecting against creep.
CREATE NAME 13 ALLOT
CREATE ADDRESS 12 ALLOT
CREATE PLACE 10 ALLOT
: WIPE NAME 13 ERASE ADDRESS 12 ERASE PLACE 10 ERASE ;
CREATE *BUF 80 ALLOT
: CLEAN *BUF 80 ERASE ;
: >*BUF  *BUF 80 EXPECT ;
: INPUT  WIPE
         CLEAN CR ." Your name:"      >*BUF *BUF NAME    13 CMOVE
         CLEAN CR ." Your address:"   >*BUF *BUF ADDRESS 12 CMOVE
         CLEAN CR ." Your residence:" >*BUF *BUF PLACE   10 CMOVE
;
: ?CHEATING    NAME    6 + C@ 74 =
               ADDRESS 4 + C@ 65 =
               PLACE   8 + C@ 68 = AND AND  ;
: ALLRIGHT  ." You passed the test. Now type LOVE and you will
               get what you want." 
;
: YES NOT ;
: TEST  BEGIN INPUT ?CHEATING YES WHILE REPEAT ALLRIGHT ;

That's all !!  For now we will rest a while. Next time we continue 
the story of input and output.  And it's getting time to tell  you 
the facts about Editor-life.
                                May Heaven protect you !!!

SUMMARY

From  now  on  a summary will consist of just a  glossary  of  the 
words.  The reason is that a file over more than  plusminus  32000 
bytes, can't be handled by ST NEWS.  Part three of this course had 
to be splitted for the same reason.

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|    WORD      |  STACKNOTATION    |         DESCRIPTION         |
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| CASE..OF     | Used as n CASE    | Multiple-branch structure.  |
| ENDOF        | n  OF (words)     | There is no limit in        |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|    WORD      |  STACKNOTATION    |         DESCRIPTION         |
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| ENDCASE      |    ENDOF          | branchingpoints.            |
|    WORD      |  STACKNOTATION    |         DESCRIPTION         |
|              | n1 OF (words)     |                             | 
|              |    ENDOF          |                             |
|              |    ENDCASE        |                             |
|    KEY       |  (  --- c )       | Waits for a key to be       |
|              |                   | pressed. The ascii-value of |
|              |                   | of the pressed key is put   |
|              |                   | TOS.                        |
|  EXPECT      | ( addr n --- )    | Addr is the address of the  |
|              |                   | inputbuffer to be used, n   |
|              |                   | the maximum number of chars |
|              |                   | which can be input. EXPECT  |
|              |                   | accepts input until a cr    |
|              |                   | occurs, or the maximum num- |
|              |                   | ber of chars is reached.    |
|   TIB        | ( --- addr )      | Supplies the address of the |
|              |                   | terminal input buffer.      |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|    WORD      |  STACKNOTATION    |         DESCRIPTION         |
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|  QUERY       | ( ----   )        | Inputs up to 80 chars termi-|
|              |                   | nated by a cr from the key- |
|              |                   | board. The text is stored in|
|              |                   | the terminal input buffer.  |
|   WORD       | ( c --- addr )    | Accepts chars from the input|
|              |                   | stream until a non-zero de- |
|              |                   | limiting char c is encount- |
|              |                   | ered, or the inputstream is |
|              |                   | exhausted. Leading delimit- |
|              |                   | ers are ignored. The chars  |
|              |                   | are stored as a packed      |
|              |                   | string, beginning at HERE.  |
|              |                   | The address of the countbyte|
|              |                   | is left on the stack.       |
|   HERE       |  ( --- addr )     | Supplies the address of the |
|              |                   | first free dictionaryspace. |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|    WORD      |  STACKNOTATION    |         DESCRIPTION         |
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|   DP         |  ( --- addr )     | The dictionarypointer. The  |
|              |                   | contents of the address addr|
|              |                   | points to the first free    |
|              |                   | byte at the top of the dic- |
|              |                   | tionary.                    |
|   COUNT      |( addr1---addr2/n )| Leaves the address addr2 and|
|              |                   | bytecount n of a textstring |
|              |                   | starting at address addr1+ 1|
|  -TRAILING   |(addr/n1---addr/n2)| Changes the char-count n1 of|
|              |                   | the textstring at address   |
|              |                   | addr so as to exclude all   |
|              |                   | trailing blanks, the result |
|              |                   | leaving as n2.              |
|   TYPE       |( addr/count --- ) | Transmits 'count' chars of a|
|              |                   | string at addr to the output|
|              |                   | device.                     |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|    WORD      |  STACKNOTATION    |         DESCRIPTION         |
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|   CMOVE      |(from/to/count---) | Moves 'count' bytes, start- |
|              |                   | ing at from to a memoryblock|
|              |                   | starting at address to.     |
|   FILL       |(addr/n/b ---  )   | Fills n bytes of memory     |
|              |                   | starting at addr with the   |
|              |                   | value b.                    |
|   ERASE      | ( addr/n ---  )   | Sets n bytes of memory star-|
|              |                   | ting at address addr to     |
|              |                   | contain zeroes.             |
|   BLANKS     | ( addr/n ---  )   | Fills n bytes of memory     |
|              |                   | starting at address addr    |
|              |                   | with blanks.                |
|   BL         | ( --- c  )        | A constant which leaves the |
|              |                   | ascii-code for a blank. (32)|
 ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

EXTRAS

Bulletin Boards seldom show the subject FORTH. The other day I got 
the telephone number of a bulletin board with a FORTH-corner.  The 
FORTH used is UNIX-FORTH. (Version 1.0, San Leandro Computer Club) 
This  FORTH contains about 1100 words and is  nearly  Standard-83. 
This board is operated by the HAARLEM GG in The  Netherlands.  The 
corner contains among other goodies a database,  written in FORTH. 
The telephone number is 023-35 99 69. The BBS is on-line,every day 
from 19.00 - 07.00 hr. It uses a 20 Mb harddisk. Give it a try.

EXERCISES

1. I gave you a definition of {QUERY}.  Replace the sequence {SP0} 
   {@} by just one word.
2. In part  5, I introduced a  {SIZE}-word.  Using  a  'KEY-CASE'-
   construction,  you should rewrite it.   The input should  be  n 
   {SIZE}. Create a variable to store this n-value.
3. Rewrite the definition of {<CMOVE} to perform a 32-bit transfer
   in memory.  So ADDR10 ADDR40 10 {<CMOVE} has to transmit 10 32-
   bit values.

4. The  intelligent  {<MOVE>} can be defined in some  other  way. 
   Which way ? Now you has to be intelligent !!
5. Why  is  the  {EXPECT}-version  of  {TEST}   a  better  way  of 
   protection than the {QUERY} one ?
6. Try  to  change the {EXPECT}-version  of  {TEST}.   The  change 
   should avoid the use of the temporary buffer {*BUF}.

SOLUTIONS

1.  Taking  the  crooked definition of  {SIZE}  for  granted,  the 
superfluous word is {?EL}.  To change is {?L} like this: : ?L  DUP 
250 < IF ." Large " ELSE ." Extra large " THEN ;.
2.   The original {SIZE} checkes numbers less than 250 and  bigger 
than  250,  but  not a tomato of exact 250 gr.  The  changed  {?L} 
checkes if a number is less than 250,  so all numbers bigger  than 
249 are handed to the {ELSE}-clause ( 250 included ).

3. The definition should look like this.
   : SIZE-3  DUP 25  < IF ." Rejected "    ELSE
             DUP 50  < IF ." Extra small " ELSE
             DUP 125 < IF ." Small       " ELSE
             DUP 200 < IF ." Medium      " ELSE
             DUP 250 < IF ." Large       " ELSE
                          ." Extra large "
             THEN THEN THEN THEN THEN DROP
   ;
4.  First  we  define some variables.
VARIABLE REJ  VARIABLE ES  VARIABLE SM
VARIABLE MED  VARIABLE LA  VARIABLE EL
0 REJ ! 0 ES ! 0 SM ! 0 MED ! 0 LA ! 0 EL ! 
 : (SIZE-4)  DUP 25  < IF REJ +! ELSE
  ( n --- )  DUP 50  < IF ES  +! ELSE
             DUP 125 < IF SM  +! ELSE
             DUP 200 < IF MED +! ELSE
             DUP 250 < IF LA  +! ELSE EL +!
             THEN THEN THEN THEN THEN 
;

: SIZE-4 ( Here insert a screen cleaning word )
         (SIZE-4)    CR ." Rejected     "  REJ @ . ." gr"
  ( n --- )          CR ." Extra small  "  ES  @ . ." gr"
                     CR ." Small        "  SM  @ . ." gr"
                     CR ." Medium       "  MED @ . ." gr"
                     CR ." Large        "  LA  @ . ." gr"
                     CR ." Extra large  "  EL  @ . ." gr"
;
5.  To  solve  this  exercise we  could use  some  code  from  the 
preceding  one. The  variables can stay totally unchanged.
: (SIZE-5)  DUP 25  < IF 1 REJ +! ELSE
            DUP 50  < IF 1 ES  +! ELSE
            DUP 125 < IF 1 SM  +! ELSE
            DUP 200 < IF 1 MED +! ELSE
            DUP 250 < IF 1 LA  +! ELSE 1 EL +!       
            THEN THEN THEN THEN THEN
;

: SIZE-5  (SIZE-5) ( Here insert a screen cleaning word )
          CR ." Rejected     " REJ @ DUP . ." Tom"   0 * . ."gr"
          CR ." Extra small  " ES  @ DUP . ." Tom"  25 * . ."gr"
          CR ." Small        " SM  @ DUP . ." Tom"  50 * . ."gr"
          CR ." Medium       " MED @ DUP . ." Tom" 125 * . ."gr"
          CR ." Large        " LA  @ DUP . ." Tom" 200 * . ."gr"
          CR ." Extra large  " EL  @ DUP . ." Tom" 250 * . ."gr"
;
6.  After you would have declared and initialised the variables to 
zero,  you  should insert the {SIZE-5)-word into an endless  loop. 
This  would  do  the job:  :  TOMATOSORT  BEGIN  SIZE-5  AGAIN  ;, 
assuming the hardware would provide the number {SIZE-5} expects on 
the stack. And the screen-output could need some improvement.

Disclaimer
The text of the articles is identical to the originals like they appeared in old ST NEWS issues. Please take into consideration that the author(s) was (were) a lot younger and less responsible back then. So bad jokes, bad English, youthful arrogance, insults, bravura, over-crediting and tastelessness should be taken with at least a grain of salt. Any contact and/or payment information, as well as deadlines/release dates of any kind should be regarded as outdated. Due to the fact that these pages are not actually contained in an Atari executable here, references to scroll texts, featured demo screens and hidden articles may also be irrelevant.