Assembler Macros

The WinAPE assembler provides Macros to allow complex or repeated code to be generated easily by defining custom directives which can optionally include parameters.

Macro definition


Macros are defined using the format:

macro name [parameter{,parameter...}]

A macro can contain any valid code, including labels and local labels (prefixed with @).

The macro name and all parameters must be defined using valid symbol characters ('A'..'Z', 'a'..'z', '_', '0'..'9').

Each parameter passed is substituted literally into the code inside the macro when it is encountered as a whole word. For this reason, it is preferable to use parameter names which are not normally used in either text strings or as register names. The example below shows a bad macro definition since it uses register names:

macro test a, b, c
  print a
  print "hello a bc"
  print 'c'
  db a, b, c
  db a + b, c + 'a'
  LD H,A
mend

testit 1, '2', 3

The output produced by this would be:

1
hello 1 bc
3
000010  0000  01 32 03        db 1, '2', 3
000010  0003  33 34           db 1 + '2', 3 + '1'
000010  0005  26 01           LD H,1


Notice that the value 1 has been substituted for all A words in the macro definition, including the a inside the string "hello a bc" which has been converted to "hello 1 bc", the 'a' inside quotes in the second db a + b, c + 'a' which has been converted to db 1 + '2', 3 + '1' and also the valid Z80 instruction LD H,A which has been converted to LD H,1. The bc inside the second print is not substituted since there is no parameter called bc.

Overriding Reserved Words


A macro can redefine standard directives or even Z80 instructions. At any time in the assembler, in order to use the standard version simply precede it with an exclamation mark. For example:

macro ld dest,src
  if "dest" = "de"
    if "src" = "bc"
      !ld d,b
      !ld e,c
    elseif "src" = "hl"
      !ld d,h
      !ld e,l
    else
      !ld dest,src
  else
    !ld dest,src
  endif
mend


As a general rule it is not advisable to redefine the function of standard directives or Z80 instructions as it can make your code very hard to maintain.

Local Labels


If a macro contains a label and it is used more than once the assembler will attempt to redefine the same symbol, which will cause an error. For example:

macro decnz_bad
  or a
  jr z,silly1
  dec a
  .silly1
mend

decnz_bad
decnz_bad  ;; <- This line will cause a Duplicate Definition error


By using a local label (a symbol prefixed with an @ character) the assembler will scope the label to the current macro only. So a correct definition for a macro to decrement A if it's not zero could be:

macro decnz_a
  or a
  jr z,@leave
  dec a
  @leave
mend

decnz_a
decnz_a


The output of the above decnz_a two lines would be:

000008  0000                decnz_a
000008  0000  B7              or a
000008  0001  28 01           jr z,@leave
000008  0003  3D              dec a
000008  0004                  @leave
000009  0004                decnz_a
000009  0004  B7              or a
000009  0005  28 01           jr z,@leave
000009  0007  3D              dec a
000009  0008                  @leave

Curly Brackets


Curly brackets allow parameters to be included in the middle of another word. For example, consider the following macro:

macro curly param
  print "H{param}o"
mend

curly ell

The assembler would print out the word Hello.

Nested Macros


Macros can use previously defined macros internally. For example:

macro swap reg1, reg2
  ld a,reg1
  ld reg1,reg2
  ld reg2,a
mend

macro swap_bcde
  swap b,c
  swap d,e
mend

swap_bcde


The assembler output for the swap_bcde line is:

000012  0000                swap_bcde
000012  0000                  swap b,c
000012  0000  78              ld a,b
000012  0001  41              ld b,c
000012  0002  4F              ld c,a
000012  0003                  swap d,e
000012  0003  7A              ld a,d
000012  0004  53              ld d,e
000012  0005  5F              ld e,a
      


Combining Macros with the LET directive


Since let allows a symbol to be redefined, global symbols can be shared between multiple macros. For example:

macro pixel_address x, y
  @y7 equ y and 7 * #800
  @addr equ y / 8 * #50 + #c000 + @y7
  let pix_addr = x / 8 + @addr
  let pix_mask = #80
  repeat x and 7
    let pix_mask = pix_mask / 2
  rend
mend

macro get_pixel_address addr_reg, mask_reg, x_pos, y_pos
  pixel_address x_pos,y_pos
  ld addr_reg,pix_addr
  ld mask_reg,pix_mask
mend

get_pixel_address hl,e,233,36


The assembler output for the get_pixel_address hl,e,233,36 line of code is:

000017  0000                get_pixel_address hl,e,235,36
000017  0000                  pixel_address 235,36
000017  0000  (2000)          @y7 equ 36 and 7 * #800
000017  0000  (E140)          @addr equ 36 / 8 * #50 + #c000 + @y7
000017  0000  (E15D)          let pix_addr = 235 / 8 + @addr
000017  0000  (0080)          let pix_mask = #80
000017  0000                  repeat 235 and 7
000017  0000                    let pix_mask = pix_mask / 2
000017  0000                  rend
000017  0000  (0040)            let pix_mask = pix_mask / 2
000017  0000  (0020)            let pix_mask = pix_mask / 2
000017  0000  (0010)            let pix_mask = pix_mask / 2
000017  0000  21 5D E1        ld hl,pix_addr
000017  0003  1E 10           ld e,pix_mask
000019  0005                end
      

That's the equivalent of ld hl,#e15d:ld e,#10 but the assembler works out the address and mask rather than you doing it manually. Unfortunately, the above code won't cope with expressions being passed in as parameters (eg. pixel_address left + 10, top + 20) because the assembler expression evaluation is always performed left-to-right with no support for parenthesis. This is as a result of maintaining Maxam source compatibility, so we would have to modify the macro definition using more local symbols or let to re-assign. For example:

macro pixel_address x, y
  @y1 equ y
  @y7 equ @y1 and 7 * #800
  @addr equ @y1 / 8 * #50 + #c000 + @y7
  let pix_addr = x;
  let pix_addr = pix_addr / 8 + @addr
  let pix_mask = #80
  repeat x and 7
    let pix_mask = pix_mask / 2
  rend
mend

Note that repeat and while are also treated as macros and have their own local symbols. It is not currently possible to access the local symbols of the parent macro, so it would not be possible to modify @y1, @y7 or @addr inside the repeat loop.

String Comparisons


The WinAPE assembler has basic string comparison support, allowing conditional compilation by comparing macro parameters to string values. For example:

macro get_edge_address edge
  if "edge" = "left"
    ld hl,#c050
  elseif "edge" = "top"
    ld hl,#c028
  elseif "edge" = "right"
    ld hl,#c09e
  else
    ld hl,#c0c8
  endif
mend

get_edge_address right