字句解析の実際

ゲームに簡単なプログラムを組み込むためにスクリプト言語とそれを実行するインタープリタをC++で作ってしまおうというコーナーです。
前回までで構文解析のことは述べたので、今度は前回・前々回でやらなかった字句解析の手順について述べます。


構文解析と同じじゃない

前々回で字句解析のことを話題に出しておいて、「詳しい過程は省きますが」として説明を避け、前回でも「字句解析が終わった時点で」とかなんとかいって詳しく説明しませんでした。
なんでやらなかったのかというと、同じBNFで書いた構文規則でありながら、やっていることが違うんですね。
だからこそわざわざ2段階に分けたわけで。

何が違うって、何しろ構文解析しやすいようにその前段階として字句解析があるわけですから、逆に言えば構文解析の前にあるのは解析しにくいただの文字列であるわけです。

もしかしたら人間様にとっての見易さのためだけにインデントと称して無駄に空白が続くかもしれませんし、コメントと称してプログラム的に何の動作もしない無意味な文字列が書かれているかもしれません。
例えば構文解析と同じ方法で字句解析を行っていた場合、見易さのためであれ単語の区切りとするためであれ空白を入れてしまえば、「1 + 1」なんかを解析する場合、「N PM N」ではなく「N 空白 PM 空白 N」となってしまい、正しく解析できなくなるわけです。
だから、空白を空読みして途中に空白をはさんだりしても正しい字句解析の結果が出せるようにしなければなりません。
しかしこれは構文解析と同じ方法ではできませんし、無理に試みたとしてもプログラムが複雑化するだけです。

また、字句解析では似たような内容が続くことが非常に多いです。
例えば数値にしても、「299792458」などのように長い数値が出てくることがありますし、現実にスクリプトを作り始めると長い名前の変数も当然出てくるわけです。
それに対して構文解析のときと同じように大きな木を作っていくのはばかばかしいことですし、字句解析の目的は木を作ることではありません。

こんな木作ってどうする

また、数式でなくスクリプトの話のほうになりますが、「.」を文字列連結演算子として使うとするならば「3.14」は3と14を文字列として連結しているのか3.14という一つの小数なのか、という疑問も出てきたりするわけです。
もちろんこれは後者として判断するのが自然ですが、実はこれは構文解析と同じ方法ではどちらかわからないのです。
「文字列連結 ::= 文字列とみなせるもの '.' 文字列とみなせるもの」と「小数 ::= 数 '.' 数」だと構造上同じ木ができてしまうんですから。


方法

で、実際のやり方はというと、例えば数値が出た場合、数値を解釈しつつ数値じゃなくなるまで読み進めていって、記号が出てきたら記号の意味を解釈しつつ記号以外が出てくるまで読み進めていって、といったように、何か文字が出てきたら、その文字の持つ意味は限られてくるので、ある程度意味に見当をつけてから、別の意味の文字が出てくるまで同じ意味を持つデータとして読み込み続けるのです。
この方法は、例えば数値の後に「.」が出てきたら高確率で小数点だから小数として数値データに含めましょうね、ということになるわけです。

もちろん、この方法にも問題はないわけではなくて、例えば先ほどの小数点の例で行くと、「.」のあとに文字列がやってきた場合は小数点じゃなくて文字列連結演算子として解釈するのが自然なわけです。
だから、間違った解釈で読み進めてしまった場合や、そうでなくとも読み進めすぎたときなどは、読みすぎた部分を読まなかったことにする必要があるのです。
また、構文解析のように構文が与えられれば解析方法がほぼ自動で決まるわけでもないので字句の種類が増えれば増えるほどプログラムが複雑化し、手に負えなくなるため、字句の種類はある程度減らさなくてはなりません(だからこそ「+」と「-」を同じ「PM」という字句にしたのです)。