Had I been present at the creation,
I would have given some useful hints
for the better ordering of the universe.
Alfonso the Wise
STRENGTH IN FORTH
part ten
A CREATIVE LANGUAGE
When I started writing this part of the FORTH-course of ST NEWS,
I didn't know yet, how the first issue of this magazine under the
wise , skillful and creative leadership of the new chief-editor,
would look like. I don't know Stefan Posthuma at all. The only
two facts of his complexity I know of are his name and his black-
and-white drawingprogram Artist. So I could write 'wise, creative
and skillful'. A skillful and creative programmer he certainly
is, and wisdom he laid in the fact that he chose to make it a
P.D.- program. I recognise these characteristics as those of an
artist, as I am kind of an artist myself. I do some drawing and
painting now and then. Quite well I might say, and if Rembrandt,
Van Gogh and some other guys hadn't stolen most of my creative
and brilliant ideas, I would have been rather famous all over
this beautiful world now. In writing this course and in
programming my computer in FORTH, I've found a worthy substitute
for that lost empire. Now let's get serious and see what made
me saying, that FORTH is a creative language.
IN THE BEGINNING...
Well, it was in part nine that I promised you to tell you more
about a particular pair of words: {CREATE...DOES>}. The first
part of this mighty combination we have used many times without
much explanation; the second part is going to be a new element in
your ever expanding knowledge of FORTH. One aspect of FORTH is
its extensibility. In defining new words you take advantage of
this feature. Every new word you will enter in the dictionary
will extend FORTH's capabilities. The creation of new words is
made possible by FORTH's compiler. In generating code for a new
entry in the dictionary, the compiler always decides to which
type this entry will belong to. This decision is made by
looking at the defining word used. The most frequently used
defining word in FORTH is {:}. It produces a new dictionary-entry
with - in its code-field - a pointer to some code to handle the
addresses and data compiled in its parameterfield. {VARIABLE} and
{CONSTANT} are two more defining words, each with substantual
different code-field pointers. So {:}, {VARIABLE} and {CONSTANT}
will create different types of entries. This compiler now can be
extended !! By you, the user. Extending the compiler means adding
new defining words. And for that purpose {CREATE...DOES>} will
perform the right action. To avoid total distress, let's begin
our explanation of what is going on with {CREATE...DOES>} by the
very beginning of what you should already know......{CREATE}.
....THERE WAS DISTRESS
I've let you use {CREATE} many times. Remember that chapter
dealing with strings. We then took {CREATE} to set aside a region
of memory for use as a buffer, so we could store a string in it.
{CREATE} is a word that will produce a dictionary head, whose
code field contains the address of the machine code used by
{VARIABLE}. I must tell you, that this code leaves on the stack
the startaddress of the parameter area, when a dictionary head
formed by {CREATE} is later executed. Unlike {VARIABLE}, however,
no parameter space is allocated. So if we
{CREATE} MBUF OK
the dictionary entry will appear as shown in the following
diagram:
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| MBUF | <-- Name field
|_________________|
| Address of |
| previous word | <-- Link field
|_________________|
|¯¯¯¯¯¯¯¯| | Code field |
|Code of |<-- | pointer | <-- Code field
|VARIABLE| |_________________|
|________| | | Free | |
| | | | <-- Parameter field
V V
Executing {MBUF} will leave the address of the first free byte in
the dictionary, assuming that {MBUF} is the last word in the
dictionary. In this case it is the same address as returned by
{HERE}. We could now {ALLOT} some space in the dictionary for
storing some values in {MBUF}'s parameter area. Thus if, after
creating {MBUF}, we then type 8 {ALLOT} we shall reserve 8 bytes
of memory. And whenever we'll execute {MBUF}, the address of the
first of these bytes will be left on the stack. To store a byte-
value in the first byte, type
134 {MBUF} {C!} OK
To store a value into the second byte, you'll have to increase
the address of {MBUF} with one
231 {MBUF} {1+} {C!} OK
Fetching a value from a byte is easily done with the help of
{C@}. Some difficulties may arise, if you want to store and fetch
16- or 32-bit values in the parameter area of {MBUF}. To avoid
these difficulties you should add {EVEN}.
{CREATE} MBUF {EVEN} 8 {ALLOT} OK
This will ensure that it all begins at an even address. To store
a 16-bit value, you have to use {W!} and to fetch {W@}. The words
{!} and {@} will accomplish the fetching and storing for 32-bit
values. It is best, if you always treat these three pairs of
words in combination. You may {C@} a value that you stored with
{W!}, but you never can tell what the result may be; that's
unpredictable. If you will respect the combined use of the word
pairs for storing and fetching, you can use this method for a
word that can serve as some kind of variable. That means,
however, a lot of typing. Things should be made easier and
simpler. Some parts ago I gave you some alternative definitions
of {VARIABLE}. Here is another one in terms of {CREATE}.
: 2VAR CREATE , , ;
Now, what are the main differences between these two sequences of
code ( MBUF and 2VAR) in both of which appears {CREATE} ?
1. In the first sequence {CREATE} acts outside a colondefinition,
in the second one it appears inside a colondefinition.
2. In {2VAR} the dictionary-entry for {2VAR} is not made by
{CREATE}, but by {:}.
3. The action of the {CREATE}-word is executed in the definition
of {MBUF}, but it is compiled in {2VAR}. The action, thus, is
postponed till the moment {2VAR} is executed.
4. It is when {CREATE} executes, that it excepts the name of
the dictionary-entry it is to make. This means that the name
should not be included in the colondefinition using {CREATE}.
This name is required when executing {2VAR}; it is then used
to create a new entry in the dictionary by the compiled code
of {CREATE}.
The use of {2VAR} is
123 234 {2VAR} DISTRESS OK
With {2VAR} we created an entry for {DISTRESS} and twice {,} put
two values into the parameter field of {DISTRESS}.
890 789 {DISTRESS} {2!} OK
The above line of code serves to put two new values in the
parameter field of {DISTRESS}. The word {2@} fetches the two
values and pushes them onto the stack and twice {.} writes them
at your display in the following line
{DISTRESS} {2@} {.} {.} 890 789 OK
BE BORN TIRED
Now we have made two steps forward. The first one was analysing
the history of {CREATE} outside a colondefinition. The second one
was, when we looked at {CREATE} inside a colondefinition as in
{2VAR}. So far the most important thing to remember is that all
examples have used the fact that a word defined using {CREATE}
will leave the startaddress of the parameter area on the stack,
irrespective if defined outside or inside a colondefinition. What
is secondly most important now is the fact, that a name for a
{2VAR} doesn't need to be embedded in the definition of {2VAR}
itself. So you can use any name you like each time you executes
{2VAR} and that as many times as you like. In other words: with
{2VAR} you can DEFINE as many words of the type {2VAR} as
necessary. Now let's make the third step. Your programming
passion (and laziness) will urge for the possibility to avoid
additional entering of words, to place on the stack the two
values stored in {DISTRESS}. If it would please you that entering
{DISTRESS} would place these two values on the stack
automatically, then we have to create a new type of word. A word
with some quite welcome action. A word that will really dó
something. In fact that does it to me:
: 2VARI CREATE , , DOES> 2@ ; OK
The difference with {2VAR} is obvious {DOES> 2@}. This part is
not ment to do anything when you are going to use {2VARI} to
DEFINE a new type of word. You may use {2VARI} as follows
123 345 {2VARI} LAZY OK (a)
{LAZY} {.} {.} 123 345 OK (b)
In line (a) we have defined a {2VARI}-word {LAZY}. It is done
essentially in the same way as {DISTRESS} with {2VAR}. As if
there were no {DOES> 2@}-part, there isn't any additional action,
at least not perceptable with one's eye. But things are different
in line (b). To put on the stack the two values in {DISTRESS} we
had to {2@} them and twice {.} made them visable at the screen.
To put on the stack the two values in {LAZY}, it suffies to
execute {LAZY}. On execution the {DOES>}-part of {2VARI} makes
itself useful. It puts the two values on the stack, using {2@}.
Twice {.} remains necessary to display the values. We didn't
include {.} in the {DOES>}-part of {2VARI}. Perhaps it occured to
you, that any word defined with {2VARI} will demonstrate the
above features. And further, did you notice, that such {2VARI}-
words perform identically as words defined with {2CONSTANT} ?
What we were doing all the time, was - step by step - defining a
high level substitute for {2CONSTANT} !! But, you will ask me, is
it possible to include twice {.} in the {DOES>}-part ? Yes, that
is possible ! You are lazier than I thought you were !
: 2VARI CREATE , , DOES> 2@ . . ;
All words defined with this variation of {2VARI} will have the
property to display their 2 values, when they are executed.
CREATING NONSENSE
Here is another example of {CREATE...DOES>}
: 2VARI CREATE , , DOES> 2@ DO 42 EMIT LOOP ;
Before you are going to use this type of {2VARI}, try to imagine
what {2VARI} will work out in the {DOES>}-part. And of course the
number and the order of the parameters, which are needed at the
moment of defining a {2VARI}-word. The parameters are going to
serve as the loop index and limit. Here you are
20 0 {2VARI} STARDUST OK
Enter
{STARDUST} ******************** OK
Is this the result you expected from the code above ? If so, you
understand the power of {CREATE...DOES>}. Try to unravel the
following:
: 2VARI CREATE , , DOES> 2@
CREATE , DOES> @ 1+ DO CR I SPACES 65 MIN DUP EMIT
LOOP DROP ;
It is a rather complicated defining word {2VARI}, which in turn
will create another defining word, which at last will define
words to emit and skew characters with at least ASCII-value 65.
Its use is as follows:
6 0 {2VARI} LOOPDEF OK
Now {LOOPDEF} has become a defining word with the additional
action of putting two values on the stack, which can be used by
all words defined by {LOOPDEF}.
67 {LOOPDEF} ASCDEF OK
{ASCDEF}
C
C
C
C
C OK
Another one
10 0 {2VARI} 10LOOP OK 69 {10LOOP} SKEWEE OK
SKEWEE
E
E
E
E
E
E
E
E
E
E OK
This, of course, is all nonsense. But it demonstrates the
possibility to create hierarchical trees of defining words, as it
is allowed to sort of nest the {CREATE...DOES>} couple. This can
be seen in the latest version of the {2VARI}-word.
STRINGS AGAIN
Let's go on and step back to part nine. There we all fought some
frightful wars with strings, which we won of course. One
complaint, alas, lasted nevertheless. We stayed without an easy
method for creating strings. This easy method can be provided by
{CREATE...DOES>} in the following way:
: STRING CREATE 253 OVER < ABORT" String out of range !"
HERE SWAP DUP 2+ EVEN ALLOT SWAP C!
DOES> 1+ ;
The {DUP 2+ EVEN} sequence serves to avoid an odd address as the
start of the parameter area. In the examples above we were so
heavily concentrating on extending the {DOES>}-part, that you may
have thought that extending the {CREATE}-part was limited to
twice {,}. As you can see now, extending the latter is just
depending on the function of the word.
128 {STRING} MBUF OK
And here you have your empty string of 128 bytes. As you can see
from the code this length is stored in the first byte of the
string. It may be used to determine string-overflow. If in
defining a string the memory allocated for exceeds 253 bytes an
error-message will be given and the system will abort. The
{DOES>}-part puts on the stack the start-address + 1, thus
jumping over the string length byte. To store a string in {MBUF},
the following word will do
: STRING" ( Stringaddr - )
34 [COMPILE] WORD SWAP 2DUP 1- C@ SWAP C@ DUP
ROT > ABORT" Inputstream too long !" 1+ CMOVE ;
It goes like
{MBUF} {STRING"} NO APARTHEID !" OK
As a delimiter {"} is used - ASCII 34. Just forget [COMPILE].
I'll explain that word and some family-members at a later date.
The stringword {STRING"} checks if there is space enough at
{MBUF} to insert the string. Defining a word that will print the
string at {MBUF} at the screen, is all up to you. It can be done
very simple, don't look too high for the solution.
HURRAY, HURRAY, ARRAY, ARRAY
In the above section we didn't mention, that the sequence of
{CREATE MBUF 8 ALLOT} was a simple form for defining a byte-
array. But it is one , although a rather simple one ! I adore
simple facts (They are very relaxing too, see !), but simple and
elegant should unite. The elegance is put there with
{CREATE...DOES>}. Here is a slightly more elegant solution:
: BARRAY CREATE ALLOT DOES> + ;
A twelf element array for single-byte variables is made by
12 {BARRAY} ELEGANT OK
The elements of the {ELEGANT}-array are NOT set to some initial
value. To store a value at the fifth element the following code
will do the job:
100 5 {ELEGANT} {C!} OK
To fetch the contents of the fifth element simply enter
5 {ELEGANT} {C@} 100 OK
For me it's easy to tell how the storing and fetching were done,
but as I might hope, for you too ! The next step could be to
create an array to hold 2-byte values, or one to hold 4-byte
values. I'll give the 2-byte version:
: 2BARRAY CREATE 2* ALLOT DOES> 2* + ;
{2BARRAY} is to be treated in the same way as {BARRAY}.
12 {2BARRAY} ELEPHANT OK
And storing a value in {ELEPHANT} and fetching it successivily
31567 10 {ELEPHANT} {W!} OK 10 {ELEPHANT} {W@} OK
The tiny word {W!} is one of the many words that - as you will
discover - live in your system and that are not standard words.
Some FORTH-systems have been equiped with a 32-bit stack. In such
a system operators aligned for 32-bit values are so to speak
'standard'. Perhaps your system is a 32-bit FORTH, perhaps not.
But my favourite system has a 32-bit stack. In this FORTH the {!}
operator for instance handles values up to 32-bits. Sixteen-bit
values are possible, as are byte-values. These explicit 16-bit
values are handled with special operators. Their names are
derived from the operators {!}, {,} and {@} by means of the
prefix W, as are the byte-handlers with C. So you'll have to
detect what system you are living in and what adaptions there are
to make. As I pointed out before, it is absolutely necessary to
take good care to choose for the right combination of operators.
As you might have already noticed, there isn't any check made in
{2BARRAY} on the range of the index. It could be possible to use
an out-of-range index and so to overwrite other dictionary-
entries. So here is an alternative definition, which will check
the range, give an error-message if needed and that is a 4-byte
array defining word.
: 4BARRAY CREATE DUP , 1+ 4* ALLOT
DOES> 2DUP @ OVER < SWAP 0< OR
IF @ CR ." Range-error - max index = " .
." ; used index = " . QUIT
THEN 4 + SWAP 4* + ;
The use is similar to that of {2BARRAY}, except of course for the
operators to use. The extensive error-checking and reporting will
slow down execution speed. (See part nine on the subject of
error-checking).
It is very easy to develop a two-dimensional-array defining word.
In the following definition the elements of the array are 32-bits
variables and the array contents are not initialised.
: 2DIMARRAY CREATE DUP , * 4* ALLOT
DOES> ROT OVER @ * ROT + 4* + 4 + ;
With
10 6 {2DIMARRAY} ELASTIC OK
you have created a 10 by 6 array called {ELASTIC}. The array
indices may range from 0,0 to 9,5 inclusive. The value 1234567 is
stored in the 3,4 element with
1234567 3 4 {ELASTIC} {!} OK
Sometimes it may be necessary to create a data-structure, known
as 'table'. Such a structure has only to leave the starting
address of the data:
: BYTABLE CREATE ALLOT DOES> ;
And that's all !!
23 {BYTABLE} LIST OK
will create the table {LIST} with room for 23 byte-values. When
{LIST} is executed it will leave the starting address of the
data. To reach the fifth byte, enter
{LIST} 5 + OK
From the early examples it should be clear to you, how to store
and fetch values into and from the table. Another method of
creating a table to store values is the following. It only can be
used if you know the values at the time of creating the table and
if they are not likely to be changed anymore.
CREATE SINUS
0 , 175 , 349 , 523 , 698 , 872 , 1045 , 1219 , 1392 ,
1564 , 1736 , 1908 , 2079 , 2250 , 2419 , 2588 , 2756 , 2924 ,
3090 , 3256 , 3420 , 3584 , 3746 , 3907 , 4067 , 4226 , 4384 ,
4540 , 4695 , 4848 , 5000 , 5150 , 5299 , 5446 , 5592 , 5736 ,
5878 , 6018 , 6157 , 6293 , 6428 , 6561 , 6691 , 6820 , 6947 ,
7071 , 7193 , 7314 , 7431 , 7547 , 7660 , 7771 , 7880 , 7986 ,
8090 , 8192 , 8290 , 8387 , 8480 , 8572 , 8660 , 8746 , 8829 ,
8910 , 8988 , 9063 , 9135 , 9205 , 9272 , 9336 , 9397 , 9455 ,
9511 , 9563 , 9613 , 9659 , 9703 , 9744 , 9781 , 9816 , 9848 ,
9877 , 9903 , 9925 , 9945 , 9962 , 9976 , 9986 , 9994 , 9998 ,
10000 ,
This table reflects the values you'll need, if you're going to
develop an application to draw circles using only integers. I
shall use this table in a part of this course still to come.
Although, if you only knew some of the basic principles of sinus
and cosinus, you would be able to do the mathematical
calculations yourself. If you would like to try, remember that
you are drawing at a screen. So you'll have to deal with the
coordinates of the screen; the solution is not only a
mathematical one.
AN AVERAGE INTELLECTUAL
A far more advanced feature of arrays - rather than of
{CREATE...DOES>} - is the phenomenon of the string-arrays and of
course FORTH is quite capable of implementing such complex
structures well. Perhaps I'll discuss string-arrays at some later
date, as now it will not contribute to the elucidation of
{CREATE...DOES>}. For now I'll give you some intelligent array-
creature, one that will always be able to tell you the exact sum
and average of all its elements.
: IQARRAY CREATE DUP , 0 , 0 , 0 DO 0 ,
LOOP
DOES> DUP DUP @ SWAP 12 + OVER 0 SWAP
0 DO OVER @ + SWAP 4 + SWAP
LOOP
SWAP DROP DUP >R SWAP / OVER 4 + ! R>
OVER 8 + ! 4 + SWAP 4* + ;
First comes the use of this remarkable array:
20 {IQARRAY} EINSTEIN OK
It is not allowed to store values in the 0-th and the 1-st
element, because they are going to be used to hold of all the
elements the average and sum respectivily. Be nice and start
storing values not lower than in the fourth element. (As the
array starts counting at zero, the fourth element is found with
3 {EINSTEIN} ).
2000 3 {EINSTEIN} {!} OK
If you now
0 {EINSTEIN} {?} 100 OK and then 1 {EINSTEIN} {?} 2000 OK
and that's quite intelligent !
3000 8 {EINSTEIN} {!} OK 0 {EINSTEIN} {?} 250 OK
1 {EINSTEIN} {?} 5000 OK
In the {CREATE}-part of the definition the initialising of the
array is done by setting the first element to the number of
elements the user wants the array to hold, and the nexr two
elements to zero. The latter are going to hold the average and
the sum. The need for these three elements is independent of the
- varying - length of the array. After that space is allocated
and set to zero; notice, that if 20 elements are allocated, the
array will count three more elements. The {DOES>}-part in the
first place supplies the address of the start of the array
several times. Then fetches the number of array-elements and sets
off to the 'real' start of the array; the start of the 'user
area', say.( 12 + !!). With {OVER 0 SWAP} the parameters are set
for the coming {DO...LOOP}: (1) start user area , (2) 0 , (3)
number of elements , (4) 0 . Inside the {DO...LOOP} the contents
of the succesive elements are fetched and summed. After the loop
has completed all parameters are set in the right order to
calculate the average of all the elements and to store this
result and the result of the summation at their right place. The
sequence {4 + SWAP 4* +} calculates the address at which the
value that has been input last has to be stored. This means, that
at the moment this value has been stored, the average and sum are
not in accordance with the average and sum of the current values
stored in the array. It is when you enter 0 (or 1) {EINSTEIN}
{?}, that the average or sum are updated. I must admit, that I
need more days to create {EINSTEIN}, than God did for this whole
universe. Perhaps I should have asked His advice ?
Halleluiah !!!
SUMMARY
In the following table I have brought together all words
concerned with storing and fetching values.
|¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| WORD | STACKNOTATION | DESCRIPTION |
|_____________|_______________________|_________________________|
| C! | ( b addr --- ) |Stores byte-value b at |
| | |address addr. |
| C@ | ( addr --- b ) |Fetches byte-value b at |
| | |address addr. |
| C, | ( b --- ) |Compiles byte-value b in|
| | |the first free byte of |
| | |dictionary. |
| W! | ( w addr --- ) |Stores word-value w at |
| | |address addr. |
| W@ | ( addr --- w ) |Fetches word-value w at |
| | |address addr. |
| W, | ( w --- ) |Compiles word-value w in |
| | |the first free word in |
| | |dictionary. |
| ! | ( n addr --- ) |Stores n at address addr |
| @ | ( addr --- n ) |Fetches n from address |
| | |addr. |
| , | ( n --- ) |Compiles n in the first |
| | |free space in the dictio-|
| | |nary. |
| 2! | ( n1 n2 addr --- ) |Stores n2 at address addr|
| | |and n1 at addr+4. |
| 2@ | ( addr --- n1 n2 ) |Fetches n1 from address |
| | |addr and n2 from addr+4. |
| ;CODE | Used as |Same as DOES> except for |
| |: nnnn CREATE (words) |the assemblercode to fol-|
| | ;CODE (assemblercode)|low. |
| |;C | |
| ;C | Use see ;CODE |Terminates assemblercode |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
EXTRAS
Another possible form of {CREATE...DOES>} implies the use of
assemblercode in the {DOES>}-part. That is, the {DOES>}-word
itself is omitted and replaced with {;CODE}. The assemblercode is
terminated with {;C}, which is the machinecode counterpart of
{;}. As I have told you in the very beginning of this course I
don't know very much (= nothing) about 68000-assembler.
Nevertheless, here is an example:
: SOMETHING CREATE ,
;CODE SP )+ A0 LMOVE
A0 ) D0 LMOVE
D0 SP -) LMOVE C;
I don't know if you have any working knowledge of 68000-
assembler, but if so, you will probably have noticed that the
syntax of a FORTH-style assembler is a reversed one. The use of
{SOMETHING} is
12345 {SOMETHING} BEAUTIFUL OK
Executing {BEAUTIFUL} puts 12345 on top of the stack. The
{CONSTANT}-word will do exactly the same !! The code above has
been proven suitable for my 32-bit FORTH. I don't know how your
FORTH will react when he gets this code 'fot bread and butter'.
I'll try to explain the code. The {CREATE}-part is quite simple:
create a header in the dictionary and store a number in the first
four bytes of the parameter area. On executing a defined word -
here {BEAUTIFUL} - {;CODE} places the parameter field address on
the stack. Now {SP )+} takes a 32-bit value from the stack, which
in this case is the PFA (Parameter Field Address) and stores it
in address register A0 by {A0 LMOVE}. Then {A0 )} fetches the
contents of the PFA stored in A0 and moves them to data register
D0. At last the contents of D0 are moved to the top of the stack,
overwriting the PFA, which was still there.
SOLUTIONS TO PART NINE
1. As to include a possible space in the string. Otherwise the
following string wouldn't have been possible: a young girl.
2. The definition of {LEFT$} is: : LEFT$ 1 SWAP MID$ ;
3. The optimized version of {COMPARE} looks like:
: COMPARE >R 2DUP C@ SWAP C@ R@ >= SWAP R@ <= AND 0=
IF ABORT" Length-count error !" THEN
R> 1+ 1 DO 2DUP I + C@ SWAP I + C@ 2DUP <>
IF > IF 2DROP 1 LEAVE
ELSE 2DROP -1 LEAVE
THEN
THEN
LOOP DUP 1 > IF 2DROP THEN ;
4. The highest number you can possibly store in a byte is 255.
5. The two parts in the compare word are a) the error-checking
and b) the true comparison.
a) : ?ERRCOMP ( addr1 addr2 count --- | addr1 addr2 count )
>R 2DUP C@ SWAP C@ R@ >= SWAP R@ <= AND 0=
IF ABORT" Length-count error !" THEN R> ;
b) : (COMPARE) 1+ 1 DO 2DUP I + C@ SWAP I + C@ 2DUP <>
IF > IF 2DROP 1 LEAVE
ELSE 2DROP -1 LEAVE
THEN
THEN
LOOP DUP 1 > IF 2DROP THEN ;
: COMPARE ( addr1 addr2 count --- 0 | 1 | -1 )
?ERRCOMP (COMPARE) ;
6. Optimizing {MID$} is done by making it buffer independent and
leaving out the error-checking:
: MID$ SWAP >R SWAP 1 MAX 2DUP R> + SWAP C@ 1+ MIN >R OVER
C@ MIN R> OVER - >R + R> ;
The stacknotation: ( addr1 offset n --- addr2 count ). As you
can see, what is left on the stack is just fine for {TYPE}.
7. The answer is to compare the count-bytes of the 2 buffers.
: IN$ 2DUP C@ SWAP C@ < IF ABORT" String too long !" THEN ;
The rest of the definition remains unchanged and should
follow {THEN}.
8. The 'neutral' {GLUE}-word: ( addr1 addr2 --- )
: GLUE 2DUP >R >R OVER C@ OVER C@ 1+ ROT + ROT 1+ SWAP ROT
MOVE R> R> SWAP C@ OVER C@ + SWAP C! ;
This {GLUE} expects two addresses of stringbuffers; the
concatenated string is placed at addr2. With addr2 {COUNT
TYPE} you can print the string at your display.
9. Here are two adapted words:
: LEFT$ 1 -ROT MID$ ;
: RIGHT$ SWAP OVER C@ OVER - 1+ SWAP ROT MID$ ;
EXERCISES
1. If you studied {CREATE...DOES>} carefully, you should be able
now to define the word {CONSTANT}. For practising purposes
only, as this word is already in your system.
2. Define a word that will print out any string given its start-
address. Call it {.STRING}.
3. Create a defining word that will let you define byte-
variables. Call it {CVARIABLE}.
4. What's wrong with the output of {SKEWEE}?
5. Define {VARIABLE} in terms of {CREATE...DOES>} in two ways:
one version should initialise the parameter field, the other
should not.
6. Replace {SWAP * +} with some other code that will work out
the same. Think it {OVER} and {OVER} !
7. Insert some error-checking in the definition of {2DIMARRAY}.
8. Define a tableword that will leave its starting-address and
the number of items it contains.
9. Which word could I have used in stead of {,} in {SINUS}?
And for what reasons would that have been an 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.