変数への代入
MASMで変数に値を代入するという動作自体は非常に簡単です。しかし、コンパイル時点で変数に値を代入するという分岐をさせるのは非常に難しいです。なぜなら、変数の代入式である「変数=値」の=が問題になるからです。
通常の言語であれば=の前後に半角スペースがあっても問題なく動作します。これを実現するために、以下のような実装を行います。
(省略) バッファは空。 //変数「空」の中身には何も入っていません 予約名は空。 //予約名 予約名スィッチは、オフ。 //現在の位置が予約名の中であるか 文字列中は、いいえ。 //現在の位置が文字列の中であるか 最新命令は、空。 //さっき指定されたばかりの命令 アセンブリは空。 //出力するアセンブリ アセンブリデータは空。 //出力するアセンブリデータ 文字列数は0。 //文字列の数 変更フラグは0。 //変数へ値を代入中? (省略) 違えば、もし、バッファが「As 」ならば、 //Dim、Asだけでなく予約語の前には半角スペース(解説参照) バッファは空。 予約名は「{予約名}{対象}」。 予約名スィッチはオン。 違えば、もし、対象が「=」&&文字列中がいいえならば、 バッファは「{バッファ}{改行}」。 変更フラグは、はい。 違えば、もし、対象が「,」&&文字列中がいいえならば、 バッファは「{バッファ}{改行}」。 (省略) 違えば、もし、対象が改行ならば、 (省略) 違えば、もし、最新命令が「Dim」ならば、 もし、予約名¥1が「Long」ならば、 アセンブリデータに「{予約名¥0} dd 0」を配列追加。 予約名は空。 違えば、もし、変更フラグがはいならば、 変更フラグは、いいえ。 バッファのタブを空に置き換えて、バッファに代入。 バッファ¥0の「 」を空に置き換えて、変数名前に代入。 バッファ¥1の「 」を空に置き換えて、変数名後に代入。 アセンブリに「mov {変数名前},{変数名後}」を配列追加。 違えば、もし、対象が改行でなければ、 バッファは「{バッファ}{対象}」。 (省略)
省略箇所が多くて見にくいとは思いますが、太字をグループに分けて解説していきます。
まず、一番最初の太字は、変更フラグという変数を定義しています。この変数の用途は、2つ目の太字の行で説明されています。文字列でないときに=が検出された場合、このフラグがオンになります。
オンになった際は、改行分岐の中の3つ目のグループで変数名をアセンブリにしています。この場合は、半角スペースとタブを空に置き換えて除去しています。これで=の前後に半角スペースを入力しても対応できるようになりました。
mov命令
ここで、mov命令について復習しておきます。movはmoveの略で、値の代入に使います(moveの直訳は移動ですが、アセンブリではコピーと同じような意味になります)。
mov (代入先),(代入するもの)
例えば、以下の例では、eaxに13を代入します
mov eax, 13
C言語風に書き直すと、
eax=13;
となります。movの復習が終わったところで、本題に戻ります。
dd型(数値型)変数の表示
では、最後のプロセス「変数の表示」をやってみたいと思います。数値型の変数ddにあわせ、以下のスクリプトで変数を表示できるようにしてみましょう。
Dim tester As Long
tester = 3
print "testerの内容は、$tester|です。"
なんか見慣れない内容なのですが、これはこのままでもコンパイルでき、「testerの内容は、$tester|です。」というそのままの文章が表示されるはずです。コンパイルした結果は以下の通りです。
あれ、何も表示されていませんね。試しにprint関数だけ(最後の一行だけ)でコンパイルしてみると、きちんと表示されることが確認できます。ではいったい何が問題なのでしょう?
(省略)
違えば、もし、対象が改行ならば、
バッファを表示。
もし、予約名スィッチがオンならば、
予約名スィッチはオフ。
予約名は「{予約名}{改行}」。
バッファは空。
もし、最新命令が「print」ならば、
アセンブリデータに「STR{文字列数} db "{バッファ}", 0」を配列追加。
アセンブリに「invoke OutputString, offset STR{文字列数}」を配列追加。
文字列数は文字列数+1。
バッファは空。
違えば、もし、最新命令が「msgbox」ならば、
アセンブリデータに「STR{文字列数} db "{バッファ¥0}", 0」を配列追加。
もし、バッファ¥1が空ならば、
アセンブリに「invoke MessageBoxA, 0, offset STR{文字列数}, offset STR{文字列数}, 0」を配列追加。
違えば、
アセンブリデータに「STR{文字列数+1} db "{バッファ¥1}", 0」を配列追加。
アセンブリに「invoke MessageBoxA, 0, offset STR{文字列数}, offset STR{文字列数+1}, 0」を配列追加。
文字列数は文字列数+1。 //文字列を1つ多く使ったので
文字列数は文字列数+1。
バッファは空。
違えば、もし、最新命令が「Dim」ならば、
もし、予約名¥1が「Long」ならば、
アセンブリデータに「{予約名¥0} dd 0」を配列追加。
予約名は空。
バッファは空。
違えば、もし、変更フラグがはいならば、
変更フラグは、いいえ。
バッファのタブを空に置き換えて、バッファに代入。
バッファ¥0の「 」を空に置き換えて、変数名前に代入。
バッファ¥1の「 」を空に置き換えて、変数名後に代入。
アセンブリに「mov {変数名前},{変数名後}」を配列追加。
バッファは空。
違えば、もし、対象が改行でなければ、
バッファは「{バッファ}{対象}」。
もし、予約名スィッチがオンならば、
予約名は「{予約名}{対象}」。
(省略)
実は、バッファを空にするのを忘れていたのです。バッファが空でないと、上にあるもし、バッファが「print」ならば、分岐で、コンパイラが「配列の最後にprintって書いはあるけど、全体がprintじゃないからprint関数ではない」と判断してしまうのです。
そのため改行分岐の時は、いちいちバッファは空をしなければいけなくなります。ただし、バッファは空を改行分岐の最後に回せば、書くのは1回で済みそうです。
(省略)
違えば、もし、対象が改行ならば、
バッファを表示。
もし、予約名スィッチがオンならば、
予約名スィッチはオフ。
予約名は「{予約名}{改行}」。
もし、最新命令が「print」ならば、
アセンブリデータに「STR{文字列数} db "{バッファ}", 0」を配列追加。
アセンブリに「invoke OutputString, offset STR{文字列数}」を配列追加。
文字列数は文字列数+1。
違えば、もし、最新命令が「msgbox」ならば、
アセンブリデータに「STR{文字列数} db "{バッファ¥0}", 0」を配列追加。
もし、バッファ¥1が空ならば、
アセンブリに「invoke MessageBoxA, 0, offset STR{文字列数}, offset STR{文字列数}, 0」を配列追加。
違えば、
アセンブリデータに「STR{文字列数+1} db "{バッファ¥1}", 0」を配列追加。
アセンブリに「invoke MessageBoxA, 0, offset STR{文字列数}, offset STR{文字列数+1}, 0」を配列追加。
文字列数は文字列数+1。 //文字列を1つ多く使ったので
文字列数は文字列数+1。
違えば、もし、最新命令が「Dim」ならば、
もし、予約名¥1が「Long」ならば、
アセンブリデータに「{予約名¥0} dd 0」を配列追加。
予約名は空。
違えば、もし、変更フラグがはいならば、
変更フラグは、いいえ。
バッファのタブを空に置き換えて、バッファに代入。
バッファ¥0の「 」を空に置き換えて、変数名前に代入。
バッファ¥1の「 」を空に置き換えて、変数名後に代入。
アセンブリに「mov {変数名前},{変数名後}」を配列追加。
バッファは空。
(省略)
改行分岐の中の、太字の場所を除くバッファは空については削除しました。これでプログラムもだいぶ見やすくなったと思います。
バグ修正が終わったところで、ここから本題に入りたいと思います。「iolib.dll」から、数値型変数を表示する関数OutputIntegerを呼び出します。呼び出すタイミングにあわせて、分岐を新しく作ります。
(省略) 違えば、もし、バッファが「As 」ならば、 //Dim、Asだけでなく予約語の前には半角スペース(解説参照) バッファは空。 予約名は「{予約名}{対象}」。 予約名スィッチはオン。 違えば、もし、対象が「=」&&文字列中がいいえならば、 バッファは「{バッファ}{改行}」。 変更フラグは、はい。 違えば、もし、対象が「,」&&文字列中がいいえならば、 バッファは「{バッファ}{改行}」。 違えば、もし、対象が「$」&&文字列中がはいならば、 //$が変数名の最初(Perlを参考にしました) 文字列中は、いいえ。 もし、最新命令が「print」ならば、 アセンブリデータに「STR{文字列数} db "{バッファ}", 0」を配列追加。 アセンブリに「invoke OutputString, offset STR{文字列数}」を配列追加。 文字列数は文字列数+1。 バッファは空。 違えば、もし、対象が「|」&&文字列中がいいえならば、 //変数名指定の最後、変数名の最後を表します。 文字列中は、はい。 もし、最新命令が「print」ならば、 アセンブリに「invoke OutputInteger, {バッファ}」を配列追加。 バッファは空。 違えば、もし、対象が「 」&&予約名スィッチがオンならば、 //予約名に半角スペースを入れてはいけないので半角スペースの部分で分岐 予約名スィッチはオフ。 予約名は「{予約名}{改行}」。 バッファは空。 違えば、もし、対象が「"」ならば、 //「"」は一般的な言語では文字列の最初を表す もし、文字列中がはいならば、 文字列中は、いいえ。 違えば、 文字列中は、はい。 違えば、もし、対象が改行ならば、 (省略)
OutputIntegerは、表示するものが文字列ではないので、offsetによりポインタを指定する必要はありません。では、改めてtest4.txtをコンパイルしてみましょう。
testerの内容は、3です。
成功しました。これで変数の実装は一通り終わりました。
