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