平易な文からアセンブラ風に置き換えることで、COBOLで作るチェックインタープリタ用の仮想マシンコード、そして定数や変数の理解を深める。
#COBOLで作るインタープリタ用の仮想マシンコードについて
##まず、元のIF文から。
IF 従業員レコード.住所.郵便番号 = "100-****"
THEN
メッセージ出力 "住所は東京都千代田区"
END-IF
##次に、これをアセンブラ風に置き換えてみる。
01:PUSH 変数.従業員レコード.住所.郵便番号のアドレス
02:PUSH 定数."100-****"へのアドレス
03:EQ
04:JPZ 07
05:PUSH 定数."住所は東京都千代田区"
06:PUT メッセージ出力エリア
07:END
##今度は、元のIF文を抽象化してみる。
IF IN1.6-8 = 定数."100-****"
THEN
メッセージ出力 OUT.1 定数."住所は東京都千代田区"
END-IF
##抽象化したIF文をアセンブラ風に置き換える。
00001:PUSHV 10000600008
00002:PUSHL "100-****"
00003:EQ___
00004:JPZ__ 00007
00005:PUSHL "住所は東京都千代田区"
00006:PUT__ 00001
00007:END__
IN1.6-8
は10000600008
に置き換わっています。この意味は、入力レコード1番の6桁目から8桁を切り出した文字列
です。
これが、今回COBOLで作ろうとしているチェックインタープリタの仮想マシンコードです。このようなコードをテキストファイルに記述して、チェックインタープリタに読み込ませて使います。
#変数と定数
仮想マシンコードがチェックインタープリタに読み込まれると、仮想マシンコードのオペレータとオペランドがインタープリタ内部で管理しているメモリ領域に設定されます。
##チェックインタープリタ内部のメモリ領域に設定された変数と定数
アドレス | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
00001 | 1 | 0 | 0 | 0 | 0 | 6 | 0 | 0 | 0 | 0 |
00011 | 8 | ■ | L | 1 | 0 | 0 | - | * | * | * |
00021 | * | ■ | 住 | 所 | は | 東 | 京 | 都 | 千 | 代 |
"10000600008"
がメモリ領域のアドレス00001番地から11桁設定されています。黒四角は文字列を区切るデリミタです。区切り文字は何でもよいのですが、HIGH-VALUEを使います。
定数"100-****"
は、メモリ領域の00013番地から8桁設定されています。
定数"住所は東京都千代田区"
は、メモリ領域の00023番地から10桁設定されています(漢字の定数なので1byteで済まないのですがイメージとしてとらえてください)。
この00001番地や00013番地、00023番地がインタープリタ内で実行される仮想マシンコードのオペランドに設定され、実行されます。
00001:PUSHV 00001
00002:PUSHL 00013
00003:EQ___
00004:JPZ__ 00007
00005:PUSHL 00023
00006:PUT__ 00001
00007:END__
##変数のメモリ設定で悩んだこと
当初結構悩んだのは、変数をメモリに設定するのはどのタイミングで設定して、どうやって扱うべきなんだろうか?ということです。字句解析と構文解析は難しいのでCOBOLインタープリタの対象外にしました。ということは、仮想マシンコードのテキストファイルを作る段階では、まだ変数のメモリ領域確保はできないこということになります。
次に考えられるタイミングは、仮想マシンコードのテキストファイルをチェックインタープリタに読み込むときです。この時に、変数名をメモリ領域に覚えこませます。これなら行けそうです。
次にその変数が指し示すメモリ領域確保をどうやるかです。チェックインタープリタでは文字列を扱います。その変数の桁数分メモリ領域の確保をするのですが、ここが結構はまりました。
教科書的には変数名が出てきたらメモリ領域を確保するとあるのですが、チェックインタープリタで扱う変数(入力レコード)は、チェックインタープリタの外部から与えられるので、変数用にインタプリタ内にメモリ領域を確保するとメモリを圧迫しますし、外部-内部の文字列移送の処理時間がばかにならないと思いました。
ということで、外部から与えられる入力レコードの開始桁位置への参照をインタープリタ内のメモリ領域で管理することとしました。
では、それをどうやって扱うかですが、その変数名自体が入力レコードの番号と、開始桁位置と切り出すべき桁数を表すようにしてしまえば、その変数名=文字列をメモリ領域に設定してあるので、それを見ればどの入力レコードを見て文字列を切り出せばよいか判断できます。
当初考えた実装は、変数名を管理する配列で実装することです。配列の1要素はHashMapで、キーが変数名、値が開始桁と桁数です。配列の添え字が変数名の変わりになります(アドレスになります)。
チェックインタープリタにプログラム行が読み込まれる都度判断して、その行がPUSHV
なら、オペランドを変数名管理配列に格納して、その戻り値としてこの配列の添え字を実行時アドレスとしてオペランドに設定したらどうかなぁと考えていました。
ただ、HashMapをCOBOLで実装するのが面倒だったのと、変数のIN1.6-8
は10000600008
であり、単なる文字列とみなすことができ、定数と同じ管理の仕方で十分だろうと考えなおしました。つまり、定数同様に変数10000600008
もインタプリタ内部メモリ領域に設定して、その文字列の先頭アドレスをPUSHV
のオペランドに設定すればよいと。
##定数のメモリ設定で悩んだこと
今思えばこうすればよかったと思っているのが、仮想マシンコードのテキストファイル内に、データ定義部を設けてそれを参照するやり方の方が教科書的だしスマートだったなぁと。
平文から仮想マシンコードへのコンパイルの際に、変数(入力レコードの参照を文字列として表現)、定数をコンパクトな形(ダブりなく)で、データ定義部に設定できるからです。
上記の「変数のメモリ設定で悩んだこと」で採用したやり方では、例えば1行目でPUSHV 10000600008
とあり、10行目にもPUSHV 10000600008
が登場すると、10000600008
がメモリ領域に2か所別物として設定されます。本来同じ変数(入力レコードの参照)なのにです。
一方で、上記方式は仮想マシンコードを見たときにやろうとしていることがまだ読みやすいですが、データ定義部の方式だと、そのデータ定義部を参照してからでないとコードが読めないので、コードの理解が面倒です。
可読性を保ちつつコンパクトなメモリ活用が今後の課題です。
00001:PUSHV 00001
00002:PUSHL 00013
00003:EQ___
00004:JPZ__ 00007
00005:PUSHL 00023
00006:PUT__ 00001
00007:END__
00008:DC___ 10000
00009:DC___ 60000
00010:DC___ 8100-
00011:DC___ ****
00012:DC___ 住所は
00013:DC___ 東京都
00014:DC___ 千代田
00015:DC___ 区"