Next Previous Contents

7. Macros

7.1 Introduction

Macros may be thought of as "parametrized super instructions". Macros are sequences of tokens that have a name. If that name is used in the source file, the macro is "expanded", that is, it is replaced by the tokens that were specified when the macro was defined.

7.2 Macros without parameters

In it's simplest form, a macro does not have parameters. Here's an example:

        .macro  asr             ; Arithmetic shift right
                cmp     #$80    ; Put bit 7 into carry
                ror             ; Rotate right with carry
        .endmacro

The macro above consists of two real instructions, that are inserted into the code, whenever the macro is expanded. Macro expansion is simply done by using the name, like this:

        lda     $2010
        asr
        sta     $2010

7.3 Parametrized macros

When using macro parameters, macros can be even more useful:

        .macro  inc16   addr
                clc
                lda     addr
                adc     #$01
                sta     addr
                lda     addr+1
                adc     #$00
                sta     addr+1
        .endmacro

When calling the macro, you may give a parameter, and each occurence of the name "addr" in the macro definition will be replaced by the given parameter. So

        inc16   $1000

will be expanded to

                clc
                lda     $1000
                adc     #$01
                sta     $1000
                lda     $1000+1
                adc     #$00
                sta     $1000+1

A macro may have more than one parameter, in this case, the parameters are separated by commas. You are free to give less parameters than the macro actually takes in the definition. You may also leave intermediate parameters empty. Empty parameters are replaced by empty space (that is, they are removed when the macro is exanded). If you have a look at our macro definition above, you will see, that replacing the "addr" parameter by nothing will lead to wrong code in most lines. To help you, writing macros with a variable parameter list, there are some control commands:

.IFBLANK tests the rest of the line and returns true, if there are any tokens on the remainder of the line. Since empty parameters are replaced by nothing, this may be used to test if a given parameter is empty. .IFNBLANK tests the opposite.

Look at this example:

        .macro  ldaxy   a, x, y
        .ifnblank       a
                lda     #a
        .endif
        .ifnblank       x
                ldx     #x
        .endif
        .ifnblank       y
                ldy     #y
        .endif
        .endmacro

This macro may be called as follows:

        ldaxy   1, 2, 3         ; Load all three registers

        ldaxy   1, , 3          ; Load only a and y

        ldaxy   , , 3           ; Load y only

There's another helper command for determining, which macro parameters are valid: .PARAMCOUNT This command is replaced by the parameter count given, including intermediate empty macro parameters:

        ldaxy   1               ; .PARAMCOUNT = 1
        ldaxy   1,,3            ; .PARAMCOUNT = 3
        ldaxy   1,2             ; .PARAMCOUNT = 2
        ldaxy   1,              ; .PARAMCOUNT = 2
        ldaxy   1,2,3           ; .PARAMCOUNT = 3

7.4 Recursive macros

Macros may be used recursively:

        .macro  push    r1, r2, r3
                lda     r1
                pha
        .if     .paramcount > 1
                push    r2, r3
        .endif
        .endmacro

There's also a special macro to help writing recursive macros: .EXITMACRO This command will stop macro expansion immidiately:

        .macro  push    r1, r2, r3, r4, r5, r6, r7
        .ifblank        r1
                ; First parameter is empty
                .exitmacro
        .else
                lda     r1
                pha
        .endif
                push    r2, r3, r4, r5, r6, r7
        .endmacro

When expanding this macro, the expansion will push all given parameters until an empty one is encountered. The macro may be called like this:

        push    $20, $21, $32           ; Push 3 ZP locations
        push    $21                     ; Push one ZP location

7.5 Local symbols inside macros

Now, with recursive macros, .IFBLANK and .PARAMCOUNT, what else do you need? Have a look at the inc16 macro above. Here is it again:

        .macro  inc16   addr
                clc
                lda     addr
                adc     #$01
                sta     addr
                lda     addr+1
                adc     #$00
                sta     addr+1
        .endmacro

If you have a closer look at the code, you will notice, that it could be written more efficiently, like this:

        .macro  inc16   addr
                clc
                lda     addr
                adc     #$01
                sta     addr
                bcc     Skip
                inc     addr+1
        Skip:
        .endmacro

But imagine what happens, if you use this macro twice? Since the label "Skip" has the same name both times, you get a "duplicate symbol" error. Without a way to circumvent this problem, macros are not as useful, as they could be. One solution is, to start a new lexical block inside the macro:

        .macro  inc16   addr
        .proc
                clc
                lda     addr
                adc     #$01
                sta     addr
                bcc     Skip
                inc     addr+1
        Skip:
        .endproc
        .endmacro

Now the label is local to the block and not visible outside. However, sometimes you want a label inside the macro to be visible outside. To make that possible, there's a new command that's only usable inside a macro definition: .LOCAL. .LOCAL declares one or more symbols as local to the macro expansion. The names of local variables are replaced by a unique name in each separate macro expansion. So we could also solve the problem above by using .LOCAL:

        .macro  inc16   addr
                .local  Skip            ; Make Skip a local symbol
                clc
                lda     addr
                adc     #$01
                sta     addr
                bcc     Skip
                inc     addr+1
        Skip:                           ; Not visible outside
        .endmacro

7.6 C style macros

Starting with version 2.5 of the assembler, there is a second macro type available: C style macros using the .DEFINE directive. These macros are similar to the classic macro type speified above, but behaviour is sometimes different:

Let's look at a few examples to make the advantages and disadvantages clear.

To emulate assemblers that use "EQU" instead of "=" you may use the following .DEFINE:

        .define EQU     =

        foo     EQU     $1234           ; This is accepted now

You may use the directive to define string constants used elsewhere:

        ; Define the version number
        .define VERSION         "12.3a"

        ; ... and use it
        .asciiz VERSION

Macros with parameters may also be useful:

        .define DEBUG(message)  .out    message

        DEBUG   "Assembling include file #3"

Note that, while formal parameters have to be placed in braces, this is not true for the actual parameters. Beware: Since the assembler cannot detect the end of one parameter, only the first token is used. If you don't like that, use classic macros instead:

        .macro  message
                .out    message
        .endmacro

(This is an example where a problem can be solved with both macro types).

7.7 Characters in macros

When using the -t option, characters are translated into the target character set of the specific machine. However, this happens as late as possible. This means that strings are translated if they are part of a .BYTE or .ASCIIZ command. Characters are translated as soon as they are used as part of an expression.

This behaviour is very intuitive outside of macros but may be confusing when doing more complex macros. If you compare characters against numeric values, be sure to take the translation into account.


Next Previous Contents