Lesson 6インタプリタの構成インタプリタは,どのように動作するか
開発する Tiny BASIC のインタプリタの基本構造を示します.このページは,石田晴久著「マイクロコンピュータプログラミング入門 —Tiny BASIC インタプリタ—」の内容です.これを理解するために書きました.
目次
どうやって書くか
インタプリタは,コンピューターがプログラムを「読む」ための仕組みです.BASIC の命令を一行ずつ解釈して実行するので,書かれたコードをそのまま動かすことができます.これは,あらかじめ全体を機械語に翻訳してから実行するコンパイラ方式と異なり,プログラムの入力と実行を一つの流れで扱えるのが特徴です.
この章では,Tiny BASIC のインタプリタがどのようにデータを入力し,どのように命令を実行し,どのように結果を返すのかをわかりやすく説明します.まず全体の流れをつかみ,次に図1の構成にしたがって各ルーチンの役割を見ていきます.
インタプリタとは
インタプリタは,プログラムの命令を順番に読み,その場で命令を実行します.Tiny BASIC では,入力した一行を解析して,行番号付きの行ならプログラムとして保存し,行番号なしの行なら命令実行やコマンド処理を行います.
Tiny BASIC インタプリタの役割
Tiny BASIC のインタプリタは,次のような仕事をします.
- キーボードから BASIC の一行を入力する
- 行番号付きの行をプログラムとして記憶する
- 行番号なしのコマンドを実行する
- プログラムの実行を開始し,次の行へ進む
- 計算や出力,条件分岐,繰り返し,サブルーチン呼び出しを処理する
このページでは,図1の構成図を使って,これらの処理がどのように分担されているかを見ていきます.
中間言語
Tiny BASIC のインタプリタでは,すべての処理を直接機械語で書くのではなく,中間言語という中間の形式を使っている部分があります.中間言語は,BASIC の式や関数,配列参照などを扱いやすい形に変換し,機械語ルーチンに渡すための仲介役です.
たとえば,A = B + C のような式は,最初に中間言語の構造で表現され,次にその構造をもとに機械語ルーチンが実際の計算を行います.このようにすることで,式の解析と演算処理を分け,プログラム全体の構造を分かりやすくしています.
この節では,中間言語が何をするためのものかを簡単に説明します.具体的なルーチンの説明は,後半の「中間言語ルーチンの機能」で行います.
構成図
図1に BASIC インタプリタの構成図を示します.インタプリタのプログラは左上の INIT から動作が始まり,矢印に従いぐるぐる回ります.通常は,GETLI(行番号付きで1行を入力) — INSERT(エディタ機能) 間のループで BASIC プログラムを1行毎に入力します.プログラムの入力が終われば,コマンド RUNによりプログラムを実行します.このプログラム実行開始コマンド RUN は,GETLI で入力されます.行番号が無いので,TSTLのルーチンで構成図の下側に分岐されます.その後は,実行行を取り出し,順次実行されます.プログラム中で END を見つけると,再びコマンド入力の GETLI に戻ります.
これだけでも,大筋の動作は分かるでしょう.より詳細な動作については,次節以降のルーチンの説明と合わせて,ひとつずつプログラムのシーケンスを追うと理解が深まります.
図1 の見方
図1 は Tiny BASIC インタプリタの大きな流れを示しています.主に「入力・編集」と「プログラム実行」の二つの部分に分かれており,矢印は処理の進み方を表します.最初は左上の INIT から始まり,必要な初期化を行った後は,行入力とコマンド処理のループに入ります.
- INIT:インタプリタの準備を行い,画面表示やメモリの初期化を行います.
- GETLI:プロンプト「*」を表示し,1行を入力します.
- TSTL:入力した行に行番号があるか調べます.行番号がある場合はプログラム行として保存し,ない場合は命令やコマンドとして処理します.
- INSRT:行番号付きの行を挿入・置換・削除してプログラムを編集します.
- RUN:プログラム実行を開始します.XINIT が実行環境を整え,プログラムの先頭行へ進みます.
- NXT:次に実行する行を決めます.直接実行の場合とプログラム実行の場合で処理が分かれます.
- END:プログラムの終わりを検出し,再びコマンド入力の GETLI に戻ります.
このように,Tiny BASIC は「入力と編集」「実行と制御」「出力と終了」を繰り返しながら動きます.入出力や計算の処理は,図の右側にある機械語ルーチンと中間言語ルーチンが協力して行います.
図1 の各矢印の意味
図1 にある矢印は,Tiny BASIC の処理がどのように移動するかを示しています.矢印の方向どおりに,入力から実行,結果の出力,そして再び入力に戻るサイクルができています.
- INIT → NLINE:インタプリタの初期化が終わると,改行を表示して次の入力を待ちます.
- NLINE → GETLI:プロンプト「*」を表示し,一行の入力を受け取ります.
- GETLI → TSTL:入力した行に行番号があるかどうかを調べます.行番号があればプログラム保存,なければ命令実行に進みます.
- TSTL → INSRT:行番号付きの行は,INSRT でプログラムの挿入・置換・削除などの編集を行います.
- TSTL → RUN/XINIT:行番号なしの行は,RUN や LIST などのコマンド,あるいは直接実行の文として処理されます.RUN の場合は XINIT で実行を始めます.
- XINIT → NXT → 実行:実行開始後は,次に実行する行を決める NXT が動き,順番に行を実行します.
- 実行結果 → END → GETLI:プログラムが END または直接実行を終えると,再び GETLI に戻り次の入力を受け付けます.
具体的なコマンド例
Tiny BASIC では,行番号付きの文と行番号なしのコマンドを区別して扱います.よく使う例は次のとおりです.
- 行番号付きの文:
10 PRINT "HELLO",20 GOTO 10,30 END
- コマンド:
RUN,LIST,NEW,CONT
- 直接実行:行番号なしで
PRINT AやA = 1を入力すると,その場で実行されます.
行番号付き文はプログラム行としてメモリに保存され,RUN を入力すると保存したプログラムが実行されます.LIST は保存したプログラム全体を表示し,NEW はプログラムを消去します.
実行例のシーケンス
次のような順番で入力と実行が進みます.
10 PRINT "HELLO" を入力すると,GETLI が行番号を検出し,INSRT がこの行をプログラムとして保存します.
20 END を入力すると,同じく INSRT が保存します.
RUN を入力すると,GETLI は行番号なしと判断し,XINIT で実行を開始します.
- XINIT の後,NXT が最初の行番号を選び,PRINT 文を実行します.出力が終わると,次の行へ進みます.
- END に達するとプログラム実行が終了し,FIN で処理をまとめて GETLI に戻ります.
このように,Tiny BASIC は行番号付き入力でプログラムを作り,RUN で実行し,END で元の入力待ちに戻る流れになります.直接実行コマンドでは,行番号なしでその場で処理して結果を返します.
機械語ルーチンの機能
先に示した Tiny BASIC のインタプリタ の構成図中の機械語ルーチンの機能は,次の通りです.
機械語ルーチンは,Tiny BASIC が高速に動くための「エンジン」です.ここでは,制御の流れ,入出力,コマンド処理,条件判定,関数呼び出し,配列処理などを担当するルーチンが紹介されています.まずは各ルーチンが何を行っているかをおおまかに把握しましょう.
| 制御の流れに関するルーチン |
| INIT |
ML-1 |
プログラムの初期化をします. |
| NLINE |
ML-2 |
改行(CR)と復帰(LF)の文字コードを出力します. |
| GETLI |
ML-3 |
プロンプト「*」を出力し,1行を読み込みます.行の入力が終わるまで,ここで待機します. |
| INSRT |
ML-5 |
行の挿入や削除,置換,追加などの編集を行います.BASIC インタプリタ中でももっとも強力なルーチン. |
| XINIT |
ML-6 |
ユーザー作成の BASIC プログラム実行時の初期化をします.スタックを空にし,最初に実行する文の行番号を用意します. |
| DONE |
ML-13A |
改行「CR」かデリミッタ「:」かを判断する.デリミッタの場合,その後の文に制御を移す. |
| DONEX |
ML-13B |
改行「CR」かデリミッタ「:」かを判断する.デリミッタの場合,その行の実行を止める. |
| NXT |
ML-13C |
現在の行番号がゼロかどうかを判断する.ゼロならばこの行は直接実行されたことになり,次の入力を促すルーチンへ移る (FIN をコール).ゼロでなければ,次に実行する行番号を求める.NXT の後半では, NXTX が実行されます. |
| NXTX |
ML-13D |
次の実行文のアドレスをスタックに積む. |
| FIN |
ML-13E |
最終終了の処理を行います.最後にアドレス LNECL に移ります. |
| XFER |
ML-14 |
GOTO あるいは GOSUB で,次の実行に実行する行番号に制御を移す. |
| STORE |
ML-20 |
変数のアドレスを求め,そこに値を代入する. |
| コマンド実行ルーチン |
| SIZE |
ML-33 |
使用済みと残りのメモリーの量を表示します. |
| LIST |
ML-34 |
リストの前処理を行います. |
| LIST0 |
ML-34 |
プログラム全体を表示します. |
| LIST1 |
ML-34 |
LIST 20 行番号 20 のみを表示します.このルーチンは,プログラムの特定の行のみを表示します. |
| LIST2 |
ML-34 |
LIST 100,300 行番号 200 — 300 を表示します.このルーチンは,プログラムの指定の範囲を表示します. |
| ある量の有無をテストするルーチン |
| TSTL |
ML-4 |
入力した1行の行番号の有無を調べます.行番号があれば,それを二進数に変換し,桁上げフラグを(CYフラグ)を 1 にセットします.行番号が無い場合は,CY=0 です. |
| TSTV |
ML-7A |
変数(英文字の A—Z)の有無を調べます.変数があれば,CY を 1 にセットし,変数のアドレスを求め,データスタックにプッシュします. |
| IF 文,FOR 文,NEXT 文,DTA 文に関するルーチン |
| CMPR |
ML-23 |
データスタックにある二つの数を比較し,真ならば IF に引き続く文を実行する.偽ならば,次の行次の行の実行の準備をする. |
| FOR |
ML-19 |
FOR に続く文のアドレスを求める. |
| NEXT |
ML-21 |
制御変数をひとつ増やし,最終値を超えたか調べます.超えていない場合には,繰り返しの入口の FOR の実行文の解釈の準備をします.超えた場合には,次の文の実行の準備をします. |
| DATA |
ML-29 |
値を代入する. |
| INPUT 文,PRINT 文に関するルーチン |
| INNUM |
ML-15 |
「?」を出力後に,入力された数値を二進数に変換し,スタックにプッシュする. |
| INSTR |
ML-15 |
「?」を出力後に,入力された文字を@配列に入力する. |
| PRN |
ML-18 |
数値を出力する. |
| PRS |
ML-16 |
文字列を出力する. |
| OUTS |
ML-16 |
文字列配列@の文字列を出力する. |
| SPAC |
ML-17 |
ゾーン(8文字)いっぱいになるまで,スペースを出力する. |
| GOSUB 文と RETURN 文に関するルーチン |
| SAVE |
ML-24 |
戻り番地をアドレススタックにプッシュする. |
| RSTR |
ML-25 |
戻り番地をアドレススタックからポップする. |
| 配列に関するルーチン |
| DIM1 |
ML-26 |
一次元配列に必要なメモリー量を計算し,メモリーを割り付ける.配列名に先頭番地を保管する. |
| DIM2 |
ML-26 |
二次元配列に必要なメモリー量を計算し,メモリーを割り付ける.配列名に先頭番地を保管する. |
中間言語ルーチンの機能
先に示した Tiny BASIC のインタプリタ の構成図中の中間言語ルーチンの機能は,次の通りです.
中間言語ルーチンは,人間に近い形の計算や判断を行う部分です.式の解析,関係演算,配列の処理などを行い,それらの結果を機械語ルーチンに渡して実行を進めます.
| 中間言語のルーチン |
| EXPR1 |
I-1 |
「=」の場合,EXPR をコールする. |
| EXPR |
I-1 |
式の演算を行う.「+」あるいは「-」毎に項(乗算 or 除算で結ばれている式)の計算を行い,最後に合計する. |
| TERM |
I-2 |
数式の項(term)の評価を行うルーチン.まず因子(変数,配列,関数)の評価を行いう.因子との間が「*」であれば乗算,「/」ならば除算を行う. |
| FACT |
I-3 |
因子(factor)は,RND関数かSPACE関数,配列,変数,括弧で囲まれた式のいずれかである.このルーチンでは,それぞれに応じた処理を行う. |
| RELOP |
I-4 |
関係演算子(relational operator)の評価を行うルーチン.演算子に応じて,ルーチンを呼び出す.呼び出すルーチンは,「=」:LIT0,「<」LIT1,「<=」LIT2,「<>」or「><<」LIT3,「>」LIT4,「>=」LIT5 である. |
| ARRAY |
I-5 |
配列名(式)のときは ARRA1 を,配列名(式,式)のときは ARRA2 をコールする. |
| AVTST |
I-6 |
変数/変数の判断を行う.変数であればそのまま戻り,配列であればARRY をコールする.いずれでもない場合はエラー. |
中間言語ルーチンの中の機械語ルーチンの機能
| 算術演算子と論理演算子の処理を行うルーチン |
| NEG |
ML-9 |
データスタックの一番上のデータをポップし,符号を反転(2の補数)し,データスタックにプッシュする. |
| SADD |
ML-10A |
データスタックの二つのデータをポップし,それらを加算する.その結果をデータスタックにプッシュする. |
| SSUB |
ML-10B |
データスタックの二つのデータをポップし,それらを減算する.その結果をデータスタックにプッシュする. |
| MUL |
ML-11 |
データスタックの二つのデータをポップし,それらを乗算する.その結果をデータスタックにプッシュする. |
| DIV |
ML-12 |
データスタックの二つのデータをポップし,それらを除算する.その結果をデータスタックにプッシュする. |
| LIT1—5 |
ML-22 |
各論理演算子 (=, <, <=, <> or ><, >, >=) のアドレスをデータスタックにプッシュする.実際の論理演算は,CMPR ルーチンで行われる. |
| 配列と関数の処理を行うルーチン |
| ARRA1 |
ML-28 |
一次元配列の要素のアドレスを計算する.添え字の値はデータスタックにある. |
| ARRA2 |
ML-28 |
二次元配列の要素のアドレスを計算する.添え字の値はデータスタックにある. |
| RANDM |
ML-31 |
0—32767の間の乱数を計算する. |
| SPACE |
ML-32 |
データスタックの一番上の値に等しい位置まで,空白を置く. |
| 制御の流れを司るルーチン |
| TSTN |
ML-8 |
数字があるかどうか調べる.数字の場合,二進数に変換し,CY=1 とする.数字以外は,CY=0. |
| TSTA |
ML-27 |
英字の後に「(」があれば,配列とみなし,必要な処理を行う.CY=1 にする. |
| TSTF |
ML-30 |
英字が続いて二つあれば,関数名とみなす.必要な処理を行い,CY=1 とする. |
| VALUE |
ML-7B |
変数のアドレスをデータスタックからポップする.そのアドレスの値を求め,データスタックにプッシュする. |
| RETRN |
ML-35 |
サブルーチンを終わり,呼び出し元の次の行へ処理を移す. |
| ERROR |
ML-36 |
エラー番号とエラーが起きた行番号を表示する. |