3.7. The Context Stack

Having labels that are local to a macro definition is sometimes not quite powerful enough: sometimes you want to be able to share labels between several macro calls. An example might be a REPEATUNTIL loop, in which the expansion of the REPEAT macro would need to be able to refer to a label which the UNTIL macro had defined. However, for such a macro you would also want to be able to nest these loops.

The NASM preprocessor provides this level of power by means of a context stack. The preprocessor maintains a stack of contexts, each of which is characterised by a name. You add a new context to the stack using the %push directive, and remove one using %pop. You can define labels that are local to a particular context on the stack.

3.7.1. %push and %pop: Creating and Removing Contexts

The %push directive is used to create a new context and place it on the top of the context stack. %push requires one argument, which is the name of the context. For example:

%push    foobar

This pushes a new context called foobar on the stack. You can have several contexts on the stack with the same name: they can still be distinguished.

The directive %pop, requiring no arguments, removes the top context from the context stack and destroys it, along with any labels associated with it.

3.7.2. Context-Local Labels

Just as the usage %%foo defines a label which is local to the particular macro call in which it is used, the usage %$foo is used to define a label which is local to the context on the top of the context stack. So the REPEAT and UNTIL example given above could be implemented by means of:

%macro repeat 0

    %push   repeat
    %$begin:

%endmacro

%macro until 1

        j%-1    %$begin
    %pop

%endmacro

and invoked by means of, for example,

        mov     cx,string
        repeat
        add     cx,3
        scasb
        until   e

which would scan every fourth byte of a string in search of the byte in AL.

If you need to define, or access, labels local to the context below the top one on the stack, you can use %$$foo, or %$$$foo for the context below that, and so on.

3.7.3. Context-Local Single-Line Macros

The NASM preprocessor also allows you to define single-line macros which are local to a particular context, in just the same way:

%define %$localmac 3

will define the single-line macro %$localmac to be local to the top context on the stack. Of course, after a subsequent %push, it can then still be accessed by the name %$$localmac.

3.7.4. %repl: Renaming a Context

If you need to change the name of the top context on the stack (in order, for example, to have it respond differently to %ifctx), you can execute a %pop followed by a %push; but this will have the side effect of destroying all context-local labels and macros associated with the context that was just popped.

The NASM preprocessor provides the directive %repl, which replaces a context with a different name, without touching the associated macros and labels. So you could replace the destructive code

%pop
%push   newname

with the non-destructive version %repl newname.

3.7.5. Example Use of the Context Stack: Block IFs

This example makes use of almost all the context-stack features, including the conditional-assembly construct %ifctx, to implement a block IF statement as a set of macros.

%macro if 1

    %push if
    j%-1  %$ifnot

%endmacro

%macro else 0

  %ifctx if
        %repl   else
        jmp     %$ifend
        %$ifnot:
  %else
        %error  "expected `if' before `else'"
  %endif

%endmacro

%macro endif 0

  %ifctx if
        %$ifnot:
        %pop
  %elifctx      else
        %$ifend:
        %pop
  %else
        %error  "expected `if' or `else' before `endif'"
  %endif

%endmacro

This code is more robust than the REPEAT and UNTIL macros given in Раздел 3.7.2, because it uses conditional assembly to check that the macros are issued in the right order (for example, not calling endif before if) and issues a %error if they’re not.

In addition, the endif macro has to be able to cope with the two distinct cases of either directly following an if, or following an else. It achieves this, again, by using conditional assembly to do different things depending on whether the context on top of the stack is if or else.

The else macro has to preserve the context on the stack, in order to have the %$ifnot referred to by the if macro be the same as the one defined by the endif macro, but has to change the context’s name so that endif will know there was an intervening else. It does this by the use of %repl.

A sample usage of these macros might look like:

        cmp     ax,bx

        if ae
               cmp     bx,cx

               if ae
                       mov     ax,cx
               else
                       mov     ax,bx
               endif

        else
               cmp     ax,cx

               if ae
                       mov     ax,cx
               endif

        endif

The block-IF macros handle nesting quite happily, by means of pushing another context, describing the inner if, on top of the one describing the outer if; thus else and endif always refer to the last unmatched if or else.