CF83-2: Forth Tutorial - Page 1 of 24 CF83-2: Forth Tutorial CF83 Forth Copyright (c) 1991 by M. David Johnson BDS Software P.O. Box 485 Glenview, IL 60025-0485 You may make as many copies of this document as you wish for your own use, but you may not sell or give away any copies to anyone else. "Thou shalt not steal" Exodus 20:15 Welcome to CF83, a 1983 Standard Forth for the 64K CoCo II, 128K CoCo III, and 512K CoCo III, with at least one standard RS-DOS SSDD floppy disk drive. This Tutorial will give you a general introduction to the Forth Operating System/Programming Language, but it will be of little practical use unless you have also purchased BDS Software's product, CF83 Forth, the foundation disk for the CF83 Forth System. You will gain the most from this tutorial if you print it out on your printer and then use CF83 to follow along and work through the examples. In addition to your CF83 Forth disk, have a blank formatted disk available. If you have not already done so, we strongly recommend that you obtain a copy of the second edition of STARTING FORTH by Leo Brodie, Prentice-Hall, Englewood Cliffs, NJ, 1987. We feel that STARTING FORTH is the best introduction to forth programming available anywhere. STARTING FORTH is also available from the Forth Interest Group, P.O. Box 8231, San Jose, CA 95155, phone (408) 277-0668, fax (408) 286-8988. Their price was $29.00 + $3.00 handling as of April 1991. California residents must also add California sales tax. Before you do anything else, BACKUP THE DISK! Put the original away in a safe place. Only use the backup copy. ** DOWN TO BUSINESS Put your CF83 Forth disk in Drive #0 and enter: RUN "CF83" CF83-2: Forth Tutorial - Page 2 of 24 Press any key to clear the opening screen. The "ok" prompt appears. CF83 Forth is an Extensible, Stack Oriented, Reverse Polish, Threaded Interpreter. ( Ouch! Is this tutorial going to be filled with big words like these? -- No! But this definition is critical: please stick with us for just a few moments. ) When you turn your CoCo on, you are in BASIC. BASIC is an interpreter. When you LOAD and RUN a BASIC program, the BASIC interpreter analyzes each program line, translates it into machine code, and then executes that code. BASIC programming is interactive: you can make changes in your program and test those changes immediately. This makes program development and debugging comparatively easy. But, because BASIC must analyze and translate each program line every time the program is run, BASIC programs are also comparatively slow. If you use an assembler (such as EDTASM) or a compiler (such as C or PASCAL), you write your program in an ASCII source code file and then assemble or compile and link it to a machine language object code file. When you want to run the program, you simply LOADM and EXEC the machine language object code file. The machine language executes directly: it doesn't have to be analyzed and translated each time the program is run. Therefore, machine language is comparatively fast. But assemblers and compilers are not interactive: if you make any changes in the program, you have to re-assemble or re-compile and re-link it before you can test the changes. This makes program development and debugging comparatively difficult. A threaded interpreter builds fully analyzed and translated machine language code by compiling new words as you write them into the program. A forth program consists of a list of word definitions, where each word definition is simply a list of previously defined words. Once it has been defined, a forth word is like a BASIC subroutine - it can be called over and over again, and it can be called from other subroutines which themselves can be called over and over again, etc., etc., etc.!! Depending on the application, forth programs can run almost as fast as assembled machine code. And they are almost as easy to develop and debug as BASIC programs. Aha! The best of both worlds! CF83-2: Forth Tutorial - Page 3 of 24 So........ Where's the catch? Well...... First of all, you have to learn a whole new way of thinking. Forget everything you've ever learned about computer programming! Start from scratch. CF83 Forth is an Extensible, Stack Oriented, Reverse Polish, Threaded Interpreter. Forth is Stack Oriented. BASIC and other languages are Variable Oriented. In BASIC, you store numbers in variables and then use the variable name whenever you want to access the number or pass it to a subroutine. Forth also uses variables when it has to, but most of the time it stores numbers on a stack and passes numbers to other words via the stack. A stack is a simple Last-In, First-Out (LIFO) storage device. Suppose you have a handful of pennies and you stack them one on top of the other on your desk. Now, if you want a penny, you take it off the top of the stack. You don't try to take it from the bottom or the middle of the stack because the entire stack would fall down. So, the last penny you put on the stack is the first penny you take off the stack, i.e. LIFO. The stack orientation saves memory - less memory is devoted to variable storage. You might think that the variable memory you save would be offset by the memory needed for the stack. But BASIC and other languages also use stacks. Programmers just aren't aware of those stacks because programmers aren't allowed to use them directly - the stacks are reserved for internal use by the language itself. CF83 Forth is an Extensible, Stack Oriented, Reverse Polish, Threaded Interpreter. Stack operations are more efficient if you use Reverse Polish Notation (RPN). RPN should be familiar if you own one of the more expensive Hewlett-Packard Scientific Calculators. Some less expensive HP calculators and most others use Algebraic Notation (AN). AN is familiar and easy to understand. If we want to add 3 and 5 with an AN calculator, we simply press: 3 + 5 = and the answer 8 appears in the display. With a RPN calculator, we press: CF83-2: Forth Tutorial - Page 4 of 24 3 ENTER 5 + and the answer 8 appears in the display. The answer is the same, but the procedure isn't as clear or natural. Similarly, under BASIC, we would enter: PRINT 3 + 5 but in CF83 Forth, we would enter: 3 5 + . ( When you try these examples using CF83, be sure to include the spaces - CF83 requires them. ) where "." in forth does the same thing as "PRINT" in BASIC. Algebraic Notation makes things easier for us. But RPN makes things easier (spell that F A S T E R ) for the CoCo. Forth programmers will readily testify that the additional speed is well worth the added effort. CF83 Forth is an Extensible, Stack Oriented, Reverse Polish, Threaded Interpreter. Forth is Extensible. In BASIC and other languages you have a set of reserved operators and keywords like: + - * / PRINT INPUT RETURN STRING$ etc. These are the only words you can use in writing programs. If you want to PRINT "This Phrase" at three different points in the program, you either have to repeat the program line three times: 110 PRINT "This Phrase" . . 260 PRINT "This Phrase" . . 430 PRINT "This Phrase" or set up a subroutine and call it three times: CF83-2: Forth Tutorial - Page 5 of 24 110 GOSUB 1000 . . 260 GOSUB 1000 . . 430 GOSUB 1000 . . 1000 PRINT "This Phrase" 1010 RETURN But in CF83 Forth, when you define a new word, you can use it just like any other word. If you define the word ptf: : ptf ." This Phrase " cr ; ( Remember those spaces! ) which does the same thing as the BASIC program line PRINT "This Phrase", then you can use ptf anywhere and it will print the phrase at that point. You have just added a completely new keyword - you have EXTENDED the CF83 Forth language. This is one of the most important features of forth: you can extend and customize it to meet your own particular needs. And, you can do so almost without limit! (Except, of course, that you can't use the word until after you have defined it). A second catch is that, unlike BASIC, CF83 Forth only allows integer numbers, i.e. 5, 263, -4497, etc. Floating point numbers, like 1.23, 467.85, -33.21, etc. are not allowed. You can add floating point arithmetic if you need it, but Forth does not initially come equipped with it. Of course, integer arithmetic is MUCH faster than floating point arithmetic, and (almost) everything you can do in floating point can also be done with appropriately scaled integers. ** THE DICTIONARY The CF83 Forth Dictionary contains the forth words you will use to write your programs. In BASIC, you can either execute a word right away or you can use it in a program. In BASIC, if you enter a command directly, you get: PRINT "This Phrase" This Phrase ok but if you enter it with a preceding line number, you get: CF83-2: Forth Tutorial - Page 6 of 24 10 PRINT "This Phrase" ok Instead of executing your command, BASIC has stored your command in program memory for later execution. When you enter the RUN command later, you get: RUN This Phrase ok CF83 Forth behaves in much the same way. Forth has two states: the Interpreting State and the Compiling State. When you first start CF83, it is in the Interpreting State. If you enter: 36 emit you get: 36 emit [] $ ok From here on, we will use the [] symbol to represent a press of the ENTER key. The characters to the left of [] are to be entered by you. The characters to the right of [] are CF83's response. 36 is the ASCII code for the dollar sign. When you touched the ENTER key after typing 36 emit, the first thing CF83 did was put the decimal number 36 on the top of the stack. Then it executed the forth word "emit" which is the word that takes an ASCII code from the top of the stack and prints its corresponding character on the screen. When CF83 encountered the characters "36 " (note the following space - all forth words must be separated by at least one space), it searched through the forth dictionary to see if it could find a word that matched "36 ". It couldn't find a match. So it checked to see if 36 was a valid number in the current number base. Since CF83 starts in base 10 (decimal), it recognized "36 " as a valid number and pushed it to the top of the stack. In the Interpret State, whenever CF83 encounters a valid number in your input, it pushes that number to the stack. Next, CF83 encountered the characters "emit " (note the following space again) and searched through the forth dictionary until it found "emit ". In the Interpret State, whenever CF83 finds a word in your input that matches a word in the forth CF83-2: Forth Tutorial - Page 7 of 24 dictionary, it executes that word. So, CF83 executed "emit " and put the dollar sign on the screen. Finally, CF83 encountered your press of the ENTER key and printed the "ok" prompt to tell you it was ready for you to do something else. Suppose you made a mistake and entered: 36 emut [] You would get: 36 emut [] emut ? CF83 put the number 36 on the stack as before. But, when it searched for "emut ", it couldn't find it in the forth dictionary and it also didn't recognize it as a valid number. So, it repeated the word (if you had input several words, you would now know which one was an error) and printed the question mark on the screen to let you know it didn't recognize your input. CF83 also took the 36 back off the stack. Whenever CF83 encounters an input error like this, it echoes the error, prints the question mark, and empties the stack. If it is in the Compiling State when it encounters the error, it also changes back to the Interpreting State. If you notice a typing mistake before you press [], you can use the left arrow key to back up to the error and correct it. But you must then retype everything following the error, whether or not it is still on the screen. Suppose you enter: bucks [] You get: bucks [] bucks ? because "bucks " can't be found in the forth dictionary either. But now, suppose you enter: : bucks 36 emit ; [] You get: CF83-2: Forth Tutorial - Page 8 of 24 : bucks 36 emit ; [] ok ": " is a forth word which acts sort of like a line number in BASIC. When CF83 is in the Interpreting State and encounters this word, it executes the word just like any other word. But ": " does just two things: it changes CF83 to the Compiling State and signals that the next word in the input stream is a new word to be compiled into the dictionary. So, when CF83 encounters "bucks " in the input stream, it doesn't search the dictionary or check to see if it's a number: it just starts a new dictionary entry for "bucks ". In the Compiling State, when CF83 encounters "36 ", it compiles it to the dictionary instead of pushing it to the stack. And, when it encounters "emit ", it compiles it to the dictionary instead of executing it. "; " is a special partner of ": ". Every forth word definition that begins with ": " must end with "; ". In the Compiling State, when CF83 encounters "; ", it executes it instead of compiling it. ( In forth, words that execute immediately, even in the Compiling State, are simply called "immediate words " ). When "; " executes, it finishes the final details of compiling the new dictionary entry, and then resets CF83 to the Interpreting State. NOW, suppose you enter: bucks [] You get: bucks [] $ ok NOW, when CF83 searched the forth dictionary for "bucks ", it found the match. "bucks" is now in the dictionary and can be used just like any other forth word. Try: : buckstoo bucks ; [] ok and: buckstoo [] $ ok and even: CF83-2: Forth Tutorial - Page 9 of 24 : bucks3 36 emit bucks buckstoo ; [] ok so that: bucks3 [] $$$ ok You have just extended the CF83 Forth dictionary to suit your own particular programming needs. In effect, you have actually created a new programming language especially suited to YOUR application! You can't do THAT with BASIC!!! Want to get fancy? Enter these definitions: : plw cr 63 2443 @ @ ; [] ok : 2r rot rot ; [] ok : ck if drop 63 cr then over 2+ - ; [] ok : cx count 127 and ; [] ok : 2v over over ; [] ok : 2d drop drop cr ; [] ok : cln cx rot dup 2 pick 2+ < ck 2r ; [] ok : tw cln 2v + @ 2r type 2 spaces ; [] ok : words plw begin tw dup 0= until 2d ; [] ok and then enter: words [] What happened? If you entered everything correctly, CF83 just displayed a list of all the words currently in the forth dictionary. Can you find the words you added? Okay! Now enter: : bucks 65 emit ; [] ok : abucks bucks ; [] ok and then enter: words [] Examine the word list closely. You should find "bucks " twice. Now enter: bucks [] A ok abucks [] A ok CF83-2: Forth Tutorial - Page 10 of 24 65 is the ASCII code for A. You can define the same word more than once in the dictionary. When you use the word, the most recent definition is the one that gets executed or compiled. Now enter: buckstoo [] $ ok Even though you have redefined "bucks ", "buckstoo " still uses the version of "bucks " that was in effect when "buckstoo " was defined. Now enter: forget bucks [] ok bucks [] $ ok abucks [] abucks ? words [] The word "forget" caused the most recent version of "bucks ", and all other words defined after that version of "bucks " to be removed from the dictionary. They no longer exist. To verify this, enter: : abucks bucks ; [] ok abucks [] $ ok Now, take a blank formatted floppy disk and label it "CF83 TEST BLOCKS". Put the blank formatted disk in Drive #0 and enter: : cb -1 56 ! -1 58 ! -1 60 ! -1 62 ! ; [] ok : clb dup 1024 32 fill ; [] ok : mb cb cr block clb 1024 expect update flush ; [] ok 10 mb [] When you press the last ENTER key, the cursor moves to the beginning of the next line without printing "ok". The disk drive runs for a few seconds before you see the cursor. Make SURE that the disk in Drive #0 is a blank formatted disk. Then, VERY carefully enter the following paragraph EXACTLY as shown. Use the ENTER key ONLY where the [] symbol is shown. Do NOT use the ENTER key at the end of lines. When you come to the end of the characters on a line, use the spacebar over and over until the cursor moves to the beginning of the next line. CF83-2: Forth Tutorial - Page 11 of 24 ( Test Block Number 1 ) .( Loading Test Block ) : make-block ( +n -- ) cr block dup 1024 32 fill 1024 expect update ; : words ( -- ) cr 63 2443 @ @ begin count 127 and rot dup 2 pick 2+ < if drop 63 cr then over 2+ - rot rot over over + @ rot rot type 2 spaces dup 0= until drop drop cr ; [] ok When you press [], the cursor moves to the beginning of the next line before printing "ok". The disk drive also runs for a few seconds, with the Drive #0 light on, before printing "ok". Turn your CoCo off. Wait 15 seconds. Turn it back on, put your CF83 Forth Disk in Drive #0, and enter: RUN "CF83" After pressing a key to clear the opening screen, enter: words [] words ? What has happened? Enter: bucks [] bucks ? ptf [] ptf ? When you turn your CoCo off, CF83 forgets all the definitions you have entered. It forgets everything except the Forth-83 Standard's Required Word Set. Put your "CF83 TEST BLOCKS" disk in Drive #0 and enter: 10 load [] Loading Test Block ok The disk drive runs a few seconds between the printing of "Loading Test Block" and the printing of "ok". Now, enter: words [] What happened? Did you get a dictionary list again? You should have. CF83-2: Forth Tutorial - Page 12 of 24 ** BLOCKS You have just stored Block Number 10 on the floppy disk, and then reloaded Block Number 10's contents after restarting CF83. From your Tandy Disk Basic Manual, you know that RS-DOS disks are organized into 35 tracks of 18 sectors each. Track 17 is dedicated to the disk directory and file allocation table, and tracks 0-16 and 18-34 are available for your use. CF83 Forth uses the same disk format, but a different organiza- tion into 157 blocks of 4 sectors each, with no disk directory or file allocation table. CF83 leaves the last two sectors on the disk unused (Track 34, Sectors 17 and 18). Because there is no disk directory, you must keep track of what your blocks contain. Each block contains 1024 bytes of data. When displayed on the screen or listed to a printer, blocks are traditionally displayed as 16 lines of 64 characters each. A block can contain whatever you want it to: loadable CF83 program code, notes about a vacation, text for a book you are writing, etc. All blocks can be examined and/or acted upon by CF83, but only program code can be loaded using "load". Note that Block #0 can never be loaded, and that every loadable block must end in a space (ASCII 32). Enter the following definition: : list block cr 1024 type ; [] ok With your CF83 Test Blocks disk still in Drive #0, enter: 10 list [] to display Block #10 on the screen. The actual block numbers assigned to the disk depend upon which disk drive the floppy disk is in. See the CF83 User's Guide. Before you can load information from a Block into your system, you first have to put that information into the block. The word "make-block" which you defined above does this for you, and it is sufficient for writing any information to any block you want. After you have finished entering a block with make-block, you should always enter the word: flush [] ok to make sure that the block is "flushed" to the disk, i.e. is transferred from the block buffer in RAM to storage on the CF83-2: Forth Tutorial - Page 13 of 24 floppy diskette. But, "make-block" is not very convenient to use. BDS Software's product CF83-3: Block Editor is designed specifically for writing and revising CF83 Forth blocks, and will greatly ease the task of preparing your applications. Another method for editing blocks is to use a word processor to prepare the blocks' text, save the text to an ASCII file, and then transfer the text to Blocks using the BLOCKXFR.BAS program included on this disk. However, in order to use BLOCKXFR.BAS, you must have at least TWO floppy disk drives, not just one. Successful use of BLOCKXFR.BAS requires that you use the word processor to correctly format the text for 64 characters per line and 16 lines per block before running BLOCKXFR.BAS. If your word processor is Telewriter 64, your file must end with a carriage return. If you use some other word processor, you will have to experiment with this requirement. To use BLOCKXFR.BAS, turn your CoCo off, wait 15 seconds, turn your CoCo back on, insert this disk in Drive #0, enter RUN "BLOCKXFR", and follow the directions that appear on the screen. You will also need both a regular RS-DOS floppy diskette with your ASCII file on it (Source Disk) and a CF83 Blocks Disk with empty blocks to which you will transfer the file's contents (Target Disk). The combination of the blocks on disk and the buffers in RAM provides CF83 Forth with a virtual memory system. This simply means that the disk is made to act like RAM. It doesn't make disk access any faster, but it does make it transparent. The CF83 word "block" (which you used in the definition of "list" above) controls the virtual memory system. In CF83, low RAM includes four block buffers. When "block" executes, the first thing it does is take the desired block number from the top of the stack. Then it checks the four block buffers to see if the desired block is already in one of them. If it finds the block in one of the buffers, it puts the address of that buffer on the top of the stack and ends execution. If "block" does not find the desired block in one of the buffers, it examines the least recently accessed buffer. If the contents of that buffer have been marked as updated, "block" copies the contents back to the disk block from which it came, overwriting the old contents of that block. Then the contents of the desired block are copied into the buffer, the buffer's CF83-2: Forth Tutorial - Page 14 of 24 address is put on top of the stack, and "block" execution ends. If the buffer's contents were not marked as updated, they are simply overwritten; but the contents are still stored in the disk block, so nothing is lost. Buffers are marked as updated by the CF83 word "update" (which you used in the definition of "make-block" above). "Update" marks the MOST recently accessed buffer as updated. "Flush" copies all updated buffers to their disk blocks and then empties all four buffers. The CF83 word "save-buffers" does the same, except that it leaves the block contents in the buffers so that CF83 can continue to access them. In CF83, the word "buffer" is an identical synonym for the word "block". ** THE STACK Try this: 1763 [] ok 3254 [] ok . . [] 3254 1763 ok When you typed in 1763 and pressed the ENTER key, CF83 converted the characters "1763" to the number 1763 and pushed it onto the top of the stack. When you typed in 3254 and pressed the ENTER key, CF83 converted the characters "3254" to the number 3254 and pushed it to the top of the stack, on top of the number 1763. When you typed in ". . " and pressed the ENTER key, CF83 looked at the first ". ", found it in the dictionary, and executed it. Since the word ". " takes the number on the top of the stack and displays it to the screen in the current number base, the result was that "3254" appeared on the screen. Similarly, the second ". " put "1763" to the screen. Now try this: 1763 [] ok 3254 [] ok . . . [] 3254 1763 17222 ok You may have got something else instead of 17222 as the last number. Whatever it was, the number is garbage. You put two numbers onto the stack and tried to take three off. What CF83-2: Forth Tutorial - Page 15 of 24 actually happened is called "Stack Underflow": CF83 printed out the 16-bit number which happened to be stored in the two bytes below the bottom of the stack. Whatever that number is, it has no real meaning. Even though you got the "ok" prompt, this result is not really okay: it is an error. Many forths will give you a "Stack Underflow" or similar message when this happens. We did not include that feature in CF83 because the stack checking takes time - time which is wasted after your application is debugged and running properly. If you want to include such stack checking in your system, either permanently or temporarily until debugging is complete, you can add the following words to your system to redefine the ". " word: : stack-check depth dup 16256 > if ." Stack Underflow " else 255 > if ." Stack Overflow " then then [] ok ( You can write this definition all as one long line that overlaps onto a second screen line - the definition is written on four lines above just for neatness - remember: at least one space between every word ) : dum . ; [] ok : . dum stack-check ; [] ok The word "dum" is required because you cannnot directly use a word in its own redefinition in CF83. If you try to write: : . . stack-check ; [] ok the first time you try to use the redefined ". ", the system will crash quite spectacularly! Recursion IS possible in CF83. A word CAN execute itself. But NOT by using itself in its own definition. Recursion requires the addition of the word "recurse" to your system. "Recurse" is available in BDS Software's product CF83-7: Controlled Reference Words Set. Many of CF83's words are designed to operate on the stack. Before examining them in more detail, however, we need to explain the concept of a stack comment. Suppose you put the numbers 665, 23, and 446 onto the stack in that order. A straightforward way to show the condition of the stack would be CF83-2: Forth Tutorial - Page 16 of 24 to simply show a picture of it: 446 23 665 with 665 on the bottom, 23 in the middle, and 446 on top. But this uses a lot of space, both on the screen and in printed documents. So the forth community has developed the convention of picturing the stack on its side: 665 23 446 with the top of the stack on the right and the bottom on the left. To show how a word effects the stack, it is only necessary to show two pictures of the stack, one before the word is executed, and one after it is executed. The two pictures are conventionally separated by a double dash, with the "before" picture on the left, and the "after" picture on the right: ( 665 23 446 -- 665 469 ) The comment is enclosed in parentheses because the word "( " indicates a comment, i.e. when loading from the input stream (keyboard or disk block), CF83 ignores everything after "( " until it encounters a following ") ". In the above double picture, 446 is on the top of the stack before the word executes, and 469 is on the top of the stack after the word executes. Since words operate on whatever numbers are on the stack at the time they are executed, symbols are usually used in stack comments instead of actual numbers. For example, in the word: / ( n1 n2 -- n3 ) the stack comment indicates that the two numbers, n1 and n2, are removed from the stack and then a third number, n3 , is placed on the stack. In this case, since "/ " is the word for division, n3 is the integer result of dividing n1 by n2. If n1 is 18 and n2 is 3, then n3 is 6. (Since CF83 only does integer arithmetic, if n1 is 19 and n2 is 3, then n3 is still 6). For a complete and detailed list of CF83's words, you will need to purchase BDS Software's product CF83-1: Technical Reference CF83-2: Forth Tutorial - Page 17 of 24 Manual or FIG's Forth-83 Standard. However, some of CF83's more commonly used words are briefly (and somewhat loosely) defined below. ! ( n1 address -- ) ... n1 is stored at the address, e.g.: 11345 47556 ! [] ok i.e. 44 is stored at 47556 and 81 is stored at 47557 ( 44 * 256 + 81 = 11345 ) * ( n1 n2 -- n3 ) ... n3 is n1 times n2, e.g.: 473 8 * . [] 3784 ok */ ( n1 n2 n3 -- n4 ) ... n4 is (n1 times n2) divided by n3, e.g.: 887 966 1025 */ . [] 835 ok This is NOT the same as "887 966 * 1025 / . []", because CF83 only operates on 16-bit integers (-32,768 to +65,535) unless you use words specifically designed for double numbers, i.e. 32-bit integers. The problem that occurs above is that 887 times 966 is 856,842 which is outside the 16-bit integer range. The word "*/ " is specifically designed to hold the intermediate result as a 32-bit number before dividing. The separate operations are not. */mod (n1 n2 n3 -- n4 n5 ) ... This is the same as "*/ " except that n4 is the remainder of the integer division and n5 is the quotient, e.g.: 887 966 1025 */mod . . [] 835 967 ok + ( n1 n1 -- n3 ) ... n3 is n1 + n2. - ( n1 n2 -- n3 ) ... n3 is n1 - n2. /mod ( n1 n2 -- n3 n4 ) ... n3 is the remainder and n4 is the quotient of dividing n1 by n2, e.g.: 97 8 /mod . . [] 12 1 ok 0< ( n -- flag ) ... flag = -1 if n is less than zero. Otherwise, flag = 0. 0= ( n -- flag ) ... flag = -1 if n = 0, else flag = 0. CF83-2: Forth Tutorial - Page 18 of 24 0> ( n -- flag ) ... flag = -1 if n > 0, else flag = 0. 1+ ( n1 -- n2 ) ... n2 = n1 + 1. 1- ( n1 -- n2 ) ... n2 = n1 - 1. 2+ ( n1 -- n2 ) ... n2 = n1 + 2. 2- ( n1 -- n2 ) ... n2 = n1 - 2. 2/ ( n1 -- n2 ) ... n2 = n1/2 ( 2/ is much faster than 2 / ) < ( n1 n2 -- flag ) ... flag = -1 if n1 < n2, else flag = 0. = ( n1 n2 -- flag ) ... flag = -1 if n1 = n2, else flag = 0. > ( n1 n2 -- flag ) ... flag = -1 if n1 > n2, else flag = 0. @ ( address -- n1 ) ... The number (n1) at the address is copied to the top of the stack, e.g., if the byte at 47556 is 44 and the byte at 47557 is 81, then: 47556 @ . [] 11345 ok ( 44 * 256 + 81 = 11345 ) abs ( n1 -- n2 ) ... n2 is the absolute value of n1. and ( n1 n2 -- n3 ) ... n3 is the logical AND of n1 with n2. c! ( n1 address -- ) ... the least significant byte of n1 is stored to the memory address. If n1 is 81 and the address is 47556, then 81 is stored at 47556. But, if n1 is 11345, then only the least significant byte (81 again in this case) is stored at 47556. c@ (address -- n1 ) ... n1 is the byte stored at the address. d+ ( d1 d2 -- d3 ) ... d3 = d1 + d2, where d1, d2, and d3 are double numbers, i.e. 32-bit numbers ( -2,147,483,648 to +4,294,967,295 ). d< ( d1 d2 -- flag ) ... flag = -1 if d1 < d2, else flag = 0. dnegate ( d1 -- d2 ) ... d2 = -d1. drop ( n1 -- ) ... The top number is "dropped" (i.e. pulled) from the stack. CF83-2: Forth Tutorial - Page 19 of 24 dup ( n1 -- n1 n1 ) ... The number on the top of the stack is duplicated. emit ( n1 -- ) ... n1 is displayed to the screen as an ASCII character. key ( -- n1 ) ... n1 is the ASCII code of the next character received. max ( n1 n2 -- n3 ) ... n3 is the greater of n1 and n2. min ( n1 n2 -- n3 ) ... n3 is the lesser of n1 and n2. mod ( n1 n2 -- n3 ) ... n3 is the remainder after dividing n1 by n2, e.g.: 8 5 mod . [] 3 ok negate ( n1 -- n2 ) ... n2 = -n1. not ( n1 -- n2 ) ... n2 is the logical NOT (one's complement) of n1. or ( n1 n2 -- n3 ) ... n3 is the logical OR of n1 with n2. over ( n1 n2 -- n1 n2 n3 ) ... n3 is a copy of n1. pick ( n -- n1 ) ... n1 is a copy of the nth stack value, not counting n itself. roll ( n -- ) ... the nth stack value, not counting n itself, is pulled out of its location on the stack and moved to the top of the stack. rot ( n1 n2 n3 -- n2 n3 n1 ) ... The top three numbers on the stack are rotated. space ( -- ) ... a space (ASCII 32) is displayed to the screen. spaces ( n -- ) ... n spaces are displayed to the screen. swap ( n1 n2 -- n2 n1 ) ... the top two numbers on the stack are exchanged. type ( address n -- ) ... the n characters beginning at the address are displayed to the screen. CF83-2: Forth Tutorial - Page 20 of 24 ** LOOPS In BASIC, if you want to do the same thing over and over, you use a FOR ... NEXT loop. In CF83 Forth, you use the following structures within a word definition: do ... loop do ... +loop begin ... until begin ... while ... repeat Try this: : t1 6 0 do i . loop ; [] ok t1 [] 0 1 2 3 4 5 ok The CF83 word "i " puts the loop index on top of the stack, and the do ... loop structure executes repeatedly from the starting index (0) until the index reaches the ending index (6). Now try these: : t2 17 9 do i . loop ; [] ok t2 [] 9 10 11 12 13 14 15 16 ok : t3 -43 -50 do i . loop ; [] ok t3 [] -50 -49 -48 -47 -46 -45 -44 ok These structures MUST be used within a word definition. They will NOT work directly. "do " sets up the structure; "loop " increments the index by one and returns to the beginning of the structure if the index limit has not been reached. The ending index must always be greater than the starting index (The structure "46 46 do i . loop" would execute 65,535 times) Next, try this: : t4 18 4 do i . 2 +loop ; [] ok t4 [] 4 6 8 10 12 14 16 ok "+loop " works just like "loop ", except that instead of adding one to the loop index, it adds whatever number is on the top of the stack to the loop index. Since we are pushing 2 to the stack each time through the loop (just before "+loop " is encountered), "+loop " increments the index by 2 in this word. Another example would be: : t5 46 59 do i . -3 +loop ; [] ok t5 [] 59 56 53 50 47 ok While the word "i " copies the index of the loop structure it is CF83-2: Forth Tutorial - Page 21 of 24 in, the word "j " copies the index of the next outer loop. For example: : t5 3 1 do 5 0 do j . loop loop ; [] ok t5 [] 1 1 1 1 1 2 2 2 2 2 ok : t6 3 1 do 5 0 do i j * . loop loop ; [] ok t6 [] 0 1 2 3 4 0 2 4 6 8 ok "i " and "j " can ONLY be used in a word definition where "do " and "loop " (or "+loop ") appear. They are invalid anywhere else. And, within the definition, they must also be within the structure itself. This is due to Return Stack Interferance. We've talked about the Stack, but CF83 Forth actually uses two different stacks. The one we've been talking about is the Data Stack, and the other is the Return Stack. The Data Stack is the one designed for the programmer to use. Whenever we refer to "the Stack" without any other identification, it is the Data Stack we mean. The Return Stack is what CF83 uses to keep track of things like what word is to be executed next, where to return after an internal subroutine call, etc. Improperly messing with the Return Stack will crash the system faster than anything else. CF83 automatically keeps "i " and "j " on the return stack - they simply don't exist outside of these structures. You can VERY CAREFULLY use the Return Stack for temporary storage within a word definition by using the following words: >r ( n1 -- ) ... n1 is pulled off the top of the Data Stack and pushed to the top of the Return Stack. r> ( -- n1 ) ... n1 is pulled off the top of the Return Stack and pushed to the top of the Data Stack. r@ ( -- n1 ) ... The number n1, on the top of the Return Stack, is copied to the top of the Data Stack. The Return Stack is not changed. The restrictions are: 1. If you use >r in a word definition, it MUST be balanced with a corresponding r> within the SAME word definition (and vice versa). 2. If you use >r within a looping structure, it MUST be balanced with a corresponding r> within the SAME looping structure (and vice versa). CF83-2: Forth Tutorial - Page 22 of 24 You can also use >r and r> interactively (i.e. direct from the keyboard) if they are balanced within the same line of input, i.e. before []. Also, note that "i " and "j " are not valid if you have placed a temporary value on the Return Stack. The CF83 Forth word "leave" can be used to leave a do ... loop or do ... +loop structure early. But if you try to leave while values are still on the Return Stack, the system will crash. To summarize: ... >r ... do ... loop ... r> ... is valid. ... do ... >r ... r> ... loop ... is valid. ... >r ... do ... r> ... loop ... will crash. ... do ... i ... >r ... r> ... loop ... is valid. ... do ... >r ... i ... r> ... loop ... is not valid. ... do ... >r ... leave ... r> ... loop ... will crash. Try this from the keyboard: 44 55 66 >r swap r> . . . [] 66 44 55 ok Now suppose you are given the algrbraic equation: f = 27 * x * x + 43 * x + 82 and need to find the value of f at x = 6. Leo Brodie's STARTING FORTH supplies the needed word (page 100): : quadratic ( a b c x -- n ) >r swap rot r@ * + r> * + ; [] ok Try: 27 43 82 6 quadratic . [] 1312 ok Now try these: : rq 6 1 do 27 43 82 i quadratic . loop ; [] ok rq [] 152 276 454 686 972 ok ... the values of f at x = 1, x = 2, x = 3, x = 4, and x = 5. CF83-2: Forth Tutorial - Page 23 of 24 These two structures ( do ... loop and do ... +loop ) are both definite loops which loop a specific number of times. But, begin ... until and begin ... while ... repeat loops are indefinite loops which loop indefinitely until a given condition is met. Try these: : t7 ( n1 -- ) begin 1- dup dup . 0= until drop ; [] ok 7 t7 [] 6 5 4 3 2 1 0 ok 12 t7 [] 11 10 9 8 7 6 5 4 3 2 1 0 ok The "0= " word puts a flag on the stack. The "until " word checks the flag. As long as the flag is false (zero), "until " returns execution to just after "begin ". As soon as the flag becomes true (other than zero), "until " transfers execution to just after "until ". Any combination of words that leave a flag on the stack for "until " can be used as the body of a begin ... until structure. In fact, since the CF83 Forth word "true " simply pushes a -1 to the stack, and the word "false " pushes a zero to the stack, an infinite loop can be written in the form: ... begin ... false until ... This is often useful for defining a "top" word - one which controls an entire application. In a begin ... until structure, the loop continues iterations UNTIL its test flag is true. In a begin ... while ... repeat structure, the loop continues iterations WHILE its test flag is true (i.e. until it becomes false). After the words between "begin " and "while " (if any) have executed, "while " checks the flag. If the flag is true (other than zero), execution continues until "repeat "; "repeat " then returns execution to just after "begin ". If "while " finds that the flag is false (zero), it transfers execution to just after "repeat ". Try these: : t8 ( n1 -- ) begin 1- dup dup 0> while . repeat drop drop ; [] ok 7 t8 [] 6 5 4 3 2 1 ok 12 t8 [] 11 10 9 8 7 6 5 4 3 2 1 ok ** DECISIONS In BASIC, we make decisions using an IF ... THEN ... ELSE structure. CF83 provides a similar decision structure. In CF83-2: Forth Tutorial - Page 24 of 24 BASIC, the structure format is: IF (condition) THEN (do something) ELSE (do something else) The ELSE phrase is optional. In CF83, the structure format is: (condition) if (do something) else (do something else) then and here too, the else phrase is optional. The (condition) in CF83 is a flag on the top of the stack. Any combination of words that leaves a flag on the stack can be used to form the condition. Try these: : ta ( -- ) ." There was a " ; [] ok : tb ( -- ) ." zero " ; [] ok : tc ( -- ) ." non-zero number " ; [] ok : td ( -- ) ." on top of the stack. " [] ok : t9 ( n1 -- ) ta 0= if tb else tc then td ; [] ok 0 t9 [] There was a zero on top of the stack. ok 7 t9 [] There was a non-zero number on top of the stack. ok ** KEYBOARD INPUT key ( -- n1 ) ... n1 (0-127) is the ASCII code of the next character received from the input stream. The input stream can be taken either from the keyboard or from a block buffer. expect ( address n -- ) ... Receive up to n (or until []) characters from the input stream, and save them to memory beginning at the address. [] is not stored. These and other keyboard input words are explained in detail in BDS Software's product CF83-1: Technical Reference Manual. ** REFERENCES This tutorial has covered the basics of CF83 Forth, but it is far from complete. The information in BDS Software's product CF83-1: Technical Reference Manual is necessary for a complete understanding of CF83. An alternate source for this information is the Forth-83 Standard. Numerous other forth references are available from the Forth Interest Group, P.O. Box 8231, San Jose, CA 95155, phone (408) 277-0668, fax (408) 286-8988. ** END **