! | M: ( Value, Address -> ) R: ( -> ) | core |
|---|
! stores a cell from the math stack into memory at a specified address. It pops the address and the value from the math stack then stores the value in memory at the address.
$ | M: ( -> ) R: ( -> ) INBUF is completely consumed, which implies that IN is updated. DP is updated. | compiling |
|---|
$ compiles a counted string into the dictionary. A new word is created to contain the string; the name of this word is taken from the next token in INBUF. The contents of the string are taken from the remainder of INBUF.
$ uses CREATE to create the dictionary entry for the word. The code field of the new word is pointed at ($).
Because GETLINE only stores printing characters and spaces in INBUF, it is not possible to include control characters in a counted string created by $.
$, | M: ( Address, Count -> ) R: ( -> ) DP is updated. | compiling |
|---|
$, appends a counted string to the dictionary. Each byte of the string is appended to the dictionary using B,. After the string is copied, it is padded with bytes of zero until the name ends on an even cell boundary. At that even cell boundary, a cell containing the length of the string (including the zeros appended to reach alignment) is appended to the dictionary. The address and count of the string are popped from the math stack.
$, is used to create the name portion of a dictionary entry. A dictionary entry is referred to by its code field address, which comes after the word's name in the dictionary. Placing the length of the string at the end of the name allows the beginning of the name to be quickly and easily found by looking backwards a fixed amount from the code field address.
Names in the dictionary are limited to being at most 63 characters long to allow space for the IMMEDIATE flag as well as a bit to mark the first word in the dictionary; it is the need for these two bits in addition to the name length which constrains the byte size to be at least eight bits. $, does not enforce this limitation; it assumes the caller has verified the length of the string.
($) | M: ( -> Address, Length ) R: ( -> ) | core |
|---|
($) knows how to execute definitions created by $. Such definitions contain a counted string of bytes usually intended for display via TYPE. ($) places the address of the first byte in the string and the number of bytes contained in the string on the math stack.
$= | M: ( Address 1, Count 1, Address 2, Count 2
-> 1 ) if the strings are equal. M: ( Address 1, Count 1, Address 2, Count 2 -> 0 ) otherwise R: ( -> ) | core |
|---|
$= compares two counted strings, pushing a flag which indicates whether they are identical. The addresses and counts of the strings to be compared are popped from the math stack.
To be considered identical, the counted strings must be the same length and each byte in the counted string must have the same value.
(+LOOP) | M: ( Increment -> ) R: ( Limit, Index -> ) if the loop exits R: ( Limit, Index -> Limit, Index + Increment ) otherwise | core |
|---|
(+LOOP) knows how to execute the bottom of a do loop which increments its index by the amount specified on the math stack. The amount by which the index is to be incremented is popped from the math stack and added to the index. If the updated index is less than the limit, the loop needs to be repeated; (+LOOP) branches to the top of the loop. Otherwise, the loop needs to be exited; (+LOOP) pops the limit and index from the return stack and does not branch.
The reference to (+LOOP) is followed by a cell containing the offset to the top of the loop. If the loop is repeated, (+LOOP) fetches this offset and uses it to update the IP. If the loop is existed, (+LOOP) advances the IP past the offset.
(:) | M: ( -> ) R: ( -> IP ) | core |
|---|
(:) knows how to execute definitions which have been created by :. Such definitions contain a list of offsets to other THIRD words.
(:) moves the IP to the return stack, loads the address of the current word's definition into IP, and executes (NEXT).
(;) | M: ( -> ) R: ( Return address -> ) | core |
|---|
(;) used by : to end a definition; it causes execution to return to the THIRD word which called the word containing the reference to (;). It pops the IP from the return stack and enters (NEXT).
(0BRANCH) | M: ( Flag -> ) R: ( -> ) | core |
|---|
(0BRANCH) is used by compiling words such as IF and UNTIL to create a conditional jump. It examines the cell on top of the math stack to determine whether or not the branch should be taken. As with (BRANCH), the reference to (0BRANCH) is followed by a cell containing an offset to the address to which the system should branch. If the flag is non-zero, this branch is taken by entering (BRANCH). If the flag is zero, IP is advanced beyond the branch and (NEXT) is entered to continue straight-line code. In both cases, the flag is dropped from the math stack.
(BRANCH) | M: ( -> ) R: ( -> ) | core |
|---|
(BRANCH) is used by compiling words such as IF to create a jump. The reference to (BRANCH) is followed by a cell containing an offset to the address to which the system should branch. (BRANCH) fetches this offset, calculates the address to which the offset refers, stores the resulting address in IP, then enters (NEXT) to begin execution at that address.
(COLD) | M: Initialized R: Initialized | core |
|---|
(COLD) performs intialization that should be done once at system startup. It is assumed that the system-specific initialization code has intialized the console and the various StupidCode registers needed by the system. (COLD) initializes the math and return stacks, the dictionary pointer, LAST, displays the sign-on banner, and then executes ABORT.
Because (COLD) initializes DP and LAST, executing (COLD) causes the system to forget any user-created definitions which were stored in the dictionary.
(CONSTANT) | M: ( -> Value ) R: ( -> ) | core |
|---|
(CONSTANT) knows how to execute THIRD words created by CONSTANT. It copies the first cell of the THIRD word's definition to the math stack.
(DO) | M: ( Limit, Initial index -> ) R: ( -> Limit, Initial index ) | core |
|---|
(DO) does the work necessary to enter a do loop. It copies the limit and initial value of the index to the return stack, where they can be found by (LOOP) and/or (+LOOP) at the bottom of the loop.
(DP@) | MP: ( -> InitialDP ) RP: ( -> ) |
|---|
(DP@) pushes a copy of the dictionary
pointer's initial value onto
the math stack. This is the address of the first byte of system memory
which will be used to contain user-created definitions. (DP@) is used
during system initialization to initialize the
dictionary pointer.
(DP@) gets the initial dictionary pointer from the StupidCode register
InitialDP (see InitialDP@). It is
the responsibility of system-specific code to initialize the InitialDP
register before entering THIRD.
(LAST@) pushes a copy of the initial value for LAST onto the math stack. This is the code field
address of the last word which has been pre-compiled into the THIRD
dictionary. (LAST@) is used during system initialization to initialize
LAST.
(LAST@) gets the initial LAST pointer from the StupidCode register
InitialLAST (see InitialLAST@). It is
the responsibility of system-specific code to initialize the InitialLAST
register before entering THIRD.
(LIT) is used by : to create code
which pushes
a constant value onto the math stack. The reference to (LIT) is followed
by a cell containing the constant to be pushed onto the math stack. (LIT)
fetches the constant, pushes it onto the math stack, and advances
IP past the constant.
(LOOP) knows how to execute the bottom of a
(LAST@)
M: ( -> InitialLAST )
R: ( -> )
core
(LIT)
M: ( -> Literal )
R: ( -> )
core
(LOOP)
M: ( -> )
R: ( Limit, Index -> ) if the loop
exits
R: ( Limit, Index -> Limit, Index+1 )
otherwise
core
The reference to (LOOP) is followed by a cell containing the offset to the top of the loop. If the loop is repeated, (LOOP) fetches this offset and uses it to update IP. If the loop is exited, (LOOP) advances IP past the the offset.
(NEXT) | M: ( -> ) R: ( -> ) | core |
|---|
(NEXT) is the inner interpreter for the system. It contains a chunk of CODE which knows how to fetch and execute the next THIRD word. It is called implicitly by CODE words and would not normally be referenced directly by THIRD code (most FORTH systems implement the inner interpreter in a headless word so it is imposible to refer to it). CODE words usually execute (NEXT) via NEXT@ PC!, so even they do not refer directly to (NEXT).
(NEXT) fetches the cell pointed at by IP and calculates the address of the THIRD word to which the cell refers. It uses this to locate the CODE word which knows how to execute the THIRD word, then enters that CODE word; consequently, the THIRD word is executed.
Because THIRD is position-indepent, THIRD needs to do a bit more work than a normal FORTH system does; the cell pointed to by IP contains an offset to the THIRD word to be executed, rather than its address; the address of the THIRD word to be executed must be calculated. Offsets are relative to the location from which they are fetched, so the contents of IP must be added to the offset to obtain the address of the next THIRD word to be executed.
(NEXT) is the single most-frequently executed piece of code in the system. The work that (NEXT) must do to calculate addresses from offsets is therefore very expensive. Because of this, THIRD will never perform as well as other FORTH systems which do not need to perform this relocation. If you are that concerned about performance, you should investigate other FORTH systems.
(OLIT) | M: ( -> Address ) R: ( -> ) | compiling |
|---|
(OLIT) is used to create a relocatable reference to the address of another word in the dictionary. The reference to (OLIT) is followed by a cell containing the offset to the address involved. (OLIT) fetches this offset and constructs the address to which it refers, pushing that address on the stack.
(OLIT) is used in the definition of words such as IF, which must have the address of another word handy (IF needs to be able to find (0BRANCH); the executable code for IF contains the sequence (OLIT) (0BRANCH) to construct the address of (0BRANCH)). (OLIT) allows such words to be compiled with position-independent offsets rather than absolute addresses.
(VAR) | M: ( -> Address ) M: ( -> ) | core |
|---|
(VAR) knows how to execute THIRD words created by VARIABLE. It places the address of the THIRD word's definition on the math stack.
+ | M: ( a, b -> a+b ) R: ( -> ) | core |
|---|
+ adds the two cells on top of the math stack, pushing the sum onto the math stack. The two cells used to create the sum are dropped from the math stack.
++ | M: ( Address -> ) R: ( -> ) | core |
|---|
++ increments a cell in memory. It pops the address from the math stack, fetches a cell from memory at that address, adds one to the value read, then writes the new value back into memory at the address.
+LOOP | M: ( Address of the top of the loop, 1 ->
) if used to terminate a do
loop. M: Initialized otherwise. R: ( -> ) if used to terminate a do loop. R: Initialized otherwise. DP is updated. | compiling immediate |
|---|
+LOOP compiles the bottom of a do loop wherein the index is incremented some variable amount. It appends a reference to (+LOOP) followed by a reference to the top of the loop, the address of which is obtained from the math stack.
+LOOP expects the cell on top of the math stack to contain a one as an indication that the math stack describes a do loop; anything else is considered an error and compilation is aborted (see :ABORT). Both the one and the address of the top of the do loop are popped from the math stack.
Because +LOOP is an IMMEDIATE word, it is executed by : instead of compiled.
When +LOOP aborts compilation, it displays NEST?.
, | M: ( Value -> ) R: ( -> ) DP is updated. | compiling |
|---|
, appends a cell containing the value on top of the math stack to the dictionary. The cell is written into memory at the address indicated by DP, which is then incremented by the number of bytes in a cell to account for the new cell. The value is popped from the math stack.
- | M: ( a, b -> a-b ) R: ( -> ) | core |
|---|
- subtracts the cell on top of the math stack from the one second on the math stack, pushing the result on the math stack. The two cells which were used to create the result are popped from the math stack.
. | M: ( Value -> ) R: ( -> ) | core |
|---|
. displays, in hex, the value contained in the cell on top of the math stack. That value is dropped from the math stack.
.NYBBLE | M: ( Value -> Value ) R: ( -> ) |
|---|
.NYBBLE displays, in hex, the most-significant four bits of the value in the cell on top of the math stack. The cell is not discarded from the math stack.
The reliance on .NYBBLE for displaying cells limits THIRD to machines for which the cell size is a multiple of four bits.
.NYBBLES | M: ( Value, Count -> ) R: ( -> ) | core |
|---|
.NYBBLES displays, in hex, the least significant Count nybbles of the Value. Both the value displayed and the number of nybbles to be displayed are dropped from the stack.
0 | M: ( -> 0 ) R: ( -> ) | core |
|---|
0 pushes the constant value zero onto the math stack. It is used frequently enough in the system code that having a word to hold the constant requires less memory space than a (LIT) 0 sequence.
0< | M: ( Value -> 1 ) if Value<0 M: ( Value -> 0 ) otherwise R: ( -> ) | core |
|---|
0< compares the value in the cell on top of the math stack against zero. If the value is less than zero, it is replaced with one. Otherwise, the value is replaced with zero.
0= | M: ( Value -> 1 ) if Value=0 M: ( Value -> 0 ) otherwise R: ( -> ) | core |
|---|
This word compares the value in the cell on top of the math stack with zero, replacing that value with the result. If the value in the cell on top of the math stack is zero, it is replaced with one. Otherwise, it is replaced with zero.
1 | M: ( -> 1 ) R: ( -> ) | core |
|---|
1 pushes the constant value one onto the math stack. It is used frequently enough in the system code that having a word to hold the constant requires less memory space than a (LIT) 1 sequence.
: creates a new executable THIRD word. The definition of such a word
contains a list of references to other THIRD words; when the new word is
executed, those words are executed in sequence.
The name of the new word is taken from the next token
available in INBUF. : uses CREATE to create the header for the new
word, pointing it's code field at (:). Once the header is created, :
executes essentially as follows:
Upon exiting, : checks the math stack depth to ensure that all
program control structures have been completed. If there is stuff left
on the math stack from an incomplete program control structure, the
error message in NEST? is
displayed and compilation is aborted.
When compilation is aborted, the changes to the dictionary made by :
are reversed; the dictionary is returned to the state it had before the
compilation was begun.
:ABORT is the escape hatch used by :
when it detects a compilation error. It restores the dictionary to it's
state before the compilation was begun and executes ABORT.
:DONE is a variable used by : to determine
whether compilation is complete.
Because :DONE is a variable, executing it
pushes the address of its definition on the math
stack.
: initializes :DONE to zero when it
begins compiling a definition. An IMMEDIATE word sets :DONE to a non-zero
value to tell : to exit. This is
primarily done by ;.
:DP is a variable used by : to hold a
copy of the value in DP before compilation
started. This allows : to back out the
changes made to the dictionary if an error occurs during compilation.
Because :DP is a variable, executing it pushes the address of its
definition on the math stack.
:LAST is a variable used by : to hold
a copy of the value in DP before compilation
started. This allows : to back out the
changes made to the dictionary if an error occurs during compilation.
Because :LAST is a variable, executing it pushes the address of its
definition on the math stack.
:MP is a variable used by : to hold a
copy of the value of the math stack pointer before compilation started.
This allows : to detect that a control
structure was not completed before the end of compilation so it can
report the error to the user. Because :MP is a variable, executing it
pushes the address of its definition on the math stack.
; terminates a definition created by :. It appends a cell to the dictionary which
contains a reference to (;) then sets :DONE to one; this causes : to perform its final error checking and
exit.
Because ; is an IMMEDIATE word, : executes it instead of compiling a
reference to it.
< compares the values in the two cells on top of the math stack,
pushing the result onto the math stack. If the value in the cell on top of the
math stack is less than the value in the next cell, a one is pushed onto the
math stack. Otherwise, a zero is pushed. The cells used in the comparison
are dropped from the math stack.
<< shifts the value in the second cell of the math stack
to the left by the
number of bits specified in the cell on top of the math stack, pushing the
result onto the math stack.
The two cells used to create the value are
dropped from the math stack.
Zeros are shifted into the rightmost bit positions.
<R moves a value from the return stack to the math stack. It pops
a cell from the return stack and pushes the value from that cell onto
the math stack.
= compares the values in the top two cells of the math stack,
pushing the result of the comparison onto the math stack. If the values are
equal, a one is pushed onto the math stack. Otherwise, a zero is pushed. The
two cells used in the comparison are dropped from the math stack.
> compares the values in the two cells on top of the math stack,
pushing the result onto the math stack. If the value in the cell on top of
the math stack is greater than the value in the next cell, a one is pushed
onto the math stack. Otherwise, a zero is pushed. The cells used in the
comparison are dropped from the math stack.
>> shifts the value in the second cell of the math stack
to the right by the number of bits specified in cell on top of the
math stack, pushing the result onto the math stack. The two cells used to
create the value are dropped from the math stack.
The shift performed is a logical shift, not an arithmetic one;
zeros are shifted into
the leftmost bit positions.
>NYBBLE converts an character from an ASCII hexadecimal digit to its
corresponding binary value. It assumes the cell on top of the math stack
contains an ASCII hexadecimal digit. It replaces that digit with the
binary value corresponding to that digit.
>NYBBLES converts a counted string of ASCII hexadecimal digits
into its corresponding binary representation. The address and count of
the string are popped from the math stack and the corresponding binary
representation is pushed onto it.
>NYBBLES assumes each character in the counted string is either an
ASCII hexadecimal digit or the character '.'. The character '.' is
ignored; the ASCII hexadecimal digits are converted to binary.
Ignoring '.' allows you to use that character to separate long numbers
into shorter, eaiser to error-check numbers.
>R moves a value from the math stack to the return stack. It pops
a cell from the math stack and pushes the value from that cell onto the
return stack.
?ABORT calls ABORT if there is a character
available at the console. Because
ABORT initializes the system, ?ABORT
will only return if there is no byte available at the console.
@ uses the cell on top of the math stack as a memory address. It
pops the address from the math stack,
fetches a cell from memory at that address,
and pushes the value fetched onto the math stack.
ABORT initializes the math and return stacks and enters INTERPRET. It
is used to put the system in a known state as part of error recovery.
Because ABORT does not initialize DP and
LAST, ABORT does not forget any user-created
definitions stored in the dictionary.
ALLOT allocates a chunk of memory onto the end of the last word in
the dictionary. It does this by adding the specified number of bytes to
DP. The number of bytes is popped from the
math stack.
The primary use of ALLOT is to create a variable containing space for
an array. However, care must be taken in this, because CREATE does not fill in the code field
address of a word it creates. The easiest way to create a variable
containing an array is through something like 0 VARIABLE BOOGER
size ALLOT. This creates a variable named BOOGER which contains
the value 0 and then appends size bytes to that definition.
When BOOGER is executed, the address of the cell containing zero is
pushed on the math stack. This wastes a cell of memory, but the
alternatives are pretty messy unless you are willing to create a word
which creates the array (perhaps something like
: ARRAY CREATE (OLIT) (VAR)
, ALLOT ;
would work).
AND forms the bitwise and of the two cells on top of the math
stack, pushing the result onto the math stack. The two cells used to
create the
result are dropped from the math stack.
B! stores a byte from the math stack into memory
at a specified address.
It pops the address and the value from the math stack then stores the
least-significant byte of the value in memory at the address.
A byte is the smallest unit of memory which is addressable on the
machine. Although this is typically an 8-bit value, it is not
necessarily so. A cell contains at least one byte. A byte contains
at least 8 bits.
B, appends a byte containing the least-significant byte of the value on
top of the math stack to
the dictionary. The byte is written into memory at the address indicated
by DP, which is then incremented to account
for the byte. The value is popped from the math stack.
A byte is the smallest unit of memory which is addressable on the
machine. Although this is typically an 8-bit value, it is not
necessarily so. A cell contains at least one byte. A byte contains at
least eight bits.
B@ uses the cell on top of the math stack as a memory address. It
pops the address from the math stack, fetches a byte from memory at that
address, zero-extends the byte into a cell, and pushes the resulting
value onto the math stack.
A byte is the smallest unit of memory which is addressable on the
machine. Although this is typically an 8-bit value, it is not
necessarily so. A cell contains at least one byte. A byte contains
at least 8 bits.
CODE creates the header for a machine code word and points its code
field address at itself because a machine code word
is a word which contains the code which knows how to execute it.
CODE creates the header using CREATE.
It then deposits the offset to DP in the
code field address (which will always be zero; i.e., the offset to the
code field address of the word containing the machine code which knows
how to execute this word is zero). When CODE is done, DP points at the first byte of the word's
definition.
THIRD does not provide an assembler to help you fill in the body of a
CODE word. You will have to do this by hand using , and related words. And you'd dang well
better know what you're doing!
CONSTANT creates a word in the dictionary which holds a constant
value. The name of the constant is taken from the next token in (a
href="#core_INBUF">INBUF
:
M: ( -> ) if there are no compilation
errors.
M: Initialized otherwise.
R: ( -> ) if there are no compilation
errors.
R: Initialized otherwise.
LAST is updated.
DP is updated.
Tokens are consumed from INBUF,
which implies that IN is updated.
:DP,
:LAST,
:MP, and
:DONE are
initialized.
compiling
:ABORT
M: Initialized
R: Initialized
DP is restored from :DP.
LAST is restored from :LAST.
compiling
:DONE
M: ( -> Address )
R: ( -> )
compiling
:DP
M: ( -> Address )
R: ( -> )
compiling
:LAST
M: ( -> Address )
R: ( -> )
compiling
:MP
M: ( -> Address )
R: ( -> )
compiling
;
M: ( -> )
R: ( -> )
:DONE is set to
one.
compiling
immediate
<
M: ( a, b -> 1 ) if a<b
M: ( a, b -> 0 ) otherwise
R: ( -> )
core
<<
M: ( Value, Count -> Value<<Count )
R: ( -> )
core
<R
M: ( -> Value )
R: ( Value -> )
core
=
M: ( a, b -> 1 ) if a=b
M: ( a, b -> 0 ) otherwise
R: ( -> )
core
>
M: ( a, b -> 1 ) if a>b
M: ( a, b -> 0 ) otherwise
R: ( -> )
core
>>
M: ( Value, Count -> Value>>Count )
R: ( -> )
core
>NYBBLE
M: ( Character -> Value )
R: ( -> )
core
>NYBBLES
M: ( Address, Count -> Value )
R: ( -> )
core
>R
M: ( Value -> )
R: ( -> Value )
core
?ABORT
M: ( -> ) if there is no byte
available at the console.
R: ( -> ) if there is no byte
available at the console.
core
@
M: ( Address -> Value )
R: ( -> )
core
ABORT
M: Initialized
R: Initialized
core
ALLOT
M: ( Number of bytes -> )
R: ( -> )
DP is updated.
compiling
AND
M: ( a, b -> a AND b )
R: ( -> )
core
B!
M: ( Value, Address -> )
R: ( -> )
core
B,
M: ( Value -> )
R: ( -> )
DP is updated.
compiling
B@
M: ( Address -&bt; Value )
R: ( -> )
core
CODE
M: ( -> )
R: ( -> )
A token is consumed from INBUF,
which implies that IN is updated.
DP is updated.
compiling
CONSTANT
M: ( Value -> )
R: ( -> )
A token is consumed from INBUF,
which implies that IN is updated.
DP is updated.
compiling
CONSTANT using CREATE to create the header for the new constant. It points the code field address of the constant at (CONSTANT), which knows how to find the first cell in the definition of a word. It then appends the value from the cell on top of the math stack to the word's definition using ,.
COUNT | M: ( Address -> Address+1, Count ) R: ( -> ) | core |
|---|
Given the address in memory of a counted string (see $), COUNT extracts the length of the string and the address of the first byte of the string.
CR | M: ( -> ) R: ( -> ) | core |
|---|
CR displays a carriage return on the console. It is equivalent to 13 EMIT.
CREATE | M: ( -> ) R: ( -> ) A token is consumed from INBUF, which implies that IN is updated. DP is updated. | compiling |
|---|
CREATE appends a new header to the dictionary. It creates the name, flag, and link fields of the name and points LAST at the new header. A dictionary entry consists of the following fields in this order:
When CREATE is done, DP contains the code field address of the new word. CREATE does not ensure the code field points to anything; whoever is creating the word must deposit a code field using O, before the new word is executed.
CREATE obtains the name of the new word from INBUF using TOKEN. If there is no token available, CREATE displays NAME? and bebops on over to ABORT.
CREATE may also fail if the token obtained from INBUF is longer than 63 characters. In this case, CREATE displays LEN? before it enters ABORT.
CVTW@ | M: ( Address -> Value ) R: ( -> ) | core |
|---|
CVTW@ uses the cell on top of the math stack as a memory address. It pops the address from the math stack, fetches a word from memory at that address, sign-extends that word to a cell, and pushes the resulting value on the math stack.
A word in this case is an intermediate unit between a byte and a cell. Although this is typically a 16-bit value, it is not necessarily so. A cell contains at least one word.
This is similar to W@, except that W@ zero-extends the word to convert it to a cell instead of sign-extending it.
DIGITMAP | M: ( -> Address ) R: ( -> ) | core |
|---|
DIGITMAP is a variable which contains an array of bytes that are used to determine whether an ASCII character is a hexadecimal digit. There is a bit in the array for each possible ASCII character (0 through 7f hex) which is set if the character is to be considered a hexadecimal digit.
Given a character code, the byte containing the bit for that character can be found by shifting the character code right three bits and adding that result to the address returned by executing digitmap; given a character code on the math stack 3 << DIGITMAP + yields the address of the byte in DIGITMAP corresponding to the character. The bit position within that byte is given by three least significant bits of the character code; the character code can be converted to a bit mask by 7 AND 1 SWAP >>.
To aid in the transcription of long numbers, '.' is accepted as being a digit. Code which converts a string of ASCII characters to a binary number should ignore '.'. This allows you to use '.' to break large numbers into smaller, easier to error-check groups; you can use DEAD.BEEF as a synonym for DEADBEEF.
DO | M: ( -> Address of the top of the loop, 1
) R: ( -> ) DP is updated. | compiling immediate |
|---|
DO compiles the top of a do loop. It appends a reference to (DO) to the dictionary and pushes the address of the top of the loop onto the math stack for later use by LOOP or +LOOP. It also pushes a one onto the math stack to indicate that a do loop is being compiled.
Because DO is an IMMEDIATE word, it is executed by : instead of compiled.
It is important to realize that there is a difference between the stack effects of DO, which occur at compilation time, and those of (DO), which occur at run time. DO compiles the top of a do loop while (DO) actually enters the loop; the loop is not entered until execution reaches the (DO) which has been appended to the dictionary by DO.
A do loop is a counted loop; it executes the loop some number of times, maintaining a loop index which may be used to identify the iteration of the loop. A do loop comes in two flavors: DO ... LOOP and DO ... +LOOP. The first form always increments the loop index by one, while the second increments the loop index by the value on top of the math stack when the bottom of the loop is executed (not when it is compiled!). DO ... LOOP is essentially equivalent to DO ... 1 +LOOP.
The do loop is executed when the index for the loop is no longer less than the limit for the loop. Thus, 10 0 DO LOOP will execute its do loop sixteen times with the index ranging from 0 through F. Similarly, 10 0 DO 2 +LOOP will execute its loop eight times with the index being first 0, then 2, then 4, etc. through E.
The loop index is stored on top of the return stack (see (DO), (LOOP), and (+LOOP)), so it may be accessed inside the loop using R.
DP | M: ( -> Address ) R: ( -> ) | compiling |
|---|
DP is a variable containing the dictionary pointer. Executing DP pushes a copy of the dictionary pointer onto the math stack.
The dictionary pointer is the memory address of the next byte which will be allocated to a user-created definition.
DROP | M: ( Value -> ) R: ( -> ) | core |
|---|
DROP discards the cell on top of the math stack.
DUP | M: ( Value -> Value, Value ) R: ( -> ) | core |
|---|
DUP makes a copy of the cell on top of the math stack, pushing the copy onto the math stack.
ELSE | M: ( Address of the branch offset left
behind by IF, 3 ->
Address of a new branch offset, 3 ) if used to
build an if construct. M: Initialized otherwise. R: ( -> ) if used to build an if construct. R: Initialized otherwise. DP is updated. | compiling immediate |
|---|
ELSE is used to construct the false branch of an if construct. It fills in the conditional branch left behind by IF, popping the information describing that branch from the math stack, and creates an unconditional branch to be filled in by ENDIF. The address of the offset cell for the unconditional branch is pushed onto the math stack, as well as a three to indicate that an if construct is being constructed.
ELSE examines the cell on the top of the math stack to ensure it is being used to build an if construct. If this is not the case, ELSE aborts compilation (see :ABORT). When ELSE aborts compilation, it display NEST? to indicate the reason for the abort.
Because ELSE is an IMMEDIATE word, it is executed by : rather than compiled.
ENDIF | M: ( Address of branch offset, 3 -> ) if
used to complete an if
construct. M: Initialized otherwise. R: ( -> ) if used to complete an if construct. R: Initialized otherwise. | compiling immediate |
|---|
ENDIF is used to complet the compilation of an if construct. It fills in the cell reserved for a branch offset by either IF or ELSE, obtaining the information about that cell from the math stack. The information about the branch offset cell is dropped from the math stack.
ENDIF expects the cell on top of the math stack to contain a three as an indication that an if construct is being compiled. If the cell on top of the math stack contains any other value, compilation is aborted (see :ABORT). When ENDIF aborts compilation, it displays NEST? to indicate the reason for the abort.
Because ENDIF is an IMMEDIATE word, it is executed by : rather than compiled.
EXEC | M: ( Code field address -> ) R: ( -> ) | core |
|---|
EXEC executes the THIRD word whose address is on top of the math stack. The address points to the THIRD word's code field, which contains a pointer to the CODE word which knows how to execute the THIRD word. EXEC locates that CODE word and enters it, consequently executing the THIRD word.
FIND | M: ( Address, Count -> Address, Count,
Code field address ) if the named word exists in the
dictionary. M: ( Address, Count -> Address, Count, 0 ) otherwise. R: ( -> ) | core |
|---|
FIND searches the dictionary for a word with the name given by Address and Count. If a word with that name exists in the dictionary, its code field address is pushed onto the math stack. Otherwise, a zero is pushed onto the math stack. The address and count of the name are not removed from the math stack.
Because FIND begins its search with the word pointed to by LAST, FIND will always find the most recent word with a given name.
GETLINE | M: ( -> ) R: ( -> ) INBUF and IN are initialized. | core |
|---|
GETLINE reads a line of input from the console, storing it in INBUF. INBUF is arranged as a standard counted string; the first byte ( INBUF B@) is the length of the line and the remaining bytes are data from the line. There is an ASCII NULL following the data from the line.
The only control characters GETLINE recognizes are carriage return, backspace, and delete. Carriage return terminates the input; the length byte is finalized, a NULL is appended to the line, IN is initialized, and GETLINE returns. Backspace and delete both delete the last character entered; that character is deleted from the line and a backspace-space-backspace sequence is displayed to delete the character from the screen.
If INBUF becomes full (there is space for 79 characters), GETLINE stops accepting printable characters; at that point, the only characters accepted are carriage return, backspace, and delete.
GETLINE uses KEY to obtain characters from the console. Several parts of the system assume that KEY will return values in the range 0 through 7f hexadecimal. GETLINE does not enforce this assumption.
ID>$ | M: ( Code field address -> Address, Count ) R: ( -> ) | core |
|---|
Given the code field address of a word in the dictionary, ID>$ returns the address and byte count of name of the word. The code field address is popped from the math stack, and the address and byte count of the name are pushed onto it.
IDENTIFY | M: ( -> ) R: ( -> ) | core |
|---|
IDENTIFY displays the sign-on banner for THIRD. It is executed at system initialization time to identify the system to the user.
The sign-on banner is "THIRD version CellSize cellsize WordSize wordsize Bits bits", where version is the version of THIRD, cellsize is the number of bytes in a cell, wordsize is the number of bytes in a word, and bits is the number of bits in a cell.
IDMSG1 | M: ( -> Address, Count ) R: ( -> ) | core |
|---|
IDMSG1 is a counted string which contains a portion of the sign-on banner printed by THIRD at system initialization time. Executing IDMSG1 pushes the address and byte count of that portion of the sign-on banner on the math stack.
The sign-on banner is "THIRD version CellSize cellsize WordSize wordsize Bits bits", where version is the version of THIRD, cellsize is the number of bytes in a cell, wordsize is the number of bytes in a word, and bits is the number of bits in a cell. IDMSG1 contains the string "THIRD version CellSize ".
IDMSG2 | M: ( -> Address, Count ) R: ( -> ) | core |
|---|
IDMSG2 is a counted string which contains a portion of the sign-on banner printed by THIRD at system initialization time. Executing IDMSG2 pushes the addres and byte count of that portion of the sign-on banner on the math stack.
The sign-on banner is "THIRD version CellSize cellsize WordSize wordsize Bits bits", where version is the version of THIRD, cellsize is the number of bytes in a cell, wordsize is the number of bytes in a word, and bits is the number of bits in a cell. IDMSG2 contains the string "WordSize ".
IDMSG3 | M: ( -> Address, Count ) R: ( -> ) | core |
|---|
IDMSG3 is a counted string which contains a portion of the sign-on banner printed by THIRD at system initialization time. Executing IDMSG3 pushes the addres and byte count of that portion of the sign-on banner on the math stack.
The sign-on banner is "THIRD version CellSize cellsize WordSize wordsize Bits bits", where version is the version of THIRD, cellsize is the number of bytes in a cell, wordsize is the number of bytes in a word, and bits is the number of bits in a cell. IDMSG3 contains the string "Bits ".
IF | M: ( -> Address of cell reserved for
branch offset, 3 ) R: ( -> ) DP is updated. | compiling immediate |
|---|
IF compiles the beginning of an if construct. It appends a reference to (0BRANCH) to the dictionary and reserves a cell following it to receive the address of the end of the construct. It pushes the address of the reserved cell onto the math stack followed by a three to indicate that an if construct is being compiled.
Because IF as an IMMEDIATE word, it is executed by : rather than compiled.
The if construct has two flavors: IF ... ENDIF and IF ... ELSE ... ENDIF. In the first case, the code between IF and ENDIF is executed if the top of the math stack contains a non-zero value upon encountering the IF; a zero value causes that code to be skipped and execution continues following ENDIF. In the second case, the code between IF and ELSE is executed if the cell on top of the math stack contains a non-zero value when the IF is encountered and the code between ELSE and ENDIF is executed if the cell on top of the math stack contains zero when the IF is encountered.
Put another way, when execution reaches the point of the IF, execution will branch either to the code immediately following the next ELSE, if there is one, or to the code immediately following the next ENDIF, if there is no ELSE if the contents of the cell on top of the math stack are zero. If the contents of the cell on the math stack are not zero when the IF is reached, execution does not branch, but flows straight through the IF. However, if there is an ELSE, execution will unconditionally branch to the ENDIF when it is encountered.
It is important to distinguish between the effects of IF during compilation and execution. During compilation, the contents of the math stack are irrelevant; IF will always append a reference to (0BRANCH) to the dictionary and updat the math stack. At execution time, the (0BRANCH) is executed, and that word's stack effects are the ones which must be considered.
IMMEDIATE | M: ( -> ) R: ( -> ) | compiling |
|---|
IMMEDIATE sets bit 7 of the flag field (see CREATE) of the dictionary entry pointed to by LAST. This marks the last word entered into the dictionary as an IMMEDIATE word, which will cause : to execute the word instead of compiling a reference to the word. This allows you to extend the behavior of the compiler.
IN | M: ( -> Address ) R: ( -> ) | core |
|---|
IN is a variable used to hold the offset of the next byte to be digested from INBUF. TOKEN uses IN to keep track of where in memory it should start looking for the next token.
The offset of the first character stored in INBUF is 1.
INBUF | M: ( -> Address ) R: ( -> ) | core |
|---|
INBUF is a variable containing an 81 byte array used to accept input from the console. This is long enough for a count byte, a 79 character line, and a terminating byte of zero. When INBUF is executed, the address of the array is pushed on the math stack.
Although INBUF contains a counted string, it is executed by (VAR) rather than ($). Yeah, it's weird, and may well be a design error. Nevertheless, that's the way it is.
INTERPRET | M: ( Varies -> Varies ) R: ( Varies -> Varies ) |
|---|
INTERPRET is the main user interface of THIRD; when you type commands into THIRD you are more likely than not interacting with INTERPRET. It accepts a line of commands, breaks it into tokens, and executes the tokens. Because the action taken by INTERPRET depends on the commands you issue, the stack effects of INTERPRET are variable.
The work done by INTERPRET is essentially:
ISCONTROL | M: ( Character -> Character, 1 ) if
Character is an ASCII control character M: ( Character -> Character, 0 ) otherwise R: ( -> ) | core |
|---|
ISCONTROL examines the value in the cell on top of the math stack to determine whether it is an ASCII control character or a printing character. The value is deemed to be an ASCII control character if it is either less than 32 (an ASCII space) or equal to 127 (an ASCII delete); all other values are deemed to be printing characters. If the character is a control character, a one is pushed onto the math stack; otherwise a zero is pushed. The character is not dropped from the math stack.
ISDIGIT | M: ( Character -> 0 ) if character is not
a hexadecimal digit. M: ( Character -> Non-zero value ) otherwise. R: ( -> ) |
|---|
ISDIGIT examines the character code on top of the math stack to see whether that character should be considered to be a hexadecimal digit. If it is such a digit, some non-zero value is pushed on the math stack. If it is not such a digit, a zero is pushed on the math stack. In both cases, the character code which was checked is popped from the math stack.
ISDIGIT assumes the character code is within the ASCII character range; i.e., is some value from 0 through 7f hex, inclusive. It does not enforce this assumption; garbage in, garbage out.
DIGITMAP considers '.' to be a digit to allow users to break up large numbers into shorter, less error-prone, ones. Because DIGITMAP considers '.' to be a digit, ISDIGIT also considers '.' to be a digit.
ISNUMBER | M: ( Address, Count -> Address, Count, 1
) if the string is an ASCII hexadecimal number. M: ( Address, Count -> Address, Count, 0 ) otherwise. R: ( -> ) | core |
|---|
ISNUMBER examines a counted string to see whether it is an ASCII hexadecimal number. A string is considered to be an ASCII hexadecimal number of each character in the string is considered to be a digit by DIGITMAP. If the counted string is an ASCII hexadecimal number, a one is pushed onto the math stack. If the counted string is not an ASCII hexadecimal number, a zero is pushed. The original address and count for the string are left on the math stack.
LAST | M: ( -> Address ) R: ( -> ) | core |
|---|
LAST is a variable used to keep track of the address of the last definition in the dictionary. The address returned by LAST is the is the code field address associated with that definition.
LEN? | M: ( -> Address, Count ) R: ( -> ) | compiling |
|---|
LEN? contains the error message displayed by CREATE when it discovers that you are trying to create a word with a name longer than 63 characters. Executing LEN? pushes the address and byte count of the error message on the math stack.
LEN? contains the string "Len?" preceded and followed by a newline (a carriage return/linefeed sequence).
LOOP | M: ( Address of the top of the loop, 1 ->
) if used to terminate a do
loop. M: Initialized otherwise. R: ( -> ) if used to terminate a do loop. R: Initialized otherwise. DP is updated. | compiling immediate |
|---|
LOOP compiles the bottom of a do loop wherein the index is incremented by one. It appends a reference to (LOOP) followed by a reference to the top of the loop, the address of which is obtained from the math stack.
LOOP expects the cell on top of the math stack to contain a one as an indication that the math stack describes a do loop; anything else is considered an error and compilation is aborted (see :ABORT). Both the one and the address of the top of the do loop are popped from the math stack.
Because LOOP is an IMMEDIATE word, it is executed by : instead of compiled.
When LOOP aborts compilation, it displays NEST?.
MP! | M: Initialized R: ( -> ) | core |
|---|
MP! initializes the math stack. Any and all items on the math stack are discarded. It is used by error handling and system initialization to force the math stack to a known state.
MP@ | M: ( -> MP ) R: ( -> ) | core |
|---|
MP@ pushes a cell containing a copy of the math stack pointer onto the math stack.
MP@ is intended for use by words which create dictionary entries to verify that the all control structures have been cleanly terminated at the end of compilation. It is not intended to provide a back door into the memory space used to hold the math stack. The most you should do with the value returned by MP@ is to compare it with another value returned by MP@. MP@ is not guaranteed to return a memory address; it may simply return some indication of the depth of the math stack. Should MP@ return a memory address, it is not specified whether the math stack pointer was copied before or after the space required to hold the copy is allocated.
NAME? | M: ( -> Address, Count ) R: ( -> ) | compiling |
|---|
NAME? contains the error message displayed by CREATE when it discovers there is no token available in INBUF to use when creating a new word. Executing NAME? pushes the address and byte count of the error message on the math stack.
NAME? contains the string "Name?" preceded and followed by a newline (a carriage return/linefeed sequence).
NEG | M: ( a -> 0-a ) R: ( -> ) | core |
|---|
NEG replaces the value in the cell on top of the math stack with its two's complement.
Note that if THIRD is ever ported to a one's complement machine (PDP-10 anyone?), this portion of the document must be fixed no matter how NEG is implemented!
NEST? | M: ( -> Address, Count ) R: ( -> ) | compiling |
|---|
NEST? contains the error message displayed by : when it discovers there is stuff left on the math stack at the completion of compilation. Executing NEST? pushes the address and byte count of the error message on the math stack.
NEST? contains the string "Nest?" preceded and followed by a newline
NEXTCHAR | M: ( -> Character ) R: ( -> ) IN is updated. | core |
|---|
NEXTCHAR pushes the next character to be digested from INBUF onto the math stack. If that character is not zero (i.e., it is not the ASCII NULL which marks the end of INBUF), IN is incremented.
Because NEXTCHAR does not increment IN if the next character to be digested is the terminating ASCII NULL, that NULL will be repeatedly returned once it is encountered until IN is re-initialized (presumably by GETLINE).
NL | M: ( -> ) R: ( -> ) | core |
|---|
NL displays a newline (a carriage return followed by a linefeed) on the console. It is equivalent to 13 EMIT 10 EMIT .
NOT | M: ( a -> ~a ) R: ( -> ) | core |
|---|
NOT replaces the value in the cell on top of the math stack with its one's complement.
O, | M: ( Address -> ) R: ( -> ) DP is updated. | compiling |
|---|
O, appends a cell containing a relative offset to the specified address to the dictionary. The address is popped from the math stack, and offset between that address and the address indicated by DP is created, and that offset is written into memory at the address indicated by DP. DP is incremented by the number of bytes in a cell to account for the new cell.
O! | M: ( Target address, Address -> ) R: ( -> ) | compiling |
|---|
O! creates a relative offset to the target address, storing that offset at the specified address. Both the target address and the address at which the offset is stored are popped from the math stack.
O@ | M: ( Address of offset -> Address ) R: ( -> ) | compiling |
|---|
O@ fetches a cell from memory through a relative offset. Given the address of a relative offset, it fetches that relative offset, turns it into an absolute address, and pushes that address on the math stack. The original address is popped from the math stack.
OR | M: ( a, b -> a OR b ) R: ( -> ) | core |
|---|
OR forms the bitwise or of the two cells on top of the math stack, pushing the result onto the math stack. The two cells used to create the result are dropped from the math stack.
OVER | M: ( a, b -> a, b, a ) R: ( -> ) | core |
|---|
OVER pushes a copy of the second cell on the math stack onto the math stack.
PREV | M: ( Code field address -> 0 ) if the
code field address is for the first word in the
dictionary. M: ( Code field address -> Code field address of previous word ) otherwise. R: | core |
|---|
Given the code field address for a word in the dictionary, PREV pushes the code field address for the word which appears in the dictionary before that word onto the math stack, if there is one. If the given code field address is the address of the first word in the dictionary, zero is pushed onto the math stack. In both cases, the given code field address is dropped from the math stack.
Because zero is reserved to indicate the beginning of the dictionary has been reached, it is not possible to build a THIRD system which places the code field address of a definition at address zero. This should not be a big limitation because there are structures in a definition which precede the code field address; a definition which begins at zero does not have a code field address of zero.
It is possible to visit every definition in the dictionary by starting at LAST and repeating PREV until zero is returned.
PROMPT | M: ( -> Address, Count ) R: ( -> ) | core |
|---|
PROMPT is a counted string which contains the "OK" prompt displayed by INTERPRET when it is ready for another line of input. Executing PROMPT pushes the address and byte count of the prompt string on the math stack.
R | M: ( -> Value ) R: ( Value -> Value ) | core |
|---|
R pushes a copy of the value contained in the cell on top of the return stack onto the math stack. The cell is not discarded from the return stack.
Because the counter involved in a do loop is stored on the return stack, using R in a do loop will push a copy of the loop counter onto the math stack.
REPEAT | M: ( -> Address of top of loop, 2 ) R: ( -> ) DP is updated. | compiling immediate |
|---|
REPEAT compiles the top of a repeat loop. It pushes the address of the top of the repeat loop onto the math stack, followed by a two to indicate that a repeat loop is being compiled.
Because REPEAT is an IMMEDIATE word, it is executed by : instead of compiled.
A repeat loop has the general form REPEAT ... UNTIL. The loop is repeat as long as the cell on top of the math stack contains zero at the bottom of the loop.
RP! | M: ( -> ) R: Initialized | core |
|---|
RP! initializes the return stack. Any and all items on the return stack are discarded, including the return address for the word which called called RP!. It is used during error recovery and system initialization to force the return stack to a known state.
SWAP | M: ( a, b -> b, a ) R: ( -> ) | core |
|---|
SWAP interchanges the top two cells on the math stack. The first shall be second and the second shall be first.
TOKEN | M: ( -> Address, Count ) Count will be
zero if there are no more tokens in
INBUF. R: ( -> ) IN is updated via NEXTCHAR | core |
|---|
TOKEN searches INBUF to find the next token, pushing the address of the first byte in the token and the count of bytes in the token onto the math stack.
If there are no non-blank characters left in INBUF, the address returned is the address of the terminating ASCII NULL. The count returned in this case is zero.
A token is a string of non-blank characters. Tokens are separated from each other by blanks. TOKEN skips blanks in INBUF until a non-blank character is found. The address of that non-blank character is pushed onto the math stack. Non-blank characters are then counted until the next blank character is found.
TOKEN assumes that INBUF has been loaded by GETLINE. Because NEXTCHAR is used to fetch characters from INBUF
The terminating ASCII NULL is treated as a blank if it immediately follows a token. Because NEXTCHAR does not advance IN upon encountering that NULL, TOKEN will see it again when it searches for the next token, and will use it at that time to terminate the search.
TYPE | M: ( Address, Count -> ) R: ( -> ) | core |
|---|
TYPE displays Count bytes from memory beginning at Address. The count and address are dropped from the math stack.
UNTIL | M: ( Address of top of loop, 2 -> ) if
used to terminate a repeat
loop. M: Initialized otherwise. R: ( -> ) if used to terminate a repeat loop. R: Initialized othewise. DP is updated. | compiling immediate |
|---|
UNTIL compiles the bottom of a repeat loop. It appends a reference to (0BRANCH) to the dictionary followed by a reference to the top of the loop, the address of which is obtained from the math stack.
UNTIL expects the cell on top of the math stack to contain a two as an indication that the math stack contains information related to a repeat loop. If the top cell contains any other value, compilation is aborted (see :ABORT). If compilation is aborted, NEST? is displayed to indicate the reason for this behavior. The two and the address of the top of the loop are popped from the math stack if compilation is not aborted.
Because UNTIL is an IMMEDIATE word, it is executed by : rather than compiled.
VARIABLE | M: ( Initial value -> ) R: ( -> ) A token is consumed from INBUF, which implies that IN is updated. DP is updated. | compiling |
|---|
VARIABLE creates a word in the dictionary which contains a cell that may be used to hold a value. The name of the variable is taken from the next token in CREATE to create the header for the new variable. It points the code field address of the variable at (VAR), which knows how to find the address of the definition of a word. It then stores the cell on top of the math stack into the word's definition using ,.
VLIST | M: ( -> ) R: ( -> ) | core |
|---|
VLIST displays the names of all of the words in the dictionary. Because it begins with the word pointed to by LAST, the most recently defined word is displayed first. The words are then displayed in order until all words in the dictionary have been displayed.
W! | M: ( Value, Address -> ) R: ( -> ) | core |
|---|
W! stores a word from the math stack into memory at a specified address. It pops the address and the value from the math stack then stores the least-significant word of the value in memory at the address.
A word in this case is an intermediate unit between a byte and a cell. Although this is typically a 16-bit value, it is not necessarily so. A cell contains at least one word.
W, | M: ( Value -> ) R: ( -> ) DP is updated. | compiling |
|---|
W, appends a word containing the least-significant word of the value on top of the math stack to the dictionary. The word is written into memory at the address indicated by DP, which is then incremented by the number of bytes in a word to account for the word. The value is popped from the math stack.
A word in this case is an intermediate unit between a byte and a cell. Alhtough this is typically a 16-bit value, it is not necessarily so. A cell contains at least one word. A word contains at least one byte. A byte contains at least eight bits.
W@ | M: ( Address -> Value ) R: ( -> ) | core |
|---|
W@ uses the cell on top of the math stack as a memory address. It pops the address from the math stack, fetches a word from memory at that address, zero-extends that word to a cell, and pushes the resulting value on the math stack.
A word in this case is an intermediate unit between a byte and a cell. Although this is typically a 16-bit value, it is not necessarily so. A cell contains at least one word.
This is similar to CVTW@, except that CVTW@ sign-extends the word to convert it into a cell instead of zero-extending it.