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 REPEAT
… UNTIL
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.
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.
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.
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
.
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
.
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
.