flexで字句解析器(スキャナー)を作ってみる

最近、プログラム言語の字句解析とか構文解析してコードのメトリクスを計測するようなツールを作りたくていろいろ調べてる。

とりあえず、rubyのsaikuroみたいなのの VB6 版を作ってみようと思ってる。

saikuroではruby-lexというライブラリでrubyのコードの字句解析を行なうことでメトリクスの計測を行なっているっぽいことがわかった。

そこで、flexでVB6の字句解析器を作ってみるのをとりあえずの目標とする。

簡単なflexの使い方

以下のようなflexの定義ファイルをテキストエディタで作成する

%%
[A-Za-z0-9]+   printf("WORD: %s\n", yytext);

これは、入力テキストの中*1[A-Za-z0-9]+という正規表現のパターンに一致するものが現れたら、printf("WORD: %s\n", yytext);というコードを実行しなさいという命令になる。
yytextグローバル変数で、この変数には先の正規表現に一致した実際のテキストの内容が格納される。

このファイルをtest.l という名前でファイルを保存して、以下のようにflexコマンドを実行する。

$ flex text.l

すると、lex.yy.cというCのソースコードが生成される。
これは、以下のように コンパイルできる。

$ gcc lex.yy.c -lfl

-lflオプションは flex のライブラリをリンクするためのもので、自前で main() 関数を提供する場合には必要ない。上記コードには 自前の main()関数を記述していないので-lflが必要となる。

上記のようにコンパイルすると、a.outが生成されるので、これを実行してみる。

test2:snaka $ ./a.out
hoge
WORD: hoge

fuga
WORD: fuga

実行すると、標準入力からの入力待ち状態になるので、適当な単語を入力してみる。すると、上記のようにWORD:(入力した単語というような出力が得られる。

今度はためしに、test.l自身を入力ファイルとして与えて字句解析してみる。

$ ./a.out < test.l
%%
[WORD: A
-WORD: Za
-WORD: z0
-WORD: 9
]+   WORD: printf
("WORD: WORD
: %WORD: s
\WORD: n
", WORD: yytext
);

この結果は自分としては予想外だった。
[-などパターンに一致しないものがそのまま出力されている。
これは、-lflオプションによってリンクされるflexのデフォルトのmain()関数で、パターンにマッチしない入力文字列はそのまま出力するという動作となっているためだ。

このマッチしなかった記号は無視するようにしてみる。最初のコード(test.l)を以下のように変更してみる。

%%
[A-Za-z0-9]+   printf("WORD: %s\n", yytext);
.              /* do nothing */

上記のようにパターンに対する処理の部分を省略すると、そのパターンは無視(処理の対象外となる)されるようになる。

これで、再度test.lの字句解析を行なってみると以下のような結果となる。

$ ./a.out < test.l

WORD: A
WORD: Za
WORD: z0
WORD: 9
WORD: printf
WORD: WORD
WORD: s
WORD: n
WORD: yytext

WORD: do
WORD: nothing

このパターンを記述する順番はとても重要。

最初の、[A-Za-z0-9]+の後に.を記述することで、[A-Za-z0-9]+のパターンに一致しないもの全てを.のパターンで処理することになる。

この順番が逆に、.が1番目、[A-Za-z0-9]+を2番目という風に記述すると、全て1番目のパターン.に一致してしまうため、意図した動作とならない。

これで、簡単な字句解析器ができた。

これを発展させれば、もうすこし複雑な字句解析が行なえるようになる。

(つづく..かも)

*1:デフォルトは標準入力