USER MANUAL OF SPECTRUM FORTH83 1. GENERAL 1.1 ABOUT This manual is a translation of the user manual that I wrote originally in Dutch for the SPECTRUM FORTH83 system in 1988. This is not a literal translation and it contains some updates and clarifications. This FORTH system is written by L.C. Benschop in Vught The Netherlands (formerly living in Eindhoven). I made use of the assembler and some code definitions designed by Coos Haak in Utrecht, The Netherlands. Some high-level definitions were derived from FIG-FORTH, the rest is my original work, in particular the screen editor and all Spectrum-specific words. This manual is not meant to be an exact definition of the FORTH-83 standard. For this, please refer to: FORTH-83 STANDARD FORTH STANDARDS TEAM P.O. BOX 4545 MOUNTAIN VIEW CA94040 USA Paper copies used to be available from FIG or its local chapters. Currently it can be found online here: http://forth.sourceforge.net/standard/fst83/ 1.2 INTRODUCTION This Forth compiler is suitable for the ZX-Spectrum 48 and 128, with or without Microdrives or floppy disk drives. The program contains the full FORTH-83 Required Word Set and System Extension Word Set. The Double Number Extension Word Set and Assembler Word Set can be loaded as extensions. Further there are extensions for floating point numbers, strings and graphics. The complete system with or without any of the above-mentioned extension word sets can be saved to tape, Microdrive of floppy disk. This system is block-oriented, which means that source code is stored in fixed-sized blocks. These blocks (also called screens) are stored in a RAM disk and they can be saved to tape, Microdrive or floppy disk. One or more of these blocks can be saved to a single file. These files can later be reloaded into the RAM disk. This RAM disk is located at the top part of the RAM on the Spectrum 48 and its size can be varied. On the Spectrum 128 on the other hand, it is always 80 kilobytes in size and it is located in the extra RAM that is not available on the Spectrum 48. The system comes standard with a screen-editor that can also edit large contiguous texts that are stored in multiple blocks in the RAM disk. The original Dutch version of this manual was edited this way. The BREAK key is capable of interrupting almost any program, be it FORTH or machine code. 1.3 LOADING AND SAVING FORTH. On the Spectrum 128, you must use 128K Basic or Tape Loader to load Forth from cassette. On the Spectrum 48 you must type LOAD "" from Basic. Forth will start automatically when loaded from tape. If you have trouble running FORTH on a Spectrum 48, it may be caused by hardware extensions that use port address 7FFDH. In this case, do the following: - Load the Basic program from tape with LOAD "". - Press BREAK to interrupt the loading procedure. - Manually load the rest of Forth with LOAD "" CODE 27028 - Patch the system with POKE 30388,250 POKE 30389,111 This patch replaces the P! address in the >BANK word with 2DROP, so this word will no longer write to the bank switching port. - Start with RUN 20. Once you are in Forth, you can save a copy of the program on cassette tape with: 0 DRIVE 60 BCAL You can save a copy of the program on Microdrive or floppy disk with: 1 DRIVE 60 BCAL But when there is already a version on disk or Microdrive, you have to delete the old files first with: 1 DRIVE DELETE run DELETE FORT83.BIN It will be stored on drive no.1. Once it is stored on disk or Microdrive you can later load it by typing just the RUN command immediately after starting the machine. On the Spectrum 128 this has to be done from 128K Basic. If you use a floppy disk, it may be necessary to edit the SAVE and LOAD commands in the Basic program, depending on the disk interface you use. From Forth you can return to Basic by pressing SYMBOL-SHIFT-W. From Basic you can return to Forth by typing the RUN command. If you have loaded extension word sets and you want to save the extended Forth system, you have to type the following commands first before you can actually save the system with 60 BCAL: HERE FENCE ! On the Spectrum 48 it is possible to change the size of the RAM disk by entering the following commands: N ' #B >BODY ! COLD Instead of N, type the desired number of screens, for instance 8 or 16 (default is 10). 1.4 SCREEN, KEYBOARD AND PRINTER In FORTH the screen uses 24 lines of 32 characters each. As opposed to Basic, all 24 lines are used and the bottom two lines are not reserved for error messages and keyboard input. As opposed to Basic, Forth will never interrupt screen output with the "Scroll?" message. The Extended mode of the keyboard is not used. You type the characters that you have to enter normally in Extended Mode (like \ { and [) by typing the corresponding keys just with SYMBOL-SHIFT. You cannot enter Basic keywords in Forth, as you would do in Basic. Special keys (outside the editor): - With DELETE (CAPS-SHIFT-0) you erase the last character typed. - With ENTER you indicate that the line you just typed is finished and then the typed commands will be executed. - CAPSLOCK (CAPS-SHIFT-2) works normally. The system starts in caps-lock mode, because the interpreter is case-sensitive and all standard Forth words are upper case. - With SYMBOL-SHIFT-W or with the BYE command you can return to Basic. SYMBOL-SHIFT-W works even if the Forth dictionary is corrupted and no Forth words can be found by the interpreter. From BASIC you may be able to save your RAM disk or reload the Forth system while preserving the RAM disk. Return to Forth with the RUN command. - BREAK (CAPS-SHIFT-SPACE) interrupts just about any program and returns to the Forth command line. - EDIT (CAPS-SHIFT-1) can be used to stop DUMP, VLIST and LIST, much more cleanly than with BREAK. The word >P redirects all output to the printer until the next command line has to be entered. The word >S directs the output to the screen again. If you use a ZX-Printer on the Spectrum 128, you have to type the word ZX-PRINT first. The ZX-Printer is not normally supported by 128K Basic, but I had this (in fact a Timex work-alike) as my printer, so I found out what it took to re-enable the ZX-Printer output on the Spectrum 128. If you use a serial printer on Interface-1, you have to edit the Basic program: - Leave Forth with SYMBOL-SHIFT-W. - Edit Line 20. Just before RANDOMIZE, add the following commands: FORMAT "t";9600:OPEN #3,"t" - Do a cold start of Forth with RUN 20. - You can save the Forth system as described in section 1.3. 1.5 ERROR CONDITIONS AND THEIR EFFECTS Whenever in chapter 3 an error or error message is mentioned, the following will happen: - An error message is printed. - The name of the forth word causing the error is printed. - ABORT is executed (which causes the system to return to the command interpreter and to clear the stacks). - If the error occurs during loading of a screen, the location of the error is pushed on the stack, so that you can type WHERE to start the editor right at the location in the source where the error occurred. Not all error conditions are detected and reported though, No error is reported in the following cases; - Division overflow or division by zero, A result of -1 is returned. - Negative number where the standard requires a positive number. In this case the number is treated as an unsigned number. - Wrong manipulations of the return stack, EXIT does not check whether the return address is valid and the system will likely crash if it is not valid. - An invalid address supplied to EXECUTE. - WORD does not find an end delimiter. 2. SCREENS AND FILES 2.1 THE RAM DISK Forth source code is stored in blocks (also called screens) of 1kB each. As the Spectrum has to work with slow cassette tapes or with not very fast Microdrives, the FORTH screens are stored in a RAM disk. On the Spectrum 48 this RAM disk is located in memory from the address stored in the LO variable (normally 55296) to the address 65535. On the Spectrum 128 it is located in five extra RAM banks, which are normally not accessible. It consists of 5 groups of 16 screens each: 1-16, 17-32, 33-48, 49-64 and 65-80. #SCR is the total number of screens. After loading FORTH, you can empty the RAM disk with the FORMAT command, which is advisable. The command N1 N2 INDEX shows the first line of every screen from number N1 through N2. Therefore it is wise to store a comment (describing the screen) in the first line of each screen. 2.2 LOADING AND SAVING SCREENS Wit the command N DRIVE you can select the drive to use for saving and loading screens. N=0 means cassette tape, N=1..8 means Microdrive or floppy disk no. N. The CAT command shows the names of all available files. For Microdrives or disks it invokes the regular CAT command, for tapes it attempts to load a file named "$$$$$$$$$$", which likely does not exist, so it will show the names of all tape blocks that are encountered. The CAT command on tape has to be terminated with the BREAK key. The command N GET filename loads the file with the specified name into the RAM disk starting at screen N. The command N1 N2 PUT filename saves screens N1 through N2 with the chosen file name, as a "Bytes" block saved with the Basic command SAVE..CODE. The command DELETE filename deletes the specified file. With Microdrives it is necessary to delete the old file first, before saving another file with the same name. IMPORTANT: On the Spectrum 128, all screens loaded or saved in the same file have to be part of the same group of 16 screens: 15 17 PUT FOO is illegal, as screen 15 and 16 are part of the first group and screen 17 is part of the second group. The following commands are allowed. 15 16 PUT FOO1 17 17 PUT FOO2 To copy a file, proceed as follows: 1 Type the FORMAT command 2 Type the command 1 GET filename 3 Type 1 N INDEX where N is the highest screen number in the RAM disk (or 16 on the 128K Spectrum). 4 Check the highest screen number with a non-blank line in it. 5 Select another drive if needed, insert the correct cassette, Microdrive or floppy disk. If required, delete any old file with the same name. 6 Type 1 M PUT where M is the highest non-empty screen, as found in step 4. 2.3 THE EDITOR With N EDIT you start the screen editor. You will see half of screen N with a blinking cursor. At the bottom you seen the screen number, followed by the letter A if the top part is shown or the letter B if the bottom part is shown. You can move the cursor with the cursor keys (CAPS-SHIFT-5 through CAPS-SHIFT-8 on the classic Spectrum keyboard). You always see one half of the screen, which is the part where the cursor is currently located. As opposed to standard Forth systems, each screen contains 32 lines of 32 characters each (standard is 16 lines of 64 characters each). The following keys have a special function in the editor: EDIT=CAPS-SHIFT-1 exits the editor. You can undo the edits in the last screen with EMPTY-BUFFERS. CAPS LOCk=CAPS-SHIFT-2 CAPS LOCK as in BASIC. TRUE VIDEO=CAPS-SHIFT-3 Inserts a blank line at the location of the cursor. The lines below shift one position down, the last line is deleted. INV VIDEO=CAPS-SHIFT-4 deletes the line at the location of the cursor. The lines below shift one position up, a blank line appears at the end. GRAPHICS=CAPS-SHIFT-9 Inserts a space at the location of the cursor. The rest of the line shifts one position to the right. If the rightmost character at the end of the line is not a space or if the last word on the current line would run together with the first word on the next line, it will move to the next line. One of more subsequent lines may be shifted to the right as well. DELETE=CAPS-SHIFT-0 Deletes the character at the location of the cursor. The rest of the line shifts one position to the left. If a word is divided between the end of the current line and the start of the next line, subsequent lines will shift to the left, avoiding these words to be split. SYMBOL-SHIFT-Q moves to the next half screen. From 1A to 1B, from 1B to 2A. SYMBOL-SHIFT-E moves to the previous half screen. From 2A to 1B, from 1B to 1A. Both of these keys can be used to page through the RAM disk. SYMBOL-SHIFT-W moves to the first position at the first line of the current screen. ENTER moves to the first position on the next line. The editor can be in two modes: file mode an blocks mode. The FILE command puts the editor in file mode. The entire RAM disk (or on the Spectrum 128K the entire group of 16 screens) is treated as one large text file. Insertions and deletions span the entire RAM disk or the entire group of 16 screens. Text will be moved across screens when lines are inserted or deleted. Cursor keys will also move across screens. The BLOCKS command puts the editor in blocks mode. Each screen is treated individually. If a line is inserted, the last line of the current screen will disappear and will not be moved to the next screen. This is the normal way of editing FORTH source code. 2.4 PROGRAM LOADING The command N LOAD will load Forth program source code from screen N. All text on a the loaded screen will be interpreted as if it was typed on the command line. The word '\' (a backslash followed by a space) means a comment until the end of the current line. The command --> can occur on a screen and it means that the next screen shall be loaded as well. The command RUN filename is equivalent to 1 GET filename 1 LOAD It will load a file into the RAM disk and immediately load (compile or execute) the source code contained in that file. Screens can contain LOAD commands, so that other screens can be loaded from one screen. 3 IMPLEMENTED WORD SETS 3.1 STACK NOTATION The words in the list are in ASCII order. The name of the word is followed by any values that the word expects on the stack, followed by three hyphens, followed by any values the word returns on the stack. After that come the following optional indicators: - I means that the word is immediate. - 83 means the word is part of the FORTH-83 Required Word Set. - S means that the word is part of the System Extension Word Set. - C means that the word may only be used in the compilation state. - D means that the word is part of the Double Number Extension Word Set, which is not fully implemented in the bare system (but the remaining words can be loaded as extensions). The stack values (expected and returned) are denoted as follows: The following stack values occupy one stack location each: f: flag 0 is false, any other value means true. true: -1, standard true value. false: 0, standard false value. c: ASCII character 8b: byte 16b: 16-bit word n: signed number between -32768 and +32767. u: unsigned number between 0 and +65535. w: number between -32768 and +65535, signed or unsigned depends on context. addr: memory address. The following stack values occupy two stack locations each: 32b: 32-bit word d: signed double number between -2147483648 and +2147483647. ud: unsigned double number between 0 and 4294967295. wd: number between -2147483648 and +4294967295, signed or unsigned depends on context. 3.2 DEFINITION OF TERMS: block buffer: buffer of 1024 bytes that contains the screen that is currently accessed. This FORTH system contains a single block buffer. Screens are moved between the block buffer and the RAM disk. colon-definition: FORTH word whose definition was started by ':'. When the colon definition is executed, the inner interpreter executes the words contained in the colon definition in succession. The address from which the colon definition was invoked gets stored on the return stack. compilation: building a colon definition by adding compilation addresses and literals to the dictionary and by executing immediate words. compilation address: the address of a FORTH word that will be added to the dictionary during compilation. counted string: string of ASCII characters stored in memory, preceded by a single byte containing the length of the string. dictionary: collection of vocabularies, which contains all FORTH words. The dictionary occupies a contiguous memory area, which is extended or reduced in size from the top end. immediate word: FORTH word that is always executed, even if the text interpreter is in compilation state. inner interpreter: piece of machine code that executes the words contained in a colon definition. input buffer: buffer containing the line that is typed on the keyboard. input stream: text read by the text interpreter, either form the input buffer or from a block buffer. interpretation: looking up words from the input stream in the dictionary and immediately executing them. When a word is not found in the dictionary, an attempt is made to convert it to a number and the resulting value is pushed on the stack if this succeeds. literal: a special word within a colon definition that, when executed, pushes the value immediately following it on the stack. loop: repetition structure used within a colon definition, using a terminal value (limit) and a counter (index) that are both stored on the return stack. numeric conversion: conversion of a number from internal binary representation to a string of ASCII characters representing the number in human readable form. return stack: LIFO (last in first out) stack containing return addresses of colon definitions, as well as other values, such as loop counters and limits. runtime part: word that is added to colon definition by an immediate word during compilation. When the colon definition is executed, the run time part will be executed. screen: block of 1024 bytes usually containing FORTH source program text. The screens are stored in a RAM disk stack: LIFO (last in first out) data stack central to the operation of FORTH, on which values are passed between words. text interpreter: FORTH word that reads words from the input stream and interprets or compiles them, depending on the STATE variable. user variable: Variable stored in a memory area whose start address can be changed. With multi tasking, each task has its own version of the user variables. vocabulary: list of FORTH words. 3.3 WORDS IN FORTH VOCABULARY ! 16b addr --- 83 writes 16b in memory at address addr. !CSP --- stores the stack pointer in the CSP variable. Used internally by the compiler for checking that constructs like IF..THEN are complete. # ud1 --- ud2 83 used between <# and #> (numeric conversion). Divides ud1 by BASE and obtains the rightmost digit as the remainder. The ASCII code of that digit is added to the numeric string using HOLD. The result is ud1 divided by BASE, which is the original number without the rightmost digit. #> ud --- addr n 83 ends numeric conversion, returns address and length of the converted string. #B --- n constant, number of screens in the RAM disk on the Spectrum 48. #S ud1 --- ud2 83 Converts all remaining digits to ASCII using #, ud2 is 0. #SCR --- n returns number of screens in RAM disk. #TIB --- addr 83 user variable containing the number of characters in the input buffer. ' --- addr 83 reads word from input stream, looks it up in the dictionary and returns the compilation address, error message if the word is not found. 'ERRNUM --- addr user variable containing the address of the word to be executed if NUMBER encounters an error. This way the word NUMBER can be extended so it can parse other data types, for instance floating point numbers. ( --- I83 skips the input stream until ), is used for comment. (+LOOP) w --- runtime part of +LOOP. (.") --- runtime part of ." (;CODE) --- runtime part of DOES> Can only occur in a colon definition. When executed, the colon definition is exited and the address following (;CODE) is put in the code field of the last created definition. (?DO) w1 w2 --- runtime part of ?DO (ABORT") f --- runtime part of ABORT" (DO) w1 w2 --- runtime part of DO (EMIT) --- addr user variable that contains the address of the word to be executed by EMIT. (ERRNUM) f --- Causes an error of f is true. Normally 'ERRNUM points to this. (FIND) addr1 addr2 --- addr3 n This is the internal routine of FIND. addr2 is the name field address of the last word in the vocabulary. Otherwise identical to FIND. (FORGET) addr --- addr is the link field address of the word to be forgotten. Removes this word and any words defined later from the dictionary. (KEY) --- addr user variable containing the address of the word to be executed by KEY. (LOOP) --- runtime part of LOOP (WAIT) --- addr variable containing the address of the word to be executed while waiting in KEY or PAUSE. A multitasker could be hooked up to this word. (WORD) c addr1 --- addr2 Main word parsing routine of text interpreter. addr1 is the address in the input stream where the search for a word must start. The first character unequal to c is the first character of the word, any consecutive characters all unequal to c are taken to be part of the word. addr2 is the address in the dictionary after the character c that terminates the word. A byte 0 marks the end of the input stream, addr2 will not increment beyond the end of the input stream. The parsed word is stored as a counted string at the address in DP. The counted string is followed by a space. The counted string will have length 0 if no word was parsed from the input stream. * w1 w2 --- w3 83 multiplies w1 by w2. */ n1 n2 n3 --- n4 83 multiplies n1 by n2 and divides by n3. The intermediate result has double precision. */MOD n1 n2 n3 --- n4 n5 83 like */ but returns n4 as the remainder and n5 as the quotient of the division. + w1 w2 --- w3 83 adds w1 an w2. +! w addr --- 83 adds w to the contents of address addr and stores the result there. +- n1 n2 --- n3 returns n1 negated if n2 is negative, otherwise returns n1. +LOOP addr 3 --- IC83 (runtime) w --- terminates a DO LOOP. adds w to the index and terminates the loop if the addition crosses the boundary between limit-1 and limit. Jumps to the start of the loop if the loop is not terminated. , 16b --- 83 increases the size of the dictionary by 2 bytes and stores 16 at the end of the dictionary. - w1 w2 --- w3 83 subtracts w2 from w1 --> --- I may only occur in a screen, causes the text interpreter to continue on the next screen. -1 --- -1 constant -1 -ROT 16b1 16b2 16b3 --- 16b3 16b1 16b2 moves the top of stack to the third position. -TRAILING addr n1 --- addr n2 83 if the string with address addr and length n1 contains spaces at the end, the length is decreased until there are no spaces at the end anymore. . n1 --- 83 prints the number n1. ." --- IC83 (runtime) --- reads the characters from the input stream until " and causes this text to be printed at runtime. .( --- I83 reads the input stream until ) and prints this text immediately. .R n1 n2 --- prints n1 right-justified to a length of at least n2 characters. Spaces are added to the left if necessary. .S --- prints the contents of the stack without changing it. / n1 n2 --- n3 83 divides n1 by n2. n3 is always rounded down (floored division). /MOD n1 n2 --- n3 n4 83 divides n1 by n2. n3 is the remainder with the same size as n2. n4 is the quotient, which is rounded down. This means rounded towards -infinity, not towards zero and it is called floored division. 0 --- 0 constant 0 0< n1 --- f 83 f=true if n1 is negative, false otherwise. 0= n1 --- f 83 f=true if n equals 0, false otherwise. 0> n1 --- f 83 f=true if n is positive, false otherwise. 1 --- 1 constant 1 1+ w1 --- w2 83 adds 1 to w1. 1- w1 --- w2 83 subtracts 1 from w1 2 --- 2 constant 2 2! 32b addr --- 83 writes 32b at address addr. 2* w1 --- w2 multiplies w1 by 2 2+ w1 --- w2 83 adds 2 to w1 2- w1 --- w2 83 subtracts 2 from w1 2/ n1 --- n2 83 divides n1 by 2. 2@ addr --- 32b 83 reads 32b from memory address addr 2DROP 32b --- D removes 32b (the top two cells) from the stack 2DUP 32b --- 32b 32n D duplicates 32b (the top two cells) on the stack, 2OVER 32b1 32b2 --- 32b1 32b2 32b1 D duplicates the second 32-bit entry on the stack. 2SWAP 32b1 38b --- 32b2 32b1 D swaps the two top most 32-bit entries on the stack, 2ROT 32b1 32b2 32b3 --- 32b2 32b3 32b1 D moves the third 32-bit element on the stack to the top. 3 --- 3 constant 3 : --- I83 this word is not allowed during compilation. It performs the following actions: - reads a word from the input stream. - creates a new colon definition in the dictionary, which is not finished and which cannot be found yet. - switches the text interpreter to compilation state. - CONTEXT vocabulary is set to CURRENT. ; --- I83 this word is only allowed during compilation. It performs the following actions: - Finishes the colon definition currently being compiled in the dictionary by adding EXIT to it. - Removes the 'smudge' bit from the header of the last defined word, so it can be found. - switches the text interpreter to interpretation state. < n1 n2 --- f 83 f=true if n1 is less than n2, false otherwise. n1 n2 --- f 83 f=true if n1 is greater than n2, false otherwise. >< 16b1 --- 16b2 swaps the bytes of 16b1 >BODY addr1 --- addr2 83 converts a compilation address to the word's parameter field address. >IN --- addr 83 user variable containing the position where the text interpreter will read the next word. It is an offset from the start of either the input buffer or the block buffer. >MARK --- addr S used for compiling words like IF. It reserves space for a forward branch address and returns the address of that location. >NAME addr1 --- addr2 converts a compilation address to the word's name field address. >P --- causes all output to be directed to the printer until either >S is executed or a new command line is read. >R 16b --- 83 pushes 16b on the return stack, >S --- causes all output to be redirected to the screen. >RESOLVE addr --- S used for compiling words like THEN. It fills the current dictionary address HERE as a forward branch address into the location previously returned by >MARK. ? addr --- reads and prints the 16-bit number at address addr. ?BRANCH f --- S can only be used inside a colon definition. It is followed by a branch target address. It branches to the target address if f=false. ?COMP --- checks that the text interpreter is in compilation state, reports an error otherwise. ?CSP --- reports an error if the contents of the CSP variable are not equal to the current stack pointer. Used by ; to check that there are no unfinished control structures. ?DO --- addr 3 (runtime) w1 w2 --- marks the start of a loop. The index will be w2, the limit will be w1. If w1 is equal to w2, the loop will be skipped completely. ?DUP 16b --- 16b 16b or 16b 83 duplicates 16b on the stack, but only if it is not equal to zero. ?EXEC --- checks that the text interpreter is in interpretation state, reports and error otherwise. ?LOADING --- reports an error if the current input stream is in the block buffer. ?PAIRS 16b1 16b2 --- reports an error if 16b1 is not equal to 16b2, used internally by the compiler to check proper nesting of control structures like IF..THEN. ?STACK --- reports an error if too much data is on the stack or if the stack is empty and more has been popped from the stack than was pushed. ?TERMINAL --- f f=true if the EDIT key (CAPS-SHIT-1) was pressed, false otherwise. @ addr --- 16b 83 reads 16b from memory at address addr. ABORT --- 83 clears all stacks and restarts forth in a well-defined state by calling WARM. ABORT" --- IC83 (runtime) f --- reads the input stream until the " character and prints this text as an error message at runtime if f=true. ABS n --- u 83 returns the absolute value of n ADDR n1 --- addr returns the address of screen n in the RAM disk. On the Spectrum 128 it switches to the correct memory bank. ALLOT n --- 83 enlarges or the dictionary by n bytes of reduces it by n bytes if n is negative. reports an error if there is insufficient memory to enlarge it or if the base system would be lost by reducing the size of the dictionary. AND 16b1 16b2 --- 16b3 83 returns the bitwise AND of 16b1 and 16b2. AT u1 u2 --- positions the cursor at line u1 and column u2. 0 0 AT is the topmost line and the leftmost column. B/BUF --- 1024 constant, the number of bytes in a block buffer. B/SCR --- 1 constant, the number of block buffers per screen. BANK n --- switches the memory bank at address C000H-0FFFFH on the Spectrum 128. n=0 for the normal RAM, 1-5 for the extra five memory banks containing the RAM disk. BASE --- addr 83 user variable containing the numeric base, used by numeric conversion, printing of numbers or reading numbers from the input stream. BCAL n --- jumps to line n in the BASIC program, The BASIC statement RANDOMIZE USR 27036 returns to Forth and Forth, continuing just after the BCAL call. If the BASIC program has used the bottom two lines on the screen, the BASIC program must execute PRINT; thereafter (this is required to set the output channel to the normal screen output). BEGIN --- addr 1 IC83 starts a BEGIN..UNTIL loop or BEGIN..WHILE..REPEAT loop. BL --- 32 constant, ASCII code for blank space BLK --- addr 83 user variable that contains the screen number from which the input stream is read. This variable contains 0 if the input stream is read from the input buffer and not from a screen. The position in the input stream is fully defined by BLK and >IN together. BLANK addr u --- fills the memory region starting at addr and u bytes in size with blank spaces. BLOCK n --- addr 83 causes screen n to be loaded into the block buffer and returns the address of that block buffer. Any screen contained in the block buffer that has been changed, will be copied back to the RAM disk first. BLOCKS --- 83 puts the editor in a state in which each screen is considered a separate text. BRANCH --- S like ?BRANCH, but jumps unconditionally. will be compiled by ELSE and REPEAT. BS --- prints a backspace. BUFFER n --- addr 83 creates an empty block buffer for screen n (without reading it from the RAM disk) and returns its address. BYE --- returns to BASIC. This is always 48k BASIC, but on the 128k Spectrum the bank switch port will not be disabled. C! 8b addr --- 83 writes 8b to memory at address addr. C, 8b --- extends the dictionary by 1 byte and puts 8b at that new location. C/L --- u returns the number of characters per line. C@ addr --- 8b 83 reads 8b from memory address addr. CAP --- switches CAPS LOCK on or off. CAT --- shows the names of all files on the disk, Microdrive cartridge or tape. If used with tape, the command has to be terminated by pressing the BREAK key. CHAN n --- selects the output channel, 2 is screen, 3 is printer. CLEAR n --- fills screen n with blank spaces. CLS --- clears the video screen. CMOVE addr1 addr2 u --- 83 copies memory area starting at addr1, size u bytes to address addr2. The byte at addr1 will be copied first. CMOVE> addr1 addr2 u --- 83 Like CMOVE, but copying the bytes in reverse order, so the byte at addr1 will be copied last. If the source block and destination block do not overlap, CMOVE and CMOVE> will have an identical result. COLD --- cold start of FORTH. It performs the following actions, in addition to WARM. - Removes all words from the dictionary at an address higher than FENCE> - Checks for the presence of the extra RAM of the 128k and sets LO accordingly. - initializes all relevant user variables. COMPILE --- 83 may only occur inside an immediate colon definition. Compiles the word immediately following it in the colon definition by adding its address to the dictionary. CONSTANT 16b --- 83 (runtime) --- 16b reads a word from the input stream and creates a constant with that name and value 16b CONTEXT --- addr S user variable containing the address of the vocabulary that will be searched first. CONVERT ud1 addr1 --- ud2 addr2 83 converts ASCII string to number. reads ASCII characters from address addr1+1. If the character is a digit, ud1 will be multiplied by the value of BASE and the digit will be added to it. CONVERT will terminate on the first character that is not a digit. addr2 is the address of the first character that is not a digit. ud2 is the result of converting the ASCII digits to a binary number. COPY n1 n2 --- copies screen n1 to n2. COUNT addr1 --- addr2 n 83 n is the byte at address addr1, addr2 is addr1+1. This is used to convert the address of a counted string to address and length. CR --- 83 prints a carriage return and newline. CREATE --- 83 (runtime) --- addr reads a word from the input stream and creates a new word in the dictionary without allocating any space for data. This can be done with ALLOT. At runtime the word returns the end address of the dictionary at the time CREATE was called, this will be the start of the area added with ALLOT. CSP --- addr user variable containing the value of the stack pointer when the last colon definition was started. Will be checked when the colon definition is finished. CURRENT --- addr S user variable containing the address of the vocabulary to which new words will be added. D+ wd1 wd2 --- wd3 83 adds wd1 and wd2. D+- d1 n --- d2 d2 is d1 negated if n is negative, otherwise it is d1. D- wd1 wd2 --- wd3 D subtracts wd2 from wd1. D. d --- D prints the double number d. D.R d u --- D like .R, but with a double precision number. D0< d --- f D f=true if d is negative, false otherwise. D0= 32b -- f D f=true if 32b equals 0, false otherwise. D< d1 d2 --- f 83 f=true if d1 is less than d2, false otherwise. DABS d1 --- ud1 D returns the absolute value of d1. DECIMAL --- 83 sets the BASE variable to 10. DEFINITIONS --- 83 sets the CURRENT vocabulary to the CONTEXT vocabulary. DELETE --- reads a word from the input stream and deletes the file with that name. DEPTH --- n 83 returns the number of items on the stack. DIGIT c --- u true or false converts ASCII digit c to digit value u with a true flag. return only a false flag if c does not contain a valid digit. DLITERAL 32b --- I like LITERAL, but with a double number instead. It compiles LIT twice. DNEGATE d1 --- d2 83 returns d1 negated. DO --- addr 3 83 (runtme) w1 w2 --- starts a loop. w2 is the index, w1 is the limit. If w1 is equal to w2, the loop will be iterated 65536 times, as opposed to ?DO that will skip the loop (iterate 0 times) in this case. DOES> --- IC83 (runtime) --- (runtime of created word) --- addr can only occur in a colon definition that contains CREATE. - When compiling, the word will compile the word (;CODE) followed by a machine code CALL instruction to DOCOL (code field for colon definitions). - At runtime, (;CODE) will change the code field of the word just created by CREATE, so it will call the part after DOES>. - At runtime of the created word, the parameter field address will be pushed on the stack and the part of the colon definition following DOES> will be called. DP --- addr 83 variable containing 1 more than the highest address of the dictionary. DPL --- addr user variable containing the location of the decimal point in the number last read by NUMBER. 0 means that the decimal point is at the right of the number, 1 means that 1 digit follows the decimal point etc. Contains -1 if the number does not contain a decimal point at all. DRIVE n --- selects the mass storage medium to use. n=0 for tape, n=1..8 to select drive no. n. DROP 16b --- 83 removes 16b from the stack. DUMP addr u --- shows the contents of the memory area starting at addr and u bytes in size. The contents will be shown both in hexadecimal and in ASCII form. DUP 16b --- 16b 16b 83 duplicates 16b on the stack. EDIT n --- starts the editor on screen n. EDITOR --- vocabulary containing words that are used only by the editor, ELSE addr1 2 --- addr2 2 IC83 used in IF..ELSE..THEN structure. Words between ELSE and THEN will be executed if and only if the flag checked by IF is false. EMIT c --- 83 prints the ASCII character c, using the word contained in (EMIT). EMPTY-BUFFERS marks the block buffer as empty. FLUSH will not copy it to the RAM disk. ERASE addr u --- fills memory area starting at addr and with a size of u bytes with byte 0. EXECUTE addr --- 83 executes the word with compilation address addr. EXIT --- 83 exits the colon definition containing this word. In interpretation, it exits interpretation of the screen containing the word. EXPECT addr u --- 83 reads u characters from the keyboard and stores them in memory starting at addr, Typed characters are echoed to the screen. Special keys have the same functions as on the command line. Exits when either ENTER is typed or when u characters are typed. FENCE --- addr variable containing the address below which the dictionary will be preserved. COLD will delete everything above this address. This must contain a link field address. FILE --- puts the editor in a state in which the entire RAM disk (or a group of 16 screens on the Spectrum 128) is considered one large text file. FILL addr u 8b --- 83 Fill memory area starting at addr, u bytes in size with byte 8b. FIND addr1 --- addr2 n 83 addr1 is the start address of a counted string containing the word to be found in the dictionary. FIND searches the CONTEXT vocabulary first, the CURRENT vocabulary next. addr2 is the compilation address of the word found or it will be equal to addr1 if the word was not found. n is as follows: 0 if the word was not found -1 if the word is found and not immediate. 1 if the word is found and immediate. FIRST --- addr 83 returns the first address of the block buffer. FLUSH --- 83 saves the contents of the block buffer to the RAM disk if it is not empty. Next it marks the block buffer as empty. FORGET --- 83 reads a word from the input stream and removes the word with that name from the dictionary, plus all words defined later. Reports an error if the word is not found or if a word below FENCE would be deleted. FORMAT --- fills the entire RAM disk with blank spaces. FORTH --- I83 vocabulary that contains all words from this list. All other vocabularies will be linked to the FORTH vocabulary. FORTH-83 --- 83 shows that this system is FORTH-83. GET n --- reads a word from the input stream and reads the file with that name from mass storage into the RAM disk at screen n and any following screens. GETFN u1 u2 --- reads a word from the input stream and stores that in the BASIC variable A$. n1 is store in BASIC variable I and n1 is stored in BASIC variable J. Is used by the mass storage commands like GET and PUT before calling a BASIC routing with BCAL. H. u --- prints u as four hexadecimal digit. HERE --- addr 83 returns the first address after the end of the dictionary. HEX --- sets BASE to 16. HLD --- addr user variable containing the address, just below which the next digit will be stored during numeric conversion. HOLD c --- 83 adds ASCII character c to the string created during numeric conversion. I --- w 83 returns the index of the innermost loop. I' --- w returns the limit of the innermost loop. ID. addr --- addr is a name field address of a FORTH word. Prints the name of that word, IF --- addr 2 IC83 (runtime) f --- used in IF..THEN and IF..ELSE..THEN. The words between IF and ELSE or between IF and THEN are only executed if flag=true. IMMEDIATE --- 83 turns the word that was defined latest into an immediate word. INDEX n1 n2 --- shows the first lines of the screens n1 through n2 INKEY --- c c=0 if no key is pressed, else c is the ASCII code of the key being pressed. INTERPRET --- The text interpreted. It reads words from the input stream until then end and interprets or compiles. Numbers containing a . will be considered 32-bit numbers. J --- w returns the index of the loop immediately outside the innermost loop. KEY --- c 83 reads a character from the keyboard and returns the ASCII value as c. Uses (KEY). LATEST --- addr returns the address of the word that was defined latest. LEAVE --- 83 leaves the innermost loop, removes values associated with the loop (index, limit) from the return stack. LIMIT --- addr 83 the address just above the block buffer. LIST n --- prints the contents of screen n. LIT --- 16b can only occur inside a colon definition and is followed by a number. pushes the number onto the stack. Will be compiled by LITERAL. LITERAL 16b --- nothing or 16b I83 of the interpreter is in compilation state, 16b will be compiled as a literal, when it is interpreting, nothing will happen. LLIST n1 n2 --- prints the contents of screens n1 through n2 on the printer. LO --- addr variable containing the bottom address of the RAM disk. Contains 0 on the Spectrum 128. LOAD n --- 83 submits screen n to the text interpreter as input stream. After the screen is loaded, it returns to the point in the input stream just after LOAD. LOOP addr 3 --- IC83 marks the end of a loop. The index will be incremented by 1 and the loop will terminate if it becomes equal to the limit, otherwise the next iteration of the loop will be started. M* n1 n2 --- d multiplies n1 and n2, giving a 32-bit product. M/ d n1 --- n2 n3 divides d by n1 giving n2 as a remainder and n3 as a quotient, using the same floored division rule as /MOD. M/MOD ud1 u1 --- u2 ud2 divides ud1 by u1 giving u2 as a remainder and ud2 as a quotient. MAX n1 n2 --- n3 83 n3 is the maximum of n1 and n2. MIN n1 n2 --- n3 83 n3 is the minimum of n1 and n2. MIR 16b1 16b2 16b3 --- 16b3 16b2 16b1 exchanges the top of stack with the number at the third position. MOD n1 n2 --- n3 83 computes the remainder of the division of n1 by n2. MTYPE addr u --- fast version of TYPE that does not use (EMIT) for each character. Is primarily used by the editor. NAME> addr1 --- addr2 converts a name field address to the word's compilation address. NEGATE n1 --- n2 83 computes n2 negated (n2 = -n1). NOOP --- no operation (do nothing). NOT 16b1 --- 16b2 83 returns the ones' complement (all bits inverted). NUMBER addr --- wd converts a string starting at address addr+1 and ending in a blank space to a double precision number. The DPL variable will contain the position of any decimal point in the number (counted from the rightmost position). DPL contains -1 if the number does not contain a decimal point. As a special case, an & character followed by a single character is converted to the ASCII code of that character. Executes the word contained in the 'ERRNUM variable if the string does not represent a number, this will normally report an error message. OR 16b1 16b2 --- 16b3 83 returns the bitwise OR of 16b1 and 16b2. OUT --- addr user variable containing the position on the line where the next character will be put by EMIT. Will be set to 0 by CR and be incremented by EMIT. OVER 16b1 16b2 --- 16b1 16b2 16b1 83 duplicates the second value on the stack. P! 8b addr --- writes 8b to the output port at address addr. P@ addr --- 8b reads 8b from the input port at address addr. PAD --- addr 83 returns the address of a 64 byte buffer for string processing. This is at a certain offset above the dictionary. PAUSE u --- waits until u timer interrupts have occurred. The Spectrum is hardwired to generate 50 timer interrupts per second. While waiting, execute the word whose address is contained in the (WAIT) variable. PICK u --- 16b 83 reads a value from the stack at position u, counted from the TOP. So: 0 PICK is equivalent to DUP 1 PICK is equivalent to OVER The value is copied from the original stack position and not removed. PKEY --- c waits until a key is pressed and returns the ASCII code. PUT n1 n2 --- reads a word from the input stream and writes the screens n1 through n2 to the mass storage medium in a file with that name. QUERY --- reads a maximum of 128 characters from the keyboard into the input buffer using EXPECT and clears BLK and >IN to zero. Make sure to undo any input and output redirections before reading the line. This is word used by FORTH to read command lines. QUIT --- 83 clears the return stack and reads command lines from the keyboard and interprets them. R> --- 16b 83 pops 16b from the return stack. R0 --- addr user variable containing the highest address (bottom address) of the return stack. R@ --- 16b 83 reads 16b from the top of the return stack without removing it. REPEAT addr1 addr2 4 --- 83 marks the end of a BEGIN..WHILE..REPEAT loop. Returns to the BEGIN position. ROLL u --- 83 moves the value at position u in the stack to the top (somewhat like PICK), but it removes the value from the original location. 1 ROLL is equivalent to SWAP. 2 ROLL is equivalent to ROT. ROT 16b1 16b2 16b3 --- 16b2 16b3 16b1 83 moves the third value on the stack to the top. RP! --- initializes the return stack pointer. RP@ --- addr returns the value of the return stack pointer. RUN --- reads a word from the input stream and loads a file with that name into the RAM disk starting from screen 1. Next loads the first screen. S->D n --- d converts a single precision number into a double precision number, sign extension. S0 --- addr user variable that contains the bottom of the stack. SAVE-BUFFERS --- 83 copies the contents of the block buffer to the RAM disk if it contains a valid screen. SCR --- addr user variable containing the number of the screen last displayed with LIST. SIGN n --- 83 used in numeric conversion. Adds a - to the numeric conversion string if n is negative. SMUDGE --- Changes the 'smudge' bit in the header of the word that is defined latest. If this bit is set the word cannot be found with FIND, if it is clear it can be found. SP! --- initializes the stack pointer. SP@ --- addr returns the value of the stack pointer. SPACE --- 83 prints a single blank space. SPACES u --- 83 prints u black spaces. SPAN --- addr 83 user variable containing the number of characters read by the latest call to EXPECT. STATE --- addr 83 user variable containing the state of the text interpreter, 0 is interpretation, otherwise it is compilation. STOPOFF --- Disables the BREAK key. STOPON --- Enables the BREAK key, so it will interrupt any running program. STYPE addr u --- Prints a string starting at addr and u characters long, thereby clearing the highest bit of each character and printing control characters as dots. Used by DUMP. SWAP 16b1 16b2 --- 16b2 16b1 83 swaps the top two values on the stack. TCH c --- prints the character with ASCII code c on the screen or printer. TERMINAL --- sets the relevant variables, so subsequent input will be read from the keyboard and output will be printed on the screen. THEN addr 2 --- IC83 marks the end of an IF..THEN or IF..ELSE..THEN structure. TIB --- addr 83 constant, the start address of the input buffer. TOGGLE addr 8b --- changes the byte stored at address addr, by exclusive-ORing it with 8b. TRAVERSE addr1 n --- addr2 adds n to addr until bit 7 of the byte at the address is set. So with "addr 1 TRAVERSE" you scan memory at increasing addresses to find a byte with bit 7 set, with "addr -1 TRAVERSE" you can memory at decreasing addresses. Is used by >NAME and NAME> to find the opposite end of the name field. TYPE addr u 83 prints u characters, starting at address addr, using (EMIT) for each character. U. u --- 83 prints the unsigned number u. U< u1 u2 ---f 83 f=true if u1 is less than u2 (unsigned numbers), false otherwise. UM* u1 u2 --- ud 83 multiply unsigned numbers u1 and u2, giving a double precision result. UM/MOD ud u1 --- u2 u3 83 divides unsigned number ud by u1, giving u2 as a remainder and u3 as a quotient. UNDER 16b1 16b2 --- 16b2 removes the second value from the stack. UNTIL addr 1 --- IC83 (runtime) f --- mars the end of a BEGIN..UNTIL loop. If f=false, return to the start of the loop, otherwise terminate. UPDATE --- 83 indicates that the contents of the block buffer have been modified and hence must be written back with SAVE-BUFFERS. As this system has a fast RAM disk, buffers will always be written back. USER n --- (runtime) --- addr reads a word from the input stream and creates a user variable with that name, stored at offset n in the user area. At runtime the address is returned. VARIABLE --- 83 (runtime) --- addr reads a word from the input stream and creates a variable with that name with storage for a single 16-bit value. At runtime the address is returned. VLIST --- shows all words in the CONTEXT vocabulary, preceded by their name field addresses. VOC-LINK --- addr user variable containing a pointer to a linked list of all vocabularies. VOCABULARY --- 83 reads a word from the input stream and creates a vocabulary with that name, which is linked to the CONTEXT vocabulary, so the words from the linked vocabulary can also be found when this vocabulary is searched. Each vocabulary will ultimately be linked to the FORTH vocabulary. At runtime set the CONTEXT vocabulary to the selected vocabulary. WARM --- Warm start of Forth. It performs the following actions: - Reset CONTEXT and CURRENT to the FORTH vocabulary. - Reset some user variables. - Execute the command loop via QUIT. WHERE n1 n2 --- n1 is the screen number, n2 the value of >IN at the time an error was detected while a screen was loaded. These are put on the stack by ABORT. Start the editor at the given screen with the cursor at the given location. WHILE addr1 1 --- addr2 4 IC83 (runtime) f --- occurs inside a BEGIN..WHILE..REPEAT loop and marks the decision point to exit the loop. If f=false, jump to the point beyond REPEAT to terminate the loop. WIDTH --- addr user variable containing the maximum number of characters that will be stored of the name of a newly created word. Normally this is 31, but it could be reduced to save memory or speed up compilation. The original length of the name will be stored and FIND will only match words with the same length. WORD c ---addr 83 reads a words from the input stream, starting at the first character that is not equal to c and ending at a character that is equal to c. It uses (WORD). The word will be stored at the end of the dictionary (at the HERE address) as a counted string. This address is returned. XOR 16b1 16b2 --- 16b3 83 bitwise exclusive-OR of 16b1 and 16b2. ZX-PRINT --- Enables the ZX-Printer on the Spectrum 128. [ --- I83 switches the text interpreter to the interpretation state. ['] --- IC83 reads a word from the input stream, looks it up in the dictionary and compiles the compilation address as a literal. [COMPILE] --- IC83 reads a word from the input stream, looks it up in the dictionary and compiles it, even if the word in immediate. ] --- 83 switches the text interpreter to the compilation mode. \ --- may only be used on a screen. Skips to the end of the current line. Used as a comment. 3.4 THE EDITOR VOCABULARY Words from this vocabulary are used internally by the editor and are not normally used by other programs, so the descriptions are very short. The editor works on data in the block buffer when it is in BLOCKS mode and directly on the RAM disk in FILE mode. CUR --- Shows or hides the blinking cursor on the screen. DEL --- erases the character at the cursor position, DN --- moves the cursor one line down. HO --- addr variable containing horizontal cursor position. HOME --- moves cursor to the start of the current screen. INS --- inserts a space character at the cursor position. LDEL --- deletes the current line. LE --- move the cursor one position to the left.. LIM --- addr One more than the last address of the text. It is the address beyond the block buffer in BLOCKS mode, it is 0 in FILE mode, as the RAM disk always extends to the end of RAM. LINS --- inserts a blank line. LPOS --- addr start address of the current line. LREST --- u number of characters to be displayed on the screen from the current line.. LST --- prints the current half screen on the video screen with a line below it showing the screen number and A or B. PD --- moves half a screen down. POS --- addr returns the address in the text. PU --- moves half a screen up. REST --- u number of characters to be displayed on the screen after the cursor position. RI --- moves the cursor one position to the right. SADR --- addr returns the start address of the current screen or block buffer. SET --- Set print position on video screen to cursor location in text. TXT --- addr variable containing the editor mode (BLOCKS or FILE). UP --- Moves the cursor one line up. VE --- addr variable containing the vertical position of the cursor from the start of the current screen (range 0..31) . 4 COMPILER INTERNALS 4.1 THE INNER INTERPRETER. The inner interpreter works with direct threaded code, this means: the addresses contained in a colon definition are directly jumped to. Therefore the code field of each FORTH word contains machine instructions. For code definitions these are the machine instructions of the code definition. For constants, variables, colon definitions and user variables, the code field contains a subroutine call to the appropriate runtime routine (DOCON for constants, NEXT for variables, DOCOL for colon definitions and DOUSER for user variables). For CREATE DOES> words, the code field contains a subroutine call to the address directly following (;CODE) in the defining word. At this address a subroutine call to DOCOL is compiled. The first call puts the parameter field on the stack, the second call starts execution of the DOES> part of the defining word as a colon definition. In all cases, the CALL instruction causes the parameter field to be pushed on the stack, so it is readily available to the handling routine (DOCON, DOCOL, DOUSER), or it just is there in case of variables. Each code definition ends with a jump to the NEXT routine. This is a JP (IX) instruction, whereby the address of the next routine is always stored in the IX register. The NEXT routine is the inner interpreter itself. It reads the address of the next word to execute (pointed to by the instruction pointer, which is incremented) and jumps to it. The NEXT routine consists of 7 instructions, as follows: NEXT: EX DE,HL ; Store the instruction pointer in HL LD E,(HL) ; Read next address, lower byte INC HL LD D,(HL) ; Read next address, higher byte INC HL EX DE,HL ; Put incremented instruction pointer in DE, jump address in HL JP (HL) ; Jump to the execution address. - The instruction pointer is DE. - The FORTH data stack pointer is SP. - The address of NEXT is stored in IX. - The return stack pointer is stored at address 69A7H, - The user area pointer is stored at address 6994H. - The W register is not used in this implementation. This is normally used to access the parameter field of the word being executed, but this is pushed on the stack by the CALL instruction in the code field. Like in most systems, both the data stack and the return stack grow downward. Whenever we refer to the top of stack, this means the lowest memory address and the bottom of stack is the highest memory access. The FORTH system runs a custom interrupt handler in Z-80 Interrupt Mode 2, which monitors the BREAK key and aborts execution if it is pressed. 4.2 HEADER STRUCTURE The words in this Forth system consist of four fields, they are: - Link Field: points to the name field of the previous word in the vocabulary. In the very first word, this field contains 0. Size is always 2 bytes. - Name Field: First byte: bit 7: always 1 bit 6: set if the word is immediate bit 5: smudge bit, set if the word shall not be found. bit 4..0: length of the name. The following bytes contain the characters of the name, whereby the final character has bit 7 set to 1. There may be fewer bytes than indicated by the length. See WIDTH variable for explanation. The end of the name is marked by setting bit 7. - Code Field, call instruction of 3 bytes. - Parameter field, contains all additional information used by the word, e.g. the value stored in a variable, the list of execution addresses in a colon definition. Note: code definitions do not have a separate code field and parameter field. Machine instructions start at the code field address and the code definition contains as many bytes of machine code as required. FIND and ' always return the code field address and this is the same as the compilation address. >NAME converts the code field address to the name field address. >BODY converts the code field address to the parameter field address. NAME> converts the name field address to the code field address. 4.3 MEMORY MAP All addresses are in decimal. 25580: highest address (bottom) of the data stack. The data stack has around 1kB free space to grow downward. The data stack lives in the memory area used by BASIC. The top part of this area is used by BASIC itself to store the stack. There are 20 bytes above the stack to allow for stack underflows without corrupting other memory areas. 25599: Top of RAM address for BASIC. Any addresses above it are not used by BASIC. 25600: input buffer, 128 bytes. 26000: highest address (bottom) of return stack. Grows toward input buffer, 272 bytes of space. 26000-27027: block buffer and its markings (which screen is contained in it). 27028: user area pointer. 27030: cold start entry 27033: warm start entry 27036: BCAL return. 27039: start of user area, 60 bytes in size. Locations 48..59 are still unallocated. 27047: return stack pointer, contained in user area. 27099: inner interpreter (=NEXT, value stored in IX instruction). The dictionary starts after this. 33792: is address 8400H. 257 bytes filled with 85H, interrupt vector table. The Z-80 expects to read a an interrupt number from the bus in Interrupt Mode 2, but the ZX Spectrum does not contain hardware to provide it. Hence the Z-80 reads an undefined byte from the bus (usually 0FFH, but there is no guarantee). By filling these 257 bytes with 85H, we make sure that the CPU will jump to 8585H regardless of the byte read. The Z-80 I register is set to 84H. Due to hardware restrictions, the interrupt vector table must be above 8000H. 34181: is address 8585H, interrupt routine. dictionary continues after this. HERE=36989: The dictionary will be extended starting here. The word read by WORD will be stored here too. PAD=HERE+56: The numeric conversion buffer extends down from this address. The address at PAD and above (at least 64 bytes) can be used as a scratchpad. LO @ =55296 on 48K Spectrum: start of RAM disk. On the Spectrum 128 LO contains 0 and all addresses till 65535 can be used by FORTH. The RAM disk is located at addresses 49152 and 65535 in five additional banks. Words in the dictionary above 49152 are not allowed to access the RAM disk directly. The editor and some words in the base system do directly access the RAM disk, but they are all located below 49152. 4.4 BASIC PROGRAM All tape and disk operations are carried out via BASIC. The variable I contains the start of the block, J the length of the block in bytes and D the drive number (0 for tape). A$ contains the file name. The BASIC program creates these variables in a particular order (including DIM A$(10) to create A$ of size exactly 10) and it is important that this order is not changed. The FORTH system writes to fixed offsets into the BASIC variable area. See section 3.3 for details on GETFN (to store values into the BASIC variables) and BCAL (to invoke a BASIC routine). Lines in the BASIC program: line 1: warm start line 10: start after loading. The BASIC program will be saved such that it automatically starts at line 10 after loading. Reserves memory and loads FORTH83.BIN as data block. Next continues into cold start. line 20: cold start. line 40: DELETE routine. line 45: CAT routine. line 50: PUT routine. line 55: GET routine. line 60: saving the FORTH system. The BASIC program temporarily stores the value of D (drive number) at address 23728 and reads it back after CLEAR, so this values will be preserved. Note that variables are also preserved across saving and loading, so D will contain the drive number when the program is started at line 10. 4.5 META COMPILING. The files META1 through META4 contain the meta compiler with which the FORTH system can be compiled from source. Proceed as follows: - When using Microdrives or disks, DELETE FORT83.BIN - RUN META1 - RUN META2 - RUN META3 - RUN META4 This will save FORT83.BIN on tape or disk. When using tape, save it on a temporary tape. - BYE - From BASIC, GO TO 10. This will load and run the newly generated FORT83.BIN file. When using tape, you have to rewind the tape to get the file just saved. - RUN EDITOR. When using tape, you will have to switch tapes to get to the tape that contains that file. - When using Microdrives or disks: DELETE run DELETE FORT83.BIN - 60 BCAL This will save the new FORTH system, complete with BASIC part and editor. When using tape, you may have to switch to the correct tape of course. Note: the BASIC part will not be regenerated by the meta compiler. It is already source and it can be edited. The target system is the end product of the meta compiler. It contains all machine code and the entire dictionary of the Forth system being built. It is however located at a different address from which it will later run. The target system resides in a special memory area, outside the dictionary of the running Forth system. Code in the target system will never be run during meta compilation. It is possible that a target system contains code that is to be run on an entirely different computer, possibly having a different CPU architecture. This meta compiler however, is intended for generating a new Forth system for the system on which it runs. The file META1 contains the meta compiler itself. It consists of: - words to access the memory area where the target system will be built. These are words like !-T @-T and ,-T that are equivalent to ! @ and , - The META vocabulary. Most words have identical names to words in the FORTH vocabulary, but they compile to the target system. - The TARGET vocabulary. This contains the immediate words of the target system. - CREATE-T. It creates headers in the target system. Further it creates definitions in the META vocabulary - The meta assembler. It generates machine code in the target system. - Words that perform the actions of immediate words in FORTH, but that act on the target system. These are located in META. Examples of vocabulary usage, the word IF. - The FORTH vocabulary contains the word IF used by FORTH. It is used when FORTH compiles normal words in its own dictionary. - The META vocabulary contains the word IF used by FORTH when meta compiling. It is used when the meta compiler compiles colon definitions into the target system. - The TARGET vocabulary contains the word IF that will be used by the target system when it will itself run. The files META2, META3 and META4 use the meta compiler to construct a target system. The file EDITOR will be used by the newly built system, to build its own editor. Using the meta compiler you can make changes to the FORTH system at the source level and any part of it can be modified. It is also possible to create special versions of Forth that do not contain headers or compilation words, that contains special application. Projects like this must only be carried out by experienced FORTH programmers. The file meta_annotated.txt contains an annotated meta source listing that documents the entire meta compiler and the FORTH system generated with it. 5 THE ASSEMBLER 5.1 INTRODUCTION Using the assembler you can create words in machine code. This manual assumes that you are familiar with the Z80 and its standard mnemonics. There are two reasons why this assembler uses mnemonics that are different from the standard: - The standard assembler uses one and the same word as a mnemonic for instructions that must be translated differently into machine code. For instance LD is used for LD A,B LD (IX+12),23 LD HL,(RPTR) LD A,I LD SP,HL and all these use different opcodes. - Forth is suitable for expressions in postfix notation. Therefore it is straightforward to write an assembler that expects opcode mnemonics after the operands and the use of expressions in operands is straightforward as well. Labels are hardly ever used in a FORTH assembler. Instead we make use of control structures like IF..THEN, as is done in FORTH as well. This assembler was designed by Coos Haak in Utrecht, The Netherlands and he used it in his own FORTH. It was ported to FORTH83 by L.C. Benschop. 5.2 LOADING THE ASSEMBLER The assembler consists of two files: TASM (size 1 screen) and ASSEMBLER (size 8 screens). If you desire to have the assembler as a permanent part of FORTH (for instance if you desire to save the system complete with the assembler), you type: RUN ASSEMBLER You can type HERE FENCE ! to make it a permanent part of the system and then you can save the system as described in section 1.3. If you use the assembler temporarily, for instance just to load other extensions like the Double Number Extension Word Set, you type: RUN TASM This way the assembler is loaded at a high memory address outside the dictionary. With the command DISPOSE you can remove the assembler from the system after use and any words defined later (including code definitions created with the assembler) will remain in the dictionary. 5.3 CREATING CODE WORDS For the creation of code definitions, the following words are available. If the word is followed by the letter A, the word is part of the Assembler Extension Word Set. If the word is followed by the letter F, it is located in the FORTH vocabulary, otherwise it is located in the ASSEMBLER vocabulary. Assembling state means: interpretation state with ASSEMBLER as the CONTEXT vocabulary and BASE set to 16. ;C --- F synonym for END-CODE ;CODE --- FICA Assembler version of DOES>. Used in a colon-definition containing CREATE. Compiles (;CODE) and puts FORTH in the assembling state. The colon definition will at runtime modify the code field of the defined word, so it calls the machine code assembled after ;CODE. The created word will in turn call the machine code assembled after ;CODE with the parameter field address on the stack. ASSEMBLER --- FA Vocabulary containing all Assembler mnemonics, register definitions and the like. CODE --- FA reads a word from the input stream and creates a code definition with that name. Puts FORTH in the assembling state and sets the 'smudge' bit, so the word will not be found. END-CODE --- FA ends a code definition. Clears the 'smudge' bit, so the word defined latest can be found. CONTEXT is set to CURRENT and BASE is set to 10. Use this word to terminate an assembler definition that was started with CODE, ;CODE or LABEL. ENDM --- IC synonym for ; ends a macro. LABEL --- F reads a word from the input stream and creates a word with that name, which will return its parameter field address at runtime. Next it puts FORTH in the assembling state. A word created with LABEL can be used as a jump or call address in the definition of other code words. MACRO --- F Like : but makes ASSEMBLER the context vocabulary and sets BASE to 16. When the macro is executed, it will assemble (add to a new code definition) the instructions that are contained in it. XY --- addr F variable indicating which of the two index registers will be used. Values are 0DDH for IX, 0FDH for IY, just like the opcode prefixes used by the Z80. 5.4 THE REGISTERS The assembler uses the names A, B, C, D, E, H and L for the 8-bit Z80 registers. In addition it uses the name M, which can be used in most places where a register is allowed. M is the memory address pointed to by HL (this is like the 8080 assembler that uses M instead of (HL)). Register pairs are named B, D and H to indicate the BC, DE and HL register pairs respectively. In addition SP an AF are register pair names. AF is only allowed with PUSH and POP. B, D, H and SP are allowed in many instructions using register pairs. The order in two operand instructions is source followed by destination, which is the reverse of standard Z80 or 8080 notation. Hence B C LD is equivalent to LD C,B Use of index registers IX and IY: - Use the word X in front of instructions where use of the HL register is implicit, For instance X LDSP to indicate LD SP,IX - Use XH or XL instead of H or L for instructions that use H or L explicitly. - For instructions that actually do indexing (IX+12) instead of (HL), use special mnemonics starting with ) By default the chosen X register is IX. The IY register is tied up by the ROM routines and will hardly ever be used on the ZX-Spectrum. The word %Y changes the used index register to IY, %X changes it back to IX. 5.5 THE INSTRUCTION SET We use the same stack notation as in chapter 3, but with the following additions to denote values (all values are a single stack entry): b: bit 0-7 cc: condition code: z, cs, pe, m, v or their negations obtained with the word NOT r: register A, B, C, D, E, H, L or M rp: register pair B, D or H rps: register pair or SP. rpa: register pair or AF. disp: displacement between -128 and 127 for use with indexed addressing. LD r1 r2 --- copies r1 to r2. Equivalent to LD r2, r1 )LD r1 disp --- loads r1 from address IX+disp. Equivalent to LD r1,(IX+disp) )ST r disp --- writes r1 to address IX+disp. Equivalent to LD (IX+disp),r1 LD# 8b r --- loads r with immediate value 8b. Equivalent to LD r1,8b )LD# 8b disp --- Loads immediate value 8b at address IX+disp. Equivalent to LD (IX+disp),8b MOV rp1 rp2 --- copies register pair rp1 to rp2. Assembles to two Z80 instructions. For example D B MOV is equivalent to LD B,D LD C,E LDP# 16b rps --- loads register pair rps with immediate value 16b. Equivalent to LD rps,16b LDP addr rps --- loads register pair rps from address addr. Equivalent to LD rps,(addr) )LDP rps disp --- loads register pair rps from address IX+disp, B 2 )LDP is equivalent to: LD C,(IX+2) LD B,(IX+3) STP addr rps --- writea register pair rps to address addr. Equivalent to LD (addr),rps )STP rps disp --- writes register pair rps to address IX+disp. B 2 )STP is equivalent to: LD (IX+2),C LD (IX+3),B LDHL addr --- like LDP, but for HL register. Uses shorter opcode form. STHL addr --- like STP, but for HL register. Uses shorter opcode form. LDA addr --- loads A from address addr. Equivalent to LD A,(addr) STA addr --- writes A to address addr. Equivalent to LD (addr),A LDAP rp --- rp=B or D only! Loads A from address in rp. Equivalent to LD A,(BC) or LD A,(DE) But use M A LD for LD A,(HL) !!! STAP rp --- rp=B or D only! Writes A to adres in rp. Equivalent to LD (BC),A or LD (DE),A But use A M LD for LD (HL),A !!! LDSP --- loads SP with HL. LD SP,HL LDAI --- loads A with I. LD A,I LDIA --- loads I with A. LD I,a EXAF --- exchanges AF and AF'. EX AF,AF' EXDE --- exchanges DE and HL. EX DE,HL EXSP --- exchanges HL with top element on stack. EX (SP),HL CLR rps --- Loads rps with 0. LD rps,0 ADD r --- adds r to accumulator A. Equivalent to ADD A,r The instructions ADC, SUB, SBC, AND, XOR, OR and CP work the same way. )ADD disp --- adds contents of address IX+disp to accumulator A. Equivalent to ADD A,(IX+disp) The instructions )ADC, )SUB, )SBC, )AND, )XOR, )OR and )CP work the same way. ADD# 8b --- adds constant 8b to accumulator A. Equivalent to ADD A,8b The instructions ADC#, SUB#, SBC#, AND#, XOR#, OR# and CP# work the same way. ADDP rps --- adds rps to HL register. Equivalent to ADD HL,rps The instructions ADCP, SUBP and SBCP work the same way. Note that SUBP is a macro composed of A AND and rps SBCP INC rps --- increments register pair rps by 1. Equivalent to INC rps The instruction DEC works the same way. INR r --- increments register r by 1. Equivalent to INC r The instruction DER works the same way (equivalent to DEC r). )INR disp --- increments the byte at address IX+disp by 1. Equivalent to INC (IX+disp) The instruction )DER works the same way (equivalent to DEC (IX+disp) RL r --- Rotates register r one position to the left. Equivalent to RL r The instructions RR, RLC, RRC, SRL, SRA and SLA work the same way. )RL disp --- Rotates the byte at address IX+disp one position to the left. Equivalent to RL (IX+disp). The instructions )RR, )RLC, )RRC, )SRL, )SRA and )SLA work the same way. BIT b r --- test bit b of register r. Equivalent to BIT b,r The instructions RES and SET work the same way. )BIT b disp --- test bit b at address IX+disp. Equivalent to BIT b,(IX+disp). The instructions )RES and )SET work the same way. TST rp --- test if register pair rp is zero. B TST expands to the instructions. LD A,B OR A,C JP addr --- jumps to absolute address addr Equivalent to JP addr. The instructions JPNZ, JPZ, JPNC, JPC, JPPO, JPPE, JPP, JPM, CALL, JR, JRNZ, JRZ, JRNC, JRC, DJNZ en RST work the same way. Relative branch instructions (JR etc) take absolute addresses as inputs, but are assembled with relative branch offsets. Example: addr JPNZ is equivalent to JP NZ,addr JPHL --- jumps to the address in HL. Equivalent to JP (HL). The instruction JPIX works the same way. CALLC addr cc --- conditional call to address addr. cc is one of the condition codes that can be specified in lowercase letters as described in 5.6 Equivalent to CALL cc,addr RETC cc --- conditional return. cc is specified the same way as in CALLC. Equivalent to RET cc. PUSH rpa --- pushes rpa onto the stack. Equivalent to PUSH rpa The POP instruction works the same way. PRT --- RST 10H to print a character. HOOK 8b --- RST 8 DEFB 8b to report a BASIC error message or to call an Interface 1 function. IN 8b --- reads accumulator A from port 8b. Equivalent to IN A,(8b) The OUT instruction works the same way. The following instructions have no operands and use the exact same mnemonics as standard Z80 assembler: NOP, RLCA, RRCA, RLA, RRA, HALT, RET, DAA, CPL, SCF, CCF, DI, HALT, EXX, LDIR, LDDR, CPIR, IM1, EI, IM2 and NEG. Note that not all Z80 instructions are implemented by this assembler, but the missing instructions are unlikely to be ever used. They can be added to the assembler if required or their opcodes can be assembled directly with , or C, RPTR, DPTR and NEXT are 3 address constants. They denote the address of the return stack pointer, the address of the user area pointer and the address of the inner interpreter. 5.6 CONTROL STRUCTURES The assembler can use the IF..THEN, IF..ELSE..THEN, BEGIN..UNTIL and BEGIN..WHILE..REPEAT control structures from FORTH and they are implemented with relative jumps. IF, WHILE and UNTIL are preceded by a condition code, which can be Z, NZ, CS or NC. BEGIN .. NC UNTIL denotes a loop that must repeat until the carry bit is clear. Hence the relative jump instruction at the end will be JRC. Further we have BEGIN..AGAIN for an infinite loop and BEGIN..DSZ for a down counting loop with a DJNZ instruction at the end. The same control structures, except BEGIN..DSZ can also be used with absolute jumps. To specify absolute jumps, specify the relevant words in lowercase. Permissible condition codes are z, cs, pe, m and v (where v is the same as pe). Each of these condition codes can be followed by NOT to indicate the opposite condition. The same BEGIN..UNTIL construct specified with absolute jumps will become: begin .. cs NOT until 6 THE DOUBLE NUMBER EXTENSION WORD SET 6.1 INTRODUCTION The file DOUBLE (3 screens in size) contains all words required to implement the full Double Number Extension Word Set and additionally all words necessary to perform all operations with double precision, including multiplication, division and square root. Before loading DOUBLE, you must first load the assembler using either RUN TASM or RUN ASSEMBLER. Then load RUN DOUBLE and finally (if applicable) DISPOSE to remove the assembler. 6.2 EXTRA WORDS 2CONSTANT 32b --- D (runtime) --- 32b like CONSTANT, but for a 32-bit value. 2UNDER 32b1 32b2 --- 32b2 removes the second value from the stack. 2VARIABLE --- D (runtime) --- addr like VARIABLE, but with 4 bytes of space. D* wd1 wd2 ---wd3 multiplies wd1 with wd2. D/ d1 d2 --- d3 divides d1 by d2, rounding down (floored division). D/MOD d1 d2 --- d3 d4 divides d1 by d1. d3 is the remainder and d4 the quotient. D2* wd1 --- wd2 Multiplies wd1 by 2. D2/ wd1 --- wd2 D divides wd1 by 2. D= 32b1 32b2 --- f D f=true if 32b1 and 32b2 are equal, false otherwise. D> d1 d2 --- f D f=true if d1 is greater than d2, false otherwise. DIVISOR --- addr double number variable to store the divisor for D/ DMAX d1 d2 --- d3 D d3 is the maximum of d1 and d2. DMIN d1 d2 --- d3 D d3 is the minimum of d1 and d2. DMOD d1 d2 --- d3 d3 is the remainder of the division of d1 by d2. DU. ud --- like D. but for unsigned number. DU.R ud u --- like D.R but for unsigned number. DU< ud1 ud2 --- f D f=true if unsigned ud1 is less than ud2, false otherwise. SQRT ud --- u u is the square root of ud, rounded down. U.R u1 u2 --- like .R but for unsigned number. UD/MOD ud1 ud2 --- ud3 ud4 divides ud1 by ud2. ud3 is the remainder and ud4 is the quotient.. 7 THE FLOATING POINT EXTENSION 7.1 INTRODUCTION With this extension you can use floating point numbers in Forth. These numbers are 32 bits in size and occupy two entries on the data stack each, hence you can use 2@ and 2! to read and write floating point numbers in memory and all double precision stack words, such as 2DUP and 2SWAP, to manipulate them on the stack. The word NUMBER is extended with a means to process floating point numbers. Now you can enter floating point numbers from the text interpreter by immediately following a number (that may or may not contain a decimal point) with an & sign. The & sign can optionally be followed by a + or - sign and then a small integer that represents the exponent. The exponent is the power of BASE with which the number must be multiplied. This way we can enter floating point numbers in scientific notation in any number base. Examples 2& the number 2 -2.718& the number -2.718 12&+3 the number 12000 -0.041& the number -0.041 -4.1&-2 the number -0.041 The floating point words are contained in two files. The first file, FLOATING (8 screens in size) contains the basic operators, words to print floating point numbers, the extension of NUMBER to allow floating point input and a square root function. The assembler must be loaded first. This can be loaded with RUN TASM or RUN ASSEMBLER. Then the floating point extension can be loaded with RUN FLOATING. Finally the assembler can be removed with DISPOSE, if desired. The second file TRANSCEN (size 4 screens) contains trigonometric and logarithmic functions. This file does not require the assembler, but it does require FLOATING to be loaded. If the system is saved (using HERE FENCE ! 60 BCAL) and later reloaded or if it is restarted via a cold start, the word FLOAT must be executed to re-activate the extension to NUMBER that allows floating point input. 7.2 FORMAT AND PRECISION. A floating point number consists of two parts. The three least significant bytes form the fractional part (also called mantissa), a 24-bit number between 1 and 2. The leading bit represents 1, the second bits represents 1/2, the third bit represents 1/4 and so on. As the leading bit is always 1, this is omitted from the number and this position is used for the sign of the number (0 for positive, 1 for negative). The most significant byte is the exponent offset by 128. This represents the power of two (in the range -128..127) with which the fractional part is multiplied. The largest negative number (closest to zero) and the smallest positive number both represent the number zero. The largest positive number and the smallest negative number represent + or - infinity. These numbers are the result of arithmetic overflow or of illegal operations. The floating point numbers have a precision of around 7 decimal digits. The following number ranges can be represented: - -3*10**38 .. -3*10**-39 - 0 - +3*10**-39 and 3*10**38 The floating point format is similar to, but not identical to the IEEE-754 single precision binary floating point format. It does not have subnormal numbers and no NaN as a value different from infinity. Also the exponent offset is different (127 for IEEE-754, 128 for the Forth format). 7.3 WORDS FROM FLOATING. The stack notation is the same as in previous chapters, but fp is added to represent a 32-bit floating point number. Words only used internally (such as assembler labels) are not listed here. 1& --- fp The constant 1. 10& --- fp The constant 10. 2CONSTANT 32b --- (runtime) --- 32b See chapter 6 D->F d --- fp converts a double precision integer d to a floating point number with the same value (which cannot be represented exactly for large integers). F* fp1 fp2 --- fp3 floating point multiplication F+ fp1 fp2 --- fp3 floating point addition, F- fp1 fp2 --- fp3 subtracts fp2 from fp1 F->D fp --- d converts the floating point number f to a double precision integer, rounding toward zero. F. fp --- prints fp in scientific notation using the & symbol as "ten to the power" symbol. F.R fp n1 n2 --- prints fp right-justified in a field of at least n1 characters wide, inserting spaces to the left if required. n2 digits are printed after the decimal point. Any number base can be used. F/ fp1 fp2 --- fp3 divides fp1 by fp2 F0< fp --- f f true if fp is less than zero, otherwise false. F0= fp --- f f true if fp is equal to zero, otherwise false. F2* fp1 --- fp2 multiplies fp1 by 2. F2/ fp1 --- fp2 divides fp1 by 2. F< fp1 fp2 --- f f is true if fp1 is less than fp2, false otherwise. F= fp1 fp2 --- f f is true if fp1 is equal to fp2, false otherwise. F> fp1 fp2 --- f f is true if fp1 is greater than fp2, false otherwise. FABS fp1 --- fp2 fp2 is the absolute value of fp1. FERRNUM f --- The extension of NUMBER for floating point input. The 'ERRNUM variable points to this. Parses the number as a floating point number and converts to a floating point value if f is true. If conversion fails, an error is reported. FI** fp1 n --- fp2 Raises fp1 to the power n. n is an integer. FLOAT --- Activates the floating point extension to NUMBER. Must be executed after a cold start. FNEGATE fp1 --- fp2 subtracts fp1 from 0. FSQRT fp1 --- fp2 computes the square root of fp1. NAN --- fp Constant, infinite value. X! fp1 8b --- fp2 Replaces the exponent byte of fp1 with 8b. X@ fp --- fp 8b 8b is the exponent byte from fp. 7.4 WORDS FROM TRANSCEN. 180/PI --- fp constant, 180 divided by pi. 2, 32b --- appends 32b to the end of the dictionary, 32-bit version of , 2VARIABLE --- (runtime) --- addr See chapter 6. ATN2 --- fp Computes the angle in radians relative to the X-axis, represented by the vector contained in the FX and FY variables. ATNTAB --- addr addr is the start address of a table containing the arc-tangents of negative powers of 2. Used internally by trigonometric computations. ATNTAB@ u --- fp fp is the arc-tangent of 2 to the power of -u. DEG fp1 --- fp2 converts angle fp1 in radians to angle fp2 in degrees. F** fp1 fp2 --- fp3 raises fp1 to the power fp2. fp1 must be nonnegative. Raising to an integer power with FI** can have a negative first argument. F*2** fp1 n --- fp2 divides fp1 by 2 to the power of n. F10** fp1 --- fp2 computes 10 to the power of fp1. FARCCOS fp1 --- fp2 computes the arc-cosine of fp1 in radians. FARCSIN fp1 --- fp2 computes the arc-sine of fp1 in radians. FARCTAN fp1 --- fp2 computes the arc-tangent of fp1 in radians. FCOS fp1 --- fp2 fp1 is in radians, computes the cosine. FEXP fp1 --- fp2 raises e to the power of fp1. FLN fp1 --- fp2 computes the natural logarithm of fp1. FLOG fp1 --- fp2 computes the base 10 logarithm of fp1. FSIN fp1 --- fp2 fp1 is in radians, computes the sine. FTAN fp1 --- fp2 fp1 is in radians, computes the tangent. FX --- addr FY --- addr Two floating point variables containing a vector used by trigonometric computations. LN10 --- fp constant, the natural logarithm of 10. LN2 --- fp constant, the natural logarithm of 2. LNTAB --- addr addr is the start address of a table containing the natural logarithms of 1+2**-i. Used by logarithmic computation.s LNTAB@ u --- fp fp is the natural logarithm of 1+2**-u PI --- fp constant, the number pi. PI/180 --- fp constant, pi divided by 180. RAD fp1 --- fp2 converts angle fp1 in degrees to angled fp2 in radians. Required if you want to compute sine, cosine or tangent of angles expressed in degrees. Example: To compute the sine of 45 degrees, type: 45& RAD FSIN F. TAN2 fp --- fp is positive. Creates a vector in the variables FX and FY that has an angle of fp radians relative to the x-axis. 8 GRAPHICS AND SOUND 8.1 GRAPHICS ROUTINES. The file GRAPHIC (2 blocks in size) contains routines to draw points and lines on the screen and further it contains commands to set colors. GRAPHIC depends on the assembler and therefore the assembler has to be loaded first, the same way as described in chapter 6 for DOUBLE. PLOT n1 n2 --- Plots a single point with x-coordinate n1 and y-coordinate n2. Coordinates are the same as in BASIC. DRAW n1 n2 --- Draws a line from the current plot position, n1 points to the right and n2 points upward. If n1 or n2 are negative, the directions are to the left and downward respectively. POINT n1 n2 --- f Reads the value 0 or 1 of the point on the screen with x-coordinate n1 and y-coordinate n2. ATTR n1 n2 --- c returns the value of the attribute byte at line n1 and column n2. HARDCOPY --- prints a copy of the screen to the ZX-Printer. SCREENLOAD --- Reads a file name from the input stream and reads the file with that name into the screen buffer. SCREENSAVE --- Reads a file name from the input stream and writes the contents of the screen buffer to a file with that name. INK n --- PAPER n --- BORDER n --- BRIGHT n --- FLASH n --- work the same way as the BASIC commands with the same name. INV n --- works the same way as the basic command INVERSE. GOVER n --- works the same way as the BASIC command OVER. NORMAL --- Resets the colors and related settings back to normal. COLOR c --- (runtime) n --- Defining word used to implement commands like IN and PAPER. 8.2 USER DEFINED GRAPHICS. The file UED (4 blocks in size) can be loaded with RUN UED after GRAPHIC has been loaded. It contains an editor for User Defined Graphics. Codes of User Defined Graphics can be entered in FORTH with ^ (up-arrow) directly followed by a letter. ^A represents the graphic A. For example: type ^A EMIT to print the character GRAPHICS-A. CHADDR n --- addr returns the address of the character bitmap of the character with code n. UED --- Starts the UDG editor. The UDG Editor shows an 8x8 field in which you can edit a graphic character. Within the UDG editor the keys have the following functions: - Cursor keys: move the graphic cursor in the 8x8 field defining the bit pattern. - ENTER go to the start of the next line in the 8x8 field. - @ Reads a character into the 8x8 field. By typing a letter, the corresponding UDG will be read. By typing an & followed by any other character, this character will be read. - ! Writes the bit pattern in the 8x8 field into memory where the graphic representation of a character. Selecting the character can be done as described for the @ command. Normally only UDG's can be written back to memory as ASCII characters are stored in ROM, but the system variable CHARS (at 23606) can be modified, so these characters are stored in RAM as well. - A do a bitwise AND on the bit pattern in the 8x8 field with any other selected character. - O do a bitwise OR on the bit pattern in the 8x8 field with any other selected character. - E do a bitwise Exclusive-OR on the bit pattern in the 8x8 field with any other selected character. - X Mirror the bit pattern in the 8x8 field in the X-axis. - Y Mirror the bit pattern in the 8x8 field in the Y-axis. - R Rotate the bit pattern in the 8x8 field 90 degrees counterclockwise. - I Inverts the bit pattern in the 8x8 field. - SPACE inverts the pixel at the location of the cursor. - EDIT (CAPS-SHIFT-1) exits the graphics editor. 8.3 SOUND. The file MUSIC (two blocks in size) contains routines to make sound, both for the Spectrum 48 and for the Spectrum 128. MUSIC depends on the assembler and therefore the assembler has to be loaded first, the same way as described in chapter 6 for DOUBLE. BEEPER u1 u2 --- produces a tone consisting of u1 pulses of u2 machine cycles each on the beeper of the Spectrum 48. TONE u1 u2 --- produces a tone with a duration of u1 milliseconds and a frequency of u2 Hz on the beeper of the Spectrum 48. MS n1 --- Delay of n1 milliseconds. The following commands only work on the Spectrum 128. SOUND 8b n --- Writes 8b to register n of the sound chip. FREQ u n --- Sets channel n (n=0, 1 or 2) to a frequency of u Hz. VOL u n --- Sets channel n (n=0, 1 or 2) to volume u (u in range 0..15). TONES --- Enables the three tone channels. NOISE --- Enables the three noise channels. With n 6 SOUND one can change the noise frequency. SHUTUP --- Disables all sound channels. The following commands work on the internal beeper or on the sound chip, depending on the Spectrum model. Note: on the Spectrum 128 you have to type 15 1 VOL before hearing the musical notes. NOOT u --- (runtime) --- defining word to define a note with frequency u. A A# B C C# D D# E F F# G G# Play the corresponding note. L u --- Sets the length of following notes to u beats. O+ O- --- Move an octave up or down. >> << --- increases or decreases the volume. R --- Rest (silence) for one beat. TEMP --- addr variable indicating the tempo, milliseconds per beat. OCT --- addr variable containing the octave (as a factor with which the not frequency) is multiplied. VOLUME --- addr variable containing the volume. LEN --- addr variable containing the number of beats per note. 9 THE DECOMPILER The file UTIL (2 blocks in size) contains the utility to decode the internal structure of a fFORTH word, a decompiler. SEE name shows the following information about a FORTH word: - the compilation address. - IMMEDIATE if the word is immediate. - The word type: CONSTANT, USER, : CREATE or CODEWORD or the name of the defining word if the word was created with CREATE..DOES> (e.g. VOCABULARY). - The contents of the word: - the value for constants and user variables. - for colon definitions, a list of words from which the colon definition is compiled. This is not the same as the source code as runtime parts of immediate words are shown (e.g. LIT and BRANCH). Parameters of well-known words are shown: the value following LIT, the branch offset for BRANCH, the string following (.") etc. - a Hex and ASCII dump for any other words. - The size in bytes of the parameter field. This length is incorrect if the next word in the CONTEXT vocabulary does not immediately follow this word in the dictionary. Decompilation of a colon definition stops when EXIT is encountered. Words that contain EXIT halfway are not decompiled completely. Newly defined words that are compiled with operands in the colon definition (like LIT and BRANCH) are not handled by the decompiler. The decompiler will have to be extended to handle any such words. Besides SEE, the following words are relevant: (SEE) addr --- Performs the action of SEE on the word with compilation address addr. HIGH-NFA addr1 --- addr2 addr2 is the name field address sin the CONTEXT vocabulary closest above addr1. #WORDS --- u returns the number of words in the CONTEXT vocabulary and any vocabularies linked to it. DOCON --- addr DOCOL --- addr DOUSER --- addr constants that return the runtime addresses of CONSTANT, : and USER. NEXT --- addr Address of the inner interpreter. Is used in the code field of variables. 10 MULTI-TASKING. 10.1 INTRODUCTION. Multi-tasking is the execution of multiple programs, seemingly at the same time. Every program has its own stack and user variables and the system switches between the various programs very fast. Applications: - control of hardware devices, for instance model trains. - video games. - polyphonic music. (128k) - background music.(128k) - continuing to work with FORTH while a background task is running for a long time. The file TASKS (size 3 blocks) contains all basic routines necessary for multi-tasking. It depends on the assembler and therefore the assembler has to be loaded first, the same way as described in chapter 6 for DOUBLE. 10.2 CREATING A TASK. Creating a task is illustrated by means of an example. We create a task that prints a number in the upper left corner of the screen. The number keeps incrementing. First we define a word that returns the current cursor position, so AT can restore it later. : UNAT 24 23689 C@ - 33 23688 C@ - DUP 32 = IF DROP 1+ 0 THEN ; Next we define the task, like a colon definition, but with TASK: and ;TASK TASK: TASK1 DECIMAL 0 BEGIN UNAT 0 0 AT 2 PICK 6 .R AT 1+ DUP 0= SWITCH UNTIL ;TASK Next start the multi-tasking system with STARTUP. The task is started by typing its name: TASK1. You should now see a number rapidly incrementing in the upper left corner of the screen, while you can continue to work with the Forth system. When you type ' TASK1 STOP, the task stops and the number on the screen stops incrementing. When you type ' TASK1 START, the task continues to run and the incrementing resumes. When you type TASK1, the task starts over from the start and you see the number incrementing from zero again. When you wait long enough and the task has printed all positive and negative 16-bit integers, the task will stop automatically. If an error message occurs in the normal FORTH task, the multi-tasking system will stop. Restart it again with STARTUP. 10.3 INTERNALS. Every time you create a new task, a new task area is created, which consists of the following parts: - 2 bytes, start address of the FORTH code. - 2 bytes, address of the previously defined task. The first task points to the last task, so all tasks are on a circularly linked list. - 1 byte, status byte, which can have the following values: 0 is runnable (active). 1 is new 2 is finished 3 is sleeping 4 is stopped. The task will only run when the status byte is 0. - 2 bytes, the task's stack pointer. - 60 bytes, user variables. - 256 bytes, return stack - 256 bytes, data stack. The fORTH code of the task will follow this task area. On the Spectrum 128 all task areas have to be located below address 49152. Task switching is performed with the word SWITCH. This word saves the return stack pointer and instruction pointer on the task's stack and saves the stack pointer on its location in the current task area. Via the circularly linked list, the next runnable task is selected. The stack pointer is loaded from the newly selected task area, the instruction pointer and return stack pointer are loaded from the stack and the new task will continue. This way all tasks are executed round-robin. The user area pointer points to the current task's user area. To make multi-tasking work, all tasks have to contain the word SWITCH in their loops, so this word will be executed frequently. Otherwise one task may execute for a long time without giving other tasks time to run. If a task never executes the word SWITCH, it will run indefinitely without giving other tasks time to run. The words KEY and PAUSE both execute switch while they wait. They execute the word whose address is contained in the (WAIT) variable. 10.4 WORDS. SWITCH --- Switches to the next runnable task. UPTR --- addr Constant, the address of the user area pointer. TASK-LINK --- addr Variable containing the address of the last created task area. FIRST-TASK --- addr Variable containing the address of the first created task area. TASK: --- Defining word to create a new task. Puts FORTH in the compilation state. At runtime the task will be started from the beginning. ;TASK --- Finishes the definition of a new task. Puts FORTH in the interpretation state. TERMINATE --- Finishes the current task. The task cannot be restarted with START. SLEEP --- Puts the current task in the sleep state. START can wake up the task to make it runnable again. STOP addr --- addr is the code field address of a task. The task is stopped and can be resumed with START. START addr --- addr is the code field address of a task. The task is resumed from the point where it was stopped. MTSK --- The task containing the FORTH interpreter. The > prompt is shown when the multi-tasking system is active. (START) --- Code word used in STARTUP. STARTUP --- Starts the multi-tasking system TLIST --- Shows a list of all tasks with their status. The status ACTIVE is shown for the task calling TLIST, all other runnable tasks are listed as RUNNBALE. 11 STRINGS AND OTHER DATA TYPES. 11.1 INTRODUCTION The file OBJECT The file OBJECT (6 screens in size) contains everything that is required to work with strings and several words to create structured data types, such as arrays. Many of the operations described in this chapter can be carried out at a lower level, which is faster. These routines are designed for ease of use, mot for speed. 11.2 THE STRING STACK Strings are arrays of ASCII characters of a variable length. They are stored in a separate stack. Every string consists of a length byte (0 to 255) followed by as many characters as specified by the length. Strings are stored contiguously in the string stack, the last byte of one string is immediately followed by the length byte of the next string. The top element of the string stack is at the lowest memory address. The string stack pointer points to the length byte of the top element. By adding the length byte plus 1 to this address, you get the address of the second element on the stack. The string stack (which grows towards lower memory addresses) is located at the top of free RAM, immediately below the address contained in the LO variable. The string stack pointer is contained in the variable $SP. The string stack supports the operations $DUP, $SWAP, $OVER and $DROP. Strings can be stored in memory using $! and $!LIM and they can be read from memory into the string stack using $@. The string stack supports the same string operations as most BASIC dialects, like LEFT$, MID$, RIGHT$, STR$, VAL, ASC and CHR$. Further they can be compared with $=, $< and $>, they can be concatenated with $+ and they can be printed with $. String constants work the same way in the compiler and the interpreter. A string constant starts with the " character followed by a space, followed by the characters contained in the string. The string constant is terminated by another " character. Example: " ABCD" is a string of length four, containing the ASCII characters A, B, C and D. " This is a string" $. is equivalent to ." This is a string" in the compiler or .( This is a string) in the interpreter, but ." and .( do not use the string stack. Another example: 1234. STR$ 1 RIGHT$ $. This converts the double-precision number 1234 into the string " 1234", next the rightmost single character string " 4" is extracted with RIGHT$ and this single character string is printed. In most BASIC versions you can achieve this with: PRINT RIGHT$(STR$(1234),1). The same effect can be obtained in pure FORTH with 1234. <# # #> TYPE which is more efficient and does not require the string stack routines. 11.3 THE 'TO' CONCEPT. Standard variables in FORTH-83 always return their addresses and the user is responsible to use the correct fetch or store operation to read or write the value. The user has to choose the correct word @, C@, 2@, $@, !, C!, 2!, $! or $!LIM. Omitting the fetch operation to read the value of a variable is a frequently occurring source of errors. When using the TO concept, the variable itself takes care of reading or writing its value. A state variable tells the variable whether it should read or write this value. The default state is 'reading', but by using the word TO you switch the state to 'writing' for the next variable access. The variable resets the state variable to 'reading' after writing the value. Now it is possible to type: A B + C D + * TO E instead of: A @ B @ + C @ D @ + * E ! For double precision numbers, you can now type: A B D+ C D D+ D* TO E instead of: A 2@ B 2@ D+ C 2@ D 2@ D+ D* E 2! So with the TO concept, you only have to change the arithmetic operations, without it you have to change all fetch and store operations. This implementation maintains the state at runtime. This makes it machine independent, but not very efficient. In some other systems the state is maintained at compile time and the compiler inserts the fetch and store operations, so the state does not have to be checked and maintained at runtime. 11.4 STRUCTURED DATA TYPES When you want to create a variable using the TO-concept, you can type: VALUE is the name of the variable to be created. is one of the following: BOOL for Boolean variables (storage size 1 byte). CHAR for single characters (storage size 1 byte). INT for integers (storage size 2 bytes) LONG for double precision numbers (storage size 4 bytes) REAL for floating point numbers (storage size 4 bytes). n STR for strings, where n is a number indicating the maximum string length storage size is n+1 bytes, but at least 5. INT values support +TO in addition to TO to add a number to the variable. String values support >ELEM and ELEM> operations in addition to TO to read and write single characters in the string. Example: INT VALUE #ITEMS REAL VALUE SIZE 10 STR VALUE NAME creates three variables: one integer, one floating point and one string of 10 characters maximum. You can create a one-dimensional array with n ARRAY where n is the number of elements contained in the array. 3 LONG ARRAY VECTOR 9 5 STR ARRAY NAMES creates two arrays: one with 3 double precision numbers and one with 9 names of at most 5 characters each. You can set the value of the first element in VECTOR with 1234. TO 0 VECTOR. Note: if A is an integer variable (using the TO-concept), you have to type 1234. A TO VECTOR instead of 1234. TO A VECTOR because in the latter case you are changing the value of A instead of VECTOR. The array element is selected by an integer in the range 0 to n-1, where n is the number of elements in the array. You can create a two-dimensional array with: n1 n2 MATRIX You then get an array with n1 times n2 elements. You select one element with two integers, the first one in the range 0 to n1-1 and the second one in the range 0 to n2-1. Internally the element type is distinguished by the storage size. - Elements of size 1 are accessed with C! and C@ and are considered to be of type BOOL or CHAR. - Elements of size 2 are accessed with ! and @ and are considered to be of type INT - Elements of size 4 are accessed with 2! and 2@ and are considered to be of type LONG or REAL. - Elements of size 5 of higher are considered to be of type string and are accessed with $!LIM and $@. It is possible to build other structured data types using the CREATE..DOES> mechanism, like records containing elements of different types. queues, stacks, lists and so on. The new defining word has to store the element size somewhere in the data structure. The runtime part can use this to compute the address of the selected data element. The word (VAL) accesses any single data element, using the element address, the size byte and the state contained in the variable %TO. 11.5 THE WORDS We use the same stack notation as in chapter 3, but with the following additions: $ is a string on the string stack. " --- (compilation) --- $ (interpretation) Reads a string from the input stream until the next " and compiles it into a string literal when the text interpreter is in compilation state. In interpretation state it pushes the string onto the string stack. "" --- $ string constant, the empty string. $! $ addr --- Stores the string $ at address addr. $!LIM $ addr u --- Stores the string $ at address addr, with a maximum of u characters. The string will be truncated if it is longer. $+ $1 $2 --- $3 Concatenates the strings $1 and $2 $- $1 $2 --- n compares two strings in ASCII lexicographic order. n is negative if $1 is less than $2, n=0 if the two strings are equal and n is positive if $1 is greater than $2. $. $ --- prints the string $. $< $1 $2 --- f f is true is $1 is less than $2, false otherwise. $= $1 $2 --- f f is true is $1 is equal to $2, false otherwise. $> $1 $2 --- f f is true is $1 is greater than $2, false otherwise. $@ addr --- $ Reads the string $ from memory at address addr. $CONSTANT $ --- (runtime) --- $ defining word. Creates a new word that pushes the constant string on the string stack at runtime. $DROP $ --- removes a string from the stack. $DUP $ --- $ $ duplicates string on stack. $OVER $1 $2 --- $1 $2 $1 duplicates second string on stack. $S0 --- addr The highest address of the string stack. $SP --- addr variable containing the string stack pointer. $SP! --- resets the string stack pointer to the topmost address, thereby emptying the string stack. $SWAP $1 $2 --- $2 $1 swaps the top two string stack elements. %TO --- addr variable containing the state used by any TO-variable to select its action. 0 for fetch. 1 for store, set with TO. 2 for add to variable, set with +TO 4 for writing a single character of a string, set with >ELEM 5 for reading a single character of a string, set with ELEM> (") --- $ runtime part of " (CHAR) addr --- c %TO=0 c addr --- %TO=1 internal word to access values of type CHAR and BOOL. (INT) addr --- n %TO=0 n addr --- %To=1 of 2 internal word to access values of type INT. (LONG) addr --- d %TO=0 d addr --- %TO=1 internal word to access values of type LONG and REAL. (STR) addr u --- $ %TO=0 $ addr u --- %TO=1 c n addr u --- %TO=4 n addr u --- c %TO=5 internal word to access values of string type. addr is the address where the string is stored. u is the maximum string length. c is the single character read or written. n is the index in the string to read or write a single character. (VAL) addr u --- accesses a variable of one of the above-mentioned types addr is the address where the values is stored. u is the size in bytes, also indicating the type. +$ $1 $2 --- $3 concatenates strings in reverse order, $2 is followed by $1. +TO --- specifies that the next variable operation will be addition to value. -RIGHT$ $1 u --- $2 $2 is the rightmost part of $1, from the u'th character. >ELEM --- specifies that the next variable operation is a single character write. Example: if NAME is a string variable, &A 1 >ELEM NAME sets the first character to 'A'. ?RANGE u1 u2 --- reports an error if U1>u2 ARRAY u1 u2 --- creates an array of type u2 containing u1 elements. ASC $ --- c c is the first character of $ BOOL --- 1 indicates variable type. CHAR --- 1 indicates variable type. CHR$ c --- $ $ is a single-character string containing the character c. ELEM> --- specifies that the next variable operation is a single character read. Example: if NAME is a string variable, 3 ELEM> NAME returns the third character in the string. ER --- Reports the error "INVALID OP". GET$ --- $ Reads a string from the keyboard with EXPECT. INT --- 2 indicates variable type. LEFT$ $1 u ---$2 $2 is the leftmost part of $1, u characters in length. LEN $ --- u u is the length of string $ LONG --- 4 indicates variable type. MATRIX u1 u2 u3 --- creates a matrix of type u3 with u1 rows and u2 columns. MID$ $1 u1 u2 --- $2 $2 is the substring of $1 starting at the u1'th position and u2 character long. REAL --- 4 indicates variable type. RIGHT$ $1 u1 --- $2 $2 is the rightmost part of $1, u1 characters in size. RV --- Returns %TO to the read state. STR u1 --- u2 indicates variable type, string with u1 characters maximum. STR$ d --- $ converts double precision integer to string. TO --- specifies that the next variable access will be a store. VAL $ --- d converts string to double precision integer. VALUE u1 --- Creates variable (single value) of type u1. WORD$ --- $ Converts the next word of the input stream to a string. 11.6 ADAPTION TO MULTI-TASKING In order to make these routines suitable for multi-tasking, you must define the $SP and %TO variables as USER variables, except when one of the following conditions is true: - Only one task makes use of these routines. - Every SWITCH occurs when the string stack is empty and the %TO variable is in the read state. If you make $SP a user variable, you have to reserve a separate string stack area for each task. For each task the string stack pointer has to be initialized to the end of its string stack area. -- End of document --