Chapter 15
Conditional assembly
Conditional assembly lets a source assemble some of its lines and skip others, depending on a condition that TATARA works out while it reads the source. One source can then make more than one program: a version with extra checks for testing and one without, or a version for the MSX1 and one for the MSX2, chosen by changing one line.
This chapter describes the conditional directives, and the two passes that TATARA makes over the source, which every condition depends on.
15.1 IF, ELSE and ENDIF
IF is followed by an expression. If its value is not zero, the lines after it are assembled; if it is zero, they are skipped. ENDIF ends the lines that IF controls, and ELSE, which is optional, starts the lines to assemble instead when the condition is false. IFE is the opposite test: its lines are assembled when the value is zero.
debug equ 1 cseg if debug db 'D' else db 'R' endif ife debug db 'E' endif
Here db ’D’ is assembled, and db ’R’ and db ’E’ are skipped; changing the first line to debug equ 0 swaps them. The listing shows every line, but the skipped ones have no address and no bytes:
0001 debug equ 1 cseg if debug 0000' 44 db 'D' else db 'R' endif ife debug db 'E' endif
Chapter 18 shows how to leave skipped lines out of the listing altogether.
Any value other than zero counts as true. The comparison operators of chapter 8 give 0FFFFh for true and 0 for false, so they can be used directly: if size gt 256.
Some of these directives have other names, which TATARA accepts so that sources written for M80 assemble unchanged (table 15.1).
M80 also accepts ENDC for ENDIF. TATARA does not; in a source written for M80, change it to ENDIF.
15.2 The two passes
A label can be used before the line that defines it, as in the first line here:
jp later later: ret
When TATARA reaches jp later, it has not seen later yet, so it cannot know its address. It gets round this by reading the source twice. Each reading is called a pass.
- On the first pass, TATARA works out how many bytes each line takes, and so the address of every label and the value of every name. It writes nothing.
- On the second pass, every value is known, and TATARA produces the bytes of the program, the listing and the object file.
The first panel of figure 15.1 shows the two passes over those two lines. Almost every assembler works this way, M80 included.
The names and values found on the first pass are kept for the second. So on the second pass, every name that the first pass defined is already defined, including names whose lines come later in the source.
This matters for conditional assembly, because TATARA evaluates each condition on both passes. If a condition gives a different answer on the second pass, the second pass assembles different lines from the first, and the labels after them move. The second panel of figure 15.1 shows it: IFDEF asks whether later is defined (section 15.3). On the first pass it is not yet, so db 1 is skipped and after is 0000h. On the second pass it is, so db 1 is assembled and after would be 0001h. TATARA stops at the line of after:
DEFLATE.AS(6): ERROR: phase error - this label had a different value on pass 1.
If a condition makes the second pass skip a label that the first pass defined, the message is the one in section 13.6 instead.
So a condition must give the same answer on both passes. The simplest way to be sure is to use only names that are defined above the condition. For IF, TATARA requires it: a name defined later stops it on the first pass.
; FWD.AS - IF of a name defined later. cseg if later db 1 endif later equ 1
FWD.AS(3): ERROR: undefined symbol in an expression.
15.3 IFDEF and IFNDEF
IFDEF is followed by a name, and its lines are assembled if the name is defined; IFNDEF assembles them if it is not. A name declared with EXTRN counts as defined.
A common use is a value that a source may set for itself, with a default otherwise:
ifndef bufsize bufsize equ 128 endif
A source that wants another size defines bufsize before these lines. It must be before them: as section 15.2 showed, a name defined further down is undefined on the first pass and defined on the second, and the answer changes between the two.
For the same reason, IFNDEF cannot keep an include file from being read twice (section 13.6).
15.4 IF1 and IF2
IF1 assembles its lines on the first pass only, and IF2 on the second pass only.
They come from M80, and are kept so that M80 sources assemble. They were used for things that should happen once, not twice, in the days when a listing went straight to a printer. In TATARA they are of little use. The listing shows the second pass, so it shows IF2’s lines and not IF1’s. And anything that defines a label or produces bytes inside IF1 or IF2 makes the two passes different, which section 15.2 forbids:
; PASSLAB.AS - a label inside IF1. cseg if1 here: nop endif
PASSLAB.AS(4): ERROR: this label was defined on pass 1 and not on pass 2 - a conditional skipped it.
15.5 IFB, IFNB, IFIDN and IFDIF
These four test text, not values:
- IFB assembles its lines if the text is blank, and IFNB if it is not.
- IFIDN assembles its lines if two pieces of text are identical, and IFDIF if they differ. The two are separated by a comma.
Each piece of text is written between angle brackets, which are not part of it. For a piece that holds no spaces or commas, the brackets can be left out:
ifb <> ; true: blank ifb <x> ; false ifnb <x> ; true ifdif <abc>,<abd> ; true ifidn <abc>,<ABC> ; false: the case differs ifidn abc,abc ; true
The comparison is exact, upper and lower case included, as in M80, and /C makes no difference to it.
With text written out like this, the answer is always the same, so these directives are of no use on their own. They are used inside macros, where the text is an argument that changes from one use of the macro to the next (chapter 16).
15.6 Nesting
A conditional can be inside another. An IF inside lines that are being skipped is not even evaluated, and none of its lines are assembled, whatever its condition:
if 0 if 1 db 1 ; skipped: the outer IF is false else db 2 ; skipped too endif else if 1 db 3 ; assembled else db 4 endif endif
Each ELSE and ENDIF belongs to the innermost IF that is still open. TATARA allows up to 16 conditionals open at once.
A label can be written on an IF, ELSE or ENDIF line, as on any other. It is defined, with the address of the location counter, if TATARA is assembling when it reaches the line, and not defined if it is skipping, as M80 does. So a label on the ELSE that follows skipped lines is not defined, and neither is one on an IF inside skipped lines. The safest place for a label is a line of its own.
15.7 Messages
Table 15.2 lists the messages about conditional assembly, each with the line that causes it.
Line |
Message |
| ELSE or ENDIF without a matching IF. |
|
| conditional not closed by ENDIF. |
|
| conditionals nested too deeply. |
|
if with no expression |
bad expression. |
if later, later defined further down |
undefined symbol in an expression. |
endc |
not a directive, a macro or an instruction. |
When a conditional is never closed, TATARA notices only at the end of the source, but the message names the line of the IF, so that it can be found. If several are open, it names the outermost one.