ANTIC v1.1 / APRIL 1982 / PAGE 22
In this premier issue of ANTIC I would like to introduce a facility I believe many programmers using the Atari 800/400 will find very useful. The tiny MULTI-TASKING kernel described here allows one to easily create machine language routines that run as part of the vertical blanking interrupt servicing routines. These routines may perform such diverse tasks as moving a player/missile image, blinking the cursor, fine scrolling of the bit map, and various musical applications.
The run-time kernel of the multi-tasking system is contained on Screen 36 and 37. There are three variables that are accessed by the routine. TASK# indicates which of several tasks will execute during the current vertical blanking interval. It is used to index into a table, called TASK-TABLE. The table is 16 bytes long, and holds the entry points for up to 8 separate machine code tasks. TASK# indicates which of these entries is to be executed; the entry point address is transferred from the indexed location in the task table, to JUMPER and an indirect jump is made through JUMPER.
The routines of Screen 37 perform the necessary run-time operations. The master vblank routine is called NEW-VBLANK. It in turn calls two other routines. The first, WHOSE-TURN, uses the value of TASK# to find the appropriate slot in the task table, which contains the address of a subroutine. Notice that if the most significant byte of the entry in the table is set to 0, we can assume that the slot really does not contain a valid address. Otherwise, the entry point address held at that slot is transferred to JUMPER and we vector through JUMPER to the actual subroutine that we want to execute.
When we return from that subroutine, we next execute NEXT-TASK, which simply bumps TASK# by 1, rolling over to if it is greater than three. We then leave the NEW-VBLANK routine by jumping to the code in the Atari Operating System that updates various hardware registers from their shadow registers.
Screen 35 establishes the equates for interfacing with the OS. SET-VBLANKD provides the linkage between Forth and Atari supplied routine for changing the vertical blanking vectors and countdown timers. To install a new vertical blanking vector, you simply push the new routine address onto the stack before call SET-VBLANKD.
Screens 38 and 39 provide us with a means of adding and deleting entries from the task-table. The routines MTOFF and MTON disable and enable the multi-tasking respectively, by changing the deferred vertical blanking vector, using our SET-VBLANKD routine. SNEAK is a way of ensuring that operations that change some variable read by the multi-tasking do so only when the multi-tasking is disabled. SCAN-TASKS is used to find an entry in the task table. It can be used to either find an available slot (an entry in the table = 0) or to find the slot holding the address of a task routine. INSTALL and REMOVE will perform these actions for us. They can be used in the form
INSTALL BLINK (for example) or REMOVE BLINK
where BLINK is one of the tasks we have defined.
TASK: and ;TASK are defining type words which will allow us to create machine code routines, which are subsequently installed in the task table. An important feature of the compilation security of these words is that the routines are not installed into the task table if some sort of structural error was generated by assembling them.
Finally, we can show some screens, that, when compiled, will install some tasks into the multi-tasking system. Once this is done, the routines operate independently of what you are doing as you program in the high level language. They have been integrated into the interrupt structure of the Atari Operating System. Screen 41 provides a task which will periodically blink the cursor. Once installed, you can vary the rate of blinking by writing a value to variable RATE.
Screens 42 & 43 show another amusing background task. Here we monitor the keyboard hardware register (address D209 hex). If the value there is different from the value contained at OLD. KEY, then we change the variable SOUNDING to indicate that we want to make noise. The current key value then becomes the OLD. KEY value. The key value is also stored to the channel 1 audio frequency register The next time the routine executes, we store a value, derived from the envelope table, into the channel 1 audio control register The index variable INTO is decremented by 1 each time, until it is less than (we’ve loaded the last value from the envelope). At this point SOUNDING is set to and we resume checking the hardware register
Other routines that various people have designed for BASIC could be integrated into this system. These would include the joystick controlled text cursor, and using some keys (space bar. Start key, etc.) to temporarily halt program execution. Those who are contemplating purchasing the Forth system produced by Pink Noise Studios might be interested to know that a complete player graphics system is supported using this multi-tasking system to refresh the player images.
Should you have any questions regarding this column or pns fig FORTH—address them to me in care of ANTIC.
( 39 new-vblank process ) decimal : TASK: create [compile] assembler assembler mem ( switches now ) here ( save pfa ) 0 scan-tasks ( find entry in table ) !csp mtoff ; : ;TASK current @ context ! ?csp ( everything secure ) ! ( store pfa into table entry ) mton ; ( do multi-tasking ) : INSTALL [compile] ' 0 scan-tasks sneak ; ( the change into table ) : REMOVE [compile] ' scan-tasks 0 swap sneak ; ( delete by nulling ) ( 41 trying a blinking task ) hex 0 variable time-left 0 variable rate 0 variable blank-state d401 constant chactl ( char control ) task: blink time-left lda, 0= if, blank-state lda, 0= NOT if, 0 # lda, else, 1 # lda, then, chactl sta, blank-state sta, rate lda, time-left sta, else, time-left dec. then, rts, ;task ( 42 sound handler example ) hex label envelope a0 c, a1 c, a2 c, 00 c, a5 c, a6 c, a8 c, 00 c, ab c, ac c, 00 c, af c, c variable into 0 variable sounding 0 variable old.key decimal ( 43 sound handler example ) hex 0 voice task: key.noise sounding lda, 0= not if, into ldx, envelope ,x lda, audc sta, volume sta, dex, 0< if, 0 # lda, sounding sta, c # ldx, then, into stx, else, d209 lda, old.key cmp, 0= not if, audf sta, old.key sta, 1 # lda, sounding sta, then, then, rts, ;task decimal ( 35 new vblank vectors ) HEX e45c constant SETVBV e460 constant VBLANKI e463 constant VBLANKX 224 @ constant VBLANKD ( OS dependant ) code Set-Vblankd ( addr is on stack ) pha, tya, pha, xsave stx, bot lda, pha, ( IS ON STACK ) bot 1+ lda, tax, pla, tay, 7 # lda, setvbv jsr, pla, tay, pla, xsave ldx, pop jmp, end-code decimal ( the 7 # lda, would be changed to 6 ) ( for the immediate vblankd vector) ( and to 1-5 for timers 1-5 ) ( 36 setting up task table ) 00 variable TASK# 00 variable JUMPER ( indirect jump ) label TASK-TABLE 00 , 00 , 00 , 00 , 00 , 00 , 00 , 00 , ( 37 setting up task table ) code WHOSE-TURN task# lda, .a asl, tax, task-table 1+ ,x lda, 0= not if, jumper 1+ sta, ( install pointer) task-table ,x lda, jumper sta, jumper ) jmp, then, rts, end-code code NEXT-TASK clc, task# lda, 1 # adc, 03 # and, task# sta, rts, end-code ( 4 entries in table are checked ) code NEW-yBLANK ' whose-turn jsr, ' next-task jsr, vblankd jmp, end-code decimal ( 38 new-vblank process ) decimal : SCAN-TASKS ( match val) -1 swap 32 0 ( flag for 0<) do i task-table + dup ( addr in tabl) @ 3 pick = ( match to given val ) if rot drop ( loose 0< flag) swap leave else drop ( addr or flag ) then 2 +loop drop ( given val ) dup 0< 44 ?error ; ( task table in lower 32K of memory !! ) : MTOFF vblankd set-vblankd ; ( disable and enable multi-tasking ) : MTON ' new-vblank set-vblankd ; : SNEAK mtoff ! mton ; ( synchronized storage )
If you want to find out what all the excitement is about get one of the Forth implementations listed above and this book. Oh, and read FORTH FACTORY. Bob Gonsalves writes this column for us and is the implementor of the version available from Mountain View Press. Watch for a comparison of these three products in a future issue.
ANTIC v1.2 / JUNE 1982 / PAGE 45
In this installment we’ll present some utility definitions that you may easily add to your system. The first set of words can be used in many Forth systems, the second set are designed to access the Atari disk file management system.
Screen 30 shows some useful extensions to fig-Forth systems. Following \, all characters on a line will be ignored; the text following the \ is used for commenting. NOT is used to reverse the logical state of the top stack item and is often used before conditional testing words, such as IF, UNTIL.
Another group of words facilitates operations on bytes in 16-bit word. LSBYTE will leave the least significant byte of the top stack value, while MSBYTE leaves the most significant byte. uses these two operations to reverse the two halves of a 16-bit stack value.
Our final category introduces a new data type. The ‘TO’ type variable, introduced by Paul Bartholdi in Forth Dimensions, improves readability and reliability of Forth programs by reducing the number of @ (fetch) and ! (store) operations that must be included in the source text. If the VAR defined variable is preceded by TO, a stack parameter will be stored in the variable. Otherwise, the variable simply leaves its contents. The following should illustrate:
0 VAR temp.cell \ our specific instance 20 TO temp.cell \ store 20 in the variable temp.cell . 20 \ prints the contents of temp. cell
With extensions such as these, we’re now ready to suggest some ways of accessing FMS formatted files. As shown in figure 1, both the FMS directory entries and the individual sectors of a FMS file share a similar structure. They both feature range of disk space, with certain bytes or 16-bit words having specific significance.
In the case of a directory entry locations are used to store the state of the file (open, closed, etc.), its length and starting sector, plus the characters that make up its name. These locations are offset from an address DIR.ADDR, which contains the address, within the disk buffers, of the start of the directory entry. The defining word FIELDER will create some words that allow us to access these fields. These access words are structured like the TO variables mentioned above. Thus, START.SECTOR for example, will normally leave the starting sector number of a file on the stack. If it is preceded by TO, however, as in:
3 TO START.SECTOR
then a value is stored to the START.SECTOR field. (This doesn’t work as well for FLAG.BYTE, which is only a single byte location.)
Three additional words on Screen 32 show ways to make use of the data from these fields. ?NULLED examines the least significant byte of the FLAG.BYTE of a directory entry, and leaves a true flag on the stack if the directory entry is not an active one. PNAME will print the name of an entry. BUMPSEC will print out the number of sectors used by an individual file, and increments a counter containing the number of sectors used on the disk.
Our DIR example makes use of all of the above definitions to print out the directory of a FMS disk. It does so by examining sectors 361 through 368 for valid directory entries. Each entry is 16 bytes long (8 per sector) and is checked to see if it is null. If it isn’t, its name is printed and the file length is added to the running total (#SEC). At the end of the directory listing, the total number of sectors used by the files is printed, as well as the number of sectors available (according to the FMS Volume Table of Contents), using .USED.
In the case of an actual data sector from a file, the words FILE, POINTER and BCOUNT (all defined by DATA) return the values stored at the end of a data sector. Because of various tricks that are performed (to save disk space) additional words are required to convert the values into a useble format. The word #FILE returns the position of the file in the directory. The next sector number in the file is returned by #POINT, which equals if this sector is the end of the file. #BYTES returns the number of data bytes in the sector, which may range from 125 to 1.
Two other words are useful in this context. DATA. FIELD leaves the address, in the disk buffers, of the start of the sector. ?LAST leaves a false flag, plus the next sector number of the file, if the end of the file has not been reached. Otherwise a true flag is left on the stack.
Our final example, on Screen 36, illustrates what it might take to list a file. Assuming that the value of DIR.ADDR has been set to point to the directory entry in the disk buffers, PRINT.FILE starts at the first sector of the file, and types #BYTES from the DATA.FIELD of the sector, until the; last sector is reached.
Because of space limitations, we’ll skip over exactly how one locates a specific directory entry. This could be done by simply DUMPing the contents of a disk block, or by actually accepting text from a user and performing a string match against the FMS directory. Other applications for this system, such as loading character fonts into memory and repairing ‘damaged’ files, can be obtained by writing to the author
c/o Pink Noise Studios
P.O. Box 785
Crockett, CA 94525.
Please include a self-addressed, stamped envelope.
pink noise studios/fig-forth 1/82 \ 30 extensions for others rfg20apr82 : \ in @ c/l / 1+ c/l * in ! ; immediate \ from Henry Laxen : NOT 0= ; hex : MSBYTE 0 100 u/ swap drop ; : LSBYTE ff and ; : >< \ byteswap dup lsbyte 100 * swap msbyte + ; 0 variable TOFLAG : TO 1 toflag ! ; : VAR <builds , does> toflag @ if ! else @ then toflag ! ; decimal ;s \ 31 fields in directory rfgl8apr82 0 variable DIR.ADDR \ points to directory entry in buffers : FIELDER <builds c, \ offset into field does> c@ dir.addr @ + \ compute addr toflag @ if ! else then 0 toflag ! ; 0 fielder FLAG.BYTE 1 fielder SECTOR.COUNT 3 fielder START.SECTOR : NAME.FIELD dir.addr @ 5 + ; ;s \ 32 utilities for DIR rfg18apr82 : ?NULLED \ return true if nulled out flag.byte Isbyte dup 80 = swap 0= or sector. count 0^ = or ; hex : PNAME name.field dup 8 type 2e emit 8 + 3 type ; 0 variable #SEC : BUMPSEC \ increment total and print sector. count dup #sec +! 4 .r ; decimal : .USED \ according to VTOC 359 block 3 + (? ^ .r ." sectors available " cr ; ;s pink noise studios/fig-forth 1/82 \ 33 directory of FMS disks rfg18apr82 decimal : DIR 0 #sec ! cr 368 360 \ directory sectors do i block dup b/buf + swap do i dir.addr ! ?nulled not if pname bumpsec cr then 16 +loop loop cr #sec @ 4 .r ." sectors used by files " cr .used ; ;s \ 34 fields in sectors rfg18apr82 decimal 0 variable SECTOR : DATA <builds c, does> c@ sector @ 1- block + toflag @ if ! else then toflag ! ; 125 data FILE 125 data POINTER 127 data BCOUNT hex \ below return values : #FILE file lsbyte 4 / ; : #POINT pointer >< 3ff and ; : #BYTES bcount 7f and 7d min ; \ above accounts for short sectors ;s \ 35 dos access utilities rfg18apr82 : DATA.FIELD \ first storage location sector @ 1- block ; : ?LAST #point -dup 0= ; \ leave true or false + link. \ to next sector pink noise studios/fig-forth 1/82 \ 36 printing a file rfg20apr82 \ assumes dir.addr points to \ directory entry in buffers : PRINT.FILE start.sector sector ! begin data.field #bytes type ?last not while sector ! repeat ;
ANTIC v1.3 / AUGUST 1982 / PAGE 57
This is the first of two articles on implementing a Turtle Graphics system in FORTH.
Let me make two quick points about FORTH:
Those of you who have Pink Noise Studios’ pns-FORTH (I use version 1.4) can edit the screens accompanying these articles “as is” and start turtle-ing. If you have another implementation of FORTH for the ATARI, some revisions are inevitable. I have used words like PLOT and DRAWTO, that pns-FORTH provides for making graphics calls to the ATARI’s operating system. Your system may already have similar words. In Part 2 (next issue), I’ll discuss the functions of any non-fig-FORTH words that I’ve used.
“Turtle Graphics” is a simple but powerful approach to creating graphic designs with a computer. It was originally developed in the 1960’s at MIT—primarily by computer scientist, child psychologist, and educator Seymour Papert—as part of the LOGO system.
Let me give you a very simple example of how it works. Suppose we want to draw a square on the screen, 10 units on a side. The sequence of commands
10 DRAW 90 TURN 10 DRAW 90 TURN 10 DRAW 90 TURN 10 DRAW 90 TURN
or, in a shorter form,
4 ( 10 DRAW 90 TURN )
requests an imaginary turtle on the screen to crawl 10 units forward, draw a line as it goes, turn 90 degrees clockwise, repeat four times. The turtle will leave behind a square. By typing
DEFINE SQUARE AS 4 ( 10 MOVE 90 TURN ) END
we can add the new command SQUARE to our turtle’s graphics repertoire. Then typing the single command
SQUARE
will have the same effect as our previous sequence of commands. For example, to draw a square tilted by thirty degrees, we need only to type
30 TURN SQUARE
The conventional approach to graphics, in which one must specify fixed screen coordinates and the endpoints of each line is much more complicated.
The principle advantage of Turtle Graphics is that it describes shapes in an intrinsic way, without referring to where they are or how they’re oriented. The numbers used in Turtle Graphics represent easily visualizable things, like lengths of lines, or angles.
A further important aspect of a Turtle Graphics system is the nature of the programming it encourages: structured, modular, and hierarchichal. The DEFINE…AS…END construct shown above is the key to this. Basic sub-designs can be made into new turtle commands which are then as much a part of the turtle’s language as the predefined system commands. These higher-level commands can then be used to define still higher ones, and so on.
For example, a simple picture of a house like that in Figure 1 could be drawn with a long sequence of DRAWs and TURNs (along with another command for the turtle to move without drawing). But the structure of the design cries out for the programmer to instead first enrich the turtle’s vocabulary by defining commands such as, perhaps, RECTANGLE, WINDOW, DOOR, FRONT AND ROOF, before using these higher-level command to define on called HOUSE.
FORTH is so ideally suited to Turtle Graphics that, in a sense, implementing it is a trivial exercise. The most complicated aspect of Turtle Graphics is the problem of providing a programming environment in which turtle commands can be executed. Such a capability is already intrinsic to FORTH, while it is quite foreign to conventional languages like BASIC. The point here is that the turtle’s language can be just an extension of the FORTH language—turtle commands are simply FORTH words. There is no need to write an extensible command language processor. That’s what a FORTH system already is!
The ten screens of FORTH listed in this article lay the necessary foundations for us to build a Turtle Graphics system in Part 2 (next issue). The words here are not specifically turtle-oriented. Rather, they extend FORTH’s capabilities in directions particularly useful to the application.
Screens 1, 2, and 3 add some trigonometric capability to FORTH. If the turtle is to move 10 units forward at 30 degrees from the vertical, we need to compute how far up and how far over she goes. For this we use a lookup-table approach. Scaling the values by 10000 enables us to store them as single-precision integers. The words SIN* and COS* are the result of this. For example,
10 30 SIN*
leaves 5, or 10 times the sine of 30 degrees, on the stack; and this is how far over the turtle would move.
Screen 4a makes available a defining word, VALUE, for a new data type. An alternative to CONSTANT and VARIABLE, VALUE words tend to make FORTH code more readable. They are best explained by the following example:
VALUE A VALUE B VALUE C ok 2 TO A 3 TO B ok A . B . 2 3 ok A B + TO C C . 5 ok
VALUE words return their value when executed, except when they are preceded by TO; in which case they store the top of the stack into themselves. (This idea has been discussed in the Forth Dimensions newsletter of the Forth Interest Group.)
In Screen 4a the words TO and VALUE are defined in assembly language, rather than FORTH, so that they will execute as fast as CONSTANTs and VARIABLEs. If you don’t have an assembler, use the alternate FORTH code on Screen 4b.
Screens 5 through 8, culminating in the word CLIP, implement a line-clipping algorithm. We want the turtle to be able to cross the edge of the screen, so that if we execute SQUARE when she is near the top we’ll get a clipped box. But the opearing system will refuse to draw a line whose endpoints aren’t both within the screen boundaries. Therefore, we must be able to calculate the endpoints of the portion of the line which lies on the screen. If we give CLIP the coordinates of two points, it first determines whether any part of the line between them lies within a “clipping rectangle” whose extent we can specify by setting the values of LEFT, RIGHT, TOP, and BOTTOM. (Note that these words are in the vocabulary CLIPPING). If so, it returns the coordinates of the endpoints of the portion within the clipping rectangle, and a true flag. If not, it returns only a false flag.
For example, suppose we set the clipping rectangle to be the size of the mode-7 graphics screen with
CLIPPING TO LEFT 159 TO RIGHT 0 TO TOP 79 TO BOTTOM
Then
30 30 50 50 CLIP
leaves 30 30 50 50 1 on the stack because the line between (30,30) and (50,50) is completely within the clipping rectangle. But
80 100 200 40 CLIP
leaves 122 79 159 61 1 because only the portion between (122,79) and (159,61) of the specified line lies inside the clipping rectangle. And
200 200 300 300 CLIP
leaves because no part of the line lies inside. The Cohen-Sutherland algorithm that CLIP uses is described in detail in Chapter 5, “Clipping and Windowing”, of Newman and Sproull’s Principles of Interactive Computer Graphics.
The last screen, Screen 9, defines the word GRAPHICS for opening the screen in the graphics mode specified by the top the stack, and LINE, which takes the coordinates of two endpoints and draws the clipped part of it on the screen. If you want to see the clipping in action, before the rest of the code is given in Part 2, try the following: Define the words BORDER, RANDOM_LINE, and RANDOM_LINES as
: BORDER CLIPPING 1 COLOR LEFT BOTTOM PLOT LEFT TOP DRAWTO RIGHT TOP DRAWTO RIGHT BOTTOM DRAWTO LEFT BOTTOM DRAWTO ; : RANDOM_LINE 4 0 DO CRANDOM LOOP LINE ; : RANDOM_LINES 0 DO RANDOM_LINE LOOP ;
and then type
CLIPPING 20 TO LEFT 140 TO RIGHT 20 TO TOP 60 TO BOTTOM 7 GRAPHICS BORDER 100 RANDOM_LINES
See you next issue!
( Turtle Graphics I, screen 1 ) DECIMAL TABLE SINES 0000 , 0175 , 0349 , 0523 , 0698 , 0872 , 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 , --> ( Turtle Graphics I, screen 2 ) : (SIN) ( n1 --- n2 ) DUP 90 > IF 180 SWAP - THEN SINES ; : SIN ( n1 --- n2 ) ( Returns 10000 times the sine ) ( of n1 degrees. ) 360 MOD DUP 0< IF 360 + THEN DUP 180 > IF 180 - (SIN) MINUS ELSE (SIN) THEN ; : COS ( n1 --- n2 ) ( Returns 10000 times the cosine ) ( of n1 degrees. ) 360 MOD 90 + SIN ; --> ( 32 Turtle Graphics I, screen 3 ) : SIN* ( n1 n2 --- n3 ) ( Returns n1 times the sine of ) ( n2 degrees. ) SIN 10000 */ ; : COS* ( n1 n2 --- n3 ) ( Returns n1 times the cosine of ) ( n2 degrees. ) COS 10000 */ ; --> ( 33 Turtle Graphics I, screen 4a ) 0 VARIABLE TO-FLAG CODE TO ( --- ) 1 # LDA, TO-FLAG STA, NEXT JMP, END-CODE : VALUE 0 CONSTANT ;CODE TO-FLAG LDA, 0= IF, 2 # LDY, W )Y LDA, PHA, INY, W )Y LDA, PUSH JMP, ELSE, 0 # LDA, TO-FLAG STA, BOT LDA, 2 # LDY, W )Y STA, BOT 1+ LDA, INY, W )Y STA, POP JMP, THEN, END-CODE --> ( 34 Turtle Graphics I, screen 4b ) 0 VARIABLE TO-FLAG : TO 1 TO-FLAG ! ; : VALUE <BUILDS 0 , DOES> TO-FLAG @ IF 0 TO-FLAG ! ! ELSE @ THEN ; --> ( Turtle Graphics I, screen 5 ) VOCABULARY CLIPPING IMMEDIATE CLIPPING DEFINITIONS VALUE LEFT VALUE TOP VALUE RIGHT VALUE BOTTOM 2 BASE ! : CODE ( p --- n ) 0 OVER TOP < IF 1000 + SWAP DROP ELSE SWAP BOTTOM > IF 0100 + THEN THEN OVER LEFT < IF 0001 + SWAP DROP ELSE SWAP RIGHT > IF 0010 + THEN THEN ; --> ( Turtle Graphics I, screen 6 ) VALUE X1 VALUE Y1 VALUE C1 VALUE X2 VALUE Y2 VALUE C2 VALUE C : CLIP_X ( n1 --- n2 ) Y1 - X2 X1 - Y2 Y1 - */ X1 + ; : CLIP_Y ( n1 --- n2 ) X1 - Y2 Y1 - X2 X1 - */ Y1 + ; ( 37 Turtle Graphics I, screen 7 ) 2 BASE ! : WHERE? ( --- p ) C 0001 AND IF LEFT LEFT CLIP_Y ELSE C 0010 AND IF RIGHT RIGHT CLIP_Y ELSE C 0100 AND IF BOTTOM CLIP_X BOTTOM ELSE C 1000 AND IF TOP CLIP_X TOP THEN THEN THEN THEN ; DECIMAL : HERE! ( p --- ) C C1 = IF TO Y1 TO X1 X1 Y1 CODE TO C1 ELSE TO Y2 TO X2 X2 Y2 CODE TO C2 THEN ; --> ( 38 Turtle Graphics I, screen 8 ) FORTH DEFINITIONS : CLIP ( p1 p2 --- p1' p2' t ) ( or ) ( p1 p2 --- f ) CLIPPING TO Y2 TO X2 X2 Y2 CODE TO C2 TO Y1 TO X1 X1 Y1 CODE TO C1 BEGIN C1 C2 OR WHILE C1 C2 AND IF 0 ;S THEN C1 IF C1 TO C ELSE C2 TO C THEN WHERE? HERE! REPEAT X1 Y1 X2 Y2 1 ; --> ( 39 Turtle Graphics I, screen 9 ) : GRAPHICS ( n --- ) ( Clears the screen and sets it up ) ( for graphics mode n with a text ) ( window. ) >R SETUP S CLOSE S SPLIT-SCREEN R> GR. ; : LINE ( p1 p2 --- ) ( Displays whatever piece of the ) ( line from p1 to p2 is within ) ( the clipping window. ) CL# @ COLOR CLIP IF PLOT DRAWTO THEN ; ;S
ANTIC v1.4 / OCTOBER 1982 / PAGE 56
This is the second of two articles on implementing a Turtle Graphics system in Forth. The first one appeared in ANTIC issue #3. It discussed Turtle Graphics in general, explained why Forth is a particularly hospitable environment for a Turtle Graphics system, and gave nine screens worth of “foundation” words.
With this article are 17 more screens of pns-Forth source code which complete the system. I’ll give an overview of the system’s features; a glossary of Turtle commands, and give a few suggestions on using the system.
Four independent turtles live in Turtleland. Multiple turtles open up interesting possibilities, like having turtles chase each other. With four turtles, each can draw in a different color (there are only four colors possible at one time). If you want a different number, you can change the value of the constant #TURTLES on Screen 2 before loading. One turtle at a time can be designated the “active turtle” with the SET ACTIVE command. She is the one who will respond when we type a command like “10 DRAW.”
Each turtle carries a pen. The active turtle’s pen can be lowered with the PENDOWN command, leaving a trail when she moves, or raised with the PENUP command. The more general SET PEN command can be used to do either.
The SET INK command fills the active turtle’s pen wtih various colors of ink, depending on the Graphics Mode used. (Modes 3 through 8 can be selected with the SET MODE command.) In all modes, ink of type 0 is erasing ink. It is black, the same color as the background, except in Mode 8 when it is light blue. The command, ERASING, is the same as 0 SET INK. Both choose erasing ink. In Modes 3, 5, and 7, there is also ink of type 1 (gold), type 2 (light green), and type 3 (dark blue). In Modes 4, 6, and 8, types 2 and 3 are not available. The number of ink types is determined by the color video capabilities of the CTIA or GTIA chip. The colors are established by the Operating System when it opens the screen. You can use pns-Forth SETCOLOR word to change them.
Each turtle has a position and a heading. The heading is the number of degrees clockwise from the vertical that she is facing. The active turtle’s heading can be changed directly to any value with SET HEADING, also known as TURNTO, or it can be changed incrementally by the commands RIGHT (or TURN) and LEFT.
The system keeps track of each turtle’s position with X and Y coordinates. These are not the same as the screen column and row numbers. The SET MODE command arranges these coordinates so that the turtle’s home at X = 0 and Y = 0 is the center of the screen, and so that there are one hundred X or Y units per pixel. This means that if a turtle is at X = 1000 and Y = 500 she will appear ten pixels to the right and five pixels up from the center. You can arrange the coordinates differently if you wish.
The active turtle’s coordinates can be individually or jointly set with the commands SET X, SET Y, or SET POSITION (also known as GOTO). They cause the turtle to leave a track only if her pen is down. MOVETO can be used to temporarily raise the pen, or DRAWTO to lower it, before changing position. The pen is restored to its original state after the change.
The most interesting way to move the active turtle is with FORWARD, BACKWARD, DRAW, and MOVE commands. These move her a specified number of steps in whatever direction she is currently heading. FORWARD and BACKWARD draw a line only if the pen is down; DRAW always draws; MOVE never does. Each step normally moves the turtle one pixel, a distance of 100 units in XY coordinates, unless you use the SET SIZE command to alter the step size. By changing the step size you can use the same word to draw the same shape in different sizes.
A turtle’s heading and her XY coordinates are always integers. The maximum range for X and Y is from -32768 to 32767. If you drive a turtle beyond this range you may see unwanted tracks as she “jumps” to the other edge of Turtleland.
Usually you can’t see all of Turtleland on the screen. For example: in Mode 7 the screen displays only the part of Turtleland from X = -15900 to X = 15800 and from Y = -7900 to Y = 7800. You can select your own “window” into Turtleland with SET WINDOW command. Any tracks beyond the edges of the window won’t be visible. Changing the window will affect the number of X or Y units per pixel. An alternate way to set the window (and the step size) is with the PER-PIXEL command.
The reason that the system defaults to 100 units per pixel is to let the turtle sit “between” pixels. If we used a coordinate system as coarse as the screen pixels, then every time we moved a turtle at some angle, her new position would get “rounded” to the nearest pixel. We wouldn’t be able to do a series of moves without errors accumulating. Using one hundred XY units per pixel gives us increased precision.
The SET MODE command establishes the whole screen as the “viewport”. This means that the view of Turtleland visible through the window will be projected onto all of the screen. You can select any rectangular piece of the screen to be the viewport with the SET VIEWPORT command. When you experiment with this, use the FRAME or NEW commands to draw a frame around the new viewport so you can see where it is.
So far, four commands—MODE, SIZE, WINDOW, and VIEWPORT—relate to Turtleland as a whole, and seven of them—ACTIVE, PEN, INK, HEADING, X, Y, and POSITION—relate to the turtles. It is also possible for you to determine the current value of any of these parameters, by leaving out the word SET or by changing it to SHOW. For example, the command X by itself (i.e., not preceded by SET) leaves the active turtle’s current X coordinate on the stack, where it can be used by any word for any purpose. So, the command SHOW X will display some message like “Turtle #1 is at X = 300”.
The system also has miscellaneous commands like CLEAR for clearing the screen, FRAME for drawing a frame around your picture, and HOME, START, and NEW for starting over. The command BYE leaves Turtleland and returns to pns-Forth.
Of course, all the usual Forth words are still available while you’re in Turtleland, in case you need to do arithmetic, comparisons, branching, looping, or whatever. You can use the more compact loop syntax (…) and (…+) in place of the structures 0 DO…LOOP and 0 DO…+LOOP.
The important command DEFINE…AS…END allows you to add new words to the turtle’s vocabulary. This makes it very easy to change any of my command names that you don’t like.
As an interesting example, you might want to
DEFINE HILDA AS 1 SET ACTIVE END DEFINE GILDA AS 2 SET ACTIVE END DEFINE MATILDA AS 3 SET ACTIVE END
so that you can talk to a turtle simply by invoking her name.
To start turtle-ing, just use the SET MODE command. If you want to have Turtleland displayed in Graphics Mode 7, for example, type 7 SET MODE. After this you can immediately move the turtles around with 10 DRAW, 45 TURN, etc. SET MODE initializes the system as follows:
After you get acquainted with the various commands, you’ll want to start extending the system by defining your own. Here is an example of a new command:
VALUE STEPS VALUE INCREMENT VALUE ANGLE DEFINE POLYSPI AS TO ANGLE TO INCREMENT 0 TO STEPS BEGIN STEPS INCREMENT + TO STEPS STEPS FORWARD ANGLE TURN AGAIN END
POLYSPI can make all sorts of interesting polygonal spirals. It expects to find two numbers on the stack. It stores the top one in ANGLE; this will be how many degrees the turtle will turn between each move. The one below gets stored in INCREMENT; this will be how many more steps the turtle will take each time compared to the previous time. Next STEPS is initialized to 0 and we enter a Forth BEGIN…AGAIN loop. The words between BEGIN and AGAIN will be executed indefinitely. (You must press a yellow console button to stop POLYSPI.) Each time through the loop, STEPS is incremented by INCREMENT, and the turtle takes the number of steps in STEPS and turns the number of degrees in ANGLE. Thus POLYSPI is just an automated sequence of FORWARDs and TURNs. For example, 2 90 POLYSPI is really the same as
2 FORWARD 90 TURN 4 FORWARD 90 TURN 6 FORWARD 90 TURN
and so on.
The three VALUE words POLYSPI uses make it easy to see what’s going on. However, another definition of POLYSPI is possible which uses no variables at all:
DEFINE POLYSPI AS 0 BEGIN 3 PICK + DUP FORWARD OVER TURN AGAIN END
This version keeps everything on the stack, using the Forth words PICK, DUP, and OVER for stack manipulation. You can make a variety of patterns with this one command by changing its two parameters.
Pressing a yellow console button will break out of an indefinite loop of turtle moves. In fact, every time a turtle changes position, the system checks the console buttons and returns to command level if one is depressed. This makes it easy to regain control.
As mentioned in Part 1, ten of the words used in my screens are pns-Forth words which won’t be available (at least not with the same meanings) in other Forth systems. Two of these, 1- and TABLE, are common Forth extensions whose high-level definitions are
and: 1- 1 -;
: TABLE <BUILDS DOES> OVER + + @ ;
The others are highly system-specific. Four of them—SETUP S, CLOSE S, SPLIT-SCREEN, and GR.—were used in the word GRAPHICS in Part 1. Their definitions are quite complex, as these words are part of pns-Forth’s interface to the CIO routines in the Operating System. Their joint effect in the word GRAPHICS, however, is quite simple. Any Forth system sold for the ATARI will probably have words for opening the screen for graphics. Simply use whatever your system provides to define your own GRAPHICS, which takes one number from the stack and opens the screen in that mode, with a text window at the bottom.
The last four words specific to pns-Forth are CL#, COLOR, PLOT, and DRAWTO. These are used by LINE (in Part 1), FRAME, and POSITION. The first two are simple to define; just use
and0 VARIABLE CL#
: COLOR DUP CL# ! PAD C! ;
CL# is a variable which is used to keep track of the color data used to plot a pixel. COLOR takes a number from the stack and stores it both in CL# and at PAD, for later use by PLOT and DRAWTO. The definitions of PLOT and DRAWTO are complicated because these words result in calls to CIO. Again, however, their functions are simple and your system probably provides similar words. Define a PLOT which takes a column and a row number from the stack, moves the screen cursor to that position, and plots a pixel there using whatever byte is at PAD as the color data. Similarly, define a DRAWTO which takes a column and a row number from the stack, and draws a line from the current position of the screen cursor to this specified position, using the byte at PAD as color data.
I believe that all the other words I’ve used in this system are either standard fig-Forth words or new words that I’ve defined.
GLOSSARY OF TURTLE COMMANDSMODE Commands
- SET MODE [ mode --- ]
- Opens the screen in the Graphics Mode specified by mode, which should be 3-8. Sets up a default viewport, window, and step size by executing WHOLE-SCREEN SET VIEWPORT and 100 PER-PIXEL. Draws a frame around the viewport with ink of type 1. Initializes the turtles by executing START.
- MODE [ --- mode ]
- Leaves the number of the current Graphics Mode on the stack.
- SHOW MODE [ --- ]
- Displays a message indicating the current Graphics Mode.
ACTIVE Commands
- SET ACTIVE [ turtle# --- ]
- Makes the turtle whose number is turtle# the active turtle. Future commands will be directed to her.
- ACTIVE [ --- turtle# ]
- Leaves the number of the active turtle on the stack.
- SHOW ACTIVE [ --- ]
- Displays a message indicating the currently active turtle.
PEN Commands
- SET PEN [ state --- ]
- Lowers the active turtle’s pen if state is nonzero and raises it if state is zero.
- PEN [ --- state ]
- Leaves 1 on the stack if the active turtle’s pen is down and 0 if it is up.
- SHOW PEN [ --- ]
- Displays a message indicating whether the active turtle’s pen is up or down.
INK Commands
- SET INK [ ink# ]
- Fills the active turtle’s pen with ink of type ink#. Type 0 ink is erasing ink. Types 1, 2, and 3 are colored. Types 2 and 3 are not available in modes 4, 6, or 8.
- INK [ --- ink# ]
- Leaves on the stack the type of ink in the active turtle’s pen.
- SHOW INK [ --- ]
- Displays a message indicating the type of ink in the active turtle’s pen.
HEADING Commands
- SET HEADING [ degrees --- ]
- Makes the active turtle head in the direction specified by degrees. Directions are measured clockwise from the vertical.
- HEADING [ --- degrees ]
- Leaves the active turtle’s heading on the stack.
- SHOW HEADING [ --- ]
- Displays a message indicating the active turtle’s heading.
X Commands
- SETX [ X --- ]
- Changes the active turtle’s X coordinate to x. Draws a line if her pen is down.
- X [ --- X ]
- Leaves the active turtle’s X coordinate on the stack.
- SHOW X [ --- ]
- Displays a message indicating the active turtle’s X coordinate.
POSITION Commands
- SET POSITION [ X y --- ]
- Changes the active turtle’s coordinates to X = x and Y = y. Draws a line if her pen is down.
- POSITION [ --- X y ]
- Leaves the active turtle’s X and Y coordinates on the stack.
- SHOW POSITION [ --- ]
- Displays a message indicating the active turtle’s X and Y coordinates.
SIZE Commands
- SET SIZE [ distance steps --- ]
- Sets the step size so that the number of steps given by steps will cover a distance in XY coordinates given by distance.
- SIZE [ --- distance steps ]
- Leaves the current size parameters on the stack.
- SHOW SIZE [ --- ]
- Displays a message indicating the current step size.
WINDOW Commands
- SET WINDOW [ xmin xmax ymin ymax --- ]
- Sets the window to be the region from X = xmin to X = xmax and from Y = ymin to Y = ymax.
- WINDOW [ --- xmin xmax ymin ymax ]
- Leaves the current window parameters on the stack.
- SHOW WINDOW [ --- ]
- Displays a message indicating the current window.
VIEWPORT Commands
- SET VIEWPORT [ left right top bottom --- ]
- Sets the viewport to extend from screen column left to screen column right and from screen row top to screen row bottom.
- WHOLE-SCREEN SET VIEWPORT [ --- ]
- Sets the viewport to extend from column 1 to the next to the last column and from row 1 to the next to the last row.
- VIEWPORT [--- left right top bottom ]
- Leaves the current viewport parameters on the stack.
- SHOW VIEWPORT [ --- ]
- Displays a message indicating the current viewport.
Y Commands
- Similar to X Commands
Other Commands
- CLEAR [ --- ]
- Clears the graphics screen without affecting the turtles.
- FRAME [ ink# --- ]
- Draw a frame around the viewport, using ink of type ink#.
- HOME [ --- ]
- Moves the active turtle to X = 0 and Y = 0 with heading 0, without drawing a line, and then lowers her pen.
- START [ --- ]
- HOMEs all the turtles first. Then fills their pens with ink. (In mode 3, 5, or 7, the Nth turtle’s pen is filled with ink of type N. In mode 2, 4, or 6, turtle’s 0’s pen is filled with type 0 ink while the pens of turtles 1, 2, and 3 are filled with type 1 ink, the only colored ink available in these modes.) Finally, makes turtle 1 the active turtle.
- NEW [ --- ]
- Clears the screen, draws a frame with type 1 ink, and initializes the turtles by executing START.
- PER-PIXEL [ distance --- ]
- Sets the window so that the point X = 0, Y = 0 is the center of the viewport, and so that the distance in XY coordinates given by distance will be the size of one pixel. Also, sets the step size so that each step is distance units long.
- FORWARD [ steps --- ]
- Moves the active turtle forward the number of steps specified by steps. The movement is in the direction she is currently heading if steps is positive and in the opposite direction if steps is negative. The turtle’s heading is unaffected. A line is drawn if her pen is down.
- BACKWARD [ steps --- ]
- Like FORWARD except in the opposite direction.
- DRAW [ steps --- ]
- Lowers the active turtle’s pen so that a line will definitely be drawn as she moves forward the number of steps given by steps. Then her pen is returned to its previous state.
- MOVE [ steps --- ]
- Raises the active turtle’s pen so that a line will definitely not be drawn as she moves forward the number of steps given by steps . Then her pen is returned to its previous state.
- RIGHT [ degrees --- ]
- Turns the active turtle the specified number of degrees, to the right if degrees is positive and to the left if negative.
- LEFT [ degrees --- ]
- Like RIGHT except in the opposite direction.
- TURN [ degrees --- ]
- The same as RIGHT.
- GOTO [ x y --- ]
- The same as SET POSITION.
- DRAWTO [ x y --- ]
- Lowers the active turtle’s pen so that a line will definitely be drawn as she moves to X = X and Y = y. Then her pen is returned to its previous state.
- MOVETO [ x y --- ]
- Raises the active turtle’s pen so that a line will definitely not be drawn as she moves to X = X and Y = y. Then her pen is returned to its previous state.
- TURNTO [ degrees --- ]
- The same as SET HEADING.
- PENDOWN [ --- ]
- Lowers the active turtle’s pen. This is the same as 1 SET PENSTATE.
- PENUP [ --- ]
- Raises the active turtle’s pen. This is the same as 0 SET PENSTATE.
- PENDOWN? [ --- flag ]
- Leaves a 1 on the stack if the active turtle’s pen is down and a if it is up. This is the same as PEN.
- PENUP? [ --- flag ]
- Leaves a 1 on the stack if the active turtle’s pen is up and a 0 if it is down. This is the opposite of PEN.
- ERASING [ --- ]
- Fills the active turtle’s pen with type ink (the erasing type.) This is the same as 0 SET INK.
- (…) [ #loops --- ]
- Executes the words between the left parenthesis and the right parenthesis the number of times given by #loops.
- DEFINE…AS…END
- Defines the word between DEFINE and AS to be a new turtle command which will execute the words between AS and END.
- BYE [ --- ]
- Leaves Turtleland and returns to pns-Forth.
( Turtle Graphics II, screen 1 ) DECIMAL : VALUES <BUILDS 0 DO 0 , LOOP DOES> OVER + + TO-FLAG @ IF 0 TO-FLAG ! ! ELSE @ THEN ; VALUE PREFIX : SET ( --- ) 2 TO PREFIX ; : SHOW ( --- ) 4 TO PREFIX ; : ROOT: ( --- ) <BUILDS SMUDGE ] DOES> PREFIX + @ EXECUTE 0 TO PREFIX ; --> ( Turtle Graphics II screen 2 ) 4 CONSTANT #TURTLES VALUE WHICH ( The number of the active turtle ) : ACTIVE! ( n --- ) TO WHICH ; : .WHICH ( --- ) ." Turtle #" WHICH . ; : ACTIVE? ( --- ) .WHICH ." IS active " CR ; ROOT: ACTIVE WHICH ACTIVE! ACTIVE? ; --> ( Turtle Graphics II, screen 3 ) : MODE@ ( --- n ) 87 C@ ; : MODE? ( --- ) ." This is graphics mode " MODE@ . CR ; TABLE MAX_COL# ( n1 --- n2 ) 39 , 19 , 19 , 39 , 79 , 79 , 159 , 159 , 319 , TABLE MAX_ROW# ( n1 --- n2 ) 19 , 19 , 9 , 19 , 39 , 39 , 79 , 79 , 159 , : WHOLE-SCREEN ( --- n1 n2 n3 n4 ) 1 MODE@ MAX_COL# 1- 1 MODE@ MAX_ROM# 1- ; --> ( Turtle Graphics II, screen 4 ) : VIEWPORT@ ( --- n1 n2 n3 n4 ) CLIPPING LEFT RIGHT TOP BOTTOM ; : VIEWPORT? ( --- ) CLIPPING ." The viewport is frow column " LEFT . ." to " CR ." column " RIGHT . ." and from row " TOP . ." to row " BOTTOM . CR ; VALUE XMIN VALUE YMIN VALUE XMAX VALUE YMAX : WINDOW@ ( --- n1 n2 n3 n4 ) XMIN XMAX YMIN YMAX ; : WINDOW? ( --- ) ." The window is front X=" XMIN . ." to X=" XMAX . CR ." and from Y=" YMIN . ." to Y=" YMAX . CR ; --> ( Turtle Graphics II, screen 5 ) VALUE 0COL VALUE 0ROW : ORIGIN! ( --- ) CLIPPING XMIN MINUS RIGHT LEFT - XMAX XMIN - */ LEFT + TO 0COL YMAX MINUS TOP BOTTOM - YMAX YMIN - */ TOP + TO 0ROW ; : VIEWPORT! ( n1 n2 n3 n4 --- ) CLIPPING MODE@ MAX_ROW# MIN TO BOTTOM 0 MAX TO TOP MODE@ MAX_COL# MIN TO RIGHT 0 MAX TO LEFT ORIGIN! ; : WINDOW! ( n1 n2 n3 n4 --- ) TO YMAX TO YMIN TO XMAX TO XMIN ORIGIN! ; --> ( Turtle Graphics II, screen 6 ) ROOT: VIEWPORT VIEWPORT@ VIEWPORT! VIEWPORT? ; ROOT: WINDOW WINDOW@ WINDOW! WINDOW? ; : LEFT- ( --- n ) CLIPPING LEFT 1- MAX ; : TOP- ( --- n ) CLIPPING TOP 1- MAX ; : RIGHT+ ( --- n ) CLIPPING RIGHT 1+ MODE@ MAX_COL# MIN ; : BOTTOM+ ( --- n ) CLIPPING BOTTOM 1+ MODE@ MAX_ROW# MIN ; : FRAME ( n --- ) COLOR LEFT- TOP- PLOT RIGHT+ TOP- DRAWTO RIGHT+ BOTTOM+ DRAWTO LEFT- BOTTOM+ DRAWTO LEFT- TOP- DRAWTO ; --> ( Turtle Graphics II, screen 7 ) #TURTLES VALUES PEN() : PEN@ ( --- f l39 ) WHICH PEN( ) ; : PENDOWN? ( --- flag ) PEN@ ; : PENUP ( --- flag ) PEN@ 0= ; : PEN! ( flag --- ) 0= 0= WHICH TO PEN() ; : PENDOWN ( --- ) 1 PEN! ; : PENUP ( --- ) 0 PEN! ; : PEN? ( --- ) .WHICH ." has her pen " PEN@ IF ." down " ELSE ." up " THEN CR ; ROOT: PEN PEN@ PEN! PEN? ; --> ( Turtle Graphics II, screen 8 ) #TURTLES VALUES INK() : INK@ ( --- n ) WHICH INKO ! : INK! ( n --- ) WHICH TO INKO ; : ERASING ( --- ) INtC ; : INK? ( --- ) .WHICH ." is using ink #" INK@ . CR ; ROOT: INK INK@ INK! INK"? ; --> ( Turtle Graphics II, screen 9 ) @TURTLES VALUES HEADING() : HEADING@ ( --- n ) WHICH HEADING() ; : HEADING? ( --- ) .WHICH ." has heading " HEADING@ . CR ) : HEADING! ( n --- ) 360 MOD WHICH TO HEADING ! : TURNTO ( n --- ) HEADING! ; ROOT: HEADING HEADING@ HEADING! HEADING? ; : TURN ( n --- ) HEADING@ + HEADING! ; : RIGHT ( n --- ) TURN ; : LEFT ( n --- ) MINUS TURN ; --> ( Turtle Graphics II, screen 10 ) #TURTLES VALUES X() #TURTLES VALUES Y() : X@ ( --- n ) WHICH X() ; : Y@ ( --- n ) WHICH Y() ; : X? ( --- ) .WHICH ." is at x=" X@ . CR ; : Y? ( --- ) .WHICH ." is at Y=" Y@ . CR ; : POSITION@ ( --- n1 n2 ) X@ Y@ ; : POSITION? ( --- ) .WHICH ." IS at X=" X@ . ." and Y=" Y@ . CR ; --> ( Turtle Graphics II, screen 11 ) : X->COL ( n1 --- n2 ) CLIPPING RIGHT LEFT - XMAX XMIN - */ 0COL + ; : Y->ROW ( n1 --- n2 ) CLIPPING TOP BOTTOM - YMAX YMIN - */ OROW + ; : SCALE ( n1 n2 --- n3 n4 ) SWAP X->COL SWAP Y->ROW ; : ?CONSOLE ( --- flag ) 53279 C@ 7 = NOT ; : POSITION! ( n1 n2 --- ) ?CONSOLE IF SP! CR ." ot(." QUIT THEN PEN@ IF INK@ COLOR OVER OVER SCALE POSITION@ SCALE LINE THEN WHICH TO Y() WHICH TO X() ; --> ( Turtle Graphics II, screen 12 ) : GOTO ( n1 n2 --- ) POSITION! ; ROOT: POSITION POSITION@ POSITION! POSITION? ; : X! ( n --- ) Y@ POSITION! ; : Y! ( n --- ) X@ SWAP POSITION! ; ROOT: X X@ X! X? ; ROOT: Y Y@ Y! Y? ; : MOVETO ( n1 n2 --- ) PEN@ ROT ROT PENUP POSITION! PEN! ; : DRAWTO ( n1 n2 --- ) PEN@ ROT ROT PENDOWN POSITION! PEN! ; --> ( Turtle Graphics II, screen 13 ) VALUE SIZE_N VALUE SIZE_D : SIZE@ ( --- n1 n2 ) SIZE.N SIZE_D ; : SIZE* ( n1 --- n2 ) SIZEe x/ ; : SIZE! ( n1 n2 --- ) TO SIZE_D TO SIZE.N ; : SIZE? ( --- ) SIZE_D DUP . 1 = IF ." step is " ELSE ." steps are " THEN ." a distance of " SIZE_N . CR ; ROOT: SIZE SIZE@ SIZE! SIZE? ; --> ( Turtle Graphics II, screen 14 ) : VECTOR ( n --- n1 n2 ) DUP HEADING@ SIN# X@ + SWAP HEADING@ COS# Y@ + ; : FORWARD ( n --- ) SIZE* VECTOR POSITION! ; : BACKWARD ( n --- ) MINUS FORWARD ; : MOVE ( n --- ) PEN@ SWAP PENUP FORWARD PEN! ; : DRAW ( n --- ) PEN@ SWAP PENDOWN FORWARD PEN! ; --> ( Turtle Graphics II, screen 15 ) : PER-PIXEL ( n --- ) CLIPPING >R RIGHT LEFT - 2 / DUP MINUS R * SWAP 1+ R * BOTTOM TOP - 2 / DUP MINUS R * SWAP 1+ R * SET WINDOW R> 1 SET SIZE ; ( Make SURE you typed the >R and R> ) ( in this correctly. ) : SCREEN-DEFAULTS ( --- ) WHOLE-SCREEN SET VIEWPORT 100 PER-PIXEL ; TABLE GR.BYTES ( n1 --- n2 ) 960 , 400 , 200 , 200 , 400 , 800 , 1600 , 3200 , 6400 , : CLEAR ( --- ) 88 @ MODE@ GR.BYTES ERASE ; --> ( Turtle Graphics II, screen 16 ) : HOME ( --- ) 0 0 MOVETO 0 TURNTO PENDOWN ; : START ( --- ) #TURTLES DO I SET ACTIVE HOME MODE@ 2 MOD IF I ELSE I 0= 0= THEN SET INK LOOP 1 SET ACTIVE ; : MODE! ( n --- ) GRAPHICS SCREEN-DEFAULTS 1 FRAME START ; ROOT: MODE MODE@ MODE! MODE? ; : NEW ( --- ) CLEAR 1 FRAME START ; : BYE ( --- ) 0 GRAPHICS 0 710 C! 68 712 C! ; --> ( Turtle Graphics II, screen 17 ) : DEFINE [COMPILE] : ; IMMEDIATE : AS ; IMMEDIATE : END [COMPILE] ; ; IMMEDIATE ; \ ( ignores rest of line ) IN @ C/L / 1+ C/L * IN ! ; IMMEDIATE : ( COMPILE 0 [COMPILE] DO ; IMMEDIATE : ) [COMPILE] LOOP ; IMMEDIATE : +) [COMPILE] +LOOP ; IMMEDIATE
ANTIC v1.5 / DECEMBER 1982 / PAGE 49
The powerful facility of defining words allows the Forth programmer to define application-specific data types, with associated execution procedures.
If you have programmed in Forth at all, you’ve used these defining words : (colon), VARIABLE and CONSTANT. These words are used to create specific instances of colon-definitions, variables and constants. In fig-Forth, we can create other defining words in the following way.
: name <BUILDS compile- time code DOES> run-time code
Three points to be noted are:
As an example, we would create the following defining word
: ByteArray <builds allot does> + ;
To compile a specific instance, we could type
300 ByteArray TESTER
to create an array with 300 bytes alloted to it. (The locations are not set to any particular value.) To use our instance, we can type
53 TESTER
so that the offset 53 is added to the first storage address, leaving the address of the 53rd byte in TESTER.
As you may know, the ATARI Operating System supports communication with peripheral devices through data structures called I/O Control Blocks. The eight control blocks consist of 16-byte arrays in memory, with each location in the array serving a fixed function. Listing 1 shows some words I use to manipulate control blocks. In this example, all the words defined by SERVES.AS execute by leaving an address on the stack. This address is the storage address for the COMMAND byte, STATUS byte, etc., associated with a particular control block.
Now, how do we use those instances? First, we must decide which control block we are referencing by storing the control-block number into IOCB#. After that point, all references using COMMAND, AUX1, etc., will refer to that particular control block. The GETCHR definition shows one way to create a generalized routine, that can be applied to different control blocks by changing the value of IOCB#. Another situation where these instances have been useful is as macros for assembly language routines.
Listing 2 illustrates a similar technique, applied to a four-voice sound sequencer. Another area where this style has been applied is in the creation of attributes associated with Player /Missile graphic images.
These examples point out three aspects of good program design.
( 60 ANTIC ISSUE #5 SCREEN #1 ) 0 VARIABLE IOCB# ( AN INDEXING GLOBAL VARIABLE ) : IOCBX IOCB# @ 16 * ; ( BYTE OFSET FROM IOCB ) HEX CODE CALL-CIO XSAVE STX, ,X LDA, TAX, ( TOP STACK INTO X REG) E456 JSR, XSAVE LDX, POP JMP, ( CIO VECTOR DROP TOP STACK ) END-CODE : SERVES.AS ( DEFINING WORD ) <BUILDS , ( COMPILE BASE ADDR FOR CONTROL BLOCK 0) DOES> @ IOCBX + ; ( GET BASE ADDR ADD OFFSET ) ( 61 ANTIC #5 ) ( BUILD ACCESS WORDS ) HEX 342 SERVES.AS COMMAND ( BYTE LOCATION ) 343 SERVES.AS STATUS ( BYTE ) 344 SERVES.AS BUFF-ADDR ( 16BIT WORD ) 348 SERVES.AS BUFF-LEN ( WORD ) 34A SERVES.AS AUX1 ( BYTE ) 34B SERVES.AS AUX2 ( BYTE ) : GETCHR ( GET CHAR FROM ANY DEVICE) 7 COMMAND C! HERE BUFF-ADDR ! 1 BUFF-LEN ! ( SETUP CONTROL BLOCK ) IOCBX CALL-CIO HERE C@ ! ( LEAVE CHAR VALUE ON STACK ) ( 62 ANTIC #5 ) 0 VARIABLE V# ( INDEXING VARIABLE ) : VOICE V# ! ; : SOUNDS ( ARRAY OF VALUES ) (BUILDS HERE OVER ERASE ALLOT ( CLEAR LOCATIONS ) DOES> V# @ + ; ( GET BYTE LOCATION RELATIVE TO V# ) ( VALUES ARE COMBINED AND SENT TO HARDWARE REGISTERS ) 4 SOUNDS VOLUME 4 SOUNDS PITCH 4 SOUNDS DISTORTION ( ARRAY USED BY 1 VOICE SEQUENCER ) 4 SOUNDS BUSYFLAG 4 SOUNDS SEQUENCELENGTH 4 SOUNDS CURRENTSTEP 4 SOUNDS SEQUENCEPOINTER ( WHICH ARRAY PROVIDES VALUES FOR V/P/D )
ANTIC v2.1 / APRIL 1983 / PAGE 94
The following Forth words were compiled by John Peters, an active Forth user in San Francisco. When Screen 50 is loaded, a 25th line will appear at the top of your television screen showing the stack contents. As with all Forth words, they can be adapted, enhanced, or altered in any way suitable to end-users. If these words suggest other ideas, let us know. We encourage the entire Forth community to share their discoveries and ideas in Forth Factory.
\ 50 25th line a.k.a. DISPLAY STACK DECIMAL : DO-THRU ( from thru --- ) compile 1+ compile swap compile (do) here 3 ; immediate : PRE ( pre get-screen to buffers ) 8 * DUP 9 + SWAP DO I BLOCK DROP LOOP ; : PRES DO-THRU I PRE LOOP ; 71 75 PRES 51 LOAD 52 LOAD 53 LOAD 54 LOAD 55 LOAD : DON info-line install ; : DOFF info-line remove ; : 25th_LINE__/ ; ( Dictionary marker ) DON ;S \ 51 25th LINE NORMAL WINDOW COLCRS A VOCABULARY INFO-LINE IMMEDIATE INFO-LINE DEFINITIONS HEX VIDEO-BASE CONSTANT NORMAL ( beginning of sreen RAM for ) ( OS graphics mode ) : WINDOW ( addr --- ) ( Makes OS think screen RAM is at ) ( addr. ) 58 ! 0 0 POSITION ; 55 CONSTANT COLCRS ( OS address of cursor column # ) FORTH DEFINITIONS DECIMAL \ 52 25th line CLEAR25 DLIST B INFO-LINE DEFINITIONS HEX HERE DUP 3F + FFC0 AND SWAP - ALLOT LABEL BUFF25 DECIMAL 40 ALLOT ( Screen RAM for info line ) : CLEAR25 BUFF25 40 ERASE ; LABEL DLIST HEX ( Part of a display list which gets ) ( patched into the OS one to create ) ( the info line. ) 70 C, 70 C, ( some blank lines ) 12 C, BUFF25 , ( mode w/LMS ) 01 C, 0 , ( ANTIC JMP back ) ( to the OS ) ( display list ) FORTH DEFINTTIONS DECIMAL \ 53 25th DEC# HEX# BIN# U# BASE# C INFO-LINE DEFINITIONS DECIMAL : DEC# ( n1 --- addr n ) DUP ABS 0 <# #S SIGN #> ; : HEX# ( u --- addr n ) 0 <# # # # # #> ; : BIN# ( u --- addr n ) 0 <# 16 0 DO # LOOP #> ; : U# ( u --- addr n ) 0 <# #S #> ; : BASE# ( n1 --- addr n ) BASE @ CASE 10 OF DEC# ENDOF 16 OF HEX# ENDOF 2 OF BIN# ENDOF >R U# R> ENDCASE ; \ 54 25th Line BAC< .STACK DISPLAY D INFO-LINE DEFINITIONS DECIMAL : BAC< ( n --- ) MINUS COLCRS +! ; : .STACK ( --- ) DEPTH 2 > IF DEPTH 1+ 3 DO I (PICK) BASE# DUP COLCRS @ < IF >R R BAC< R TYPE R> 1+ BAC< ELSE DROP DROP LEAVE THEN LOOP THEN ; : DISPLAY ( --- ) LOCATION CURSOR-OFF CLEAR25 BUFF25 WINDOW 34 0 POSITION ." <-TOS" 33 0 POSITION .STACK NORMAL WINDOW POSITION CURSOR-ON ; \ 55 25th Line PATCH- ROUTINE INSTALL E INFO-LINE DEFINITIONS HEX : PATCH- ( --- ) 0230 @ DUP C@ 01 = NOT IF DUP 3 + DLIST 6 + ! 01 OVER C! DLIST SWAP 1+ ! ELSE DROP THEN ; : ROUTINE ( --- ) PATCH- DISPLAY CR ; : INSTALL ' ROUTINE CFA ' ABORT 6 + ! ' ROUTINE CFA ' QUIT 0A + ! [COMPILE] FORTH ; : REMOVE ' CR CFA ' ABORT 6 + ! ' CR CFA ' QUIT 0A + ! 0 022F C! 0230 @ 3 70 FILL 22 022F C! [COMPILE] FORTH ; FORTH DEFINITIONS DECIMAL
ANTIC v2.4 / JULY 1983 / PAGE 96
As an ATARI BASIC programmer, I began noticing Forth articles at about the same time as I noticed BASIC’s limitations. One thing that grabbed my attention was the frequent comment, “It’s real fast.”
Well, fine. Assembly Language is fast but it’s a lot of work writing anything useful in it. Macro assemblers helped a little by allowing me to create a library of useful subroutines, but they were up to me to create. What I really wanted was easy access to the graphics and sounds functions of the ATARI, at speeds fast enough to write an arcade game, without having to develop advanced programming skills. Little did I know, but I was describing Forth.
My interest in Forth led me to ask: “What’s in it for me?”, and “How hard is it to use?” To answer these questions I’ll discuss what Forth can offer the BASIC programmer, and then show the development of an example Forth “word”.
Forth is analogous to the human body. The body has systems comprised of organs, organs comprised of tissues, tissues comprised of cells. Similarly, Forth is a hierarchical structure.
This structure creates what is called extensibility. It’s like having a number of subroutines in BASIC, custom designed to perform any operation you could imagine, and calling them in the order in which they are to be executed. Extensibility makes Forth a very flexible programming environment. New commands are called words, and as they are defined, they are compiled into memory in a group called the dictionary. This compiled form of Forth executes at about ten to twenty times the speed of BASIC, or about half the speed of machine language. If this isn’t fast enough, Forth comes with an assembler which permits critical routines to be coded in machine language. The assembler itself is written in Forth, which may give you some idea of the power of this language. With the assembler, Forth can be a high-level and a low-level language at the same time.
Forth code is also very compact. Once defined, a word can be used in many places in the application program. A “bottom-up” approach to program development allows very efficient program design. Words can be used in many applications. Once you have defined a word, it can be used in any program with a suitable context.
The mechanics of Forth require getting used to, but they are not too difficult. Forth is a stack-oriented language, and uses RPN (Reverse Polish Notation) logic. The stack is used as a last-in, first-out storage area. To add four and three, you would type:
4 3 +
When you type 4 it goes on the stack. When you type 3 it goes on top of 4, pushing 4 down. When you type +, that’s the signal to add the 4 and 3, replacing these with the value 7.
The stack holds results of arithmetical calculations, and passes parameters between words.
Suppose we have a frequent need to add three to the value on the top of the stack. We could create a new word for just that purpose:
: 3PLUS 3 + ;
Whenever 3PLUS was typed from the console, that operation would be performed. Let’s take a look at the components of the above example.
The colon (:) is a Forth word which basically means “start defining”. It takes the next group of characters and prepares to make a dictionary entry called 3PLUS.
The compilation process then takes the next group of characters (the 3) and tries to turn it into a number (in this case 3). The number is now compiled into the definition of 3PLUS. The plus sign, is a Forth word which means, “add the first two numbers on the stack together, and leave the result on the stack”. The final character is the semicolon (;) which means “definition finished”. Control now returns to the console.
This new word, 3PLUS, can now be used to add the value of three to any value on top of the stack. It can also be used in higher-level definitions such as:
: 2+3+ 2 + 3PLUS ;
This would first add two, then three to the number on the top of the stack. If you don’t understand this, don’t worry. It just illustrates an important point: there is no limit to the number of ways you can structure your dictionary. Your library of commands can be as large and as varied as the memory size of your computer will allow.
Features of the ATARI not readily available from BASIC can be easily accessed at machine language speeds with Forth. This includes Player/Missile graphics, versatile sound control, redefined character sets, and custom display lists. I am currently working on an arcade game which uses multi-tasking in the Vertical Blank Interrupt, several Display List Interrupts, ANTIC 4 graphics, and a new character set. These features were easily implemented in Forth, and can be quickly modified and improved. The program executes fast enough to provide quite a challenge at the highest level of difficulty.
Let’s write a small program in both Forth and BASIC and see how they compare. This routine will produce a sweeping tone when [START] is pressed.
In BASIC:
10 IF PEEK(53279)<>6 THEN GOTO 10 20 POKE 53761,168:REM Distortion=10, Volume=8 30 FOR FREQ=10 TO 200 40 POKE 53760,FREQ 50 NEXT FREQ
In Forth:
: WAIT BEGIN 53279 C@ 6 =: ( LOOK FOR START KEY ) UNTIL; : TONE 168 53761 C! ( SET DISTORTION AND VOLUME ) 201 10 DO ( THE ACTUAL SWEEPING SOUND ) I 53760 C! LOOP ; : SWEEP ( PUT IT ALL TOGETHER ) WAIT TONE;
To understand the Forth word SWEEP, several terms should be defined. Remember, every term in WAIT, TONE and SWEEP is either a FORTH word or a number. The words contained in parentheses are comments only; they do not execute. In WAIT, C@ fetches the eight-bit value contained in the address on the top of the stack. After C@ is executed, the address is replaced with its contents. So, the definition of WAIT has this effect:
OPERATION STACK 53279 53279 C@ contents of 53279 6 contents, 6 = (compare) 1 if equal, 0 if not
The control structure BEGIN…UNTIL loops back to BEGIN until the stack contains a true (non-zero) value when it reaches UNTIL. The word WAIT acts just like line 10 of the BASIC program.
In the word TONE, we reproduce the effect of the FOR/NEXT loop by using another control structure called DO…LOOP The numbers that precede DO are the limit and the starting point, respectively. Our loop will execute by counting from ten to 200. When the index reaches 201, the loop terminates. Inside the loop, the index may be found with the Forth word I which leaves the current index on the stack. The final term to be explained is C!. It is the store operation opposite C@, and its function is this: take the second number on the stack and store it at the address at the top of the stack. The operation of TONE looks like this:
OPERATION STACK 168 168 53761 168, 53761 C! empty 201 10 201, 10 DO (begins loop) empty I (fetch index) 10 to 200 53761 10 to 200, 53761 C! empty LOOP (go back to DO if I < 201)
Finally, the words WAIT and TONE are combined to form SWEEP. One principle of Forth programming is that it is better to create many small, useful words than several large ones. WAIT could be used anywhere that requires the program to wait until [START] is pressed. We have another job for TONE below.
Both the BASIC and the Forth programs produce a rising tone. To hear the Forth tone well, however, a delay would have to be inserted in the DO…LOOP structure.
Say your game project needs a succession of five of these sweeps to signal “red alert.” In Forth, this could be accomplished using the DO…LOOP;
: REDALERT 5 0 DO TONE LOOP ;
Will BASIC give you this kind of flexibility?
The best way to judge Forth is to read the best book on the subject: Starting FORTH, by Leo Brodie. It describes the language from the ground up, and will serve as a valuable reference after you start FORTH. This book is available from the FORTH Interest Group, PO Box 1105, San Carlos, CA 94070. FIG is an excellent resource for beginning and advanced programmers alike. They will send you a catalog upon request.
ValFORTH (available from Valpar International, 3801 East 34th Street, Tucson, AZ 85713) is a very versatile Forth package which can be purchased in modules.
There is a certain fanaticism in the Forth community about this powerful young language. It seems that once you start using Forth, you never turn back. This article touches only the surface of this language; there is much more ground to cover.
Forth is a language which is immediately useful, but it will never be fully explored. It allows every ATARI programmer to get the most out of his machine.
ANTIC v2.6 / SEPTEMBER 1983 / PAGE 76
The title of this article may be a bit confusing—is it about Forth or assembly language? Well, it’s about both. This article demonstrates a powerful feature of Forth: any language, new or existing, can be written in it. Assemblers written in Forth combine the power of Forth with the speed of machine code. Because assembly is taking place under the Forth system control, macro capability and complex arithmetical calculations are available during compilation. Some features of the ATARI, such as display list interrupts and vertical blank routines, can be written only in 6502 machine code; these are the applications addressed in this article. But first we will cover some of the essentials of Forth assembler.
Because Forth is a stack-based language, it appears to be written “backwards”. The same is true for the Forth assembler. Here is a comparison of some traditional instructions versus Forth:
Traditional Forth LDA 712 712 LDA, STA COUNT,X COUNT ,X STA, INX INX,
Read the INX, instruction as “compile the opcode for the Increment X instruction.” Note that Forth instructions require the operand to precede the operation. In the second example above, COUNT has been defined previously as a constant, so that its address appears on the stack before the indexed store command is compiled. The display list example contains a standard ATARI assembler listing which is the equivalent to the Forth word GR7DLI; it demonstrates the “backwards” instruction coding and a comparison of control structures.
In traditional assemblers, the instructions which control program execution are limited to Branch and Jump group of insructions. Since there is no label field in Forth assemblers, jumping around inside a definition is not possible. In making up for the lack of a label field, the Forth assembler allows for many more ways to control program flow. The IF…ENDIF and IF…ELSE…ENDIF can be used to control execution based on the tested status of the Sign, Overflow, Zero, and Carry flags. Operation of these words is identical to the same structure in Forth. Other Forth control structures which are repeated in the Forth assembler include BEGIN…UNTIL, BEGIN…WHILE…REPEAT, and BEGIN…AGAIN. These combine to create a powerful set of control structures which greatly simplify programming.
You must know the rules of parameter passing to use the Forth assembler. Follow these rules whenever you use an assembly language definition along with other Forth words. The most important rule is that the X register must be preserved. It is the pointer to the top of the parameter stack and should be manipulated only when changes to the stack are desired. The second rule is that every assembly language definition must end with an appropriate re-entry jump; these jumps link the assembler definition with the rest of Forth. Of course, if a subroutine is being written, it may end in RTS. Two of the examples in this article end in other ways. However, these words are never executed within Forth, as their return statements would cause the ATARI to lock up. Selection of the appropriate re-entry point will depend on your requirements for the stack; these points are fully described in the assembler documentation.
Screens 10 through 13 are the words needed to install and test a vertical blank routine. The first screen contains the word SUBROUTINE, which is supplied in the ValFORTH documentation. This is a defining word which returns its address when executed; it also invokes the ASSEMBLER vocabulary. It is used on both the vertical blank routine and the display list interrupt, and it passes its address to the Forth word responsible for instafling the routine.
The definition of INSTALL is a good example of a traditional Forth assembler word. Note that INSTALL begins with CODE instead of ‘:’, and ends with a C; instead of ‘;’. These are defining words which switch in the ASSEMBLER vocabulary while the definition is being compiled. The word INSTALL is designed to synchronously set the deferred vertical blank vector; this prevents the vertical blank routine from occurring while the new vector is being set up. INSTALL can also be used to set vectors for system timers 1-5 and the immediate vertical blank by changing the #7 in line 10 of Screen 11 as follows:
Vector Value Timer 1-5 # 1-5 Imm. VBLANK # 6
Screen 12 contains the world VBLANK, which turns the routine on and off, and uses INSTALL to insert the address in the appropriate subroutine. Screen 13 contains the vertical blank subroutine, a simple word which increments the background color in any graphics mode. TEST is the word which brings it all together, installing the address and diverting the vertical blank flow through the subroutine ROTBAK. Note that ROTBAK ends with JVB JMP,. JVB is the re-entry point for the vertical blank routine and is defined as a constant on Screen 11. All vertical blank routines must end this way.
To use, LOAD Screens 10-13 and type TEST. The background color should begin to change. To turn off this routine, type OFF VBLANK. It is important to remember to turn off vertical blank routines before doing anything to the Forth dictionary. If you FORGET all or part of your routine, the vertical blank vector will be pointing at nothing, and the machine will lock up.
This gets a little more spectacular. Screens 20 through 23 contain the installation word, the display list modifier, and a test word. Screen 20 is the display list equivalent of the INSTALL word used in vertical blanks. DLI installs the subroutine address and enables display list interrupts. DLIMOD, on Screen 21, sets the interrupt bit on each Graphics 7 instruction in the display list. Screen 22 is the actual interrupt routine.
GR7DLI increments the variable COUNTR each time the subroutine is called. Since there are 79 interrupts in our modified display list, COUNTR is reset to zero after 79 (hex 4F) executions. COUNTR is used in the X register as an index into COLTAB (color table) which is really page 2. The color selection in this example is therefore random, but COLTAB could easily point to a user-defined color table. GR7DLI is the Forth translation of the routine which appears on pages 5–6 of De Re Atari, which also contains an excellent discussion of display list interrupts and their application. Note that the subroutine GR7DLI ends with RTI, since it will be called as an interrupt by the operating system. 80COLORS is optimistically named, since many of the colors from our page two color table are black, but it does produce an interesting display when executed. Note that Screen 22 uses SUBROUTINE, so Screen 10 must be loaded first.
Accompanying this example is a listing of GR7DLI (Listing 2) written with the Atari Assembler Editor. Check this listing with Screen 22 to see how the Forth assembler compares. It may be easier to learn the “backward” assembler using this as a basis for comparison.
After loading Screens 20-23, try Screens 24 and 25. RAINBOW is a routine which increments COUNTR, then makes it the background color. This makes every line on the screen a different color. After 79 interrupts, COUNTR is reset to the value of RESET. SPECTRUM installs RAINBOW and turns the DLI routine on. CYCLE is a vertical blank routine that either increments or decrements COUNTR based on the value of MVFLG. MOVECOLORS uses the constants UP or DOWN to determine the direction of color movement on the screen. To make the colors swim upwards, type UP MOVECOLORS; DOWN MOVECOLORS moves them down. This is not a scroll routine; try some PLOT and DRAWTO statements and note that the drawn figure stays stationary while the background colors move.
The Forth assembler can be used to code critical routines for speed, and it can be used for machine language routines such as those described above. For further study in this area, I recommend ValFORTH’s 6502 Macro Assembler, and De Re Atari, available from APX.
Listing 1 Scr #10 ( GRAPHICS - SUBROUTINE WORD : SUBROUTINE 0 VARIABLE -2 ALLOT [COMPILE] ASSEMBLER ?EXEC !CSP ; --> Thanks to Valpar International for the use of this word. Scr #11 ( VBLANK EQUATES, INSTALL ) HEX 224 @ CONSTANT OLDBLANK E45C CONSTANT SETVBL E462 CONSTANT JVB DECIMAL CODE INSTALL ( ADDR -- ) # 1 LDA, SETUP JSR, XSAVE STX, N LDY, N 1+ LDX, # 7 LDA, SETVBL JSR, XSAVE LDX, NEXT JMP, C; Scr #12 ( GRAPHICS - VBLANK ROUTINE ) 0 CONSTANT OFF 1 CONSTANT ON : VBLANK ( ADDR/ON or OFF - ) IF INSTALL ELSE OLDBLANK INSTALL ENDIF ; --> To use: Compose SUBROUTINE, then turn on with: SUBROUTINE ON VBLANK Turn off with: OFF VBLANK Scr #13 ( GRAPHICS - TEST VBLANK ) SUBROUTINE ROTBAK 712 INC, ( INCREMENT COLOR4 ) JVB JMP, ( MUST END THIS WAY ) : TEST ROTBAK ON VBLANK ; Scr #20 ( DISPLAY LIST INSTALLATION ) : DLI ( ADDR/ON or OFF -- ) IF 192 54286 C! 512 ! ELSE 64 54286 C! ENDIF ; --> To use: Compose SUBROUTINE, then turn on with: SUBROUTINE ON DLI Turn off with: OFF DLI Scr #21 ( MODIFY THE GR. 7 DISPLAY LIST) : DLIMOD 7 GR. 560 @ ( FIND DISPLAY LIST ) DUP 85 + SWAP 6 + DO I C@ 128 + I C! LOOP ; --> Sets the high bit in every mode 7 (Antic D) line. Scr #22 ( DISPLAY LIST INTERRUPT ) HEX 0 VARIABLE COUNTR D01A CONSTANT COLBAK D40A CONSTANT WSYNC 0200 CONSTANT COLTAB SUBROUTINE GR7DLI PHA, TXA, PHA, COUNTR INC, COUNTR LDX, COLTAB ,X LDA, WSYNC STA, COLBAK STA, # 4F CPX, EQ IF, # 0 LDA, COUNTR STA, ENDIF, PLA, TAX, PLA, RTI, C; DECIMAL --> Scr #23 ( 80 COLORS ON THE SCREEN ! ) : 80COLORS DLIMOD GR7DLI ON DLI ; Scr #24 ( GRAPHICS 7 RAINBOW ) 0 VARIABLE RESET SUBROUTINE RAINBOW PHA, COUNTR INC, COUNTR LDA, WSYNC STA, COLBAK STA, SEC, RESET SBC, # 79 CMP, EQ IF, RESET LDA, COUNTR STA, ENDIF, PLA, RTI, C; : SPECTRUM DLIMOD RESET @ COUNTR ! RAINBOW ON DLI ; Scr #25 ( CYCLE THE COLORS IN VBLANK ) 1 VARIABLE MVFLG SUBROUTINE CYCLE PHA, MVFLG LDA, EQ IF, COUNTR INC, ELSE, COUNTR DEC, ENDIF, PLA, JVB JMP, C; 1 CONSTANT UP 0 CONSTANT DOWN : MOVECOLORS ( UP or DOWN -- ) MVFLG ! CYCLE ON VBLANK SPECTRUM : -->
Listing 2
0100 ; ATARI Assembler Editor
0110 ; equivalent to the Forth word
0120 ; GR7DLI
0130 ;
0140 *= $600
0150 ;
0160 COLBAK = $D01A
0170 WSYNC = $D40A
0180 COLTAB = $0200
0190 ;
0200 GR7DLI
0210 PHA
0220 TXA
0230 PHA
0240 ;
0250 INC COUNTR
0260 LDX COUNTR
0270 LDA COLTAB,X
0280 STA WSYNC
0290 STA COLBAK
0300 CMP #$4F
0310 BNE ENDDLI
0320 LDA #0
0330 STA COUNTR
0340 ;
0350 ENDDLI
0360 PLA
0370 TAX
0380 PLA
0390 RTI
0400;
0410 COUNTR .BYTE 0
ANTIC v2.7 / OCTOBER 1983 / PAGE 32
Before I started programming in Forth, I had done a fair amount of work designing special character sets. I had also been using the VersaWriter from Versa Computing, Inc. All this work was stored away under Atari DOS format. One of my first tasks was to salvage this work for use in my Forth programs.
Atari DOS and Forth use a different scheme for numbering disk sectors. DOS begins numbering with one while Forth begins with zero. Forth also uses all 128 bytes for data, while DOS uses the last three bytes for a file number identification, character count and the number of the next sector in the chain. The 128th byte of a DOS sector (byte 127) contains the number of bytes used in the sector. This is usually 125 except in the case of a final short sector. Bytes 125 and 126 contain the file number I.D. (six bits) and the “forward sector pointer” (ten bits). The Forth words in the listings take care of these minor complications and allow you to move DOS sectors to Forth sectors easily.
The code is valFORTH 1.1, which is an extended fig-Forth. One of their diskettes contain words to load and invoke special character sets. The same diskette contains a character editor and a sound editor as well as words for Player/Missile graphics.
The words MOVEIT, NEXTSECTOR and CHAIN on Screens 5 and 6 of the listing generalize the movement of DOS sectors to Forth sectors. The rest of the listing contains two application examples.
CHAIN uses MOVEIT and NEXTSECTOR to follow the DOS sector links and stores the data at PAD.
NEXTSECTOR expects the current buffer address on the stack and leaves the next sector number.
MOVEIT gets the character count from the section-link byte, moves the proper number of characters, and increments the address at DEST.
GETFONT expects the source DOS sector number and the destination Forth screen number on the stack. The Forth screen number is multiplied by four to convert it to a BLOCK number and the DOS sector number is decremented to compensate for the difference in the way sectors are numbered in the two systems. It then uses CHAIN to read the DOS sectors. At lines 12 to 15 the data stored at PAD is moved to the BLOCK buffers, UPDATEd and FLUSHed to the disk.
VERSA@ expects the Atari Graphics Mode number and the starting DOS sector number on the stack. It then uses CHAIN to read the DOS formatted picture data. It uses the address of the display memory stored in location 88 by the ATARI OS. This address is decremented by 14 to provide space for the color information stored with the picture. This information is then used by the words GR8CLRS or GR7CLRS to set the proper colors.
BLANKSCREEN is optional, it just blanks the screen while the picture is being read in. If you want to see the picture being formed, you can omit this word. If you do omit it, your picture may not be in the correct colors until the set color words in lines 8 thru 12 are executed.
Scr # 5 ( DOS TO FORTH ) 0 VARIABLE DEST 2 ALLOT : MOVEIT ( ADDR. -- ADDR.) DUP DUP 127 + C@ DUP tROT DEST @ SWAP CMOVE DEST +! ; : NEXTSECTOR ( CURR.BLK.ADDR -- NXT.SECT) DUP 125 + C@ 3 AND 256 * SWAP 126 + C@ + 1- ; --> Scr # 6 ( DOS TO FORTH ) : CHAIN ( FORTH.SECTOR. # -- ) BEGIN BLOCK MOVEIT NEXTSECTOR DUP -1 = UNTIL DROP ; : ?RETURN ." PRESS RETURN." BEGIN KEY 155 = UNTIL ; --> Scr # 7 ( DOS TO FORTH .. GETFONT) : GETFONT ( DOS.SECT.# FORTH.SCREEN.# -- ) 4 * SWAP 1- PAD DEST ! CR ." INSERT SOURCE DISK." ?RETURN CHAIN CR ." INSERT DEST. DISK." ?RETURN 1024 0 DO 1+ DUP 1- BLOCK PAD I + SWAP 128 CMOVE UPDATE 128 +LOOP DROP FLUSH ; --> Scr # 8 ( VERSAWRITER PICTURE FETCH ) : BLANKSCREEN ( -- ) 0 0 0 SE. 1 0 0 SE. 2 0 0 SE. 4 0 0 SE. ; : GR8GLRS ( ADDR. -- ) DUP 4 + C@ 1 0 ROT SE. DUP 6 + C@ SWAP 5 + C@ 2DUP 2 <ROT SE. 4 <ROT SE. ; --> Scr # 9 ( VERSAWRITER PICTURE FETCH ) : GR7CLRS ( ADDR. -- ) DUP 4 + C@ OVER 7 + C@ 0 <ROT SE. DUP 5 + C@ OVER 8 + C@ 0 <ROT SE. DUP 6 + C@ OVER 9 + C@ 2 <ROT SE. DUP 10 + C@ SWAP 11 + C@ 4 <ROT SE. ; --> Scr # 10 ( VERSAWRITER PICTURE FETCH ) : VERSA@ ( GR.MODE DOS.SECTOR.# -- ) 1- SWAP GR. BLANKSCREEN 88 @ 14 - DEST ! CHAIN ( NOW GET COLORS ) 88 @ 14 - DUP 3 + C@ 30 = IF GR8CLRS ELSE GR7CLRS ENDIF;
valFORTH 1.1
Valpar International
3801 E. 34th Street
Tucson, Arizona 85713
609-790-7141
800-528-7070
24K—Diskette
#59.95
Reviewed by Bill Van Hassell
Being a full time programmer for IBM and DEC mainframes, my home ATARI was used mainly for game playing. I had programmed some BASIC and experimented with special character sets and other graphics but didn’t want to spend the time needed for Player/Missile graphics and display list formatting.
Then I bought Valpar Internationals implementation of Forth. Using their system, programs that I would not have attempted in BASIC have been finished in an afternoon. My first game program was unplayable until I put delay loops in to slow it down!
As a programming language, Forth is 15 to 20 times faster than BASIC and gives you control over the full capabilities of your computer. The Valpar implementation is the fig-Forth model with many ATARI-specific extensions.
The complete system comes on eight diskettes, but you can get started with valFORTH 1.1 which contains the Forth kernel, 6502 assembler and editors. In addition to these fundamentals, this first diskette also has sound and graphics, floating point, debugging aids, printer utilities and disk format and copy words.
The other seven diskettes extend your programming power to include Player/Missile graphics, character and sound editing, display list formatting and interrupts, text formatting, turtle graphics and more. Quite an impressive array!
The system core requirements will vary depending upon your choice of features, The kernel itself requires 24K RAM. My “working” system includes some sections from all but the valDOS diskettes and uses about 35K. This may sound like a lot of core, but applications link into stored code and require little in additional core.
The documentation is good. Each diskette has its own tutorial that takes you through the material step by step. This section is followed by a glossary that describes the function of each of the words in the package. Source code listings are provided for all the extensions, and several demo programs are included to further explain the system.
I would recommend Forth and this particular implementation to anyone who really wants to control the ATARI.
ANTIC v2.10 / JANUARY 1984 / PAGE 105
Those of you who intend to do serious number crunching often need to trace calculations throughout your programs, usually for comparison with hand calculations of the problems. In Forth, this is difficult. There does not appear to be an easy way to print out calculations for validation. Further, if you have “overflow,” you cannot even tell when and where it occurred. To improve this situation, I developed a couple of techniques that I will share with you here.
I am designing a game that requires speed, and I elected to program in Forth. The program contains more than 60 equations, most of which result in large numbers. As you know, there is a +/- 32768 maximum value in the integer, non-floating-point mode. Using existing Forth words, I scaled the calculations up and down—generally by 1000—to keep the answers within the integer range. To accomplish this, I used a standard Forth definition, */, the notation for which is:
( n1 n2 n3 -- n )
This definition first uses the 32-bit register for the multiplication of the first two items, and then divides by the third item, leaving the answer as a single word on top of the stack. The value should be less than 32768.
In non-Forth notation, this operation is:
n = ( n1 * n2 ) / n3
In Forth, the data load-in might look like:
22222 3 100 */
which produces 666.
Well, I was moving right along, crunching through equations, when I discovered that random values exceeded the 16-bit limit of 32768, and overflowed, thereby losing the most significant bits of the answer. This was disastrous. To make matters worse, I couldn’t tell where in the sequence the overflow(s) occurred. Though my knowledge of assembly language is sketchy, I was sure I needed to check the overflow bit of the Processor Status register.
Unfortunately, the PS register only shows overflow in addition, and I didn’t know enough to break into the machine code so it would print a warning showing when and where an overflow occurred. I wasted a week of evenings thinking about ways to observe overflow.
Finally, there came a glimmer of hope. I was using */ which is a Forth word (definition) that I could modify. The definition for */ was essentially */MOD, which was:
: */MOD >R M* R> M/ ;
I decided to insert a DUP and PRINT after M* and M/, so that by comparing the two side-by-side values (which generally would be different by a scaling factor of 10, 100, 1000, etc.) I could see if the significant figures changed. If so, an overflow was likely. Examples of this would be:
66666 666 (okay, division by 100 probable) 22222 22222 (okay, no scaling) 66666 -1 (overflow, bad scaling chosen)
By tracing through and numbering each */ used on each screen in the program, I was able to identify exactly where overflow had occurred.
I also found a need to turn off the trace facilities, so I created a flag for this purpose. The calculations are in a big loop that prints out the final values once, at the end of the loop. I’ve injected the ?TERMINAL to break into the calculations at the end of the loop. When normal output shows that an overflow has occurred, I invoke the yellow terminal key, set the trace flag to one (1) with:
1 TFLAG !
and continue to run the program. Now the */ printout shows the suspect values.
To accomplish this, the following */MOD definitions are redefined:
0 VARIABLE TFLAG : */MOD >R M* TFLAG @ IF 2DUP D. R> M/ DUP . CR ELSE R> M/ ENDIF ; : */ */MOD SWAP DROP ;
Note that the */ did not change from the original definition, but it must be redefined because the */MOD it calls is now a new definition.
Remember, you must first boot your Forth system, then load these new definitions, and then load your program in order for it to use the redefined sequence.
Still, I had occasional troubles. Because my number-crunching problem was so complex, I couldn’t do a good hand calculation to check the answers. So I made a complete copy of my Forth disk and proceeded to convert the entire program to floating-point arithmetic. This was not as time consuming as I had feared; it was just a short-term fix to check the equations and their implementation. I still intended to return to integer calculations when I got the calculations correct. Having just conquered the */ integer problem, 1 was led to modify the floating-point operators in a similar way.
As you Forth users know, there are four main floating-point operators: F+, F-, F*, and F/. Again, the plan is to load the original operator definitions, then to insert the modified ones that suit your needs, and then to load the program. In this case, I wanted to see the result of each operation and to compare it with the hand calculations. The technique developed here is simply to perform a FDUP and then a F. each time an operation occurs. This prints out only the floating-point value, with no identification, so I inserted a ." to print the operator symbol as a clue to the origin of the number. The modified definitions become:
: F+ F+ FDUP CR ." + " F. ; : F- F- FDUP CR ." - " F. ; : F* F* FDUP CR ." * " F. ; : F/ F/ FDUP CR ." / " F. ;
Then it was relatively easy to line up the symbol identification with each screen, and thereby have numerical values that could be compared directly for each operation to the hand-calculated values. This was progress! Of course, the TFLAG approach, used in the */ modification above, can also be used here to turn the trace on and off.
In my regular work as an engineer, I constantly use computer programs. To get them working properly, I usually trace and inspect the values of each step and compare them to hand calculations. Until now, I had not been able to do this at home. But using these little mods to the standard Forth operators, I am now able to follow the number crunching and have a way to find (most of) my errors. Maybe I’ll soon get this monster program checked out and on the market for big bucks. If I don’t, at least I will have had the satisfaction of discovering something new for this neat language called Forth.
ANTIC v2.12 / MARCH 1984 / PAGE 75
This article describes a Forth program to disassemble 6502 machine code instructions. Using it, you can get a listing of assembler mnemonics to help you figure out how programs written by others are put together. This is one of the best ways of improving your programming skills.
The article is divided into two parts. The first part provides some background information on the 6502 instruction set, to help you understand how the disassembler works. It is not necessary to read this part to get the disassembler working, but it will help you to understand the output. The second part describes the program itelf and gives a sample result from running it.
Machine instructions can contain up to three bytes, the first of which is the operation code (telling the machine what to do), and the remainder give the operand or its address. These “address” bytes can be interpreted in one of several different ways, depending on the “addressing mode.”
Imagine that we are considering an instruction with opcode “OP” and the next two bytes contain the two hex numbers AB and CD, respectively (remember that each 8-bit byte contains 2 hex numbers):
: O P : A B : C D :
The “Table of Address Modes” lists the various 6502 addressing modes and describes how the hex numbers ABCD following the opcode are to be interpreted.
In the 6502, absolute addresses require two bytes and the most significant digits of the address are stored in the byte with the highest address. That is why the absolute addresses are shown as CDAB. The notation (X) indicates “the contents of the X register.” In this notation (OOAB) + (X) indicates “the contents of the memory byte at address OOAB plus the contents of the X register.” A comma is used to separate the high and low bytes of an address where clarity requires.
All multiple address mode instructions in the 6502 instruction set can be used in the absolute address mode. The numerical “mode number” shown in the Table of Address Modes is the difference between an instruction’s absolute address opcode and its “mode” opcode (plus hex 10 to avoid negative mode numbers).
A table of the absolute address opcodes (+10) for the various multiple address instructions, called MULTIMODE, is included in Screen 30. Given an arbitrary opcode (say 65) we can find the first entry in MULTIMODE which exceeds the opcode (7D in this case) and subtract to get the mode number (08, corresponding to Zero Page, X). The mnemonic can be read as ADC from the ninth entry in MULTINAME (7D is the ninth entry in MULTIMODE).
The 22 entries in MULTIMODE account for 117 of the 151 valid 6502 opcodes (out of a maximum of 256 possible). The remaining 34 opcodes each identify a single address mode instruction and are dealt with by looking up tables called ONEMODE and ONENAME. These tables also include ten, renegade, multiple-address opcodes that, for reasons best known to the 6502 designers, don’t result in correct mode number. The most irregular instruction is LDX, where only two of its five address modes fit the pattern. That is why LDX appears three times in ONENAME.
Given an arbitrary opcode, the first step in the disassembly process is to check the list of single address mode opcodes. If the wanted opcode is present, the instruction type is known immediately. If not, the list of multiple mode opcodes is used to determine both the instruction type and the addressing mode.
Now for the disassembler program itself. The listing appears in screens 30 to 35. As is usual in Forth listings, the interesting part of the program appears last. The first few screens contain the building blocks from which the main program, called “DISASSEMBLE,” or “DIS” for short, is constructed.
Before describing how DISASSEMBLE works, I shall define what each of the words used in the program does.
Armed with the definitions of the “building blocks,” we can now analyze the “main program.” I have found the coding form used in the box headed “Description of Disassemble” useful for both analyzing and writing Forth code. The first column is an instruction number(for reference); the second, the contents of the stack; and the third, the instruction (Forth word). The number against a stack entry indentifies the instruction removing that entry from the stack.
You can use a form like the one I have just described to analyze the remaining code. Of course, you will need to know Forth or have in front of you the fig-Forth glossary. Finally, here is an example of using the disassembler to see how the Forth word C@ replaces an address on the stack with the contents of that address. The process is started by entering ' C@ DIS [RETURN]. This sequence puts the parameter field address of C@ on the stack and starts disassembly. The result looks like:
13FB LDA X) 0 13FD STA ,X 0 13FF STY ,X 1 1401 JMP .. F47 OK
Note that the address 0,X points to the byte on the bottom of the data stack (it grows down!) and 1,X is the next byte up. F47 is the address of the Forth procedure NEXT, which passes execution to the next Forth word.
The first instruction loads the accumulator with the byte which was at the 16-bit address on the “top” (physically at the bottom) of the stack. The second instruction at 13 FF stores the contents of the Y register (which you can count on being zero) into the high order byte on “top” of the stack. Thus the address on the “top” of the stack is replaced by the byte which was (and still is) stored at that address.
A word of warning: DISASSEMBLE will disassemble anything! It does not try to stop you from disassembling data. Forth code or even machine code starting at the wrong point. However, you can easily detect a listing of gibberish. The listing will tend to be long (over a screen), the addresses will be all over the place and rarely used instructions will pop up frequently.
Step Stack Instruction Comment OPadd (2) Address of current opcode 1 POINTER! Store OPadd in POINTER 3 CR Start a new line. 4 BEGIN CR Start a loop with a new line. 6 OPadd (11) POINTER @ Fetch the opcode address. 7 OPadd (9) DUP 8 O (9) O Print the address (double precision - 9 D. to avoid negative addresses!). 10 2 SPACES Leave 2 spaces. 11 OP (14) C@ Fetch the opcode. 12 OMad (14) ' ONE MODE Calculate the start address for - ONEMODE table. 13 2D (14) 2D ONEMODE is 2D (45) entries long. 14 I (20)(27) SEARCH For OP in ONEMODE table. Leave - f (15) Index and flag. 15 IF Test f. False part starts Step 26. 16 1 (18) DUP TRUE part (f=1), i.e., OP in ONEMODE table. 17 ONad (18) ' ONENAME Start address for ONENAME table. 18 PRINTNAME Type the mnemonic 20 2I+1 (23) 2 * 1 + 22 OMad (23) ' ONEMODE Start address of ONEMODE table. 23 OMad+21+1 (24) + Address of MODE for entry I. 24 MODE (25) C@ Fetch MODE. 25 MODE/4 (41) 4 / True part jumps to Step 38. 26 ELSE FALSE part OP not in ONEMODE. 27 DROP The index left by SEARCH (Step 14). 28 OP POINTER @ c @ Prepare to search MULTIMODE - 29 MMad ' MULTIMODE table for opcode. Table is 30 16 16 16 (hex) entries long. 31 I (32) SEARCH For OP. Leave Index and flag. f (32) f is not used in this case 32 J (33) CHKMODE Check whether MODE for entry - MODE/4 (33) 1 in MULTIMODE table is - 33 J (34) CHKMODE divisible by 4. If it is - MODE/4 (34) return J = I otherwise J = I + 1. 34 J (35) CHKMODE It may be necessary to - MODE/4 (41) increment 1 twice (3 CHKMODEs). 35 J (37) SWAP 36 MNad (37) ' MULTINAME Start address for MULTINAME table 37 PRINTNAME type the mnemonic 38 ENDIF terminates the IF at line 15. 39 MODE/4 (40) DUP 40 PRINTMODE Print address mode mnemonics 41 f (43) PRINTADD Print the address part of the - instruction and update the - pointer, f = 1 indicates a - jump instruction (finish), 42 f (43) ?TERMINAL f = 1 indicates a key is - pressed (finish). 43 f (44) OR 44 UNTIL Jump to BEGIN (step 4) if f=0 45 ; END
SCR #30 0 ( FORTH DISASSEMBLER ANTIC 3/84 ) 1 HEX 0 VARIABLE MULTIMODE -2 ALLOT 2 1D , 1E , 3C , 3D , 3E , 5C 3 5D , 5E , 7D , 7E , 9C , 9D 4 9E , BC , BD , BE , DC , DD 5 DE , FC , FD , FE , 6 7 : SEARCH 1 + DO OVER OVER I 8 * + C@ - DUP 0= IF DROP 9 DROP DROP I 1 LEAVE ELSE 10 IF DROP DROP I LEAVE 11 ENDIF ENDIF LOOP ; 12 13 0< VARIABLE POINTER 14 15 --> SCR #31 0 : STRING ( COMPILE TEXT ) 1 BL BLK @ IF BLK @ BLOCK ELSE 2 TIB @ ENDIF IN @ + SWAP 3 ENCLOSE IN +! OVER - >R + 4 HERE R CMOVE R> ALLOT ; 5 6 0 VARIABLE MULTINAME -2 ALLOT 7 8 STRING ORAASLBITANDROLJMPEORLSR 9 STRING ADCRORSTYSTASTXLDYLDALDX 10 STRING CPYCMPDECCPXSBCINC 11 12 : CHKMODE DROP DUP 2 * ' 13 MULTIMODE + @ POINTER @ C@ - 14 4 /MOD SWAP IF SWAP 1+ SWAP 15 ENDIF ; --> SCR #32 0 0 VARIABLE ONEMODE -2 ALLOT 1 2C00 , 2C08 , 280A , 3010 , 2 2C18 , 1020 , 2C28 , 282A , 3 3030 , 2C38 , 2C40 , 2C48 , 4 284A , 3050 , 2C58 , 2C60 , 5 2C68 , 286A , 246C , 3070 , 6 2C78 , 2C88 , 2CBA , 3090 , 7 2096 , 2C98 , 2C9A , 14A0 , 8 14A2 , 2CA8 , 2CAA , 30B0 , 9 20B6 , 2CB8 , 2CBA , 04BE , 10 2CC8 , 2CCA , 30D0 , 2CD8 , 11 2CE8 , 2CEA , 30F0 , 2CF8 , 12 00FF , ( 00FF IS A DUMMY ) 13 14 15 --> SCR #33 0 0 VARIABLE ONENAME -2 ALLOT 1 STRING BRKPHPASLBPLCLCJSRPLPROL 2 STRING BMISECRTIPHALSRBVCCLIRTS 3 STRING PLARORJMPBVSSEIDEYTXABCC 4 STRING STXTYATXSLDYLDXTAYTAXBCS 5 STRING LDXCLVTSXLDXINYDEXBNECLD 6 STRING INXNOPBEQSED??? 7 8 0 VARIABLE MOBE -2 ALLOT 9 STRING ,X,Y,X)Y..##0PX).Y().AIMRE 10 11 0 VARIABLE LENGTH -2 ALLOT 12 2 C, 2 C, 1 C, 1 C, 2 C, 1 C, 13 1 C, 1 C, 1 C, 2 C, 0 C, 0 C, 14 1 C, 0 C, 15 --> SCR #34 0 : PRINTNAME SPACE SWAP 3 . + 1 3 TYPE 2 SPACES ; 2 3 : PRINTMODE 2 * ' MODE + 2 TYPE 4 2 SPACES ; 5 6 : PRINTADD POINTER @) C@) OOP 20 7 = OVER 40 = OR OVER 4C = OR 8 OVER 60 = OR SWAP 6C = OR 9 SWAP ! LENGTH + C@ 1 POINTER 10 +1 POINTER @ OVER POINTER +! 11 OVER 0= IF DROP DROP ELSE 12 OVER 1 = IF C@ . DROP ELSE 13 @ D. DROP ENDIF ENDIF : 14 15 --> SCR #35 0 : DISASSEMBLE POINTER ! CR 1 BEGIN CR 2 POINTER @ DUP 0 D. 2 SPACES 3 C@ ' ONEMODE 2D SEARCH 4 IF ( FOUND ) DUP ' ONENAME 5 PRINTNAME 2 * 1+ 6 . ONEMODE + C@ 4 / 7 ELSE ( NOT ) DROP POINTER @ 8 C@ ' MULTIMODE 16 SEARCH 9 CHKMODE CHKMODE CHKMODE 10 SWAP ' MULTINAME 11 PRINTNAME ENDIF 12 DUP PRINTMODE PRINTADD 13 ?TERMINAL OR 14 UNTIL ; 15 : DIS DISASSEMBLE ;