Tatara

第15章
条件付きアセンブル

条件付きアセンブル(conditional assembly)を使うと、TATARAがソースを読みながら判断する条件によって、ソースの一部の行をアセンブルし、ほかの行を飛ばすことができます。これにより、1つのソースから複数のプログラムを作れます。たとえば、テスト用のチェックを入れた版と入れない版、MSX1用とMSX2用の版を、1行を変えるだけで切り替えられます。

この章では、条件付きアセンブルの疑似命令と、すべての条件の前提となる、TATARAがソースを2回読むしくみ(2つのパス)を説明します。

15.1 IF、ELSE、ENDIF

IFのあとには式を書きます。その値が0でなければ、あとに続く行がアセンブルされ、0ならば飛ばされます。ENDIFはIFが制御する行の終わりを示します。省略できるELSEは、条件が偽のときに代わりにアセンブルする行の始まりを示します。IFEは逆の判定で、値が0のときに行がアセンブルされます。

debug           equ     1 
                cseg 
                if      debug 
                db      'D' 
                else 
                db      'R' 
                endif 
                ife     debug 
                db      'E' 
                endif
 

ここではdb ’D’がアセンブルされ、db ’R’とdb ’E’は飛ばされます。最初の行をdebug equ 0にすると、これが入れ替わります。リスティングにはすべての行が表示されますが、飛ばされた行にはアドレスもバイトもありません。

  0001                          debug           equ     1 
                                                cseg 
                                                if      debug 
  0000'   44                                    db      'D' 
                                                else 
                                                db      'R' 
                                                endif 
                                                ife     debug 
                                                db      'E' 
                                                endif
 

飛ばされた行をリスティングから完全に除く方法は、18章で説明します。

0以外の値はすべて真とみなされます。8章の比較演算子は、真のときに0FFFFh、偽のときに0を返すので、if size gt 256のようにそのまま使えます。

これらの疑似命令のいくつかには別の名前があり、M80向けに書かれたソースをそのままアセンブルできるように、TATARAはそれらも受け付けます(表15.1)。

別の名前 同じもの
IFT、COND IF
IFF IFE
表 15.1: 条件付きアセンブルの疑似命令の別の名前

M80はENDIFの別名としてENDCも受け付けますが、TATARAは受け付けません。M80向けに書かれたソースでは、ENDIFに書き換えてください。

15.2 2つのパス

ラベルは、それを定義する行より前でも使えます。次の最初の行がその例です。

                jp      later 
later:          ret
 

TATARAがjp laterに来たとき、まだlaterを見ていないので、そのアドレスはわかりません。TATARAはこれを、ソースを2回読むことで解決します。1回ごとの読み込みをパス(pass)と呼びます。

図15.1の上半分は、この2行に対する2つのパスを示しています。M80を含め、ほとんどのアセンブラーはこのように動きます。

PIC

図 15.1: それぞれのパスが行うこと。上は、定義する行より前で使われたラベル。下は、パスごとに違う答えになる条件

第1パスで見つけた名前と値は、第2パスでもそのまま使われます。そのため第2パスでは、第1パスで定義されたすべての名前が、ソースのもっとあとで定義されるものも含めて、すでに定義されています。

これは条件付きアセンブルに関わってきます。TATARAは、それぞれの条件を両方のパスで評価するからです。条件が第2パスで違う答えになると、第2パスでは第1パスと違う行がアセンブルされ、そのあとのラベルがずれます。図15.1の下半分がその例です。IFDEFはlaterが定義されているかどうかを調べます(15.3節)。第1パスではまだ定義されていないので、db 1は飛ばされ、afterは0000hになります。第2パスでは定義されているので、db 1がアセンブルされ、afterは0001hになってしまいます。TATARAはafterの行で止まります。

DEFLATE.AS(6): ERROR: phase error - this label had a different value on pass 1.
 

第1パスで定義したラベルを条件のせいで第2パスが飛ばした場合は、13.6節のメッセージになります。

したがって、条件は両方のパスで同じ答えにならなければなりません。確実にそうするいちばん簡単な方法は、条件より上で定義された名前だけを使うことです。IFでは、TATARAがそれを要求します。あとで定義される名前を使うと、第1パスで止まります。

; 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とIFNDEF

IFDEFのあとには名前を書き、その名前が定義されていれば行がアセンブルされます。IFNDEFは、定義されていなければアセンブルします。EXTRNで宣言した名前は、定義されているものとみなされます。

よくある使い方は、ソースが自分で値を決められ、決めなければ既定値になるようにするものです。

                ifndef  bufsize 
bufsize         equ     128 
                endif
 

別の大きさにしたいソースは、これらの行より前でbufsizeを定義します。必ず前でなければなりません。15.2節で見たように、もっとあとで定義される名前は、第1パスでは未定義、第2パスでは定義済みになり、2つのパスで答えが変わってしまうからです。

同じ理由で、IFNDEFを使ってインクルード・ファイルが2回読まれるのを防ぐことはできません(13.6節)。

15.4 IF1とIF2

IF1は第1パスでだけ、IF2は第2パスでだけ、その行をアセンブルします。

これらはM80から来たもので、M80のソースをアセンブルできるように残してあります。リスティングがそのままプリンターに出力されていた時代に、2回ではなく1回だけ行いたいことのために使われていました。TATARAではほとんど役に立ちません。リスティングは第2パスを表示するので、IF2の行は表示されますが、IF1の行は表示されません。また、IF1やIF2の中でラベルを定義したりバイトを作ったりすると、2つのパスが違うものになり、15.2節の決まりに反します。

; 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、IFDIF

この4つは、値ではなくテキストを調べます。

それぞれのテキストは山かっこ(<>)で囲み、かっこはテキストに含まれません。空白もコンマも含まないテキストでは、かっこを省略できます。

                ifb     <>              ; true: blank 
                ifb     <x>             ; false 
                ifnb    <x>             ; true 
                ifdif   <abc>,<abd>     ; true 
                ifidn   <abc>,<ABC>     ; false: the case differs 
                ifidn   abc,abc         ; true
 

比較は、M80と同じように大文字と小文字も含めて完全に一致するかどうかで、/Cの有無は関係ありません。

このようにテキストをそのまま書いたのでは、答えはいつも同じなので、これらの疑似命令はそれだけでは役に立ちません。マクロの中で、使うたびに変わる引数をテキストとして使います(16章)。

15.6 入れ子

条件付きアセンブルは、別の条件付きアセンブルの中に入れることができます。飛ばされている行の中のIFは評価さえされず、条件がどうであれ、その行はどれもアセンブルされません。

                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
 

それぞれのELSEとENDIFは、まだ閉じていないいちばん内側のIFに対応します。TATARAは、同時に16個までの条件付きアセンブルを開いておけます。

IF、ELSE、ENDIFの行にも、ほかの行と同じようにラベルを書けます。M80と同じように、TATARAがその行に来たときにアセンブル中であれば、ラベルはロケーション・カウンターのアドレスで定義され、飛ばしている最中であれば定義されません。そのため、飛ばされた行のあとのELSEに書いたラベルは定義されず、飛ばされている行の中のIFに書いたラベルも定義されません。ラベルは、独立した行に書くのがいちばん安全です。

15.7 メッセージ

条件付きアセンブルについてのメッセージを、それぞれの原因となる行とともに表15.2に示します。

行

メッセージ

開いているIFがないのに
ELSEかENDIF、
1つのIFに2つ目のELSE

ELSE or ENDIF without a matching IF.

閉じられていないIF

conditional not closed by ENDIF.

16個のIFの中の
17個目のIF

conditionals nested too deeply.

式のないif

bad expression.

if later
(laterはもっとあとで定義)

undefined symbol in an expression.

endc

not a directive, a macro or an instruction.

表 15.2: 条件付きアセンブルについてのメッセージ

閉じられていない条件付きアセンブルにTATARAが気づくのはソースの終わりでですが、見つけやすいように、メッセージはIFの行を示します。複数が開いたままのときは、いちばん外側のものを示します。