Line convert TO UPPer の略です。行入力バッファの内容を英大文字に変換します。なぜそうするのかというとプログラムの解釈実行の際「キーワードはかならず大文字になっているもの」としたほうが文字列の比較が速くなるからです。
クオーテーションの中や注釈は、プログラム実行の際 SCAN や Qxx シリーズのルーチンの比較対象になることはないので変換しません。
また、このルーチンはプログラム入力の際の行末の空白を取り除く作用もあります。大文字に変換することと、行末の空白を取り除くことは本来全然関係ない処理で、私がごく初期に作ったインタプリタでは別のルーチンになっていました。しかし、どちらもトップレベルから1度づつ呼ばれるだけで、他の処理から呼ばれることはないため、いっしょくたにまとめられました。
LTOUPP: MOV.L LINBUF4,R3 ; 行入力バッファの先頭番地 |
LTOUPL: MOV.B @R3,R0 ; 1文字読む CMP/EQ #0,R0 ; 行末なら BT LTOUE2 ; LTOUE2へとぶ CMP/EQ #H'27,R0 ; 注釈なら BT LTOUE1 ; LTOUE1へとぶ CMP/EQ #H'22,R0 ; ダブルクオートなら BT LTOUPQ ; LTOUPQへとぶ MOV #H'61,R13 ; 'a'より CMP/HS R13,R0 ; 小さければ BF LTOUP1 ; LTOUP1へとぶ MOV #H'7A,R13 ; 'z'より CMP/HI R13,R0 ; 大きければ BT LTOUP1 ; LTOUP1へとぶ |
ADD #-H'20,R0 ; H'20を引いて大文字にに変換 MOV.B R0,@R3 ; バッファを書き直す |
LTOUP1: BRA LTOUPL ; ループ先頭に戻る ADD #1,R3 ; R3を1文字進める(遅延スロット) |
; ダブルクオートの場合 LTOUPQ: MOV.B @(1,R3),R0 ; 次の文字を読む LTUPQL: ADD #1,R3 ; R3を1文字進める CMP/EQ #0,R0 ; 行末なら BT LTOUE2 ; LTOUE1へとぶ CMP/EQ #H'22,R0 ; ダブルクオートなら BT LTOUP1 ; LTOUP1へもどる BRA LTUPQL ; ダブルクオートループに戻る MOV.B @(1,R3),R0 ; 次の文字を読む(遅延スロット) |
; 注釈スキップ LTOUE1: MOV.B @R3,R0 ; 1文字読む CMP/EQ #0,R0 ; 行末なら BT LTOUE2 ; LTOUE2へとぶ BRA LTOUE1 ; 注釈スキップループに戻る ADD #1,R3 ; R3を1文字進める(遅延スロット) |
; 行末の空白トリム LTOUE2: ADD #-1,R3 ; 1文字戻す MOV.B @R3,R0 ; 1文字読む MOV #H'20,R13 ; 空白の文字コード EXTU.B R0,R0 ; 読んだ文字の符号無し化 CMP/HI R13,R0 ; 読んだ文字が空白でなければ BT LTOUPE ; LTOUPEへとぶ MOV #0,R0 ; 空白を行末の0で BRA LTOUE2 ; 空白トリムループに戻る MOV.B R0,@R3 ; 置き換えて(遅延スロット) |
LTOUPE: RTS ; リターン NOP ; (遅延スロット) |
それぞれ、SCaN EQual Line、SCaN High or Same Line の略です。同じ行番号、または等しいか大きい行番号の行を探すルーチンです。 ENTPRG では編集の対象となる行を探したり、入力された行を挿入すべき位置を求めるためにこれらのルーチンを使います。ちなみに、 SCNEQL はGOTO文、GOSUB文で目的の行番号を探すのにも使われます。
SCNEQL: MOV.L @(BASTOP-WRKTOP,GBR),R0 ; BASICプログラムの開始番地 MOV R0,R3 ; をR3にロード SCNELP: MOV.L @R3,R1 ; 行番号をR1にロード MOV.B @(4,R3),R0 ; 行の長さをR0にロード TST R1,R1 ; 行番号が0(プログラムの終わり)なら BT SCNEN ; スキャン失敗 CMP/EQ R1,R2 ; 目的の行番号と一致したら BT SCNEE ; スキャン成功 EXTU.B R0,R0 ; 長さ符号無し化 ADD #5,R3 ; 行番号と長さをスキップ BRA SCNELP ; ループ先頭に戻る ADD R0,R3 ; 次の行の番地を計算(遅延スロット) ; スキャン成功 SCNEE: RTS ; リターン SETT ; Tビットをセット ; スキャン失敗 SCNEN: RTS ; リターン CLRT ; Tビットをクリヤ SCNHSL: MOV.L @(BASTOP-WRKTOP,GBR),R0 ; BASICプログラムの開始番地 MOV R0,R3 ; をR3にロード SCNHLP: MOV.L @R3,R1 ; 行番号をR1にロード MOV.B @(4,R3),R0 ; 行の長さをR0にロード TST R1,R1 ; 行番号が0(プログラムの終わり)なら BT SCNEN ; スキャン終了 CMP/HS R2,R1 ; 目的の行番号と一致したら BT SCNEN ; スキャン終了 EXTU.B R0,R1 ; 長さ符号無し化 ADD #5,R3 ; 行番号と長さをスキップ BRA SCNHLP ; ループ先頭に戻る ADD R1,R3 ; 次の行の番地を計算(遅延スロット) |
名前通り、Z80のブロック転送命令と類似の働きをします。R3に転送元の番地、R2に転送先の番地、R1に転送するバイト数を入れて呼び出します。
LDIR はほぼZ80と同じポストインクリメント動作なのですが、LDDR はプリデクリメント動作になります。結局そのほうが使い勝手がよいからです。
LDIR: TST R1,R1 ; 転送するバイト数が BT LDIRE ; 0なら即終了 LDIRL: MOV.B @R3+,R0 ; 1バイト読む DT R1 ; 残りバイト数チェック MOV.B R0,@R2 ; 1バイト書き込む BF/S LDIRL ; まだ残りがあるならループ ADD #1,R2 ; R2インクリメント(遅延スロット) LDIRE: RTS ; リターン NOP ; (遅延スロット) |
LDDR: TST R1,R1 ; 転送するバイト数が BT LDDRE ; 0なら即終了 LDDRL: ADD #-1,R3 ; R3デクリメント MOV.B @R3,R0 ; 1バイト読む DT R1 ; 残りバイト数チェック BF/S LDDRL ; まだ残りがあるならループ MOV.B R0,@-R2 ; 1バイト書き込む LDDRE: RTS ; リターン NOP ; (遅延スロット) |
なぜプリデクリメント動作のほうが使い勝手がよいかですが、それは以下のZ80版CMBの ENTPRG の一部分(行挿入を行う INSLNX )のリストを見てもらえばわかります。LDDRを実行する前にHLとDEをデクリメントしているでしょう。LDDR がプリデクリメント動作ならこの2つのデクリメント命令は要らないわけです。
|