【スクリプトコンパイラとスクリプトエンジンの分割】

なぜ、分けた方が良いか。


●前回の懸念事項
  毎回、命令の為に文字を読み込むので実行スピードが遅くなる。
  ラベルの解決を毎回し直すので、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を実行する。
  ソースは、単独でコンパイルできるように作っている。