コンパイラの作成
アセンブルできたところで、今度はコンパイラを作成します。
まずはそのままコピー
トークン分割の部分のコンパイラへの移植は、長いわけではないのですが、じっくり理解してほしいので細かく分けて解説します。前回作成した「tokentest.nako」該当部分を、「build.nako」にコピーします。
もし、コマンドライン¥1が空ならば、 「ファイル名を指定してください」と表示。 終わる。 コマンドライン¥1を開く。 それを表示。 バッファは空。 //変数「空」の中身には何も入っていません 文字列中は、いいえ。 //現在の位置が文字列の中であるか 最新命令は、空。 //さっき指定されたばかりの命令 「msgbox "やってみました" print "やってみました"」を文字列分解して、反復 //このようにすれば文字列に変数を埋め込めます もし、バッファが「print」ならば、 最新命令はバッファ。 バッファは空。 //まだ他の場所にprintがあっても //「print(なんとか)print」などとなってしまい //検出できなくなる 違えば、もし、バッファが「msgbox」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、対象が「"」ならば、 //「"」は一般的な言語では文字列の最初を表す もし、文字列中がはいならば、 文字列中は、いいえ。 もし、最新命令が「print」ならば、 バッファを表示。 バッファは空。 違えば、もし、最新命令が「msgbox」ならば、 バッファを言う。 バッファは空。 違えば、 文字列中は、はい。 バッファは空。 //文字列の最初についてしまう「"」対策 違えば、もし、対象が改行でなければ、 バッファは「{バッファ}{対象}」。 //上記以外の場合 //printの後の半角スペースとかは //この行から5行上で対処してしまっています
アセンブリ用の変数
この「build.nako」には、まだアセンブリを出力する部分がありません。出力するアセンブリファイル名を記述します。今回は、コマンドライン¥1に拡張子「.asm」を付与したものにします。
もし、コマンドライン¥1が空ならば、 「ファイル名を指定してください」と表示。 終わる。 コマンドライン¥1を開く。 それを表示。 バッファは空。 文字列中は、いいえ。 最新命令は、空。 「msgbox "やってみました" print "やってみました"」を文字列分解して、反復 //このようにすれば文字列に変数を埋め込めます もし、バッファが「print」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、バッファが「msgbox」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、対象が「"」ならば、 もし、文字列中がはいならば、 文字列中は、いいえ。 もし、最新命令が「print」ならば、 バッファを表示。 バッファは空。 違えば、もし、最新命令が「msgbox」ならば、 バッファを言う。 バッファは空。 違えば、 文字列中は、はい。 バッファは空。 違えば、もし、対象が改行でなければ、 バッファは「{バッファ}{対象}」。 「.586 .model flat, stdcall include iolib.inc includelib iolib.lib include kernel32.inc includelib kernel32.lib .data PrintText db "Hello Word!", 0 .code _start: invoke OutputString, offset PrintText invoke ExitProcess, 0 end _start 」を「{コマンドライン¥1}.asm」に保存。
なお、テキストをファイルに保存するときは
SをFに保存
と記述します。これでテキストSをファイルFに保存します。
しかし出力するアセンブリは、例えば「script.txt」をコンパイルした時、「script.txt.asm」というファイル名で保存されることになります。これでは格好悪いので、一応、仮の拡張子として、「.txt」を使用する事にします。
アセンブリの出力
出力するアセンブリの変数には、データを格納する.data専用のアセンブリデータ、プログラム本体を書く.code専用のアセンブリの2つを用意します。
もし、コマンドライン¥1が空ならば、 「ファイル名を指定してください」と表示。 終わる。 コマンドライン¥1を開く。 それを表示。 バッファは空。 文字列中は、いいえ。 最新命令は、空。 アセンブリは空。 //出力するアセンブリ アセンブリデータは空。 //出力するアセンブリデータ 「msgbox "やってみました" print "やってみました"」を文字列分解して、反復 もし、バッファが「print」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、バッファが「msgbox」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、対象が「"」ならば、 もし、文字列中がはいならば、 文字列中は、いいえ。 もし、最新命令が「print」ならば、 バッファを表示。 バッファは空。 違えば、もし、最新命令が「msgbox」ならば、 バッファを言う。 バッファは空。 違えば、 文字列中は、はい。 バッファは空。 違えば、もし、対象が改行でなければ、 バッファは「{バッファ}{対象}」。 // 「.data」の下、「_start:」の下が変わっています 「.586 .model flat, stdcall include iolib.inc includelib iolib.lib include kernel32.inc includelib kernel32.lib .data {アセンブリデータ} .code _start: {アセンブリ} invoke ExitProcess, 0 end _start 」を「{コマンドライン¥1}.asm」に保存。
.codeの部分で、ExitProcess関数の部分を残しています。これがないとWindows保護例外エラーが発生するからです。また、プログラミングする人がこのコードを出力する部分を忘れてしまうと、実行時にエラーが発生してしまいます。すべての場合において必ず出力するようにしておきましょう。
アセンブリコードへの配列追加
変数にアセンブリを追加するのは、バッファを表示している箇所がよさそうです。また、アセンブリコードに配列を追加するには「配列追加」の命令文を使います。これは、配列型変数Hに文字列Sを新しい配列要素として追加します。例えば、
テスト配列は「あいう えおか きくけ こさし」。 テスト配列に「すせそ」を配列追加。 テスト配列を表示。
とすると、
あいう えおか きくけ こさし すせそ
の、結果が得られます。文字数は違っていても構いません。要するに、変数の最後に改行と文字列を追加すると思ってください。実際にこれを使ったプログラムを見てみましょう。
もし、コマンドライン¥1が空ならば、 「ファイル名を指定してください」と表示。 終わる。 コマンドライン¥1を開く。 それを表示。 バッファは空。 文字列中は、いいえ。 最新命令は、空。 アセンブリは空。 アセンブリデータは空。 文字列数は0。 //文字列の数 コマンドライン¥1を開く。 それを文字列分解して、反復 もし、バッファが「print」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、バッファが「msgbox」ならば、 最新命令はバッファ。 バッファは空。 違えば、もし、対象が「"」ならば、 もし、文字列中がはいならば、 文字列中は、いいえ。 もし、最新命令が「print」ならば、 アセンブリデータに 「STR{文字列数} db "{バッファ}", 0」を配列追加。 アセンブリに 「invoke OutputString, offset STR{文字列数}」を配列追加。 文字列数は文字列数+1。 バッファは空。 違えば、もし、最新命令が「msgbox」ならば、 アセンブリデータに 「STR{文字列数} db "{バッファ}", 0」を配列追加。 アセンブリに 「invoke MessageBoxA, 0, offset STR{文字列数}, offset STR{文字列数}, 0」を配列追加。文字列数は文字列数+1。 バッファは空。 違えば、 文字列中は、はい。 バッファは空。 違えば、もし、対象が改行でなければ、 バッファは「{バッファ}{対象}」。 「.586 .model flat, stdcall include iolib.inc includelib iolib.lib include kernel32.inc includelib kernel32.lib .data {アセンブリデータ} .code _start: {アセンブリ} invoke ExitProcess, 0 end _start 」を「{コマンドライン¥1}.asm」に保存。
なんとなく分かるでしょうか。変数文字列数はなぜ必要かと言うと、1つのプログラム内で複数の文字列を使う時、同じ変数を使いまわすわけにもいきません。ここでは、カウントしながら変数名に連番をつけることで、多くの変数を用意しています。また、開いたファイルの内容をコンパイルするように修正しました。
