トッププログラミング言語 > 字句解析ツールFlex

字句解析ツールFlex

1.字句解析とは

ソースコードをプログラム上の最小単位に分割することを字句解析(Lexical Analysis)と言う。 最小単位を字句(token)と呼ぶ。例えば、
  while (abc > 10) { abc = abc - 1; }
の場合、"while", "(", "abc", ">", "10", ")", "{", "abc", "=", "abc", "-", "1", ";", "}" に分解される。

例えば、C言語では、トークンの種類は、マクロには、

に分かれる。

字句解析では、予約語か識別子の区別、演算子と記号の区別はしない考え方と 各予約語や演算子、記号に名前をつけて、異なるトークン種別とする考え方がある。

2.Flexによる字句解析

字句解析自体はさほど難しくないため、C言語などで字句解析器(Lexer)を作成することは 比較的に容易である。しかし、特にボトムアップ構文解析法で構文解析器(Parser)を直接作成するのは簡単ではない。

このため、BNFに似た記法から構文解析器を生成するツールとして、 Yacc(Yet Another Compiler Compiler、ヤック)や GNU の Bison がよく知られている。また、 字句解析器のほうも、BNFに似た規則定義を与えれば字句解析器を自動生成してくれる便利なツールとして、 Lex(lexical analyzer)やFlex(fast lexical analyzer generator)などのレキシカルアナライザジェネレータがある。

ここでは、 Flex(first lexical analyzer generator) によるプログラムソースコードの字句解析について述べる。 下に、英数字からなる文字列と記号だけを区別するプログラムを示す。 %%から%%で囲まれた字句規則は正規表現で記述されている。 [A-Za-z0-9]+ は、AからZ、aからz、および0から9が1個以上を表す。 0個以上のときは、"+" を "*" に変える。0個か1個のときは "?" とする。 2行目([\r\n\t ])は空白文字、即ち、'\r', '\n', '\t', ' ' があったときは何もしないことを表す。 3行目はその他の文字、すなわち、この場合は記号文字が現れたときの処理を表す。 先頭文字「.」は全ての1文字にマッチする正規表現である。

[flex01.lex]
%{
int  yywrap(void) { return 1; }
%}
%%
[A-Za-z0-9]+   printf("ID: %s\n", yytext);
[\r\n\t ]
.              printf("SY: %s\n", yytext);
%%
void main() { yylex(); }

flexでC言語プログラムlex.yy.cを生成し、TCCでそれをコンパイルし、flex01.exe を生成する。 その後、flex01.exe を起動し、while (abc > 10) { abc = abc - 1; } をコピペすると、次の結果を得る。

c:\chap05\flex_bison>flex flex01.lex
c:\chap05\flex_bison>tcc lex.yy.c -o flex01.exe
c:\chap05\flex_bison>flex01.exe
while (abc > 10) { abc = abc - 1; }
ID: while
SY: (
ID: abc
SY: >
ID: 10
SY: )
SY: {
ID: abc
SY: =
ID: abc
SY: -
ID: 1
SY: ;
SY: }

演習問題

問題1.先頭は英字、2文字目以降は英数字のものを識別子IDとし、 数値は NM: 10 のように出力するように、プログラムを書き換え、 while (abc > 10) { abc = abc - 1; } をコピペして実行せよ。
解答例
[flex01.lex]
%{
int  yywrap(void) { return 1; }
%}
%%
[\n\t ]
[A-Za-z][A-Za-z0-9]*   printf("ID: %s\n", yytext);
[0-9]+                 printf("NM: %s\n", yytext);
.                      printf("SY: %s\n", yytext);
%%
void main() { yylex(); }
c:\chap05\flex_bison>flex flex02.lex
c:\chap05\flex_bison>tcc lex.yy.c -o flex02.exe
c:\chap05\flex_bison>flex02.exe
while (abc > 10) { abc = abc - 1; }
ID: while
SY: (
ID: abc
SY: >
NM: 10
SY: )
SY: {
ID: abc
SY: =
ID: abc
SY: -
NM: 1
SY: ;
SY: }
問題2.先頭は英字、2文字目以降は英数字のものを識別子IDとし、小数もサポートし、 数値は NM: 12.3 のように出力するように、プログラムを書き換え、 while (abc > 10.5) { abc = abc - 1; } をコピペして実行せよ。
解答例
[flex01.lex]
%{
int  yywrap(void) { return 1; }
%}
%%
[\n\t ]
[A-Za-z][A-Za-z0-9]*   printf("ID: %s\n", yytext);
[0-9]+                 printf("NM: %s\n", yytext);
[0-9]+"."[0-9]+        printf("NM: %s\n", yytext);
.                      printf("SY: %s\n", yytext);
%%
void main() { yylex(); }
c:\chap05\flex_bison>flex flex02.lex
c:\chap05\flex_bison>tcc lex.yy.c -o flex02.exe
c:\chap05\flex_bison>flex02.exe
while (abc > 10.5) { abc = abc - 1; }
ID: while
SY: (
ID: abc
SY: >
NM: 10.5
SY: )
SY: {
ID: abc
SY: =
ID: abc
SY: -
NM: 1
SY: ;
SY: }

3.C言語の字句規則

ここでは、数値の表記を大きくは 10進表記と16進表記とする。 16進表記は 0x12ab、0X12AB のように、"0x" または "0X" で始まり、後続の文字は数字か英字A〜F、a〜fである。 10進表記には 12, 10,23 などのほか 123L、23u、3.14f のように、 サフィックス L,l, U,u, F,f が付くことがあり(12ULのようにサフィックスが2文字のこともある)、 更に 6.12e-14 のような指数表記もある。

数値は、最終的には、int型やdouble型など数値データ型に変換される。 その変換は字句解析フェーズで行ってもいいし、意味解析フェーズで行ってよい。 制作演習では、意味解析フェーズで行う。 この場合、数値表記の細かいチェックは意味解析で行うほうが楽である。

C言語には、'a'、'3', '\n' のような文字定数、"abc", "学校" のような文字列が含まれる。 これらを含めた字句規則のFlexによる正規表現を以下に示す。 ただし、文字列は、エスケープ文字 '\"'に対応していないので、 文字列内に二重引用符 「"」を含んではならない。

[flex03.lex]
%{
int  yywrap(void) { return 1; }
%}
%%
[\n\t ]
"'"."'"                   printf("CH: %s\n", yytext);
"'\\"."'"                 printf("CH: %s\n", yytext);
"\""[^\"]*"\""            printf("ST: %s\n", yytext);
[_A-Za-z][_A-Za-z0-9]*    printf("ID: %s\n", yytext);
"0"[xX][0-9a-fA-F]+       printf("NM: %s\n", yytext);
[0-9.]+[lLuUfF]?          printf("NM: %s\n", yytext);
[0-9.]+[eE][+-][0-9]+     printf("NM: %s\n", yytext);
.	                  printf("SY: %s\n", yytext);
%%
void main() { yylex(); }

実行例を下に示す。

c:\flex_bison>flex03
123.1+12L 6.12e-14-0x34F + 'n' '\n' ("学校") abc
NM: 123.1
SY: +
NM: 12L
NM: 6.12e-14
SY: -
NM: 0x34F
SY: +
CH: 'n'
CH: '\n'
SY: (
ST: "学校"
SY: )
ID: abc

flex03.lex の字句規則は文字列にエスケープ文字や日本語文字が含むとき、正しく動作しないことがある。

実際のコンパイラでは、文法エラーを検出した場合、エラーのある場所をユーザに知らせる必要がある。 即ち、ファイル名や行番号(および行の先頭からの位置)を知らせる。 Flexの場合も追加は可能と思われるが、C言語プログラム部分が大きくなり、 シンプルさが徐々に薄れる。

参考文献

[1] bisonとflexで自作パーサーを作る