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

> `

## 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 solution is, to start a new lexical block inside the macro:

> `

>             .macro  inc16   addr
>             .proc
>                     inc     addr
>                     bne     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

> `

## 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:

- Macros defined with `.DEFINE` may not span more than a line. You may use line continuation (see `.LINECONT`) to spread the definition over more than one line for increased readability, but the macro itself may not contain an end-of-line token.
- Macros defined with `.DEFINE` share the name space with classic macros, but they are detected and replaced at the scanner level. While classic macros may be used in every place, where a mnemonic or other directive is allowed, `.DEFINE` style macros are allowed anywhere in a line. So they are more versatile in some situations.
- `.DEFINE` style macros may take parameters. While classic macros may have empty parameters, this is not true for `.DEFINE` style macros. For this macro type, the number of actual parameters must match exactly the number of formal parameters. To make this possible, formal parameters are enclosed in braces when defining the macro. If there are no parameters, the empty braces may be omitted.
- Since `.DEFINE` style macros may not contain end-of-line tokens, there are things that cannot be done. They may not contain several processor instructions for example. So, while some things may be done with both macro types, each type has special usages. The types complement each other.

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

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

Next Previous Contents