次のページ 前のページ 目次へ

3. Lex

Lex プログラムは '字句解析器 (Lexer)' と呼ばれるものを生成します。これ は入力に文字列ストリームをとる関数で、キーにマッチする文字列群を見つけ た時に、ある決まった動作をさせることができます。以下はその簡単な例です。

%{
#include <stdio.h>
%}

%%
stop    printf("Stop command received\n");
start   printf("Start command received\n");
%%

%{ と %} の組で括られる最初のセクションは、出力プログラムでは直接イン クルードされます。これは、stdio.h で定義されている printf が、後で必要 となるためです。

セクションは '%%' で区切られ、二つ目のセクションの第一行は 'stop' キー で始まることになります。入力で 'stop' キーが発現した時は、残り行 ( printf() 呼び出し) が実行されます。

"stop" に加えて、ここでは "start" というほとんど同じ動作をするものも 定義しました。

上記のコードセクションを '%%' で閉じます。

Example 1 をコンパイルするには以下のようにします。

lex example1.l
cc lex.yy.c -o example1 -ll

注意 - lex の代わりに flex を使用している方は、コンパイルスクリプトの'- ll' を '-lfl' に置き換える必要があるかもしれません。RedHat 6.x やSuSE では 'flex' を 'lex' として起動しているかもしれませんが、この変更が必 要です!

以上により、'example1' というファイルが生成されたと思います。実行する と、キーボードからの入力待ちになります。定義済みのキー ( 即ち、'stop' や 'start') 以外のものを入力すると、それがそのまま出力されます。'stop' を入力すると、'Stop command received' が出力されます。

EOF (^D) でプログラムを終了させることができます。

main() 関数も定義されていないのに、どうやってプログラムが動いたのか不 思議に思われたかもしれません。これは、-ll コマンドでコンパイル時にリン クした libl (liblex) が、main() 関数の定義を含んでいたからです。

3.1 正規表現でのマッチ

上記の例は、それ自身ではあまり使えるものではありませんでした。次の例も それほど利用価値のあるものではないのですが、後々重宝することになる、 Lex での正規表現の使い方を例示しています。

Example 2:

%{
#include <stdio.h>
%}

%%
[0123456789]+           printf("NUMBER\n");
[a-zA-Z][a-zA-Z0-9]*    printf("WORD\n");
%%

この Lex ファイルでは WORD と NUMBER という、二種類のマッチ(トークン) を記述しています。正規表現と聞くとびくついてしまう人もいるかもしれませ んが、ちょっと勉強すればすぐに理解できるようになるものです。NUMBER に 対するマッチを見てみましょう。

[0123456789]+

これは、0123456789 のどれか一文字を含む文字、または文字列が存在すると いう意味です。以下のような簡略表記もできます。

[0-9]+

WORD マッチはもう少し複雑になります。

[a-zA-Z][a-zA-Z0-9]*

前半部分は、'a' から 'z' または 'A' から 'Z' の間の文字列、つまりアル ファベットのどれかという意味です。アルファベットの後には、アルファベッ トもしくはアラビア数字がゼロ個以上続きます。アスタリスクを使っているの は何故でしょう? '+' というのは一個以上のマッチを表しますが、WORD は、 前半部分で既にマッチした一文字のみという可能性もあります。その場合には、 後半部分でのマッチがゼロになってしまうので、'*' とする必要があるのです。

このようにして、多くのプログラミング言語が要求するような、最初の文字が アルファベットで 始まらなくては *ならず*、その後はアラビア数字を含んで も良いというような、変数名の規則に似せたものを作ることができました。つ まり、'temperature1' は良いですが、'1temperature' はだめということにな ります。

Example 1 でやったように、Example 2 をコンパイルしてみてください。それか ら以下の例のようにテキストを入力してみてください。

$ ./example2
foo
WORD

bar
WORD

123
NUMBER

bar123
WORD

123bar
NUMBER
WORD

出力のホワイトスペースがどこから来たのか、不思議に思われたかもしれませ ん。理由は簡単です。これらは、もともと入力に含まれていたものですが、マッ チしないためそのまま出力となって現れたというだけの話です。

Flex の man ページには、使われている正規表現について詳しく載っています。 また、perl の正規表現の man ページ (perlre) を便利だと感じられる方々も たくさんいます - もっとも Flex の正規表現の実装は、perl ほど完全ではあ りませんが。

"[0-9]*" のように、長さゼロのマッチは行わないように注意してください。 字句解析器が混乱してしまい、空文字列とのマッチを繰り返すようなことにな ります。

3.2 C のようなシンタックスをもつもう少し高度な例

以下のような設定ファイルを構文解析したいとします。

logging {
        category lame-servers { null; };
        category cname { null; };
};

zone "." {
        type hint;
        file "/etc/bind/db.root";
};

このファイル内にはいくつものカテゴリ(トークン)があるのがわかります。

対応する Lex ファイルは Example 3 のようになります。

%{
#include <stdio.h>
%}

%%
[a-zA-Z][a-zA-Z0-9]*    printf("WORD ");
[a-zA-Z0-9\/.-]+        printf("FILENAME ");
\"                      printf("QUOTE ");
\{                      printf("OBRACE ");
\}                      printf("EBRACE ");
;                       printf("SEMICOLON ");
\n                      printf("\n");
[ \t]+                  /* ホワイトスペースは無視 */;
%%

プログラムに設定ファイルを入力すると、この Lex ファイルから (example3.compile を使って)以下のような出力が得られます。

WORD OBRACE 
WORD FILENAME OBRACE WORD SEMICOLON EBRACE SEMICOLON 
WORD WORD OBRACE WORD SEMICOLON EBRACE SEMICOLON 
EBRACE SEMICOLON 

WORD QUOTE FILENAME QUOTE OBRACE 
WORD WORD SEMICOLON 
WORD QUOTE FILENAME QUOTE SEMICOLON 
EBRACE SEMICOLON 

設定ファイルと見比べると、適切に 'トークン化' されたのがわかります。ファ イルの各々の部分で、正規表現によるマッチがとられ、トークンに変換されて います。

これが、YACC を活用するために必要なことなのです。

3.3 おさらい

Lex は任意の入力から、それぞれの部分が何であるか決定することができる、 ということがわかりました。これを 'トークン化する' といいます。


次のページ 前のページ 目次へ