827 lines
31 KiB
Plaintext
827 lines
31 KiB
Plaintext
CHAPTER 11 MACROS AND CONDITIONAL ASSEMBLY
|
||
|
||
|
||
Macro Facility
|
||
|
||
A86 contains an easy-to-use, but very powerful macro facility.
|
||
The facility subsumes the capabilities of most assemblers,
|
||
including operand concatenation, repeat, indefinite repeat (often
|
||
called IRP), indefinite repeat character (IRPC), passing macro
|
||
operands by text or by value, comparing macro operands to
|
||
strings, and detecting blank macro operands. Unlike other
|
||
assemblers, A86 integrates these functions into the main macro
|
||
facility; so they can be invoked without clumsy syntax, or
|
||
strange characters in the macro call operands.
|
||
|
||
|
||
Simple Macro Syntax
|
||
|
||
All macros must be defined before they are used. A macro
|
||
definition consists of the name of the macro, followed by the
|
||
word MACRO, followed by the text of the macro, followed by #EM,
|
||
which marks the end of the macro.
|
||
|
||
Many assembly languages require a list of dummy operand names to
|
||
follow the word MACRO. A86 does not: the operands are denoted in
|
||
the text with the fixed names #1, #2, #3, ... up to a limit of
|
||
#9, for each operand in order. If there is anything following
|
||
the word MACRO, it is considered part of the macro text.
|
||
|
||
Examples:
|
||
|
||
; CLEAR sets the register operand to zero.
|
||
|
||
CLEAR MACRO SUB #1,#1 #EM
|
||
|
||
CLEAR AX ; generates a SUB AX,AX instruction
|
||
CLEAR BX ; generates a SUB BX,BX instruction
|
||
|
||
|
||
; MOVM moves the second operand to the first operand.
|
||
; Both operands can be memory variables.
|
||
|
||
MOVM MACRO
|
||
MOV AL,#2
|
||
MOV #1,AL
|
||
#EM
|
||
|
||
VAR1 DB ?
|
||
VAR2 DB ?
|
||
|
||
MOVM VAR1,VAR2 ; generates MOV AL,VAR2 followed by MOV VAR1,AL
|
||
11-2
|
||
|
||
Formatting in Macro Definitions and Calls
|
||
|
||
The format of a macro definition is flexible. If the macro text
|
||
consists of a single instruction, the definition can be given in
|
||
a single line, as in the CLEAR macro given above. There is no
|
||
particular advantage to doing this, however: A86 prunes all
|
||
unnecessary spaces, blank lines, and comments from the macro text
|
||
before entering the text into the symbol table. I recommend the
|
||
more spread-out format of the MOVM macro, for program
|
||
readability.
|
||
|
||
All special macro operators within a macro definition begin with
|
||
a hash sign # (a hex 23 byte). The letters following the hash
|
||
sign can be given in either upper case or lower case. Hash-sign
|
||
operators are recognized even within quoted strings. If you wish
|
||
the hash sign to be treated literally, and not as the start of a
|
||
special macro operator, you must give 2 consecutive hash signs:
|
||
##. For example:
|
||
|
||
FOO MACRO
|
||
DB '##1'
|
||
DB '#1'
|
||
#em
|
||
|
||
FOO abc ; produces DB '#1' followed by DB 'abc'
|
||
|
||
The format of the macro call line is also flexible. A macro call
|
||
consists of the name of the macro, followed by the operands to be
|
||
plugged into the macro. A86 prunes leading and trailing blanks
|
||
from the operands of a macro call. The operands to a macro call
|
||
are always separated by commas. Also, as in all A86 source
|
||
lines, a semi-colon occurring outside of a quoted string is the
|
||
start of a comment, ignored by A86. If you want to include
|
||
commas, blanks, or semi-colons in your operands, you must enclose
|
||
your operand in single quotes.
|
||
|
||
|
||
|
||
Macro Operand Substitution
|
||
|
||
Some macro assemblers expect the operands to macro calls to
|
||
follow the same syntax as the operands to instructions. In those
|
||
assemblers, the operands are parsed, and reduced to numeric
|
||
values before being plugged into the macro definition text. This
|
||
is called "passing by value". As its default, A86 does not pass
|
||
by value, it passes by text. The only parsing of operands done
|
||
by the macro processor is to determine the start and the finish
|
||
of the operand text. That text is substituted, without regard
|
||
for its contents, for the "#n" that appears in the macro
|
||
definition. The text is interpreted by A86 only after a complete
|
||
line is expanded and as it is assembled.
|
||
11-3
|
||
|
||
If the first non-blank character after the macro name is a comma,
|
||
then the first operand is null: any occurrences of #1 in the
|
||
macro text will be deleted, and replaced with nothing. Likewise,
|
||
any two consecutive commas with no non-blanks between them will
|
||
result in the corresponding null operand. Also, out-of-range
|
||
operands are null; for example, #3 is a null operand if only two
|
||
operands are provided in the call.
|
||
|
||
Null operands to macros are not in themselves illegal. They will
|
||
produce errors only if the resulting macro expansion is illegal.
|
||
|
||
The method of passing by text allows operand text to be plugged
|
||
anywhere into a macro, even within symbol names. For example:
|
||
|
||
; KF_ENTRY creates an entry in the KFUNCS table, consisting of a
|
||
; pointer to a KF_ action routine. It also declares the
|
||
; corresponding CF_ symbol, which is the index within the table
|
||
; for that entry.
|
||
|
||
KF_ENTRY MACRO
|
||
CF_#1 EQU ($-KFUNCS)/2+080
|
||
DW KF_#1
|
||
#EM
|
||
|
||
KFUNCS:
|
||
KF_ENTRY UP
|
||
KF_ENTRY DOWN
|
||
|
||
; The above code is equivalent to:
|
||
;
|
||
; KFUNCS:
|
||
; DW KF_UP
|
||
; DW KF_DOWN
|
||
;
|
||
; CF_UP EQU 080
|
||
; CF_DOWN EQU 081
|
||
|
||
|
||
|
||
Quoted String Operands
|
||
|
||
As mentioned before, if you want to include blanks, commas, or
|
||
semicolons in your operands, you enclose the operand in single
|
||
quotes. In the vast majority of cases in which these special
|
||
characters need to be part of operands, the user wants them to be
|
||
quoted in the final, assembled line also. Therefore, the quotes
|
||
are passed in the operand. To override this, and strip the
|
||
quotes from the string, you precede the quoted string with a hash
|
||
sign. Examples:
|
||
11-4
|
||
|
||
DBW MACRO
|
||
DB #1
|
||
DW #2
|
||
#EM
|
||
|
||
DBW 'E', E_POINTER
|
||
DBW 'W', W_POINTER
|
||
|
||
; note that if quotes were not passed, the above lines would have
|
||
; to be DBW '''E''', E_POINTER; DBW '''W''', W_POINTER
|
||
|
||
FETCH_CHAR MACRO
|
||
LODSB
|
||
#1
|
||
CALL PROCESS_CHAR
|
||
#EM
|
||
|
||
FETCH_CHAR STOSB ; generates STOSB as second instruction
|
||
FETCH_CHAR #'INC DI' ; generates INC DI as second instruction
|
||
|
||
|
||
|
||
Looping by Operands in Macros
|
||
|
||
A86's macro facility contains two kinds of loops: you can loop
|
||
once for each operand in a range of operands; or you can loop
|
||
once for each character within an operand. The first kind of
|
||
loop, the R-loop, is discussed in this section; the second kind,
|
||
the C-loop, is discussed later.
|
||
|
||
An R-loop is a stretch of macro-definition code that is repeated
|
||
when the macro is expanded. In addition to the fixed operands #1
|
||
through #9, you can specify a variable operand, whose number
|
||
changes each time through the loop. You give the variable
|
||
operand one of the 4 names #W, #X, #Y, or #Z.
|
||
|
||
An R-loop begins with #R, followed immediately by the letter
|
||
W,X,Y, or Z naming the variable, followed by the number of the
|
||
first operand to be used, followed by the number of the last
|
||
operand to be used. After the #Rxnn is the text to be repeated.
|
||
The R-loop ends with #ER. For example:
|
||
|
||
STORE3 MACRO
|
||
MOV AX,#1
|
||
#RY24 ; "repeat for Y running from 2 through 4"
|
||
MOV #Y,AX
|
||
#ER
|
||
#EM
|
||
|
||
STORE3 VAR1,VAR2,VAR3,VAR4
|
||
|
||
; the above call produces the 4 instructions MOV AX,VAR1; MOV VAR2,AX;
|
||
; MOV VAR3,AX; MOV VAR4,AX.
|
||
11-5
|
||
|
||
The #L Last Operator and Indefinite Repeats
|
||
|
||
A86 recognizes the special operator #L, which is the last operand
|
||
in a macro call. #L can appear anywhere in macro text; but its
|
||
big power occurs in conjunction with R-loops, to yield an
|
||
indefinite-repeat facility.
|
||
|
||
A common example is as follows: you can take any macro that is
|
||
designed for one operand, and easily convert it into a macro that
|
||
accepts any number of operands. You do this by placing the
|
||
command #RX1L, "repeat for X running from 1 through L", at the
|
||
start of the macro, and the command #ER at the end just before
|
||
the #EM. Finally, you replace all instances of #1 in the macro
|
||
with #X. We see how this works with the CLEAR macro:
|
||
|
||
CLEAR MACRO #RX1L
|
||
SUB #X,#X
|
||
#ER
|
||
#EM
|
||
|
||
CLEAR AX,BX ; generates both SUB AX,AX and SUB BX,BX in one macro!
|
||
|
||
It is possible for R-loops to iterate zero times. In this case,
|
||
the loop-text is skipped completely. For example, CLEAR without
|
||
any operands would produce no expanded text.
|
||
|
||
|
||
Character Loops
|
||
|
||
We have seen the R-loop; now we discuss the other kind of loop in
|
||
macros, the character loop, or C-loop. In the C-loop, the
|
||
variable W,X,Y, or Z does not represent an entire operand; it
|
||
represents a character within an operand.
|
||
|
||
You start a C-loop with #C, followed by one of the 4 letters
|
||
W,X,Y, or Z, followed by a single operand specifier-- a digit,
|
||
the letter L, another one of W,X, Y, or Z defined in an outer
|
||
loop, or one of the more complicated specifiers defined later in
|
||
this chapter. Following the #Cxn is the text of the C-loop. The
|
||
C-loop ends with #EC. The macro will loop once for every
|
||
character in the operand. That single character will be
|
||
substituted for each instance of the indicated variable operand.
|
||
For example:
|
||
|
||
PUSHC MACRO #CW1
|
||
PUSH #WX
|
||
#EC#EM
|
||
|
||
PUSHC ABC ; generates 3 instructions PUSH AX | PUSH BX | PUSH CX
|
||
|
||
If the C-operand is quoted in the macro call, the quotes ARE
|
||
removed from the operand before passing characters to the loop.
|
||
It is not necessary to precede the quoted string with a hash sign
|
||
in this case. If you do, the hash sign will be passed as the
|
||
first character.
|
||
11-6
|
||
|
||
If the C-operand is a null operand (no characters in it), the
|
||
loop text is skipped completely.
|
||
|
||
|
||
The "B"-Before and "A"-After Operators
|
||
|
||
So far, we have seen that you can specify operands in your macro
|
||
in fourteen different ways: 1,2,3,4,5,6,7,8,9,W,X,Y,Z,L. We now
|
||
multiply these 14 possibilities, by introducing the "A" and "B"
|
||
operators. You can precede any of the 14 specifiers with "A" or
|
||
"B", to get the adjacent operand after or before the specified
|
||
operand. For example, BL means the operand just before the last
|
||
operand; in other words, the second-to-the-last operand. AZ
|
||
means the operand just after the Z operand. You can even repeat,
|
||
up to a limit of 4 "B"s or 3 "A"s: for example, BBL is the
|
||
third-to-last operand.
|
||
|
||
Note that any operand specifier can appear in contexts other than
|
||
by itself following a # within a macro. For example, BBL could
|
||
appear as the upper limit to an R-loop: #RZ1BBL loops with Z
|
||
running from the first operand to the third-to-last operand.
|
||
|
||
In the case of the variable operand to a C-loop, the "A" and "B"
|
||
specifiers denote the characters before or after the current
|
||
looping-character. An example of this is given in the next
|
||
section.
|
||
|
||
|
||
Multiple Increments within Loops
|
||
|
||
We have seen that you end an R-loop with a #ER, and you end a
|
||
C-loop with a #EC. We now present another way to end these
|
||
loops; a way that lets you specify a larger increment to the
|
||
macro's loop counter. You can end your loops with one of the 4
|
||
additional commands #E1, #E2, #E3, or #E4.
|
||
|
||
For R-loops terminated by #ER, the variable operand advances to
|
||
the next operand when the loop is made. If you end your R-loop
|
||
with #E2, the variable operand advances 2 operands, not just one.
|
||
For #E3, it advances 3 operands; for #E4, 4 operands. The #E1
|
||
command is the same as #ER.
|
||
|
||
The most common usage of this feature is as follows: You will
|
||
recall that we generalized the CLEAR macro with the #L-variable,
|
||
so that it would take an indefinite number of operands. Suppose
|
||
we want to do the same thing with the DBW macro. We would like
|
||
DBW to take any number of operands, and alternate DBs and DWs
|
||
indefinitely on the operands. This is made possible by creating
|
||
an R-loop terminated by #E2:
|
||
|
||
DBW MACRO #RX1L
|
||
DB #X
|
||
DW #AX
|
||
#E2
|
||
#EM
|
||
|
||
DBW 'E',E_POINTER, 'W',W_POINTER ; two pairs on same line!
|
||
11-7
|
||
|
||
The #E2 terminator means that we are looping on a pair of
|
||
operands. Note the crucial usage of the "A"-after operator to
|
||
specify the second operand of the operand pair.
|
||
|
||
A special note applies to the DBW macro above: A86 just happens
|
||
to accept a DW directive with no operands (it generates no object
|
||
code, and issues no error). This means that DBW will accept an
|
||
odd number of operands with no error, and do the expected thing
|
||
(it alternates bytes and words, ending with a byte).
|
||
|
||
You could likewise generalize a macro with 3 or 4 operands, to an
|
||
indefinite number of triples or quadruples; by ending the R-loop
|
||
with #E3 or #E4. The operands in each group would be specified
|
||
by #X, #AX, #AAX, and, for #E4, #AAAX.
|
||
|
||
For C-loops terminated by #E1 through #E4, the character pointer
|
||
is advanced the specified number of characters. You use this in
|
||
much the same way as for R-loops, to create loops on pairs,
|
||
triplets, and quadruplets of characters. For example:
|
||
|
||
PUSHC2 MACRO #CZ1
|
||
PUSH #Z#AZ
|
||
#E2
|
||
#EM
|
||
|
||
PUSHC2 AXBXSIDI ; generates PUSH AX | PUSH BX | PUSH SI | PUSH DI
|
||
|
||
|
||
Negative R-loops
|
||
|
||
We now introduce another form of R-loop, called the Q-loop-- the
|
||
negative repeat loop. This loop is the same as the R-loop,
|
||
except that the operand number decrements instead of increments;
|
||
and the loop exits when the number goes below the finish-number,
|
||
not above it. The Q-loop is specified by #Qxnn instead of #Rxnn,
|
||
and #EQ instead of #ER. You can also use the multiple-decrement
|
||
forms #E1 #E2 #E3 or #E4 to terminate an Q-loop.
|
||
|
||
Example:
|
||
|
||
MOVN MACRO #QXL2 ; "negative repeat X from L down to 2"
|
||
MOV #BX,#X
|
||
#EQ#EM
|
||
|
||
MOVN AX,BX,CX,DX ; generates the three instructions:
|
||
; MOV CX,DX
|
||
; MOV BX,CX
|
||
; MOV AX,BX
|
||
|
||
Note: the above functionality is already built into the MOV
|
||
instruction of A86. The macro shows how you would implement it
|
||
if you did not already have this facility.
|
||
11-8
|
||
|
||
Nesting of Loops in Macros
|
||
|
||
A86 allows nesting of loops within each other. Since we provide
|
||
the 4 identifiers W,X,Y,Z for the loop operands, you can nest to
|
||
a level of 4 without restriction-- just use a different letter
|
||
for each nesting level. You can nest even deeper, for example,
|
||
by having two nested R-loops that use W is its indexing letter.
|
||
The only restriction to this is that you cannot refer to the W of
|
||
the outer loop from within the inner W loop. (I challenge anyone
|
||
to come up with an application in which these limitations /
|
||
restrictions cause a genuine inconvenience!)
|
||
|
||
|
||
Implied Closing of Loops
|
||
|
||
If you have a loop or loops ending when the macro ends, and if
|
||
the iteration count for those loops is 1, you may omit the #ER,
|
||
#EC, or #EQ. A86 closes all open loops when it sees #EM, with no
|
||
error.
|
||
|
||
For example, if you omit the #ER for the loop version of the
|
||
CLEAR macro, it would make no difference-- A86 automatically
|
||
places an #ER code into the macro definition for you.
|
||
|
||
|
||
Passing Operands by Value
|
||
|
||
As already stated, A86's defualt mode for passing operands is by
|
||
text-- the characters of the operand are copied to the macro
|
||
expansion line as-is, without any evaluation. You may override
|
||
this with the #V operator. When A86 sees #Vn in a macro
|
||
definition, it will evaluate the expression given in the text of
|
||
operand n, and pass a string representing the decimal constant
|
||
answer, instead of the original text. The operand must evaluate
|
||
to an absolute constant value, less than 65536. For example:
|
||
|
||
JLV MACRO
|
||
J#1 LABEL#V2
|
||
#EM
|
||
|
||
JINDEX = 3
|
||
JLV NC,JINDEX+1 ; generates JNC LABEL4
|
||
JINDEX = 6
|
||
JLV Z,JINDEX+2 ; generates JZ LABEL8
|
||
|
||
|
||
Passing Operand Size
|
||
|
||
The construct #Sn is translated by A86 into the decimal string
|
||
representing the number of characters in operand n. One use of
|
||
this would be to make a conditional-assembly test of whether an
|
||
operand was passed at all, as we'll see later in this chapter.
|
||
Another use is to generate a length byte preceding a string, as
|
||
required by some high-level languages such as Turbo Pascal.
|
||
Example:
|
||
11-9
|
||
|
||
LSTRING MACRO
|
||
DB #S1,'#1'
|
||
#EM
|
||
|
||
LSTRING SAMPLE ; generates DB 6,'SAMPLE'
|
||
|
||
|
||
Generating the Number of an Operand
|
||
|
||
The construct #Nn is translated by A86 into the decimal string
|
||
represented by the position number n of the macro operand. Note
|
||
that this value does not depend on the contents of the operand
|
||
that was passed to the macro. Thus, for example, #N2 would
|
||
translate simply to 2; so this usage of #N is silly. #N achieves
|
||
usefulness when n is variable: W,X,Y,Z, or L. I give an example
|
||
of #N with a loop-control variable in the next section. Here is
|
||
an example of #NL, used to generate an array of strings, preceded
|
||
by a byte telling how many strings are in the array:
|
||
|
||
ZSTRINGS MACRO
|
||
DB #NL ; generates the number of operands passed
|
||
#RX1L
|
||
DB '#X',0
|
||
#EM
|
||
|
||
ZSTRINGS TOM,DICK,HARRY ; generates DB 3 followed by strings
|
||
|
||
|
||
Parenthesized Operand Numbers
|
||
|
||
We've seen that macro operands are usually specified in your
|
||
macro definition by a single character: either a single digit or
|
||
one of the special letters W,X,Y,Z, or L. A86 also allows you to
|
||
specify a constant operand number up to 255. You do so by giving
|
||
an expression enclosed in parentheses, rather than a single
|
||
character. The expession must evaluate at the time the macro is
|
||
defined, to a constant between 0 and 255. You can use this
|
||
feature to translate many programs that use MASM's REPT
|
||
directive. For example, if the following REPT construct occurs
|
||
within a MASM macro:
|
||
|
||
TEMP = 0
|
||
REPT 100
|
||
TEMP = TEMP + 1 ; MASM needs an explicitly-set-up counter
|
||
DB TEMP
|
||
ENDM
|
||
|
||
you may translate it into an A86 loop, as follows:
|
||
|
||
#RX1(100) ; the counter X is built into the A86 loop
|
||
DB #NX
|
||
#ER
|
||
|
||
If the REPT does not occur within a macro, you must define a
|
||
macro containing the loop, which you may then immediately call.
|
||
11-10
|
||
|
||
Note that the expression enclosed in praentheses must not itself
|
||
contain any macro operators. Thus, for example, you cannot
|
||
specify #(#NY+1) to represent the operand after Y-- you must use
|
||
#AY.
|
||
|
||
|
||
Exiting from the Middle of a Macro
|
||
|
||
For MASM compatibility, A86 offers the #EX operator, which is
|
||
equivalent to MASM's EXITM directive. #EX is typically used in a
|
||
conditional assembly block within a loop, to terminate the loop
|
||
early. When the #EX code is seen in a macro expansion, the
|
||
expansion ceases at that point, and assembly returns to the
|
||
source file (or to the outer macro in a nested call). You
|
||
couldn't use #EM to do this, because that would signal the end of
|
||
the macro definition, not just the call.
|
||
|
||
|
||
Local Labels in Macros
|
||
|
||
Some assemblers have a LOCAL pseudo-op that is used in
|
||
conjunction with macros. Symbols declared LOCAL to a macro have
|
||
unique (and bizarre) symbol names substituted for them each time
|
||
the macro is called. This solves the problem of duplicate label
|
||
definitions when a macro is called more than once.
|
||
|
||
In A86, the problem is solved more elegantly, by having a class
|
||
of generic local labels throughout assembly, not just in macros.
|
||
Recall that symbols consisting of a single letter, followed by
|
||
one or more decimal digits, can be redefined. You can use such
|
||
labels in your macro definitions.
|
||
|
||
I have recommended that local labels outside of macros be
|
||
designated L1 through L9. Within macro definitions, I suggest
|
||
that you use labels M1 through M9. If you used an Ln-label
|
||
within a macro, you would have to make sure that you never call
|
||
the macro within the range of definition of another Ln-label with
|
||
the same name. By using Mn-labels, you avoid such potential
|
||
conflicts.
|
||
|
||
The following example of a local label within a macro is taken
|
||
from the source of the macro processor itself:
|
||
|
||
; "JHASH label" checks to see if AL is a hash sign. If it is,
|
||
; it processes the hash sign term, and jumps to label.
|
||
; Otherwise, it drops through to the following code.
|
||
|
||
JHASH MACRO
|
||
CMP AL,'##' ; is the scanned character a hash sign?
|
||
JNE >M1 ; skip if not
|
||
CALL MDEF_HASH ; process the hash sign
|
||
JMP #1 ; jump to the label provided
|
||
M1:
|
||
#EM
|
||
11-11
|
||
|
||
...
|
||
L3: ; loop here to eat empty lines, leading blanks
|
||
CALL SKIP_BLANKS ; skip over the leading blanks of a line
|
||
INC SI ; advance source ptr beyond the next non-blank
|
||
JHASH L3 ; if hash sign then process, and eat more blanks
|
||
CMP AL,0A ; were the blanks terminated by a linefeed?
|
||
JE L3 ; loop if yes, nothing on this line
|
||
L5: ; loop here after a line is seen to have contents
|
||
CMP AL,';' ; have we reached the start of a comment?
|
||
JE L1 ; jump if yes, to consume the comment
|
||
JHASH >L6 ; if hash sign then process it; get next char
|
||
...
|
||
L6:
|
||
LODSB ; fetch the next definition char from the source
|
||
CMP AL,' ' ; is it blank?
|
||
JA L5 ; loop if not, to process it
|
||
...
|
||
|
||
|
||
Debugging Macro Expansions
|
||
|
||
There is a tool called EXMAC which will help you troubleshoot
|
||
program lines that call macros. If you are not sure about what
|
||
code is being generated by your macro calls, EXMAC will tell you.
|
||
See Chapter 13 for details.
|
||
|
||
|
||
Conditional Assembly
|
||
|
||
A86 has a conditional assembly feature, that allows you to
|
||
specify that blocks of source code will or will not be assembled,
|
||
according to the values of equated user symbols. The controlling
|
||
symbols can be declared in the program (and can thus be the
|
||
result of assembly-time expressions), or they can be declared in
|
||
the assembler invocation.
|
||
|
||
You should keep in mind the difference between conditional
|
||
assembly, invoked by #IF, and the structured-programming feature,
|
||
invoked by IF without the hash sign. #IF tests a condition at
|
||
assembly time, and can cause code to not be assembled and thus
|
||
not appear in the program. IF causes code to be assembled that
|
||
tests a condition at run time, possibly jumping over code. The
|
||
skipped code will always appear in the program.
|
||
|
||
All conditional assembly lines are identified by a hash sign # as
|
||
the first non-blank character of a line. The hash sign is
|
||
followed by one of the four keywords IF, ELSEIF, ELSE or ENDIF.
|
||
|
||
#IF starts a conditional assembly block. On the same line,
|
||
following the #IF, you provide either a single name, or an
|
||
arbitrary expression evaluating to an absolute constant. In this
|
||
context, a single name evaluates to TRUE if it is defined and not
|
||
equal to the absolute constant zero. A name is FALSE if it is
|
||
undefined, or if it has been equated to zero. An expression is
|
||
TRUE if nonzero, FALSE if zero.
|
||
11-12
|
||
|
||
If the #IF expression evaluates to FALSE, then the following
|
||
lines of code are skipped, up to the next matching #ELSEIF,
|
||
#ELSE, or #ENDIF. If the expression is TRUE, then the following
|
||
lines of code are assembled normally. If a subsequent matching
|
||
#ELSEIF or #ELSE is encountered, then code is skipped up to the
|
||
matching #ENDIF.
|
||
|
||
#ELSEIF provides a multiple-choice facility for #IF-blocks. You
|
||
can give any number of #ELSEIFs between an #IF and its matching
|
||
#ENDIF. Each #ELSEIF has a name or expression following it on
|
||
the same line. If the construct following the #IF is FALSE, then
|
||
the assembler looks for the first TRUE construct following an
|
||
#ELSEIF, and assembles that block of code. If there are no TRUE
|
||
#ELSEIFs, then the #ELSE-block (if there is one) is assembled.
|
||
|
||
You should use the ! instead of the NOT operator in conditional
|
||
assembly expressions. The ! operator performs the correct
|
||
translation of names into TRUE or FALSE values, and handles the
|
||
case !undefined without reporting an error.
|
||
|
||
#ELSE marks the beginning of code to be assembled if all the
|
||
previous blocks of an #IF have been skipped over. There is no
|
||
operand after the #ELSE. There can be at most one #ELSE in an
|
||
#IF-block, and it must appear after any #ELSEIFs.
|
||
|
||
#ENDIF marks the end of an #IF-block. There is no operand after
|
||
#ENDIF.
|
||
|
||
It is legal to have nested #IF-blocks; that is, #IF-blocks that
|
||
are contained within other #IF-blocks. #ELSEIF, #ELSE, and
|
||
#ENDIF always refer to the innermost nested #IF-block.
|
||
|
||
As an example of conditional assembly, suppose that you have a
|
||
program that comes in three versions: one for Texas, one for
|
||
Oklahoma, and one for the rest of the nation. The three programs
|
||
differ in a limited number of places. Instead of keeping three
|
||
different versions of the source code, you can keep one version,
|
||
and use conditional assembly on the boolean variables TEXAS and
|
||
OKLAHOMA to control the assembler output. A sample block would
|
||
be:
|
||
|
||
#if TEXAS
|
||
DB 0,1,2,3
|
||
#elseif OKLAHOMA
|
||
DB 4,5,6,7
|
||
#else
|
||
DB 8,9,10,11
|
||
#endif
|
||
|
||
If a block of code is to be assembled only if TEXAS is false,
|
||
then you would use the exclamation point operator:
|
||
|
||
#if !TEXAS
|
||
DB 0FF
|
||
#endif
|
||
11-13
|
||
|
||
Conditional Assembly and Macros
|
||
|
||
You may have conditional assembly blocks either in macro
|
||
definitions or in macro expansions. The only limitation is that
|
||
if you have an #IF-block in a macro expansion, the entire block
|
||
(i.e., the matching #ENDIF) must appear in the same macro
|
||
expansion. You cannot, for example, define a macro that is a
|
||
synonym for #IF.
|
||
|
||
To have your conditional assembly block apply to the macro
|
||
definition, you provide the block normally within the definition.
|
||
For example:
|
||
|
||
X1 EQU 0
|
||
BAZ MACRO
|
||
#if X1
|
||
DB 010
|
||
#else
|
||
DB 011
|
||
#endif
|
||
#EM
|
||
BAZ
|
||
X1 EQU 1
|
||
BAZ
|
||
|
||
In the above sequence of code, the conditional assembly block is
|
||
acted upon when the macro BAZ is defined. The macro therefore
|
||
consists of the single line DB 011, with all the conditional
|
||
assembly lines removed from the definition. Thus, both
|
||
expansions of BAZ produce the object-code byte of 011, even
|
||
though the local label X1 has turned non-zero for the second
|
||
invocation.
|
||
|
||
To have your conditional assembly block appear in the macro
|
||
expansion, you must literalize the hash sign on each conditional
|
||
assembly line by giving two hash signs:
|
||
|
||
X1 EQU 0
|
||
BAZ MACRO
|
||
##if X1
|
||
DB 010
|
||
##else
|
||
DB 011
|
||
##endif
|
||
#EM
|
||
BAZ
|
||
X1 EQU 1
|
||
BAZ
|
||
|
||
Now the entire conditional assembly block is stored in the macro
|
||
definition, and acted upon each time the macro is expanded. Thus,
|
||
the two invocations of BAZ will produce the different object
|
||
bytes 011 and 010, since X1 has become non-zero for the second
|
||
expansion.
|
||
11-14
|
||
|
||
You will usually want your conditional assembly blocks to be
|
||
acted upon at macro definition time, to save symbol table space.
|
||
You will thus use the first form, with the single hash signs.
|
||
|
||
|
||
Simulating MASM's Conditional Assembly Constructs
|
||
|
||
Microsoft's MASM assembler has an abundance of confusing
|
||
conditional assembly directives, all of which are subsumed by
|
||
A86's #IF expression evaluation policies. IF and IFDEF are both
|
||
covered by A86's #IF directive. IFE and IFNDEF are duplicated by
|
||
#IF followed by the exclamation-point (boolean negation)
|
||
operator. IFB and IFNB test whether a macro operand has been
|
||
passed as blank-- they can be simulated by testing the size of
|
||
the operand with the #Sn operator. Finally, IFIDN and IFDIF do
|
||
string comparisons of macro operands. This is more generally
|
||
subsumed by the string-comparison capabilities of the operators
|
||
EQ, NE, and =.
|
||
|
||
Examples of translation of each of these constructs is given in
|
||
the next chapter, on compatibility with other assemblers.
|
||
|
||
|
||
Conditional Assembly and the XREF Program
|
||
|
||
Previous versions of A86 contained a warning, that XREF will not
|
||
correctly handle conditional-assembly blocks controlled by
|
||
variables whose values change during assembly. Starting with
|
||
V3.12, this has been corrected, by writing to the SYM file a log
|
||
of each conditional-assembly test result. XREF will consult the
|
||
log to determine which blocks to consider.
|
||
|
||
|
||
Declaring Variables in the Assembler Invocation
|
||
|
||
To facilitate the effective use of conditional assembly, A86
|
||
allows you to declare boolean (true-false) symbols in the command
|
||
line that invokes the assembler. The declarations can appear
|
||
anywhere in the list of source file names. They are
|
||
distinguished from the file names by a leading equals sign =. To
|
||
declare a symbol TRUE (value = 1), give the name after the equals
|
||
sign. DO NOT put any spaces between the equals sign and the
|
||
name! To declare a symbol FALSE (value = 0), you can give an
|
||
equals sign, an exclamation point, then the name. Again, DO NOT
|
||
embed any blanks! Example: if your source files are src1.8,
|
||
src2.8, and src3.8, then you can assemble with TEXAS true by
|
||
invoking A86 as follows:
|
||
|
||
a86 =TEXAS src1.8 src2.8 src3.8
|
||
|
||
You can assemble with TEXAS explicitly set to FALSE as follows:
|
||
|
||
a86 =!TEXAS src1.8 src2.8 src3.8
|
||
11-15
|
||
|
||
Note that if TEXAS is used only as a conditional-assembly
|
||
control, then you do not need to include the =!TEXAS in the
|
||
invocation, because an undefined TEXAS will automatically be
|
||
interpreted as false.
|
||
|
||
A user pointed out to me that it's impossible to get an
|
||
equals-sign into an environment variable. So A86 now accepts an
|
||
up-arrow (hex 5E) character in place of an equals-sign for an
|
||
invocation variable.
|
||
|
||
|
||
Null Invocation Variable Names
|
||
|
||
A86 will ignore an equals-sign by itself in the invocation line,
|
||
without error. This allows you to generate assembler invocation
|
||
lines using parameters that could be either boolean variable
|
||
names, or null strings. For example, in the previously-mentioned
|
||
TEXAS-OKLAHOMA-nation example, the program could be invoked via a
|
||
.BAT file called "AMAKE.BAT", coded as follows:
|
||
|
||
A86 =%1 *.8
|
||
|
||
You invoke A86 by typing one of the following:
|
||
|
||
amake texas
|
||
amake oklahoma
|
||
amake
|
||
|
||
The third line will produce the assembler invocation A86 = *.8;
|
||
causing no invocation variables to be declared. Thus both TEXAS
|
||
and OKLAHOMA will be false, which is exactly what you want for
|
||
the rest-of-the-nation version of the program.
|
||
|
||
|
||
Changing Values of Invocation Variables
|
||
|
||
The usual prohibition against changing the value of a symbol that
|
||
is not a local label does not apply to invocation variables. For
|
||
example, suppose you have a conditional control variable DEBUG,
|
||
which will generate diagnostic code for debugging when it is
|
||
true. Suppose further that you have already debugged source
|
||
files src1.8 and src3.8; but you are still working on src2.8. You
|
||
may invoke A86 as follows:
|
||
|
||
A86 src1.8 =DEBUG src2.8 =!DEBUG src3.8
|
||
|
||
The variable DEBUG will be TRUE only during assembly of src2.8,
|
||
just as you want.
|
||
|
||
|