Skip to content

Instantly share code, notes, and snippets.

@jaredkrinke
Created August 21, 2024 03:50
Show Gist options
  • Select an option

  • Save jaredkrinke/d8a801274a9a66bb06a0e2c8778624ca to your computer and use it in GitHub Desktop.

Select an option

Save jaredkrinke/d8a801274a9a66bb06a0e2c8778624ca to your computer and use it in GitHub Desktop.
ASCII dump of Blazin' Forth (Commodore 64) documentation, by Scott Ballantyne ("Distribution on a not for profit basis is encouraged.")
Introduction
This manual describes the special features of the Blazin' Forth compiler. It is not necessary to read the entire document before using the compiler.
I would strongly urge you to at least glance through the first part paying special attention to the sections describing MOUNT and the EDITOR.
Once this has been taken care of, you may proceed immediately to the portion you are most interested in. Blazin' Forth supports the sound chip, Turtle Graphics, and Sprites, as well as containing a complete string handling package. Please feel free to start with the sections which interest you the most.
Blazin' Forth Documentation,System Information
Description
Blazin' Forth is a complete Forth-83 system for the Commodore-64 computer. It includes all the words from the Required Word Set, the Double Number Extension Word Set, and the Assembler Extension Word set. It also includes almost all of the Controlled Reference Word set, and applicable words from the Uncontrolled Reference Word Set. The words omitted from the Controlled Word Set are --> , K , and Octal , all others are included. This system also contains its own versions of the System Extension Word Set, which include various compiler security features.
In short, this is a complete implementation of the Forth-83 standard, suitable for developing programs which may be ported to other systems, or running programs developed on other systems.
There are also various extensions to the standard system, which include additional boolean operators, words like ?KEY , ?DO, and ?LEAVE , which are very useful, but not yet part of the standard Forth language.
When I first started writing this compiler, I had two main goals in mind. The first was to have a fast Forth-83 compiler on the C64. The other was to provide access to all of the nifty hardware features of this computer. Blazin' Forth includes words which make it simple to access the sound chip, and graphics chips of the 64. In particular, it includes a Turtle Graphics extension, which is by far the fastest implementation on the C64 I have ever seen.
This document describes all of the features specific to Blazin' Forth. For information on Forth, I would suggest you obtain a copy of the Forth-83 standard, which is available from the Forth Interest Group (FIG) for a few dollars. If you are new to Forth, the book Starting Forth, by Leo Brodie, contains a very complete tutorial on the Forth Language. There is a file available, Start Blazin' Forth, which is intended to help you over the hurdles caused by the differences between the Starting Forth dialect of Forth (which is an earlier implementation of Forth) and Forth-83. Note that there are not many differences between the two.
This system, its documentation and source code are Copyright (C) 1985 by Scott Ballantyne. Free distribution of this compiler is encouraged, distribution for profit is not allowed. (Users Groups and SYSOP's of bulletin boards may charge a small fee to cover their operating expenses. If in doubt, please ask.) Note that this system is not public domain, nor is it freeware. I don't want your money. If you like this system, why not contribute some software of your own? On the other hand, if you don't like it, why don't you contribute some software of your own? We all badly need good software.
Blazin' Forth has been very carefully tested by a group of several people, some old Forthers, and some new ones. I believe this system is free of bugs, but suggestions for improvement are welcome, as are bug reports, should any new ones be discovered. A version for the C128 is planned which will include more features, such as Multi-tasking, a hashed dictionary structure (for super fast compiles) and other stuff. Dynamic vocabulary chaining is also being examined. Comments on any of these topics, or any other suggestions for improvements are very welcome. If you do communicate with me, please include the version number of your Forth system, (obtainable by typing .VERSION). I may be reached through Electronic mail:
Compuserve: 70066,603
Sourcemail: BDE712
Enjoy - and may the Forth be with you!
SDB
System Configuration, SAVE-FORTH
Forth differs from other languages in many ways - one of the nicer ones is that it gives you a choice of the final configuration of your own system. When you first run Blazin' Forth, everything will be present in the dictionary - the string extensions, the Turtle Graphics words, the Sound Words, all utilities, everything. Since source code is provided for all system extensions starting with THRU , you may FORGET up to that point, and selectively compile the words you need. It is even possible to have many different Forth versions available. You may choose to compile only the editor, and the sound extensions, for example, to give more room for music applications. You may then save the new system by using the word SAVE-FORTH . You will be prompted for a filename, and the new system will then be saved to disk. Note that the modules are relatively independent (the sound extensions, string extensions, and graphics extensions are completely independent), but all require that the assembler be loaded, and may also use a few, but by no means all, of the Utility words.
If you FORGET THRU , you will have 41167 bytes of available memory, which you can use in any way you wish. (Warning: You will also remove the Assembler, and the Editor from memory if you do this!) This is a *lot* of memory, since Forth code is extremely compact.
Note that this also gives you the freedom to define your own system extensions - if you want more or different utilities, you can add them. If you don't like the way the string functions work, you can change them. There is no need to work around a system word that doesn't do exactly what you want - you can customize this Forth to your liking. ( Warning: Do *not* save a new Forth system to a disk which contains Forth source screens. See the EDITOR section for more info.)
CONFIGURE
Using CONFIGURE, you may alter the actual memory configuration of the system. At power up, Blazin' Forth has 4 virtual disk buffers, located from $C000 to $D000 . Should you want more or less ( adding buffers subtracts from the dictionary space, and cuts down on disk accesses, removing buffers increases dictionary space) you can do this as by using CONFIGURE .
Example: Configure to use 1 disk buffer:
1 IS #BUF ( tell Forth how many buffers)
CONFIGURE ( reconfigure)
You can also lower the top of memory. This could be useful to reserve memory for an RS-232 buffer, sprite data, or machine language programs.
Example: Lower top of memory to $C000
HEX ( use Hexidecimal base)
$C000 IS LIMIT ( tell Forth how high it can go)
CONFIGURE ( reconfigure the system)
STARTUP
You can have FORTH come up running a resident application. STARTUP is a system variable that holds the code field address of the first word to execute when first run, on an error, or on a RESTART. Your word must take over the functions of the FORTH word QUIT . For example, to have FORTH run a word processing application when first run, we define a word FOO , that will execute this program:
: FOO BLK OFF SP! BEGIN RP! ( take on functions of QUIT)
PROCESS-WORDS ( execute the program ) AGAIN ; ( forever)
Now that this is done, we change STARTUP :
' FOO STARTUP ! ( store code-field of FOO in STARTUP )
And then save this as a new system file. Warning: Be careful not to cause an error at this point, or the system will dump you into your word processing program!
MISC:
This Forth ignores case, unless you are entering literal strings between quotes. So, for example, whether you type EMIT emit Emit or EmIt , the result will be the same - EMIT will be executed. This allows you to program in a mixture of upper and lower case.
Hitting RUN/STOP RESTORE will cause the FORTH system to warmstart. Default screen colors will be restored, the stack will be cleared, and decimal will become the current numeric base. Channels to the disk are unaffected, as are user programs and source screens in memory.
Typing RESTART will cause a cold start of the Forth system. This will return the system to the same configuration it had when first powered up.
Typing BYE will exit FORTH, and return to BASIC.
THRU is the best way to compile a sequential range of screens. It is far better than filling a screen with a series of LOAD commands. Example: 5 25 THRU will load screens from 5 to 25 inclusive.
EDITOR
The EDITOR included with the system is the same as the one included in the book STARTING FORTH. All the editor commands described there are included in Blazin' Forth.
VERY IMPORTANT !!
Before accessing the disk drive, you must first type MOUNT, to initialize Forth's virtual memory system, and the disk drive. (Make sure a disk is in the drive before using this command.) This command must be used before LIST , INDEX , TRIAD , LOAD or THRU . MOUNT should also be used after changing disks. You only need to execute MOUNT once, at the start of an editing session, or before compiling (unless you RESTART , or change disks), but you can't get to the disk without it.
Forth uses random access files. Random access files don't show up in the directory. A disk can be filled with Forth screens and a directory listing will still show an empty disk. Therefore, it is very important not to mix Forth source screens with standard CBM files. (Note: You can use standard CBM file types from forth - you just shouldn't mix them with your source code!)
The disk drive light will remain lit while the system is enabled. This is normal, and should not cause you any concern.
The word DCLOSE will close all of the channels to the disk drive, and turn off the light on the drive.
PROGRAM DOCUMENTATION
The following words are included to aid in program documentation:
xx LIST Lists screen# XX to the current output device. Enters the Editor.
xx TRIAD Lists the three screens including XX as a member to the current output device. The screens are formatted to fit on one normal sized sheet of printer paper. Example: 7 TRIAD will list screens 6, 7, and 8 to the screen. Includes PAUSE.
XX YY TRIADS list all triads from XX to YY . Note that this word assumes your printer has automatic paging. Includes PAUSE.
XX YY INDEX Lists all first lines of all the screens starting with XX through and including YY. If you follow the FORTH editing convention of starting each screen with a comment, you can use this word to quickly locate words. Includes pause.
WORDS Lists all the words in the current search order. Note that WORDS includes PAUSE. See PAUSE for more information.Same as the older VLIST.
PROGRAM DEVELOPMENT AIDS
LOCATE
Forth Screens are great, allowing you to break up your programs into small modules, and even form interchangable libraries. One drawback, particularly in debugging, is the need to remember what screen number a particular word is located on. LOCATE removes this problem. For example, if you have a word, FOOBAR, which is in an application which you are currently testing, all you have to do is type LOCATE FOOBAR - and the system will list the screen which includes FOOBAR on the screen, and enter the editor. LOCATE will even access the disk if necessary! This is an extremely handy word. Try it, you'll soon wonder how you got along without it. Note that words in the pre-compiled portion cannot be LOCATEd.
DEBUGGING
Blazin' Forth contains several aids to assist in debugging and developing programs. Perhaps the most powerful word is TRACE. TRACE allows you to single-step through a Forth word, and observe it's execution step by step. For example, to trace a word called XYZZY , all you have to do is type TRACE XYZZY . Nothing will appear to happen, but the next time XYZZY is executed, Forth will enter single step mode. The word about to be executed will be displayed, along with the current stack contents. You have two options here. You can hit the RUN/STOP key, which will halt the program, and turn off the trace, or you can type CONTROL-P, which will enter PAUSE mode. Forth will display a P? prompt, and wait for you to do something. At this point, you can do anything you want. Alter the stack contents, LIST a screen, define a new word. You can also decide to trace a new word. This can be useful in tracing through levels of execution. To continue where you left off, type CONT . When the word has finished executing, normal program execution will resume. Once tracing has been enabled, there are only two ways to turn it off. Hitting the run/stop key during a trace, as described above. Or typing NOTRACE, either during a pause, or after the program has executed. Note: If you cause an error while in PAUSE mode, you will be dumped out of the current trace, but the trace is still enabled.
WATCH
Another kind of debugging aid, very useful in some situations, is WATCH. WATCH will keep an eye on a memory location, and halt program execution when it changes, printing out all the words executed in that line of execution. This can be handy when trying to locate a word that is altering the value of a variable, or even destroying the dictionary! For example, if you have a variable PROBLEM.VAR , which is getting changed to a bad value at some point, and you can't find out where, first initialize the variable to the value you want it too have, and then type: PROBLEM.VAR WATCH . When the value of PROBLEM.VAR changes from what it was when you executed WATCH, the system will stop program execution, and execute UNRAVEL , which displays the higher level nesting of Forth words which occurred up to that point.
UNRAVEL
Essentially, UNRAVEL displays all words executed by Forth up to the point that UNRAVEL was executed. It does this by decoding the contents of the return stack. (For more information on this, see Starting Forth.) This allows you to see what words preceeded a given words execution. For example:
: FOO BAR ;
: BAR FOO2 ;
: FOO2 FOO3 ;
: FOO3 UNRAVEL ;
When FOO is executed, it will ultimately arrive at FOO3 , which will execute UNRAVEL. This will cause the following display at your terminal:
FOO FOO3 FOO2 BAR 7106 INTERPRET RUN
(the words 7106 etc, are system words which are responsible for executing your input. They will always appear, and can just be ignored.)
This can be handy in determining what words lie in a certain execution path. Note that if you store values on the return stack, or if you use a DO LOOP, the return stack will contain stuff that is not relevant. UNRAVEL will just print these out as numbers, but this in itself, can be a very useful clue to debugging an application.
?PARAMS
This word will give an UNRAVEL trace if there are not enough parameters on the return stack. One use of ?PARAMS is to add run time error checking to the system. No run time error checking is performed by Forth. This allows applications to run faster, but can make life harder when you are developing an application. Forth does allow you to add this, if you want. When the program is finished, you can remove it, and the application will then run at full speed. For example, you can redefine the following:
: DROP 1 ?PARAMS DROP ;
: 2DROP 2 ?PARAMS 2DROP ;
etc.
DROP and 2DROP will now check the stack for the proper number of arguments, and give an error message, and an UNRAVEL dump if there are not enough arguments. One very nice thing about this way of doing things is that you don't have to re-edit your application once it's finished. You can redefine all of FORTH's stack operators in this way, load them in, and then load your application. When it's debugged, simply loading your application without loading the redefined stack operators will remove the run-time error checking, and allow your application to run at full speed.
This is only one example - such runtime error checks can be very useful, and are limited only by your imagination and debugging skills.
.S
.S is a common and highly useful word. It non-destructively prints out the contents of the stack, or the message STACK EMPTY, if there is nothing on the stack. Note that this version of .S displays unsigned numbers, so, for example, a value of -1 will display as 65535.
DUMP
DUMP takes two arguments, a starting address, and a count. It will then dump the contents of the memory locations to the current output device (in hex) and also the symbolic ASCII contents of those addresses. A period will be displayed if there is no corresponding ASCII character. This can be extremely useful for monitoring memory arrays, or simply trying too figure out why a CODE defintion doesn't work. Note that DUMP includes PAUSE. See PAUSE for more information.
EXAMPLE:
2000 64 DUMP will display 64 memory locations starting with 2000.
DEFER and IS
These two words form a remarkably useful combination. DEFER is similar to the FORWARD declarations of other compilers. Ordinarily, you must compile a word before using it in another definition. DEFER lets you out of this obligation. EXAMPLE:
DEFER FOO ( postpone working on FOO)
: BAR FOO ;
Later in the application, you may define:
: (FOO) ." This is foo. " ;
At this point, FOO will simply give an error message, "UNSPECIFIED VECTOR". You must tell FOO what to do, like this:
' (FOO) IS FOO // make FOO do (FOO)
Note the tick (') - it's important! Now, typing FOO will result in the execution of (FOO).
Note that if you dislike the way a DEFERed word is performing, you can redefine it without recompiling the entire application. For example:
: NEWFOO ." This is new foo." ;
' NEWFOO IS FOO
FOO will now execute NEWFOO, instead of (FOO). This allows you the incredible freedom of altering an already compiled word's behaviour - this is, so far as I know, unique to Forth!
Note that the usefulness of DEFER IS isn't limited to development. IS may be used inside a definition, and it's a convenient way to handle vectored execution. (See Starting Forth for more on vectored execution.) For example:
DEFER MESSAGE
: HI ." Hi there." ;
: BYE ." See you later " ;
: SOLONG ['] BYE IS MESSAGE ;
: HELLO ['] HI IS MESSAGE ;
Executing SOLONG will cause MESSAGE to print "See you later" while executing HELLO will cause MESSAGE to print "Hi there". Note that when used inside a colon definition, you need to use ['] instead of ' .
{$00}Blazin' Forth Documentation, System Information
Input and Output
Blazin' Forth supports all the necessary words to handle any peripheral device on the serial bus, or on the IEEE bus, if you have an extension card like the BUSCARD II. I left certain definitions out deliberately, so that users who require them can define them as they like.
Higher Level Words.
The system supports I/O redirection of output to a printer. The words PRINTER and NOPRINTER will open a channel to a 1526 printer, connected as device # 4. After executing PRINTER , all output will be directed to the printer. NOPRINTER will redirect output to the screen. Both PRINTER and NOPRINTER are DEFERed (see DEFER for more information), to make it easy for you to alter them, if you need to, without having to recompile the system. #LP is a constant which contains the current device number of the systems line printer. As currently configured, PRINTER sends a control code to the printer which turns on paging, and NOPRINTER sends a control code which turns paging off (these codes are for the 1526 printer). If your printer uses different control codes, or doesn't require them, you will probably want to revector these words. I would suggest you examine the definitions of LP and NLP for an example of how to write such words in Forth. In particular, it is important to have PRINTER set the user variable PRINTING? to TRUE, and NOPRINTER should reset PRINTING? to false.
The word DOS" has been provided to allow you to send commands to the disk drive from Forth. Any command which is recognized by the 1541 or a compatible drive may be sent using this word. As an example:
DOS" N0:DISKNAME,ID"
will format a disk in drive 0. Note that there must be a space between the " and the command.
To read the disks error channel, you can use the word ?DISC . Note that ?DISC will only display the message if there is an error.
MOUNT
MOUNT initializes the virtual disk operating system of Forth. It *must* be used before any word which accesses the disk. Note that you only need to use this word once, at the start of a session, and not before every LIST or LOAD command. (You must reissue it if you use RESTART, or if you change disks during a session.) Note that the disk light will remain on while the system is in operation. This is normal, and should not cause you a second thought.
If you require information on accessing standard CBM disk files, please see the appendix. These are the only words required in normal usage of the system.
Extensions to the 83 Standard
BOOLEANS:
Blazin' Forth contains the following boolean tests which are not part of the 83 standard:
0<> Leaves a true flag if top of stack is non-zero.
0> Leaves a true flag if top of stack is positive.
<> Leaves a true flag if top of stack is not equal to second element of stack.
D> Leaves a true flag if the second double number is greater than the doublenumber on the top of the stack.
CONDITIONAL EXECUTION
BEGIN...AGAIN is included. It is the same as
BEGIN .... FALSE UNTIL
Or, in other words, an infinite loop.
Multiple WHILEs are supported by Blazin' Forth. For example:
BEGIN <action> <condition> WHILE <action> <condition> WHILE <action> REPEAT
Note that you are not limited to only two whiles, this is simply an example.
LOOPING
Forth-83 loops are very fast, make the older /LOOP unnecessary, and are generally much improved over the earlier Forth Loops. However, when the loop index is equal to the loop limit, the loop will not execute once, but 64k times. For occasions when this is not wanted, Blazin' Forth provides ?DO . ?DO will not allow the loop to be executed if the limit and index are equal. For example:
: LOOP1 ?DO I . LOOP ;
Does the same thing as:
: LOOP2 2DUP <> IF DO I . LOOP ELSE 2DROP THEN ;
Note that the first is much shorter, and also much faster. Of course, ?DO will also work with +LOOP, just like DO does.
Forth-83's LEAVE is also changed from earlier LEAVEs. Forth-83's LEAVE jumps immediately to the word after LOOP or +LOOP, while the older LEAVE would continue through the remainder of the loop, exiting at the LOOP or +LOOP. Blazin' Forth contains a word, ?LEAVE which can be quicker and a real code saver when all you need to do is scram. For example:
: EXIT1 10 0 DO I . 5 = ?LEAVE LOOP ;
Will cause the same effect as:
: EXIT2 10 0 DO I . 5 = IF LEAVE THEN LOOP ;
Note that ?LEAVE takes less room, and is also quicker, than the second alternative.
Interactive Extensions
?KEY ( -- char )
This word works just like the standard KEY , but while KEY will wait for a keypress, ?KEY reads the keyboard, and returns the ASCII value of the key pressed, or a null (0) if no key was pressed.
PAUSE ( -- flag)
This word is basically a souped up ?TERMINAL. ?TERMINAL will return a TRUE flag if the stop key is pressed, and false otherwise. PAUSE will do the same thing, but, additionally, if any key other than the stop key has been pressed, it will halt execution and wait for the next key. Note that although PAUSE handles the waiting, it is up to the calling word to process the flag.
JOYSTICK ( port# -- direction )
Requires a value on the stack, which should be either 0 or 1. If 0, joystick port 1 will be read, If 1, joystick port 2 will be read. Joystick leaves a value on the top of the stack which indicates the direction of the joystick. -1 means the stick is centered, 0 is up, with values increasing to the right, so 1 is forward and up, 2 is to the right, etc. Note that if these values are multiplied by 45 the result is suitable for SETH (see graphics extensions).
JOYBUTTON ( port# -- flag )
Leaves true on the stack if the joy stick button at the appropriate port is depressed, otherwise leaves false. Requires the same values on the stack as JOYSTICK.
PADDLE ( paddle# -- value )
Requires a number from 0 to 3 on the stack which specifies the paddle. Leaves a value between 0 and 255 on the stack, depending on the setting of the appropriate paddle. Note that these words may also be used to read other devices, such as a Koala Pad.
PADDLEBUTTON ( paddle# -- flag)
Leaves a true or a false on the stack, depending on whether or not the addressed button has been depressed. Requires the same values as PADDLE.
JIFFY! ( doublenumber -- )
Used to set the 64's Jiffy Clock. Requires a double number on the stack.
JIFFY@ ( -- doublenumber )
Used to read the 64's Jiffy Clock. Leaves a double number on the stack. Note that 1 Jiffy = 1/60 of a second.
JIFFIES ( n -- )
Requires a single number on the stack. JIFFIES will cause a wait of n jiffies before continuing.
CURSOR ( x y -- )
Positions the cursor at the x y coordinates on the top of the stack.
CURSORPOS ( -- x y )
Leaves the current position of the cursor ( x and y ) on the top of the stack.
CONSTANTS
Blazin' Forth has precompiled the following constants.
0 1 2 3
These simply leave their value on the stack.
TRUE
Leaves the value for TRUE (-1) on the stack.
FALSE
Leaves the value for FALSE (0) on the stack.
VARIABLES
Blazin' Forth has the following variables which may be of use to you:
#LINE
A user variable which contains the number of times the word CR has been executed. May be examined to control output formatting. Note that this word is reset by PAGE to 0.
#OUT
A user variable which contains the number of characters TYPEd or EMITted since the last CR. Note that words such as SPACE or SPACES, which use EMIT, will also bump this variable. May be examined by the user to control output formatting. CR resets to 0.
DPL
A user variable which contains the number of places after the decimal point for input conversion.
FENCE
A user variable which contains the address below which FORGET will not operate. This may be changed by the user, but caution must be observed.
VIEW?
A user variable which controls whether or not a view field will be compiled by CREATE. The view field is the field used by LOCATE to find the source code for definitions in the dictionary. If VIEW? is 0 , then no view field will be compiled. To compile view fields, VIEW? must contain a 2. Any other value will cause the system to behave oddly. Note that turning VIEW? off will save memory, but it does not affect execution speed. You should also be aware that executing SAVE-FORTH will save the current values of certain system variables, such as FENCE, VIEW?, WIDTH and WARNING.
WARNING
A variable which controls the printing of non-fatal error messages (such as NAME ALREADY EXISTS). If warning is false, no messages will be printed.
WIDTH
Controls the number of characters actually stored in the dictionary. The default is 31, which is specified by the Forth-83 standard. This may be changed by the user. Note that using fewer characters will save memory.
Although not variables, the following are used so often, this is probably a good place to tell you about them:
OFF
Stores a false (0) in a variable. VARIABLENAME OFF is the same as FALSE VARIABLENAME ! , but much quicker.
ON
Stores a true (-1) in a variable. VARIABLENAME ON is the same as TRUE VARIABLENAME ! , but much quicker.
MATH
(Note: The stack notation below uses n as an abbreviation for single numbers, and d to represent double numbers. Also note that floored division is used, to be consistant with the rest of the standard)
2* ( n -- 2*n)
Performs an arithmetic left shift of n, leaving the result on the top of the stack.
D2* ( d -- d*2)
Same as 2* , but for double numbers.
M* ( n1 n2 -- d )
D is the doublenumber product of the single numbers n1 n2.
M*/ ( d1 n1 n2 -- d2 )
d2 is the result of multiplying d1 by n1 and dividing the resulting product by n2. An triple-precision intermediate product is used, and all values are signed.
M+ ( d1 n -- d2 )
d2 is the sum of d1 and n. All values are signed.
M/ ( d1 n -- n2 n3)
n3 is the remainder and n2 is the quotient of the division of d by n1. All values are signed.
M/MOD ( ud1 u1 -- u2 ud2 )
ud2 is the quotient and u2 is the remainder of the division of ud1 by u1.
S>D ( n -- d )
n is sign extended to form the double number d.
MISC:
ASCII
A handy word. Used in the form
ASCII *
to either leave the ascii code for the following character on the top of the stack, or compile it as a literal for later execution. ASCII makes programs much more readable, and also saves much time spent looking up ascii codes in tables.
CONTROL
Also handy. Used in the form
CONTROL "c"
where c is one of the 64's weird reversed control characters, such as a cursor left or a clear screen.
FREE ( -- #bytes)
Leaves the number of bytes free on the stack. This may be changed by CONFIGURE. (Note that the amount of memory occupied by the disk buffers is not considered FREE, although it may be used by user programs if no disk accesses are to be performed.)
//
Causes the rest of the line to be viewed as a comment. Corresponds to a backslash, found on some computers, but, regrettably, not on the C64.
Blazin' Forth also includes Kim Harris's experimental proposals for dictionary operators. See the text of the Forth-83 standard for more information.
{$00}Blazin' Forth Documentation, Strings
String Extensions
Overview:
There are two values associated with every string variable in Blazin' Forth. The first is the maximum allowable length of the string, which is set when you create a string variable. The second is the actual length of the string, which is set when you actually store the string in the variable. It is important to keep this in mind when using strings, since the string operators will truncate the string if you try to store more characters in the string than you have allotted room for. Of course, you don't have to use the whole space, but you can't overflow it.
STRING STRINGS
These two words are used to reserve variable space for the strings in memory. STRING is used to allocate a single string variable, while STRINGS allocates a string array. You must always tell Forth how long your string is going to be. It can be any length, within the maximum limit of 255. Example:
10 STRING NAME$
Creates a string variable called NAME$ in the dictionary. Space is reserved for 10 characters. This variable will behave exactly like a regulation Forth variable, and leave its address on the stack. Note that the address is the address of the count byte, which allows you to use COUNT TYPE on the string to type it out.
STRINGS requires the number of strings you intend to store, as well as their maximum length. Example:
5 20 STRINGS ARRAY$
Reserves room in the dictionary for 5 strings of 20 characters each. To access the 3rd string in ARRAY$, you simply type:
3 ARRAY$
And the address of the third 20 character string in ARRAY$ will be left on the stack. Each string in a string array is stored with its own count byte, and the address left when you access a string in a string array will be the address of the length byte for that string. Note that both STRING and STRINGS initialize the string length to zero.
"
" is used to compile string literals. When it is executed, it leaves the address of the count byte of the string on the stack. For example:
: JOHN$ " JOHN" COUNT TYPE ;
When executed, JOHN$ will print JOHN on the terminal. Note that " may also be used outside a definition. In this case, the string will be stored at PAD, and the current address of PAD will be left on the stack.
$SIZE $LENGTH
These two words are used to determine the length of strings or their maximum allowable size. $SIZE will leave the maximum allowable length of the string on the stack, while $LENGTH will leave the length of the string currently stored in the variable. Example:
NAME$ $SIZE ( leaves the maximum number of characters allowable in NAME$)
NAME$ $LENGTH ( leaves the length of the string currently stored in NAME$)
$!
This word is used to store a string in a variable. You can use it to initialize a string variable from a string literal. Example:
" This is a string" NAME$ $! ( store string literal in NAME$)
Note that strings will be truncated to the max length allowable for the string variable you are moving the string too. So for example, if NAME$ was created with a maximum size of 5, the only characters actually stored in NAME$ would be "THIS ".
You can also move strings from one variable to another with this word:
TRASH$ NAME$ $! ( move the string in TRASH$ to NAME$ )
Note that this word only operates on string variables - you can't use it, for example, to move a string to the PAD (see COPY$ for a way to do this.)
$+
This performs the concatenation operation. Once again, if the total length of the concatenated string exceeds the length of the variable, then the string will be truncated. Example:
FOO$ BAR$ $+ ( the concatenated string is left in FOO$ )
Strings may be added (concatenated) to themselves:
FOO$ FOO$ $+ or FOO$ DUP $+
$?
This simply types out the string. Example:
NAME$ $? ( type current string in NAME$)
COPY$
This word will extract a portion of a string, and move it to another string. It takes four arguments. The string address from which the string is going to be copied, the starting character of the substring, then ending character of the substring, and the destination. Example:
NAME$ 2 4 LIST$ COPY$ ( move characters 2 thru 4 of NAME$ to the start of LIST$)
You can also move a string to the pad with this word:
NAME$ 2 10 PAD COPY$ ( move characters 2-10 of NAME$ to the PAD)
Note that you can move the entire string by simply giving indexes of 1 and 255:
NAME$ 1 255 PAD COPY$ ( move the whole string to the PAD)
+$!
This word is used to store a string in the middle of another string. For example:
NAME$ JUNK$ 4 +$!
Will store NAME$ starting at the 4th character in JUNK$. This is a very useful string operation, particularly in conjuntion with COPY$ :
NAME$ 2 4 PAD COPY$ PAD JUNK$ 4 +$!
Will move the 2 thru the 4th characters of NAME$ to JUNK$, starting at the 4th character of JUNK$ ( try this one in BASIC!).
Note that +$! will only modify an already initialized string. For example, if JUNK$ contains a 10 character string, then:
NAME$ 2 4 PAD COPY$ PAD JUNK$ 11 +$!
will be a null operation. There are often cases when you want to build up an unitialized string from smaller pieces, or to use a single string variable as a one dimensional string array. To do this, you must first initialize the string to all spaces. The word $BLANK has been provided for this purpose. $BLANK will set the entire string to spaces, and then set the length of the string equal to the maximum size for that string. Example:
LIST$ $BLANK ( blank LIST$)
IN$
This word will find the first occurence of a string in another string, and return the index of the first character. If nothing is found, a 0 is returned. Example:
NAME$ LIST$ IN$ ( return the first occurence of NAME$ in LIST$)
$= $< $> $<> $<= $>=
These are the string boolean operators. Note that, everything else being equal, the longer string is considered the greater, and also that lower case is not the same as upper case.
>LOWER
This word is principly useful for carrying out string comparisons when you want to ignore case. It takes a starting address, and a count, and converts the entire thing to lower case. (Lower case being the same as upper case when the computer is in uppercase/graphics mode). Note that the string is converted where it stands, so if you want to preserve the original form, move it somewhere else before using this word.
Example: Check if two strings are equal:
NAME$ TEST$ $=
Example: Check if two strings are equal, but ignore case:
NAME$ DUP COUNT >LOWER TEST$ DUP COUNT >LOWER $=
Example: Check if two strings are equal, ignore case, but preserve the original form of the strings:
NAME$ 1 255 PAD COPY$ ( move to pad)
PAD COUNT >LOWER ( convert to lower case)
TEST$ TRASH$ $! ( move TEST$ to a temporary variable)
TRASH$ COUNT >LOWER ( convert to lower case)
PAD TRASH$ $= ( compare)
INPUT$
This accepts input from the keyboard, and moves the string to the variable whose address is on the stack. Note that the PAD is used for temporary storage, and a copy will remain there immediately after using this word. Note also that the full length of the input will be in pad, but it will be truncated if moved to a variable which is not large enough.
Example:
: GETNAME ." Please type your name " NAME$ INPUT$ ;
This example will prompt the user, and store his response in NAME$.
VALUE$
This word will convert a string stored at an address to a double number on the stack. Example:
NUMBER$ VALUE$ ( convert the string in NUMBER$ to a double number on the stack.)
Using these basic string operations, all necessary string handling operations may be carried out. You can also easily define your own custom string handling words. For example, if you are particularly fond of BASIC's string handling procedures, it is easy to define Forth equivalents using these operators. For example:
: LEFT$ ( addr1 n addr2 --)
>R 1 SWAP R> COPY$ ( move n leftmost characters of addr1 to addr2) ;
Which you would use as follows:
NAME$ 4 TEMP$ LEFT$ ( the 4 leftmost characters in NAME$ are left in TEMP$)
: RIGHT$ ( Move n right most characters of addr1 to addr2.
Stack: addr1 n addr2 -- )
>R OVER $LENGTH 1+ SWAP - OVER $LENGTH R> COPY$ ;
You would use RIGHT$ as follows:
NAME$ 4 TEMP$ RIGHT$ ( the 4 rightmost characters of NAME$ are left in TEMP$.)
You can use definitions like these to easily convert BASIC programs to FORTH. Personally, I think you will find it more flexible in the long run to use indexes into the strings, rather than the less flexible BASIC type of command. That's only an opinion - many people have lived long and productive lives, and never once indexed into a string.
{$00}Blazin' Forth Documentation, Graphics Support
VIC chip Support
Blazin' Forth contains an extensive implementation of Turtle Graphics which supports all of the graphics modes of the 64, and is also by far the fastest turtle graphics implementation I have ever seen on this computer. I am particularly proud of this aspect of Blazin' Forth, and I hope you get a lot of pleasure out of it.
Turtle Graphics was developed by Seymour Papert of MIT as part of his Logo language, and it is an excellent way to explore computer graphics. The Turtle in Turtle Graphics is a graphics cursor, generally shaped like a triangle (shaped like an arrow-head in Blazin' Forth). You control the turtle by telling it to move FORWARD, or BACK , and as it moves, it draws a line. You control the turtles direction by giving it commands like RIGHT or LEFT. You can also use coordinates, through the SETXY command.
The graphics screen in Blazin' Forth is 240 turtle steps high ( screen coordinates 0 to 239) and 320 turtle steps wide ( screen coordinates 0 to 319). Wraparound, which is usually part of Turtle Graphics systems, is not implemented, since I think it's preferable to see part of a figure instead of a mess. The lines drawn by the turtle are therefore clipped. It should also be mentioned that the graphics screen does not take any of the memory normally available to Forth due to the intensive use made of the C64's powerful bank switching feature.
DRAW
Sets up the graphics screen, with the turtle in the center of the screen, pointing straight up. Defaults to SCOLOR and SPLITSCREEN , but these may be changed. It is extremely important that this word (or SCOLOR or DCOLOR) be used at least once before typing any of the other graphics words!
FD or FORWARD
Takes a number on the stack and moves the turtle forward that distance. Draws a line if the turtles pen is down. Example:
20 FD ( move the turtle 20 turtle steps forward)
BK or BACK
Takes a number on the stack and moves the turtle back that distance. Draws a line if the pen is down. Example:
20 BK ( move the turtle 20 turtle steps back)
RT or RIGHT
Takes a number on the stack, and rotates the turtle right that number of degrees. Example:
90 RIGHT ( rotate the turtle 90 degrees to the right)
LT or LEFT
Takes a number on the stack, and rotates the turtle left that number of degrees. Example:
90 LT ( rotate the turtle 90 degrees to the left)
SP or SPLITSCREEN
Enters SPLITSCREEN mode, with 5 lines of text on the bottom of the screen, and the rest of the screen in graphics mode. This is the default condition. Due to the location of the hires screen, a small amount of flicker may be observed, particularly when the turtle is in the region of the split. This is a hardware limitation over which I have no control. Sorry.
FS or FULLSCREEN
Exits SPLITSCREEN mode. The entire graphics screen is displayed. Note that if you want to mix music and graphics, you must be in FULLSCREEN mode. This is because both splitscreen mode and the music words are interrupt driven.
NOSPLIT
This will cause DRAW to default to FULLSCREEN mode. Executing SPLITSCREEN will restore SPLITSCREEN as the default condition.
BG or BACKGROUND
Takes a number on the stack, and sets the background color of the hires screen to that color. When in splitscreen mode, the border color will also be affected by this command. The number should be between 0 and 15, as described in the programmers reference guide. Example:
1 BG ( sets background to white)
PC or PENCOLOR
Takes a number on the stack, and sets the pencolor of the turtle to that color. Note that a pencolor of -1 is allowed, and will cause the turtle to erase, instead of draw. Example:
2 PC ( draw in red)
PE or PENERASE
Causes the turtle to erase instead of draw as it moves. Note that the pen must be down in order to erase. (Or the eraser must be down, in this case.)
PU or PENUP
Lifts the turtles pen. The turtle may be moved without drawing or eraseing a line.
PD or PENDOWN
Sets the turtles pen down. The turtle will once again draw a line as it moves.
HT or HIDETURTLE
Makes the turtle invisible. Note that a pleasant side effect of this is that drawing will become even faster.
ST or SHOWTURTLE
Makes the turtle visible.
SCOLOR
Enters single color or HIRES mode. In this mode, all colors are available, but drawing over lines drawn in one color with a line in another color will change a small portion of the previous line to the new color. Lines have a finer, sharper appearance in this mode. Note that executing either SCOLOR or DCOLOR will have the same effect as DRAW - the screen will be cleared, and the turtle moved to its home position.
DCOLOR
Enters double color mode. In this mode, up to three lines of different colors may share the same area without conflict. Lines are thicker, but the colors are easier to see. Note that when in DCOLOR mode, scrolling of the text screen underneath the draw screen may affect certain of the colors. Since splitscreen mode is usually used only when debugging, this should not be a serious problem. (It's due to the sharing of color memory between the text screen and the hires screen.) Once DCOLOR has been selected, draw will default to this mode until SCOLOR is once again executed.
HOME
Sends the turtle to its home - center screen, pointing straight up. Note that the turtle will draw a line if the pen is down. (see: WINDOW)
SETXY
Takes two values from the stack - the X and Y coordinates of a point, and moves the turtle to that location, drawing a line if the pen is down. Example:
100 100 SETXY ( move the turtle to 100 100 )
SETH or SETHEADING
Takes a number on the stack which sets the current heading of the turtle. 0 degrees is pointing straight up, with degrees increasing to the right. Example:
90 SETH ( aim turtle at the right of the screen)
G$
Takes the address of a text string, and two coordinates. G$ then types the textstring on the graphics screen. The characters are not drawn by the turtle, and so the turtles position and heading are not affected. The only commands which affect both the turtle and G$ are PC ( except -1 PC , which has no effect on G$) and WINDOW. Characters drawn in DCOLOR mode are not very legible, incidentally. Example:
" THIS IS A STRING" 100 100 G$ ( type string starting at 100 100)
WINDOW ( x1 y1 x2 y2 -- )
Sets up a drawing window for the turtle. Takes four entries from the stack. x1 and y1 are the coordinates of the lower left corner of the drawing window, while x2 and y2 are the coordinates of the upper right hand corner of the drawing window. Executing this command will cause the turtle to center itself inside the window, and, although the turtle can move outside of the window, lines will be drawn only within the window boundary. Note that G$ is also affected by this command. HOME will center the turtle at its new home, but DRAW will reset the window to its maximum. Note that an error occurs if the coordinates passed to WINDOW are outside of the current window. An example of windowing is provided on the source disk. Make sure the disk is mounted, and then type 130 LOAD .
RESWIND
Calling this word will reset the turtle window to its default condition. This word must be used if you intend to set up multiple windows, since the new window will likely be outside of the old one, and therefore cause an error. Example:
: WINDOW1 RESWIND 0 0 50 50 WINDOW ;
: WINDOW2 RESWIND 100 100 150 150 WINDOW ;
The following are variables which may be accessed to determine the state of the turtle.
XCOR YCOR These are system variables which contain the current X coordinate andd Y coordinate of the turtle. Note that these variables should only be used to determine the coordinates of the turtle. Storing values in these variables will have strange effects. If you want to change the turtles position, you should use SETXY . Example:
XCOR @ YCOR @ . . ( display the current xy coordinates of the turtle.)
HEADING This contains the current heading of the turtle, in degrees. 0 is straight up, increasing to the right. The same caution applies as for XCOR YCOR . If you want to change the turtles heading, use SETH or SETHEADING. Example:
HEADING @ . ( print the turtles heading on the screen)
PENSTATE True if pen is down.
TURTLESTATE True if turtle is visible.
Don't be misled by the apparent simplicity of turtle graphics. It is extremely powerful, and allows many remarkable pictures to be drawn, as well as many interesting mathematical explorations to be carried out. It's also a *lot* of fun. As a simple example, here is a word that takes one value from the stack, and draws a square with sides of that length:
: SQUARE ( SIDE -- )
4 0 DO DUP FD 90 RT LOOP DROP ;
To try out this word, type DRAW and then 100 SQUARE. A square of 100 turtle units per side will be drawn.
This is obviously a simple example, but the power of turtle graphics, combined with the speed and power of Forth, allow amazingly rich and complex graphics displays to be drawn with words that are barely more complex than our SQUARE definition. As another example, here is a word that trues a truly remarkable curve:
: C-CURVE ( SIDE LEVEL )
?DUP 0= IF FD EXIT THEN
2DUP 1- RECURSE
90 RT
2DUP 1- RECURSE
90 LT 2DROP ;
This innocent looking definition draws a real whopper of a curve. To try it out, type DRAW, and then issue the following sequence of commands to orient the turtle:
70 BACK CLEARSCREEN FULLSCREEN 90 LEFT 3 10 C-CURVE
This type of curve is known as a fractal, which have received a lot of attention recently in the popular press. They have the reputation of being *very* hairy. Note the simplicity of the definition, and note also that Turtle Graphics and recursion are naturals when used together. I hope this brief introduction will encourage you to explore turtle graphics further on your own. There are some other programing examples included on the source disk. Aside from the windowing demo mentioned earlier, there are:
Another fractal, the DRAGON curve. This program draws a whole family of Dragons. Access this by typing 137 LOAD.
Space-filling curves. The famous HILBERT curve is here, as is the less well know but very pretty and interesting SIERPINKSI curve. To view these curves, type 135 136 THRU. The dragon curve, and both of these curves, are further examples of recursive programming in Forth.
There is a simple example of the use of DCOLOR mode. Access this by typing 131 133 THRU.
All of the above will run when they are loaded. In addition, there are some examples included which you may play around with interactively. Block 131 contains some words which draw circles. Screen 134 contains some words which use these words to build up more complex designs. First type 131 LOAD, and then 134 LOAD. The words available are:
SPINSHRINK Type DRAW SPINSHRINK to see this design.
The next two words require you to place a parameter on the stack.
SLINKY requires a value on the stack, which determines the radius of the circles drawn.
SPINSLINKY requires the same value. (This is a particularly pretty display.)
Finally, screen 138 contains three examples which deal with polygons. These are quite famous among turtle graphics users, and they are presented here to show you one way to implement them in Forth, or to introduce you to them, if you haven't met them before. Typing 138 LOAD will compile the following words:
POLY This word requires two values on the stack, which specify a side length and an angle. POLY draws closed polygons, and any polygon may be drawn by POLY. For example DRAW 100 90 POLY will draw a square of 100 turtle units.
POLYSPI This word requires the same two parameters on the stack as POLY, but POLYSPI draws spiraling polygons. This is easier to see than it is to describe. For example, DRAW 1 90 POLYSPI will draw a spiraling square (or SQUIRAL).
INSPI This word also requires two values on the stack, but where POLYSPI increased the side each time, INSPI increases the angle. An incredible variety of shapes can be drawn with both INSPI and POLYSPI. One of my favorite INSPI designs is DRAW 10 1 INSPI . You will no doubt discover others you like, as you try out the effects of different stack values on each of these words.
Blazin' Forth also contains words that allow you to manipulate sprites. Blazin' Forth will take care of most of the hardware details for you, but some caution must be exercised. Since you must setup your own sprite data areas, it is possible to garbage the disk buffers with your sprite data. However, with a small amount of care, this should not occur, since there is enough room for 45 sprites without stealing any room from Forth. (A total of 109 sprite images are available.)
You should first enter a sprite definition into the Forth dictionary. The method you use to do this is up to you. One possibility is to use C, as follows:
CREATE SPRITE1 0 C, 2 C, etc.
Or you can use the " word, if the string handlers are loaded into the system:
: SPRITE1 "ANCND" etc. ;
Note that if you use this second way, then you must add 1 to the address left by " , since the address left by " is the address of the strings count byte, and SETSHAPE , which is the word used to transfer the data to the sprite areas, expects the address of the start of the data on the stack.
However you decide to do it, your sprite definitions must be 64 bytes long. There are 63 bytes of spirte display data, while the last byte is used by the system to set the sprites color mode. If the last byte is 0 , then the sprite will be a hires (single color) sprite. If the last byte is anything else, then the sprite will be a multicolored one.
Each sprite-shape is assigned a sprite number by the hardware. You must use a slight amount of care in setting your shapes, since it is possible to garbage your disk buffers, or to select an area which the system is already using for something else, such as the color storage for the graphics screen.
There are 45 shape numbers which are available to you and won't cause any conflicts anywhere. Shape-numbers from 64 to 94 or perfectly safe, as are shape numbers from 112 to 126. Sprite numbers greater than 126, or between 95 and 111 are not available at anytime.
In addition, the sprite shapes from 0 to 64 may be used, but this will cause garbage to be written to the disk buffers. There are two solutions to this problem. One is to make sure you always type EMPTY-BUFFERS after using the sprite words. The other way (and it's probably the easiest in the long run) is to lower the top of memory to hex $C000. (For information on how to do this, please see CONFIGURE.) Note that you may have to pair down the dictionary if you take this second route. In any case, I find it hardly likely you will need more than the 45 sprites already available to you - but if you do need the full 109 shapes, they are available to you for very little extra work.
Once you have your shape data safely installed in the dictionary, you are ready to go:
SETSHAPE ( addr shape# -- )
SETSHAPE takes the address of a sprite image, and a shape# and moves the sprite data to the proper area for the display of that sprite. Note that DRAW must have been executed at least once prior to using this word!
S1 S2 S3 S4 S5 S6 S7 S8
These words set the current sprite. Executing these words will determine which sprite is affected by other sprite words.
SPRITEON ( SHAPE# -- )
Takes a shape number from the stack, and sets the current sprite to that shape. It then turns on the sprite. Note that the sprite must be on screen to be seen.Example:
S1 65 SPRITEON ( set sprite1 to shape# 65, and turn it on.)
One way of using this word for effective animation effects is to use it to shift between sprite images for the same sprite:
: SWITCH S1 40 0 DO 65 SPRITE ON 70 SPRITEON LOOP ;
(Note that the above example will probably go much to fast for effective animation. It's just an illustration.)
SPR-DCOLOR ( c1 c2 -- )
Takes two color codes from the stack, and sets the multi-color sprite color registers to these values. Note that all multi colored sprites will share these colors. Sprites not in multicolor mode are unaffected by this command.
SPRITECOLOR ( c -- )
Takes a color code from the stack, and sets the the current sprite to that color. Example:
S2 0 SPRITECOLOR ( set sprite 2 to black)
HIDESPRITE
Hides the current sprite. Example:
S1 HIDESPRITE ( hide sprite 1)
SHOWSPRITE
Shows the current sprite, if it is on screen. Example:
S1 SHOWSPRITE
SPRITEXY ( X Y -- )
Sets x y position of current sprite. Coordinate values are the same as for the turtle. Note that negative values are legal, and will cause the sprite to move offscreen at the left or bottom. Example:
S1 -1 100 SPRITEXY ( move the current sprite one pixel off of the screen.)
WIDE-SPRITE ( flag -- )
If flag is true, the current sprite will be made wider. Example:
S1 TRUE WIDE-SPRITE ( make sprite 1 wider)
S2 FALSE WIDE-SPRITE ( make sprite 2 narrow)
HIGH-SPRITE ( flag -- )
Same as wide-sprite, but makes the current sprite taller or shorter. Example:
S3 TRUE HIGH-SPRITE ( make sprite 3 taller)
SPRITECOVER ( flag -- )
If flag is true, the current sprite will cover the background. If flag is false, the current sprite will be transparent. Example:
S1 TRUE SPRITECOVER ( have sprite #1 cover its background.)
SPRITEBANG ( -- flag)
Leaves a true flag if the current sprite has banged into another sprite, and a false otherwise. Note that sprites can bang into each other even when off screen. Example:
: MOVEIT S1 319 0 DO I 150 SPRITEXY SPRITEBANG IF " BOOM! " I 150 G$ LEAVE THEN
LOOP ;
DATABANG ( -- flag)
Same as SPRITEBANG, but leaves true if the current sprite has banged into non-sprite data on the screen. Note that sprites can bang into data even when off screen.
For more information on defining sprites, as well as more details on multi-colored sprites, please see the programmers reference guide.
{$00}Blazin' Forth Documentation, Appendix
1: Accessing CBM disk files
Since Forth uses a virtual memory arrangement and the screen concept to manage the disk, I felt that providing words to access CBM disk files would be a waste of memory space. Blazin' Forth contains all the primitives necessary to write words to access CBM disk files, or other serial devices. This appendix describes how to do this from Forth, for users whose applications require such access.
(OPEN) ( file# device# sa address-of-filename filename-length -- error code)
This word requires 5 parameters, as described above. This is the same as from BASIC, with the exception of the address of filename, and the length of the filename. The address can be any memory location which stores a legal filename. Using Blazin' Forths string handling words, an open statement would look like this:
8 8 8 " LETTERS,S,R" COUNT (OPEN)
Which opens a sequential file called "LETTERS" for a read, with logical file# 8, on device 8, with a secondary address of 8. Note that (OPEN) returns an error code, which is 0 (false) if everything was ok, and otherwise is one of the error codes as described in the Programmers Reference Guide. This error code may be processed by your program, ignored, or you may use the Blazin' Forth word IOERR. (see IOERR for more info on this.)
If you intend to use (OPEN) a lot, you will probably want to define a different version:
: OPEN COUNT (OPEN) IOERR ;
With this new word, the OPEN statement becomes:
8 8 8 " LETTERS,S,R" OPEN
If you need to open a file to a device which doesn't require a filename, such as a printer or plotter, just use a zero as the filename and length:
4 4 0 0 0 (OPEN) IOERR
The above will open a channel to a printer connected as device #4.
CLOSE ( File# -- )
This will close an already open file. Example:
8 CLOSE ( closes file# 8 )
To send or receive data from the device, you will need the following words:
(CMDOUT) ( File# -- errorcode)
This is an I/O redirection word. Executing this word will cause all system output to be directed to the logical file whose number is on the stack. All system output words: EMIT , SPACES , TYPE , ." .( etc. will direct data to this device. Note that you must have opened a channel to the file before using this word. The error code is suitable for being read by IOERR . Example:
8 (CMDOUT) IOERR ( send all output to file# 8 )
(CMDIN) ( File# -- errorcode)
Another I/O redirection word. Executing this word will cause all system input to be obtained from the file whose number is on the stack. All system input words, EXPECT KEY ?KEY etc. will recieve data from this file. Note that you must have previously opened a channel to the file before using this word. The error code is suitable for being read by IOERR. Example:
8 (CMDIN) IOERR ( get all input from file# 8)
CMDOUT ( -- )
Executing this word will restore the default input and output channels. All input will now come from the keyboard, and all output will be sent to the screen. Note that all files remain open.
IOERR ( errorcode -- )
This word will execute an error abort if the value on the top of the stack is non-zero. It will decode the error# and type the appropriate message. Note that if you do not want your application to halt on an error, you should not use this word.
Using the above words, it is a simple matter to send or receive data from a disk. For example:
: GET# ( FILE# -- character )
(CMDIN) IOERR ?KEY CMDOFF ;
This defines a word which works exactly like GET# in BASIC. Note the use of CMDOFF at the end of the word to restore default devices. This is generally the best practice to follow, since the serial bus does not handle multiple channels being connected all that well.
The following word is an example of how to implement PRINT# in Forth:
: PRINT# ( address file# -- )
(CMDOUT) IOERR COUNT TYPE ( send text at address to disk)
CMDOFF ( restore default devices) ;
If you require more exotic file handling words, they are also easy to define. Just follow the examples given above. The following are also useful in handling disk serial bus access:
(?DISC) ( --- flag )
This word reads the disk error channel, leaving a true flag if a disk error has occurred. This is useful if you want your program to handle errors, since the higher level ?DISC will abort on an error. Note that the command channel must be opened before using this word. Example:
: RECEIVE 8 GET# (?DISC) IF .DERR ABORT THEN ;
This word will print the disk error message, and halt execution if there is an error.
.DERR ( -- )
Prints the disk error message stored in the error message buffer. Note that this is only valid after (?DISC) or (R/W).
STATUS ( -- status )
Pushes the serial bus status byte on the stack. Same as ST in BASIC.
2: System DEFERed words
The system contains several DEFERed words for your convenience. These allow changes to be made in the actual Forth system itself:
PRINTER NOPRINTER
These words are described in the section on I/O.
PUNCT? ( char -- flag)
This word controls the characters accepted by NUMBER. In order to conform to the 83 standard, Blazin' Forth will only accept a period when entering double numbers. All other characters will result in an error. In order to change this behaviour, code a new word, which accepts the characters you wish, and then execute:
' NEW.PUNCT IS PUNCT?
Of course, NEW.PUNCT must accept the same inputs, and provide the same outputs, as the older version.
R/W ( addr blk# flag -- )
This word is the virtual memory interface primitive. It may be altered to add new disk handling operations to FORTH, such as a file system.
3: Vocabulary Structure
Blazin' Forth uses the same tree structured vocabulary linkages as FIG forth. Some of the newer dynamic vocabulary structures were examined, but found unsatisfactory. Most people seem to prefer FIG to forth-79, but all reports are not in as far as the newer efforts are concerned. Comments on these newer vocabulary structures are welcomed.
In Blazin' Forth, a new vocabulary is chained to the vocabulary within which it is created. For example:
FORTH DEFINITIONS ( Put all definitions in FORTH vocabulary)
VOCABULARY CARTON ( chain CARTON to FORTH )
CARTON DEFINITIONS ( Put all definitions in CARTON vocabulary)
VOCABULARY EGGS ( Chain EGGS to CARTON, which chains to FORTH )
FORTH DEFINITIONS ( restore Forth )
4: Misc. Information
You may change the screen color defaults of Blazin' Forth to ones more suitable to your monitor or tv. Example:
0 0 +ORIGIN C! ( border color)
4 1 +ORIGIN C! ( Screen color)
6 2 +ORIGIN C! ( character color)
Once this is done, RESTART, or RUN/STOP RESTORE will default to the new colors, as will NODRAW etc. Once you find an acceptable combination, you can save the system using SAVE-FORTH. Note that some early model 64's have a screen editing bug which appears when trying to delete across a screen boundary. The words LOAD RUN will appear, and although the cursor will continue to flash, the computer will not respond to any input. The only solution, (aside from obtaining a ROM upgrade from CBM) is to select a color with a low number, such as 0 or 1. Note that if this problem occurs, it is NOT a Blazin' Forth bug.
You may desire to convert the present editor to a fullscreen editor. This is easily accomplished. Probably the easiest way is to define 16 words, for example, 0: 1: 2: etc., or P1 P2 or whatever you like. These words should accept 64 characters from the input stream, and then move them to the appropriate postion in the buffer for that screen. (In other words, they combine the functions of T , in selecting the line, and P in placing the line in the buffer.) You can then modify LIST so that instead of line numbers, it lists out with your words 0: 1: 2: or whatever you used. Once this has been done, you may use the resident features of the 64's screen editor to their full extent. I didn't provide such an editor with the system since it was desired to make this system as compatible with the book Starting Forth as possible.
You may define additional USER variables. User offsets from 58 to 198 are available to you for new user variables. Offsets less than 58 are used by the system. Blazin' Forth will initialize the entire user area to zero's on a RESTART or on POWERUP. Note that this is not a Standard Feature.
The system is configured for use with one 4040 compatible dual drive. Single drives are usable with Blazin' Forth, but the highest screen accessable is 166. Note that it is be possible to interface additional drives, or non-standard drives to Blazin' Forth by vectoring R/W. (see: DEFERed words.) Note that users of single disk drives will not receive an ILLEGAL SCREEN error if they attempt to access screens greater than 166. (R/W) may bepatched, or modified to do this if so desired. (See: Defered words)
Membership in the Forth Interest Group (FIG) is encouraged. They provide a magazine, FORTH DIMENSIONS, which presents a variaty of interesting articles and applications. They also supply books, reprints, and sponsor Forth seminars. Of particular interest to those using Blazin' Forth is that they supply copies of the FORTH-83 standard ( $15.00 at this writing). Membership at this writing is $15.00 per year, which includes a free subscription to Forth Dimensions, and may obtained by writing to:
The Forth Interest Group
P.O. Box 8231
San Jose
CA 95155
USA
The Forth Standards Team is responsible for the development of new Forth standards. The welcome comments and proposals. The may reached at:
Forth Standards Team
P.O. BOX 4545
Mountain View
CA 94040
USA
{$00}Start Blazin' Forth
FOREWARD
By far the most read (and best) instructional book on Forth is STARTING FORTH, by Leo Brodie. It is very clear, amusing, and covers the territory. Unfortunately, since Starting Forth was written, Forth itself has undergone considerable evolution, so not everything in Starting Forth is applicable today. Additionally, Starting Forth is based on a particular dialect of Forth (POLY-FORTH), which is a Forth implementation by Forth Inc., the company Mr. Brodie worked for when he wrote his excellent book. As a result, even for the time it was written, certain passages and examples would not work on anything but a Forth Inc. system. ( .S is a classic example of this, but there are others. )
Blazin' Forth is written to support the Forth-83 standard. This is the most recent available standard, and the one which in my opinion, and from all I can tell, the opinion of most other Forth users, the most significant standard so far. As a result, it is not completely compatible with Starting Forth. ( Mr. Brodies latest book THINKING FORTH , uses Forth-83, by the way.)
Perhaps surprisingly, most of the examples Brodie gives work without a single change in Blazin' Forth. This document is intended to help you when the examples don't work as described, speeding up the learning process, easing the pain, and generally helping to prevent you from damaging yourself or your computer through frustation. Differences between the earlier Forth's and the present standard are described, and alternate definitions are provided.
Not all the comments are about differences - there are many more words in Blazin' Forth than are included in the Standard. I couldn't resist telling you about a few of them.
Good luck! And may the Forth be with you.
SDB
NY NY 1985
Chapter 1
Page 11
The definition STAR will work as supplied. Blazin' Forth contains a word called ASCII that can make words like STAR more readable (and save you a lot of time looking up characters in ASCII tables). Using ASCII , the definition of STAR would be:
: STAR ASCII * EMIT ;
Try it - you'll like it.
By the way, if you change the definition of MARGIN on the next page to
: MARGIN CR 15 SPACES ;
your letter will look a little better on the screen.
The Dictionary Page-16
Blazin' Forth normally allows 31 characters to be saved in a definitions name. (This is the number specified by the 83 standard.) However, should you prefer fewer characters to be saved, you can do this by changing the value of the variable WIDTH . For example, to have Blazin' Forth save only three characters of your definitions names, type 3 WIDTH !
Say-What?
Regarding the second foot-note on page-18. The Forth-83 ." will not execute outside of a colon definition. Forth-83 provides the word .( for the occasions when you need to display strings outside of colon definitions. Use it like this:
.( PRINT ME )
Chapter 2
The handy hint on page-50 is a little notorious. I have, personally, never seen a system on which this definition would work. .S is never the less a very handy word, and Blazin' Forth contains it, already compiled into the system. Note that the extra 0 won't be there - you see only what's on the stack, or you get a message "STACK EMPTY", if nothing is on the stack.
Chapter 3
Blazin' Forth's editor is a "Starting-Forth-Clone". You will be able to follow this chapter pretty much as it stands. One very important point - you must issue the command MOUNT , to initialize the virtual memory system, before using any words that access the disk - like LIST , for example. Just remember to type MOUNT , and you will be OK . (You only have to give this command once - at the start of your editing session, unless you change disks, issue the command RESTART, or inadvertantly cause a disk error.)
Some general points: Brodie likes to keep his screen numbers high - in the 100's or 200's . Note that if you have a dual drive, such as a 4040 or MSD , you will have no problem with these high screen numbers ( of course, you will have to have a disk in drive 1!). However, if you are using the system with a single disk drive, such as a 1541, then the highest screen that can be accessed from that drive is 165 . Just keep all your screen numbers below this value, and you will be OK .
The WIPE command is very important, never edit a new screen without using this command first! It's so important, that Blazin Forth contains the word W , which is just a short way to perform a WIPE . Works just the same.
Finally, due to the characteristics of the 64's operating system, trailing blanks are suppressed, and not passed to the editor. This means, for example, that Brodies example of blanking a line:
P bb <return>
won't work. Trailing blanks must be followed by an ^ character in order to be received by the editor. For example:
P bb^ ( blanks current line )
U bb^ ( blanks line under current line)
That's enough for now - check back here when you get to FLUSH.
In Forth-83, FLUSH and SAVE-BUFFERS are not quite the same. Both save any updated blocks to the disk, but FLUSH will "hose down" the buffers - if you want to access the same blocks again, Forth will have to re-read them. SAVE-BUFFERS will leave all the blocks current - Forth won't have to re-read them if you need to access them again. Also, Blazin' Forth contains an additional comment word, // , which can be used to include 1 line comments in source screens.
Getting Loaded Page-81
The suggestion here is not really very good. If the source for your text takes up more than one block, the best solution is not to use many load commands, as Brodie suggests, but to use THRU .
THRU takes two numbers on the stack. The first number is the first screen to load, and the second number is the last screen to load. THRU will then load these two screens, and all screens in between.
So, instead of putting 180 LOAD 181 LOAD 182 LOAD on your load screen, use:
180 182 THRU
instead. It's really much better.
Chapter 4
A Closer Look at IF . Page-95
One of the big differences between the 83 standard and earlier Forths is that Forth-83 returns a negative 1 ( -1 ) instead of 1 when the result of a test like 0= or 0< is true. So Brodies examples will look like this:
5 4 > . -1 OK
5 4 < . 0 OK
NOT works differently too. In earlier Forths, it was really just another name for 0= . But in Forth-83, it returns the ones-compliment of the number on the top of the stack. Notice that the '83 NOT still reverses the value of the '83 flags:
-1 NOT . 0 OK
0 NOT . -1 OK
But NOT won't change any non-zero value to false ( 0 ) like the older NOT would:
1 NOT . -2 OK
And, of course, FORTH will still regard -2 as being TRUE , since it isn't zero.
So when upgrading programs, or reading books that talk about earlier Forth systems, keep this in mind. On the other hand, if you're not quite sure what all this means, and want to play it safe, just replace every pre-83 NOT with 0= , and you'll be OK .
A Little Logic Page-97
To upgrade these examples to FORTH-83, just replace every occurance of 1 with -1 , and you'll have a Forth-83 example. While we are on the subject, Blazin' Forth contains the two flags pre-compiled as constants. TRUE leaves a true value (-1) on the top of the stack, and FALSE leaves a false value ( 0 ). You'll appreciate this more when you learn about constants later in the book.
Two Words with built in IFs Page 102
Just a note - if you do want to add stack checking to your application, all you need to add in Blazin' Forth is the word ?STACK . The ABORT" isn't needed, since ?STACK itself contains the necessary ABORT" .
Problems Page-104
Problem 5
Actually, in Forth-83, 0 STARS won't print just one star, but 65,535 stars. Brodies answer ( or the one you cook up for yourself) should fix this problem, but there are better ways. More on this when we get to the chapter on LOOPs. Incidentally, if you tried 0 STARS before reading this note, you can get control of things by hitting RUN/STOP RESTORE , just like in BASIC .
Chapter-5
The Return Stack
Page-110
The definitions for I , I' and J are not correct for Forth-83. I' does not exist, and I and J are both defined differently. Don't worry, they still do what they are supposed to (which is return the current index value of a loop), it's just that they don't do what Brodie says they would. Just wait till chapter 6 for more information on these two words.
The correct word to copy the top of the return stack is R@ in Forth-83. (i.e. R@ does what Brodie says I should do.)
Page-111
The phrase >R SWAP I would be >R SWAP R@ in Forth - 83. Both phrases will crash the system
Page-112
The definition of QUADRATIC should be:
: QUADRATIC ( A B C X -- N )
>R SWAP ROT R@ * + R> * + ;
In other words, R@ should be used instead of I . See the note above for an explanation.
Page-119
The footnote on this page contains a typo in my copy. The correct definition is:
: R% 50 */ 1+ 2/ ;
Problems
Problem 1
In the answer to this problem, Brodie means NEGATE , not MINUS .
You might also notice that Blazin' Forth does not return a -17 like Brodie says it should. This is due to the 83-standards use of floored division. In floored division, numbers are always truncated to the next lower value. To get the same result as Brodie in 83-Forth systems, use ABS and then NEGATE.
Chapter 6
Forth-83 uses a new loop structure which is very fast, but differs in a few points from the older loops used in older Forths. Sorry about this, but there will be a little more information here than previously.
Page-130
Earlier (in the notes to chapter 5) we said that "I" works differently in '83 Forths. Well, originally, "I" was meant to be used as it is here, to access the loop index. Since in those days, the loop index was stored on the top of the return stack, you could also use "I" to copy that value, even if you weren't in a loop, which was not a purpose for which it was originally intended. So that's the difference, I now only returns the loop index, while R@ copies the top of the return stack to the parameter stack. (For the curious: In Blazin' Forth, I returns the sum of the top two return stack items. It may seem strange, but it works just fine!)
The terminating condition for loops is also different than in earlier Forths. Don't be fooled by the fact that most of the examples in this chapter work the same - the exit condition for the loops are different, and if you ignore this fact, you are going to get bit by it.
In '83 loops, the loop is finished when the Index crosses the boundary between the LIMIT and LIMIT - 1 . A couple of examples:
If the Index is 0 , and the LIMIT is 10 , then the loop will terminate when the Index is incremented from 9 to 10 (crosses the boundary).
If the Index is 0 , and the Limit is -10 , then the loop will terminate when the index is incremented from -11 to -10 or decremented from -10 to -11 . This means, if you are using a DO..LOOP, that your loop will take the long way around...0 1 2 3 4 ...32767 -32768 .....-15 -14 -13 -12 -11 . If you are using +LOOP with a negative step value, then your loop will look like this: 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 (boundary crossed, and looped terminated).
One odd side effect of this new loop structure is that anytime the limit and the index are equal, the loop will execute 65,535 times. So, for example, the word STARS :
: STARS 0 DO ASCII * EMIT LOOP ;
If used like this:
0 STARS
will EMIT an awful lot of stars. Blazin' Forth contains a word to handle cases like this - ?DO . ?DO acts just like DO , except when the loop arguments are equal. If the arguments are equal, then the loop won't be entered at all - FORTH will jump to the word following LOOP or +LOOP . In addition to keeping out nasty surprises (like 0 STAR) ?DO can also shorten and speed up your code. For example, we often find we don't want to perform a loop when the LIMIT is zero (as in STARS , above.) The traditional way to do this is:
: STARS ?DUP IF 0 DO ASCII * EMIT LOOP THEN ;
But you can now do it this way:
: STARS 0 ?DO IF ASCII * EMIT LOOP ;
You can think of ?DO as having a built in IF .
Page 135
The definition given by Brodie:
: TEST 100 10 DO I . -1 +LOOP ;
will execute quite a few more times than one (in '83 Forth). Remember, '83 loops stop when the boundary is crossed between the LIMIT and LIMIT-1 . This crossing can be in either direction, either down ( 101 100 (crossed to 99, so exit) ) or up ( 98 99 (crossed to 100, so exit)) . Since TEST counts down, it will proceed all the way around the number circle, starting with 10, and then going negative, until it eventually goes 101 100 (as in our first example, above). Just though I would mention this.
Page-140 LEAVE
In '83 Forth, LEAVE leaves immediately, not at the next execution of LOOP or +LOOP. Brodies examples will work the same for both '83 and earlier Forths, but problems can arise if you aren't aware of the difference. For example:
: FOO 0 DO I DUP 100 = IF LEAVE THEN . LOOP ;
FOO is a word that will exit when the loop counter reaches 100 . Notice, however, that the actual behaviour of FOO will differ between '83 and earlier standards. In earlier Forths, FOO's execution would be this:
When the Counter reaches 100, LEAVE will set the index equal to the limit.
" . " will be executed, printing 100 on the terminal screen, and leaving the stack empty.
LOOP will execute, and discover its time to stop (since LEAVE changed the INDEX).
In '83 Forth, this is what happens.
When the Counter reaches 100, LEAVE will jump to the word past LOOP , which in this case is the end of the definition. 100 will not be printed, but left on the stack. Quite different behaviours result, as you can see. Just remember that the 83 LEAVE leaps , and you'll be OK .
By the way, Blazin' Forth has a handy word ?LEAVE . ?LEAVE will cause an exit from the loop if the flag on top of the stack is true, otherwise, the loop continues undisturbed. This can shorten your code quite a bit. Here's an example:
: BAR 0 DO I 100 = IF LEAVE THEN LOOP ;
This word just leaves when the index reaches 100. Using ?LEAVE would make things a little nicer:
: BAR 0 DO I 100 = ?LEAVE LOOP ;
Works just the same. Just remember that you have to leave a flag on the stack for ?LEAVE to eat.
Chapter 7
Section II
Page 161
The Forth-83 word for U* is UM* . It works the same as U* , but has been renamed.
The Forth-83 word for U/MOD is UM/MOD . It works the same.
/LOOP is not included. It is no longer necessary in '83 systems. (For the curious: /LOOP was essentially a kludge to fix a problem with the older loops. Just use +LOOP instead.)
Page 164
'83 systems will only accept the period as punctuation in numbers. However, in Blazin' Forth, you may change this. Here are the steps to follow to get Blazin' Forth to accept the same characters those given by Brodie:
First, type in and LOAD the following definition:
: NEW-PUNCT ( CHAR -- FLAG)
ASCII : OVER = SWAP
ASCII + ASCII 0 >R OVER < SWAP R> < AND OR ;
Then, once your NEW-PUNCT has been loaded, type the following:
' NEW-PUNCT IS PUNCT?
Type the above carefully, or you could cause problems (don't forget the ' ). Blazin' Forth will now accept numbers as described in this section. If you prefer this new version, you may save the system with the word SAVE-FORTH. If you want to change back to the oolder version, do the following:
' (PUNCT?) IS PUNCT?
Number Formatting -- Double Length Unsigned.
Pages 167-168
All of the definitions in this section will work as described, but they would be so much more readable using ASCII . Here are some examples:
: .PH# <# # # # # ASCII - HOLD #S #> TYPE SPACE ;
: .DATE <# # # ASCII / HOLD # # ASCII / HOLD #S #> TYPE SPACE ;
: :00 # SEXTAL # DECIMAL ASCII : HOLD ;
Page 171
The definition that Brodie gives for .$ won't work in 83-forth. SIGN works on the value on the top of the stack. A definition that will work in 83-Forth is:
: .$ SWAP OVER DABS
<# # # ASCII . HOLD #S ROT ( to bring signed number to top)
ASCII $ HOLD SIGN #> TYPE SPACE ;
You might also noticw that the positions of the $ and SIGN have been switched. I prefer my negative dollars to have their signs in front of the $ sign. That's the only reason for this change.
Page 174
The warning to experimenters is not true in Blazin' Forth. You can include punctuated, double-precision numbers inside of colon definitions, and they will work just fine. (I have never understood this restriction.) Note that other systems may not allow this.
Page 175
The definition for R% on this page is simply wrong. It should be, instead:
: R% 10 M*/ 5 M+ 10 M/ S>D ROT DROP ;
Which gives $148.15 as an answer.
Chapter 8
Page 194
See the note above (page 174). You do not need to use 2CONSTANT to include double numbers in Blazin' Forth. You may wish to do so as a matter of style, but it is not forced on you.
Chapter 9
Brodies example:
110 ' LIMIT !
won't work with the '83 tick. Use
110 ' LIMIT >BODY !
instead. By the way, LIMIT is an important system constant in Blazin' Forth, and I don't recommend that you try this example on it.
Vectored Execution:
Page 218
The behaviour of ' and ['] in '83 Forth is exactly that described here. Ignore the footnote on '79s tick. You might also wish to look at the documentation for DEFER IS , which are very handy words for handling Vectored Execution.
The Structure of a Dictionary Entry.
Page 220
The structure of a dictionary entry for Blazin' Forth than described here. (Note: The '83 standard regards this as a system dependency, and does not specify any particular form of dictionary structure. Other systems may, and probably do, differ.)
In Blazin' Forth, the structure is:
LINK FIELD
NAME FIELD
CODE POINTER FIELD
PARAMETER FIELD
There is also an optional field, called the VIEW or LOCATE field. This field is used by LOCATE, and is added or not at the option of the user. With the view field included, the structure looks like this:
VIEW FIELD
NAME FIELD
CODE POINTER FIELD
PARAMETER FIELD
Compiling without the VIEW FIELD results in more compact (but not faster running) definitions, but note that LOCATE will not operate on any definition without a VIEW FIELD. To prevent the view field from being compiled:
VIEW OFF
To re-enable the compilation of the VIEW FIELD:
2 VIEW !
Parameter Field
Page 223
The address returned by tick and expected by EXECUTE is not the parameter field address, but the CODE FIELD address. If you want the parameter field address, you may convert the address returned by tick (the code field address) to the parameter field address using the '83 standard word >BODY . For an illustration of its use, see the example using LIMIT , given earlier.
{$00}
Start Blazin' Forth
Forth Geography
Page 231
Not surprisingly, the memory maps of Forth systems tend to differ. Here is the one for Blazin' Forth:
LOW MEMORY **********************
* PARAMETER STACK *
**********************
* TERMINAL INPUT *
* BUFFER *
**********************
* RETURN STACK *
**********************
* ERROR MESSAGE *
* BUFFER *
**********************
* OPERATING SYSTEM *
* VARIABLES *
**********************
* USER VARIABLES *
**********************
* SCREEN MEMORY *
**********************
* PRE-COMPILED *
* FORTH *
**********************
* PRE-COMPILED *
* ELECTIVES *
**********************
* USER DICTIONARY *
**********************
* PAD *
**********************
* BUFFER CONTROL *
* QUEUE *
**********************
* BLOCK BUFFERS *
* (4) *
* or *
* SPRITE IMAGES *
**********************
* MEMORY MAPPED IO *
HIGH MEMORY* AND KERNAL OS *
* or *
* HIRES COLOR MEMORY *
* HIRES SCREEN *
* SPRITE IMAGES *
**********************
User Dictionary
Page 233
The arrangement described here works the same in Blazin' Forth. The only difference is that the variable H , used by Brodie, is called DP in Blazin' Forth. (Stands for Dictionary Pointer.)
Parameter Stack
Page 235-236
'S is SP@ in '83 Forth. Just substitute SP@ for 'S in the examples given, and they will work just fine. In reference to the example showing how to copy the fifth element down, you might want to consult the system documentation for the words PICK and ROLL. (These are '83 standard words.)
S0 is SP0 in Blazin' Forth. Note that this is NOT a '83 standard word. There is also a word SP! , which copies the value stored in SP0 to the stack pointer. This effectively empties the return stack.
Input Message Buffer
Page 236
The modern name for this memory region is Terminal Input Buffer. SP0 does not reference this area. Instead, use the system constant TIB . TIB leaves the starting address of the terminal input buffer on the top of the stack.
Chapter 10
page 255-256
Forth-83 makes a distinction between FLUSH and SAVE-BUFFERS. Both words force all updated buffers to be written to the disk. The difference is that after executing FLUSH , the buffers will be emptied (FLUSH-ed , get it?). But after SAVE-BUFFERS, the blocks will still be in memory, although they will no longer be marked as UPDATEed, since they have been written to disk.
Output Operators
Page 258
Instead of S0 (SP0 in Blazin' Forth) use TIB :
TIB 12 TYPE
Note that a word like S0 or SP0 is very system dependent, but since TIB is a '83 standard word, it can always be used to access the terminal input buffer.
Page 259
To use TEST in the way Brodie describes, it is important to remember that the '83 tick returns the code field address, and not the address of the paramter field. Just remember to use >BODY to do stuff like this, and you'll be fine. I.E.:
' TEST >BODY 3 + 7 TYPE
where
' TEST
gives the CFA (compilation address) of TEST
>BODY
converts this address to the PFA (address of parameter field), and the rest works as described by Brodie.
Note that the same remark applies to the definition of LABEL . In '83 Forth, LABEL would be:
: LABEL 8 * ['] "LABEL" >BODY 3 + + 8 TYPE SPACE ;
Note the addition of >BODY to convert the compilation address (CFA) to the address of the parameter field.
Internal String Operators
Page 266
MOVE is not part of the '83 standard. It is included in the Uncontrolled Reference Word set, and in Blazin' Forth, but its definition is different from that given by Brodie. Essentially, MOVE is just a smart CMOVE. It examines the addresses, and then picks CMOVE or CMOVE> as appropriate. This prevents accidentally overwriting data, and is in general one less worry for the hacker.
<CMOVE has been renamed CMOVE> in '83. (The idea is to emphasize function, instead of how its done. CMOVE> is usually used to slide strings towards high memory, as Brodie explains. That's why the upward pointing bracket.)
Single-Character Input
Page 268
Following is from the Forth-83 standard:
"...characters received by KEY will not be displayed." Other than this, KEY behaves as described. To duplicate the behaviour of the KEY described by Brodie use the phrase:
KEY DUP EMIT
You might also examine the systems ?KEY routine, which works like KEY , but doesn't wait for the user to type a key.
Page 270
'83 systems return the ASCII code for every key pressed, including the return key. So the definition of BLOCKS in '83 Forth is:
: BLOCKS ( count --- )
SCR @ + SCR @ DO I LIST
KEY 13 = ( CR) IF LEAVE THEN LOOP ;
You can also use Blazin' Forths ?LEAVE :
: BLOCKS ( count --- )
SCR @ + SCR @ DO I LIST
KEY 13 = ( CR) ?LEAVE LOOP ;
String Input Commands, from the Bottom up
page 270
The definition for text is almost the same as Blazin' Forths TEXT. Blazin' Forth's TEXT follows the recommendation of the '83 standard team, and stores the count in the first byte of the string, as does WORD. Otherwise, works the same as described.
Page 271
The example
S0 @ 80 EXPECT
would be
TIB 80 EXPECT
in Forth-83 systems.
Note also that the '83 EXPECT does not store a null at the end of the string. This is one of the major improvements of the '83 standard. '83 standard systems use a count, instead of a delimiter, to determine the end of the input stream. '83 EXPECT stores the count of the characters received in the variable SPAN . More on this later.
Page 275
As you might discover, the FORM LOVE LETTER does not work as described. Certain names will tend to change the text colors, or do other odd things to the display. For example, using:
VITALS BJ,BLUE,FRED
the program will appear to work just fine. But using
VITALS VIOLA,BLUE,FRED
will cause a problem - the text will change white when printing the word VIOLA. Clearly, VIOLA causes a problem, while BJ does not. Why is this?
It has to do with the fact that '83 TEXT stores a count byte in the first byte of PAD. So when you execute the phrase
NAME 14 -TRAILING TYPE
You are TYPEing extra characters. In the case of BJ, you will be typing a ASCII 2, an ASCII B and an ASCII J. On the CBM-64, ASCII 2 is a do-nothing character - doesn't do anything at all. So the program appears to work fine. On the other hand, when NAME contains VIOLA (or any other 5 character name), you will be TYPING an ASCII 5, which is the character to EMIT if you want to change the character color to white.
There are two solutions to this problem. One is to change the definition of VITALS to the following:
: VITALS // modification 1
ASCII , TEXT ( 44) PAD 1+ NAME 14 MOVE
ASCII , TEXT PAD 1+ EYES 12 MOVE
1 TEXT PAD 1+ ME 14 MOVE ;
Which will prevent the count (stored at PAD) from being moved to the variable. However, there is a better solution all together. The solution is to use COUNT. Count takes an address on the stack, and returns the address of the start of the string, and the count byte in the order needed by TYPE. In this version, VITALS remains unchanged (i.e. Just as Brodie gives it in his book), but you will have to replace every occurrence of
<number> -TRAILING TYPE
with the shorter (and faster) phrase
COUNT TYPE
Here is how the beginning of LETTER will look with this second version:
: LETTER PAGE // modification 2
." DEAR " NAME COUNT TYPE ." ,"
CR ." I GO TO HEAVEN WHENEVER I SEE YOUR DEEP "
EYES COUNT TYPE ." EYES. CAN "
and so on. If you try this version, you will notice that it runs much faster, and also occupies less space in the dictionary.
Page 276
The version of GREET given here doesn't work because of a rather subtle problem. Remember that '83 Forth uses a count, and not a delimiter to determine the length of the input stream. So in order to get this definition of GREET to work, in '83 Forth, you must do the following:
: GREET83 CR ." WHAT'S YOUR NAME? " TIB 40 EXPECT
SPAN @ #TIB ! 0 >IN ! 1 TEXT CR ." HELLO, "
PAD COUNT TYPE ." , I SPEAK FORTH. " ;
All of the changes to this definition have been discussed previously, with the exception of the phrase
SPAN @ #TIB !
Since '83 Forth doesn't use a delimiter, but a count which determines the size of the input stream, in addition to reseting the input stream back to the beginning of the TIB with 0 >IN ! , you must also tell Forth how long the current stream is. #TIB controls the length of the input stream when you are not interpreting from disk. SPAN is a variable which is set by EXPECT , and which always contains the number of characters actually received by EXPECT . So by storing the value of SPAN in #TIB , we have told Forth that the input stream is as long as the name the user typed.
Number Input Conversions
Page 277
PLUS will not work as shown, since the version of NUMBER used in Blazin' Forth follows the recommendations of the '83 Standard, and always returns a double number. Use this version of plus on '83 systems:
: PLUS BL WORD NUMBER DROP + ." =" . ;
This just drops the high cell of the double number, which will be zero, if you are entering single length numbers. The word BL in the above definition is a system constant that returns the value for an ASCII blank (32). Generally, it's better to use constants like this when you have them, since they result in memory savings. I just threw it in as an excuse to tell you this.
Page 278
Footnote: See the system documentation for NUMBER PUNCT? and DPL ( which is equivalent to Brodies PTR ). See also the example in this document, under Chapter 7, which explains how to alter the behaviour of NUMBER .
Page 282
Blazin' Forths -TEXT returns TRUE if there is a match, unlike the one Brodie uses, which returns false if there is a match. You will need to keep this in mind if you want to get the file system example in the appendix to work.
Chapter 11
Page 304
Because of the compiler security, the second definition for LIMIT won't work in Blazin' Forth. In many peoples view, this is kind of dubious practice, but if you want to use it, here's how to trick the security:
HERE 10 ALLOT
: LIMIT 2* LITERAL + [ HERE ] ; DROP
This keeps the stack position the same. Another way to do the same thing is:
: LIMIT 2* LIMIT + [ SP@ !CSP ] ;
Page 306
Handy Hint. Blazin' Forth will remain in compile mode until a ; is encountered, or a fatal error occurs. A message like <name> ALREADY EXISTS is not a fatal error. In fact, you can tell the system not to tell you about these errors at all, by typing:
WARNING OFF
To reactivate these non-fatal messages, type
WARNING ON
{$00}Blazin' Forth Documentation, SID Support
SID Chip Support
Blazin' Forth Contains all the words necessary to access the SID (Sound Interface Device) of the C64. Forths speed and flexibility make it ideal for this kind of real time control. Included with the source code is an extensive example of using Blazin' Forth to program music. This is loaded by typing: 105 124 THRU . (Remember, you must type MOUNT before using THRU or LOAD. You need only issue MOUNT once, of course. See MOUNT for more information.)
MUSIC.ON
This word *must* be executed before executing any of the note words. It clears the sound chip, initializes important variables and then starts up a interrupt driven routine that is responsible for timing the voices, and gating off the sounds at the proper time.
MUSIC.OFF
This word should be at the end of the music program. It restores the 64's normal interrupt system, and shuts off SID. Note that peculiar results will occur if these two words are not used in their proper order. (Note: Typing RUN/STOP RESTORE will have the same effect as MUSIC.OFF)
V1 V2 V3
These words set the current voice. For example, typing V1 will cause all of the following commands to affect only voice 1.
ATTACK DECAY SUSTAIN RELEASE
These set the envelope parameters for the sid chip. Each one takes a parameter between 0 and 15. Example:
V1 0 ATTACK 15 DECAY 0 SUSTAIN 0 RELEASE ( setparams for voice 1)
V2 15 ATTACK 0 DECAY 15 SUSTAIN 15 RELEASE ( setparameters for voice 2)
VOLUME
Takes a value between 0 and 15. Note that this command sets the volume for all voices.
15 VOLUME ( set volume for all voices to max)
C D E F G A B C# D# F# G# A# D< E< F< E# G< A< B< R
These words do the actual playing of the notes. Note that the sign "<" by some of the notes is actually the left arrow sign, but EZ-script won't allow that to be entered into a document. It means the notes are flatted. There is also a word R , which acts like a REST. (TIP: You may find music which has long passages of rests for one voice, while the others continue. It is not necessary to code all of these rests. Once R has been executed, the voice will remain off until it is reactivated with one of the note words. This is a great memory and patience saver. The only thing you must be careful of when coding this way is to make sure the rest specified isn't *longer* than the actual rest, since Blazin' Forth will not play any note before its time.)
1/32 .1/32 1/16 .1/16 1/8 .1/8 1/4 .1/4 1/2 .1/2 WHOLE TRIPLET TIE
These words set the rythmic duration of the note values. Note that you need to set a rythmic value for a particular voice only once - setting other rythmic values for other voices will not affect each other.
OC0 OC1 OC2 OC3 OC4 OC5 OC6 OC7
These words set the octaves for each voice. Note that, as for the note length values, once an octave has been set for a voice, it remains set for that voice until you specifically change it - specifying a new octave for a different voice will affect only the new voice, not any of the others. A C64 octave extends from C to B, with OC4 C playing a middle C.
TEMPO
This word sets the tempo and the release time (which is a fraction of the tempo) for each note. The tempo value is stored in MAAZEL, while the amount of time the note spends in its release cycle is stored in REL.TIME. (Note for music hackers: You may tweek the value in REL.TIME after you have set it with TEMPO. For example, if you don't wish the note to be gated off at all, you can store a 0 in REL.TIME, while if you would like the notes in the piece to spend more time in the release cycle, you can increase the value stored here by the system.)
TRIANGLE SAWTOOTH PULSE NOISE SYNCH RING
These words set the waveform for the current voice. Example:
V1 TRIANGLE ( set voice 1 to a triangle wave)
PULSE.WIDTH
This word takes a value on the stack, which sets the pulse width for the current voice. (Note that you must have selected the PULSE waveform for this command to have any effect.) This is a number between 0 and 4095. Typing 2048 PULSE.WIDTH will make a square wave.
Now that we have covered the basics, here are some examples:
MUSIC.ON ( initialize SID and timing routine)
100 TEMPO 15 VOLUME ( set tempo and volume)
V1 TRIANGLE 0 ATTACK 9 DECAY ( set waveform and envelope for voice 1 )
1/4 OC4 ( set quarter notes and octave)
C C# D D# E F F# G G# A A# B OC5 C (play a chromatic scale)
MUSIC.OFF ( turn everything off)
This is the basic form of all music programs in Blazin' Forth. Note that MUSIC.ON is executed before any paramaters are set, and that the tempo, volume, and note lengths are set before any notes are played.
Here is a slightly more complex example. You may enter this interactively, or edit it onto a screen.
: SETTINGS ( setup to play some Bach)
100 TEMPO 15 VOLUME
V1 9 SUSTAIN SAWTOOTH ( voice 1 params)
V2 9 SUSTAIN SAWTOOTH ( voice 2 params) ;
: BACH1 ( two part invention# 1, first measure)
V1 1/16 OC4 ( voice, duration, octave for voice 1)
R C D E F D E C 1/8 G
V2 1/16 OC3 R C V1 OC5 C V2 D E ( play 1/8 notes in V1 against 1/16 in V2)
V1 OC4 B V2 F D V1 OC5 C V2 E C
V1 1/16 D V2 G ;
Note that it was only necessary to set the octave and duration for each voice once. The system keeps track of the settings for each voice for you, so you only need to change them when the music demands it.
You probably also noticed that I left out the MUSIC.ON MUSIC.OFF words. That's because we are going to use BACH1 in another word, in just a moment. You can hear the BACH1 word by typing:
MUSIC.ON SETTINGS BACH1 MUSIC.OFF
or you can make another word:
: PLAY-BACH1 MUSIC.ON SETTINGS BACH1 MUSIC.OFF ;
to do the work for you. You can use BACH1 to get almost two measures of this Invention (with slight apologies to JSB) like this:
: BACH2 MUSIC.ON SETTINGS BACH1 7 TRANSPOSE BACH1 MUSIC.OFF ;
BACH2 will play BACH1 and then repeat it again, a fifth (7 half-steps) higher, which is all Bach really did. Note that we could have easily changed the waveform or the envelope settings for the second measure if we wanted to. The possibilities for programming musical Forth words is limited only by your imagination.
Accessing SID's advanced features:
Blazin' Forth contains words that make it easy to access the special features of the SID chip, such as the filters.
HIPASS LOWPASS BANDPASS NOTCH
These filters specify the type of filter to be used. Note that SID will only allow one type of filter to be selected at once, but this filter will only affect the voices which are routed through it. You may elect to filter all the voices, or only one or two.
FILTER
This word routes the current voice through the filter. For example:
V1 LOWPASS FILTER
Will turn on the lowpass filter and route only voice 1 through it. The other voices are un-affected.
NOFILTER
This word stops the filtering for the current voice. For example:
V1 NOFILTER
Will restore voice 1 to its un-filtered state. Note that the actual filter setting is unaffected by this command.
RESONANCE
This word takes a number between 0 and 15 on the stack, and sets the resonance of the filter, with 15 being maximum resonance. Example:
15 RESONANCE ( set resonance to maximum)
CUTOFF
This word takes a number between 0 and 2047 which sets the cutoff frequency for the current filter.
Example:
Using our BACH1 example from above, we can play around a little with the filters, to see how they change the sounds that come out:
MUSIC.ON SETTINGS ( set it up)
V1 HIPASS FILTER 2000 CUTOFF 15 RESONANCE ( set up a highpass filter for v1)
BACH1 ( play it)
V2 FILTER ( filter both voices)
BACH1 ( play it with both filtered voices)
V1 NOFILTER ( don't filter voice 1)
BACH1
V1 LOWPASS FILTER
( setup a lowpass filter with the same settings, and send v1 thru it)
BACH1 ( play it - note that V2 is still filtered)
V1 NOFILTER V2 NOFILTER BACH1 ( play without filters)
MUSIC.OFF ( shut down everything)
As you can see, it is quite easy to access the SID chip using the words provided with the system.
MISC.
V3OFF
This word will disconnect the audio output of voice 3. It is usually advisable to do this when using the output of voice 3 to modulate the other voices.
OSC3@
This word allows you to read the output of the voice 3's oscillator. This can be used to modulate one of the other voices. Note that a waveform must be selected for voice three in order for this register to output anything other than 0.
ENV3@
Same as OSC3@ , but allows you to read the value of voice 3's envelope generator. Note that envelope parameters must be set, and voice 3 gated in order for this register to return anything other than 0.
TRANSPOSE
Takes a number on the stack, which determines the number of half-steps the following notes will be moved up. Note that only positive values will have any effect. Using TRANSPOSE can save memory (and typing!), since sections which are repeated in a musical work may be replayed in a different key with this word. 0 TRANSPOSE will restore the notes to their normal values.
Voice Modulation:
One of the most fascinating areas to explore with SID is the area of voice modulation. In order to modulate voices, it is necessary to modify the source code to some of the sound words in Blazin' Forth. The heart of the music words is a procedure called PLAY. By modifying this word, it is possible to dynamically change the quality of a note while it is actually being sounded, by modulating it or otherwise tampering. There are several examples of how to do this located on the source disk. Note that these screens should be loaded in the order presented below.
Screens 125-126 contain a demonstration of how to add vibrato effects. It is possible to vary the width and speed of the vibrato, to get many different types. To access this demo, make sure the disk is available (see MOUNT) and then type: 125 126 THRU . When the screens are compiled, the demo is executed by typing PLAY-LIKE-LYNN .
The next 3 screens contain sound effects demonstrations. Screen 127 has the words SIREN and CLAPS. To access these words, type 127 LOAD , and then SIREN or CLAPS .
Screen 128 contains an example of RING MODULATION. Type 128 LOAD and then BELL-SOUNDS.
Screen 129 contains an example of voice synchronization. Type 129 LOAD , and then SYNCH-DEMO.
The SID chip is a fascinating device, and the brief discussion here hardly does it credit. It is possible to generate really remarkable music and sound effects using these words with SID. If you need basic information on things like envelopes, waveforms, and filtering, I would strongly urge you to obtain a copy of the programmers reference quide.
*====================================================================*
*Everything you wanted to know about Forth (but were afraid to ask). *
*Copyright (c) 1986, by Scott Ballantyne.*
*====================================================================*
This file contains a description of how a threaded-code Forth
compiler works, with specific reference to Blazin' Forth. You don't need
to know the stuff in this file, unless you are interested in the particulars
of how Forth compilers work, or are interested in improving or changing one.
Specifically, I wrote the following as an aid to people who might be
trying to understand the source to Blazin' Forth, and it should be considered
part of the documentation for the source files to the system.
To understand this document, you will need a decent knowledge of
Forth. An understanding of pointers won't hurt any either.
You don't need to know machine language to understand the stuff in this file,
but you will, of course, need it to understand the actual source.
I have attempted to provide sample code in hi-level Forth that illustrates
the routines involved. This code is similar, but not exactly the same, as
the actual machine level routines in the actual compiler. In particular,
you should not expect these routines to actually work if you type them into
a Forth system in an attempt to build a "Forth in Forth". They are
provided to add clarity, and that is their only function.
---------------------------------------------------------------------------
------------------------ What is a Virtual Machine? -----------------------
---------------------------------------------------------------------------
A Virtual Machine is a creation, in software, of a piece of hardware.
Note that this hardware does not actually have to exist - it is only within
the last year that an actual hardware Forth computer has been built. Forth
has been around a lot longer than that.
All high level languages are essentially virtual machines, since they
implement instructions which are not part of the actual hardware CPU.
As an example, Forth uses two stacks, a parameter stack, and a return stack.
On an actual hardware Forth computer, the built-in machine language of the
computer would contain instructions for manipulating each stack, and the
pointers to the bottom of each stack. On the 6502, there is actually only
one stack - one or the other of the Forth stacks must be emulated using
software routines. So you could say that one stack is a hardware stack,
and the other stack is a Virtual stack.
As another example, the function call mechanism (how the actual
functions, procedures, or words are eventually caused to execute) of a
high level language is rarely directly supported by hardware instructions.
On the majority of CPU's in use on personal computers today, the only
real function call mechanism implemented in the hardware is the
subroutine call (usually referred to as a JSR or CALL instruction). This
instruction usually only saves the return address automatically. Any other
information or any other method of invoking a subroutine must be done by
software that, essentially, is a software (or virtual) function call, as
opposed to the hardware function call and return. This is particularly true
of threaded code Forth, and the routine which implements this function call
mechanism is called NEXT. Understanding NEXT is the key to understanding
the functioning of the Forth compiler at its lowest level.
--------------------------------------------------------------------------
-------------------------- Introducing NEXT. -----------------------------
--------------------------------------------------------------------------
To understand how NEXT (and the various Forth machine registers that
NEXT uses to do its thing) works, let's first take a quick look at the
structure of a compiled higher level Forth word. It is essentially a list
of addresses:
: EXAMPLE W1 W2 W3 ;
( Standard Forth Header goes here)
Address of W1
Address of W2
Address of W3
Address of EXIT ( ; )
NEXT uses several auxiliary registers to keep track of where the user
program is. On a CPU with many registers, these would be kept in a selected
CPU register. On the 6502, which has only 4 user accessible registers,
these are maintained as virtual registers (page zero locations are used
for greater speed). One of these virtual registers is called the
Interpreter Pointer, or IP for short, and it is responsible for keeping
track of the progress of the current program. When NEXT is entered, in the
course of running a program, the IP will be pointed at the word we want
to execute. NEXT does some stuff (to be described in a moment) to cause the
this word to begin to execute, but before transferring control to this
word, it moves the IP ahead to the next words address, so it will know what
word to run the next time it is called.
Here is a sample execution of EXAMPLE, given above:
IP --> Address of W1 ( NEXT executes W1, first moving IP ahead to W2)
IP --> Address of W2 ( NEXT executes W2, first moving IP ahead to W3)
IP --> Address of W3 ( NEXT executes W3, first moving IP ahead to EXIT)
IP --> Address of EXIT
I like to think of the IP as a kind of Address Slider, that can be moved
ahead or behind to direct the flow of execution of the current program.
Ultimately, of course, NEXT must cause machine language instructions to be
executed, which essentially means changing the hardware program counter of
the CPU to point to the appropriate batch of instructions to be executed.
It does this using another virtual register called the Current Word Pointer,
or W for short. To understand this portion of NEXT, we have to clear up
exactly what we mean by "Address of Word" in the above discussion.
The individual members of the list that makes up a Forth definitions
executable body are the addresses of the code field in the header of the
compiled word. (These "addresses of code fields" will be refered to as
"the execution address" of a word in the rest of this document. When the
term "code field" is used, the reference will be to the actual code field
portion of a dictionary header. Or, at least, that is how I am going to
try to use these terms.)
This execution address (as you may recall) itself stores an address which
points to machine level (assembly language) instructions.
It is these instructions that NEXT causes the CPU to execute, by forcing
the CPU's program counter to the address stored at the execution address of
that word. So we have a couple of levels of indirection here:
The IP points to a location which holds the execution address of a
word.
The execution address pointed to by the location pointed to by the IP
points to executable machine language instructions.
So the full story of NEXT is as follows:
1) NEXT retrieves the value stored at the address in the IP.
2) It saves this value ( the execution address of a word)
in W (the current word pointer).
3) It then moves the IP ahead to point at the next word to be
executed.
4) Finally, it forces the hardware program counter to the value
stored at the address in W, which causes machine level
instructions to execute.
Here is an example of the full execution of a Forth word.
Let's make up some example addresses for our example execution:
Address Contents Description
-----------------------------------------------------------------------
$A000 [$0600] W1's execution address is $A000, and contains $0600.
$B000 [$0700] W2's execution address is $B000, and contains $0700.
$C000 [$0800] W3's execution address is $C000, and contains $0800.
$0900 [$0880] EXIT's execution address is $0900, and contains $0880.
And here is how the compiled EXAMPLE word from earlier looks - let's say
that the body of example starts at $E000:
Address Contents Description
----------------------------------
$E000 [$A000] Compiled W1
$E002 [$B000] Compiled W2
$E004 [$C000] Compiled W3
$E006 [$0900] Compiled EXIT.
So, at the entry to NEXT, the IP will contain $E000.
NEXT fetches the address stored here, and stuffs it into W, so W will now
contain $A000, which is the execution address of W1.
NEXT now increments the IP to point at the next word, so the IP will contain
$E002.
Finally, NEXT forces the program counter of the CPU to the address stored
in the address stored in W. So here's a quickie quiz - what will be the
address in the hardware program counter?
(Answer: $0600 - which is the address of the machine language code for W1).
Here is a quick synopsis of the values stored in the IP, W, and hardware
PC for the execution of EXAMPLE, given above. It might be a good idea
to pause here, and try to run through the rest of the example on your own,
to check your understanding (and the clarity of my explanation) of how
NEXT functions.
Word-to-Execute IP W IP-AT-EXIT PC
----------------------------------------------------------
W1 $E000 $A000 $E002 $0600
W2 $E002 $B000 $E004 $0700
W3 $E004 $C000 $E006 $0800
EXIT $E006 $0900 $E008 $0880
As a final aid to understanding, here is an implementation of NEXT in
hi-level Forth:
: NEXTIP// Get address of IP
@// Get value of IP (address of next word to execute)
@// Get that words execution address
W !// And stuff into the current word pointer.
2 IP +!// Move IP along to next word, for next time.
W @// Get the execution address from W.
@// Get the actual address of the code.
PC !// Force into hardware PC, so that it will execute.
;
Now that you understand NEXT (I hope), and the role of the Forth registers
IP and W, you are in a good position to understand the rest of the
Forth system.
---------------------------------------------------------------------------
---------------- EXECUTE - or how Forth launches programs -----------------
---------------------------------------------------------------------------
You might be wondering at this point exactly how an application gets
launched in the first place. Since NEXT uses the IP, and assumes that the
IP is pointing at a compiled execution address, how do words that you just
type in from the terminal get executed? Obviously, words typed directly
to the interpreter from the terminal don't have an address which is valid
for the IP.
The answer is the Forth word EXECUTE, which takes an execution
address as it's argument. When you type a word to the interpreter that
it can find in the dictionary, it pushes the execution address of the
word onto the parameter stack, and calls EXECUTE. Execute first saves this
execution address in W, and then forces the PC to the address stored in
this execution address, just like the last part of NEXT. Here is EXECUTE
in high level Forth:
: EXECUTE ( execution-address --- )
W !// save in W - then do last part of NEXT
W @ @// get the address of the code to execute
PC !// and execute it.
;
Note that EXECUTE does not call NEXT - it assumes the EXECUTEd word
will be doing that.
At this point you are no doubt wondering how the IP gets initialized at all.
It's not hard to understand, but let's put off a detailed discussion of it
until we talk about the DOCOLON and EXIT routines, a little further on, but
here is a brief hint: When EXECUTE executes your word, there is already a
valid value in the IP - it is pointing somewhere inside INTERPRET. If the
word you are executing is a colon definition, then the first thing it does
is save the current value of the IP, and then changes it to point to itself.
A CODE definition won't change the IP at all (unless the code you write is
supposed to), and so the pointer to inside INTERPRET just hangs around until
the code defintion gets to it's NEXT call, which causes the INTERPRET word
to resume. This will all become clearer when you understand exactly how
DOCOLON and EXIT work.
Incidentally, there is also an EXECUTE inside of the compiler loop - it's
there to handle IMMEDIATE definitions - the ones that execute even when you
are compiling. The logic here is the same as above. The only difference is
that the IP will be pointing somewhere inside ] , instead of inside
INTERPRET.
----------------------------------------------------------------------------
------------------------- How Forth Does Branching -------------------------
----------------------------------------------------------------------------
In our discussion of NEXT, above, we only talked about sequential
execution of words. What happens if we need to branch around words ( as
we do in conditionals like IF) or cause the same words to be executed
repeatedly ( as we do in DO LOOP or BEGIN UNTIL constructs)?
The answer is actually very simple - we just change the IP to point
to the word we want to branch to, and then execute NEXT. If you followed the
above discussion on NEXT, it should be obvious that this causes a complete
diversion of the flow of control for the current word.
When a branch is compiled, two things are done: a special word that
controls the branch is compiled, and the destination address of the branch
is compiled. For example:
: CR'S BEGIN CR AGAIN ;
This word will just print newlines, until a rude action is taken by the
operator to stop it. Here is how the compiled word looks in memory.
(Standard Forth Header goes here)
$A000 CR (execution address of CR)
$A002 BRANCH (execution address of BRANCH)
$A004 $A000 (address to BRANCH to)
$A006 EXIT (execution address of EXIT)
When CR'S executes, NEXT executes CR, and then it executes BRANCH.
BRANCH takes the address immediately following it in memory, in this case
$A000, and stuffs it into the IP. BRANCH then JMP's to NEXT. Since the
IP is once again pointing at CR, (having been changed by BRANCH), NEXT
once again executes CR, and then BRANCH, which causes the IP to be changed,
and so on, forever.
BRANCH is an example of an unconditional branching primitive - it always
branches, no matter what. ?BRANCH is a conditional branching word - it
will branch if the value on the top of the stack is FALSE - otherwise,
no branch takes place. Here is an example of a word that would cause ?BRANCH
to be compiled:
: CR? ( BOOLEAN -- ) IF CR THEN ;
CR? will obviously print a CR if the top of the stack is non-zero, otherwise,
nothing happens. Here is how CR? would look in memory:
(Standard Forth Header)
$A000 ?BRANCH (execution address of ?BRANCH)
$A002 $A006 (destination address of branch)
$A004 CR (execution address of CR)
$A006 EXIT (execution address of EXIT)
In this case, the execution would execute ?BRANCH first, which tests the
value of the top of the stack. Notice that two things can happen here,
BOTH of which will change the IP:
1) If the top of the stack is FALSE, ?BRANCH will force the IP
beyond the branch address, by adding two. This will obviously
cause CR to be executed.
2) If the top of the stack is TRUE, ?BRANCH will act exactly like
BRANCH, and stuff $A006 (the word immediately following ?BRANCH)
into the IP, which will obviously just EXIT the definition.
In any branching word, one or the other of these two things will happen.
All of the branching words are compiled in exactly this way, with the
branching primitive first, and the destination address of the branch
immediately following it in memory. The reason that there are more
branching primitives in Blazin' Forth than just these two has more to do with
entry and exit conditions that it does with the actual branching mechanism.
For example, IF-THEN, IF-ELSE-THEN, BEGIN-UNTIL, BEGIN-WHILE-REPEAT,
BEGIN-AGAIN are all implemented with combinations of ?BRANCH and BRANCH,
since all of these involve boolean testing of the top of the stack.
Things like DO-LOOP and ?DO-LOOP and DO-+LOOP, etc. have additional things
to do, like move the loop parameters to the return stack, add or subtract
the loop index, test the loop index, and clean up the return stack on
the loop exit. But the actual mechanics of branching are exactly the same,
only the entry/exit conditions differ from word to word. Among other
advantages, it makes the compiler code much simpler, since there are fewer
'special cases' to check for.
Once again, as an aid to understanding, here are sample implementations of
BRANCH and ?BRANCH in hi-level Forth. As you read these, keep in mind that
when BRANCH or ?BRANCH is executing, the IP will be pointing at the branch
address - since it gets incremented before the execution of the next word
by NEXT:
: BRANCH ( branch unconditionally STACK: -- )
IP @// Get the value of IP, ordinarily the address of the code
// field of the next word to execute. In this case, it is
// a branch address.
IP @// Get the value stored at the address - which is the
// destination branch value.
IP !// Change the IP to the destination address.
NEXT// and execute.
;
: ?BRANCH ( conditional branch STACK: BOOLEAN -- )
0= IF// test top of stack - if FALSE ( equal to zero)
BRANCH// just execute BRANCH
ELSE// value was TRUE, don't branch.
2 IP +!// move IP over branch address, to next word.
THEN
NEXT// and execute.
;
----------------------------------------------------------------------------
---------------- How Forth Does Nesting - DOCOLON and EXIT -----------------
----------------------------------------------------------------------------
In the above examples there was never any question of remembering where we
came from - the course of execution of the word was changed, and we never
really cared to remember what called what. But what about having one colon
definition calling another one? How does Forth remember where to come
back to when it has finished the called definition?
This is not particularly difficult either. Once again, the IP and W, the
current word pointer, play central roles. In what follows, remember that
W points to the actual address of the word we want to execute, while the IP
points to a memory location which contains the address of the word.
What happens is this:
NEXT starts to execute a colon definition. All colon definitions have the
same address stored at their execution address, which is the address of a
machine language routine called DOCOLON or NEST. It is this routine that
is responsible for saving the current execution environment.
DOCOLON first pushes the current value of the IP (which holds the address
of the word we want to return to) onto the return stack. At this point,
W will be holding the execution address of the new word to execute. We want
to execute the body of this word, so DOCOLON now adds two to the value in W,
which makes it point to the BODY of this definition, and stuffs it into the
IP. DOCOLON now calls NEXT, which causes the new word to execute.
Eventually, NEXT will execute EXIT, which is the word compiled by ; .
EXIT's job is to restore the previous execution environment, and it does
this by very simply by pulling the top of the return stack, and stuffing it
into the IP. It then calls NEXT, which causes the calling word to resume
execution as though nothing had happened.
Here is an example:
: FOOBARCR ;
: COLON-CALL FOOBAR ;
Compiled view of the above:
(Header for FOOBAR)
$A000 DOCOLON (Code field portion of header)
$A002 CR (Body)
$A004 EXIT
(Header for COLON-CALL)
$B000 DOCOLON (Code field portion of header)
$B002 FOOBAR (Body)
$B004 EXIT
And here is a simplified execution of COLON-CALL.
Word-to-Execute IP W IP-AT-EXIT RETURN-STACK
-------------------------------------------------------------------------
FOOBAR $B002 $A000 $B004 XXXXX
DOCOLON $B004 $A000 $A002 $B004
CR $A002 CR's EA $A004 $B004
EXIT $A004 EXIT EA $B004 XXXXX
EXIT (in CALL-COLON) $B004 EXIT EA ----- ------
(NOTE: EA stands for Execution Address.)
Once again, here is a sample implementation in higher level Forth, of
DOCOLON and EXIT:
: DOCOLON IP @// get current value of IP
>R// Save it on return stack
W @// Get execution address of current word.
2+// Convert to Address of body.
IP !// Change IP
NEXT// Execute new word
;
: EXIT R<// Get old IP (saved by DOCOLON)
IP !// Restore
NEXT// Resume execution.
;
Since the most recent caller is always at the top of the return stack, the
Forth system can find it's way through any number of levels of nesting, no
matter how deep. There is no theoretical limit to the depth of nesting of
Forth words, although there is the practical limit of the size of the return
stack.
So how deeply can one nest definitions in Blazin' Forth? Well, the obvious
answer is around 123 levels, since there is an entire page of memory
allocated for the return stack. It is equally obvious that certain actions
can modify this, such as pushing literals to the return stack in your
definitions, or using DO LOOPS, since DO LOOPS store the loop control
information on the return stack.
Less obviously, you should note that CODE definitions do not cause the above
nesting to occur. The majority of the primitives in Blazin' Forth are
CODE definitions, and the desire to maximize the level of nesting was one
of the design considerations that led to this decision.
In practice, I have never even approached the theoretical maximum level for
nesting, much less had a crash that was traceable to return stack overflow,
even when using highly recursive words.
----------------------------------------------------------------------------
------------------------- Forth's DOES> construct --------------------------
----------------------------------------------------------------------------
The implementation of the DOES> feature of Forth is usually one of
the hardest for people to understand. The thing to remember when we get down
to the actual details of the implementation is exactly how the current word
pointer W works. When a word is executing, W will contain the execution
address of that word. Stored at the address in W is the actual address of
the code that is executing. In Forth, we would say that W @ is the execution
address of the word, and W @ @ is the address of the code. Keeping this
in mind will help you to understand what is going on.
First, a quick refresher on what DOES> does. DOES> is possibly the
most unique feature of Forth, since it allows you to extend the actual
Forth compiler to compile new types of words. DOES> words are compiler
words, and as such, they are used to create new words to execute. To help
keep the discussion clear, lets call words which contain DOES> parent words,
and words which are created by DOES> words, child words.
When a parent word executes, it creates a dictionary entry for the
child. When the child executes, it leaves the address of it's body on the
parameter stack, and then executes the hi-level Forth words after DOES> in
the parent word. A common way to teach beginners about DOES> is to redefine
one of the Forth primitives, such as CONSTANT, as a DOES> word. I'll do the
same thing here, but I will also try to explain exactly how these words
do their thing on an implementation level.
: CONSTANT CREATE , DOES> @ ;
Here we have our CONSTANT definition. When CONSTANT (the parent)
executes, it will create a dictionary entry with a standard header (that's
the function of the CREATE in our definition). It then allocates two bytes
of parameter space, and compiles the value on the top of the stack into the
dictionary (that's the function of the , in our definition). The words
following the DOES> don't do anything when CONSTANT executes - they execute
when the child word (the word created by CONSTANT) executes.
When the child executes, it will leave the address of it's body on
the stack, and then the words following the DOES> will be executed. In this
example, there is only the @ - which will replace the address of the BODY
with the value stored there, just like CONSTANT should, and EXIT, which will
return us to wherever we came from.
Thus
10 CONSTANT TEN
creates a dictionary entry for the name TEN, and a 2 byte parameter field for
the value 10, which CONSTANT also stuffs there.
Executing
TEN
will first leave the address of TEN's body on the stack, and then the words
following DOES> (in the parent word CONSTANT) will execute, which result in
the value 10 being left.
Now for the implementation details:
Here is how our definition of CONSTANT would look in the dictionary:
(Preceeded, as always, with the standard Forth header)
$A000 DOCOL (code field portion of header)
$A002 CREATE (execution address)
$A004 , (execution address)
$A006 (;CODE) (execution address)
$A008 JMP DODOES (actual machine language instructions)
$A00B @ (execution address)
$A00D EXIT (execution address)
And here is how the definition of TEN would look:
(Standard dictionary header goes here)
$B000 $A008 (code field portion of header)
$B002 10 (value stored in parameter field)
Ok, here is how it all sorts out. Remember that DOES> is defined as an
IMMEDIATE word, and so it executes when you are compiling. The mysterious
portion of the CONSTANT defintion, above - the (;CODE) and the JMP DODOES
are written into the dictionary whenever DOES> executes.
(;CODE) is an unusual primitive. When it executes, it overwrites the current
contents of the code field of the last word added to the dictionary with
the address of the machine code which follows it in the defintion currently
executing. In our example above, it will cause all words created with
CONSTANT to have a code field whose value is $A008 - the address of the
JMP instruction in CONSTANT. This will obviously cause the JMP DODOES
instruction to be executed each time a word created by CONSTANT is executed.
DODOES is the routine that does the actual magic. It must do three things:
1) It must save the current value of the IP (just like DOCOLON) so
Forth knows how to get back to the caller.
2) It must push the address of the child's body to the stack.
3) It must execute the words following the JMP DODOES in the parent.
Using TEN as an example, DODOES must push the value $B002 to the parameter
stack, and it must then cause the words starting at $A00B to be executed.
Here is how it's done in Blazin' Forth:
When TEN executes, it should be clear that the value stored in the current
word pointer ( W ) is $B000, which is the execution address of TEN. The
IP will be pointing somewhere important, so DODOES first saves it, which it
does exactly like DOCOLON, by pushing it onto the return stack.
Once the IP has been safely tucked away, we have two tasks to perform. We
must push the address of the parameter field of TEN to the stack, and we
must then cause the hi-level Forth words in the DOES> part of CONSTANT to
execute. We can use the value of W to do both these things.
Remember that W, the current word pointer, is currently pointing at the
execution address of TEN, and so contains $B000. So it is a simple matter to
calculate the address of the body of 10 - we just add two to the current
value of W ( which gives us $B002), and push it to the parameter stack.
Now, the value stored at $B000 is $A008, which is the address of the
JMP DODOES instruction in CONSTANT. We want to execute the hi-level Forth
words beyond this instruction - a piece of cake. We simply add 3 ( the size
of an absolute JMP instruction on the 6502 ) to the value stored at the
execution address of the child word TEN, and stuff it into the IP. Once this
has been done, all we need to do is call NEXT, which takes care of everything
else, since we just pointed the IP at the proper spot.
Since we saved the previous value of the IP first off, when the EXIT
at the end of the DOES> stuff is executed, we get returned to whatever called
us.
Once again, here is an example of DODOES in hi-level Forth:
: DODOESIP @ >R// Save current IP on return stack
W @ 2+// Leave address of parameter field on stack
W @ @// Get address of JMP DODOES instruction
3 +// Add in size of JMP absolute instruction
IP !// Set as new execution address
NEXT// and execute it.
;
That wasn't so hard, was it?
----------------------------------------------------------------------------
------------------- LITERALS, CONSTANTS, and VARIABLES ---------------------
----------------------------------------------------------------------------
In this last section, I talk about how Blazin' Forth handles compiled
literals, and how the words defined by CONSTANT, VARIABLE and USER are
implemented.
There are two kinds of literals recognized by Forth, numeric and string.
Numeric literals are compiled automagically, by the compiler loop, while
string literals are compiled by ." (usually).
Numeric literals first. As you probably remember, when you compile a
definition, Forth attempts to look up each word in the definition in the
dictionary. If it finds the word, then it compiles the execution address
of the word into the dictionary (unless the word is defined IMMEDIATE, of
course!). If the word is not found, then it attempts to convert the string
of characters you just fed it into a number. If it succeeds, then it
compiles a special primitive called (LIT) into the dictionary, and
immediately past that, it places the value of your literal. (If it can't
convert it to a number, then it issues the famous "NOT IN CURRENT SEARCH
ORDER" message.)
Here is an example:
: BIG 1000 ;
and here is how it looks in the dictionary:
(standard Forth header)
$A000 (LIT) (start of BIG's BODY)
$A002 1000 (The value of your literal)
$A004 EXIT (EXIT - Tadah!)
(LIT)'s function is to place the value following it in memory on the top
of the parameter stack, and to move the IP over the literal value, to the
next valid Forth word. It's pretty simple in practice, if you remember
that if (LIT) is being executed, the IP must be pointing at the address
of the literal, since it was incremented by NEXT. Here it is as an
example FORTH definition:
: (LIT)( -- 16bit)
IP @ @// Get value of literal to stack.
2 IP +!// Move IP past literal value, to next valid word.
NEXT// and call NEXT
;
Blazin' Forth has a memory saving feature for values that will fit in one
byte. For these values another word, called CLIT is compiled, instead of
(LIT). It works very similarly to (LIT):
: CLIT( --- byte-value)
IP @ C@// get the byte to the parameter stack
1 IP +!// move over byte literal to next valid word.
NEXT// and execute next
;
The case of string literals is very similar. ." is an immediate word which
first compiles (.") . It then searches the input stream for an ending ", and
moves everything before this final quote into the dictionary, with a leading
count byte, as is normal for Forth. It also moves the pointers to the
input stream past the string, so the interpreter won't try to evaluate it.
Here is an example:
: GREETING ." HELLO" ;
And here is how GREETING would look in memory:
(Header)
$A000 (.") (primitive to print the following inline string)
$A002 5 (the length of the string)
$A003 H E L L O (The characters are stored here, one per byte )
$A008 EXIT
The (.") primitive is one of the few low level words in Blazin' Forth that
is actually written in Forth (i.e. it's a colon definition). Since (.")
is a colon definition, this means that when (.") is called, DOCOLON will
save the current value of the IP on the return stack. But, by a pretty
stroke of fate, this will be exactly the address of the string following
(."). To get a little more concrete about it:
When Greeting executes, the IP will eventually contain the value $A000. This
will cause NEXT to execute (."), but NEXT will first, as always, bump the
IP to $A002 (the start of the inline string). When (.") executes, since it
is a colon definition, DOCOLON will push $A002 (the current IP) to the
return stack, and then enter the definition. So at entry, we have the
address of the string on the return stack. All we have to do is retrieve the
address, use COUNT and TYPE to display it, and adjust the return address
on the stack before we exit. Once the return address has been adjusted and
placed back on the stack, EXIT will return us to the word past the end of
the inline string.
Here is (."), just as it appears in the Blazin' Forth:
: (.") ( --- )
R@( get string address from return stack)
COUNT ( get the count byte, adjust address )
DUP 1+( total length of string, including count byte)
R> +( get address, move past end of string)
>R( and restore, for EXIT)
TYPE( the string)
;( and return, using adjusted address as return)
So much for literals.
Constants and variables (including USER variables) run time action is
determined by the routines pointed to by their code fields. There is no
special primitives compiled, as there is with the literals.
Here is a short run down of the actions of each:
Constants place the value stored in their body on the parameter stack.
Variables place the address of their body on the parameter stack.
User variables place the address of the associated variable on the stack. The
actual value stored in the parameter field is an offset from a base address.
Armed with your present knowledge of the IP and W, understanding these
definitions should be a snap. They all work very much the same. We start with
variable, since it's the simplest.
When the code field of a variable (or constant or USER) is executed, W
will contain the execution address (the address of the code field) of the
word in question. So it's easy: take the value in W, add two, and leave that
value on the stack. Here is a hi-level definition of DOVARIABLE:
: DOVARIABLE ( -- address)
W @// Get the execution address of this variable
2+// Add two to get the body.
NEXT// and that's it!
;
Constants are very similar to variables - the only difference is the extra
step required to retrieve the value in the constants body. Here is a hi-level
definition of DOCONSTANT:
: DOCONSTANT ( -- value)
W @ 2+// As in variable - get the address of the body.
@// Get the value stored there.
NEXT
;
USER variables are very similar to constants. The only addition here is that
we add the base address of the user area to the value stored in the body of
the user variable.
: DOUSER ( -- address )
W @// get execution address of this user variable
2+ C@// get offset - we only use byte offsets in Blazin' Forth.
UP @// get base address of user area
+// add to offset to get actual address of variable
NEXT
;
Here is a question for those who want to test their general comprehension
of the topics discussed here. Why can't we use the IP instead of W in the
definitions of VARIABLE, CONSTANT, and USER ?
Answer:
Aside from making the definition more complex, it would be impossible to
retrieve the addresses of variables, or the values of constants, when we
are typing their names directly into the
er from the terminal!
Remember that the interpreter launches programs by stuffing the
execution address of a word into W. In the following situation, there is
no way to get from the address of the IP to the address of the parameter
field:
VARIABLE BLETCH
BLETCH . XXXX
since the IP is still pointing somewhere inside INTERPRET. The only pointer
that is valid to code such as DOVARIABLE in all cases is W.
* * * * * * * * * * * * * * * *
* *
* Blazin' Forth for CBM-64 *
* *
* System Documentation *
* *
* * * * * * * * * * * * * * * *
This documentation and the software it describes are
Copyright (C) 1985 by Scott Ballantyne.
Distribution on a not for profit basis is encouraged.
Sale or Resale of this manual or software is not allowed.
I would like to acknowledge the following people, who contributed time, support or knowledge to this effort:
Glen Haydon, author of MVP-FORTH, whose book ALL ABOUT FORTH was my constant companion during the early stages of this project.
Henry Laxen, Forth Wizard and writer. Many of the best features of this compiler are due to his ideas, and his columns in Forth Dimensions are among the best and most creative writing on Forth I have ever seen. In particular, Laxen is the creator of the DEFER IS concept.
To RMS and WRG, wherever you are, thanks for everything you taught me.
Thanks to Chris M. A friend indeed.
Special thanks to BJ, who knows what she did.
Thanks to all those who put up with various versions of this compiler, and suffered almost as much as I did while debugging it.
CBM C64 C128 and Commodore-64 are trademarks of Commodore Business Machines.
This software is dedicated to the memory of Leonard Rose.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment