Chapter 16: The System And Its Environment

The Endless Interpreter

Upon start-up, StrongForth enters the interpreter loop, which repeatedly executes the following steps:

This is what QUIT does. The semantics and the implementation of QUIT is not different from any other Forth system. Actually, QUIT performs some initialization work before entering the interpreter loop, as specified in the ANS Forth standard:

The implementation of QUIT is straightforward:

: QUIT ( -- )
  RP0 RP!
  POSTPONE [ LOCALS!
  NULL FILE TO SOURCE-ID
  BEGIN REFILL
  WHILE INTERPRET PROMPT
  REPEAT BYE ;

In StrongForth, QUIT performs one additional initialization step, which is the initialization of the local name space. PROMPT displays the OK prompt if in interpretation state:

DEFER PROMPT ( -- )

:NONAME ( -- )
  STATE @ INVERT IF ."  OK" CR THEN ; IS PROMPT

Since it's a deferred definition, you can change StrongForth's prompt to whatever you prefer. For example, you might consider it being useful to display the contents of the data type heap:

:NONAME STATE @ IF ." Compiler" ELSE ." Interpreter" THEN
."  data type heap: "
DEPTH IF POSTPONE .S ELSE ." <empty>" THEN CR ; IS PROMPT
Interpreter data type heap: <empty>
8 BASE
Interpreter data type heap: UNSIGNED DATA -> UNSIGNED

Once QUIT cannot refill the input buffer, it simply exits StrongForth by executing BYE. The semantics of BYE is identical to that of the corresponding ANS Forth word:

BYE ( -- )

Since input provided by the user input device typically never ceases, QUIT is actually an endless loop. But it is not only used upon system startup. QUIT may also be used as a simple means for suppressing the OK prompt, and for error recovery. The OK prompt is always displayed after successful interpretation of the input buffer. However, if QUIT is the last word to be interpreted, a new interpreter loop is started before the current one can be finished:

." Stay in the same interpreter loop."
Stay in the same interpreter loop. OK
." Start a new interpreter loop." QUIT
Start a new interpreter loop.

Error Handling

ABORT extends the semantics of QUIT by emptying the data stack as well:

:NONAME ( -- )
  POSTPONE [ SP0 SP! DTP! CR QUIT ; IS ABORT

At this point, we can see a small difference to other Forth systems. In StrongForth, emptying the data stack requires emptying the data type heap as well. This is done by DTP!. Executing [ before DTP! ensures that DTP! is applied to the interpreter data type heap. Note also that ABORT is a deferred definition, because it might become necessary to extend the semantics of this word with additional tasks, like initializing the hardware floating-point unit.

ABORT" is an immediate word that parses a string at compile time. Its runtime semantics is performed by the internal word (ABORT"):

: (ABORT") ( SINGLE CCONST -> CHARACTER UNSIGNED -- )
  ROT IF TYPE ABORT ELSE DROP DROP THEN ;

: ABORT" ( -- )
  ?COMPILE POSTPONE " POSTPONE (ABORT") ; IMMEDIATE
" is itself an immediate word that parses a string terminated by " (double-quote), and compiles it as a string literal to be used by (ABORT"). During runtime, (ABORT") checks the value of the parameter of data type SINGLE. If this parameter is non-zero, (ABORT") displays the character string before executing ABORT. Here's a simple example:

: MAKE-UNSIGNED ( SIGNED -- UNSIGNED )
  DUP 0< ABORT" Negative value!" CAST UNSIGNED ;
 OK
+56 MAKE-UNSIGNED .
56  OK
-56 MAKE-UNSIGNED .
Negative value!
SEE MAKE-UNSIGNED
: MAKE-UNSIGNED ( SIGNED -- UNSIGNED )
  DUP 0< " Negative value!" (ABORT") ;  OK

Whenever an ambiguous condition is recognized, StrongForth throws an exception by executing THROW with an appropriate error code. THROW is a deferred definition. As long as the Exception word set has not been loaded, THROW simply executes ERROR. ERROR is not part of the ANS Forth specification, although it is available in many Forth systems:

DEFER THROW ( SIGNED -- )

: ERROR ( SIGNED -- )
  DUP
  IF CR SOURCE DROP >IN @ DECIMAL -TRAILING TYPE
     ."  ? ERROR " . CR POSTPONE .S ABORT
  ELSE DROP
  THEN ;
  
' ERROR IS THROW

ERROR expects an error code of data type SIGNED on the stack. If the error code is non-zero, ERROR displays an error message and then executes ABORT. The error message consists of three parts:

The most common error message you will see is probably the one that indicates an undefined word:

STATE BASE - .

STATE BASE - ? ERROR -13
DATA -> FLAG DATA -> UNSIGNED

In chapter 17, a version of ERROR is presented that uses blocks to display a more descriptive error message. With this version, the same error condition would produce the following error message.

STATE BASE - .

STATE BASE - ? undefined word
DATA -> FLAG DATA -> UNSIGNED

The reason why you'll see this specific error message more often than in an ANS Forth system is simply the fact that finding a word in StrongForth's dictionary is bound to an additional condition. In an ANS Forth system, the only condition for finding a word is that its name matches the parsed name. In StrongForth, a word will only be found if its name matches and its input parameters match the contents of the data type heap. In the above example, none of the overloaded versions of - fits:

- ( CADDRESS 1ST -- INTEGER )
- ( ADDRESS -> DOUBLE 1ST -- INTEGER )
- ( ADDRESS -> SINGLE 1ST -- INTEGER )
- ( ADDRESS 1ST -- INTEGER )
- ( CFAR-ADDRESS INTEGER -- 1ST )
- ( FAR-ADDRESS -> DOUBLE INTEGER -- 1ST )
- ( FAR-ADDRESS -> SINGLE INTEGER -- 1ST )
- ( FAR-ADDRESS INTEGER -- 1ST )
- ( INTEGER-DOUBLE SIGNED -- 1ST )
- ( INTEGER-DOUBLE UNSIGNED -- 1ST )
- ( INTEGER-DOUBLE INTEGER-DOUBLE -- 1ST )
- ( CADDRESS INTEGER -- 1ST )
- ( ADDRESS -> DOUBLE INTEGER -- 1ST )
- ( ADDRESS -> SINGLE INTEGER -- 1ST )
- ( ADDRESS INTEGER -- 1ST )
- ( INTEGER INTEGER -- 1ST )

Even the third version in this list fails to match, because the second address has not the same data type as the first one.

Environment Queries

ENVIRONMENT? is one of those words with ambiguous stack diagrams. ANS Forth specifies

( c-addr u -- false | i*x true )

as the stack diagram of ENVIRONMENT?. In StrongForth, a possible approximation is

ENVIRONMENT? ( CDATA -> CHARACTER UNSIGNED -- CONST FLAG )

Only one attribute of data type CONST is returned for all environment queries. CONST usually has to be casted to a more specific address in order to obtain the actual parameter, like in the following example:

PARSE-WORD /PAD ENVIRONMENT? . -> UNSIGNED @ .
TRUE 84  OK

Furthermore, StrongForth's version of ENVIRONMENT? returns a dummy address even if the keyword is unknown. However, the value of the dummy address is undefined:

PARSE-WORD /XYZ ENVIRONMENT? . .
FALSE 11392  OK

ANS Forth does not specify whether environment queries can be added to an existing system, and how this should be done. Neither does the standard specify how to change the parameters if this becomes necessary. In StrongForth, all environment query strings are definitions in the ENVIRONMENT-WORDLIST word list, and all those definitions are constants defined by CONSTANT. After loading the Search-Order word set, you can access them in the usual way:

185 192 THRU \ load Search-Order word set
 OK
ALSO ENVIRONMENT
 OK
/PAD .S .
UNSIGNED 84  OK
DOUBLE-EXT .S .
FLAG TRUE  OK

Now it should be pretty obvious how you can add your own environment query strings and how you can change their values. All you have to do is adding a constant to the ENVIRONMENT-WORDLIST word list:

ALSO ENVIRONMENT DEFINITIONS
 OK
-12345 CONSTANT MY-QUERY
 OK
ONLY FORTH DEFINITIONS
 OK
PARSE-WORD MY-QUERY ENVIRONMENT? . -> SIGNED @ .
TRUE -12345  OK

Using SET-CURRENT, you can even add new environment queries without loading the Search-Order word set. This technique is used in StrongForth's own libraries, because they cannot assume that the Search-Order word set is available:

ENVIRONMENT-WORDLIST SET-CURRENT
TRUE CONSTANT FACILITY
TRUE CONSTANT FACILITY-EXT
FORTH-WORDLIST SET-CURRENT

And finally, changing the value of an environment query is simple enough:

+23456 PARSE-WORD MY-QUERY ENVIRONMENT? . -> SIGNED !
TRUE  OK
PARSE-WORD MY-QUERY ENVIRONMENT? . -> SIGNED @ .
TRUE 23456  OK

Of course, you shouldn't do that in an application at runtime, because your CONST memory area might be read-only.

The definitions of the two overloaded versions of ENVIRONMENT? are straight-forward:

: ENVIRONMENT? ( CDATA -> CHARACTER UNSIGNED -- CONST FLAG )
  FALSE CODE-FIELD ENVIRONMENT-WORDLIST SEARCH
  0<> SWAP >BODY SWAP ;

: ENVIRONMENT? ( CCONST -> CHARACTER UNSIGNED -- CONST FLAG )
  TRANSIENT ENVIRONMENT? ;

The second version, which expects the environment query string to be located in the CONST memory area, allows to compile environment queries without having to move the string to a transient memory area:

: LOCALS-WORDSET? ( -- )
  ." StrongForth " " LOCALS" ENVIRONMENT?
  SWAP -> FLAG @ AND
  IF ." supports "
  ELSE ." does not support "
  THEN ." the LOCALS word set." ;
 OK
TEST
StrongForth supports the LOCALS word set. OK

You will probably use the phrase SWAP -> FLAG @ AND pretty often, because many environment queries, especially those for querying the presence of word sets, return a flag. Therefore, you might consider the following definition as being useful:

: WORDSET? ( -- FLAG )
  PARSE-WORD ENVIRONMENT? SWAP -> FLAG @ AND ;
 OK
WORDSET? DOUBLE-EXT .
TRUE  OK

Dr. Stephan Becher - January 4th, 2008