^Forth Words

Version 0.1.4

David N. Williams
MCTP
University of Michigan
Ann Arbor, MI 48109-1120

Starting date:
Last revision:
August 18, 2001
February 5, 2005



Copyright © 2001-2004 by david.n.williams@umich.edu

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation, with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license may be found at http://www.gnu.org/copyleft/fdl.html.


Contents

1. Introduction
 
2. The translation model
 
3. ^Forth words
3.1 Scoping words
host[ | ]host
3.2 Sectioning words
BEGIN-EXTERNAL | END-EXTERNAL
MAKE-INDEX
{" | c{ | /*
3.3 Defining words
def: | ;def | defm: | ext-def:
constant: | variable: | 2variable: | ext-variable:
create-allot:
3.4 List of mode-dumb words
3.5 Immediacy
 
4. Sample code
4.1 Inlining example
4.2 Mixing example
4.3 Scoping example
 

1. Introduction

^Forth (pronounced "hat Forth", or "to the power Forth") is a dialect of Forth intended for automatic text translation into source for the implementation language of a normal Forth. It grew out of a set of assembly language macros dating back to mid-1991, written for the Ann Arbor macro assembler (AAma by Martinus Veltman) to run on Motorola 680x0 cpu's under a variety of operating systems.

Here is an example from that earlier scheme:

; #S      ( ud -- 0 0 )                                6.1.0050

; Convert all digits of an unsigned, 64-bit number ud, adding
; each to the pictured numeric output text, until the remainder
; is zero.  A single zero is added to the output string if the
; number was initially zero.  Use only between <# and #>.
; "sharp-s", x79

DEF #s,numDigits
  BEGIN |numDigit |over |over |or |zeroEqual |UNTIL
ENDEF

This defines numDigits as an assembly language macro that expands to a subroutine call in the target Forth, which is subroutine threaded. The "|" character is used by AAma to separate multiple assembly language statements on the same line.

Words like over are defined as assembly language macros that expand to inline source:

; OVER    ( a b -- a b a )                             6.1.1990

DEFM over,over |.MACRO over
  movel 4(ps),-(ps)
.ENDM |ENDEFM over

The AAma macro pairs DEF ENDEF and DEFM ENDEFM take care of inserting the words #s and over into normal Forth word lists for use by the normal Forth interpreter and compiler.

The language thus made up out of assembly language macros was of course not interactive Forth -- it was intended for compilation only (by the assembler), in a sort of source analog to Forth target compilation. Although its syntax was a bit rough, it had some advantages:

It has long been claimed that C fulfills the role that assembly language used to. In particular, gcc, the GNU C compiler, supports many cpu's and UNIX-like systems, with pretty good optimization, and access to assembly language as needed. One could wish for something better than the usual intricate C-header environment, but maybe the problems that addresses are intrinsically complex and unavoidable.

Anyway, that's where we went. The name "^Forth" formerly referred to the system of macros described above together with the ANS Forth they helped to implement (all unpublished). Now it refers to an actual dialect of Forth, which allows mixing of C and Forth source in files interpreted by a normal Forth, with C source as output. Currently the host Forth system is pfe (Portable Forth Environment), and the C ouput links with pfe, either directly or by treating pfe as a dynamic library on systems that permit it, such as Linux and Darwin.

The translation still makes heavy use of macros. But of the natural C/UNIX analogs, it uses cpp macros only a little, and m4 macros not at all. Instead it uses text processing facilities from our Dynamic-Strings word set, ported to pfe. As a result, we are able to make the Forth part of the syntax almost completely Forth-like, with some inevitable differences due to the nature of cross compilation.

Part of the point is to give C optimization a chance to act on Forth code. Anton Ertl and Martin Maierhofer in Translating Forth to Efficient C have analyzed a number of the issues, and actually implemented a proof of concept translator called Forth2C. Some benchmark results, including comparisons to hand-coded C, can be found on Ertl's performance page, which is based on his 1996 Ph.D. dissertation, Implementation of Stack-Based Languages on Register Machines.

Our current translator produces code for a pfe loadable module. The main optimizations are:

In effect the translator produces subroutine-threaded code with inlining.

We have some ideas for enabling more sophisticated C optimization, but maybe the scheme above, together with whatever results from the judicious inclusion of explicit C code, is good enough.

The only other Forth to C translator we have heard about is part of a quite ambitious, rule-based translator project by Rob Chapman, called Timbre. It predated Ertl and Maierhofer's work and is still actively developed. Our style of intermixing Forth and C is quite a bit like that described in Chapman's C Without C article.

2. The translation model

Here we try to distinguish the translation model from its pfe implementation. The language in this document does assume that the output is to be C source. With small changes in wording, it could just as well have been source for some other development language (like assembly language).

The ^Forth translator converts a ^Forth source file to a C source file. The translator is written in the host Forth. In particular it defines all of the ^Forth-specific words described in this document. The translator converts the input file by interpreting it in the usual way.

^Forth, colon-like defining words do not switch to compilation state for the body of a word definition. Instead they act in a special word list context, where previously defined words in the body of the definition emit text rather than compiling code.

This kind of situation is called "scoping" in Elizabeth Rather's proposal for a cross-compiler standard. While there is an overlap between the concepts of Forth to C translation and Forth cross-compilation, it is only partial. We have two, maybe three scopes instead of four. One is analogous to HOST, and one to perhaps some combination of INTERPRETER and COMPILER. The third, consisting of the compiled and linked C code, we take to be analogous to TARGET; but since that is outside the translation process, we don't make it a formal scope. We do use the term "target" to refer to the extended host system, or the application, or even a new Forth system that results from compiling and linking the translation output.

We call the two basic scopes the "host" and "translator" scopes. The translator scope uses a translation word list, which contains redefined or new words that emit source translations, and ^Forth words which are to be invisible in host scope. We make a translation word list part of the model rather than an implementation detail, because we think it adds clarity.

^Forth files are translated mostly in translation scope.

Besides the output file itself, the translator uses two logical text streams to build the output, a definition stream and an index stream.

The definition stream starts over with each ^Forth colon-like definition. It builds the source for the corresponding C function.

The index stream is used to build C source for target dictionary entries.

How the translator combines the definition and index streams into the output file depends on how the target dictionary is implemented. If it keeps word headers and code together, the translator would intermix the index and definition streams in the output. With pfe, headers and code are separated; and the pfe translator appends the index stream onto the end of the output file in one piece.

3. ^Forth words

^Forth words fall into two main categories, ^Forth-special words, and words they define. The ^Forth-special words are specified in this document. Some of them organize the structure of a ^Forth source file, and others are special defining words.

3.1 Scoping words

The host and translator scopes are managed by the words host[ and ]host. With the exception of these two, all of the words specified in this document are to be in the translation word list, visible in translator scope and not in host scope. These two words are used to escape to and return from the host scope, for example, to do a setup calculation or to modify the translator.

host[              ( -- )
Switch to the host scope, where the translation word list is not in the search order and not available for new definitions. This word belongs to the host scope. "host-left-bracket "
]host              ( -- )
Switch to the translation scope, where the translation word list is in the search order above that of the host scope, and is the recipient of new word definitions. This word belongs to the host scope. "right-bracket-host"

3.2 Sectioning words

^Forth source files have two basic sections:

A ^Forth source file gets treated in either of two ways, as the main translation file, or as an external resource for the main translation file. ^Forth defining words in the body of the file correspondingly work in one of two modes, nonexternal or external.

The main translation file loads the ^Forth files mentioned with REQUIRES in its preamble as external resources to get calling stubs, inlined source, storage references, C prototypes, etc. These are produced by the action of ^Forth defining words in external mode. External files do not load additional external resources; their preambles are simply ignored. The body of the main file produces source definitions in the normal, nonexternal mode.

This logic is handled by BEGIN-EXTERNAL and END-EXTERNAL.

BEGIN-EXTERNAL     ( -- )
If the current file is the main translation file, the preamble between BEGIN-EXTERNAL and END-EXTERNAL is to be read. First, source essential for the target C environment is written to the output file; and the mode is set so that subsequent ^Forth definitions are in external mode. Then the preamble is interpreted. This typically writes source for any extra C includes that might be needed, and then loads external ^Forth files mentioned with REQUIRES.

If the current file is an external resource for the main translation, it is being loaded in external mode. Its preamble and the immediately following END-EXTERNAL are skipped, and there is no switch to nonexternal mode in the rest of the file.

END-EXTERNAL       ( -- )
This word should be interpeted only if the current file is the main translation module, otherwise it is skipped. The external mode which was set by BEGIN-EXTERNAL is restored to nonexternal, so ^Forth definitions in the body of the file act accordingly.

BEGIN-EXTERNAL and END-EXTERNAL are the only ^Forth words that can switch between nonexternal and external modes. They are to appear exactly once in a ^Forth file.

Note that preambles do not nest, and shouldn't need to.

^Forth word definitions in the main translation file are made available for insertion into the target dictionary by MAKE-INDEX.

MAKE-INDEX         ( `<name> "<title>"` -- )
If the current file is the main translation file, start an index stream for C source that adds searchable words to the target dictionary when the translation output is linked into the target system. Here <name> has to be a legal C identifier, while <title> is descriptive text not containing any double quotes.

This word followed by the name and quoted title must appear between END-EXTERNAL and the first ^Forth definition that is to appear in the target dictionary. Subsequent ^Forth defining words emit C code to the index stream to make the words searchable in the target dictionary.

If the current file is external, MAKE-INDEX does nothing but skip over <name> and "<title>".

In the pfe implementation, MAKE-INDEX starts a string concatenation stream for the index with a dictionary-code preamble, and sets a flag so that subsequent ^Forth defining words know to concatenate extra dictionary code to the index stream. When it reaches the end of the main translation file, the translator appends the index stream plus a dictionary postamble to the output file.

The following two words transmit literal C text up to a terminating string. The first writes directly to the output file. The second concatenates to the definition stream, which is written to the output file at the end of a definition. There are restrictions on usage.

{"                 ( `<text>"}` -- )
Write the input stream to the output file across line ends, up to the first occurrence of "}, leaving the input stream positioned just after "}. End of line characters are written after each line end encountered before the terminating "}. "left-brace-quote"

{" is not to be used inside a ^Forth definition, and is normally used only in the preamble.

c{                 ( "<text>}c" -- )
Concatenate the input stream to the definition stream across line ends, up to the first occurrence of }c, leaving the input stream positioned just after }c. End of line characters are concatenated after each line end encountered before the terminating }c. "c-left-brace"

c{ and the terminating }c are to be used only between def: and ;def, in particular not between defm: and ;defm.

The last sectioning word adds C-style comments to ^Forth.

/*                 ( "<text>*/" -- )
Ignore the input stream across line ends, leaving it positioned just after the first occurrence of */. "slash-star"

Note that even without this word, C-style comments inside {" "} or c{ }c pairs appear in the output. Outside of such pairs, /* is a normal Forth word that must be followed by whitespace, but */ need not be delimited by whitespace on either side.

3.3 Defining words

^Forth defining words have different names than their ANS Forth counterparts. With the exception of ;def, they all parse not only a name but also a quoted label from the input stream. The label is the Forth pronunciation with underscores instead of hyphens, used to build the identifier for the corresponding C function. The quotes are just a reminder that the label comes from the pronunciation.

All ^Forth defining words except ;def have different actions in nonexternal and external mode.

def:               ( `<name> "<label>"` -- )
Define <name> in the translation word list as a word that concatenates its C function call to the definition stream, with identifier based on <label>.

In external mode, also write the function prototype to the output file; and skip past ;def.

In nonexternal mode, also start or restart the definition stream with the C function header; and concatenate C code to the index stream that inserts a searchable target dictionary entry, if one was started by MAKE-INDEX.

Ordinary numbers are not permitted between def: and ;def. Instead they should be predefined with constant:. "def-colon"

;def               ( -- )
This word should get interpreted only in nonexternal mode. Concatenate the C function trailer for the function started by def:, e.g., "}", to the definition stream; and empty the definition stream into the output file, while treating the index stream, if present, as required by the target dictionary. "semicolon-def"

The reason for prohibiting ordinary numbers between def: and ;def is that they do not emit source code; they just leave their values on the data stack. It's good programming practise anyway to use named constants instead.

The next word defines words that do C inlining.

defm:              ( `<name> "<label>"` -- )
Define <name> in the translation word list as a word that concatenates the text starting on the line following "<label>", and across lines up to ;defm, to the definition stream. Leave the input stream positioned just after ;defm.

In other words, when executed in translator scope, <name> concatenates the body of the definition to the definition stream. Note that the body must be C code.

In addition when the mode is nonexternal, start or restart the definition stream with the C function header (identifier based on <label>), append the body of the definition, and append the C function trailer. Also, if MAKE-INDEX started an index stream, emit to it C code that inserts a searchable target dictionary entry. Empty the definition stream into the output file, while treating the index stream, if present, as required by the target dictionary. "def-m-colon"

As stated above, any stack comment would have to be on the same line as "<label>". Maybe we should relax the starting line condition for the body of a defm: word by making a stack comment mandatory, so it could serve as a delimiter for the start of the body.

The reason for prohibiting c{ and }c between defm: and ;defm is simply that they are not C code.

The next word enables words already defined in the host system to be used in ^Forth definitions.

ext-def:           ( `<name> "<label>"` -- )
Write to the output file the C function prototype for a word existing in the host system, with identifier based on <label>. Define <name> in the translation word list as a word that concatenates the C function call for the host word to the definition stream. "ext-def-colon"

Remaining are the ^Forth data-defining words. Here we use "handle the index" as a shorthand for "if MAKE-INDEX started one, emit C code to the index stream that inserts a searchable target dictionary entry, and treat it as required by the target dictionary".

constant:          ( `<name> "<label>"` n -- )
Define <name> in the translation word list as a word that concatenates C code to the definition stream for a normal Forth constant, with stack pattern ( -- n ). Write to the output file a CPP define that replaces an identifier based on <label> by n.

In nonexternal mode, also handle the index. "constant-colon"

variable:          ( `<name> "<label>"` -- )
Define <name> in the translation word list as a word that concatenates C code to the definition stream for a normal Forth variable, with stack pattern ( -- addr ).

In nonexternal mode, also write to the output file a C data declaration with identifier based on <label>; and handle the index.

In external mode, also write to the output file a C extern data declaration with identifier based on <label>. "variable-colon"

2variable:          ( `<name> "<label>"` -- )
Define <name> in the translation word list as a word that concatenates C code to the definition stream for a normal Forth 2variable, with stack pattern ( -- addr ).

In nonexternal mode, also write to the output file an appropriate C data declaration with identifier based on <label>; and handle the index.

In external mode, also write to the output file an appropriate C extern data declaration with identifier based on <label>. "two-variable-colon"

ext-variable:      ( `<name> "<label>"` -- )
Define <name> in the translation word list as a word that concatenates C code to the definition stream for an existing host system variable, with stack pattern ( -- addr ).

Also write to the output file any C declarations required by the host implementation, with identifier based on <label>. "ext-variable-colon"

Note that any C declarations written by ext-variable: should be the same in external and nonexternal mode, because the variable in question is external to the translation process. In the pfe version, ext-variable: does not emit such declarations, because they are taken care of in a basic include generated by BEGIN-EXTERNAL.

In the pfe case, variables defined by variable: and ext-variable: have an altogether different structure. In pfe, ANS variables like BASE, along with some other variables and data structures, have a special implementation so threads can have their own copies, like user variables.

create-allot:      ( `<name> "<label>"` u -- )
Define <name> in the translation word list as a word that concatenates C code to the definition stream for a normal Forth variable/array of u address units, with stack pattern ( -- addr ).

In nonexternal mode, also write to the output file a C data declaration with identifier based on <label>; and handle the index.

In external mode, also write to the output file a C extern data declaration with identifier based on <label>. "create-allot-colon"

3.4 List of mode-dumb words

Besides having two basic scopes, the specification above has introduced external and nonexternal modes. We hope this doesn't lead to the kind of problem associated with traditional state-smart words. The mode is relevant only in translator scope.

The only mode-dumb words among those specified above are the following:

  host[  ]host  {"  c{  /*  ;def

Actually ;def is a special case, since it is skipped in one mode but not the other. It is somewhat analogous to words defined by the ^Forth defining words, which get different definitions in the two modes, each one mode-dumb.

3.5 Immediacy

In the ANS Forth cross-compiler proposal, it is an ambiguous condition to use immediate in any but the host scope. It is also true in ^Forth that immediacy makes no sense for words in translator scope that may appear between def: and ;def. But that does not mean that immediate should not be defined in translator scope, affecting the source output to make words in the target dictionary immediate.

4. Sample code

Here we display and comment on abbreviated extracts from two ^Forth files from our pfe implementation.

4.1 Inlining example

The first excerpt is from a file that adopts a number of pfe primitives, mostly for ^Forth inlining. The pfe version of BEGIN-EXTERNAL emits source for pfe setup that includes def-config.h, defines some extra register variables, and includes pfe-base.h. That defines pfe types, defines things like SP, and provides prototypes for pfe subroutines like mmul(). The ^Forth defining words prepend "p4_" to the labels they parse, to satisfy pfe.

/*     ^Forth:  Kernel Words
         File:  kernel.hf
       Author:  David.N.Williams@umich.edu
      License:  LGPL

The parts of this code not derived from PFE are 
Copyright (C) 2001 by David N. Williams
*/

BEGIN-EXTERNAL
{"
#include <pfe/double-sub.h>
#include <string.h>
"}

/* No REQUIRES. */
END-EXTERNAL

MAKE-INDEX app "Kernel Word Set"
decimal

\ *** System Variables

\ CORE
ext-variable: >in "TO_IN"
ext-variable: base "BASE"

\ FIG
ext-variable: csp "CSP"

\ *** Constants

\ CORE EXT
-1 constant: true "true"
 0 constant: false "false"

\ COMMON
defm: cell "cell"  ( -- PFE_SIZEOF_CELL ) 
  *--SP = PFE_SIZEOF_CELL; ;defm

defm: -cell "minus_cell"  ( -- -PFE_SIZEOF_CELL ) 
  *--SP = -PFE_SIZEOF_CELL; ;defm

\ *** Glossary

\ Many of these primitives are copied from pfe.

\ FIG
defm: !csp "store_csp"  ( -- )
  p4_CSP = SP;
;defm

\ CORE
defm: ! "store"  ( n addr -- )
  *(p4cell *) SP[0] = SP[1];  SP += 2;
;defm 

defm: @ "fetch"  ( addr -- n )
  *SP = *(p4cell *) *SP;
;defm

defm: * "star"  ( n1 n2 -- n )
  SP[1] = SP[1] * SP[0];  SP += 1;
;defm

defm: */ "star_slash"  ( n1 n2 n3 -- n4 )
  *(fdiv_t *) &SP[0] =
      p4_d_fmdiv (p4_d_mmul (SP[2], SP[1]), SP[0]);
  SP[2] = SP[0];  SP += 2;
;defm

If the word */ were to be used in a high-level def: word in another ^Forth file, that file would need an explicit "#include <pfe/double-sub.h>" statement, besides "REQUIRES kernel.hf", in its preamble, to get the pfe prototypes for p4_d_fmdiv() and p4_d_mmul(), since preambles don't nest.

4.2 Mixing example

The second excerpt is the prime number benchmark from a ^Forth version of the Gforth collection of four traditional benchmarks. It illustrates higher level definitions, and a couple of specialized uses of c{ and }c to mix C and Forth code in such definitions. More discussion follows the code.

/*     ^Forth:  Gforth Benchmarks
         File:  gfbench.hf
   Derived by:  david.n.williams@umich.edu
      License:  Public Domain  (We're guessing that includes
                the original benchmark codes.)
*/

BEGIN-EXTERNAL

{"
/* No extra include's. */
"}

REQUIRES kernel.hf

END-EXTERNAL

\ debug
\ ext-def: .s "dot_s"

MAKE-INDEX app "Gforth Benchmarks Word Set"

/* *** siev.fs *** */

\ : SECS TIME&DATE  SWAP 60 * + SWAP 3600 * +  NIP NIP NIP ;

\ CREATE FLAGS 8190 ALLOT
8190 create-allot: flags "flags"
8190 constant: #flags "sharp_flags"  \ for primes up to 16,384

\ FLAGS 8190 + CONSTANT EFLAG
variable: eflag "eflag"

def: primes "primes"  ( -- #primes ) 
  flags #flags one fill  zero three ( #primes odd)
  eflag @ flags
  DO ( #primes odd) i c@
    IF ( prime) dup i + ( hole) dup eflag @ <
      IF ( hole) eflag @ swap
        DO zero i c! ( prime) dup +LOOP
      ELSE ( hole) drop
      THEN ( #primes prime) swap 1+ swap
    THEN ( odd) two +
  LOOP ( odd) drop ( #primes)
;def

/* This is intended to be a fairly direct translation
   of PRIMES above.
*/
def: cprimes "c_primes"  ( -- #primes )
c{
  int primes = 0, odd = 3;
  char *p = (char *) &flags, *q = (char *) eflag, *r; 

  memset (p, 1, sharp_flags);
  while (p < q){
    if (*p){ /* odd is prime */
/*  *--SP = odd; }c . c{ */ /* print primes for debug */
      r = p + odd;
      if (r < q){
        while (r < q){
          *r = 0;  r += odd;}}
      primes += 1;}
    odd += 2; p++;}
  *--SP = primes;
}c ;def

1000 constant: #iters "sharp_iters"
\ 1 constant: #iters "sharp_iters"  \ debug
def: benchmark "benchmark"  ( -- )
  zero #iters zero DO primes nip LOOP ;def

def: cbenchmark "c_benchmark"  ( -- )
  zero #iters zero DO cprimes nip LOOP ;def

\ SECS BENCHMARK . SECS SWAP - CR . .( secs)
def: sieve-main "sieve_main"  ( -- )
  flags #flags + eflag !
  benchmark ( . ) drop
;def

def: csieve-main "c_sieve_main"  ( -- )
  flags #flags + eflag !
  cbenchmark ( . ) drop
;def

The examples above of C/Forth mixing within a definition are degenerate cases, where all or nearly all of the code is C. Note the debugging line near the middle of cprimes. When not commented out, it illustrates how to mix C and Forth in the same definition, albeit with only one word of Forth.

The words zero, one, and three in primes are defined with constant: in kernel.hf.

The word cprimes is a pretty direct, hand-coded C translation of primes which should nevertheless optimize pretty well. We've done that for all four traditional benchmarks, so we could compare pfe, Gforth, ^Forth, and pure C execution times. The complete file and results for a Macintosh OS X system are included in our Forth archive.

Ertl and Maierhofer included hand-coded C versions of the Gforth benchmarks in their earlier Forth2C studies.

4.3 Scoping example

Finally, here is an excerpt from the bubble sort benchmark in gfbench.hf to illustrate the use of host[ and ]host:

\ 6000 constant elements ( -- int)
6000 constant: elements "elements"

\ align create list elements cells allot
host[ 6000 cells ]host create-allot: list "list"

We could not do this in translator scope, because cells is defined in kernel.hf to emit C source.