Literal and friends compile data values into the current
definition. You can also write words that compile other words into the
current definition. E.g.,
: compile-+ ( -- ) \ compiled code: ( n1 n2 -- n ) POSTPONE + ; : foo ( n1 n2 -- n ) [ compile-+ ] ; 1 2 foo .
This is equivalent to : foo + ; (see foo to check this).
What happens in this example? Postpone compiles the compilation
semantics of + into compile-+; later the text interpreter
executes compile-+ and thus the compilation semantics of +, which
compile (the execution semantics of) + into
foo.
Compiles the compilation semantics of name.
Compiling words like compile-+ are usually immediate
(see How to define immediate words) so you do not have to switch
to interpret state to execute them; modifying the last example
accordingly produces:
: [compile-+] ( compilation: --; interpretation: -- ) \ compiled code: ( n1 n2 -- n ) POSTPONE + ; immediate : foo ( n1 n2 -- n ) [compile-+] ; 1 2 foo .
You will occasionally find the need to POSTPONE several words;
putting POSTPONE before each such word is cumbersome, so Gforth
provides a more convenient syntax: ]] ... [[. This
allows us to write [compile-+] as:
Switch into postpone state: All words and recognizers are
processed as if they were preceded by postpone.
Postpone state ends when [[ is recognized.
The unusual direction of the brackets indicates their function:
]] switches from compilation to postponing (i.e., compilation
of compilation), just like ] switches from immediate execution
(interpretation) to compilation. Conversely, [[ switches from
postponing to compilation, ananlogous to [ which switches from
compilation to immediate execution.
The real advantage of ]] ... [[ becomes apparent when
there are many words to POSTPONE. E.g., the word
compile-map-array (see Advanced macros) can be
written much shorter as follows:
: compile-map-array ( compilation: xt -- ; run-time: ... addr u -- ... ) \ at run-time, execute xt ( ... x -- ... ) for each element of the \ array beginning at addr and containing u elements {: xt: xt :} ]] cells over + swap ?do i @ xt 1 cells +loop [[ ; : sum-array ( addr u -- n ) 0 [ ' + compile-map-array ] ;
If you then say see sum-array, it shows the following code:
In addition to ]]...[[, this example shows off some
other features:
xt:) local xt;
mentioning such a local inside ]]...[[ results in
compile,ing the xt in the local, i.e., ]] xt [[ is
equivalent to action-of xt compile,.
]]...[[
compiles the value of the local, i.e., ]] x [[ is equivalent to
x ]] literal [[.
1 inside ]]...[[. This
results in postponeing the 1, i.e. compiling it when
compile-map-array is run. ]] 1 [[ is equivalent to
1 ]] literal [[.
compile-map-array is run (while sum-array is
compiled), 1 cells is compiled and optimized into #8 by
Gforth’s constant folding.
Note that parsing words such as s\" don’t parse at postpone
time and therefore not inside ]]...[[. Instead of
s\" mystring\n" you can use the string recognizer and write
"mystring\n", which works inside ]]...[[.
Likewise, the parsing word ['] does not parse inside
]]...[[ while the recognizer notation starting with
` works inside ]]...[[.
But if you prefer to use s\" (or have a parsing word that has
no recognizer replacement), you can do it by switching back to
compilation:
Definitions of ]] and friends in Standard Forth are provided in
compat/macros.fs.
Immediate compiling words are similar to macros in other languages (in particular, Lisp). The important differences to macros in, e.g., C are:
postpone etc. deal with the language at a
higher level than strings; name binding and other resolutions happen
at macro definition time, so you can avoid the pitfalls of name
collisions that can happen in C macros. Of course, Forth is a liberal
language and also allows you to define text-based macros using
evaluate (see below).
You may want the macro to compile a number into a word. The word to do
it is literal, but you have to postpone it, so its
compilation semantics take effect when the macro is executed, not when
it is compiled:
: [compile-5] ( -- ) \ compiled code: ( -- n ) 5 POSTPONE literal ; immediate : foo [compile-5] ; foo .
A more convenient, but less portable way to write [compile-5]
is:
You may want to pass parameters to a macro, that the macro should
compile into the current definition. If the parameter is a number, then
you can use postpone literal (similar for other values).
If you want to pass a word that is to be compiled, the usual way is to
pass an execution token and compile, it:
: twice ( xt -- ) \ compiled code: ... -- ... dup compile, compile, ; : 2+ ( n1 -- n2 ) [ ' 1+ twice ] ;
Append the semantics represented by xt to the current
definition. When the resulting code fragment is run, it behaves
the same as if xt is executed.
A more convenient, but less portable way to write twice is:
An alternative that allows you to pass the compilation semantics as parameters is to use the compilation token (see Compilation token). The same example in this technique:
: ctwice ( ... ct -- ... ) \ compiled code: ... -- ... 2dup 2>r execute 2r> execute ; : 2+ ( n1 -- n2 ) [ ``1+ name>compile ctwice ] ;
In particular, you want to pass the compilation token and
execute it in case of words without interpretation semantics or
with non-default and non-immediate compilation semantics (i.e., not
for 1+).
In the example above 2>r and 2r> ensure that ctwice
works even if the executed compilation semantics has an effect on the
data stack.
You can also define complete definitions with these words; this provides
an alternative to using does> (see User-defined Defining Words). E.g., instead of
you could define
: curry+ ( n1 "name" -- ) \ name execution: ( n2 -- n1+n2 ) >r : r> POSTPONE literal POSTPONE + POSTPONE ; ; -3 curry+ 3- see 3-
The sequence >r : r> is necessary, because : puts a
colon-sys on the data stack that makes everything below it unaccessible.
A more convenient, but less portable way to define curry+ is:
This way of writing defining words is sometimes more, sometimes less
convenient than using does> (see Advanced does> usage example). One advantage of this method is that it can be optimized
better, because the compiler knows that the value compiled with
literal is fixed, whereas the data associated with a
created word can be changed. But if you use
does>/set-does> with set-optimizer, the resulting
code will be at least as good, and the defined word will consume less
memory.
Legacy word. Use postpone instead. Works like
postpone if name has non-default compilation
semantics. If name has default compilation semantics
(i.e., is a normal word), compiling [compile] name
is equivalent to compiling name (i.e. [compile] is
redundant in this case).
flag is true if and only if there is an active colon
definition to which compile, and friends would append
code.
Some people argue in favour of evaluate-based macros like:
We do not recommend using this technique, because it moves a number of decisions to macro-use time, when the setup may be different than you intended:
rec-meta to
reduce potential ambiguity.
rec-scope to eliminate the search
order dependency, but there is no easy way to eliminate the changes in
the wordlists, except to avoid evaluate-based macros
altogether.
State may be different, and if this influences the behaviour of
the macro, it will be state-smart (see state-smartness).
Base may be different, potentially changing the meaning of
multi-digit numbers. Use number prefixes to avoid this problem.
An advantage of an evaluate-based macros is that you may be
able to use it interpretively, whereas postpone-based macros
are useful only inside colon definitions and other macros. Our
recommended alternative is to instead use an inline definition
(see Inline Definitions) or define a colon definition and an
optimizer for it (e.g., see the my2dup examples in
User-defined compile,).