Next: Flex and C (Flex 2.5), Previous: Interfacing to Flex, Up: Interfacing to Flex
Flexに対するCの主要なインターフェイスは、 以下に挙げるルーチンと変数によるものです。 以下の節を読む際には、 いくつかの細かな部分でFlexとLexとの間に相違点があるということを意識しておいてください。 Lexが提供していない関数がいくつかありますし、 宣言の内容が違うものもあります。 こうした相違点は、 通常大きな問題にはなりません。 というのは、 相違のある関数は一般的にはあまり使われていないからです。 相違点に関する詳細については、 Flex and LexおよびFlex and POSIXを参照してください。
yylex()
yylex()
は実際のスキャン処理を行う関数です。
ファイル(デフォルトはstdin
)を読み込み、
パターン・マッチングを行い、
パターンに関連付けされたアクションを実行します。
デフォルトでは、
入力の終端に達するまでマッチングを行い、
終端に達したところでゼロを返します。
(return
を使って、
呼び出し側のプログラムにほかの値を返すことは可能です。
これは、
Flex and Bisonで説明しています。)
したがって、
インターフェイスを提供する簡単な方法の1つは、
オプションのCコード領域の1つに以下のようなコードを追加することです。
#include <stdio.h> int main(argc,argv) int argc; char *argv; { yylex(); }
しかしこのような場合には、
Flexライブラリ(‘-lfl’)もしくはLexライブラリ(‘-ll’)のいずれかをリンクして、
そこからこれと同じようなmain()
関数を取り込むことができます。
この場合は、
スキャナは単にファイルをスキャンして、
ルールに関連付けされたアクションを実行するだけであるという点に注意してください。
yylex()
の使い方としてもう1つよく見られるのが、
マッチされたものが何であるかを示す値を返させることです。
これは、
アクションにreturn
文を追加することで行われます。
return
文を見つけると、
yylex()
は指定された値を返します。
これが、
BisonによるパーサがFlexによるスキャナから情報を獲得する方法です。
ルールの中に、
マッチされたテキストが何を表しているかを示すコードを返すreturn
文があれば、
以下のようなインターフェイスを使うことができます。
#include <stdio.h> int main(argc,argv) int argc; char *argv; { int return_code; while((return_code = yylex()) != 0){ switch(return_code){ case KEYWORD1: /* 何かを行う */ break; case KEYWORD2: /* 何か別のことを行う */ break; ... case KEYWORDn: } } }
yylex
のデフォルトの定義は‘int yylex(void)’ですが、
これはYY_DECL
マクロを使うことによって変更することができます。
例を示すと、
以下のコードはyylex()
の名前をgettok()
に、
型をcharに対するポインタ型に変更し、
パラメータbuffer
を受け取るように指定します。
#undef YY_DECL #define YY_DECL char *gettok(char *buffer)
注:ANSI対応でないCを使っている場合は以下のように定義しなければなりません。
#define YY_DECL char *gettok(buffer) \ char *buffer;
言葉を変えると、 再宣言はターゲットとなるCコンパイラにとって正当な関数宣言でなければなりません。 さらに、 この再宣言は、 ファイルの先頭にあるオプションのCコード領域になければならないという点に注意してください。
yyin
yyin
は、
yylex()
が文字を読み込む元となるファイルです。
デフォルトはstdin
ですが、
fopen()
を使って変更することができます。
yyin
を読み込むデフォルトの方法は、
複数文字から成るブロックを一度に読むというものです。
これは、
YY_INPUT
マクロによって変更できます。
YY_INPUT
マクロは、
ファイルではなく文字列をスキャンするためのスキャナを生成するのに便利です。
YY_INPUT
を定義する方法は以下のとおりです。
YY_INPUT(buffer,result,max_size)
ここで、
buffer
は入力バッファ、
result
は読み込まれた文字数がセットされる変数、
max_size
はbuffer
のサイズです。
以下に、
一度に1文字ずつ読み込むという入力方法に変更する再定義の例を示します。
この方法を使うとかなり遅くなるので、
お勧めはできません。
#undef YY_INPUT #define YY_INPUT(buffer,result,max_size) \ {\ buffer[0] = getchar();\ if(buffer[0] == EOF)\ result = YY_NULL;\ else\ result = 1;\ }
注:この再宣言は、 ファイルの先頭にあるオプションのCコード領域になければなりません。
yyout
yyout
はスキャナがECHO
の出力を書き込むファイルです。
デフォルトはstdout
ですが、
これもfopen()
を呼び出すことで変更できます。
yytext
yytext
は最後にマッチされた文字列、
つまり最後に認識されたトークンを含む大域変数です。
yytext
の正しい外部定義は、
Lexの場合のcharの配列とは異なり、
charに対するポインタ型である点に注意してください。1
つまり、
yytext
は
extern char yytext[];
ではなく、常に
extern char *yytext;
のように宣言されなければならないということです。
このようになっている理由は性能です。
yytext
が配列であると、
スキャナ中でそれを操作するコードは、
コピー処理をたくさん行う必要があります。
これに対してyytext
がポインタである場合には、
このようなことは必要ありません。
通常は、
yytext
は変更すべきではありません。
yytext
の内容が変更される必要がある場合には、
代わりのバッファが使われるべきです。
(examplesサブディレクトリのyymore2.lex
ファイルでは、
yytext
を直接操作する技法が実演されています。
ただし、
このようなやり方はお勧めできません。)
yyleng
yyleng
は、
最後に認識されたトークンの長さを保持する大域変数です。
yywrap()
yywrap
は、
yyin
の終端に達した時に呼び出される関数です。
この関数がTRUE
(ゼロ以外)を返すとスキャナは終了し、
FALSE
(ゼロ)を返すと、
yyin
が次の入力ファイルを指すように設定されたものと仮定して、
スキャン処理が続行されます。
現在のところyywrap()
は、
常に1を返すよう定義されているマクロです。
このため、
再定義するには、
まず最初に#undef
で定義解除しなければなりません。
Lexでは、
yywrap()
は関数です。
Flexも将来のある時点で、
これを関数として定義することになるでしょう。2
yymore()
yymore()
は、
次に認識されるトークンでyytext
の内容を更新するのではなく、
その時点のyytext
の内容の後ろにそのトークンを追加するようFlexに通知する関数です。
したがって、
以下の例に対して‘foobar’という文字の並びを入力として与えると、
stdout
に‘foofoobar’という文字の並びが書き込まれます。
%% foo ECHO; yymore(); bar ECHO;
これは、
まずfoo
ルールによって‘foo’という文字の並びが認識されてECHO
され、
次に‘bar’という文字の並びが認識されてyytext
の内容の後ろに追加された後に、
‘foobar’という文字の並びがECHO
されるからです。
もう少し現実的な例を取り上げましょう。
以下のコードは複数行の文字列を処理するのにyymore()
を使っています。
/* * yymore.lex: yymore()を有効に使う例 */ %{ #include <memory.h> void yyerror(char *message) { printf("Error: %s\n",message); } %} %x STRING %% \" BEGIN(STRING); <STRING>[^\\\n"]* yymore(); <STRING><<EOF>> { yyerror("EOF in string."); BEGIN(INITIAL); } <STRING>\n { yyerror("Unterminated string."); BEGIN(INITIAL); } <STRING>\\\n yymore(); /* 複数行にわたる * 文字列を処理する */ <STRING>\" { yytext[yyleng-1] = '\0'; printf("string = \"%s\"",yytext); BEGIN(INITIAL); } %%
この例では、
エスケープ・シーケンスの変換がまったく行われていないので、
文字列に対してさらに処理が必要である点に注意してください。
この例は、
文字列リテラルの処理において、
エスエープ・シーケンスを処理する、
より役に立つ形式に拡張されます。
yyless(
n)
yyless()
は、
yymore()
とほぼ反対のことを行うものです。
この関数は、
最初のn文字以外のすべてを戻します。
戻された文字の並びは、
次のトークンをマッチするのに使われ、
yyleng
とyytext
には、
この変化を反映した値が設定されます。
引数nにゼロを指定してyyless()
を呼び出すと、
全入力データが戻され、
スキャナは
(BEGIN
、
またはそれに類似のものでデフォルトの動作が変更されない限り)
無限ループに入ります。
例えば、
次のコードに‘foobar’という文字の並びを入力として与えると、
‘foobarbar’という文字の並びが出力されます。
%% foobar ECHO; yyless(3); [a-z]+ ECHO;
これは、
‘foobar’が認識されECHO
された後に、
‘bar’が戻されるからです。
となると、
次にマッチするのは
(‘[a-z]+’というルールでマッチされる)
‘bar’だけで、
これが次にECHO
されることになります。
input()
input()
は、
yyin
から次の文字を取って返す関数です。
これは、
標準的なFlexルール・システムを使ったのではうまく扱えないケースを処理するのによく使われます。
例えば、
ほとんどの言語におけるコメントは、
これを使って処理することができます。
これを使う理由は、
%% "/*".*"*/"
が、 ピリオドが改行以外の任意の文字にマッチしてしまうために 複数行にわたるコメントをうまく処理できず、 また、
%% "/*"[.\n]*"*/"
は、 文字クラスが任意の文字にマッチしてしまうために、 バッファをオーバーフローさせるか、 さもなくばファイルの内容をすべて読み込んでしまうからです。 (実際には、 排他的スタート状態を使うことで、 こうしたことを非常にエレガントな方法で処理することができます。 実例については、 役に立つコードの抜粋を参照してください。 しかし、 POSIXによりサポートされているにもかかわらず、 ここで必要になるいくつかの機能をLexが提供していないために、 この方法には移植性がありません。) Cのコメントは以下のようにして移植性のある方法で処理することができます。
%% "/*" { int a,b; a = input(); while(a != EOF){ b = input(); if(a == '*' && b == '/'){ break; }else{ a = b; } } if(a == EOF){ error_message("EOF in comment"); } }
注:スキャナがC++コンパイラを使ってコンパイルされる場合は、
この関数input
はyyinput
という名前になります。
これは、
input
という名前が同一名のC++ストリームと衝突するからです。
また、
Flexではinput()
はyytext
の内容を破壊しますが、
Lexではyytext
は変更されずそのまま残ります。
これは将来のリリースで修正される予定です。
unput(c)
unput()
は、
文字c
が次にスキャンされる文字になるように、
文字c
を入力ストリームに置く関数です。
例えば、
%% foo unput('b');
は‘foo’を‘b’で置き換えます。
これは、
‘foo’にマッチして‘b’を戻し、
この‘b’が次にスキャンされる文字になるからです。
デフォルトのルールにより、
‘b’はstdout
に書き込まれます。
1つの文字が次にスキャンされる文字になるということには1つ微妙な点があって、 それは、 文字列を入力ストリームに置きたい場合には、 逆順に行わなければならないということです。 以下に例を示します。
foobar { char *baz = "baz"; int i = strlen(baz)-1; while(i >= 0){ unput(baz[i]); i--; } }
これは、 ‘foobar’がマッチされた時に、 入力ストリームに‘baz’を置きます。 以下は、 してはならないことを示す例です。
/* * unput.l : unput()を使って行ってはならない * 処理の例 */ %{ #include <stdio.h> void putback_yytext(void); %} %% foobar putback_yytext(); raboof putback_yytext(); %% void putback_yytext(void) { int i; int l = strlen(yytext); char buffer[YY_BUF_SIZE]; strcpy(buffer,yytext); printf("Got: %s\n",yytext); for(i=0; i<l; i++){ unput(buffer[i]); } }
この例に‘foobar’を入力として与えると、 まず‘foobar’にマッチし、 次に‘raboof’にマッチする無限ループに陥ります。
注:input()
と同様にunput()
もyytext
の内容を破壊します。3つまり、
yytext
から文字情報を返したい場合には、
上の例に示されるように、
まずyytext
の内容をコピーしなければならないことを意味しています。
yyterminate()
yyterminate()
はスキャナの実行を終了させ、
その後にyylex()
が0を返します。
この後は、
yyrestart()
(下記参照)が呼び出されない限り、
yylex()
を呼び出してもすぐに復帰してしまいます。
yyrestart(
file)
yyrestart()
は、
スキャナの実行を再開するようFlexに通知する関数です。
これは引数を1つだけ、
すなわち、
スキャンの対象となるファイル(通常はyyin
)を取ります。
これは、
EOFを処理するために使うこともできますし、
また、
Flexに割り込みをかけ、
その後に再開始させることができるようにするために使うこともできます。
(Flexスキャナは再入可能ではないので、
このようなことが必要になります。)
YY_NEW_FILE
yyin
が新しいファイルを指すよう変更され、
処理が継続されるべきであるということをFlexに通知するマクロです。4
以下に例を示します。
/* * cat.lex: YY_NEW_FILEの実演 */ %{ #include <stdio.h> #define ERRORMESS "Unable to open %s\n" char **names = NULL; int current = 1; %} %% <<EOF>> { current += 1; if(names[current] != NULL){ yyin = fopen(names[current],"r"); if(yyin == NULL){ fprintf(stderr,ERRORMESS, names[current]); yyterminate(); } YY_NEW_FILE; } else { yyterminate(); } } %% int main(int argc, char **argv) { if(argc < 2){ fprintf(stderr,"Usage: cat files....\n"); exit(1); } names = argv; yyin = fopen(names[current],"r"); if(yyin == NULL){ fprintf(stderr,ERRORMESS,names[current]); yyterminate(); } yylex(); }
ECHO
yytext
の内容をyyout
に書き込むマクロです。
REJECT
REJECT
は、
その時点においてマッチしているものを受け入れず、
次に最もよくマッチするものを受け入れるようスキャナに通知するマクロです。
スキャナはマッチするものの中で最長のものを探し、
マッチするものが2つあってその長さが同じ場合は、
記述ファイルにおいて最初に定義されている方を選択します。
つまり、
認識されるテキストの長さは、
同一の長さになることもあり、
または短くなることもあるということを意味しています。
REJECT
を使った後は、
yytext
とyyleng
は新しい値を取ります。
REJECT
に関して知っておくべき重要な点が2つあります。
1つめは、
REJECT
は分岐命令であり、
決して戻ってこないので、
REJECT
の後ろに記述されたアクションは実行されないということです。
2つめは、
REJECT
とファスト・テーブル(fast table/‘-F’)は一緒に使うことはできないということです。
以下に簡単な例を示します。
/* * reject.lex: REJECTとunput()を悪用する実例 */ %% UNIX { unput('U'); unput('N'); unput('G'); unput('\0'); REJECT; } GNU printf("GNU is Not Unix!\n"); %%
この例は、
新式のテキスト代替の技法を示しています。
‘UNIX’にマッチするものが見つかると、
unput()
によって‘GNU’という文字の並びが戻され、
その時点におけるスキャン・バッファの内容が上書きされます。
次にREJECT
により分岐が行われ、
別のものにマッチするようスキャナに対して通知が行われます。
‘GNU’がバッファに書き込まれたので、
これが次にマッチされ、
そのアクションが実行されます。
以下に、
その結果こうなるであろうと思われる例を示します。
UNIX return GNU is Not Unix!
実際のところは、
FlexにおいてREJECT
の用途はほんの少ししかありません。
上記以外では、
重複するパターンや状態の変更に使うことができます。
例を示すと、
以下のようになります。
nday [1-9]|[1-2][0-9]|3[0-1] nmonth [1-9]|1[0-2] nyear [0-9]{1,4} %x DAY MONTH YEAR %% {nday} BEGIN(DAY); REJECT; <DAY>{nday} ... {nmonth} BEGIN(MONTH); REJECT; <MONTH>{nday} ... {nyear} BEGIN(YEAR); REJECT; <YEAR>{nday} ...
この例では、
日付の形式は重複しており、
最初に認識された構成要素によって、
どのように日付をパースするかを決定します。
しかし、
この例は少々不自然な感じがします。
というのは、
少し考えれば、
REJECT
を使わずに、
より効率的なスキャナにすることができるからです。
これは、
スタート状態の使用例に示しています。
BEGIN
BEGIN
は、
スキャナをある特定のスタート状態にするためのマクロです。
BEGIN
に続く名前はスタート状態の名前です。
例えば、
%x FLOAT %% floats BEGIN(FLOAT) <FLOAT>some_rule some_action ...
は、
‘floats’という単語がマッチした時に、
スタート状態をFLOAT
に設定します
(詳細については、
see Start States Explained)。
YY_USER_ACTION
YY_USER_ACTION
は、
ルール・セクション中のどのアクションよりも前に実行されるアクションを定義するマクロです。
これは、
以下の例で示すように、
yytext
の内容の小文字から大文字への変換等を行うのに役に立ちます。
/* * user_act.lex: YY_USER_ACTIONを使う * ユーザ・アクションの例 */ %{ #include <ctype.h> void user_action(void); #define YY_USER_ACTION user_action(); %} %% .* ECHO; \n ECHO; %% /* * このユーザ・アクションはすべての文字を * 単に大文字に変換する */ void user_action(void) { int loop; for(loop=0; loop<yyleng; loop++){ if(islower(yytext[loop])){ yytext[loop] = toupper(yytext[loop]); } } }
これは、
すべての入力文字を単に大文字に変換してECHO
します。
YY_USER_ACTION
のデフォルトの設定では、
何も実行されません。
YY_USER_INIT
YY_USER_INIT
は、
スキャン処理が開始される前に実行されるアクションを定義するマクロです。
基本的には、
main()
関数の中で、
yylex()
を呼び出す文の前に同様のコードを記述するのと同じことです。
以下に簡単な例を示します。
/* * userinit.lex: YY_USER_INITを使う例 */ %{ #define YY_USER_INIT open_input_file() extern FILE *yyin; void open_input_file(void) { char *file_name,buffer[1024]; yyin = NULL; while(yyin == NULL){ printf("Input file: "); file_name = fgets(buffer,1024,stdin); if(file_name){ file_name[strlen(file_name)-1] = '\0'; yyin = fopen(file_name,"r"); if(yyin == NULL){ printf("Unable to open \"%s\"\n", file_name); } } else { printf("stdin\n"); yyin = stdin; break; } } } %} %%
これは、
ファイルがオープンされるかEOFが検出されるまで、
入力ファイル名を入力するようユーザに催促します。
EOFが検出された場合は、
入力元はデフォルトでstdin
になります。
これは以下と同じことです。
/* * この例は、前の例と同じことをYY_USER_INITを * 使わずに行う */ %{ void open_input_file(void) { char *file_name,buffer[1024]; yyin = NULL; while(yyin == NULL){ printf("Input file: "); file_name = fgets(buffer,1024,stdin); if(file_name){ file_name[strlen(file_name)-1] = '\0'; yyin = fopen(file_name,"r"); if(yyin == NULL){ printf("Unable to open \"%s\"\n", file_name); } } else { printf("stdin\n"); yyin = stdin; break; } } } %} %% %% int main(int argc, char *argv[]) { open_input_file(); yylex(); }
YY_BREAK
YY_BREAK
はマクロです。
インターフェイス的な機能というよりも、
むしろ生成されるコードを変更するために使うことができるものです。
スキャナ中において、
すべてのアクションは1つの大きなswitch
文の要素であり、
デフォルトでCのbreak;
文に置き換えられるYY_BREAK
によって区切られます。
ルールのアクション部が多くのreturn
文を含んでいる場合、
コンパイラが‘statement not reached’というエラー・メッセージをたくさん出力するかもしれません。
YY_BREAK
を再定義することによって、
この警告メッセージの出力を止めることが可能です。
再定義は、
セミ・コロンを含む正当なCの文でなければなりません。
注:YY_BREAK
を再定義して空にするのであれば、
アクションの最後は必ずreturn;
かbreak;
になるようにしてください。
[1] 訳注:Flex 2.5では、
‘%pointer’と‘%array’により、
yytext
の型を選択できるようになりました。
‘%pointer’を指定した場合はchar *yytext
、
‘%array’を指定した場合はchar yytext[YYLMAX]
となります。
デフォルトは‘%pointer’です。
‘%array’を指定した場合の配列のサイズは、
YYLMAX
を再定義することによって変更可能です。
[2] 訳注:Flex 2.5では、
‘%option noyywrap’が指定されない限り、
yywrap()
は関数です。
再定義をするのに、
#undef
で定義解除する必要はありません。
[3] 訳注:Flex 2.5では、
‘%array’が指定された場合は、
unput()
はyytext
の内容を破壊しません。
[4] 訳注:Flex 2.5では、
yyin
を変更した後にYY_NEW_FILE
を実行する必要はなくなりました。