Tatara

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).

Other name Same as
IFT, COND IF
IFF IFE
Table 15.1: Other names of the conditional directives.

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.

The first panel of figure 15.1 shows the two passes over those two lines. Almost every assembler works this way, M80 included.

PIC

Figure 15.1: What each pass does. Above, a label used before its line. Below, a condition that gives a different answer on each pass.

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:

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 with no IF open, or a second ELSE for one IF

ELSE or ENDIF without a matching IF.

an IF never closed

conditional not closed by ENDIF.

a seventeenth IF inside sixteen others

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.

Table 15.2: Messages about conditional assembly.

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.