(special form)
.MACROprocname
:input1
:input2
...
A macro is a special kind of procedure whose output is evaluated as instructions in the context of the macro's caller. .MACRO is like TO except that the new procedure becomes a macro.
Macros are useful for inventing new control structures comparable to REPEAT, IF, and so on. Such control structures can almost, but not quite, be duplicated by ordinary procedures. For example, here is an ordinary procedure version of REPEAT:
TO MY.REPEAT :num :instructions IF :num=0 [STOP] RUN :instructions MY.REPEAT :num-1 :instructions END
This version works fine for most purposes, for example
MY.REPEAT 5 [PRINT "hello]
But it doesn't work if the instructions to be run include OUTPUT, STOP, or LOCAL. For example, consider this procedure:
TO EXAMPLE PRINT [Guess my secret word. You get three guesses.] REPEAT 3 [IF READWORD = "secret [PRINT "Right! STOP]] PRINT [Sorry, the word was "secret"!] END
This procedure works as written, but if MY.REPEAT were used instead of REPEAT, it wouldn't work because the STOP would stop MY.REPEAT instead of stopping EXAMPLE as desired.
The solution is to make MY.REPEAT a macro. Instead of carrying out the computation, a macro must output a list containing instructions. The contents of that list are evaluated as if they appeared in place of the call to the macro. Here's a macro version of REPEAT:
.MACRO MY.REPEAT :num :instructions IF :num=0 [OUTPUT []] OUTPUT SENTENCE :instructions (LIST "MY.REPEAT :num-1 :instructions) END
Every macro is an operation; all macros always output something. Even in the base case, MY.REPEAT outputs an empty instruction list.
To see how the MY.REPEAT macro works, let's take the example
MY.REPEAT 5 [PRINT "hello]
For this example, FMSLogo first runs MY.REPEAT as a normal procedure. MY.REPEAT outputs the following instruction list:
[PRINT "hello MY.REPEAT 4 [PRINT "hello]]
Then, because MY.REPEAT is a macro, FMSLogo immediately runs this instruction list. This prints "hello" once and invokes another repetition.
The technique just shown, although fairly easy to understand, is too slow because each repetition constructs an instruction list for evaluation. Another approach is to make MY.REPEAT a macro that works just like the non-macro version unless the instructions to be repeated includes OUTPUT or STOP:
.MACRO MY.REPEAT :num :instructions CATCH "repeat.catchtag [OUTPUT REPEAT.DONE RUNRESULT [REPEAT1 :num :instructions]] OUTPUT [] END TO REPEAT1 :num :instructions IF :num=0 [THROW "repeat.catchtag] RUN :instructions .MAYBEOUTPUT REPEAT1 :num-1 :instructions END TO REPEAT.DONE :repeat.result IF EMPTYP :repeat.result [OUTPUT [STOP]] OUTPUT LIST "OUTPUT QUOTED FIRST :repeat.result END
If the instructions do not include STOP or OUTPUT, then REPEAT1 reaches its base case and invokes THROW. As a result, MY.REPEAT's last instruction line outputs the empty list, so the second evaluation of the macro does nothing. But if a STOP or OUTPUT happens, then REPEAT.DONE outputs a STOP or OUTPUT instruction that is re-run in the caller's context.
The macro-defining commands have names starting with a dot because macros are an advanced feature of Logo. It's easy to get in trouble by defining a macro that doesn't terminate, or by failing to construct the instruction list properly.
LISP users should note that Logo macros are not special forms. That is, the inputs to the macro are evaluated normally, as they would be for any other procedure. It's only the output from the macro that's handled unusually.
Here's how you can implement LOCALMAKE with .MACRO:
.MACRO MY.LOCALMAKE :name :value OUTPUT (LIST "LOCAL WORD "" :name "APPLY ""MAKE (LIST :name :value) ) END
It's used this way:
TO TRY MY.LOCALMAKE "myvar "hello PRINT :myvar END
MY.LOCALMAKE outputs the list:
[LOCAL "myvar APPLY "MAKE [myvar hello]]
MY.LOCALMAKE uses APPLY to avoid deciding whether the second input to MAKE requires a quotation mark before it.
In this case it would—MAKE "myvar "hello
—but the quotation mark would be wrong if the value were a list.
It's often convenient to use the ` operation to construct the instruction list:
.MACRO MY.LOCALMAKE :name :value OUTPUT `[LOCAL ",:name APPLY "MAKE [,:name ,:value]] END
On the other hand, ` is slow, since it's tree-recursive and written in Logo.