Defining Macros

Here are some extra examples of macros. Please refer to the book, or to your class notes, for a description of how macros differ from procedures.

Here is a macro that moves one memory location to another, using AX as a temprorary holding area.

mov_mem	macro	mem1,mem2
	push	ax
	mov	ax,mem2
	mov	mem1,ax
	pop	ax
endm

Here are some examples of calling the macro. Assume that num and total have been declared as words.

mov_mem	num,total
mov_mem	num,[bx+4]
mov_mem	[si],[di]

The names of the parameters are mem1 and mem2. In each example, the arguments from the macro call are substituted for these parameters in the macro body.

	push	ax
	mov	ax,total
	mov	num,ax
	pop	ax

	push	ax
	mov	ax,[bx+4]
	mov	num,ax
	pop	ax

	push	ax
	mov	ax,[di]
	mov	[si],ax
	pop	ax

It is important to note that the argument [bx+4] cannot be typed with any spaces in it, otherwise the macro will not assemble. If you wanted to include spaces in an argument, then use the < > to indicate that everything inside is the argument.

mov_mem	num,<[bx + 4]>

Notice that this macro must always be called with two arguments.

A macro to initialize memory

Many times it is necessary to initalize data in a procedure. It is possible to use a macro to minimize the number of statments that need to be written. This macro will initialze three variable/value pairs.

init	macro	pair1,pair2,pair3
	mov	pair1
	mov	pair2
	mov	pair3
endm

It could be called many ways.

init	<ax,0>,<bx,0>,<cx,0>
init	<ax,cx>,<num,7>,<[bx+2],dx>

Notice that in each call above, there are three parameters, separated by commas. The < > allow commas to be included as part of the argument. These calls would expand as

	mov	ax,0
	mov	bx,0
	mov	cx,0

	mov	ax,cx
	mov	num,7
	mov	[bx+2],dx

A macro to initialize memory that allows for blank arguments using IFNB

It is possible to send blank arguments to a macro. For example, it is possible to make a call like

init <ax,0>

Notice that there are 2 commas in the call. That is because the macro definition has three parameters: pair1, pair2, pair3. By only passing one argument, the other two are passed as blank.

There is a problem when this macro is expanded. Enevthough it is possible to make the call, it doesn't mean that the expanded macro will make any sense.

	mov	ax,0
	mov	
	mov	

Notice that pair2 and pair3 were blank, so there is nothing after the mov for the last two instructions.

To fix this, it is necessary to use conditional assembly. Conditional assembly uses an if-like structure to determine when to generate machine code. The staement needed here is IFNB, which stands for If Not Blank. If the argument is blank, then the code inside the IFNB .. ENDIF will not be generated.

init	macro	pair1,pair2,pair3
	IFNB 	<pair1>
		mov	pair1
	ENDIF
	IFNB	<pair2>
		mov	pair2
	ENDIF
	IFNB	<pair3>
		mov	pair3
	ENDIF
endm

This macro could be called with 1 non-blank argument, 2 non-blank arguments, or 3 non-blank arguments.

init <ax,0>
init <cx,ax>,<num,7>
init <ax,9>,<dh,al>,<[bx],bh>

These calls would expand to

	mov	ax,0
	
	mov	cx,ax
	mov	num,7

	mov	ax,9
	mov	dh,al
	mov	[bx],bh

A macro to initialize memory that uses IFB to set a default value for an argument

Conditional assembly can be used to set default arguments: if an argument is blank, then use a default value. Looking back at mov_mem, it is apparent that only words can be moved, since AX is being used. Here is a version that sets a default size of word, but allows bytes to be moved as well. Notice the addtion of the extra parameter size. The macro uses the conditional assembly statement IFB: If Blank.

mov_mem	macro	mem1,mem2,size
	IFB	<size>
		textequ	temp,<ax>
	ELSE
		textequ	temp,<ah>
	ENDIF
	push	ax
	mov	temp,mem2
	mov	mem1,temp
	pop	ax
endm

It doesn't matter what value the size has. If it is non-blank, then it indicates that a byte move is occuring.

A macro to initialize memory that has two default parameters and uses & to insert a parameter into a string

Here's a more complicated example that lets you specify which register to use as a temporary. Pass the letter a,b,c, or d to use ax, bx, cx, or dx. The size is also passed. If the size is not blank, then the ah,bh,ch, or dh are used. The & allows the reg paramter to be expanded into the middle of a string.

mov_mem	macro	mem1,mem2,size,reg
	IFB	<size>
		IFB	<reg>
			temp	textequ	<ax>
		ELSE
			temp	textequ	<reg&x>
		ENDIF
	ELSE
		IFB	<reg>
			temp	textequ	<ah>
		ELSE
			temp	textequ	<reg&h>
		ENDIF
	ENDIF
	IFB	<reg>
		save	textequ	<ax>
	ELSE
		save	textequ	<reg&x>
	ENDIF
	push	save
	mov	temp,mem2
	mov	mem1,temp
	pop	save
endm

This would be called as follows. Assume that num and total are words, and first and last are bytes.

mov_mem num,total,,d
mov_mem first,last,byte,c
mov_mem num,total

And expanded as

	push	dx
	mov	dx,total
	mov	num,dx
	pop	dx
	
	push	cx
	mov	ch,last
	mov	first,ch
	pop	cx

	push	ax
	mov	ax,total
	mov	num,ax
	pop	ax

Notice that the first macro call has a blank parameter. It is necessary to include the blank. Otherwise, the macro would think that d was the size, and not the reg.

A macro that will define a string that belongs to a menu

This macro implements many of the above ideas. It declares a string that could be part of a menu. The name of the menu is used as the label for the string. Also, there are two assembler variables that kepp track of the maximum width of all strings that are created for this menu, as well as how many strings are in the menu. It uses the conditional assembly statement IFNDEF to determine if the length and width assembler variables should initialized to 0. It also uses the conditional assembly statement IF along with the relational operator GT.

setMenuOpt	macro	label,num,string
	IFNDEF	label&Width
		label&Width = 0
		label&Length = 0
	ENDIF	
	label&Length = label&Length + 1
	label&num	db	string
	IF ($-label&num) GT label&Width
		label&Width = ($-label&num)
	ENDIF
endm

It could be called as follows

setMenuOpt	main,1,<"1: (I)nput a list of numbers",0dh,0ah,"$">
setMenuOpt	main,2,<"2: (D)isplay the numbers that were entered",0dh,0ah,"$">
setMenuOpt	main,3,<"3: (F)ind the average of the numbers",0dh,0ah,"$">
setMenuOpt	main,4,<"4: (P)rint the even numbers above the average",0dh,0ah,"$">
setMenuOpt	main,5,<"5: (E)xit the program",0dh,0ah,"$">
setMenuOpt	main,6,<0dh,0ah,"$">
setMenuOpt	main,7,<"Please make a selection (1-5): ","$">

This would generate the following and would create two variables mainWidth with the value 48 and mainLength with the value 7

main1 db "1: (I)nput a list of numbers",0dh,0ah,"$"
main2 db "2: (D)isplay the numbers that were entered",0dh,0ah,"$"
main3 db "3: (F)ind the average of the numbers",0dh,0ah,"$"
main4 db "4: (P)rint the even numbers above the average",0dh,0ah,"$"
main5 db "5: (E)xit the program",0dh,0ah,"$"
main6 db "0dh,0ah,"$"
main7 db "Please make a selection (1-5): ","$"

A macro to generate a for loop

Here is a macro that generates a for loop. It can only have one statement, but that could be a call to a procedure. It usees the local directive to force the assembler to generate new, unique labels for top and done each time the macro is called. Without the local directive, the macro would generate duplicate labels if it was called more than once inside the same procedure.

forloop macro start,stop,incr,statement
	local	top,done
	push	ax
	mov	ax,start
top:	cmp	ax,stop
	jge	done
	statement
	add	ax,incr
	jmp	top
done:
	pop	ax
endm

This could be called as

xor bx,bx
forloop 5,10,3,<add bx,ax>

It would generate the following. It would accumulate the loop control variable ax.

	push	ax
	mov	ax,5
top1:	cmp	ax,10
	jge	done1
	statement
	add	ax,3
	jmp	top1
done1:
	pop	ax