HI-RES ISSUE #1 / 1983-11 / PAGE 28
Hi-Res has asked Steve Maguire and Evan Rosen, creators of Val-forth, to talk to you about a language they believe heralds the future of programming.
We’ll caution you. The column may be too advanced for some, but in the interests of expanding the readerships’ knowledge of programming, Hi-Res presents the following. Your comments will be welcomed by the editors and by Rosen and Maguire.
Critics of Forth, ourselves included, are quick to point out that for all its talent, the language is not yet fully housebroken. For instance, Reverse Polish Notation (RPN) used by Forth can produce disturbing phrases like: X Y Z / + , which is not unlike saying, “The rat the cat I bought caught escaped.”
Both phrases will “work,” but they are easier on machines than humans. In Basic the first expression would be: X + Y/Z. (The second expression is already correct English.)
And the “troubles” with Forth don’t end there. Because of the way Forth currently uses its stack to pass parameters, programmers have to train themselves to picture a sequence before operations may occur. This can be serious nuisance.
Well, why Forth at all, then? We’ll give you two convincing reasons right here, though there are many more.
In Atari Basic, the program
100 FOR X = 1 TO 30000 : NEXT X
runs in just about 64 seconds. In Valforth the equivalent program
: TEST 30000 0 DO LOOP ;
runs in 3.2 seconds.
Another reason why you might like Forth is that it is extensible. This means that you can invent new kinds of data structures or other, stranger animals as your program. For example, if you wanted a lookup table structure in your program, you could put one in as simply as this:
: LOOKUP <BUILDS DOES> SWAP 1 - 2* + ;
No fooling. Now, the command LOOKUP is not the table itself. LOOKUP creates lookup tables roughly the same way that DIM in Basic defines arrays. To use LOOKUP to define a table of, say, days in each month, we would do:
LOOKUP MONTH-TO-DAYS 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ,
Then, by saying, for example, 6 MONTH-TO-DAYS, the number 30, for 30 days in June, would be left on the stack. Any number of other lookup tables could also be generated by LOOKUP. This kind of flexibility is hard to beat. As a matter of fact, we have used this same extensibility in-house to produce a very nice solution to the stack manipulation problem, without sacrificing speed. We hope to release it in a few months.
Which Forth? Virtually all Forths available for the Atari are based on the FIG (Forth Interest Group) model for 6502 by William Ragsdale, so there is relatively good code compatibility. There are currently about a half-dozen Forths for the 400/800’s. In approximate chronological order of appearance, they are: Coin-op Forth, a sort of in-house version at Atari; Sunnyvale Forth, a public-domain version from a users group there (?); QS Forth, from Quality Software; PNS Forth from Pink Noise Studios; APX Forth, from Atari; Ataforth, (sorry, not sure whose); and Valforth, from Valpar International. We wrote and use Valforth, though the programs appearing in this column will generally run without much prodding on any of these systems.
For the newcomer, the best introductory Forth book is Starting Forth, by Brodie, though for some reason the book is not based on the most common dialect of Forth, FIG-Forth. If you already have the book and would like the translation key to FIG-Forth, you can send a stamped, self-addressed envelope to: Starting Forth notes, Valpar International, 3801 E. 34th Street, Tucson, AZ 85716. We’ll send you a free copy of the key. (We’ll sell you the book, too, if you like.)
In the coming months we’ll give you what you seem to want most: novelty, simplicity, and utility. We also hope to hear from you by mail with your questions and comments.
Dr. Quatro will be fielding technical questions as space allows. Here’s the Doctor now…
Hello Users, my name is Quatro. Since you haven’t written me with any of your questions yet I’ll make some up. But next month I want real mail, do you hear? Good, Now, here is our first letter.
I hope you can settle a question for us. We’ve been trying to figure out whether Forth is compiled or interpreted. We hear about the “interpreter” in Forth, but since Forth is so much faster than most interpreted languages, something must be up. What?
Forth, like the hermit crab, needs to flex itself a little bit to fit into a new shell or microprocessor. But not too much. For Forth, the flexing is performed by an interpreter of a few dozens of bytes. I don’t know how the crabs do it. But any way, I myself prefer to think of Forth as the best assembler in the world, needing only to find the best processor in the world on which to run itself. And if I read properly the gleam in the eye of Mr. Charles Moore, who invented Forth in the first place, such a machine is coming. I think this device will do for hardware what Forth is doing for software. Take a heed, you venturesome capitalists! Until then, Chet, Forth is what you might call a little bit interpreted.
Address Letters to:The Amazing Doctor Quatro
c/o Hi-Res Magazine
933 Lee Rd., Suite 325
Orlando, FL 32810
HI-RES #2 / JANUARY 1984 / PAGE 70
Last issue we talked about why we like Forth, and why you might (or might not) enjoy it also. The hardest single task when first using Forth is just getting oriented. Questions like, “How do I start to attack a problem?”; “How is memory organized, and should I care?” and “How do I interface to the joysticks?” are common for novice programmers in any language. Moreover, since many new Forth users have first learned Basic, their “instincts” about how things are done can make learning Forth harder for them than for someone who has never programmed. In this issue, then, we’ll talk not only about the code itself, but about the thoughts behind it.
Have you ever drawn pictures on the Atari Memo Pad using the control characters? Memo Pad appears when you turn on the Atari with no Basic cartridge.
Try it. Randomly type several lines of characters using only CTRL-F and CTRL-G; throw in some occasional CTRL-T’s and spaces. What you get looks like a maze made of diagonal paths (the CTRL-F’s and G’s) surrounding some balls (the CTRL-T’s) and some open spaces. This has possibilities. Bring up your version of Forth and we’ll automate our doodling.
There are several ways to get a character, say “*,” onto the screen. First, we can send it by saying ." *" which sends an asterisk to the next cursor position. This is similar to a Basic Print statement. Or we can type 42 EMIT, which is the ASCII code for an asterisk. Both of these methods go through the Atari ROM operating system, which on the 400/800 is rather slow. So instead we’ll POKE directly into video memory to bypass the Operating System. But what do we POKE, and where?
In normal graphics modes the memory location 88 contains the address of the upper left corner of video memory. We’ll send all the characters in the ROM set to the screen in code order and pick out the ones we want by hand. Define a word, call it Showme, as follows:
: SHOWME 256 0 DO I 88 @ I + C! LOOP ;
Now position the cursor roughly halfway down the screen so it won’t interfere with the display. Type Showme and the characters appear. Showme is a Do Loop that starts at 0 and ends at 255. For each numeral between 0 and 255 the program stores the Loop index I into the next video memory location. Showme computes the next memory location by adding I to the address at 88, and the characters are displayed starting at the upper left of the display.
For our maze we want the blank character, the two slanted lines, and the ball. You’ll find the blank in the upper left corner of the display. Its screen code is zero. The two slants are toward the right end of the second line. Their codes are 70 and 71. The ball is 84. We’ll put these codes in a reference table.
We can construct a single-purpose lookup table for just these codes, but instead we’ll define a more general word called Byte-Lookup, which you can use for making byte lookup tables for any purpose. Since a Byte-Lookup employs Forth’s Complex Builds Does command, we’ll skip the explanation here. (If you’d like such an article, please write the editor!)
: BYTE-LOOKUP <BUILDS DOES> + C@ ; BYTE-LOOKUP BOARD-FIGS 0 C, 70 C, 71 C, 84 C, 0 C, 0 C, 0 C, 0 C, 70 C, 70 C, 70 C, 70 C, 71 C, 71 C, 71 C, 71 C,
In the above, we define Byte-Lookup and then use it to define our table called Board-Figs. “Board Figures” has 16 one-byte entries. The first four entries are the necessary screen codes. We’ll add four blanks, and four extra of each of the slants as well.
Board-Figs, or any other word defined using Byte-Lookup, will take one stack argument and return the value of the corresponding element in its table. You can put any number of elements into the table as long as you use the letter C, just as we have here.
Next, let’s write a word that will select an element from Board-Figs at random. This word will substitute for the random typing we did earlier in Memo Pad.
: PICK-BF 53770 C@ 15 AND BOARD-FIGS ;
Pretty simple. Pick-BF, for “pick a board figure,” delivers a random byte to the stack from the location 53770, which is a hardware random-byte generator. Pick-BF then ANDs this number with 15 to pick off the low four bits, creating a number from 0 to 15. The resulting number is used as a random index to the Board-Figs table which returns one of our characters. Now, we’re ready to make our first game board.
88 @ CONSTANT TOPLEFT TOPLEFT 960 + BOTRIGHT : MAKE-BOARD BOTRIGHT TOPLEFT DO PICK-BF I C! LOOP ;
We define the constant Topleft because it’s faster and takes less space than repeatedly saying 88 @. Botright is defined for similar reasons. (Botright actually points one byte past the end of video memory. This is more convenient.) Make-Board starts at the top left corner of memory and fills the entire screen with our four characters. The characters appear in proportion to their presence in the Board-Figs table. You can test the screen by typing Make-Board. The “OK” prompt that appears in the middle of the display wouldn’t be there if you were running the program.
Your board should look interesting. Roads go every which way, and balls or dots are tucked in here and there.
Let’s make the screen into a kind of solitaire maze-capture game, in which a joystick-controlled Wumpus runs along the lines and collects balls. We need some rules. Let’s say that if Wumpus is over a diagonal, then he can only move in the two directions pointed to by the diagonal. But if Wumpus is on a blank it can move up, down, left, right, but not diagonally. If you land Wumpus on a ball, you score a point. The ball will disappear, and then, since Wumpus is sitting on a blank, he can move again up, down, left or right.
What about edges? How about connecting the edges so that if Wumpus goes off the left or right, he reappears on the other side of the screen? Let’s do the same with the top and bottom.
Let’s put a one-minute time-limit on a round of play. After that the score will be displayed and the player will be asked if he or she wants to play again.
We’ve tested the game on Val-forth, APX Forth, and QS Forth. You Fig-Forth owners should be able to edit it onto screens, load it, and run it as well. To run the game, enter the name of the last word, DOT-MAZE.
Since most 400/800 Forths don’t allow you to interface with a joystick we’ll examine that first. You can use a number of approaches to the Stick construction. Stick should take one argument, which is the number of the stick you want to read, and return two arguments, x and y:
X should be second on the stack or the “side-to-side” displacement being read from the stick. Leave -1 for left, 0 for center and 1 for right. At the top of stack should be the y, or “up-and-down” displacement, with -1 for up, 0 for center and 1 for down. The reason that “up” is negative is that moving an image upward on the screen involves working from higher memory into lower memory.
The 400/800 joysticks are read into locations 632, 633, 634 and 635. Only the low four bits of each location have meaning, one for each direction. A bit is 0 if its corresponding switch is closed, and 1 if it is open. With some testing on a real stick, we find that the word Stick works properly when defined as on screen 13 in the listing.
Now, about Wumpus. Let’s keep him simple. His location in the maze will be tracked using inverse video. To invert the video set the high bit of whatever square he happens to be over. That way, you can still see which way the map under Wumpus is pointing. We’ll hold Wumpus’s position, its address in video memory, in a variable called WUMPUS.
How do we move this inverse video image through video memory without letting it run rampant through the rest of the computer? Look at screen 14, at the definition of MOVE-WUMPUS.
The variable DY contains the up/down value read from the joystick by another word, GET-STICK. DX contains the left/right value. Since there are 40 characters to a line, we multiply the DY value by 40, and then add the DX value to find the displacement. Because display memory is continuous, we can get away with this: As we go off the right side of the screen, we’ll reappear on the left side on the next row down ; if we go off the left edge, we come back a row higher on the right side.
But what if we go off the top or bottom? Then we’d be in forbidden memory. So, after we add 40 times DY and DX to WUMPUS, we check to see if we’ve gotten to Botright or higher. If so, we back up 960 characters. This puts us somewhere near the top left. Then, we check to see if we’ve gone lower in memory than TOPLEFT, and, if so, we move forward the 960 bytes instead, placing us near the bottom of the screen.
Finally, we want to move the screen image. When the program reaches the Endif on screen 14, line 9, the new address of Wumpus is on the stack. We ignore it for the moment, while WUMPUS @ 128 TOGGLE inverses the image of the old position by toggling its high bit. This returns the address to normal video. Line 10, DUP 128 TOGGLE, makes an extra copy of that new Wumpus address, inversing the video image there. Lastly, we store the remaining copy of the new Wumpus address into WUMPUS, moving him on screen.
Next issue, we’ll talk more about the code. Until then, try your hand at the maze. Duration changes the game’s length and the value of Stick-Delay changes the stick response by Stick-Delay. By changing any two of the zeros in the Board-Figs table to a 71 and a 72, you can make the maze much more difficult to thread.
Screen: 10 0 ( CONSTANTS AND VARIABLES ) 1 2 88 @ CONSTANT TOPLEFT 3 TOPLEFT 960 + CONSTANT BOTRIGHT 4 5 15 VARIABLE DURATION 6 25 VARIABLE STICK-DELAY 7 8 0 VARIABLE STICK-COUNT 9 0 VARIABLE HIGH-SCORE 10 0 VARIABLE POINTS 11 0 VARIABLE DX 12 0 VARIABLE DY 13 0 VARIABLE WUMPUS 14 : 2DUP OVER OVER ; 15 : NOT 0= ; --> Screen: 11 0 ( BYTE-LOOKUP BOARD-FIGS ) 1 2 : BYTE-LOOKUP 3 <BUILDS DOES> + C@ ; 4 5 BYTE-LOOKUP BOARD-FIGS 6 00 C, 70 C, 71 C, 84 C, 7 00 C, 00 C, 00 C, 00 C, 8 70 C, 70 C, 70 C, 70 C, 9 71 C, 71 C, 71 C, 71 C, 10 11 12 13 14 15 --> Screen: 12 0 ( PICK-BF MAKE-BOARD ) 1 2 : PICK-BF 3 53770 C@ 15 AND BOARD-FIGS ; 4 5 : MAKE-BOARD 6 BOTRIGHT TOPLEFT 7 DO PICK-BF I C! 8 LOOP ; 9 10 11 12 13 14 15 --> Screen: 13 0 ( STICK ) 1 2 : STICK ( N -- L/R U/D ) 3 632 + C@ >R R 8 AND 0= 4 IF 1 ( RIGHT ) 5 ELSE R 4 AND 0= 6 IF -1 ( LEFT ) 7 ELSE 0 8 ENDIF 9 ENDIF R 1 AND 0= 10 IF -1 ( UP ) 11 ELSE R 2 AND 0= 12 IF 1 ( DOWN ) 13 ELSE 0 14 ENDIF 15 ENDIF R> DROP ; --> Screen: 14 0 ( MOVE-WUMPUS DOT-CHECK 1 2 : MOVE-WUMPUS ( -- ) 3 DY @ 40 * DX @ + 4 WUMPUS @ + DUP 5 BOTRIGHT U< NOT 6 IF 960 - 7 ENDIF DUP TOPLEFT U< 8 IF 960 + 9 ENDIF WUMPUS @ 128 TOGGLE 10 DUP 128 TOGGLE WUMPUS ! ; 11 12 : DOT-CHECK ( -- ) 13 WUMPUS @ C@ 212 = ( 84 + 128 ) 14 IF 128 WUMPUS @ C! 1 POINTS +! 15 ENDIF ; --> Screen: 15 0 ( MOVE-OK? 1 2 : MOVE-OK? ( -- F ) 3 DX @ DY @ AND 4 IF DX @ DY @ = 5 WUMPUS @ C@ 199 = OVER AND 6 WUMPUS @ C@ 198 = ROT NOT AND 7 OR 8 ELSE WUMPUS @ C@ 128 = 9 ENDIF ; 10 11 12 13 14 15 --> Screen: 16 0 ( GET-STICK ) 1 2 : GET-STICK ( -- ) 3 0 STICK 2DUP DY ! DX ! 4 2DUP OR ROT ROT AND NOT AND 5 WUMPUS @ C@ 128 = AND 6 IF STICK-COUNT @ 0= NOT 7 IF -1 STICK-COUNT +! 8 0 DX ! 0 DY ! 9 ELSE STICK-DELAY @ 10 STICK-COUNT ! 11 ENDIF 12 ENDIF ; 13 14 15 --> Screen: 17 0 ( PICK-ONE INITIALIZE ) 1 2 : PICK-ONE ( N -- N ) 3 53770 C@ 256 * 4 53770 C@ + 5 U* SWAP DROP : 6 7 : INITIALIZE ( -- ) 8 0 STICK-COUNT ! 9 0 POINTS ! 10 960 PICK-ONE 11 TOPLEFT + DUP ' 12 WUMPUS ! 128 TOGGLE ; 13 14 15 --> Screen: 18 0 ( SHOW-SCORES FINISHED? ) 1 2 : SHOW-SCORES ( -- ) 3 HIGH-SCORE @ POINTS C- 4 MAX HIGH-SCORE ! 5 125 EMIT CR CR CR CR 6 3 SPACES 7 ." YOUR SCORE: " 8 POINTS @ 4 .R 9 CR CR 3 SPACES 10 ." HIGH SCORE: " 11 HIGH-SCORE @ 4 .R CR CR ; 12 13 : FINISHED? ( -- F ) 14 3 SPACES ." TRY AGAIN? (Y/N) " 15 KEY 223 AND 89 - NOT ; --> Screen: 19 0 ( DOT-MAZE ) 1 2 : DOT-MAZE ( -- ) 3 0 HIGH-SCORE ! 4 BEGIN MAKE-BOARD INITIALIZE 5 DURATION @ 0 6 DO 1000 0 7 DO DOT-CHECK 8 GET-STICK MOVE-OK? 9 IF MOVE-WUMPUS ENDIF 10 764 C@ 255 = NOT 11 IF LEAVE ENDIF 12 LOOP 13 LOOP SHOW-SCORES 14 FINISHED? 15 UNTIL 125 EMIT ;
HI-RES #3 / MARCH 1984 / PAGE 72
Let’s quickly finish up discussion of the code for last issue’s one-player “Dot-Maze” game and then move on to new topics.
On screen 16 last month was the word GET-STICK which stored the horizontal increment read from joystick 0 in the variable DX, and the vertical increment into DY. (Remember, the increments may be -1, 0, or 1.) Built into GET-STICK, however, is a delay, determined by the variable STICK-DELAY, to slow the Wumpus down when traveling over open spaces in the maze. An open-space move is determined by the logic on line 4, which looks for a stick move in one and only one of the four directions up, down, left, or right, and line 5 which checks to see that the variable WUMPUS is over an inverse-video blank, value 128. The Wumpus is not slowed when running along the diagonal roads.
The word MOVE-OK? on screen 15 picks up the values in the variables DX and DY which are set by GET-STICK and checks to see if they are legal. The 198 and 199 in the definition are inverse video screen codes for 70 and 71. That is, 70 + 128 = 198 and 71 + 128 - 199. If the move is valid, then MOVE-OK? leaves a true flag, which is a 1. Otherwise MOVE-OK? leaves a false flag, a 0.
On screen 17 was PICK-ONE, a handy, random-picking routine which is aided by hardware-supplied random bytes from location 53770, which always provides a fresh random byte. Given a positive number n, PICK-ONE will randomly pick a number from 0 to n-1 and leave it on the stack. PICK-ONE is used in INITIALIZE, also on screen 17, to pick an initial position for the WUMPUS. INITIALIZE also sets the variables STICK-COUNT (the stick delay counter) and POINTS (the score counter) to 0 to clear any data left from a previous game.
The words SHOW-SCORES and FINISHED? on screen 18 are fairly self-explanatory. Remember, if you don’t know what something does, like 125 EMIT, for example, you can always try it out. Lastly, the word DOT-MAZE on last issue’s screen 19 puts all the previous words together to run the game. Note the check on the value in the key input buffer, location 764, on line 10 of screen 19. This allows ending a game early by pressing any key. If the player wishes to start a new game, pressing the “Y” key will let him exit the present game and then go to a new one as the “Y” is picked up by the word FINISHED?. More on this key buffer in a moment.
This issue’s “Snow” game is somewhat representative of FORTH-style coding in general. At the end of the listing you’ll find the top word, SNOW, which calls some of the words below it, which in turn call words below them. The words are short, each composed of only about 30 or 40 other words at most, and all words have well-defined tasks which are indicated by the words’ names. This is roughly how a program should look. But how do you get it to look this way?
If the programmer already has a precise idea of what a given program has to do, then it is possible to program “top down.” That is, the programmer writes out the top word first. The top word will contain many undefined words, and these are defined next. These words will usually still contain undefined words, and the defining process continues down to the level until all words are defined in terms of the original set of words in the FORTH kernel. At this point the program is written and is ready for testing. This “top down” coding style is used with success in much commercial programming and in many languages, FORTH included.
In hobby hacking, however, “bottom up” or “inside out” coding also occurs. For instance, you may program random-number pickers or joystick-reading routines first, just because they happen to be more fun in themselves, or because you don’t really know where you’re going with the program yet. But eventually, when you have a solid plan in mind for your game (or whatever), make a final pass or two in “top down” style to clean things up. This will greatly simplify debugging, improve clarity and organization and will facilitate the making of changes later on.
For our main topic this issue we would like to define some words which simplify picking up keyboard input from within a running program. Restricting ourselves to single key inputs (rather than sequences or “string” inputs), we can still distinguish at least two different types of keyboard input: “Waiting for a key,” and “Looking for a key.”
This type of input may be implemented simply using the word KEY. KEY enters a loop and waits for any key to be pressed. When one is, KEY leaves the pressed key’s ASCII code (or its Atari-modified ASCII code, called ATASCII) on the stack. Very often, a yes-no answer is required from the keyboard and the program prints a question and indicates it is waiting for a “Y” or “N” to be pressed: This happens so often, in fact, that it makes sense to define a word called Y/N, which can be tacked on after the question string in the word that asks the question.
Y/N may be implemented in many ways. On screens 70 and 71 in this article are two definitions which also illustrate differences between “hobby” or “quick” programming, and more “polished” or “friendly” code. Type in and load the code and type EXAMPLE, which uses the friendly and sophisticated version of Y/N, and note that it will accept only “Y,” “y,” “N,” and “n” and won’t let go until it sees one of these.
KEY may also be used to wait for a key that represents a choice from a menu displayed on the screen. That is, suppose we had four options at some point in a game. The code might look like that on screens 72-75. If you edit this into your system and type 72 LOAD (after loading the previous example) and then GAME, you’ll get a sample menu.
Of course, this is a simple example, but the words RUN, FIGHT, SPELL and WAIT could have much more complex actions. They might look at some variables which would have information on “who” the enemy was and what “powers” it had, what kind of “power” the player had and how his “luck” was holding out, and so on.
So much for “wait for a key” input. But what if you are programming an action game where you don’t want to stop everything while the Dr. Quatro player ponders a move? This requires getting key input of a different sort.
Suppose we want to pick up keys “on the fly” rather than waiting for them. We can’t use KEY, which enters its own wait-loop. As it happens, in Atari 400’s and 800’s, location 764 holds a value, called a “key code,” which corresponds to the last key pressed and is not absorbed by the operating system. These key codes don’t seem to have any rhyme or reason to their numbering scheme, but still they can be used to identify key presses. Let’s define a word to tell us a key’s key code when we press the key.
: KEYCODE ( -- ) BEGIN 53279 C@ 7 = 0 = UNTIL 764 C@ . 255 764 C! ;
To find the key code of a key, type KEYCODE (Return), then press the key for which you want the keycode, and then press any one of the yellow console buttons, i.e., START, SELECT, or OPTION. (Location 53279 reads the console buttons, but in a slightly involved way.)
To illustrate one use of picking up keycodes on the fly, we’ll make up a simple game called “Snow.” The code is on screens 76 through 81, and may be loaded independently of the earlier code. For QS FORTH you must also first load its I/O package to get the word GR. into the system. For APX FORTH, first load the assembler and then the graphics package, to get GR. in. ValFORTH will load the code without extras. To run the game, type SNOW. The idea is to maneuver the bucket, a “U,” under the falling asterisk snowflakes without letting any flakes hit the ground. The bucket is moved by pressing the “F” and “J” keys, which is really the point of the illustration. If you’re using APX FORTH, then on successive runs of snow you’ll need to say XGR SNOW instead of just SNOW.
Space does not permit a long discussion of the code, but here are some highlights. SNOW starts with an initialization routine and then enters a loop which moves the snowflakes and the bucket, and then delays for a while to slow the process down to human speed. The loop doesn’t exit until the variable ?DONE becomes non-zero. This can only happen when a snowflake reaches the ground, which is determined in FALL?. Note the use of the random byte register, 53770, in FALL?, which is used to move a flake only one time in eight that FALL? is called.
And that’s about it for now, except for a quick word from Dr. Quatro. Doctor? Yes, yes, boys. The word is:
: BASE36( -- ) 36 BASE ! ;
This little treasure lets us see short strings as numbers. For supposing, we can say now:
BASE36 QUA TRO
and we will, get back “ok” because in base 36 “QUA” and “TRO” are numbers. You see it? And then we can also say:
TWA 467 LAX
which means Trans World Airliners flight 467, Los Angeles International airport. Yes, it does!
HI-RES #4 / MAY 1984 / PAGE 60
In the previous three articles, we have discussed what FORTH is and have given some examples of FORTH code. But the novice still has many questions stemming from FORTH’s esoteric way of doing things. For example, why doesn’t FORTH run under a Disk Operating System? This installment will answer this and many other questions still remaining about FORTH.
In fact, if the programmer understands why FORTH seems quirky, a better appreciation of the language is gained.
All of the popular FORTHs for the Atari are implementations of the public domain software provided by the FORTH Interest Group (fig). About six years ago, this group implemented a usable FORTH system on many different computers. This was made available to anyone for reproduction costs and put no restrictions on its use. The effort to increase the popularity of FORTH resulted in literally hundreds of implementations of fig-FORTH. This dialect by far has become the most popular.
And just for the record, a great deal of credit should be given to Bill Ragsdale for making his 6502 fig-FORTH available to the public. Without his implementation, none of the FORTHs for the Atari would exist. He too was a main driving force in making fig-FORTH available for as many processors as possible.
Ironically, fig-FORTH (which conforms to the 77-standard) has become so widely accepted all attempts to bring it up to the 79-standard have failed. Incidentally, the 83-standard has just been accepted and a new public domain FORTH soon will be available that conforms to this standard.
This is probably the question most frequently asked by new FORTH programmers. There are several reasons for not using a DOS. The best answer is the vast majority of programs do not need the capabilities provided by DOS and would waste 10K-plus by having it in memory. And for those programs which do need to save and retrieve data from disk, very few require the file manager DOS affords. Many can simply perform basic read/writes to the disk.
A second reason for going DOSless is transportability of data. For example, FORTH source code can be written on a TRS-80 — which unlike the Atari uses a standard disk format. And an IBM PC running FORTH can read the disk as though it was developed on that machine.
However, disks written using TRS-80 DOS cannot be read by the IBM DOS because the file structure is vastly different. Thus, because of software incompatibility in the DOS, file transfers between the two systems are practically impossible. This problem does not exist in FORTH.
Finally, it is very easy to implement FORTH on any new processor. Minimal disk routines need to be written to have FORTH up and going, whereas a good DOS could take more than a year to write.
FORTH source code is not stored in files, but on screens. By using the minimal disk routines available in FORTH, source code is entered directly onto disk sectors. A screen is generally 512 or 1024 bytes (four or eight sectors) in length. A special editor is used to write source code to these screens. To compile a FORTH program, it is LOADed. LOAD takes a screen number on the stack and source code on that screen is compiled.
Because source code is stored directly on disk and not in files, a great deal of available disk space is wasted. For example, if only the first two lines of a screen contain code, the remainder of the screen is wasted disk spaces. Usage shows that nearly half the disk is effectively wasted. It is for this reason FORTH programmers traditionally pack as much code on a screen as possible. Basically, program readability is given up for increased disk storage. Hence, FORTH has become known as a write-only language.
With the cost of disks much lower today than ever before, it is far better to code for readability than to minimize disk usage. This means indenting DO loops — IF … ENDIF constructs and the like.
Without going into detail, Reverse Polish Notation is a method of expressing arithmetic formulas without the need for parentheses. Hewlett Packard has used this form of notation in its calculators for more than a decade. FORTH does not use RPN for any particular reason except it fits nicely into the whole FORTH picture.
The FORTH language is composed entirely of subroutines which all behave in the same manner. These subroutines — words — take a number of arguments, operate on them and return a number of arguments.
To simplify, FORTH sets aside a special stack called the parameter stack which holds arguments until needed. FORTH words will remove arguments from this stack and possibly replace with others. The arithmetic words - *, /, etc. — do exactly this. Each removes two arguments and puts one back.
Thus, to write the expression (3 + 5)/2 in FORTH, we have:
3 5 + 2 /
This is evaluated as follows: The numbers 3 and 5 are pushed onto the parameter stack and the word + is called, which removes them. The sum is computed and the results, 8, is pushed onto the stack. Next, 2 is pushed and / is called which then divides 8 by 2 and pushes 4. Notice no parentheses were needed to ensure the addition was done before the division. If we had wanted to express 3 + (5/2), we would have written:
3 5 2 / +
Because FORTH uses the stack in this manner, it is called a stack-oriented language — which has a great deal to offer.
Because words can leave more than one argument, FORTH can perform many computations more efficiently (and therefore faster) than its non-stack counterparts such as BASIC. For example, when a division is performed internally both the quotient and the remainder come out naturally. But BASIC allows only one value to be returned from an operation. If both the quotient and the remainder are wanted in BASIC, the division routine must be called twice — once more than needed. FORTH does not suffer from this. The FORTH word /MOD takes two numbers and leaves two — the quotient and the remainder.
Neither BASIC nor FORTH support complex numbers, but they can be added to both languages. Since complex numbers have both a real and an imaginary part, they must be represented by two numbers. This presents problems in BASIC. How would 3 + 5i and 4 + 2i be multiplied? BASIC allows only one value to be returned from a function and we need to return two. In FORTH, simply write a word C* — which takes four numbers off the stack and leaves two. C* would have the stack notation
(r1 i1 r2 i2 --- r3 i3).
To do the multiplication above, we need only write:
3 5 4 2 C*
Doing this, the result is left on the stack. Of course, a complete set of complex operators could be written and used just as easily.
Since BASIC does not allow more than one argument to be returned from a function, many simple programs become quite complicated. Any program which needs to read a joystick becomes cluttered because a single stick value must be broken into its horizontal and vertical components. A BASIC program which has to perform a simple function like returning the current (x,y) cursor position also becomes cluttered. Stack-oriented languages do not suffer the same problems.
FORTH is one of few languages which are extensible. This means the FORTH compiler can be extended and made more useful. Everything in FORTH can be changed to suit individual preferences. This cannot be done in BASIC.
If a Microsoft BASIC program must be translated to Atari BASIC, there will most probably be statements Atari BASIC does not have such as the WHILE … WEND construct. If a WHILE … WEND is in the original program, it must be recoded into recognizable keywords. This may take a great deal of time.
In FORTH, however, if a WHILE … WEND is needed it can be added to the compiler and the program itself does not need to be recoded. Hence, the compiler is extended. For example, there is no FOR … NEXT structure in FORTH. The DO … LOOP structure is very similar to a FOR … NEXT loop, but it is not as nice as it could be. Take the following program:
10 REM TEST 20 FOR I=1 TO 10 30 PRINT I 40 NEXT 50 END
In traditional FORTH, this is written as:
: TEST ( --- ) 11 1 (last + 1 1st) DO I . CR ( . means PRINT ) LOOP ; ( CR means c/r )
If we were to extend the compiler, we could have:
: TEST ( --- ) 1 10 FOR I PRINT NEXT ;
Screen 10 actually contains the source code for the FOR … NEXT structure. This extensibility makes FORTH so powerful Charles Moore, creator of FORTH, was able to write a BASIC compiler in only eight screens of code!
I’ve looked at the definition of FILL in my FORTH and it is defined using the word CMOVE. I’m confused, what’s up?
Do you ever wonder where the programmers get the name “hacker”? Well, I’ll tell you. They get this name because they sit around all day thinking up the “hacks” — the programming tricks. In FILL, CMOVE is a hack. If you put a character in the first byte, move it to the second byte, then move the second byte to the third byte, you are filling memory.