next up previous contents
Next: CHAPTER.5: NUMERICAL AND Up: CHAPTER.4: PROCEDURES IN Previous: Tracing procedures

Defining macros and syntax words to extend the language.

Macros

Macro procedures can be used to extend the syntax of Pop-11. They are evaluated at compile time (ie when read in by the compiler).

A macro typically reads in items (e.g. words, strings, numbers) from the current program text input stream and then creates new text items to replace those read in. The original items need not have been legal Pop-11. The replaced items will be legal Pop-11, unless they include macros, which can cause further changes.

An example would be a macro procedure to read in two variable names and produce instructions to swap their values. It transforms illegal Pop-11 expressions like,

    swap x y;
    swap list1 list2;
into legal Pop-11 like

    x, y -> x -> y;
    list1, list2 -> list1 -> list2;
which are equivalent to

    x, y -> (y, x);
    list1, list2 -> (list2, list1);
as explained in Chapter 2.

This is how the macro "swap" could be defined.

    define macro swap var1 var2;
        ;;; Transform "swap x y" into "x, y -> x, -> y"

        var1, ",", var2, "->", var1, "->", var2

    enddefine;
This could be used as follows:

    vars x = 99, y = 66;
    x, y =>
    ** 99 66

    swap x y;
    x, y =>
    ** 66 99
The word "nonmac" can precede a macro name to prevent its evaluation, as in this example

    nonmac swap =>
    ** <procedure swap>
For more details and further examples see HELP MACRO.

Macros can be recursive

Macros can invoke other macros (or themselves) recursively. If the text put on the input stream by one macro includes another macro, the second macro's procedure will be invoked when it is read in. This can happen several times, causing the program text to be rearranged several times, until all that is left is legal Pop-11 syntax words and other text items, whereupon the program will simply be translated into machine code, i.e. compiled.

Unlike some languages with macros, Pop-11 macros can operate after some user programs have been compiled, and may therefore invoke those programs. (In fact a macro is just a user program.) This means that users can write macros that do very complex manipulations of the text stream, as in Lisp. An example of a fairly complex macro is "switchon" defined in a Pop-11 library program LIB SWITCHON, and described in HELP SWITCHON.

Macro arguments are text items, not expressions

NOTE: The arguments of a macro are bound to the individual following TEXT ITEMS, not the two following EXPRESSIONS: so if the macro swap has two arguments then the expression

    'swap x y' is correct
but

    'swap hd(x) hd(y)' is wrong
because the latter would give the next two text items "hd" and "(" as arguments to swap. So it would be transformed into:

    hd, (, -> hd, -> (, x) hd(y)
which will cause a mishap to occur, in this case:

    ;;; MISHAP - COMPILING ASSIGNMENT TO PROTECTED IDENTIFIER
    ;;; INVOLVING:  hd
It would be possible to write a macro that coped with expressions like

    swap hd(x) hd(y)
by translating them into

    hd(x), hd(y) -> hd(x) -> hd(y)
This would require making use of the procedure pop11_comp_expr and the variable pop_syntax_only. However this will not be explained here, as there is a more powerful facility already available, explained in the next section.

See REF PROGLIST, REF POPCOMPILE, REF VMCODE for full details.

Using "define :inline" to define the SWAP macro

It is possible to use a facility described in HELP INLINE to produce the required version of swap, which accepts arbitrary expressions. The following defines a new macro SWAP

    define :inline SWAP(expr1, expr2);

        expr1, expr2 -> (expr2, expr1)

    enddefine;

    ;;; check that it is a macro

    identprops("SWAP") =>
    ** macro
It can be invoked in the format

    SWAP(<expresion1>, <expression2>)
and will cause the values of the two expressions to be exchanged.

We can test this as follows. First the easy case, where the expressions are individual identifiers.

    vars x = 66, y = 88;

    x,y =>
    ** 66 88

    SWAP(x, y);
    x,y =>
    ** 88 66
The format can also be used when the expressions are complex, e.g.

    vars list = [cat dog mouse];

    SWAP(list(1), list(3));

    list =>
    ** [mouse dog cat]
Note that SWAP thus defined is a macro, not a procedure. It is run at compile time, i.e. while procedures are being compiled, not at run time. Unlike ordinary procedures, it does not evaluate its two arguments and then do something with the values. Rather, during the compilation process it creates new code using the expressions to which it has been applied, not the values. This can be seen by tracing the macro

    trace SWAP;
    SWAP(list(1), list(3));
    > SWAP
    < SWAP list ( 1 ) , list ( 3 ) -> ( list ( 3 ) , list ( 1 ) )
Because the "define :inline" form produces macros, not ordinary procedures, users are STRONGLY advised to use names that give a reminder that something unusal is involved, e.g. by choosing upper case names, as in the example above.

For more information see HELP INLINE.

Note on efficiency of macros

Often the use of macros produces "inline" code instead of procedure calls and can therefore be very efficient. However, in some cases the use of macros defined as above can be very wasteful as the repetition of complex expressions may cause the same thing to be recomputed unnecessarily.

For example

    vars list1 = [cat mouse dog flea], list2 = [red orange yellow];

    SWAP( hd(tl(tl(list1))) , hd(tl(tl(list2))) );

    list1, list2 =>
    ** [cat mouse yellow flea] [red orange dog]
To achieve that exchange, the use of SWAP produces the equivalent of the following Pop-11:

    hd(tl(tl(list1))), hd(tl(tl(list2)))
        -> ( hd(tl(tl(list2))), hd(tl(tl(list1))) );
I.e. tl(tl(list1)) is computed twice, and tl(tl(list2)) is computed twice, once before and once after the assignment arrow. It would have been more efficient to store their values in two variables, temp1, and temp2, and then produced code to do:

    hd(temp1), hd(temp2) -> ( hd(temp2), hd(temp1) );
Note, however that it would not do to replace the above with these two expressions:

    hd(tl(tl(list1))) -> temp1; hd(tl(tl(list2))) -> temp2;

    temp1, temp2 -> (temp2, temp1);
Why not? In order to answer that question, the reader needs a deep understanding of how expressions are evaluated, how complex structures are referred to, and what the relationship is between a variable and its value.

If you try all the above you will see that all it does is assign two values to temp1 and temp2 and then swap their values, without altering the contents the lists, list1 and list2. For that it is essential to run the updater of the procedure hd, which is done by the expression after the assignment arrow here:

    hd(temp1), hd(temp2) -> ( hd(temp2), hd(temp1) );

Syntax procedures

Syntax procedures provide even more powerful facilities for extending the syntax of Pop-11.

Unlike macros they do not take arguments. Like macros, they too are executed at compile time. System syntax words, such as "if", "define", "until", "for", etc. correspond to syntax procedures: they typically read in some or all of a Pop-11 expression and tell the compiler how to understand it.

There are other syntax words that do nothing except flag the end of a complex syntactic construction. Examples are ")", "endif", "enddefine" and other closing brackets. Some syntax words, like "then", "do", "elseif" are used to indicate intermediate expression boundaries in complex expressions, as in

    if <condition> then <action> elseif <condition> then <action> endif
The difference between syntax words and macros is quite subtle. Both macros and syntax words read items in from the program input text stream called "proglist", a dynamic list, defined in REF PROGLIST. A macro will rearrange text items, put them back on the front of proglist, then let the compiler continue.

A syntax word will typically invoke a procedure that reads in items from proglist, and then gives instructions to the compiler to create machine code corresponding to the text read in. Strictly speaking, the syntax procedure "plants code" for the Poplog virtual machine PVM (described briefly in Chapter 2) rather than the physical machine on which Pop-11 is running. The virtual machine instructions are then translated to real machine code by a VM compiler.

This means that a MACRO definition can extend Pop-11 syntax only by introducing new constructs that translate into existing legal Pop-11, whereas a SYNTAX word can introduce any syntactic extension that is capable of being translated into PVM instructions. Since the Poplog virtual machine is extremely general, syntax words can extend Pop-11 in enormously varied ways. (For example, this is how languages like ML and Prolog are defined in Pop-11, languages which could not be translated into Pop-11).

This is both a strength of the language, and a problem: it means on the one hand that extensions relevant to particular applications are relatively easily produced, in order to make programs easier to develop and maintain. But it also means that the ways in which Pop-11 programs can vary syntactically are so great that it is difficult to produce tools for manipulating them automatically, e.g. cross reference tools, program validation tools etc. However, to some extent the power of the language is such that users can provide their own tools to suit their own extensions.

Example: defining a syntax word: loop

If we did not already have the construct "repeat <actions> endrepeat" we could define a new syntax format "loop <actions> endloop", as follows.

    ;;; first define the closing bracket to be a syntax word
    global constant syntax endloop;

    ;;; Now define the main syntax word

    define syntax loop;
        ;;; First define labels for the beginnings and ends of the loop.
        ;;; We need the end label for use with "quitloop"

        lvars Lab, Endlab;
        sysNEW_LABEL() -> Lab;      pop11_loop_start(Lab);
        sysNEW_LABEL() -> Endlab;   pop11_loop_end(Endlab);

        ;;; Plant the first label
        sysLABEL(Lab);
        ;;; read and compile up to "endloop"
        pop11_comp_stmnt_seq_to([endloop]) ->;
        ;;; Now code to jump back to the beginning of the loop
        sysGOTO(Lab);
        ;;; Now plant the send label, for clean exits from the loop
        sysLABEL(Endlab);
    enddefine;

    ;;; test it.

    vars x = 3;
    loop
        x =>
        x - 1 -> x;
        if x < 0 then quitloop() endif;
    endloop;
    ** 3
    ** 2
    ** 1
    ** 0
More detailed information is provided in the online files: HELP SYNTAX and REF SYNTAX.

We now give examples of some of the more commonly used forms of syntax.



next up previous contents
Next: CHAPTER.5: NUMERICAL AND Up: CHAPTER.4: PROCEDURES IN Previous: Tracing procedures



Aaron Sloman
Fri Jan 2 03:17:44 GMT 1998