行入力処理ENTPRG

戻る



 いよいよ実際に行を入力する部分、ENTPRG の解説です。以前見たトップレベルからは、R0に行番 号が入った状態で ENTPRG に飛んできます。また、行番号部分を取り除いたテキストの先頭番地はワー クエリア GTPTR に入っています。

・空行か、中身のある行かの判別

ENTPRG:	MOV	R0,R2			; 行番号をR2へ
	MOV.L	@(GTPTR-WRKTOP,GBR),R0	; 入力されたテキストの先頭番地
	MOV	R0,R3			;   をR3へ
	BSR	SKIPSP			; 空白をとばす
	MOV	R3,R4			; R3をR4に保存(遅延スロット)

	MOV	R4,R3			; R3(テキストの先頭番地)復帰
	CMP/EQ	#0,R0			; SKIPSPが返した文字をチェック
	BT	DELLIN			; 空テキストだったら行削除

 まず SKIPSP で空白をとばしてみます。その結果見つかったのが行末の0であったなら、入力されたのは空行なので、その行を削除する処理 DELLIN へ飛びます。そうでなければメモリに入力されるべき中身のある行です。

・行入力(中身のある行だった場合)

・長さ再チェック

	MOV	#MAXLL+6,R13		; 入力可能な最大バイト数
	MOV	#6,R1			; 長さカウンタ=最小バイト数(行番号+長さ+行末)
	EXTU.B	R13,R13			; 127より大きいのでEXTUする

ENTPL:	CMP/EQ	R13,R1			; 長さカウンタ=最大バイト数なら
	BT	ENTP1			;   ENTP1へとぶ
	MOV.B	@R3,R0			; その位置に
	CMP/EQ	#0,R0			; 行末の0はあるか?
	BT	ENTP2			; あればENTP2へとぶ
	ADD	#1,R3			; ポインタインクリメント
	BRA	ENTPL			; ENTPLに戻る
	ADD	#1,R1			; 長さカウンタインクリメント(遅延スロット)

; 最大バイト数まで行末が見つからなかった場合
ENTP1:	MOV	#0,R0			; 強制的に
	MOV.B	R0,@R3			; 行末の0を置く

 入力された行の長さを再度チェックします。長さのチェックは GETLN でも行っているのになぜまたやるのかというと、行番号部分を処理すると長さが変わってしまうからです。メモリ上では行番号は4 バイト整数として格納しますから、たとえば行番号として1をつけると、入力時には1バイトのアスキ ーコードだったのがメモリ上では H'00000001 という4バイト整数にふくれあがります。
 チェックの結果許される長さを超えている場合、強制的に行末の0を置きます。

・4バイト境界合わせ

ENTP2:	MOV	R1,R0			; 長さカウンタの下位2ビットが0なら
	TST	#3,R0			; (つまり4で割り切れるなら)
	BT	ENTP3			;   ENTP3へとぶ
	MOV	#0,R0			; でなければ0を
	ADD	#1,R3			; ポインタインクリメントして
	MOV.B	R0,@R3			; もう1つおく
	BRA	ENTP2			; ENTP2に戻る
	ADD	#1,R1			; 長さカウンタインクリメント

 行の長さを4バイト境界に合わせる処理です。4バイト境界に合うまで長さカウンタR1をインクリメントしながら0を置いていきます。

・行番号検索(行入力の場合)

ENTP3:	MOV	R1,R0			; 長さをR0へ
	BSR	SCNHSL			; SCNHSLで入力すべき番地を探す
	MOV.B	R0,@(ENTLEN-WRKTOP,GBR)	; R0をENTLENにストア(遅延スロット)

	MOV	R3,R0			; 入力すべき番地を
	MOV.L	R0,@(ENTADR-WRKTOP,GBR)	; ENTADRにストア

 R1に求められた、メモリに格納されるときの最終的な長さをワークエリア ENTLEN に書き込み、その行を格納すべき番地を前回説明した SCNHSL で探します。DELLIN の時と違い、必ずしも同じ行番号がすでに存在している必要はありません。まったく同じ行番号があればその番地が返りますし、なければ入力された行番号の値を最初に超える行の番地が返ります。入力された行番号が既に存在するどの行番号よりも大きい場合(行が全く存在しない場合も含みます)、最後に置かれている「見えない行」の番地を返ります。
 見つけた行の番地をワークエリア ENTADR に書き込みます。

・同じ行番号が無かった場合

	CMP/EQ	R2,R1			; すでに同じ行番号の
	BF	INSLIN			; 行がなければINSLINへとぶ

 見つけた行と全く同じ行番号が存在していればその行を「置換」することになりますが、同じ行番号が無かった場合は既存の行の間に「挿入」することになりますので INSLIN へ飛びます。

・同じ行番号があった場合

	ADD	#4,R3			; 見つけた行の行番号をスキップ
	MOV.B	@R3+,R2			; 見つけた行の長さ
	MOV.L	@(BASEND-WRKTOP,GBR),R0	; BASICプログラムの終わり
	EXTU.B	R2,R2			; 長さバイトを符号無し化
	ADD	R2,R3			; 次の行の先頭番地
	SUB.L	R3,R0			; 転送する長さを計算
	MOV.L	R0,@(GTLEN-WRKTOP,GBR)	; 転送する長さをGTLENにストア

 同じ行番号があった場合の処理です。この場合は、見つかった行の長さが変化するわけですから、見つけた行の次の行から最後の行までを、見つけた行の長さの変化に応じてずらさなければいけません。そこでまず、見つけた行の次の行から最後の行までの長さを計算します。

・長さの増減による仕分け

	MOV.B	@(ENTLEN-WRKTOP,GBR),R0	; 入力された行の長さ
	MOV	R0,R1			; 
	ADD	#-5,R1			; 行番号と長さバイトぶん引く
	SUB	R2,R1			; 番地の増加分を計算
	TST	R1,R1			; 
	BT	REPLNX			; 同じ長さならREPLNXへ
	CMP/PL	R1			; 
	BT	INCLIN			; 増える場合はINCLINへ

 入力された行の長さが元々の長さと同じ場合、減る場合、増える場合がありますので、それぞれ仕分けします。
 R2には見つけた行の長さ(行番号と長さバイトを除いた長さ)が入っていますので ENTLEN の値と比較します。ENTLEN の値から5を引いているのは、その値には行番号と長さバイトの長さも含まれているからです。

・行が短くなる場合

	MOV	R3,R2			; R3には次の行の先頭番地
	ADD	R1,R2			; 転送先番地の計算
	MOV.L	@(GTLEN-WRKTOP,GBR),R0	; 転送する長さ
	BSR	LDIR			; 次の行以降を前にずらす
	MOV	R0,R1			; 転送する長さをR1に(遅延スロット)

	MOV	R2,R0			; 
	MOV.L	R0,@(BASEND-WRKTOP,GBR)	; BASEND と FREE を設定しなおす
	BRA	REPLNX			; REPLNX に飛ぶ
	MOV.L	R0,@(FREE-WRKTOP,GBR)	; (遅延スロット)

 行が短くなる場合の処理です。新しい行を目的の番地に書き込む前に、その行以降のプログラムを、目的の行が短くなったぶん前にずらさなければいけません。R3には次の行の先頭番地、R1には番地の増加分がはいっていますので、加算すると転送先番地が計算できます。
 LDIR で次の行以降をずらした後、BASEND と FREE を設定しなおしてから、新しく入力された行の内容に書き換えるべく、REPLNX に飛びます。

・行が長くなる場合

INCLIN:	MOV.L	@(BASEND-WRKTOP,GBR),R0	; 転送元番地
	BRA	INSLNX			; INSLNX へ飛ぶ
	MOV	R0,R3			; 転送元番地をR3へ(遅延スロット)

 行が長くなる場合の処理は、次の行以降を後ろにずらしてから目的の行を書き換えるわけですが、次の行以降を後ろにずらす処理は、行を挿入する処理 INSLIN とほとんど同じです。すでに番地増加分は求まっていますので、転送元番地である BASEND をR3にロードして、INSLIN の実行部分 INSLNX へ飛びます。

・同じ行番号が無くて行挿入を行う場合

INSLIN:	MOV	R0,R3			; 見つけた行の番地
	MOV.L	@(BASEND-WRKTOP,GBR),R0	; BASICプログラムの終わり
	MOV	R0,R2
	SUB	R3,R0			; 転送する長さを計算
	MOV.L	R0,@(GTLEN-WRKTOP,GBR)	; 長さを GTLEN にストア
	MOV.B	@(ENTLEN-WRKTOP,GBR),R0	; 入力された行の長さ=番地増加分
	MOV	R2,R3			; BASEND=転送元番地
	EXTU.B	R0,R1			; 番地増加分の符号無し化

 同じ行番号がなかったために、行挿入を行うわけですが、その前処理として SCNHSL で見つけた行以降を、入力された行の長さぶん後ろにずらすための番地計算をします。転送には LDDR を使うので、転送元の転送開始番地は BASEND です。

・プログラムサイズのチェック(INCLIN、INSLIN共通)

INSLNX:	MOV.L	@(ENDFRE-WRKTOP,GBR),R0	; フリーエリアの終わりの番地
	MOV	R3,R2			; 現在の BASEND の値に
	ADD	R1,R2			; 番地増加分を足した値が
	CMP/HI	R0,R2			; フリーエリア最終番地より
	BT	JOUTMEM1		; 大きければ OUT OF MEMORY

 プログラムが大きくなるわけですから、フリーエリアに収まるかどうかの判定を行います。収まらないようであれば OUT OF MEMORY を表示するエラー処理エントリに飛び、その行の入力はなされません。

・次の行以降を再配置(INCLIN、INSLIN共通)

	MOV	R2,R0			; 転送先転送開始番地=新しいBASEND
	MOV.L	R0,@(BASEND-WRKTOP,GBR)	;   をストア
	MOV.L	R0,@(FREE-WRKTOP,GBR)	;   FREE にもストア
	MOV.L	@(GTLEN-WRKTOP,GBR),R0	; 転送する長さ
	BSR	LDDR			; 次の行以降を再配置
	MOV	R0,R1			; 長さをR1に(遅延スロット)

 BASEND、FREE を設定しなおして次の行以降を番地増加分だけ後ろにずらします。このあと、すぐ後ろに続いている REPLNX にそのままなだれ込みます。

・古い行を入力された行で置き換え(行入力する全てのケースに共通)

REPLNX:	MOV.L	@(ENTADR-WRKTOP,GBR),R0	; 置き換えるべき行の番地を
	MOV	R0,R2			;   R2に
	MOV.L	@(ENTNO-WRKTOP,GBR),R0	; 入力された行番号を
	MOV	R0,@R2			;   行の先頭に書き込む
	ADD	#4,R2			; 4バイト進める
	MOV.B	@(ENTLEN-WRKTOP,GBR),R0	; 入力された行の長さを
	EXTU.B	R0,R1			;   符号無し化して
	ADD	#-5,R1			;     行番号と長さバイトのぶんを引く
	MOV.B	R1,@R2			; 長さバイトを書き込む
	ADD	#1,R2			; 1バイト進める
	MOV.L	@(GTPTR-WRKTOP,GBR),R0	; 入力された行のテキスト部分
	BSR	LDIR			; テキスト部分を新しいテキストで置き換え
	MOV	R0,R3			; 転送元番地をR3へ(遅延スロット)

	BRA	TOPLEV			; トップレベルに飛ぶ
	NOP				; (遅延スロット)

 見つけた行(同じ行番号がなかった場合 INSLIN で確保された領域)を新しく入力された行で上書きします。それで行入力処理は完了ですので、トップレベルにジャンプします。

・行を削除する場合

・行番号検索(行削除する場合)

DELLIN:	BSR	SCNEQL			; 同じ行番号を探す
	NOP				; (遅延スロット)

	BF	JBADLIN3		; 見つからなければエラー

 行番号だけ入力した場合の一行削除処理です。指定の行番号と完全に行番号が一致する行が存在していなければいけないので、行の検索には SCNEQL を使います。見つからなかった場合はエラーとなります。

・行削除実行

	MOV	R3,R2			; 見つけた番地をR2に保存
	ADD	#4,R3			; 行番号スキップ
	MOV.B	@R3+,R1			; 行の長さ
	MOV.L	@(BASEND-WRKTOP,GBR),R0	; BASICプログラムの終わり
	EXTU.B	R1,R1			; 長さを符号無し化
	ADD	R1,R3			; 次の行の番地を計算
	MOV	R0,R1			; BASEND をR1へ
	BSR	LDIR			; 転送実行
	SUB	R3,R1			; 転送する長さの計算(遅延スロット)

	MOV	R2,R0			; LDIR実行後のR2の値は 
	MOV.L	R0,@(BASEND-WRKTOP,GBR)	;   新しいBASENDの値
	BRA	TOPLEV			; 同じ値を
	MOV.L	R0,@(FREE-WRKTOP,GBR)	;   FREEにもストア(遅延スロット)

 見つけた行の次の行から最後の行までを、見つけた行の先頭番地に転送することによって、見つけた行を削除します。削除後はトップレベルにとびます。

 ところで、今回あちこちに BASEND と FREE に全く同じ値を書き込んでいるところがあります。「これ、意味ないんじゃ?」と思う方もいるかもしれません。確かに現状のCCMBでは意味がないのですが、これは将来配列変数や、C言語で言うところの malloc() のような処理を実装するための布石です。
 RUNコマンドでBASICプログラムを走らせて、配列変数の宣言などが行われた際には、その領域は BASEND の直後に確保され、その大きさのぶんだけ FREE の番地が進むようにする予定でした(実際ワンダーウィッチ版はそうなっています)。ですから、トップレベルでプログラムを入力している時点では BASEND と FREE は全く同じ値なのですが、RUNしてから変わってくる可能性があるわけです。



 最後に、文章だけでは処理のイメージがつかみにくい、という人のために、同じ行番号が存在して長さが変わる場合の処理イメージ図を載せます。 長さが変わらない場合と、INSLIN、DELLINについてのイメージ図は省略しますが、皆さん各自で描いて見ると理解が深まるとおもいます。
 では、今回はここまでとします。

・同じ行番号があり行が長くなる場合



・同じ行番号があり行が短くなる場合