A slight change to Fleet Forth.
Fleet Forth has a word COLS which holds the number of columns for either the screen or printing.
Fleet Forth also had a word ?CR which took a number on the data stack and would emit a carriage return if that number plus the number of characters on the line exceeded the value in COLS .
There were a lot of phrases like this:
This phrase causes a carriage return to be emitted if text output is not already at the left margin.
I've renamed ?CR to ?LINE . The new name means: is there room for this many characters on the line? If not, emit a carriage return.
The name ?CR now does what the phrase
CHARS is a VARIABLE which holds the count of the characters sent to the current line. 0EXIT takes a value off the data stack and exits the word it is in if the value is zero.
My question is, are there better names for these words?
No, I'm not aware of better names. This kind of thing has crossed my mind many times, regarding how you would work the line wrap but also allow it at places other than spaces, for example at the hyphen in compound adjectives like "a hand-powered grinder" or "a microwave-safe container." I haven't written any programs that would require this kind of thing though.
In his book Thinking Forth, Leo Brodie gives an example of hiding information to minimize the effects of design changes by localizing things that might change. He claims that direct memory access with @ and ! makes it possible. In his example, he uses a variable APPLES to keep a tally of apples. Then at the eleventh hour after a lot of code using APPLES has been written, a tally must be kept of two different kinds of apples, red and green. Leo Brodie proceeds to show how APPLES can be redefined as a colon definition which can be set to return the correct location for each tally.
VARIABLE COLOR ( pointer to current tally)
VARIABLE REDS ( tally of red apples)
VARIABLE GREENS ( tally of green apples)
: RED ( set apple-type to RED) REDS COLOR ! ;
: GREEN ( set apple-type to GREEN) GREENS COLOR ! ;
: APPLES ( -- adr of current apple tally) COLOR @ ;
To quote Leo Brodie "If we first say RED, then we can use APPLES to refer to red apples. If we say GREEN, we can use it to refer to green apples. We didn't need to change the syntax of any existing code that uses APPLES. We can still say
20 APPLES !
and
1 APPLES +!"
I have been thinking about this and I find myself wondering if VALUEs are all that desirable. It seems the only advantage to VALUE was eliminating a @ ( fetch).
I wonder if VALUE was an idea meant to appeal to programmers from other languages.
I have removed the definition of VALUE from Fleet Forth's kernel. It is not even defined in the system loader.
Fleet Forth still has a few words which are what I call 'system values'. A system value is normally only altered by the system and appears to the user to be a constant. IS only works on DEFERred words and there is currently no TO ; although, if there was, it would not work on system values.
An example of a word which is better as a system value than a VALUE is #BUF . #BUF returns the number of block buffers. Since the number of block buffers can be changed, #BUF is not a CONSTANT . The usage when #BUF was a VALUE was:
where N is the desired number of block buffers. The problem arises if CONFIGURE is not executed after changing the number of block buffers. CONFIGURE was rewritten and #BUF was changed to a system value ( a constant) to remedy this:
CONFIGURE now updates the number of block buffers in #BUF as well as reconfigure the block buffer area and entries in the buffer table. Unlike Blazin' Forth, Fleet Forth's CONFIGURE does not cause a cold start. It saves buffers before changing anything. The last thing CONFIGURE does is EMPTY-BUFFERS to clear the buffers and initialize the entries in the buffer table.
(IS) , the runtime of IS , will change the parameter field of a constant, or any other Forth word, even colon definitions. It does no checking and must not be used outside a colon definition. IS parses the text stream so it can check if the following word is a DEFERred word and will abort if it isn't.
: IS ( CFA -- ) // PARSES NEXT WORD
' DUP @ [ ' EMIT @ ] LITERAL <>
ABORT" NOT DEFERRED"
STATE @
IF
COMPILE (IS) , EXIT
THEN
>BODY ! ; IMMEDIATE
(IS) is defined in Fleet Forth's kernel and IS is defined in Fleet Forth's system loader.
My latest implementation of VALUE (before I removed it) was as a word which created multi code field words. I still see the benefit of multi code field words. The machine state words in the finite state machine lexicon are multi code field words with two code fields. They are not values.
ANSI Forth has :NONAME to create headerless colon definitions. To the best of my knowledge, ANSI Forth does not have a way to create other types of headerless words.
Suppose a word uses a string array, or some other data type, and this string array is not used outside that word. It would be nice to make the string array headerless.
I've given Fleet Forth's CREATE the ability to create headerless words. The word NH causes the next occurrence of CREATE to create a headerless word while still doing everything else a CREATE for the C64 does. NH : behaves differently than :NONAME . CREATE , and therefore : (colon), parses the text stream for a name even when not creating a header. When a header is not created, the parsed name is simply discarded. Unlike :NONAME , CREATE never leaves the CFA of the word it is creating on the stack, even if the word is headerless. CREATE always leaves the CFA of the word it is creating in the system value LAST even when creating a word with a header.
Fleet Forth's : (colon), CODE , and SUBR all use LATEST and TRUE for colon sys and code sys. LATEST is not a Forth-83 Standard word and some systems define it to fetch the name field from the CURRENT vocabulary. Fleet Forth's LATEST is a system value set by CREATE .LATEST returns the NFA , name field address, of the latest word created, unless that word is headerless. In that case, LATEST returns a dummy address which can have bits toggled without harm.
With the ability to create headerless words, the word compiled by DOES> and ;CODE can not reliably use LATEST to get to the CFA of the last word created; therefore, it uses LAST , a system value set by CREATE to hold the CFA of the last word created.
Here is an example of creating and using a headerless word.
COLORS is a dummy name. The headerless word COLORS will have its CFA stored in LAST until another word is created. LAST >A is used to place it on Fleet Forth's auxiliary stack before BCOLOR is created.
I was not satisfied with two names in the Fleet Forth kernel.
The first name, 'STREAM , implies the address of the input stream. 'STREAM actually returns the address and count.
From the Forth-83 Standard.
: STREAM ( -- ADR N )
BLK @ ?DUP
IF
BLOCK B/BUF
ELSE
TIB #TIB @
THEN
>IN @
OVER UMIN /STRING ;
The next word, 'TEXT , takes a delimiter and returns the address and count of a group of characters, not just the address.
The name TEXT is already in the Forth-83 Standard. 'TEXT is used by the following:
." -- C,I,83 "dot-quote"
-- (compiling)
Used in the form:
." ccc"
___
Later execution will display the characters ccc up to but
___
not including the delimiting " (close-quote). The blank
following ." is not part of ccc.
ccc
___
A sequence of arbitrary characters accepted from the input
stream until the first occurrence of the specified
delimiting character. The delimiter is accepted from the
input stream, but is not one of the characters ccc and is
___
therefore not otherwise processed.
Fleet Forth has the word DONE? which checks if a key was pressed. If no key was pressed DONE? returns FALSE. If the RUN/STOP key was pressed DONE? returns TRUE. If any other key was pressed DONE? waits for a key press and returns TRUE if the RUN/STOP key is pressed and FALSE otherwise.
I added MORE? to add the option of waiting if a full screen of text is displayed. The number of lines of text to display is determined by the value of ROWS. MORE? incorporates DONE? so a word does not have to include both. I've kept DONE? as a separate word for commands which may take a while without producing enough output for MORE? to be useful.
I added this option because I feel that Fleet Forth on the Commodore 64 is right at the threshold of being slow enough to stop the output with a key press at the desired point. If Fleet Forth was running on a two megahertz machine, stopping the text output at the desired spot would be harder.
Some Forths have a word .S which non-destructively displays the contents of the data stack. Fleet Forth also has the word .RS to non-destructively display the contents of the return stack. This word is used in one of the step display words in Fleet Forth's TRACE and in Fleet Forth's (ERR) , an error word which non-destructively displays the contents of all three stacks. .RS is a colon definition; therefore, the contents of the instruction pointer, IP , get pushed to the return stack when .RS is executed.
This is the display when .RS is executed from the command line.
On each line the display shows an address and, if the address is within the dictionary, the name of the word that address is a part of.
Here is a simple test word to demonstrate .RS .
As I said, .RS is a colon definition and IP gets pushed onto the return stack; therefore, what it actually shows is the contents of the instruction pointer IP followed by the contents of the return stack as they were just before .RS executes.
: WITH-WORDS ( -- NFA )
CONTEXT @
AHEAD
BEGIN
DUP R@ 2>R L>NAME CO R>
CS-SWAP THEN
@ DUP 0=
UNTIL
R> 2DROP ;
The only test to break out of the loop in WITH-WORDS is a test to see if there are no more words in the CONTEXT vocabulary. This was done to speed up words using WITH-WORDS which do not need a premature exit, such as a word to count the words in the CONTEXT vocabulary, count and display the number of words for each name length from one to thirty one, calculate the space taken up by the headers, and so on.
I've mentioned the operation of WITH-WORDS before, but to summarize, everything in the colon definition before WITH-WORDS is executed once. Everything after WITH-WORDS , up to the first encountered exit, is executed once for each name in the CONTEXT vocabulary.
This streamlined version of WITH-WORDS does not have a test to finish before all the words in the CONTEXT vocabulary have been processed; however, the ability to leave early can be added to words using WITH-WORDS by using WWEXIT .
The simple word GLOSSARY will be used to show how WWEXIT works. GLOSSARY uses WITH-WORDS and is executed by WRAPPER , a word used just to show how far WWEXIT needs to unnest. WRAPPER is in no way needed for any of this to work.
WRAPPER is executed from the command line. When GLOSSARY is executed, but before WITH-WORDS is executed, the contents of IP and the return stack are as follows:
After WITH-WORDS executes and returns control to the thread of Forth code after WITH-WORDS in GLOSSARY , the contents of IP and the return stack are as follows:
This shows that to exit from the threaded fragment of code in GLOSSARY to WRAPPER , three items on the return stack must be discarded before exiting.
This is one way to define WWEXIT
Here is a version of GLOSSARY which checks to see if the user wishes to exit early. If DONE? returns FALSE , exit this fragment of threaded code and WITH-WORDS will place the NFA of the next word on the data stack and rerun this threaded code fragment. If DONE? returns TRUE , don't exit this fragment and WWEXIT is executed which returns control to the word which executed GLOSSARY .
Since WWEXIT always exits to the word ( WRAPPER ) which executed the word using WITH-WORDS ( GLOSSARY ), an EXIT does not need compiled so the word ends with -; (dash semicolon) rather than ; (semicolon).
Here is a version of GLOSSARY which prints '. . .' on a new line if exiting prematurely.
GLOSSARY is a simple word used for demonstration purposes. Although GLOSSARY shows the words in the CONTEXT vocabulary, it shows only one name per line. WORDS is a much better tool.
The word 0EXIT removes the top data stack item and if FALSE will exit. I am currently considering renaming it to either ?STAY or ?0EXIT ( the name ?0EXIT is a suggestion from Dr Jefyll).
Fleet Forth dictionary and vocabularies.
The structures:
There is only one Dictionary in Fleet Forth. As is true with the Forth Leo Brodie discusses in "Starting Forth", Fleet Forth's vocabularies are independently linked lists which weave through the dictionary.
Each dictionary entry consists of an optional view field, a link field, a name field, a code field, and a variable sized parameter field. Some words have a parameter field size of zero with a code field pointing into the body of a primitive.
The link field of the first word in a vocabulary has a value of zero. The link field of every other word in a vocabulary points to the link field of the previous word defined in that vocabulary.
The chain of all vocabularies:
There is a variable, VOC-LINK , which points to the third cell of the latest vocabulary defined. This vocabulary's third cell points to the third cell of the vocabulary defined prior to that. The first vocabulary, FORTH , has a third cell which holds a zero. It is the first vocabulary.
The structure of a vocabulary:
A vocabulary's parameter field has three cells. The first cell points to the link field of the last word defined in that vocabulary. When a vocabulary is first created, this cell holds a value of zero. The second cell points to the parameter field of the parent vocabulary, the one the vocabulary was defined in. This cell holds a value of zero in the FORTH vocabulary, it is the parent, grandparent, etc. of all other vocabularies. The third cell is part of the vocabulary link chain of all vocabularies.
The tools:
There are three find primitives in Fleet Forth.
All three take the same parameters, the address of a counted string containing the name to be found and the PFA of a vocabulary. For example:
will ship the latest two words in the context vocabulary.
All three find primitives return the same results, the address of the string and FALSE if the name was not found, the CFA and TRUE if the name was found and the word is not immediate, the CFA and ONE if the name was found and the word is immediate. PFIND searches the vocabulary specified. If the word is not found, the parent vocabulary of that vocabulary is searched followed by its parent until the word is found or there are no more parent vocabularies to search. VFIND searches only the vocabulary specified. It does NOT search parent vocabularies.
The headerless find primitive used by FORGET only searches the vocabulary specified, like VFIND ; however, it will also find a word if the word's smudge bit set, unlike the other find primitives.
This is the main high level find word, FIND
: FIND ( ADR -- ADR2 F )
CONTEXT @ PFIND
?DUP ?EXIT
CURRENT @ VFIND ;
Note the single @ (fetch) after CONTEXT and CURRENT.
The ability of VFIND to search a vocabulary without searching its parent vocabularies adds some flexibility to Fleet Forth's dictionary search facilities. For example, this fragment from Fleet Forth's metacompiler
will only find the name USER.AREA if it is defined in the SHADOW vocabulary.
Forgetting words.
Fleet Forth's Forget sets the CONTEXT vocabulary equal to the CURRENT vocabulary and will only find a word to forget if it is defined in the CURRENT vocabulary. This makes it impossible to forget the CURRENT vocabulary by accident, or the CONTEXT vocabulary since it is the same. The idea here is new words are added to the CURRENT vocabulary so FORGET should only work on that vocabulary. There is a variable, FENCE , which holds a value below which FORGET will not work. FORGET also has a built in fence so it will not forget anything in the Fleet Forth kernel. To change something in the kernel, the metacompiler should be used to build a new kernel. FORGET first switches off multitasking and unlinks all tasks, it also restores NEXT . It then prunes the vocabulary link so the only vocabularies remaining in the vocabulary link are those defined below where the new HERE will be. FORGET then uses the vocabulary link to prune the remaining vocabularies. It then sets the new HERE and sets the empty point, used by EMPTY , to the lesser of the old empty point and the new HERE . FORGET then resets to its default value any system deferred word which had a vector defined after the new HERE .
This FORGET is in my humble opinion a well written FORGET which avoids the shortcomings of other Forth's FORGET .
Navigating the vocabulary structure: VOCS will show a display of all the vocabularies in the system and their relation. An example of the output from VOCS .
FORTH
SHADOW
META
FORTH
ASSEMBLER
EDITOR
ASSEMBLER
The original FORTH vocabulary is the parent vocabulary of the ASSEMBLER , EDITOR , and SHADOW vocabularies. The SHADOW vocabulary is the parent of the META vocabulary, which is the parent of the target FORTH vocabulary, which is the parent of the target ASSEMBLER vocabulary.
ORDER will show the CONTEXT and CURRENT vocabularies as well as their parent vocabularies. This is useful when there is more than one vocabulary with a given name, such as when metacompiling. An example of the output from ORDER when editing.
CONTEXT: FORTH
META
SHADOW
FORTH
CURRENT: FORTH
META
SHADOW
FORTH
In my opinion, Fleet Forth's vocabulary system is better than Ragsdale's 'ONLY' solution. Within this system, Fleet Forth's FORGET is, in my opinion, a superior solution to ANSI Forth's MARKER .
Thanks for that. I never had a good handle on the vocabulary stuff, and this explains some of it. At viewtopic.php?p=50247#p50247, I have scans of a letter from Walter Rottenkolber about figForth's innards, which I also never dug into like I should have.