PForth source code is freely available. The author is available for customization of pForth, porting to new platforms, or developing pForth applications on a contractual basis. If interested, contact Phil Burk at philburk@softsynth.com
Back to pForth Home Page
To learn more about Forth, see the Forth Tutorial.
The dictionary files that can be saved from pForth are almost host independant. They can be compiled on one processor, and then run on another processor. as long as the endian-ness is the same. In other words, dictionaries built on a PC will only work on a PC. Dictionaries built on almost any other computer will work on almost any other computer.
PForth can be used to bring up minimal hardware systems that have very few system services implemented. It is possible to compile pForth for systems that only support routines to send and receive a single character. If malloc() and free() are not available, equivalent functions are available in standard 'C' code. If file I/O is not available, the dictionary can be saved as a static data array in 'C' source format on a host system. The dictionary in 'C' source form is then compiled with a custom pForth kernel to avoid having to read the dictionary from disk.
ansilocs.fth = support for ANSI (LOCAL) word
c_struct.fth = 'C' like data structures
case.fth = CASE OF ENDOF ENDCASE
catch.fth = CATCH and THROW
condcomp.fth = [IF] [ELSE] [THEN] conditional compiler
filefind.fth = FILE?
floats.fth = floating point support
forget.fth = FORGET [FORGET] IF.FORGOTTEN
loadp4th.fth = loads basic dictionary
locals.fth = { } style locals using (LOCAL)
math.fth = misc math words
member.fth = additional 'C' like data structure support
misc1.fth = miscellaneous words
misc2.fth = miscellaneous words
numberio.fth = formatted numeric input/output
private.fth = hide low level words
quit.fth = QUIT EVALUATE INTERPRET in high level
smart_if.fth = allows conditionals outside colon definition
see.fth = Forth "disassembler". Eg. SEE SPACES
strings.fth = string support
system.fth = bootstraps pForth dictionary
trace.fth = single step trace for debugging
Usage:
pforth [-i] [-dDictionaryFilename] [SourceFilename]
pforth -dgame.dic
3 4 + .It should print "7 ok". Now enter:
Word sets supported include:
The ENVIRONMENT queries are not implemented.
Word sets NOT supported include:
INCLUDE filenameYou can nest calls to INCLUDE. INCLUDE simply redirects Forth to takes its input from the file instead of the keyboard so you can place any legal Forth code in the source code file.
c" custom.dic" SAVE-FORTHYou can then leave pForth and use your custom dictionary by enterring:
pforth -dcustom.dicOn icon based systems, you may wish to name your custom dictionary "pforth.dic" so that it will be loaded automatically.
Be careful that you do not leave absolute addresses stored in the dictionary because they will not work when you reload pForth at a different address. Use A! to store an address in a variable in a relocatable form and A@ to get it back if you need to.
VARIABLE DATA-PTR CREATE DATA 100 ALLOT DATA DATA-PTR ! \ storing absolute address! BAD DATA DATA-PTR A! \ storing relocatable address! GOOD DATA-PTR A@ \ fetch relocatable address
FORGET XXXX-MINE : XXXX-MINE ;This would automatically FORGET for you every time you load. Unfortunately, you must define XXXX-MINE before you can ever load this file. We have a word that will automatically define a word for you the first time, then FORGET and redefine it each time after that. It is called ANEW and can be found at the beginning of most Forth source files. We use a prefix of TASK- followed by the filename just to be consistent. This TASK-name word is handy when working with INCLUDE? as well. Here is an example:
\ Start of file INCLUDE? TASK-MYTHING.FTH MYTHING.FTH ANEW TASK-THISFILE.FTH \ the rest of the file follows...Notice that the INCLUDE? comes before the call to ANEW so that we don't FORGET MYTHING.FTH every time we recompile.
FORGET allows you to get rid of code that you have already compiled. This is an unusual feature in a programming language. It is very convenient in Forth but can cause problems. Most problems with FORGET involve leaving addresses that point to the forgotten code that are not themselves forgotten. This can occur if you set a deferred system word to your word then FORGET your word. The system word which is below your word in the dictionary is pointing up to code that no longer exists. It will probably crash if called. (See discussion of DEFER below.) Another problem is if your code allocates memory, opens files, or opens windows. If your code is forgotten you may have no way to free or close these thing. You could also have a problems if you add addresses from your code to a table that is below your code. This might be a jump table or data table.
Since this is a common problem we have provided a tool for handling it. If you have some code that you know could potentially cause a problem if forgotten, then write a cleanup word that will eliminate the problem. This word could UNdefer words, free memory, etc. Then tell the system to call this word if the code is forgotten. Here is how:
: MY.CLEANUP ( -- , do whatever ) MY-MEM @ FREE DROP 0 MY-MEM ! ; IF.FORGOTTEN MY.CLEANUPIF.FORGOTTEN creates a linked list node containing your CFA that is checked by FORGET. Any nodes that end up above HERE (the Forth pointer to the top of the dictionary) after FORGET is done are executed.
: [FORGET] ( -- , my version ) ." Change things around!" CR [FORGET] ( must be called ) ." Now put them back!" CR ; : FOO ." Hello!" ; FORGET FOO ( Will print "Change things around!", etc.)This is recommended over redefining FORGET because words like ANEW that call FORGET will now pick up your changes.
10 0 DO I . LOOP
WORDS.LIKE FOR WORDS.LIKE EMIT
SEE SPACES SEE WORDS
: SQUARE ( n -- n**2 ) DUP * ; : TSQ ( n -- , test square ) ." Square of " DUP . ." is " SQUARE . CR ;Even though this program should work, let's pretend it doesn't and try to debug it. Enter:
7 trace tsq << TSQ +0 <10:1> 7 || (.") Square of " >> okThe "TSQ +0" means that you are about to execute code at an offset of "+0" from the beginning of TSQ. The <10:1> means that we are in base 10, and that there is 1 item on the stack, which is shown to be "7". The (.") is the word that is about to be executed. (.") is the word that is compiled when use use .". Now to single step, enter:
sYou should see:
Square of << TSQ +16 <10:1> 7 || DUP >> ok
The "Square os" was printed by (."). We can step multiple times using the "sm" command. Enter:
3 smYou should see:
<< TSQ +20 <10:2> 7 7 || . >> 7 << TSQ +24 <10:1> 7 || (.") is " >> is << TSQ +32 <10:1> 7 || SQUARE >> okThe "7" after the ">>" was printed by the . word. If we entered "s", we would step over the SQUARE word. If we want to dive down into SQUARE, we can enter:
sd
You should see:
<< SQUARE +0 <10:1> 7 || DUP >> ok
To step once in SQUARE, enter:
sYou should see:
<< SQUARE +4 <10:2> 7 7 || * >> ok
To go to the end of the current word, enter:
g
You should see:
<< SQUARE +8 <10:1> 49 || EXIT >> << TSQ +36 <10:1> 49 || . >> okEXIT is compiled at the end of every Forth word. For more information on TRACE, enter TRACE.HELP:
TRACE ( i*x <name> -- , setup trace for Forth word ) S ( -- , step over ) SM ( many -- , step over many times ) SD ( -- , step down ) G ( -- , go to end of word ) GD ( n -- , go down N levels from current level, stop at end of this level )
TRUE constant USE_FRENCH USE_FRENCH [IF] : WELCOME ." Bienvenue!" cr ; [ELSE] : WELCOME ." Welcome!" cr ; [THEN]Here is how to conditionally compile within a colon definition by using [ and ].
: DOIT ( -- ) START.REACTOR IF [ USE_FRENCH [IF] ] ." Zut alors!" [ [ELSE] ] ." Uh oh!" [THEN] THEN cr ;
Consider a word that calculates the difference of two squares, Here are two ways of writing the same word.
: DIFF.SQUARES ( A B -- A*A-B*B )
DUP *
SWAP DUP *
SWAP -
;
( or )
: DIFF.SQUARES { A B -- A*A-B*B }
A A *
B B * -
;
3 2 DIFF.SQUARES ( would return 5 )
In the second definition of DIFF.SQUARES the curly bracket '{' told the
compiler to start declaring local variables. Two locals were defined, A
and B. The names could be as long as regular Forth words if desired. The
"--" marked the end of the local variable list. When the word is executed,
the values will automatically be pulled from the stack and placed in the
local variables. When a local variable is executed it places its value
on the stack instead of its address. This is called self-fetching. Since
there is no address, you may wonder how you can store into a local variable.
There is a special operator for local variables that does a store. It looks
like -> and is pronounced "to".
Local variables need not be passed on the stack. You can declare a local variable by placing it after a "vertical bar" ( | )character. These are automatically set to zero when created. Here is a simple example that uses -> and | in a word:
: SHOW2*
{ loc1 | unvar -- , 1 regular, 1 uninitialized }
LOC1 2* -> UNVAR
(set unver to 2*LOC1 )
UNVAR . ( print UNVAR )
;
3 SHOW2* ( pass only 1 parameter, prints 6 )
Since local variable often used as counters or accumulators, we have a
special operator for adding to a local variable It is +-> which is pronounced
"plus to". These next two lines are functionally equivalent but the second
line is faster and smaller:
ACCUM 10 + -> ACCUM 10 +-> ACCUMIf you name a local variable the same as a Forth word in the dictionary, eg. INDEX or COUNT, you will be given a warning message. The local variable will still work but one could easily get confused so we warn you about this. Other errors that can occur include, missing a closing '}', missing '--', or having too many local variables.
:STRUCT SONG LONG SONG_NUMNOTES \ define 32 bit structure member named SONG_NUMNOTES SHORT SONG_SECONDS \ define 16 bit structure member BYTE SONG_QUALITY \ define 8 bit member LONG SONG_NUMBYTES \ auto aligns after SHORT or BYTE RPTR SONG_DATA \ relocatable pointer to data ;STRUCT
SONG HAPPY \ define a song structure called happy
400 HAPPY S! SONG_NUMNOTES \ set number of notes to 400 17 HAPPY S! SONG_SECONDS \ S! works with all size members
CREATE SONG-DATA 23 , 17 , 19 , 27 , SONG-DATA HAPPY S! SONG_DATA \ store pointer in relocatable form
HAPPY DST SONG \ dump HAPPY as a SONG structure
HAPPY S@ SONG_NUMNOTES . \ fetch numnotes and printSee the file "c_struct.fth" for more information.
WHAT'S EMIT >NAME ID.
DEFER PRINTIT ' . IS PRINTIT ( make PRINTIT use . ) 8 3 + PRINTIT : COUNTUP ( -- , call deferred word ) ." Hit RETURN to stop!" CR 0 ( first value ) BEGIN 1+ DUP PRINTIT CR ?TERMINAL UNTIL ; COUNTUP ( uses simple . ) : FANCY.PRINT ( N -- , print in DECIMAL and HEX) DUP ." DECIMAL = " . ." , HEX = " .HEX ; ' FANCY.PRINT IS PRINTIT ( change printit ) WHAT'S PRINTIT >NAME ID. ( shows use of WHAT'S ) 8 3 + PRINTIT COUNTUP ( notice that it now uses FANCY.PRINT )Many words in the system have been defined using DEFER which means that we can change how they work without recompiling the entire system. Here is a partial list of those words
ABORT EMIT NUMBER?
DEFER OLD-EMIT ' QUIT IS OLD-EMIT ( set to known value ) : EEMMIITT ( char --- , our fun EMIT ) DUP OLD-EMIT OLD-EMIT ; : STUTTER ( --- ) WHAT'S OLD-EMIT 'C QUIT = ( still the same? ) IF ( this must be the first time ) WHAT'S EMIT ( get the current value of EMIT ) IS OLD-EMIT ( save this value in OLD-EMIT ) 'C EEMMIITT IS EMIT ELSE ." Attempt to STUTTER twice!" CR THEN ; : STOP-IT! ( --- ) WHAT'S OLD-EMIT ' QUIT = IF ." STUTTER not installed!" CR ELSE WHAT'S OLD-EMIT IS EMIT 'C QUIT IS OLD-EMIT ( reset to show termination ) THEN ;In the above example, we could call STUTTER or STOP-IT! as many times as we want and still be safe.
Suppose you forget your word that EMIT now calls. As you compile new code you will overwrite the code that EMIT calls and it will crash miserably. You must reset any deferred words that call your code before you FORGET your code. The easiest way to do this is to use the word IF.FORGOTTEN to specify a cleanup word to be called if you ever FORGET the code in question. In the above example using EMIT , we could have said:
IF.FORGOTTEN STOP-IT!
FS. FE. FG. F. 1.234000e+12 1.234000e+12 1.234e+12 1234000000000. 1.234000e+11 123.4000e+09 1.234e+11 123400000000. 1.234000e+10 12.34000e+09 1.234e+10 12340000000. 1.234000e+09 1.234000e+09 1.234e+09 1234000000. 1.234000e+08 123.4000e+06 1.234e+08 123400000. 1.234000e+07 12.34000e+06 1.234e+07 12340000. 1.234000e+06 1.234000e+06 1234000. 1234000. 1.234000e+05 123.4000e+03 123400. 123400.0 1.234000e+04 12.34000e+03 12340. 12340.00 1.234000e+03 1.234000e+03 1234. 1234.000 1.234000e+02 123.4000e+00 123.4 123.4000 1.234000e+01 12.34000e+00 12.34 12.34000 1.234000e+00 1.234000e+00 1.234 1.234000 1.234000e-01 123.4000e-03 0.1234 0.1234000 1.234000e-02 12.34000e-03 0.01234 0.0123400 1.234000e-03 1.234000e-03 0.001234 0.0012340 1.234000e-04 123.4000e-06 0.0001234 0.0001234 1.234000e-05 12.34000e-06 1.234e-05 0.0000123 1.234000e-06 1.234000e-06 1.234e-06 0.0000012 1.234000e-07 123.4000e-09 1.234e-07 0.0000001 1.234000e-08 12.34000e-09 1.234e-08 0.0000000 1.234000e-09 1.234000e-09 1.234e-09 0.0000000 1.234000e-10 123.4000e-12 1.234e-10 0.0000000 1.234000e-11 12.34000e-12 1.234e-11 0.0000000 1.234568e+12 1.234568e+12 1.234568e+12 1234567890000. 1.234568e+11 123.4568e+09 1.234568e+11 123456789000. 1.234568e+10 12.34568e+09 1.234568e+10 12345678900. 1.234568e+09 1.234568e+09 1.234568e+09 1234567890. 1.234568e+08 123.4568e+06 1.234568e+08 123456789. 1.234568e+07 12.34568e+06 1.234568e+07 12345679. 1.234568e+06 1.234568e+06 1234568. 1234568. 1.234568e+05 123.4568e+03 123456.8 123456.8 1.234568e+04 12.34568e+03 12345.68 12345.68 1.234568e+03 1.234568e+03 1234.568 1234.568 1.234568e+02 123.4568e+00 123.4568 123.4568 1.234568e+01 12.34568e+00 12.34568 12.34568 1.234568e+00 1.234568e+00 1.234568 1.234568 1.234568e-01 123.4568e-03 0.1234568 0.1234568 1.234568e-02 12.34568e-03 0.01234568 0.0123456 1.234568e-03 1.234568e-03 0.001234568 0.0012345 1.234568e-04 123.4568e-06 0.0001234568 0.0001234 1.234568e-05 12.34568e-06 1.234568e-05 0.0000123 1.234568e-06 1.234568e-06 1.234568e-06 0.0000012 1.234568e-07 123.4568e-09 1.234568e-07 0.0000001 1.234568e-08 12.34568e-09 1.234568e-08 0.0000000 1.234568e-09 1.234568e-09 1.234568e-09 0.0000000 1.234568e-10 123.4568e-12 1.234568e-10 0.0000000 1.234568e-11 12.34568e-12 1.234568e-11 0.0000000
void pfExecuteToken( ExecToken XT );It is passed an execution token the same as EXECUTE would accept. It handles threading of secondaries and also has a large switch() case statement to interpret primitives. It is in one huge routine to take advantage of register variables, and to reduce calling overhead. Hopefully, your compiler will optimise the switch() statement into a jump table so it will run fast.
[NOT IMPLEMENTED] Dictionaries can be split so that the compile time words can be placed above the main dictionary. Thus they can use the same relative addressing but be discarded when turnkeying.
Execution tokens are either an index of a primitive ( n < NUM_PRIMITIVES), or the offset of a secondary in the code segment. ( n >= NUM_PRIMITIVES )
The NAME HEADER portion of the dictionary contains a structure for each named word in the dictionary. It contains the following fields:
bytes 4 Link Field relative address of previous name header 4 Code Pointer relative address of corresponding code n Name Field name as counted string Headers are quad byte aligned.The CODE portion of the dictionary consists of the following structures:
4*n Parameter Field execution tokens 4 ID_NEXT = 0 terminates secondary
4 ID_CREATE_P token 4 Token for optional DOES> code, OR ID_NEXT = 0 4 ID_NEXT = 0 n Body = arbitrary data
4 ID_DEFER_P same action as ID_NOOP, identifies deferred words 4 Execution Token of word to execute. 4 ID_NEXT = 0
4 ID_CALL_C 4 Pack C Call Info Bits
0-15 = Function Index Bits 16-23 = FunctionTable Index (Unused) Bits 24-30 = NumParams Bit 31 = 1 if function returns value
4 ID_NEXT = 0
PF_NO_INIT
To build on Macintosh:
-DPF_USER_INC1='"pf_mac.h"'To build on PCs:
-DPF_USER_INC2='"pf_win32.h"'To build a system that only runs turnkey or cloned binaries:
-DPF_NO_INIT -DPF_NO_SHELL
gmake pfembFor other systems, here are the steps to create an embedded pForth.
-DPF_NO_INIT -DPF_NO_MALLOC -DPF_NO_FILEIO \ -DPF_USER_CHARIO="user_chario.h" \ -DPF_NO_CLIB -DPF_STATIC_DIC
You can call 'C' from pForth by adding your own custom 'C' functions to a dispatch table, and then adding Forth words to the dictionary that call those functions. See the file "pfcustom.c" for more information.
pforth include tester.fth include coretest.fth byeThe output will be self explanatory. There are also a number of tests that I have added that print the number of successes and failures. Enter:
pforth t_corex.fth pforth t_locals.fth pforth t_strings.fth pforth t_floats.ftNote that t_corex.fth reveals an expected because SAVE-INPUT is not fully implemented. (FIXME)
Back to pForth Home Page