Previous: Literals, Up: Compiling words


5.12.2 Macros

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.1

postpone       "name" –         core       “postpone”

Compiles the compilation semantics of name.

Compiling words like compile-+ are usually immediate (or similar) 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 occassionally 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:

     : [compile-+] ( compilation: --; interpretation: -- )
       ]] + [[ ; immediate

]]              gforth       “right-bracket-bracket”

switch into postpone state

[[              gforth       “left-bracket-bracket”

switch from postpone state to compile state

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 Tutorial) 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 }
       ]] cells over + swap ?do
         i @ [[ xt compile,
       1 cells ]]L +loop [[ ;

This example also uses ]]L as a shortcut for ]] literal. There are also other shortcuts

]]L       postponing: x – ; compiling: – x         gforth       “right-bracket-bracket-l”

Shortcut for ]] literal.

]]2L       postponing: x1 x2 – ; compiling: – x1 x2         gforth       “right-bracket-bracket-two-l”

Shortcut for ]] 2literal.

]]FL       postponing: r – ; compiling: – r         gforth       “right-bracket-bracket-f-l”

Shortcut for ]] fliteral.

]]SL       postponing: addr1 u – ; compiling: – addr2 u         gforth       “right-bracket-bracket-s-l”

Shortcut for ]] sliteral; if the string already has been allocated permanently, you can use ]]2L instead.

Note that parsing words don't parse at postpone time; if you want to provide the parsed string right away, you have to switch back to compilation:

     ]] ... [[ s" some string" ]]2L ... [[
     ]] ... [[ ['] + ]]L ... [[

Definitions of ]] and friends in ANS 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:

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 .

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:

     : twice1 ( xt -- ) \ compiled code: ... -- ...
       dup compile, compile, ;
     
     : 2+ ( n1 -- n2 )
       [ ' 1+ twice1 ] ;

compile,       xt –         unknown       “compile,”

An alternative available in Gforth, that allows you to pass compile-only words as parameters is to use the compilation token (see Compilation token). The same example in this technique:

     : twice ( ... ct -- ... ) \ compiled code: ... -- ...
       2dup 2>r execute 2r> execute ;
     
     : 2+ ( n1 -- n2 )
       [ comp' 1+ twice ] ;

In the example above 2>r and 2r> ensure that twice 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

     : curry+ ( n1 "name" -- )
         CREATE ,
     DOES> ( n2 -- n1+n2 )
         @ + ;

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.

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.


Fußnoten

[1] A recent RFI answer requires that compiling words should only be executed in compile state, so this example is not guaranteed to work on all standard systems, but on any decent system it will work.