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.