第11回 自然言語解析学序奏(チャットプログラムを作ろう!)

やねうらおは、チャットプログラムを作っていたことがある。高校に入りたてのころ、初めてPrologに触れ、感動のあまり次の日に、単純な論理応答/学習も行なうチャットプログラムを作ったのだ!

現在ですら、世間のチャットプログラムは非常に低いレベルにあり、それが人工無能だの何だの言われているのが実情である。自慢するわけではないが、僕があのとき作ったレベルのチャットプログラムさえ、世間では類を見ない。この分野が、未成熟であるというよりは、むしろ学校教育のレベルが低いから、誰ひとりとして、この手のプログラムをまともに書けないのではないかと勘ぐってしまう。そもそも大学講義の在り方に問題がある気がする。

言うまでもなく、プログラミングや数学という分野は積み重ねであって、基礎が出来ていないのに一歩たりとも先に進むべきではないし、進んではいけない。逆に、その部分が理解しているのなら、どんどん先に進んで良いし、むしろ好んで進まなければならない。そういう分野である。誰かに支えてもらうことはあっても、最終的に自分が理解しないことには何にもならない。

よって、やねうらお的な、正しいプログラミング教育とは、第一回目の授業で先生は「来週までにSSTの証明検証系を勉強してきなちゃい」と一言だけ言い残し帰って行く。第二回では「それでは前回の証明検証系までのところで何か質問は?んー、、、何もないようでちゅね。それでは、次回までに共謀数による反復深化法について学習してきなちゃい。ごきげんやう」と、毎回、20秒やそこらで帰って行く授業が理想的だと思っている。(もちろん生徒の質問を時間中に取り上げ、それについて徹底的に付き合う姿勢も同時に要求される) そもそも、それぐらいのスピードで授業を進まないことには、大学の4年間ぽっちで、到底、まともなプログラマは養成できない。テキスト一冊を半期で消化するようなカタツムリ的歩行では、決して、頭の良い小学生の足元にも及びはしないだろう。

前置きが長くなったが、まずはチャットであるからには、コンピュータは何らかの形で学習をしなければならない。学習結果とは、もちろんデータとして蓄積させていく部分なのであるが、そのデータがプログラム構造に変革をもたらすようなものでなければ、真に柔軟性があるとは言えない。

具体的には、まず第一に話し手の言語構造を真似る能力(=例からの帰納的学習)を持たねばならないと考えている。そして、第二に、学習した事実の論理的整合性を検証するルーチン(TMS:Truth Maintenance System)が必要である。あと、第三に感情が必要である。それも、怒りとか笑いとか、その手のパラメータ的なものではなくて、目的意識から来るもっと複雑で、体系的なものが欲しい。

TMSのほうは、Prologの上級者向けの教科書か何かを探せば載っているだろうから、ここでは割愛する。感情のインプリメントも、それほど難しくないのでここでは問題にしない。(とか書くと、まじめに心理学だの認知科学だの研究してる連中から袋叩きにされそうだが...)

問題は、「話し手の言語構造を真似る能力」である。言うまでもなく、人間は生まれたときからこの言語獲得能力を持っている。しかし、このような普遍的かつ汎用的な言語獲得機構を実現するのは、実は非常に難しい。これが出来るならば、やねうらおは、全財産を投げ打っても良いと思っている。というのも、例からの学習というのは、学習メカニズムの本質的な部分なのだが、そのことを説明するには、この場所はあまりにも狭すぎる。(とか言って逃げる奴)


第12回 Emulating Kernel(その時やね氏が仮想CPUを採用した理由)

コモンプラットホーム構想では、ターゲットマシンとしてi386ではなく、仮想CPUを採用することにした。i386にしたほうが、他の開発ツールが使えるという利点はあるものの、糞WINでは開発ツールは馬鹿高いので、敷居が高くなることと、その価格がシェアウェアというような形でユーザーの方に跳ね返ってくることを懸念したからである。

また、他のCPUで動作させるには、結局トランスレータが必要であって、変数のレジスタ割付等の最適化のためには、フラットな(出来れば、レジスタなど存在しない、RISC的な)仮想CPUのほうが都合が良い。(というよりは、そうでなければいったん、そのような中間コードに変換する必要があるのでなかろうか)

インタプリトする短所は、遅いことであるが、それは前述のようにゲームのカーネルを記述する部分は多少遅くとも(速度は5倍から10倍程度遅くなる)それほど問題ではない。どうせ、サウンドと描画が9割以上を占めるのだから...。それでまだ遅いとなれば、JIT(Just In Time)でコンパイルすれば良い。そのへんの実装はあとでなんとでもなるのだから...。

ここでは、その仮想CPUコードを実行するカーネルをCで記述することを考えよう。もっとも重要な部分は、命令を拾ってきて、分岐する部分である。ここをどうやって実装すれば良いかを考えていたのだが、フツー、アセンブラならジャンプテーブルを用意すべきなのだが、Cでそれを実装するのは、非常に手間である。

関数ポインタを格納した配列を用意する手もあるが、関数呼び出しというのは、変数のレジスタ割付を阻害するため、莫大なオーバーヘッドを伴うのである。ローカルラベルはジャンプテーブルに利用できないし、その部分だけアセンブラで実装する手もあるが、それではその部分の移植性が落ちるし...と考えていたところ、次のコードが意外にも最適化されることに気付いた。

switch (iIndexCode) {
case 0: xxxxxxx; break;
case 1: xxxxxxx; break;
case 2: xxxxxxx; break;
case 3: xxxxxxx; break;
}

はあ?それってただのswitch〜caseやん?と思われるかも知れないが、VC++5.0で試してみたところ、きちんとジャンプテーブルに変換されていた。間違ってもここにdefault文をかまさないこと。このジャンプテーブル最適化が行なわれるのは、caseの最小値から最大値が一定のrangeに収まっているときのみであって、defaultが存在すると、ジャンプテーブル化できないので、このような最適化は施されない。よくは調べていないが、gccでも同様の最適化が施されると思われるし、この程度の最適化はCコンパイラとして当然備わっているべき範疇だと思うので、このように実装することにする。

どうでも良いが、switch文は、どうして定数しか取れないのか疑問である。まあ、歴史的な背景があるのかも知れないが、最近の言語でも、排他的選択構文が存在しなかったりするので、

alt {
case xxx : xxxxx;
case xxx : xxxxx;
case xxx : xxxxx;
}

ぐらい欲しいかな〜とか思う今日このごろ。みなさん、どうされてますか?お便り待ってます。:p


第13回 Zero Origine(やねうらおが実際に高校生相手に行なった数学講義)

僕が教育実習に高校へ数学を教えに行ったとき、数字というのは、いくらから始まるか?について話した。確かあれは、どうでも良いような雑話だけで固められた授業だったが、生徒たちは、機嫌よく聞いてくれた。何より指導教官も、何も言わずに聞いてくださった。(ちょっと困ったような顔をされていたが) いまさら恥ずかしい気もするが、そのときの授業の一部をここに再現しようと思う。なぜなら、そこに、やねうらお的プログラミングの思想性を顕著に見てとれるからである。対象は、比較的進学校の高校一年生。文化祭を控えており、放課後の練習のことで頭がいっぱいの生徒たちである。数学が苦手な人は、その高校生のように放課後の文化祭の練習のことを頭に思い浮かべ、適当に読み飛ばすと良い。しかし、ここにあるのは、数学の授業なんてものではなく、近未来的プログラミング論である。(つもり)

あらかじめことわっておくが、ここで使われている名前・団体名等はフィクションであり、実在の人物・団体等とは一切関係がない。

「はい。みなさん。今日も、どうでもいいやん的数学の授業の時間がやってきましたね〜。ははは。みなさんは、ここから、何か役に立つことを聞こうだなんて思わないこと。数学が現実的に役に立つのは、日常生活の、ごく限られたシーンに限るからです。

たしか、後藤君のグループが昨日、フェルマーの最終定理について自由研究課題として提出していましたね。あれ、なんであんなに単純な式なのに証明するのにそんなに時間がかかったのかわかりますか?んー?わかりません?わかりません?誰かわかる人...いてますか?いや、私だって、そんな答えみたいなものは用意してないから、思ったこと、感じたこと言ってください。

秋原さん、どうですか?んー。わからない?そう。わからない!それで良いのです。式の単純性は、証明の単純性とは結びつかないのですね。なぜか?は、言いません。私にもよくわかりません。ははは。しかし、あなたたちは、一生かかって、その問題と戦わないといけない。愛とは何か?生きるってどういうことか。そんな単純な命題だって、解くのに一生かかるかも知れないし、一生かかっても解けやしないかも知れない。命題の単純性と証明の単純性とは無縁と言っても過言ではないのですから。

証明というのは、そもそも何なのか?かつて、阪大かどこかの数学科の院試で、(−1)×(−1)=1を証明せよ、とかいう問題が出たことがあったと思います。そんなの当たり前田のクラッカー!とか思いますよね?そんな問題が出たんですよ。こんな問題が出たら、トレビアル(自明の)とか書いて、もう立ち去ってやろうかとか思いますよ。私なんかのように、気の短い人なら、当たり前田のクラッカー!!とか殴り書きして帰りますね。そうなのです。当然の事実なんですもんね?

そんな風に、証明っていうのはいつも、あるレベルの人たちから見れば、いつも当然の事実なの。そのことを忘れないでください。そんな当然の事実を理路整然と説明する。もっと簡単に言えば、高校生のあなたたちが、小学生の人たちに教えるように説明する。もちろん、相手は小学生ですから、中学生で習う数学用語を持ち出しても通用しません。ですよね?小学校で習ったことだけを使って、高校の数学を教える。そこには、証明と同じような流れがあると思いませんか?

つまり、高度な用語や概念を使わないで、もっとワンランク下の言葉や概念で説明しなくっちゃならない。高校生には、高校生の常識があって、高校生にとっては当然じゃん?とかダメじゃん!とか思うことも、小学生にとっては、当然じゃん!でもなければ、ダメじゃん!でもない。

それは、獲得している概念に大きな差があるからですね。言語の記述能力が違うからですよ。もっと簡単に言えば、掛け算を知らない子供は、足し算で表現しなければならない。7×5を、7+7+7+7+7と書かなければならない。100×200なんて、もう死にますね。わはは。これは言語の持つ表現能力の違いなのです。フェルマーの最終定理にしても、それを証明をするために必要な言語能力、すなわち数学的なコトバを人類が獲得するのに、幾年も必要だったということではないでしょうか。

さて、雑談は、このくらいにして教科書のほうに向かいましょう。前回の続きは、25ページの集合の話しですね。集合ってのは、なんかフクザツな記号が出てきますけど、その意味だけ理解すれば十分です。聞いた話ですが、昭和時代の始めは、これ、小学校でやってたんですって。小学4年ぐらいかな。空集合とか、集合演算だとか言って、難しい言葉を学校で習ってきた子供たちが大人にたずねるものだから、父兄の方が、お前ところの小学校はなんて高度なこと教えてるんだ、おれところは百姓だから、足し算と引き算だけでええんだ。なんて怒鳴ってきたとか来ないとか...。

さて、秋永さん、自然数というのは、どんな数ですか?...ん?ゼロ、イチ、ニ、...、マイナスは含まれるの?含まない?ああ、それでは、ゼロ、イチ、ニ、ですか。はい。ありがとうございます。そこ、後ろ、騒がないの。秋永さん、残念だけど、ゼロは自然数に含まれないの。そそそ。残念だけど。でもね。僕は、ゼロを含んだ、秋永さんの気持ち、痛いほどよくわかるんだ。だってゼロから始まったほうが自然!でしょ?みんなそう思わない?

いや、混乱させるといけないから、あまり余計なこと言いたくはないんだけど、最初、1,2,3という数字が作られて、ゼロという数は、歴史的にはずいぶん後になってから発明されたの。マイナスの数はもっとあとね。ものが在る状態。カウントできる状態。それが自然数の自然らしさなのかもね。最初の第一歩と言っても、第0歩とは言わない。決して言わない。きっと、無から有に変化する部分、それが1なのかも知れないね。数字はゼロから始まるのか、それともイチから始まるのか。それが問題だ。な〜んて私はよく悩みます。

先週、コンピュータの時間にも言ったけど、zero origineと言って、0から始まったほうが自然です。ゼッタイ自然です。あーそうか。秋永さんは、あのときの私の言葉を覚えてたんですね。こいつは失礼。私の雑談が、みなさんに、とんだ混乱を招いているようです。自然数は1からです、はい。

ついでに言えば、10進数ってのも不自然です。10でなんで次の桁に繰り上がるの?だいたい時計は60進数じゃん?コンピュータは2進じゃん?時計が60進数なのは何故だか知ってますか?原田君知ってますか?んー?知らない?誰か知ってる人いますか?いてたら昼メシおごりますよ。えー。誰もいないのかー。それとも私の昼メシが食いたくないのか。わはははは。いやいや。時計が60進数を採用してるのは、たぶん、なるべく多くの数でディバイド(割り算)出来る必要があったんでないかと思います。つまり、1,2,3,4,5,6と。その最小公倍数が60でしょ?んで、7を入れると7は素数だから、420と一気に大きくなるんで、そこでヤメたんでないかと思います。

じゃ、コンピュータが2進数を採用してるのは?南さん、なんで?えっ?電気的ON/OFFだから?そそそ。良く知ってるね。南さん、昼メシ一緒に食べようか。わはは。あっそっか。先週のコンピュータの時間でやったのね。ごめんごめん。きみたちを見くびってた。

まあ、60進数も2進数もそれなりの合理性があるわけよ。なのに10進数の奴と来たら、どうも合理的でない。10進数をやめて、16進数を使えとか言った人がいます。九九でなくて、FFとか言って、9×9=81でなく、F×F=E1とかゆって。いや、それはどうでもいいや。

教科書に戻って、見て..ああ。書いてあるじゃない!自然数にゼロとマイナスの仲間を加えたのが整数ね。そこに分数を加えたのが有理数、そこにルート2とか分数にならない無理数と呼ばれるものを加えたのが、実数。あと、解析の時間に出てきたと思いますが、虚数なんてものもありましたね。世のなかには、四元数なんて、さらに親玉もいるのですが、ここでは詳しくは述べないでおきましょう。お楽しみのイチゴは最後に食べるのが良いんであって、先に食べてしまうと楽しみが激減してしまいますもんね」

(中略)

「このように、数という集合が発展してきた過程というのは、演算の整合性を保つためであって、いま説明したように、3割る5なんていう自然数割る自然数が自然数にならない、それならば、新しい集合Zを作って、集合Zの要素X割る、集合Zの要素Yが集合Zに含まれるようにしましょうとか、そういう発想なんですね。つまり、そのことにより、例外処理が減るわけです。自然数しか知らない人だと、3引く5は、うーわからん!となっちゃうところ、整数を知ってる人には、それが−2という数字で表現できる。

これこそ、今日の授業の最初でお話した、言語の記述能力の問題に直接的に絡んでくるわけですが、よりパワフルで記述力の高い言語、数学的道具にするために、あなたがたはいままで数という概念を拡張してきたわけです。あるいは、新たな演算子を定義してきたのです。そのことにより、思考のストロークを拡大しました。高度な概念であるほうが、一歩で遠くまで踏み出せるからです。

たとえば、今日説明した、集合演算の和集合と積集合の公式ですが、「かつ」と「または」を入れ替えてもこの式が成立することに気付いた人、おられますかー?おられませんか?29ページ、(1)の式を「かつ」と「または」を入れ替えると、(3)の式になりますね。逆に、(3)の式でこれをやると(1)の式になる...わかりますか?同様に(2)の式と(4)の式もこのことが言えますね。これは、和と積の定義にduality(双対性)があるからなのですが、この事実を知っている私にとって、(1)と(3),(2)と(4)の式は同じ式に見えています。そうでない、そうでなかったあなたがたには、まったく別の式に見えていたはずです。あー、こんなに公式ばっかり出てきてダルイな〜とかね。違うんですよ。

数学っていうのは、高い場所に上れば上るほど簡単になるんです。覚えることは少なくなる。いままでのものがバカバカしくなる。それは、もちろん、数学の言語能力が高まり、統合され、思考のストロークが伸びるからなのですが、しかし、あなたがたは、まだそこまで上れないし、上るべきでないのです。もっともっと苦しまなければならない。意地悪しているのではないのです。苦しんでこそ見えてくるものもあるでしょうから。地獄からでしか見えないものもあるのですよ。今日は、そのことだけでもよく覚えておいてください」

やねうらおの恥ずかしい40分の授業は以上である。初回からこの連載を読んでいる人は気付いているであろうが、この授業には、OOPのパラダイム、プログラミング言語の記述能力に対する重大な示唆が含まれている...(つもり)←弱気やな!?


第14回 普遍言語獲得装置(第11回の補足事項)

前回が長すぎたので、今回は単刀直入に本題に入る。

普遍言語獲得能力という人間が生まれながらにして持っている性質をコンピュータ上で実現するのは、それほど容易ではない。

オートマトン(意味のわからん人は、駅前のスーパーの名前とでも思って欲しい)の状態をいくつかマージして1つにすると、より大きな言語を受理する新しいオートマトンが出来る。しかし、それは”受理”するだけであって、負のケースを”受理しない”わけではない。(ここが一番重要)

それと、オートマトンという構造そのものが、負の例を学習するにはあまり適していないか、不充分である気がする。まずはオートマトンを拡張する必要がある。あと、実際の教師信号にはノイズが含まれるので確率的同定を行なう必要があるだろう。

そのへんまではなんとかなるのだが、そこから先が容易ではない。どう容易でないのかは説明しない。簡単だぜ〜ベイベ〜とか思う人は、やってみて欲しい(とか言う奴)

それとは別に、構文解析と意味解析、文脈解析といったように文章理解のフェイズが分かれているべきでもないと感じている。それらは、互いに不可分なもので、文脈解析している最中でさえ、構文解析の段階までバックトラックしなければならないこともあるはずだからである。

ついでに、言えば、日本語のシソーラスを作るような作業もしたくない。そういうのを作って、日本語のみを対象とするなら、あらかじめ文法をインプリメントして、ある程度の言語理解を行なったり、story makerのようなプログラムが書くことは可能なのだが、そんなプログラムを書くぐらいなら、バージョンがあがってもがっかりするような変換しかしないMS−IMEとATOKより賢いFEPでも作るほうがよっぽど簡単だし、世のなかの為になる。

余談だが、CMで大々的にやっているMS−IMEの「入れた手のお茶」は論外としても、ATOKの「入れたてのお茶」も正しくは「淹れたてのお茶」だと思うのだが...


第15回 翻訳プログラム(日本の間違った英語教育の事情)

やねうらおが敬愛する大修館の英和辞書『GENIUS』だが、大学のときにちょうどこの編纂者が講師だった。他の授業なんか、出席さえほとんど代筆だけで済ますやねうらおだったが、この授業だけは毎回用意周到なまでに予習していた。

だいたいやねー、代筆して試験だけで単位の取得できる大学のシステムと、教科書読めばわかる程度の授業しかしない(できない)講師にも問題があるわけでやねー、十年一昔と言われるこのご時世に、何十年間も毎年同じ講義と同じ試験をやる改善意欲のない教授や、自分の出した本を教科書にして金儲けすることしか頭にない教授がいたんではあかんと思うんよ。

東大の教養学部なんか、その点、学生側の対策が進んでいてシケタイ(試験対策本部)てなもんまであって、学生は時間の有効活用に励んでいたりする(らしい) それに比べると、やねうらおも、そこまで徹底しきれない甘さがあったのかも知れない。

ともかく。

近年、読み書きは出来てもまったく話すことが出来ない英語教育のありかたが問題視されているが、やねうらおに言わせれば、そもそも翻訳行為自体が何なのかわかっていない人がほとんどではないのか。

「次のセンテンスを日本語にしなさい」と言われても、そんなもんは、文脈に依存する。一行だけ引っ張ってきて直訳することは出来ても、翻訳することは出来ない。あるいは、英語の教師ですら、英語の1つのセンテンスが日本語の1文(句点「。」で終わるまで)に1対1で対応すると考えているのだから、あきれる。というか、中学・高校の英語授業というのは、つねに、1センテンスと1文を対応させて訳すことを心がけていると思う節すらある。もちろん、そういうのは過程的には必要なのだが、最終的に、それで翻訳が終わりではない。

たとえば、つぎの段落を翻訳しなさい、と言われたら、ほとんどの学生は英語の1センテンスと日本語の1文を対応させるように訳出する。それは、もちろん間違いではないけれど、1センテンスと1文は対応させるだとかいう原則はそもそも翻訳にはないはずだ。それでは順番が...などという馬鹿もいるが、語順を並び変えて文を訳出している時点で順番もクソもあったもんではない!文の前後関係が日本語にマッチしていると思えばそのように訳出すべきである。日本語の論理構造に適さないと思えば、センテンスの入れ替えをしたり、段落の入れ替えをしても良い。(いや、当然すべきである!)

いま一度、Cコンパイラの最適化を考えてみていただきたい。関数のインライン展開といって短い関数をその場所に埋め込んだり、ループ内で使用する計算式をループ外に逃がしたりするのは常套的手段ではないか。

それと同じことが英語翻訳に言える。いまだに、産業翻訳のみならず、ほとんどの文学的翻訳すらも1センテンスと1文とを対応させて訳出する。これは歴史的な経緯もあるし、悪しき伝統でもある。

やねうらおは、大学の貴重な英語授業の時間にそんなことを思った。


第16回 エンデアン嘘つかない!(8086君の家庭の事情)

今回は、久しぶりに、エミュレータの話である。

いま、コモンプラットホーム構想のため、仮想CPUのアーキテクチャを考えてみよう。アーキテクチャってのが、何なのかいまいちわかんない人は、こりゃ英和!等の英文和訳ソフトで、architectureを和訳し、「建築」とか出てくるから、「ん?建築?そっかー。やねうらおって、さかんに職業プログラマとちゃうとかゆうてたもんなー。そうかー。建築屋さんだったのかー」とか適当に納得しておくと未来永劫、幸せになれること間違いない。

さて。

インテル8086系では、little-endianと言って、メモリの若いアドレスに下位バイトがくる。12345678Hという数字なら、若いアドレスからは、78H,56H,34H,12Hと逆順に並ぶ。なんだか気持ちが悪い。ちなみにモトローラ68000などはbig-endianであって、この逆である。(ちなみに、このエンディアンというのは、小説「ガリバー旅行記」のなかの話に由来してと思う) 

仮想CPUを考えるとき、JIT(Just In Time)でコンパイルされることを想定してもよいかも知れないが、JITコンパイルで、良質のコードを生成するには限度がある。

だとしたら、一応、インタプリタでそれなりの速度が出ないといけないのだが、そのとき、仮想CPUがlittle-endianを採用しているかbig-endianを採用しているかどうかというのは、大きな問題である。たとえば、little-endianで格納された32ビット整数を、big-endianに変換するのは、相当面倒である。下手すると、これが原因でインタプリタでの実行速度が2,3倍遅くなりうる。

そこで、とりあえずバイナリ段階はlittle-endianで、実行するCPUアーキテクチャに合わせて、(ダイナミック)リンクの段階で、容易にlittle-endianから、big-endianに変換できるようなコード体系にしておければ都合が良い。

ところが、little-endianとbig-endianと言っても、そのデータが何バイトのデータかわからないことには、逆順に変換しようがない。たとえば、12H,34H,56H,78Hとデータが並んでいて、これがlittle-endianな2バイト定数×2なら、big-endianには、34H,12H,78H,56Hと変換しなければならないし、4バイト変数なら、78H,56H,34H,12Hである。

つまりは、命令長の解析を経て、どこに何バイトのデータが配置してあるのかを決定しないことには、変換しようがない。その処理を(ダイナミック)リンクのときに行なうのは、どうもうざったい気がする。

だから、とりあえず命令は、すべて4バイト1命令、データも4バイト1定数ということにしておけば、モトローラ68000君などで実行する際には、リンク時に、何も考えずに4バイトごとに逆順に変換していけば良いので楽ちんである。別に4バイト1命令だからと言って、命令の種類をそれだけ用意する必要はないのだから...


第17回 LALR解析法(あるいは、夜逃げ的プログラミングについて)

ひさびさに、昔の友達の家に遊びに行った。その友達とは、これまた有名な、超一流のプログラマーなのだ。(名前は、本人の希望により伏せる) もし、僕より彼が年輩だったなら、彼のことを相当リスペクトしていたに違いない...。

そのとき、たまたま、やねうらおの作成途中のBMR用のコンパイラが槍玉にあがった。LALR解析なら、BISON使えばいいじゃん?(BISON?ああ、スト2に出てくる背の高いボクサーね!)手書きでパーサー作るんて辛くないか?(パーサー?ああ。スターフォースで、出てくる合体すると自機の速度が1.5倍になりオート連射になるあいつか?でも、あれを取るぐらいなら、手動で連射したほうが速くないか?)どうせまともなLALR解析しようと思ったら、BISONみたくなっちゃうんだろうけど。(なんや?バイソンみたく筋肉むきむきになっちゃうのか..)  ※ ( )内は、やねうらおの心の声

そんなわけで、話があまり噛み合わないので、彼はコンパイラの教科書(洋書)を開いて英語まじりに講義を始めるが、さっぱり理解できない。こんな語学力と才能で高校をブッチして、マジでMIT(マサチューセッツ工科大学)に行こうなどと考えていたのだから、自分でもかなりあきれる。相当の阿呆だったのかも知らない。

そんなわけで、えーい!んなもん、そんな七面倒なことせんでも、Prolog実装してしもたらええんじゃ〜ボケ〜。などと負け犬の遠吠えにも似た咆哮をあげ、逃げ去るように帰ってきたやねうらおであった。

しかし、LALR解析法が理解できないからPrologを実装した人って、他にいないんだろうか...。そりゃ、卵焼きがどうしてもこげてしまうと相談されたから、テフロン加工のフライパンを勧めた親切な友人に対し、フライパンなんかで焼くからこげるんじゃボケ〜と言い放ち、とりあえず俺は宇宙船を打ち上げて、その無重力状態のなかで試してみるぜ!と言っているようなものかも知れない。

ともかく、自分がやってて楽しくないプログラミングならやらないのが僕の主義だし、プログラミング行為とは、完成した作品を作るための過程などではなく、それ自体が目的なのだから...。


第18回 ゲーム移植技術の根底(80862C解答速報)

第B回(いまさら気づいた人もいるだろうが、第何回というカウントは、16進数を採用している)の解答をするとしよう。この答案は、5通ほどメールが来ていたが、残念ながら、どれも満足いくものではなかった。その理由を簡単に説明しよう。

まず、何が問われているかわかってない人。8086のアセンブラで書かれたゲームをいったんC言語に変換し、それをターゲットマシン用にコンパイルすることで移植作業を行なうというコンテクストのなかからの出題なわけだから、機種依存性のあるC言語のコードを出力したのでは問題外。

あと、例外処理の嵐(if文の山ね!)にしてもいいなら簡単じゃんという人もいたが、if文をそれだけ書くというようなコーディングを行なうあたり、ダサすぎる。そもそも、10分でそんなプログラムが書けるはずもない。

最適なものを考えていたらそれだけで10分かかってしまうという指摘もあったが、「8086のコード表をにらめっこしながら」というのであれば、その時点で失格である。8086用にローカライズされたマッチングルーチンでは応用に乏しいし、確かに10分では終わらない。そういう方法は、かなりダサイ。

となれば、コード変換表はあらかじめ用意しておいて、マッチングしながら、そいつにリプレイスして行くぐらいしか方法はないのだが、うまく拡張性のあるフォーマットを定義して、8086固有の問題にも対応できるようにしておかなければならない。

では、ここで、変換表のフォーマットを考えるに際して、どんな問題があるか考えてみよう。まず、アドレスの問題。ラベルやジャンプアドレスをいかに形成するかである。こいつが意外と難しい。

ラベルは、一命令ごとに入れていく仕様が良いだろう。

ADR0510: _ax = _bx; // 2byte命令
ADR0512: _ax++;   // 1byte命令
ADR0513: ...

というようなC言語のソースコードに変換されるようなもので良い。ついでにdsは、ポインタにして、64Kbytesをallocしておけば良いだろう。相対ジャンプは面倒だから、後回しにして、絶対ジャンプだけを考えるとしよう。

ここまでくれば、あとは簡単。データをどういうところに格納して比較するかだが、CWordArrayに1命令を格納するとして、そいつから成るCPtrArray&を用意すると楽だろう。MFCを散々馬鹿にしながら、こんなところに来てCObArrayを使用するのは、動的配列クラスは意外と便利だからである。MFCが便利だというわけではない。あくまで動的配列クラスが...(しつこいなぁ..)

ともかく。データが可変長だとかなれば、こうやって解決するのがスマートである。別に固定配列でもいいが、何かの折りに再コンパイルしなければならないという仕様ではそのうち泣きを見るので、あまりおすすめできない。ついでに、CPtrArrayに&をつけているのは、->を使わず.(ピリオド)でメンバ参照できるようにするためのおまじないである。CByteArrayでなくCWordArrayにしているのは、”逃げ道”の確保のためである。

たとえば、E7のあとにXX(1バイトの定数)が来て、そのXXを拾って、out(XX,_AX);というコードを出力したいというような場合、

E7 -1
out(~1,_AX);

とデータファイルに書くことができれば便利である。このとき、-1というマイナスの数字がメタキャラクタとして働き、そこにあった数値が~1に書き込まれるという仕組みである。CByteArrayでなくCWordArrayにしているのは、この-1を受容するための受け皿である。たとえば、以下のデータなら、

FF -1 -2 -3 -4
_ax = 0x~4~3~2~1h;

「FF 12 34 56 78」というコードに対して、_ax=0x78563412h;というCのソースを出力する。

ここまで書けば、この中核となるマッチング→リプレイスする部分については語る必要はないだろう。(10分あればこのプログラム、書けるような気がしてきたでしょ?)

この技法を巧みに利用してコンパイラのパーサーを書くと、なんとメイン部分が20行ほどで書けてしまう!20行ほどで書けたとき、ちょっとびっくるした。もとい、びっくりした。

あと、ここで説明しきれなかった部分について、問題を残して終わることにする。

1.相対ジャンプをどう解決するか。参考文献としては、プリプロセッサパワーとかゆー、C言語のプリプロセッサのテクがいっぱい載っている本あたりを見ると良いかも知れない。

2.16進数←→10進数変換ルーチンを書くのと、他の言語のDLLを呼び出すのとどちらが楽か?あるいは、そもそも、そんなものは書かずに、データファイル自体を10進数で堂々と書き、アドレスラベルも、10進数化して、ADR_12233:などと変換するような仕様にしてはどうか?

3.本当に10分で作れるか?


戻る