3.4. Conditional Assembly

Similarly to the C preprocessor, NASM allows sections of a source file to be assembled only if certain conditions are met. The general syntax of this feature looks like this:

%if<condition>
    ; some code which only appears if <condition> is met
%elif<condition2>
    ; only appears if <condition> is not met but <condition2> is
%else
    ; this appears if neither <condition> nor <condition2> was met
%endif

The %else clause is optional, as is the %elif clause. You can have more than one %elif clause as well.

3.4.1. %ifdef: Testing Single-Line Macro Existence

Beginning a conditional-assembly block with the line %ifdef MACRO will assemble the subsequent code if, and only if, a single-line macro called MACRO is defined. If not, then the %elif and %else blocks (if any) will be processed instead.

For example, when debugging a program, you might want to write code such as

          ; perform some function
%ifdef DEBUG
          writefile 2,"Function performed successfully",13,10
%endif
          ; go and do something else

Then you could use the command-line option -D DEBUG to create a version of the program which produced debugging messages, and remove the option to generate the final release version of the program.

You can test for a macro not being defined by using %ifndef instead of %ifdef. You can also test for macro definitions in %elif blocks by using %elifdef and %elifndef.

3.4.2. %ifmacro: Testing Multi-Line Macro Existence

The %ifmacro directive operates in the same way as the %ifdef directive, except that it checks for the existence of a multi-line macro.

For example, you may be working with a large project and not have control over the macros in a library. You may want to create a macro with one name if it doesn’t already exist, and another name if one with that name does exist.

The %ifmacro is considered true if defining a macro with the given name and number of arguments would cause a definitions conflict. For example:

%ifmacro MyMacro 1-3

     %error "MyMacro 1-3" causes a conflict with an existing macro.

%else

     %macro MyMacro 1-3

             ; insert code to define the macro

     %endmacro

%endif

This will create the macro MyMacro 1-3 if no macro already exists which would conflict with it, and emits a warning if there would be a definition conflict.

You can test for the macro not existing by using the %ifnmacro instead of %ifmacro. Additional tests can be performed in %elif blocks by using %elifmacro and %elifnmacro.

3.4.3. %ifctx: Testing the Context Stack

The conditional-assembly construct %ifctx ctxname will cause the subsequent code to be assembled if and only if the top context on the preprocessor’s context stack has the name ctxname. As with %ifdef, the inverse and %elif forms %ifnctx, %elifctx and %elifnctx are also supported.

For more details of the context stack, see Раздел 3.7. For a sample use of %ifctx, see Раздел 3.7.5.

3.4.4. %if: Testing Arbitrary Numeric Expressions

The conditional-assembly construct %if expr will cause the subsequent code to be assembled if and only if the value of the numeric expression expr is non-zero. An example of the use of this feature is in deciding when to break out of a %rep preprocessor loop: see Раздел 3.5 for a detailed example.

The expression given to %if, and its counterpart %elif, is a critical expression (see Раздел 2.8).

%if extends the normal NASM expression syntax, by providing a set of relational operators which are not normally available in expressions. The operators =, <, >, <=, >= and <> test equality, less-than, greater-than, less-or-equal, greater-or-equal and not-equal respectively. The C-like forms == and != are supported as alternative forms of = and <>. In addition, low-priority logical operators &&, ^^ and || are provided, supplying logical AND, logical XOR and logical OR. These work like the C logical operators (although C has no logical XOR), in that they always return either 0 or 1, and treat any non-zero input as 1 (so that ^^, for example, returns 1 if exactly one of its inputs is zero, and 0 otherwise). The relational operators also return 1 for true and 0 for false.

3.4.5. %ifidn and %ifidni: Testing Exact Text Identity

The construct %ifidn text1,text2 will cause the subsequent code to be assembled if and only if text1 and text2, after expanding single-line macros, are identical pieces of text. Differences in white space are not counted.

%ifidni is similar to %ifidn, but is case-insensitive.

For example, the following macro pushes a register or number on the stack, and allows you to treat IP as a real register:

%macro  pushparam 1

  %ifidni %1,ip
        call    %%label
  %%label:
  %else
        push    %1
  %endif

%endmacro

Like most other %if constructs, %ifidn has a counterpart %elifidn, and negative forms %ifnidn and %elifnidn. Similarly, %ifidni has counterparts %elifidni, %ifnidni and %elifnidni.

3.4.6. %ifid, %ifnum, %ifstr: Testing Token Types

Some macros will want to perform different tasks depending on whether they are passed a number, a string, or an identifier. For example, a string output macro might want to be able to cope with being passed either a string constant or a pointer to an existing string.

The conditional assembly construct %ifid, taking one parameter (which may be blank), assembles the subsequent code if and only if the first token in the parameter exists and is an identifier. %ifnum works similarly, but tests for the token being a numeric constant; %ifstr tests for it being a string.

For example, the writefile macro defined in Раздел 3.3.3 can be extended to take advantage of %ifstr in the following fashion:

%macro writefile 2-3+

  %ifstr %2
        jmp     %%endstr
    %if %0 = 3
      %%str:    db      %2,%3
    %else
      %%str:    db      %2
    %endif
      %%endstr: mov     dx,%%str
                mov     cx,%%endstr-%%str
  %else
                mov     dx,%2
                mov     cx,%3
  %endif
                mov     bx,%1
                mov     ah,0x40
                int     0x21

%endmacro

Then the writefile macro can cope with being called in either of the following two ways:

        writefile [file], strpointer, length
        writefile [file], "hello", 13, 10

In the first, strpointer is used as the address of an already-declared string, and length is used as its length; in the second, a string is given to the macro, which therefore declares it itself and works out the address and length for itself.

Note the use of %if inside the %ifstr: this is to detect whether the macro was passed two arguments (so the string would be a single string constant, and db %2 would be adequate) or more (in which case, all but the first two would be lumped together into %3, and db %2,%3 would be required).

The usual %elifXXX, %ifnXXX and %elifnXXX versions exist for each of %ifid, %ifnum and %ifstr.

3.4.7. %error: Reporting User-Defined Errors

The preprocessor directive %error will cause NASM to report an error if it occurs in assembled code. So if other users are going to try to assemble your source files, you can ensure that they define the right macros by means of code like this:

%ifdef SOME_MACRO
    ; do some setup
%elifdef SOME_OTHER_MACRO
    ; do some different setup
%else
    %error Neither SOME_MACRO nor SOME_OTHER_MACRO was defined.
%endif

Then any user who fails to understand the way your code is supposed to be assembled will be quickly warned of their mistake, rather than having to wait until the program crashes on being run and then not knowing what went wrong.