Next Previous Contents

12. Macros

12.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.

12.2 Macros without parameters

In its 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

12.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 occurrence 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 expanded). 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

Macro parameters may optionally be enclosed into curly braces. This allows the inclusion of tokens that would otherwise terminate the parameter (the comma in case of a macro parameter).

        .macro  foo     arg1, arg2
                ...
        .endmacro

                foo     ($00,x)         ; Two parameters passed
                foo     {($00,x)}       ; One parameter passed

In the first case, the macro is called with two parameters: '($00' and 'x)'. The comma is not passed to the macro, since it is part of the calling sequence, not the parameters.

In the second case, '($00,x)' is passed to the macro, this time including the comma.

12.4 Detecting parameter types

Sometimes it is nice to write a macro that acts differently depending on the type of the argument supplied. An example would be a macro that loads a 16 bit value from either an immediate operand, or from memory. The .MATCH and .XMATCH functions will allow you to do exactly this:

        .macro  ldax    arg
                .if (.match (.left (1, {arg}), #))
                    ; immediate mode
                    lda     #<(.right (.tcount ({arg})-1, {arg}))
                    ldx     #>(.right (.tcount ({arg})-1, {arg}))
                .else
                    ; assume absolute or zero page
                    lda     arg
                    ldx     1+(arg)
                .endif
        .endmacro

Using the .MATCH function, the macro is able to check if its argument begins with a hash mark. If so, two immediate loads are emitted, Otherwise a load from an absolute zero page memory location is assumed. Please note how the curly braces are used to enclose parameters to pseudo functions handling token lists. This is necessary, because the token lists may include commas or parens, which would be treated by the assembler as end-of-list.

The macro can be used as

        foo:    .word   $5678
        ...
                ldax    #$1234          ; X=$12, A=$34
        ...
                ldax    foo             ; X=$56, A=$78

12.5 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 immediately:

        .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

12.6 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
                inc     addr
                bne     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 possible solution is the command .LOCAL. It 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 can solve the problem above by using .LOCAL:

        .macro  inc16   addr
                .local  Skip            ; Make Skip a local symbol
                inc     addr
                bne     Skip
                inc     addr+1
        Skip:                           ; Not visible outside
        .endmacro

Another solution is of course to start a new lexical block inside the macro that hides any labels:

        .macro  inc16   addr
        .proc
                inc     addr
                bne     Skip
                inc     addr+1
        Skip:
        .endproc
        .endmacro

12.7 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 described 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  DEBUG   message
                .out    message
        .endmacro

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

12.8 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.

12.9 Deleting macros

Macros can be deleted. This will not work if the macro that should be deleted is currently expanded as in the following non working example:

        .macro  notworking
                .delmacro       notworking
        .endmacro

        notworking              ; Will not work

The commands to delete classic and define style macros differ. Classic macros can be deleted by use of .DELMACRO, while for .DEFINE style macros, .UNDEFINE must be used. Example:

        .define value   1
        .macro  mac
                .byte   2
        .endmacro

                .byte   value           ; Emit one byte with value 1
                mac                     ; Emit another byte with value 2

        .undefine value
        .delmacro mac

                .byte   value           ; Error: Unknown identifier
                mac                     ; Error: Missing ":"

A separate command for .DEFINE style macros was necessary, because the name of such a macro is replaced by its replacement list on a very low level. To get the actual name, macro replacement has to be switched off when reading the argument to .UNDEFINE. This does also mean that the argument to .UNDEFINE is not allowed to come from another .DEFINE. All this is not necessary for classic macros, so having two different commands increases flexibility.


Next Previous Contents