トップレベルプロンプト

コマンドと文のダイレクト実行

戻る



 ユーザーからのコマンドやプログラムの入力を受け付け、実行する処理です。「トップレベル」というのは一応ここがBASICインタプリタプログラムの階層としては最上位だからです。BASIC言語に限らず、キャラクタユーザーインターフェースの対話処理系を作る際に参考になると思います。

 BASICインタプリタは初期化が終わった後、トップレベルでユーザーからの入力とそれへの応答を延々繰り返します。BASICプログラムの実行も結局のところはRUNコマンドに対する応答です。

 基本的には GETLN で1行入力し、先頭に数値があればそれをプログラムの入力とみなしてメモリに貯え、そうでなければコマンドもしくは文のダイレクト実行とみなして即時実行します。

・トップレベルプロンプト

TOPPRM:	MOV.L	M_OK1,R2		; "OK"
	BSR	PUTSTR			;   を表示する
	NOP				; (遅延スロット)

 「コマンド」が終了した場合ここに飛んできます。「OK」のプロンプトを表示します。

TOPLEV:	MOV.L	WRKTOP3,R15		; ワークエリアの先頭=ハードスタックの初期値
	LDC	R15,GBR			; GBR初期化
	MOV.L	@(SPLIM-WRKTOP,GBR),R0	; ソフトスタックの初期値
	MOV	R0,R14			; ソフトスタック初期化

 GBR、そしてハードスタック(R15)とソフトスタック(R14)を初期化します。こうして TOPLEV の頭で毎回GBRとスタックを初期化しているので、呼ばれたルーチンがGBRを通常とは違う値に書き換えたまま、あるいはスタックに何かデータを積み残したまま JMP で直接 TOPPRM や TOPLEV に飛んでくるような乱暴な戻り方をしても大丈夫です。
 ちなみに、このシステムのRAM部分のメモリマップは次のようになっています。



WRKTOP はワークエリアの先頭番地であると同時にハードスタックの初期値であり、SPLIM はハードスタックの下限アドレスであると同時にソフトスタックの初期値でもあります。ついでに言うとソフトスタックの下限アドレスは SSLIM です。

	MOV	#0,R0			; 0で
	BSR	GETLN			;             1行入力
	MOV.B	R0,@(RUNFLG-WRKTOP,GBR) ;   RUNFLG をクリヤして

	BT	TOPLEV			; CTRL−Cなら破棄

 ワークエリア RUNFLG をクリヤしてから GETLN を呼びだします。RUNFLG は、RUNコマンドによるBASICプログラム実行中のみ1になります。トップレベルでは必ず0にします。ダイレクト実行するときとプログラム中で実行するときとで違う動きをする必要のある文は、このワークエリアを見て判断しています。
 また、GOTO文をダイレクト実行した場合、世間に流通する多くのBASICインタプリタはそのままプログラムが実行される(RUN <行番号>とほぼ等価な働きをする)のですが、CMB/CCMBはGOTOをダイレクト実行しても RUNFLG が立たないのでプログラムは実行されません。プログラムの実行はRUNコマンドによってのみ可能です。

 GETLN は前回説明した通り、1行分の入力を行います。入力結果はワークエリア LINBUF に、末尾が0で終わる形の文字列として格納されます。GETLN がCTRL−Cで中断されたときは TOPLEV に戻り、入力された内容は無視します。

	BSR	LTOUPP			; 大文字に変換する
	NOP				; (遅延スロット)

	MOV.L	LINBUF4,R3		; 行入力バッファの先頭番地
	BSR	SKIPSP			; 空白を飛ばす
	NOP				; (遅延スロット)

 LTOUPP で LINBUF の内容を英大文字に変換します。次の SKIPSP で行先頭の空白を飛ばします。

	BSR	GETLIT			; 行番号はあるか?
	NOP				; (遅延スロット)

	BT	TOPLV1			; あれば TOPLV1 へ(R2には行番号)
	MOV	#0,R2			; なければR2を0にして
	BRA	TOPLV2			;   TOPLV2 へ

 「10進数を扱う」の回で出てきた GETLIT を使い、行頭に数字があるかどうか調べます。数字があったら行番号付きのプログラム行入力なので TOPLV1 に飛び、数字でなければコマンドもしくは文のダイレクト実行なのでR2を0にして TOPLV2 に飛びます。(この MOV #0,R2 、遅延スロットに入れるべきだろ・・・ダメだなぁ俺。次の命令がが特に害のないTSTだから命拾いしてる・・・)

TOPLV1:	TST	R2,R2			; R2(入力された行番号)が0なら
	BT	JBADLIN2		;   JBADLIN2 へ
	ADD	#1,R2			; R2が H'FFFFFFFF
	TST	R2,R2			;   なら
	BT	JBADLIN2		;     JBADLIN2 へ
	ADD	#-1,R2			; 1足したぶん戻す

 行頭に数字があったときの処理です。行番号として0と H'FFFFFFFF の入力は認められませんから、ここではじきます。
 一応説明しておくと、 H'FFFFFFFF であるかどうかを調べるため、1を足して0になるかどうかをチェックします。もちろんチェックが終わったら1を引いて元の値に戻します。

TOPLV2:	MOV	R2,R0			; R2(入力された行番号)を
	MOV	R0,@(ENTNO-WRKTOP,GBR)	;   ENTNO に格納

 ここで、「プログラム入力かダイレクト実行か」で分かれた処理が再び合流します。R2には、ダイレクト実行の場合には0が、プログラム入力の場合には GETLIT で得た行番号が入っています。それをワークエリア ENTNO に格納します。以後、「プログラム入力かダイレクト実行か」は ENTNO が0かどうかで判断します。

 入力する行番号として0が認められないのは、初回に説明した「見えない行」のためもありますが、「プログラム入力かダイレクト実行か」の判別のためでもあります(0ならダイレクト実行とみなす)
 H'FFFFFFFF を認めないのは、CCMBにはありませんが、CMBではスタートアップスクリプトの有無を判断するために必要でした。

	MOV.B	@R3,R0			; 行番号の後の最初の数字じゃない文字
	CMP/EQ	#H'20,R0		;   が空白なら
	BF	TOPLV3			;     TOPLV3 へ
	ADD	#1,R3			; 空白1文字スキップする

TOPLV3:	MOV	R3,R0			; 文字ポインタを
	MOV.L	R0,@(GTPTR-WRKTOP,GBR)	;   GTPTR に格納

 R3は、数値がなければ進んでいませんが、数値があったらそのぶん GETLIT で進んでいるはずです。数字の後の最初の1文字を見て、それが空白なら1文字とばします(ダイレクト実行の場合は最初の SKIPSP で空白は飛ばされているのでここに空白はないはずです)
 そして最終的にその番地を GTPTR という、汎用ポインタ保存ワークエリアに保存します。

	MOV.L	@(ENTNO-WRKTOP,GBR),R0	; ENTNO が
	CMP/EQ	#0,R0			;   0でなければ(行番号付きなら)
	BF	ENTPRG			;     ENTPRG へ

 ENTNO に格納されている行番号を見て、0でなければプログラムを入力する処理 ENTPRG に飛びます。ENTPRG の説明は回を改めて行う予定です。

; (ダイレクト実行するケース)
	BSR	EXELIN			;   EXELIN を呼びだす
	MOV	R3,EXEP			; 文字ポインタを EXEP にコピーして(遅延スロット)

	BRA	TOPLEV			; 呼びだし後 TOPLEV に戻る
	NOP				; (遅延スロット)

 EXELIN を呼びだして入力されたコマンドもしくは文をダイレクト実行します。R3には GTPTR にも書きこんだ空白スキップ済みの番地が入っているので、それを EXEP にコピーします(GTPTR に書きこんだ値はプログラム入力のために使い、ダイレクト実行には使いません)。 呼びだし先からRTSで戻ってきた場合はその次のBRAでトップレベルプロンプトに飛びます。コマンドなどはRTSせずに直接 TOPPRM にジャンプしています。

・行実行ルーチン EXELIN

 トップレベルプロンプトから呼ばれる、行実行ルーチンです。(ちなみにRUNコマンドからも呼ばれます。RUNコマンドはメモリに蓄えられたプログラムの1行1行に対して順次このルーチンを呼び出すことによってプログラムの実行をするわけです。)
EXELIN:	STS.L	PR,@-R15		; PR保存
	MOV	#0,R0			; 0で
	MOV.B	R0,@(EXLFLG-WRKTOP,GBR)	; EXLFLG をクリヤ

 行実行の先頭で1回だけ実行される部分です。PRを保存し、ワークエリア EXLFLG を0にクリヤします。EXLFLG についての説明は後ほど。

EXELLP:	MOV	#0,R0			; 0で
	MOV.B	R0,@(EXSFLG-WRKTOP,GBR)	; EXSFLG をクリヤ

	BSR	CHKBRK			; CTRL−Cキーのチェック
	NOP				; (遅延スロット)

 行実行ルーチンのループ先頭部分です。行の内容が複数の文からなる「マルチステートメント」であった場合、個々の文の実行前に実行されます。
 文の実行前に毎回 EXSFLG を0にクリヤします。EXSFLG についてものちほど説明します。
 次に CHKBRK を呼び、CTRL−Cによる中断要求がないかどうか調べます。もしCTRL−Cが押されていたら戻ってきません。

	BSR	SKIPSP			;   空白を飛ばす
	MOV	EXEP,R3			; EXEP をR3にコピーして(遅延スロット)

	CMP/EQ	#0,R0			; 行末なら
	BT	EXELNE			;   行実行終了

 実行ポインタ EXEP をR3にコピーし、空白を飛ばします。その結果、行末に到達してしまったら、もうやることはないので行実行ルーチンを終了します。

	MOV.L	STATM1,R2		; 文・コマンドのネームテーブル
	BSR	SCAN			; SCAN 呼びだし
	NOP				; (遅延スロット)

	BT	EXELN1			; 一致するキーワードがあったら EXELN1 へ

 文やコマンドのキーワードであるかどうか調べます。「低レベルテキスト解析ルーチン」の回で説明した SCAN を使います。一致するキーワードがあれば EXELN1 へ飛びます。

;(一致するキーワードが無かった場合)
	BSR	QLET			; 代入文か?
	NOP				; (遅延スロット)
;(代入文ならR2には変数の番地が入っている)

	BF	EXELN2			; 代入文でなければ EXELN2 へ

;(代入文だった場合)
	BSR	GETFRM			;   GETFRM を呼びだし
	MOV.L	R2,@-R15		; 変数の番地を保存(遅延スロット)

;(R2には式の値が入っている)
	MOV.L	@R15+,R0		; 変数の番地を取りだし
	BRA	EXELN3			;   EXELN3 へ
	MOV.L	R2,@R0			; 式の値を変数に代入して(遅延スロット)

 QLET で代入文であるかどうか調べます。違っていたら EXELN2 へ飛びます。代入文なら '<変数>=' のぶん文字がスキップされR2には変数の番地が入っているはずなのでスタックに保存し、数式を解釈するルーチン GETFRM を呼んで数式の値をR2に獲得します。なお、GETFRM については後日徹底的に解説するつもりです。
 GETFRM から帰ってきたら、スタックから変数の番地をR0に取りだし、R2の数式の値を書きこみます。それで代入文の実行は完了なので、文実行後共通処理 EXELN3 へ飛びます。

;(一致するキーワードがあった場合)
EXELN1:	JSR	@R2			; そのキーワードの処理ルーチンを呼びだす
	MOV	R3,EXEP			; R3を EXEP に書き戻す

	MOV.B	@(EXLFLG-WRKTOP,GBR),R0	; EXLFLG が
	CMP/EQ	#0,R0			;   0でなければ
	BF	EXELNE			;     行実行終了

 文やコマンドの処理です。R3を EXEP に書き戻してからJSRで処理ルーチンを呼びだします。呼びだし後 EXLFLG をチェックし、0でなければその行の実行を終了します。
 EXLFLG とは、Exit line flag の略で、GOTO文やGOSUB文で他の行に飛ぶ場合や、1行IF文で条件不成立の場合などで、その行の残り部分を実行する必要がなくなった場合に1にセットされます。行実行ルーチンは文の実行ルーチンを呼んだ後、EXLFLG がセットされていたらその行の残りの部分の実行を放棄するわけです。

EXELN2:	MOV.B	@(EXSFLG-WRKTOP,GBR),R0	; EXSFLG が
	CMP/EQ	#0,R0			;   0でなければ
	BF	EXELLP			;     ループ先頭に戻る
	MOV	EXEP,R3			; EXEP をR3にコピー

 EXSFLG をチェックします。EXSFLG が0でなければ、文が終わったものと解釈しループ先頭 EXELLP へ飛びます。通常の文は、文の終わりは行末の0か、マルチステートメントのための ':' 、または注釈のシングルクオートでなければならず、それ以外の文字があったらエラーとみなします(EXELN3 以降の処理)
 IF文は条件式とその後の文の間に ':' はありませんが、条件が成立した場合はIF文が完結したものとみなしてその後の文をマルチステートメントと同様に実行しなければいけません。そういうときに EXSFLG を1にセットするのです。
 ちなみに EXSFLG は Exit statement flag の略ですが、EXLFLG とは微妙にニュアンスが異なるので注意してください。EXLFLG は文実行ルーチンが「その行を出ろ!」とインタプリタに命令する感じですが、EXSFLG は、「文の終了処理は文実行ルーチン側で済ませてます」という感じです。
 最後にEXEP をR3にコピーしているのは次で SKIPSP を使うためです。

;(文の実行が終わった後の処理)
EXELN3:	BSR	SKIPSP			; 空白を飛ばす
	NOP				; (遅延スロット)

	MOV	R3,EXEP			; R3を EXEP に書き戻す
	CMP/EQ	#0,R0			; 行末なら
	BT	EXELNE			;   行実行終了
	CMP/EQ	#H'27,R0		; 注釈なら
	BT	EXELNE			;   行実行終了
	CMP/EQ	#H'3A,R0		; コロンでなければ
	BF	JSYNERR4		;   SYNTAX ERROR にする
	ADD	#1,R3			; コロンをスキップ

	BRA	EXELLP			; ループ先頭に戻る
	MOV	R3,EXEP			; R3を EXEP に書き戻す(遅延スロット)

 文の実行が完了した後の処理です。SKIPSP で空白を飛ばし、行末、注釈、':' 以外のものを検出したらエラーとします。行末か注釈なら EXELIN を終了します。':' ならその先にまだ文があるので ':' をスキップして EXELLP に戻ります。

EXELNE:	LDS.L	@R15+,PR		; PR復帰

	RTS				; リターン
	NOP				; (遅延スロット)

 お約束のリターン処理です。

・QLET

 本来「低レベルテキスト解析」の回で説明すべきルーチンでしたが、すっぽり抜け落ちていました。この手のルーチンはさんざん説明したので、リストを挙げるだけにとどめます。みなさん、練習問題だと思って各自で読解してみてください。
QLET:	STS.L	PR,@-R15	; PR保存
	BSR	SKIPSP		;   空白を飛ばす
	MOV	EXEP,R3		; EXEP をR3にコピーして(遅延スロット)

	BSR	GETVAR		;   変数かどうかチェック
	MOV.L	R3,@-R15	; R3を保存して(遅延スロット)

	BF	QLETN		; 変数でなければ否定的終了

;(変数だった場合、R2には変数の番地が入っている)
	BSR	SKIPSP		; 変数の後の空白を飛ばす
	NOP			; 

	CMP/EQ	#H'3D,R0	; '=' か?
	BF	QLETN		;   違ったら否定的終了

	ADD	#1,R3		; '=' をスキップ
	MOV	R3,EXEP		; R3を EXEP に書き戻す

	ADD	#4,R15		; スタックに保存したR3を破棄
	LDS.L	@R15+,PR	; PR復帰
	RTS			; リターン
	SETT			; Tビットをセットする(遅延スロット)

;(否定的終了処理)
QLETN:	MOV.L	@R15+,R3	; R3復帰
	LDS.L	@R15+,PR	; PR復帰
	RTS			; リターン
	MOV	R3,EXEP		; Tビットをクリヤする(遅延スロット)