【スクリプトコンパイラとスクリプトエンジンの分割】
なぜ、分けた方が良いか。
●前回の懸念事項
毎回、命令の為に文字を読み込むので実行スピードが遅くなる。
ラベルの解決を毎回し直すので、g 命令と i 命令の実行スピードが遅くなる。
●解決方法
起動後、txt ファイルを読み込んだときにラベルと飛び先の対応表を作る。
:start は 0byte目
:next は 10byte目
:end は 20byte目
とかという対応表を作る。
この方法だと、最初にtxtファイルの読み込みのときに遅くなってしまう。
●解決方法の続き
txt ファイルの読み込みが遅くなってしまうを解決するには、
あらかじめ txt ファイルを調査してラベルと飛び先の対応表を作る。
しかし、そうなってくるといっそのこと全ての命令を加工して、
実行するときにプログラムが処理しやすくしておいた方がいいのではないのだろうか?
そこで加工するプログラム(スクリプトコンパイラ)と
実行するプログラム(スクリプトエンジン)の分割をした法が効率がよいということになる。
txt ファイルをスクリプトコンパイラに通す。
そして制作された bin ファイルを中間コードと呼ぶことにする。
●メリット
実行スピードが早くなる
実行部での扱いが楽
他人に配布する際に、binファイルだれを配布すればよく、txtファイルは配布しなくても良くなる
●デメリット
まえもって bin ファイルを作らなくてはならない
txt ファイルに対応する bin ファイルが必要になる
コンパイラを作るのがめんどくさい
●結論
商用ゲームなどではコンパイラとエンジンに分けた方がよいと思われる。
●命令をコンパイルするために必要なもの
いままでは、命令を半角 1 文字にしていたが、命令を全て通し番号にして、1byteにする。
いままでも 1 文字目 = 1 byte なのでそのまま扱えばよいのだが、今後、命令を半角 1 文字ではなく、
たとえばメッセージなら mes、ジャンプなら goto としたときのために通し番号にしておく。
こうしておけば、同じ頭文字の命令もスムーズに制作できる。
そして、どの命令にどれだけパラメーターがつくかを定義する必要がある。
これを厳密に定義しておけば、コンパイラ部分とエンジン部分を別の人が作っても問題はない。
●パラメーター形式の定義
(1) 文字列は最後に0(1byte)が付く。
"メッセージ",0
(2) 即値(数字)とレジスタ(変数)を区別するために、1byteを先頭に付加する。
100 → 0,100(byte単位) 即値(数字)
r100 → 1,100(byte単位) レジスタ番号(変数)
レジスタセットの際の左辺 ( r200=10 の r200 ) などは必ずレジスタ番号になるので、
先頭に1byte付加する必要はない。こういうケースも多々ある
(3) g 命令や i 命令、ラベルなどで使用する飛び先のアドレスは 0 から 255 まで。
(4) i 命令の比較用の文字も、順番に数をつける。
= → 0(byte単位)
> → 1(byte単位)
< → 2(byte単位)
! → 3(byte単位)
>= → 4(byte単位)
<= → 5(byte単位)
(5)演算命令の演算子も、順番に数を付ける。
= → 0(byte単位)
+ → 1(byte単位)
− → 2(byte単位)
/ → 3(byte単位)
* → 4(byte単位)
●各命令
○レジスタセット(let,命令番号0x00)
レジスタ番号 演算子 数字、もしくはレジスタ番号(即値/レジスタ番号)
r20=100
r20=r10
r20+r30
r20-100
r20/5
r20*5
bin上では、命令として認識させるので、命令番号が必要。
◇データー上での並び(全てbyte単位)
r20=100
0x00 calc命令
0x14 レジスタ番号 0x14は十進数で20
0x00 演算命令用の演算子の種類 0x00は'='
0x00 即値と言う印
0x64 即値 0x64は十進数で100
r5=r10
0x00 calc命令
0x05 レジスタ番号
0x00 演算命令用の演算子の種類 0x00は'='
0x01 レジスタと言う印
0x0a レジスタ番号 0x0aは十進数で10
r8*2
0x00 calc命令
0x08 レジスタ番号
0x04 演算命令用の演算子の種類 0x00は'*'
0x00 即値と言う印
0x02 即値
○メッセージ(message,命令番号0x01)
文字と文字の区切りに space/tab を用いるので、メッセージ中に半角の space/tab は使用できない
m メッセージ
◇データー上での並び(全てbyte単位)
m abcdef
0x01 message命令
"abcdef",0
m メッセージ
0x01 message命令
"メッセージ",0
○ジャンプ(goto,命令番号0x02)
g start
◇データー上での並び(全てbyte単位)
:start
g start
0x02 goto命令
0x00 ジャンプ先のアドレス ( 先頭から何byte目の命令に飛ぶか )
○条件分岐(if,命令番号0x03)
i r20=10 start
i r20<10 start
i r20>10 start
i r20<=10 start
i r20>=10 start
◇データー上での並び(全てbyte単位)
:start
i r20=100 start
0x03 if命令
0x14 レジスタ番号 0x14は十進数で20
0x00 比較演算子の種類 0x00は'='
0x00 即値と言う印
0x64 即値 0x64は十進数で100
0x00 ジャンプ先のアドレス ( 先頭から何byte目の命令に飛ぶか )
○終了命令(exit,命令番号0x04)
e
◇データー上での並び(全てbyte単位)
e
0x04 exit命令
○grphロード(load命令番号0x05)
f filename
この命令は、サンプル命令なのでファイルネームを表示するだけです
◇データー上での並び(全てbyte単位)
f c:\cg.bmp
0x05 grph load命令
"c:\cg.bmp",0
○選択肢(select,命令番号0x06)
s r20 選択肢1,選択肢2,選択肢3
◇データー上での並び(全てbyte単位)
s r0 選択肢1,選択肢2,選択肢3
0x06 select命令
0x00 レジスタ番号 0x00は十進数で0
0x03 選択肢の数 ここでは3個
"選択肢1",0
"選択肢2",0
"選択肢3",0
○ラベル(label)
行頭で : がつく場合のみ
:start
:end
○コメント
;コメント
●問題点
コンパイラは命令を頭から1命令ずつコンパイルしていくとする。
:start
m スタート
s r20 終了,続ける
i r20=0 end
g start
:end
e
すると、i 命令の飛び先 end が i 命令より前方にあるため、この時点では飛び先のアドレスが決定できない。
これを前方参照というが、前方というと、ソースの上の方の事をさすのでは?という疑問があると思う。
しかし、コンパイラはプログラムを上から下へ読み込んでいくため、
まだ読み込んでいない下の方がコンパイラにとっては前方となるので、前方参照と言う。
●ラベルの前方参照の解決方法
前方参照ラベルのテーブルを作る事によって解決できる。
i 命令、g 命令の前方参照の際には、ラベル名、binファイルでの場所(=アドレス)を記しておく。
これを参照テーブルと呼ぶ
それと、ラベルがある毎にラベル名とアドレスの対応テーブルも作っておく。
これをラベルテーブルと呼ぶ
コンパイル後、参照テーブルをラベルテーブルと照らし合わせて解決していく。
この方法を、後からパッチを当てるのでバックパッチという。
また、コンパイラや言語によっては何回もソースコードを読み直して解決する物もある。
何回もソースコードを読み直す事を複数PASSと言う
●ラベルの重複
同じ名前のラベルをラベルテーブルに登録した際にはラベルの2重定義といったエラーを出すべきだろう。
それと、ラベルの数が多くなった場合、現在の検索ルーチンだと非常に遅くなる。
それを解決するためにハッシュ関数などを用いる方法があるが、ここではふれない。
●ジャンプ命令などの飛び先の問題
コンパイラ部分が bin ファイルを生成する際、bin が実際に配置されるアドレスと、
エンジン部分が bin ファイルを実行する際に配置されるアドレスは同じではない。
その解決のために、bin ファイルを作るときには、
bin ファイルの先頭からの距離 ( 先頭からの相対アドレス ) で記録しておく。
そして実行部では、bin ファイルを読み込んだ先頭アドレスを足してジャンプする。
●サンプルソース(windowsのコマンドラインプロンプト用)
DOWNLOAD
使用方法は、
>scr01.exe scr01.txt scr01.bin[ret]
でtxtをコンパイルする。
そして、
>scr01a.exe scr01.bin[ret]
でbinを実行する。
ソースは、単独でコンパイルできるように作っている。