Macros

A Macro can be compared with a chorus in a song. Most of the time the chorus in a song is only defined once. The simple text chorus will mark those places where the chorus text should be pasted.
The SB-Assembler treats Macros in much the same way. However there are some important differences.
Don't worry if you don't understand the concept of macros (yet). You can still create wonderful programs without them. Once you become an expert in assembly programming you might consider coming back here to see if it macros make more sense to you than before.

You can also think of a Macro being a subroutine for the assembler. There is a clear difference between a Macro (a subroutine for the assembler) and a normal subroutine for the target processor.
A normal subroutine for the target processor will exist only once in program memory and is called with a special instruction every time we need it.
A Macro is effectively pasted into the source file every time we call it (expand the Macro). This could produce code for the target processor every time the Macro is expanded.

A Macro must be defined before it can be used. There is no such thing as forward referenced Macros.
No code is generated during the definition of a Macro. The syntax check is only limited to finding out whether you used illegal directives or whether the Macro definition is ready. Labels in the label field will not be assigned during the definition of the Macro either.

When a Macro is called (expanded) all lines of the Macro will be interpreted one by one as if it were lines coming directly from a normal source file. At the end of the expansion of the Macro the assembler continues from the source file following the line which called the Macro expansion.

Why would we use Macros when normal subroutines are more efficient when it comes to memory use?

  • Macro code is flexible. You can supply one or more parameters to change the actual appearance of the Macro. This could produce different code every time the Macro is called.
  • Some functions can not be handled by normal subroutines. Think about functions that alter the processor's stack. A subroutine would not be able to find its return address anymore.
  • Some Macros produce only a limited number of processor instructions. CALL and RET overhead increases when subroutines are extremely short.
  • Sometimes the CALL and RET overhead simply takes too long in time critical applications. A Macro does not require CALL and RET, which saves some microseconds in exchange for a few more bytes of code.
  • Macros don't necessarily have to produce code. Sometimes a Macro is only used for some housekeeping activities.

Macro Definition

Macros are defined using two dedicated directives with the SB-Assembler. The .MA directive marks the start of the Macro definition and the .EM directive marks the end.
All program lines in between these two directives will be stored in the Macro definition, without being interpreted by the assembler yet.

The next example shows a Macro definition that pushes the registers P, A, X and Y of a 65C02 processor onto the stack:

PUSHALL   .MA          ; Start of the definition
          PHP          ; Save P register
          PHA          ; Save Accu
          PHX          ; Save X register
          PHY          ; Save Y register
          .EM          ; End of the definition

Please note that this example can't be replaced by a simple subroutine because that would make the return address inaccessible for that subroutine.

So the .MA directive starts the Macro definition. At the same time the Macro is given the unique name PUSHALL.
The Macro name appears in the label field of the program line. Optionally the Macro name may appear in the operand field instead, behind the .MA directive. This syntax is only a relic from the Apple ][ version of the SB-Assembler and is not recommended.
Please note that no code is generated during the Macro definition. The push instructions of the example will not be part of your program's code if you never expand the Macro later in your program. Not even the syntax is checked during the Macro definition. So you can type almost anything during a Macro definition without hearing a word of protest from the SB-Assembler. But rest assured, the SB-Assembler will protest on all errors once you try to use the Macro later.

NOTE: Macro names and Label names are stored separately in the SB-Assembler. This makes it perfectly legal to use Labels with the same name as Macro definitions.

The .EM directive finally terminates the Macro definition. Everything will be back to normal after that.

Please note that the directives .MA and .EM only signal the start and end of the Macro definition and will never be part of the definition itself.

For various reasons some directives are not allowed within a Macro definition between the .MA and .EM directives.

.MAMacro definition nesting is not allowed
.INA Macro definition may not span multiple source files
.CHNot allowed for the same reason as .IN
.BIThis is also not allowed
.ENWould also cause spanning (Allowed in Version 3)
.COWould use too much memory resources on older systems
.ECNot allowed for the same reason as .CO

In version 2 a single Macro definition may not be larger than 64k bytes (-16 bytes). Please tell me if that's not enough for you. Maybe I can throw in a good word for you at your favourite shrink. Alright, if 64k really isn't enough for you, then try version 3 because there is no limit there any more.

When the .EN directive is found during Macro expansion in Version 3 of the SB-Assembler, all nested Macro expansions will be terminated. Then the current source file is closed and assembly continues from the previous level of source file, if any. Otherwise assembly will be terminated all together.

Macro Expansion

Once you have defined a Macro you can call it as often as you need it. This calling of a Macro is called Expanding the Macro.

You expand a Macro by typing >macroname in the instruction field of a program line. The macroname is the name of the Macro you wish to expand at this point.
The normal assembly process is now interrupted. Instead of interpreting the next program line in your source file the SB-Assembler will now start interpreting the lines that were placed in the Macro's definition. The normal assembly process is resumed once all program lines of your Macro's definition are processed, i.e. when the Macro is completely expanded.

This is an example of a Macro expansion in a program:

TEST      NOP          Just an imaginary program line
          >PUSHALL
          NOP          Another imaginary program line

If you take a look at the List file of this segment of the program you could see something like this:

100 TEST       NOP          Just an imaginary program line
101            >PUSHALL
M1             PHP
M2             PHA
M3             PHX
M4             PHY
102            NOP          Another imaginary program line

The line numbers on the left indicate where we are in our program. This comes in handy when errors are detected in your program.
These line numbers are replaced by the letter M, followed by the Macro line number during the expansion of the Macro (Try to guess what the letter M stands for).

As you can see the expanding Macro uses the lines of the Macro definition literally. Well, almost literally. Maybe you've noticed that the comment fields that were present during the definition don't exist anymore after expansion.
This is because the comment fields were preceded by the semicolon ; symbol during the definition of the Macro. The SB-Assembler will not remember comment fields in a Macro definition that start with a ; symbol. This is done to reduce the size of the Macro symbol table, which used to be limited on older computers.
Other uses of the semicolon during a Macro definition can be a bit tricky. A semicolon preceded by anything but a space character is no problem. But a semicolon preceded by a space character will always truncate the program line immediately! This is even true when the semicolon is part of a literal string!

Don't use the ; symbol during the Macro definition if you want to keep the comment field in your definition. This will produce a larger symbol table though.

Version 3 of the SB-Assembler doesn't have the old DOS memory limitations. Therefore the ; has no special meaning to the Version 3 of the SB-Assembler. Everything is included in the Macro definition, even comments which are preceded by a semicolon.

A List Option exists to control the listing of expanding Macros (See .LI directive). The List Options MON and MOFF can switch the listing of expanding Macros on or off. Expanding Macros will not be listed to the list file if the MOFF option is chosen.
Please note that only expanding Macros are affected by this List Option. A Macro definition is not affected by it.

The example above will look something like this when the MOFF option is selected:

100 TEST       NOP          Just an imaginary program line
101            >PUSHALL
102            NOP          Another imaginary program line

It goes without saying that the Macro will be expanded, even though it is not listed!

Macro Labels

We will encounter a small problem if we want to define a label within a Macro. This label will be defined every time we expand the Macro. And it is not very likely that this label will get the same value every time we define it. This is unacceptable!
Does this mean that we can't define labels within a Macro? Of course we can, with Macro labels.

Here is a small example of the problem in 8051 code:

1  WAIT       .MA
2             MOV    R0,#10      ; Initialize delay
3  LOOP       NOP
4             DJNZ   R0,LOOP     ; Count down to 0
5             .EM
6
7  START      >WAIT              ; Expand the Macro once
M1            MOV    R0,#10
M2 LOOP       NOP
M3            DJNZ   R0,LOOP
8             >WAIT              ; Expand the Macro again
M1            MOV    R0,#10
M2 LOOP       NOP               <--- Produces extra definition error
M3            DJNZ   R0,LOOP    <--- Jumps back to the first LOOP

As you can see the label LOOP is defined twice in this example and obviously with a different value the second time.
Local labels offer only a limited solution to the problem. In this example they wouldn't have solved the problem because there was no Global label definition in between the two Macro expansions.

Please note that the label LOOP was not declared during the definition of the Macro yet. The label LOOP is first declared when the Macro is expanded for the first time.

The solution to the problem is found in Macro Labels.
A Macro label is very similar to a Local Label but has one significant difference. A Macro label always starts with a colon : , followed by the label's name, which obeys to the same rules as Local Labels. Apart from this obvious difference the internal handling differs a little as well.

Every Macro expansion gets a unique internal number. Every time you declare a Macro Label this internal Macro number is appended to the Macro Label's name, making the label unique again.

In Version 2 of the SB-Assembler the internal Macro number ranges from 1 to 255, excluding 46 (decimal point). This immediately shows the limitation of the Macro Labels.
One could say that every single Macro Label can be defined by 254 different expanding Macros. But that is not entirely true because every expanding Macro will increment the internal Macro number, even if that particular Macro Label is not defined inside all the expanding Macros. B.T.W. Expanding Macros that did not define a Macro label at all will not increment the internal Macro number.
The internal Macro number is reset every time you define a new Global label (which is usually done outside an expanding Macro), giving you the opportunity to expand an infinite number of Macros in your program.
This means that between two adjacent Global labels you can expand up to 254 different Macros without getting into trouble. In my opinion this is more than enough.
The SB-Assembler doesn't protest if you let the Macro number grow up to and above 255. If you're interested you can try to crash the program by expanding more than 254 Macros behind one single Global label. Don't forget to declare at least one Macro label in each expansion if you do.
There is no limit to the number of different Macro Labels that is assigned during the expansion of a Macro, as long as every Macro Label gets it's own unique name.

Version 3 of the SB-Assembler has almost 4 times a bigger range for Macro labels than Version 2. Up to 999 Macro expansions can exist in between two adjacent Global labels before you'll get into trouble.
No error will be given if the you exceed the maximum number of 999 expansions between two Global labels. Feel free to crash the program if you like.

The next example shows the solution that Macro Labels offer for the problem found in the previous example:

1  WAIT       .MA
2             MOV    R0,#10      ; Initialize delay
3  :LOOP      NOP
4             DJNZ   R0,:LOOP    ; Count down to 0
5             .EM
6
7  START      >WAIT              ; Expand the Macro once
M1            MOV    R0,#10
M2 :LOOP      NOP
M3            DJNZ   R0,:LOOP
8             >WAIT              ; Expand the Macro again
M1            MOV    R0,#10
M2 :LOOP      NOP              <--- No problem this time
M3            DJNZ   R0,:LOOP  <--- Correct jump this time

The program in the example above looks a lot like the one in the previous example, but it is working so much better this time.

Apart from the advantages of Macro Labels they also have some disadvantages. Macro Labels "live" only inside the expanding Macro. So they can only be reached from within the Macro that defined them.

The program example below will not work:

1  WAIT       .MA
2             MOV    R0,#10      ; Initialize delay
3  :LOOP      NOP
4             DJNZ   R0,:LOOP    ; Count down to 0
5             .EM
6
7  START      >WAIT              ; Expand the Macro once
M1            MOV    R0,#10
M2 :LOOP      NOP
M3            DJNZ   R0,:LOOP
8             JMP    :LOOP    <--- Lable doesn't exist any more

Parameters

Macros can also handle parameters in the SB-Assembler. A parameter is a variable piece of text that is pasted in the expanding Macro at predefined positions.

Parameters follow the Macro expansion call in the operand field. Multiple parameters are separated from each other by commas. Empty parameters are also allowed.
A Macro expansion call may be followed by 0 to 10 different parameters. A parameter that contains spaces, commas, or semicolons, must be enclosed in quotes " or single quotes ' . A parameter may contain upper and lower case characters.

A parameter number is assigned to every parameter entered in the operand field of the Macro expansion call. The first parameter will be parameter ]1, the second is ]2, the third is ]3, .., etc. Please note that the tenth parameter will be parameter ]0, not ]10 because that would require 2 digits.

The parameters are referred to by the parameter identifiers ]x, where x is the parameter number that was assigned to it. The parameter numbers range from ]1 to ]9 for the first 9 parameters and ]0 means the tenth parameter (not the first).
This means that a total of 10 different parameters can be used per Macro expansion. You can specify more than 10 parameters after the Macro expansion call, however you would only be able to reach the first 10.

The parameter ]x is inserted in the expanding Macro line every time the SB-Assembler encounters the parameter identifier ]x (where x is a digit from 1 to 0).

I think this calls for a small example to clarify it all. Imagine a Z80 routine to copy a piece of memory. The Z80 has a very nice instruction that handles this fully automatically. But this instruction does require the proper initiation of some registers. A Macro with three parameters will simplify the process for the programmer.

1  COPY      .MA     SOURCE,DEST,LENGTH
2             LD     BC,]3       ; Number of bytes to copy
3             LD     HL,]1       ; Source pointer
4             LD     DE,]2       ; Destination pointer
5             LDIR               ; Start copying
6             .EM
7
8             .CR    Z80
9
10 RANGE1     .EQ    $1000       Source's begin address
11 RANGE2     .EQ    $2000       Destination's begin address
12
13 START      >COPY  RANGE1,RANGE2,$200
M1            LD     BC,$200
M2            LD     HL,RANGE1
M3            LD     DE,RANGE2
M4            LDIR
14            NOP                The rest of the program

The text behind the .MA directive are only comments and may be anything you like. You could type the name of your favourite movie star here, but it's more useful to type the names of the expected parameters in this case. This also shows the expected order in which the parameters should be given.

During the expansion fo the Macro parameter ]3 is completely replaced by the text $200. Please note that this is a text, not a value $200. Eventually the SB-Assembler treats that text as a value when it tries to interpret the composed program line.
The parameter ]1 is replaced by RANGE1, which again is a text and not the label's value. Once the program line is created it is passed to the parser, which finally will interpret RANGE1 as a label's name and thus replaces it by the label's value.
The parameter ]2 is finally replaced by the text RANGE2.

Please note that the Z80 doesn't need the # symbol to represent immediate addressing mode, although it is permitted to use it in the SB-Assembler version of the Z80 Cross-Overlay.

Please also note that it is perfectly legal to put the .CR Z80 line after the definition of the Macro. It is not recommended for clarity reasons, but it is done in this example to emphasize that the mnemonics inside the Macro definition are not interpreted. Outside a Macro definition you would get an error message when no appropriate Cross-Overlay was loaded.

I can think of an even weirder example using parameters. Have a look at the next program.

          .CR   6502

SOURCE    .EQ   $40        Just any memory location

MADMAC    .MA   labelfield,opfield,operandfield,commentfield
]1        ]2    ]3         ]4
          .EM

          >MADMAC   START,CLC,,'Clear Carry'
          >MADMAC   ,LDA,SOURCE,'Get source'
          >MADMAC   ,ADC,#10,'Add 10 to it'
          >MADMAC   ,STA,SOURCE,'Save result'

This program shows an insane application of Macros. This demonstrates that the parameters are treated just as a piece of text by the expanding Macros.

All that the Macro MADMAC does is paste parameter ]1 in the label field, parameter ]2 is pasted in the instruction field, parameter ]3 is pasted in the operand field, and finally parameter ]4 is pasted in the comment field.
This way the Macro creates a normal program line that can be assembled as usual. I admit the Macro MADMAC is not very useful, but I hope you get the idea behind parameters.

The list file of the program above could look something like this after assembly:

1            .CR   6502
2
3  SOURCE    .EQ   $40        Just any memory location
4
5  MADMAC    .MA   labelfield,opfield,operandfield,commentfield
6  ]1        ]2    ]3         ]4
7            .EM
8
9            >MADMAC   START,CLC,,'Clear Carry'
M1 START     CLC              Clear Carry
10           >MADMAC   ,LDA,SOURCE,'Get source'
M1           LDA   SOURCE     Get source
11           >MADMAC   ,ADC,#10,'Add 10 to it'
M1           ADC   #10        Add 10 to it
11           >MADMAC   ,STA,SOURCE,'Save result'
M1           STA   SOURCE     Save result

Empty leading parameters consist of a single comma, like the label field parameters in front of the LDA, ADC and STA instructions. Every time the SB-Assembler finds a comma as first parameter this comma will be interpreted as an empty parameter. Such a parameter will be replaced by no text at all.
In the example above the CLC command is followed by two commas in a row. The first comma is the one separating the second and third parameter. No text in between the commas means that the parameter is empty and will be replaced by an empty string again.
The comment text is placed between single or double quotes because they contain spaces. In fact all parameters may be enclosed in single or double quotes if you want.
The single or double quotes are only required if a space, a TAB character, a comma, or a semicolon is present in the parameter text.
Empty parameters at the end of the parameter list don't have to be supplied at all. So it is not necessary to add a row of commas to indicate that all following parameters are empty.

There is one special parameter which is the parameter counter ]# . This parameter indicates the number of parameters that were used when calling the currently expanding Macro. You can use this parameter together with conditional directives to adapt your Macro to the total number of parameters available.
In fact the parameter counter counts from the first parameter until the last not empty parameter. So empty parameters that precede the last filled parameter are counted as well. But trailing empty parameters are not counted any more.

In Version 3 a single space may follow a comma for better human readability.

Nesting Macros

Macros may be nested endlessly. Nesting Macros means that one Macro calls for the expansion of another Macro, which can call yet another Macro, etc.
Macro definitions can never be nested though! This means that no .MA directive is allowed between the opening .MA directive and a closing .EM directive.
But an expansion call within a Macro definition is allowed.
The free memory of your system is the only limit to the maximum nesting depth. No error is given in the unlikely event if you nested your Macros too deep. Go ahead en try to crash the program if you want, especially Version 3 :-)

The next example shows a 6502 program with a nested Macro. The 6502 doesn't have a decrement Accu instruction. In this example we define a decrement Accu Macro, which will be called from another Macro.

1            .CR    6502        Use the 6502 Cross-Overlay
2
3  WAIT      .MA    STARTVALUE
4            PHA                ; Save Accu contents
5            LDA    ]1          ; Load the start value
6  :LOOP     >DECA              ; Decrement Accu
7            BNE    :LOOP       ; Repeat until Accu is 0
8            PLA                ; Restore Accu
9            .EM
10
11 DECA      .MA
12           SEC                ; Prepare C for subtraction
13           SBC    #1          ; Decrement Accu by 1
14           .EM
15
16 START     NOP                Just an imaginary instruction
17           >WAIT 10           Wait a while
M1           PHA
M2           LDA    #10
M3 :LOOP     >DECA
M1           SEC                <--- Nested Macro
M2           SBC    #1          <--- Nested Macro
M4           BNE    :LOOP
M5           PLA
18           RTS                The rest of the program

As you can see it doesn't matter to the SB-Assembler in which order the Macros are defined. It is perfectly legal to use a Macro call to a Macro that is not defined yet while defining another Macro. Remember that nothing is interpreted during the definition of a Macro.

Please note that the line counter restarts at M1 when a new nested Macro is called. The line number continues where it left off once the nested Macro is completed.

This example also demonstrates a different purpose of Macros: Combining a few instructions into a new instruction. A subroutine would be very inefficient here.

Exit Macro Expansion

The .XM directive is specifically designed to exit a Macro expansion prematurely. This exit can be either conditional or unconditional.

The exit is unconditional when no expression follows the .XM directive. In that case the Macro definition ends immediately and the SB-Assembler continues as if the natural end of the Macro expansion was reached.
Unconditional exits are used in combination with the other conditional directives like .DO .EL and .FI . Using an unconditional exit without the combination of conditional directives has no particular use because the rest of the defined Macro would never be executed.
The .XM directive will pop one active .DO level from the conditional stack, like the .FI directive would have done.
In a useful program the unconditional .XM directive will always be followed by a .FI or .EL directive anyway. If the condition was true for the line containing the .XM directive, the Macro will be aborted and the SB-Assembler will never see the .FI or .EL directive. But the .FI directive will end the condition if the condition was false for the program line containing the .XM directive. The .EL directive would make the condition true in that case.

Conditional exits will abort the Macro expansion if the expression that follows the .XM directive evaluates to be true (<> 0). If the expression is false ( = 0 ), the Macro expansion continues as it normally would.

You will often find the conditional .XM directive together with the parameter counter ]# to create a "do for every parameter" structure.

The next program shows an example of the use of the conditional .XM directive together with the parameter counter.
The Macro can be used to push any number of register pairs (maximum 7) of a Z80 on the stack. You could also write a similar Macro to pop the register pairs from the stack again.

1  REGPUSH    .MA     regpairlist (maximum 7)
2             PUSH    ]1        ; Save 1st register pair
3             .XM     ]#=1      ; Done when 1 parameter
4             PUSH    ]2        ; Save 2nd register pair
5             .XM     ]#=2      ; Done when 2 parameters
6             PUSH    ]3        ; Save 3d register pair
7             .XM     ]#=3      ; Done when 3 parameters
8             PUSH    ]4        ; Save 4th register pair
9             .XM     ]#=4      ; Done when 4 parameters
10            PUSH    ]5        ; Save 5th register pair
11            .XM     ]#=5      ; Done when 5 parameters
12            PUSH    ]6        ; Save 6th register pair
13            .XM     ]#=6      ; Done when 6 parameters
14            PUSH    ]7        ; Save 7th register pair
15            .EM
16
17 START      >REGPUSH   AF,BC,HL
M1            PUSH    AF
M2            .XM     3=1
M3            PUSH    BC
M4            .XM     3=2
M5            PUSH    HL
M6            .XM     3=3
18
19            >REGPUSH   IX,IY
M1            PUSH    IX
M2            .XM     2=1
M3            PUSH    IY
M4            .XM     2=2
20            NOP                 Rest of the program

The .XM directive is used here to stop the expansion of the Macro when all available parameters are processed.
On the first M2 line in the example above you see the expression 3=10. The 3 clearly is the value of the parameter counter ]# . The expression 3=1 is false and the .XM directive will not exit the Macro expansion after line M2.
The expression is 3=2 on the first M4 line. This expression is also false and the .XM directive is ignored again. The expression 3=3 on line M6 is true however and the Macro expansion is terminated immediately.

The second example demonstrates a little variation on the example above. The Macro has the same function, but this time it is intended for the 65C02 processor.
The difference between the push instructions of the 65C02 from the Z80 instructions is the addressing mode. The Z80 requires an operand to tell it what register pair is to be pushed. The 65C02 expects to find the register information directly in the instruction (PHA, PHX, PHY and PHP).
Since there are only 4 registers in the 65C02 that can be pushed we only need a maximum of 4 parameters.

1  REGPUSH    .MA    register list (max 4)
2             PH]1           ; Save 1st register
3             .XM    ]#=1    ; Done when 1 parameter
4             PH]2           ; Save 2nd register
5             .XM    ]#=2    ; Done when 2 parameters
6             PH]3           ; Save 3d register
7             .XM    ]#=3    ; Done when 3 parameters
8             PH]4           ; Save 4th register
9             .EM
10
11 START      >REGPUSH   A,X,Y,P
M1            PHA
M2            .XM    4=1
M3            PHX
M4            .XM    4=2
M5            PHY
M6            .XM    4=3
M7            PHP
12
13           >REGPUSH   X,Y
M1           PHX
M2           .XM    2=1
M3           PHY
M4           .XM    2=2
14           NOP            Rest of the program

The program above clearly demonstrates that the parameters are literally treated as pieces of text. This text can be pasted at any position you want, in this case the text will become part of the instruction.
So you're not limited to variables or operands when you use parameters.

It goes without saying that the result of the pasting procedure should generate legal code. A parameter like "SILLY EXAMPLE" would result in the mnemonic PHSILLY and operand EXAMPLE. I think it is obvious that you won't find that mnemonic in the 65C02 data sheet.