The word EMIT takes a single ASCII representation on the stack, using the low-order byte only, and prints the character at your terminal. For example, in decimal:
65 EMIT↵ A ok
66 EMIT↵ B ok
The word TYPE prints an entire string of characters at your terminal, given the starting address of the string in memory and the count, in this form:
( addr u -- )
We’ve already seen TYPE in our number-formatting definitions without worrying about the address and count, because they are automatically supplied by #>.
Let’s give TYPE an address that we know contains a character string. Remember that the starting address of the terminal input buffer is returned by TIB? Suppose we enter the following command:
TIB #TIB @ TYPE
This will type 15 characters from the terminal input buffer, which contains the command we just entered:
TIB #TIB @ TYPE↵ TIB #TIB @ TYPE ok
Let’s digress for a moment to look at the operation of .”. At compile time, when the compiler encounters a dot-quote, it compiles the ensuing string right into the dictionary, letter-by-letter, up to the delimiting double-quote. To keep track of things, it also compiles the count of characters into the dictionary entry. Given the definition
: TEST ." sample " ;
and looking at bytes in the dictionary horizontally rather than vertically, here is what the compiler has compiled:
If we wanted to, we could type the word “SAMPLE” ourselves (without executing TEST) with the phrase
' TEST >BODY CELL+ 1+ 7 TYPE
' TEST >BODY
gives us the body address of TEST,
offsets us past the address and the count, to the beginning of the string (the letter “s”), and
types the string “sample.”
That little exercise may not seem too useful. But let’s go a step further.
Remember how we defined LABEL in our egg-sizing application, using nested IF…THEN statements? We can rework our definition using TYPE. First let’s make all the labels the same length and “string them together” within a single definition as a string array. (We can abbreviate the longest label to “XTRA LRG” so that we can make each label eight characters long, including trailing spaces.)
: "LABEL" ." REJECT SMALL MEDIUM LARGE XTRA LRGERROR " ;
Once we enter
' "LABEL" >BODY CELL+ 1+
to get the address of the start of the string, we can type any particular label by offsetting into the array. For example, if we want label 2, we simply add sixteen (2 x 8) to the starting address and type the eight characters of the name:
16 + 8 TYPE
Now let’s redefine LABEL so that it takes a category-number from zero through five and uses it to index into the string array, like this:
: LABEL 8 * ['] "LABEL" >BODY CELL+ 1+ + 8 TYPE SPACE ;
Recall that the word [‘] is just like ‘ except that it may only be used inside a definition to compile the address of the next word in the definition (in this case, “LABEL”). Later, when we execute LABEL, bracket-tick-bracket followed by to-body will push the body address of “LABEL” onto the stack. The number corresponding to CELL+ 1+ is added, then the string offset is added to compute the address of the particular label name that we want.
This kind of string array is sometimes called a “superstring.” As a naming convention, the name of the superstring usually has quotes around it. Note that this method is in practice never used, as the same result can be had with the completely portable ANS Forth word C”, as follows:
: "LABEL" C" REJECT SMALL MEDIUM LARGE XTRA LRGERROR " ;
: LABEL 8 * "LABEL" 1+ + 8 TYPE SPACE ;
Our new version of LABEL will run a little faster because it does not have to perform a series of comparison tests before it hits upon the number that matches the argument. Instead it uses the argument to compute the address of the appropriate string to be typed.
Notice, though, that if the argument to LABEL exceeds the range zero through five, you’ll get garbage. If LABEL is only going to be used within EGGSIZE in the application, there’s no problem. But if an “end user,” meaning a person, is going to use it, you’d better “clip” the index, like this:
: LABEL 0 MAX 5 MIN LABEL ;
( addr u — )
Transmits u characters, beginning at address, to the current output device.
Outputting Strings from Disk
We mentioned before that the word BLOCK copies a given block into an available buffer and leaves the address of the buffer on the stack. Using this address as a starting-point, we can index into one of the buffer’s 1,024 bytes and type any string we care to. For example, to print line 0 of block 1, we could say (assuming you’ve executed USE blocks.forth)
CR 1 BLOCK 64 TYPE↵
To print line eight, we could add 512 (8 x 64) to the address, like this:
CR 1 BLOCK 512 + 64 TYPE
Before we give a more interesting example, it’s time to introduce a word that is closely associated with TYPE.
( addr u1 — addr u2 )
Eliminates trailing blanks from the string that starts at the address by reducing the count from u1 (original byte count) to u2 (shortened byte count).
Handy Hint: A Random Number Generator
This simple pseudo-random number generator can be useful for games, although for more sophisticated applications such as simulations, better versions are available.
( Random number generation -- High level )
VARIABLE rnd HERE rnd !
: RANDOM rnd @ 31421 * 6927 + DUP rnd ! ;
: CHOOSE ( u1 -- u2 ) RANDOM UM* NIP ;
where CHOOSE returns a random integer within the range 0 = or < u2 < u1.
Here’s how to use it:
To choose a random number between zero and ten (but exclusive of ten) simply enter
and CHOOSE will leave the random number on the stack.
-TRAILING can be used immediately before the TYPE command so that trailing blanks will not be printed. For instance, inserting it into our first example above would give us
CR 1 BLOCK 64
The following example uses TYPE
16 CHOOSE 64 *
2 BLOCK +
CR 64 -TRAILING
Internal String Operators
The commands for moving character strings or data arrays are very simple. Each requires three arguments: a source address, a destination address, and a count.
( addr1 addr2 u — )
Copies a region of memory u bytes long, byte-by-byte beginning at addr1, to memory beginning at addr2. The move begins with the contents of addr1 and proceeds toward high memory.
( addr1 addr2 u — )
If u is greater than zero, copy u consecutive characters from the data space starting at c-addr1 to that starting at c-addr2, proceeding character-by-character from higher addresses to lower addresses.
( addr1 addr2 u — )
After this move, the u bytes at addr2 contain exactly what the u bytes at addr1 contained before the move (no “clobbering” occurs).
Notice that these commands follow certain conventions we’ve seen before:
When the arguments include a source and a destination, the source precedes the destination.
When the arguments include an address and a count (as they do with TYPE), the address precedes the count.
And so with these three words the arguments are
( source destination count -- )
To move the entire contents of a buffer into the PAD, for example, we would write
210 BLOCK PAD 1024 CMOVE
although on cell-address machines the move might be made faster if it were cell-by-cell, like this:
210 BLOCK PAD 1024 MOVE
The word CMOVE> lets you move a string to a region that is higher in memory but that overlaps the source region.
If you were to use CMOVE, the first letter of the string would get copied to the second byte, but that would “clobber” the second letter of the string. The final result would be a string composed of a single character.
Using CMOVE> in this situation keeps the string from clobbering itself during the move.
You probably notice that CMOVE can be used to fill an array with a certain byte. On older systems the word FILL, which we introduced earlier, may have been defined using this trick. On modern Forths it is recommended to explicitly use FILL, if fill is what you want to do. For example, to store blanks into 1024 bytes of the pad, we say
PAD 1024 CHAR BL FILL
The word KEY awaits the entry of a single key from your terminal keyboard and leaves the character’s ASCII equivalent on the stack in the low-order byte.
To execute it directly, you must follow it with a return, like this:
The cursor will advance a space, but the terminal will not print the “ok”; it is waiting for your input. Press the letter “A,” for example, and the screen will “echo” the letter “A,” followed by the “ok.” The ASCII value is now on the stack, so enter .:
KEY A↵ ok
.↵ 65 ok
This saves you from having to look in the table to determine a character’s ASCII code.
You can also include KEY inside a definition. Execution of the definition will stop, when KEY is encountered, until an input character is received. For example, the following definition will list a given number of blocks in series, starting with the current block, and wait for you to press any key before it lists the next one:
: BLOCKS ( count -- )
SCR @ + SCR @ DO I LIST KEY DROP LOOP ;
In this case we drop the value left by KEY because we don’t care what it is.
Or we might add a feature that allows us either to leave the loop at any time by pressing return or to continue by pressing any other key, such as as space. In this case we will perform a conditional test on the value returned by KEY.
13 CONSTANT #EOL
: BLOCKS ( count -- ) SCR @ + SCR @ DO I LIST KEY #EOL = ( cr) IF LEAVE THEN LOOP ;
Note that in some Forth systems, the carriage-return key is received as a linefeed (10) or as a null (zero).
( — char )
Returns the ASCII value of the next available character from the current input device.
String Input Commands, from the Bottom up
There are several words involved with string input. We’ll start with the lower-level of these and proceed to some higher-level words.
( addr u1 — u2 )
Receives u1 characters (or until carriage return) from the terminal keyboard and stores them, starting at the address. The count of received characters is returned.
( char — addr )
Parses one word from the input stream, using the character (usually blank) as a delimiter. Moves the string to the address HERE with the count in the first byte, leaving the address on the stack.
The word ACCEPT stops execution of the task and waits for input from your keyboard. It expects a given number of keystrokes or a carriage return, whichever comes first. The incoming text is stored beginning at the address given as an argument, the count of received characters is returned on the stack.
For example, the phrase
TIB 80 ACCEPT
will await up to eighty characters and store them in the Terminal Input Buffer (TIB). (Storing directly in the TIB is not standard, but e.g. iForth has no problem with this tradition.)
This phrase is the one used in the definition of QUIT to get the input for INTERPRET.
Let’s move on to the next higher-level string-input operator. We’ve just explained that QUIT contains the phrase
... TIB 80 ACCEPT #TIB ! INTERPRET ...
but how does the text interpreter scan the terminal input buffer and pick out each individual word there? With the phrase
WORD scans the input stream looking for the given delimiter, in this case space, and moves the sub-string into a different buffer of its own, with the count in the first byte of the buffer. Finally, it leaves the address of the buffer on the stack, so that INTERPRET (or anyone else) knows where to find it. WORD’s buffer usually begins at HERE, so the address given is HERE.
WORD looks for the given delimiter in the terminal input buffer, and moves the sub-string to WORD’s buffer with the count in the first byte.