lex.lからCのソースを生成する
それでは、lex.lのソースがどう処理されるかについて、streemのMakefileファイルを利用して説明しましょう。
Makefileは、伝統的にCでプログラムを開発する際に作成するファイルで、さまざまなマクロの定義や、ソースファイル間の依存関係と依存先を生成/更新するためのコマンドを記述します。makeコマンド(MicrosoftのSDKではnmake.exe)が、Makefileに記述されているコマンドを実行します。
IDEを利用して開発している方は見たことがないかもしれませんが、Makefileは、Javaのpom.xml(Mavenが処理する)やbuild.xml(Antが処理する)の原型のような存在です。
streemのMakefileは、streemリポジトリのルートディレクトリに置かれています。Makefileからlex.lに関連する箇所を抜き出したものが次のリストです。
LEX = flex
src/lex.yy.c : src/lex.l
$(LEX) -osrc/lex.yy.c src/lex.l
最初の行では、flexというコマンドを以降ではLEXというマクロで参照することを指示しています。flexはlexのフリーな実装です。lexのトリビアとして、Googleの現会長エリック・シュミットが同僚と開発したということが知られています。マクロ化しているのは、環境によってはflexではなくlexやその他の互換コマンドを利用できるようにしているからです。
重要な点は、上記で2行目に引用した箇所です。
2行目は、src/lex.yy.cという拡張子.cのファイル、つまりCのソースファイルが、src/lex.lに依存しているということを定義しています。Makefileでいう依存とは、「:の左辺のファイルが存在しないか、右辺のファイルより更新日付が古ければ、以降の行のコマンドを実行する」という意味です。つまり、lex.lをLEX(最初のマクロ定義からflex)に入力すると、lex.yy.cというCのソースファイルが生成されるということになります。
実際にコマンドラインから、streemのルートディレクトリへ移動して、次のように入力すると、srcディレクトリにlex.yy.cというCのソースファイルが生成されることが確認できます。
flex -osrc/lex.yy.c src/lex.l
なお、Windowsで試す場合には、http://gnuwin32.sourceforge.net/packages/flex.htmからWin32版のflex.exeを入手して、PATHの通ったディレクトリへコピーした後、コマンドプロンプトからstreemのディレクトリへ移動し、次のように入力すればlex.yy.cが生成されます。
flex -osrc¥lex.yy.c src¥lex.l
テキストエディタなどで眺めるとわかりますが、生成されたlex.yy.cは、人間が読むのは相当至難なコードです[4]。
[4] ここは重要な点ですが、このことはflexが生成したソースファイルが読みにくいというよりも(それにしても読みやすいとは言いがたいですが)、Cで効率を重視した字句解析を行うソースファイルそのものが読みにくいと考えるべきです。これは当然、書きにくくデバッグしにくいということでもあります。そこにflex(lex)の存在意義があります。
また、lex.yy.cを読むと、Cのソースファイルとしては不完全で、未定義の変数が利用されている箇所にも気づかれると思います。たとえば、op_barは定義されていません。この理由は、Makefileの最終目的であるTARGET(bin/streem、Windows_NETの場合はbin/streem.exeに展開される)の依存をたどるとわかります。
TARGETの依存関係は次のように記述されています。
$(TARGET) : src/parse.o
右辺にあるsrc/parse.oをたどります。
src/parse.o : src/y.tab.c src/lex.yy.c
$(CC) -g -c src/y.tab.c -o src/parse.o
見ると、y.tab.cとlex.yy.cの2つのCソースファイルに依存しているにもかかわらず、parse.oというオブジェクトファイルを生成するCC(Cコンパイラ)への入力がsrc/y.tab.cだけとなっています。つまり、lex.yy.cは単独でコンパイルされるファイルではないということです[5]。
[5] これはまつもとさんがそのように構成しているということであって、単独でコンパイル可能な.lファイルを記述することも可能です。
なお、後で説明をするparse.yには、
#include "lex.yy.c"
という行が含まれています。つまり、lex.yy.cの元となるlex.lは、最初からparse.yに組み込まれることを前提として記述されているわけです。当然、op_barといった変数はparse.yに定義されています。
では、今度は構文解析に移りましょう。
