SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

MASMとなでしこによるオリジナルコンパイラの制作

MASMとなでしこによるオリジナルコンパイラの制作 5

変数の宣言、代入、表示と四則演算

インタプリタ言語のバグの怖さ

 さて、プログラミングが終わったところで四則演算をコンパイルしてみます。ところが、コンパイルするとアセンブルエラーが発生してしまいます。

 アセンブリを見てみると足し算しかやっていません。試しに2つの演算の途中にprint関数をはさんでみるとエラーが発生します。どうやらprint関数を実行したことが原因となっているようです。もう1度アセンブリを見てみます。

STR2 db "kekka
 tas1
 tas2", 0

 .dataの中に上のような行がありました。一体なぜ出力されたのか、ちょっと考えてみましょう。これは、コンソールに表示された文字列と同じであり、コンソールには改行分岐の際にバッファを表示させています。そのバッファの内容と一致しているのです。

 ここで注目するのは、「改行分岐の中で、print分岐を演算分岐より先に実行している」ということです。print分岐をする条件は、最新命令という変数の中がprintであるということです。そのため、この行の出力された原因はprintを書いてしまったことにより最新命令が更新され、結果、どの演算をやろうともprint分岐を先にするので常に分岐に引っかかってしまうというわけです。

 解決手段は簡単です。次の1行をつければOKです。

build.nako(改行分岐の最後)
 バッファは空。
 最新命令は空。
違えば、もし、対象が改行でなければ、
 バッファは「{バッファ}{対象}」。
 もし、予約名スィッチがオンならば、
  予約名は「{予約名}{対象}」。

 改行分岐の最後の方にあります。この行を記述しコンパイルすると、演算はうまくいきませんでしたが、コンパイルエラーは発生しませんでした。

 今度は演算がうまくいかなかった原因を考えてましょう。1行ずつデバッグしながら確認していきます。試しに改行分岐の足し算の分岐の中で「あああ」と表示するようにし、コンパイルしてみます。VEFBLプログラムでは、さっききちんとコンパイルできた足し算の行がちゃんと入っていますから、大丈夫です。「あああ」は、先程のスクリプトでは足し算処理を1回していますので、1回表示されるはずです。

 ところが「あああ」とは表示されていませんでした。ここでアセンブリをよく見てみるとmov命令がなくなっていることが分かります。よくよく考えてみると原因は=にあるかもしれません。今度は変数を宣言してから代入するまで演算や表示を全部抜いてしまってコンパイルしてみます。

 これをコンパイルするとわかるのですが、代入がまったくありません。代入文だけなのに代入部分がないのです。原因を「=が正常に認識されていない」と仮定して調査します。当分の間演算部分抜きでコンパイルします。

test5.txt
Dim tas1 As Long
Dim tas2 As Long
Dim kekka As Long

tas1 = 8
tas2 = 4

 これをコンパイルしても、movの欠片たりとも出てこないのが確認できます。

build.nakoデバッグ
(省略)

違えば、もし、対象が「=」&&文字列中がいいえならば、
 バッファは「{バッファ}{改行}」。
 変更フラグは、はい。
 「あああ」と表示。

(省略)

 上記のようにすると、きちんと「あああ」と表示されますので、=の処理は正常にできているという事です(さっき「あああ」と付けた行は削除してください)。

 次に、改行分岐の下に変更フラグと表示を書いてみました。

build.nako Debug
違えば、もし、対象が改行ならば、
 変更フラグと表示。
 バッファを表示。

 これでコンパイルしてみると……きちんと1(はい)と表示されていますね。では、ここから変更フラグがはい分岐までの間に何が起こったのでしょうか。

 試しに、違えば、もし、変更フラグがはいならば、分岐を1つずつ上に移動してみます。すると1回目の移動であっさり「あああ」と表示されてしまいました。

build.nako Debug
 違えば、もし、最新命令が「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。
 //ここから下の順番が変わっています
 違えば、もし、変更フラグがはいならば、
  「あああ」と表示。
  変更フラグは、いいえ。
  バッファの「    」を空に置き換えて、バッファに代入。
  バッファ¥0の「 」を空に置き換えて、変数名前に代入。
  バッファ¥1の「 」を空に置き換えて、変数名後に代入。
  アセンブリに「mov {変数名前},{変数名後}」を配列追加。
 違えば、もし、最新命令が「Dim」ならば、
  もし、予約名¥1が「Long」ならば、
   アセンブリデータに「{予約名¥0} dd 0」を配列追加。
  予約名は空。

 これをコンパイルしたら正常にアセンブルできてしまいました。このまま演算部分を戻して、

test5.txt
Dim tas1 As Long
Dim tas2 As Long
Dim kekka As Long

tas1 = 8
tas2 = 4

kekka = tas1 + tas2
print "$tas1| + $tas2| = $kekka|"

kekka = tas1 - tas2
print " : $tas1| - $tas2| = $kekka|"

kekka = tas1 * tas2
print " : $tas1| * $tas2| = $kekka|"

kekka = tas1 / tas2
print " : $tas1| / $tas2| = $kekka|"

 とすると、代入部分は正常に追加されているのに演算部分がアセンブリに出力されていません。しかし、Dim分岐を改行分岐の最後に移動してみると、正常にコンパイルできてしまいました。以下はプログラムの改行部分の全部です。

build.nako改行部分の全部
違えば、もし、対象が改行ならば、
 変更フラグと表示。
 バッファを表示。
 もし、予約名スィッチがオンならば、
  予約名スィッチはオフ。
  予約名は「{予約名}{改行}」。
 もし、最新命令が「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。
 違えば、もし、変更フラグがはいならば、
  「あああ」と表示。
  変更フラグは、いいえ。
  バッファの「    」を空に置き換えて、バッファに代入。
  バッファ¥0の「 」を空に置き換えて、変数名前に代入。
  バッファ¥1の「 」を空に置き換えて、変数名後に代入。
  アセンブリに「mov {変数名前},{変数名後}」を配列追加。
 違えば、もし、足し算フラグがはいならば、
  足し算フラグは、いいえ。
  バッファの「    」を空に置き換えて、バッファに代入。
  バッファ¥0の「 」を空に置き換えて、代入変数名に代入。
  バッファ¥1の「 」を空に置き換えて、変数名前に代入。
  バッファ¥2の「 」を空に置き換えて、変数名後に代入。
  アセンブリに「mov eax, {変数名前}
push eax
mov edx, {変数名後}
push edx
pop edx
pop eax
add eax, edx
push eax
pop eax
mov {代入変数名}, eax」を配列追加。
 違えば、もし、引き算フラグがはいならば、
  引き算フラグは、いいえ。
  バッファの「    」を空に置き換えて、バッファに代入。
  バッファ¥0の「 」を空に置き換えて、代入変数名に代入。
  バッファ¥1の「 」を空に置き換えて、変数名前に代入。
  バッファ¥2の「 」を空に置き換えて、変数名後に代入。
  アセンブリに「mov eax, {変数名前}
push eax
mov edx, {変数名後}
push edx
pop edx
pop eax
sub eax, edx
push eax
pop eax
mov {代入変数名}, eax」を配列追加。
 違えば、もし、掛け算フラグがはいならば、
  掛け算フラグは、いいえ。
  バッファの「    」を空に置き換えて、バッファに代入。
  バッファ¥0の「 」を空に置き換えて、代入変数名に代入。
  バッファ¥1の「 」を空に置き換えて、変数名前に代入。
  バッファ¥2の「 」を空に置き換えて、変数名後に代入。
  アセンブリに「mov eax, {変数名前}
mov edx, {変数名後}
push eax
push edx
pop edx
pop eax
mul edx
push eax
pop eax
mov {代入変数名}, eax」を配列追加。
 違えば、もし、割り算フラグがはいならば、
  割り算フラグは、いいえ。
  バッファの「    」を空に置き換えて、バッファに代入。
  バッファ¥0の「 」を空に置き換えて、代入変数名に代入。
  バッファ¥1の「 」を空に置き換えて、変数名前に代入。
  バッファ¥2の「 」を空に置き換えて、変数名後に代入。
  アセンブリに「push {変数名前}
push {変数名後}
pop ebx
pop eax
mov edx, 0
div ebx
push eax
pop eax
mov {代入変数名}, eax」を配列追加。
 違えば、もし、最新命令が「Dim」ならば、
  もし、予約名¥1が「Long」ならば、
   アセンブリデータに「{予約名¥0} dd 0」を配列追加。
  予約名は空。
 バッファは空。
 最新命令は空。

 一体なぜでしょう。最新命令をきちんと空にしているのになぜ、しかもDim分岐の内容もきちんとやっている。

 これはよくある事なんです。なでしこのようなインタプリタ言語では、複雑な処理を任せるとたまにこうなってしまうのです。少しずつ付け加えていった計算式が複雑になりすぎでもはや判読不能になってしまったなどとの理由も考えられますが、インタプリタであるという性質も深く関連しているものと思われます。そのため、原因究明はあきらめて、これで通してしまいましょう。

 バグのせいで長くなってしまいましたが、これをコンパイルすると正常に、意図したとおりの結果となります。

回末後記

今回回想

 今回は変数の宣言から、代入、表示までをやりました。それから演算まで実装してしました。

次回予告

 次回は、そろそろ他のWin32APIの実装をやろうと思います。また、時間があれば\nのような特殊記号をprintで出力してみたいと思います。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
MASMとなでしこによるオリジナルコンパイラの制作連載記事一覧

もっと読む

この記事の著者

KMY(かみぃ)

とあるごく平凡な中学生。兵庫県在住。日本語プログラミング言語「なでしこ」に惹かれ、たくさんのプログラムを作り利用している。なでしこ以外にも、他の言語を心得ており、HTMLがなでしこの次に得意であり、Perlが第3位(かも)。ベクターにも多彩な作品を残している。作文がど苦手。KMYpage

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1339 2008/08/26 14:03

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー