A translator is a table of three actions: interpretation, compilation, and postponing. Each action consumes the data shown as input in the stack effect of the translator, possibly performs additional parsing (but most translators don’t parse), and then performs some action, which may have a stack effect on its own:
translate-nt
performs the respective semantics of nt, and
these semantics may have a stack effect of their own (the stack effect
of postponing a word is ‘( -- )’).
translate-num
) is to push (actually keep) the literal on
the stack.
You define a translator with
Defines name, a translator containing int-xt,
comp-xt, and post-xt. In all the following
descriptions data is the data that the recognizer pushes
below the translator.
Executing int-xt ‘( ... data -- ... )’ performs
the interpretation semantics represented by data xt-name.
Executing comp-xt ‘( ... data -- ... )’ performs
the compilation semantics represented by data xt-name.
Executing post-xt ‘( data -- )’ compiles the
compilation semantics represented by data xt-name.
To make this a little more concrete, here is an implementation for
translate-num
:
' noop ( x -- x ) \ int-xt ' lit, ( compilation: x -- ; run-time: -- x ) \ comp-xt :noname lit, postpone lit, ; ( postponing: x -- ; run-time: -- x ) \ post-xt translate: translate-num
If a recognizer for a single-cell literal (e.g., rec-tick
)
matches the input string, it pushes the value x of the literal
(the xt of the ticked word in case of rec-tick
) on the data
stack and the xt of translate-num
. When the interpretation
semantics is needed, int-xt is execute
d, and x stays on
the stack. For the compilation semantics, x is compiled into the
current definition as literal.
For postponing, more time levels are involved: at text-interpretation
time (when the recognizer runs and the translator action is performed)
the current definition is d1. When d1 runs, the current
definition is d227. The post-xt of
the translate-num
implementation above first compiles x
into d1 and also compiles lit,
into d1 (that’s the
postpone lit,
part). When d1 runs, it pushes x and
then the lit,
compiles x into d2.
Many literal translators follow this scheme.
A translator that is quite different is translate-nt
. Here’s
an implementation:
: name-intsem ( ... nt -- ... ) name>interpret execute-exit ; : name-compsem ( ... nt -- ... ) name>compile execute-exit ; : name-compcompsem ( nt -- ) lit, postpone name-compsem ; ' name-intsem ' name-compsem ' name-compcompsem translate: translate-nt
Name-intsem
performs the interpretation semantics of nt, by
getting the xt of the interpretation semantics and executing it. Here
execute-exit
is used, in order for return-stack words to work
(that’s a Gforth 1.0 feature). Also, in Gforth 1.0 all words have
interpretation semantics, so the result of name-interpret
is
not tested for 0.
Name-compsem
performs the compilation semantics of nt.
Name-compcompsem
compiles the compilation semantics of nt.
This is achieved by compiling nt and name-compsem
into the
current definition d1. When d1 runs, the result performs the
compilation semantics of nt at that time.
If there is no current definition when something is compiled, Gforth outputs a warning.