It has been mentioned that on some Forth systems a zero is stored in the last byte of a screen of source, and possibly in the text input buffer, to signal termination of compiling or interpreting. Since I've seen no mention of this in the Forth-83 standard, I will describe how Fleet Forth interprets/compiles text.
Here is Fleet Forth's QUIT loop:
Code:
: QUIT ( -- )
[COMPILE] [
BEGIN
RP! CR QUERY INTERPRET
STATE @ 0=
CS-DUP UNTIL // CONTINUE
." OK"
AGAIN ; -2 ALLOT
Notice that QUIT doesn't directly clear BLK to zero. QUERY does:
Code:
HEX
: QUERY ( -- )
'TIB IS TIB TIB 50 EXPECT
SPAN C@ DUP #TIB !
1+ CHARS +! BLK OFF >IN OFF ;
'TIB is a metacompiler macro that evaluates to $2A7, the starting address for the text input buffer. QUERY sets the system value TIB (actually a constant, the system is in RAM) to the correct address and reads in characters with EXPECT . The value returned by SPAN is stored in #TIB and CHARS is updated. CHARS is used to keep track of the number of characters sent to a line for words that use text formatting. BLK and >IN are zeroed.
EXPECT stores text up to 80 characters or until a carriage return is encountered. It does not store the carriage return. It does not store a zero as an end of string marker.
INTERPRET , which interprets or compiles depending on the STATE , uses WORD to parse the text stream. WORD uses 'STREAM to return the address of the text stream.
Code:
: 'STREAM ( -- ADR U )
BLK @ ?DUP
IF
BLOCK B/BUF
ELSE
TIB #TIB @
THEN
DUP >IN @ UMIN DUP NEGATE
UNDER+ UNDER+ ;
'STREAM returns the address of the current position in the text stream, as indicated by the offset >IN , and the number of bytes of text stream yet to parse. When the text stream is exhausted (when >IN is equal to or greater than the size of the text stream) 'STREAM returns a text stream size of zero. 'STREAM uses UMIN to return the unsigned minimum of the text stream size and >IN thus treating both sizes as unsigned numbers.
WORD uses >HERE to copy the parsed text to HERE. >HERE takes an address and a count and returns HERE . It places the count at HERE followed by the text at the given address and then a trailing blank. If the text stream is exhausted, WORD supplies an address just past the text stream and a count of zero to >HERE . >HERE places the zero at HERE and the trailing blank.
When INTERPRET executes FIND with the address of HERE , FIND finds an entry in the Forth vocabulary with a count of zero and a blank for its name. This word is an alias for EXIT which is immediate. Executing EXIT or its alias is all that is needed to exit INTERPRET and resume the quit loop.
Loading a block is similar. Here is the definition of LOAD
Code:
: LOAD ( U -- )
0 SWAP LINELOAD ;
and LINELOAD
Code:
: LINELOAD ( LINE# BLK# -- )
?DUP 0=
ABORT" CAN'T LOAD 0"
BLK 2@ 2>R
BLK ! C/L * >IN !
INTERPRET 2R> BLK 2! ;
Note: BLK is a regular variable (not a user variable) but it has two cells. >IN is actually a constant that points to the second cell. It behaves just like a variable with its data just after BLK's data.
LOAD first saves the values of BLK and >IN , by way of LINELOAD , and gives them new values. INTERPRET is then executed. Once again, when INTERPRET executes the alias for EXIT it exits. The original values of BLK and >IN are restored and interpretation resumes at the terminal.
The system doesn't see individual lines in a source block. Those are for readability. The system effectively "sees" the screen as a 1024 byte string.
EVALUATE interprets/compiles strings. It takes an address and a count. The string to be evaluated, like a screen or the terminal input buffer, does not need a terminating zero. Here is the source:
Code:
: EVALUATE ( ADR U -- )
BLK 2@ 2>R
TIB #TIB @ 2>R
#TIB ! (IS) TIB
BLK OFF >IN OFF
INTERPRET
2R> #TIB ! (IS) TIB
2R> BLK 2! ;
As with LOAD and LINELOAD , EVALUATE saves the original values of BLK and >IN . It also saves the values of TIB and #TIB . It sets TIB to the address of the string to be evaluated and sets #TIB to the count. It clears BLKand >IN .
Once INTERPRET exits, EVALUATE restores the values that were saved.
EVALUATE , like LOAD , is nestable. An evaluated string can load a block and a screen (text in a block) can evaluate a string. They can both be mutually nested.
The inclusion of EVALUATE is why QUERY sets TIB to the proper address.
Loading source from a file is easy enough in Fleet Forth. Here is one way to implement Ansi Forth's INCLUDED in Fleet Forth:
Code:
: INCLUDED ( ADR U -- )
DR# DUP DUP OPEN IOERR
BEGIN
DR# CHKIN IOERR
-2 BUFFER DUP B/BUF (EXPECT)
CLRCHN
STATUS >R
SPAN @ EVALUATE
." ."
R> DONE? OR
UNTIL
DR# CLOSE ;
The first line opens a file on the current disk drive and checks for an I/O error. This disk is not a disk for Forth blocks.
The first line after BEGIN redirects input from this disk drive and checks for an I/O error.
BUFFER is used to return the address of a block buffer. Since it doesn't read from external storage, as long as no blocks are in the buffer this is perfectly safe. It would also be safe if blocks were being accessed from a drive other than the one with the sequential file on it.
Each line is read in with EXPECT's vector, (EXPECT) .It can be used because each line is terminated with a carriage return ($0D).
CLRCHN clears the I/O back to the defaults until the next line is read. A decimal point is displayed for each line loaded just in case it is a large file.
Each line is evaluated until the end of file is reached or until the user decides this file doesn't need to finish loading, perhaps it was the wrong one.
Only when loading a file is there something new in the data. A carriage return terminates each line. In this case the lines are real. The file could be quite large and it would be difficult (on a C64) to read in the entire file as one really long line. As is, this version of INCLUDED can only handle files with lines that are no longer than 1024 bytes.