| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
この章ではCおよびBisonと一緒にFlexを使う方法を説明します。(6)C、Bisonのそれぞれが非常に多くの細目を含むため、 本章は2つの部分に分割されています。 その両方に、 全般的なインターフェイス概念に関する節と実例を示す節があります。
========================================================================
| 4.1 FlexとC | CとFlexのインターフェイス | |
| 4.2 FlexとC(Flex 2.5の補足情報) | 同上(Flex 2.5の補足情報) | |
| 4.3 FlexとCの簡単な実例 | フィルタにおける文字列代替 | |
| 4.4 FlexとBison | ||
| 4.5 FlexとBisonのもう1つの実例 | ||
| 4.6 FlexとC++(Flex 2.5の補足情報) | C++とFlexのインターフェイス (Flex 2.5の補足情報) |
========================================================================
Flexに対するCの主要なインターフェイスは、 以下に挙げるルーチンと変数によるものです。 以下の節を読む際には、 いくつかの細かな部分でFlexとLexとの間に相違点があるということを意識しておいてください。 Lexが提供していない関数がいくつかありますし、 宣言の内容が違うものもあります。 こうした相違点は、 通常大きな問題にはなりません。 というのは、 相違のある関数は一般的にはあまり使われていないからです。 相違点に関する詳細については、 FlexとLexおよびFlexとPOSIXを参照してください。
関数説明と実例
yylex()yylex()は実際のスキャン処理を行う関数です。
ファイル(デフォルトはstdin)を読み込み、
パターン・マッチングを行い、
パターンに関連付けされたアクションを実行します。
デフォルトでは、
入力の終端に達するまでマッチングを行い、
終端に達したところでゼロを返します。
(returnを使って、
呼び出し側のプログラムにほかの値を返すことは可能です。
これは、
Flexと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コード領域になければならないという点に注意してください。
yyinyyinは、
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コード領域になければなりません。
yyoutyyoutはスキャナがECHOの出力を書き込むファイルです。
デフォルトはstdoutですが、
これもfopen()を呼び出すことで変更できます。
yytextyytextは最後にマッチされた文字列、
つまり最後に認識されたトークンを含む大域変数です。
yytextの正しい外部定義は、
Lexの場合のcharの配列とは異なり、
charに対するポインタ型である点に注意してください。(7)
つまり、
yytextは
extern char yytext[]; |
ではなく、常に
extern char *yytext; |
のように宣言されなければならないということです。
このようになっている理由は性能です。
yytextが配列であると、
スキャナ中でそれを操作するコードは、
コピー処理をたくさん行う必要があります。
これに対してyytextがポインタである場合には、
このようなことは必要ありません。
通常は、
yytextは変更すべきではありません。
yytextの内容が変更される必要がある場合には、
代わりのバッファが使われるべきです。
(`examples'サブディレクトリのyymore2.lexファイルでは、
yytextを直接操作する技法が実演されています。
ただし、
このようなやり方はお勧めできません。)
yylengyylengは、
最後に認識されたトークンの長さを保持する大域変数です。
yywrap()yywrapは、
yyinの終端に達した時に呼び出される関数です。
この関数がTRUE(ゼロ以外)を返すとスキャナは終了し、
FALSE(ゼロ)を返すと、
yyinが次の入力ファイルを指すように設定されたものと仮定して、
スキャン処理が続行されます。
現在のところyywrap()は、
常に1を返すよう定義されているマクロです。
このため、
再定義するには、
まず最初に#undefで定義解除しなければなりません。
Lexでは、
yywrap()は関数です。
Flexも将来のある時点で、
これを関数として定義することになるでしょう。(8)
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の内容を破壊します。(9)つまり、
yytextから文字情報を返したい場合には、
上の例に示されるように、
まずyytextの内容をコピーしなければならないことを意味しています。
yyterminate()アクションの中で呼び出されると、
yyterminate()はスキャナの実行を終了させ、
その後にyylex()が0を返します。
この後は、
yyrestart()(下記参照)が呼び出されない限り、
yylex()を呼び出してもすぐに復帰してしまいます。
yyrestart(file)yyrestart()は、
スキャナの実行を再開するようFlexに通知する関数です。
これは引数を1つだけ、
すなわち、
スキャンの対象となるファイル(通常はyyin)を取ります。
これは、
EOFを処理するために使うこともできますし、
また、
Flexに割り込みをかけ、
その後に再開始させることができるようにするために使うこともできます。
(Flexスキャナは再入可能ではないので、
このようなことが必要になります。)
YY_NEW_FILEyyinが新しいファイルを指すよう変更され、
処理が継続されるべきであるということをFlexに通知するマクロです。(10)
以下に例を示します。
/*
* 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();
}
|
ECHOyytextの内容をyyoutに書き込むマクロです。
REJECTREJECTは、
その時点においてマッチしているものを受け入れず、
次に最もよくマッチするものを受け入れるようスキャナに通知するマクロです。
スキャナはマッチするものの中で最長のものを探し、
マッチするものが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を使わずに、
より効率的なスキャナにすることができるからです。
これは、
スタート状態の使用例に示しています。
BEGINBEGINは、
スキャナをある特定のスタート状態にするためのマクロです。
BEGINに続く名前はスタート状態の名前です。
例えば、
%x FLOAT
%%
floats BEGIN(FLOAT)
<FLOAT>some_rule some_action
…
|
は、
`floats'という単語がマッチした時に、
スタート状態をFLOATに設定します
(詳細については、
see section スタート状態の説明)。
YY_USER_ACTIONYY_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_INITYY_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_BREAKYY_BREAKはマクロです。
インターフェイス的な機能というよりも、
むしろ生成されるコードを変更するために使うことができるものです。
スキャナ中において、
すべてのアクションは1つの大きなswitch文の要素であり、
デフォルトでCのbreak;文に置き換えられるYY_BREAKによって区切られます。
ルールのアクション部が多くのreturn文を含んでいる場合、
コンパイラが`statement not reached'というエラー・メッセージをたくさん出力するかもしれません。
YY_BREAKを再定義することによって、
この警告メッセージの出力を止めることが可能です。
再定義は、
セミ・コロンを含む正当なCの文でなければなりません。
注:YY_BREAKを再定義して空にするのであれば、
アクションの最後は必ずreturn;かbreak;になるようにしてください。
Flex 2.5では、 前節(FlexとC)で説明されていない、 以下のマクロもサポートされています。
yy_set_interactive(is_interactive)カレント・バッファを、
対話的なものと見なすか、
非対話的なものと見なすかを制御します。
引数is_interactiveにゼロ以外の値を渡すと、
カレント・バッファは対話的なものと見なされ、
ゼロを渡すと、
非対話的なものと見なされます。
yy_set_interactive()による指定は、
`%option always-interactive'や`%option never-interactive'による指定に優先します。
このマクロは、
バッファからのスキャン処理が始まるよりも前に呼び出されなければなりません。
yy_set_bol(at_bol)バッファは、 様々なコンテキスト情報を保持しています。 例えば、 行頭を表す`^'を含むルールが適用されるのは、 バッファ内のカレントな位置が実際に行の先頭である場合だけですが、 カレントな位置が行の先頭にあるか否かという情報は、 バッファのコンテキスト情報として保持されています。
マクロyy_set_bol()は、
バッファ内のカレントな位置が行の先頭にあるか否かを表すコンテキスト情報を設定します。
引数にゼロ以外の値を渡すと、
バッファ内のカレントな位置は行の先頭である、
というコンテキスト情報がセットされます。
したがって、
次にトークンのマッチ処理が行われる時には、
行頭を表す`^'を含むルールの適用が試みられます。
逆に、
引数にゼロを渡すと、
バッファ内のカレントな位置は行の先頭ではないことになり、
次にトークンのマッチ処理が行われる時には、
行頭を表す`^'を含むルールの適用が試みられなくなります。
YY_AT_BOL()次にトークンのマッチ処理が行われる時に、 行頭を表す`^'を含むルールの適用が試みられるようなコンテキスト情報がセットされている場合には、 ゼロ以外の値を返します。 それ以外の場合は、 ゼロを返します。
ある単語が現れた時に、 それを別の単語に置き換える必要の生じることがよくあります。 例えば、 ある名前が現れるたびに、 それをある1つの環境変数の値で置き換えてくれるユーティリティを作りたいとしましょう。 そして、 以下のようなことができるように、 そのユーティリティがフィルタとして動作するようにさせたいとします。
nick% myname < infile | more nick% myname < infile > outfile |
以下に、 こうしたことを実現する方法を示すFlexファイルの簡単な例を挙げます。
/*
* myname.lex : トークンの置き換えを行うFlexプログラム
* のサンプル
*/
%%
%NAME { printf("%s",getenv("LOGNAME")); }
%HOST { printf("%s",getenv("HOST")); }
%HOSTTYPE { printf("%s",getenv("HOSTTYPE"));}
%HOME { printf("%s",getenv("HOME")); }
%%
|
このソース・ファイルは`examples'サブディレクトリにあり、 その名前は`myname.lex'です。 これをビルドするには、 `examples'サブディレクトリに移動して`make myname'を実行するか、 以下を実行します。
flex myname.lex cc lex.yy.c -o myname -lfl |
ここで`-lfl'は、
リンカに対してFlexライブラリをリンクするよう通知します。
現在のところ、
Flexライブラリにはデフォルトのmain()関数だけが含まれています。
将来のバージョンのFlexでは、
他の関数も含まれるようになるでしょう。
Flexライブラリがインストールされていない場合は、
この部分は`-ll'でなければなりません。
いずれの場合でも、 最終的には`myname'という名前の実行ファイルが生成されるはずです。 これは、 以下のような変換処理を実行するフィルタです。
%NAME ユーザのログイン名に置き換えられます。
%HOSTユーザのホスト・コンピュータ名に置き換えられます。
%HOSTTYPEユーザのホスト・コンピュータのマシン・タイプに置き換えられます。
%HOMEユーザのホーム・ディレクトリを表すパスに置き換えられます。
したがって、 以下のような内容を持つファイル`myname.txt'を作成して、
Hello, my name is %NAME. Actually "%NAME" isn't my real name, it is the alias I use when I'm on %HOST, which is the %HOSTTYPE I use. My HOME directory is %HOME. |
以下を実行すると、
myname < myname.txt |
以下のテキストに似たものがstdoutへ書き込まれます。
Hello, my name is foobar. Actually "foobar" isn't my real name, it is the alias I use when I'm on baz, which is the cray I use. My HOME directory is /home/foo/foobar. |
このプログラムがうまく動作するのは、
yyinとyyoutがデフォルトではstdin、stdoutにそれぞれ割り当てられ、
かつ、
デフォルトのアクションがyyinの内容をyyoutにコピーするからです。
また、
個々のルールに対応する唯一のアクションが単一行で記述されているため、
`{ }'は必要ではないことに注意してください。
このような場合には、
アクションを`{ }'で囲むか否かは、
個人的な好みの問題になります。
これが、 引用符で囲まれた部分にあるものも含めて、 指定された名前が現れるところすべてにマッチしたことに気がつきましたか? Flexにおいては、 引用符で囲まれた部分にあるものにマッチさせたくない場合には、 それに対応するルールを作成することにより、 そうしないよう明示的にFlexに通知しなければなりません。 以下に例を示します。
/*
* myname2.lex : トークンの置き換えを行うFlexプログラムの例
*/
%{
#include <stdio.h>
%}
%x STRING
%%
\" ECHO; BEGIN(STRING);
<STRING>[^\"\n]* ECHO;
<STRING>\" ECHO; BEGIN(INITIAL);
%NAME { printf("%s",getenv("LOGNAME")); }
%HOST { printf("%s",getenv("HOST")); }
%HOSTTYPE { printf("%s",getenv("HOSTTYPE"));}
%HOME { printf("%s",getenv("HOME")); }
|
この例では、 排他的スタート状態を使って、 文字列中のテキストが変更されることのないようにしています。 この例も`examples'サブディレクトリにあるもので、 その名前は`myname2.lex'です。
Bisonは、 Flexと同様、 ある記述情報を受け取って、 それをもとにCのコードを生成するプログラムです。 両者の違いは、 BisonがCやPascalのような言語の文法に関する記述情報を入力として受け取り、 その記述情報からパーサを生成する点にあります。 FlexとBisonを結合することにより、 言語の字句解析と構文解析の両方を処理することができるようになります。 (これは、 コンパイラ・デザインにおいて最も容易に自動化できる部分です。)
生成されるパーサが機能するためには、
Bisonはyylex()という関数を必要とします。
この関数はユーザによって提供され、
呼び出された時に、
パースされている言語のある要素を表す整数値をBisonに返します。
Flexにおいてスキャン処理を行うルーチンはyylex()であり、
デフォルトでは整数値を返します。
これにより、
FlexとBisonを一緒に使うのは非常に簡単になります。
警告: 以下の節では、 読者がBisonの基本的なパーサの宣言を理解しているものと仮定します。 Bisonを使った経験のない人には、 パーサの定義は混乱をもたらす可能性がありますので、 先に進む前に是非Bisonのマニュアルを読んでください。 Bisonに興味の無い人は、 この節全体を飛ばしても構いません。
========================================================================
| 4.4.1 FlexとBisonのインターフェイス | 一般的な方法 | |
| 4.4.2 YYSTYPEとyylval | 単なる整数以上の情報の渡し方 |
========================================================================
FlexとBisonの間で情報を渡す基本的な方法は、
関数`yylex()'を使うことです。
これは、
Flexにより生成されるスキャナにおいて、
スキャン処理を実行する関数の名前です。
Flexの入力ファイルのアクション部分において`return'文を使うことによって、
単なる0や1以外の値を返すことができます。
この方法で、
yylex()は最後に認識されたトークンを表す整数値を返すことができます。
Bisonを`-d'オプション付きで使うと、 Bisonは`.tab.h'という拡張子を持つファイルを生成します。 このファイルには、 記述情報中にある正当なトークンの1つ1つに対する一意な定義情報が含まれます。 この出力情報は、 特にスキャナによって使用されることを想定して設計されています。 このファイルをFlexにより生成されたスキャナに含めることで、 2つのプログラムの間に非常に明確なインターフェイスを作ることができます。 例として、 以下にBisonのファイルを示します。 このファイルの名前を`expr.y'としましょう。
/*
* expr.y : Bisonマニュアル中の例に基づく
* Bisonによる簡単な表現式パーサ
*/
%{
#include <stdio.h>
#include <math.h>
%}
%union {
float val;
}
%token NUMBER
%token PLUS MINUS MULT DIV EXPON
%token EOL
%token LB RB
%left MINUS PLUS
%left MULT DIV
%right EXPON
%type <val> exp NUMBER
%%
input :
| input line
;
line : EOL
| exp EOL { printf("%g\n",$1);}
exp : NUMBER { $$ = $1; }
| exp PLUS exp { $$ = $1 + $3; }
| exp MINUS exp { $$ = $1 - $3; }
| exp MULT exp { $$ = $1 * $3; }
| exp DIV exp { $$ = $1 / $3; }
| MINUS exp %prec MINUS { $$ = -$2; }
| exp EXPON exp { $$ = pow($1,$3);}
| LB exp RB { $$ = $2; }
;
%%
void yyerror(char *s)
{
printf("%s\n",s);
}
int main()
{
yyparse();
}
|
これは非常に簡単な計算機の文法定義です。
`-y -d'オプション付きで呼び出されると、 Bisonは`y.tab.h'というファイルを生成します。 このファイルには以下のような定義か、 それにきわめてよく似た定義が含まれます。
typedef union {
float val;
} YYSTYPE;
extern YYSTYPE yylval;
#define NUMBER 258
#define PLUS 259
#define MINUS 260
#define MULT 261
#define DIV 262
#define EXPON 263
#define EOL 264
#define LB 265
#define RB 266
|
Flexがトークンの値を正しくBisonに返すことができるように、
(#includeを使って)
これをスキャナに含めることができます。
そのコードは以下のようなものになります。
/*
* expr.lex : 簡単な表現式パーサのためのスキャナ
*/
%{
#include "y.tab.h"
%}
%%
[0-9]+ { yylval.val = atof(yytext);
return(NUMBER);
}
[0-9]+\.[0-9]+ {
sscanf(yytext,"%f",&yylval.val);
return(NUMBER);
}
"+" return(PLUS);
"-" return(MINUS);
"*" return(MULT);
"/" return(DIV);
"^" return(EXPON);
"(" return(LB);
")" return(RB);
\n return(EOL);
. { yyerror("Illegal character");
return(EOL);
}
%%
|
上記のファイルは、 以下のようにしてコンパイルすることができます。
bison -d -y expr.y flex -I expr.lex cc -o expr y.tab.c lex.yy.c alloca.c |
また、 この例のソースが手元にあれば、 `examples'サブディレクトリにおいて`make expr'を実行するだけでコンパイルできます。 どちらの方法でも、 `expr'という名前の簡単な計算機が生成されます。 これは以下のような表現式をパースして、 その結果を出力します。
1 + 2 * (199*2) |
これを見てお分かりのように、 この種のインターフェイスは非常に柔軟であり、 かつ、 保守も非常に容易です。 (トークンを定義する名前が変わらない限り) BisonとFlexの間のインターフェイスを変更することなく、 Flex、 Bisonいずれの入力情報においても、 機能の追加や削除、 定義やコードの変更を行うことが可能です。
この例では、
FlexとBisonの間で情報を渡すための別の方法を導入していることに注意してください。
この例では、
数字の値をBisonに返すのにyylvalを使っています。
これについては次の節でより詳細に説明します。
ここではとりあえず、
return文の使い方を学んでおいてください。
注:これは単純な例です。 表現式のパース処理についてより詳しく知りたい人は、 Bisonのマニュアルを参照してください。
FlexからBisonに対して、 単なる整数値以上の情報を渡す必要の生じることがよくあります。 例えば、 コンパイラにおいては、 どのような種類のトークンが認識されたかだけではなく、 そのトークンの値についても知る必要のある場合がときどきあります。 文字列、文字、および数値定数などが良い例です。 ここで問題なのは、 どのようにしてFlexにこうした情報を返させるかです。
その答は、
Bisonが持っている%union文です。
これは、
YYSTYPEという型を定義するものです。
YYSTYPEは、
パーサ定義中において使われるすべての正当なデータ型の共用体(union)です。
Bisonがカレントなパース状態に関連づけたデータを保存するために使う、
YYSTYPE型の変数yylvalというものがあり、
Flexからyylvalに値を設定することができるので、
トークンの型だけでなく、
それ以上の情報をBisonに返すことができます。
Bisonにおいて%unionを宣言して`-d'オプションを使うと、
Bisonは`.tab.h'という拡張子を持つファイルを作成して、
そこにトークンの定義情報だけでなく、
YYSTYPEとyylvalの宣言も含めます。
したがって、
yylvalにアクセスするためにしなければならないことは、
Flexの定義情報の中にこの`.tab.h'ファイルをインクルードすることだけです。
これは、
追加のCコード・セクションにおける定義の先頭でインクルードしなければなりません
(see section FlexとBisonのインターフェイス)。
注:初期のバージョンのBisonは、
自動的にYYSTYPEとyylvalの宣言を生成しません。
この場合には、
より新しいバージョンのBisonを入手するか、
もしくは、
Flexの定義ファイルの先頭においてYYSTYPEとyylvalを宣言する必要があります。
コードを読むのは、 プログラミングの方法を学ぶ良い方法です。 そこで、 Flex、Bisonのインターフェイス例をもう1つ示すことにします。 下の例では、 拡張してデータベースを操作するために使うことができるような、 小規模な言語のための簡単なパーサを作ります。
========================================================================
| 4.5.1 インターフェイス言語 | ||
| 4.5.2 実装:コマンド文パーサ | ||
| 4.5.3 実装に関する注 |
========================================================================
データベースとのインターフェイス言語は、 英語の非常に小さなサブセットになります。 文法はおおよそ以下のようになります。
command_list ::= sentence {sentence …}
sentence ::= verb_phrase noun_phrase position_phrase
adverb period
verb_phrase ::= VERB | adverb VERB
noun_phrase ::= declared_noun | qualified_noun | noun
declared_noun ::= declarator NOUN
declarator ::= THIS | THAT | THE | THOSE
qualified_noun ::= qualifier NOUN
qualifier ::= SOME | MANY | ALL { declarator } NOUN
position_phrase ::= position declarator NOUN | empty
position ::= IN | ON | AT
adverb ::= ADVERB | empty
|
結果として作成されるプログラムは、 以下のような文章を受け付けます。
FIND MEN QUICKLY FIND MEN FIND ALL MEN ON THE NETWORK QUICKLY FIND ALL MEN ON THE NETWORK FIND ALL MEN ON THE NETWORK QUICKLY |
この例では、 BisonとFlexの間のインターフェイスが明確に示されるよう、 文章の簡単な解析結果が表示されます。 このプログラムを試しに実行してみると、 その表示結果は大体以下のようになります。
% front FIND MEN I understand that sentence. VP = FIND NP = MEN PP = AD = QUICKLY FIND ALL THE MEN ON THE NETWORK I understand that sentence. VP = QUICKLY FIND NP = ALL THE MEN PP = ON THE NETWORK AD = ^C % |
これは特別便利なものではありません。
というのは、
これは文章の構成要素を表示する以外に何も行わないからです。
しかし、
そこには拡張のためのフックもありますし、
一般的な技法も示されています。
より一般的な形式の文章を受け付けるよう、
この例を拡張してみてください。
ほとんどの場合、
文章は動詞句(VERB)と名詞句(NOUN)に分割することができますが、
所有格名詞、
名詞の後ろに名詞が続く場合など、
文章を構成する他の要素も許容されるようにする必要があります。
(`FIND ALL JONE'S CAT NAMES'のような文章をどうやってパースするかを想像してみてください。)
Bisonの文法やその使い方に関する詳しい説明については、
Bisonのマニュアルを参照してください。
上の節で、 小規模な言語について説明しました。 次にそれを実装してみることにしましょう。 以下のファイルがこれを実現します。
注:これはあくまでも1つの例として見てください。 特に文法の部分は、 英語のパース処理としてはあまり良い例ではありません。
以下はBisonのファイルです。
%unionの部分、
および、
yylvalにアクセスするために$$と$nを使う方法に注目してください。
/* Cコードはファイルの先頭で提供する */
%{
#include <stdio.h>
#include <string.h>
extern int yylexlinenum; /* lex.yy.cに存在する */
extern char *yytext; /* カレント・トークン */
%}
/* キーワードと予約語がここから始まる */
%union{ /* これはデータの共用体 */
char name[128]; /* 名前 */
}
/*------------- 予約語 ------------------*/
%token PERIOD
%token NEWLINE
%token POSITIONAL
%token VERB
%token ADVERB
%token PROPER_NOUN
%token NOUN
%token DECLARATIVE
%token CONDITIONAL
%type <name> declarative
%type <name> verb_phrase
%type <name> noun_phrase
%type <name> position_phrase
%type <name> adverb
%type <name> POSITIONAL VERB ADVERB PROPER_NOUN
%type <name> NOUN DECLARATIVE CONDITIONAL
%%
sentence_list : sentence
| sentence_list NEWLINE sentence
;
sentence : verb_phrase noun_phrase position_phrase
adverb period
{
printf("I understand that sentence.\n");
printf("VP = %s \n",$1);
printf("NP = %s \n",$2);
printf("PP = %s \n",$3);
printf("AD = %s \n",$4);
}
| { yyerror("That's a strange sentence!"); }
;
position_phrase : POSITIONAL declarative PROPER_NOUN
{
sprintf($$,"%s %s %s",$1,$2,$3);
}
| /* 空 */ { strcpy($$,""); }
;
verb_phrase : VERB { strcpy($$,$1); strcat($$," "); }
| adverb VERB
{
sprintf($$,"%s %s",$1,$2);
}
;
adverb : ADVERB { strcpy($$,$1); }
| /* 空 */ { strcpy($$,""); }
;
noun_phrase : DECLARATIVE NOUN
{
sprintf($$,"%s %s",$1,$2);
}
| CONDITIONAL declarative NOUN
{
sprintf($$,"%s %s %s",$1,$2,$3);
}
| NOUN { strcpy($$,$1); strcat($$," "); }
;
declarative : DECLARATIVE { strcpy($$,$1); }
| /* 空 */ { strcpy($$,""); }
;
period : /* 空 */
| PERIOD
;
%%
/* main()およびyyerror()関数を提供する */
void main(int argc, char **argv)
{
yyparse(); /* ファイルをパースする */
}
int yyerror(char *message)
{
extern FILE *yyout;
fprintf(yyout,"\nError at line %5d. (%s) \n",
yylexlinenum,message);
}
|
以下はFlexのファイルです。 文字列が渡される方法に注意してください。 これは最適化された方法ではありませんが、 最も理解しやすい方法です。
%{
#include <stdio.h>
#include <string.h>
#include "y.tab.h" /* これはBisonにより生成される */
#define TRUE 1
#define FALSE 0
#define copy_and_return(token_type) \
{ \
strcpy(yylval.name,yytext);\
return(token_type); \
}
int yylexlinenum = 0; /* 行数カウント用 */
%}
%%
/* 字句解析ルールがここから始まる */
MEN|WOMEN|STOCKS|TREES copy_and_return(NOUN)
MISTAKES|GNUS|EMPLOYEES copy_and_return(NOUN)
LOSERS|USERS|CARS|WINDOWS copy_and_return(NOUN)
DATABASE|NETWORK|FSF|GNU copy_and_return(PROPER_NOUN)
COMPANY|HOUSE|OFFICE|LPF copy_and_return(PROPER_NOUN)
THE|THIS|THAT|THOSE copy_and_return(DECLARATIVE)
ALL|FIRST|LAST copy_and_return(CONDITIONAL)
FIND|SEARCH|SORT|ERASE|KILL copy_and_return(VERB)
ADD|REMOVE|DELETE|PRINT copy_and_return(VERB)
QUICKLY|SLOWLY|CAREFULLY copy_and_return(ADVERB)
IN|AT|ON|AROUND|INSIDE|ON copy_and_return(POSITIONAL)
"." return(PERIOD);
"\n" yylexlinenum++; return(NEWLINE);
.
%%
|
これらのファイルは、 以下を実行することでコンパイルできます。
% bison -d front.y % flex -I front.lex % cc -o front alloca.c front.tab.c lex.yy.c |
または、 この例のソースが手元にあれば、 `examples'サブディレクトリにおいて`make front'を実行することでもコンパイルできます。
注:Bisonパーサは`alloca.c'というファイルを必要とします。
このファイルはexamplesサブディレクトリにあります。
Bisonの代わりにyaccを使うのであれば、
このファイルは必要ありません。
以下に実装に関する注を示します。
YYSTYPE と yylval
yylvalがFlexからアクセスされる方法に注目してください。
Bison文法においてパース・ツリーの上位にデータを渡す方法については、
Bisonのマニュアルに説明されていますが、
Flexに対しては何の影響も持ちません。
整数値、浮動小数点数値、および他の任意の型のデータも同様の方法で返すことができます。
トークン値の返却
この例では、
トークンの型と値の両方がBisonからアクセスできるように、
トークンの値と文字列の値の両方がBisonに返されていることに注意してください。
BisonとFlex
BisonとFlexがいかにうまく調和しているかに注目してください。
データを交換するためのコード以外に、
インターフェイスのためのコードは一切ありません。
Bisonはyylex()を呼び出し、
スキャナがトークン定義を提供しています。
Flex 2.5では、 Flexに対するC++インターフェイスが提供されています。
FlexのC++インターフェイスを使うためには、 Flex実行時に`-+'オプションを指定するか、 スキャナ定義ファイルの中で`%option c++'を指定する必要があります。 これにより、 C++のスキャナ・クラスを実装する`lex.yy.cc'というファイルが生成されます。
`lex.yy.cc'は、
Flexが提供する`FlexLexer.h'をインクルードします。
この`FlexLexer.h'の中に、
C++スキャナ・クラスの実装に利用される2つのC++クラス
(FlexLexerとyyFlexLexer)
が定義されています。
FlexLexerは、
C++スキャナ・クラスが実装すべきインターフェイスを構成する抽象仮想関数を定義するクラスです。
FlexLexerの持つメンバを以下に示します。
次に、
FlexLexerの持つメンバ関数のうち、
抽象仮想関数ではないものを以下に示します。
const char* YYText()メンバyytextの値を返します。
int YYLeng()メンバyylengの値を返します。
int yylex(istream* new_in, ostream* new_out = 0)new_inとnew_outを引数に指定して、
メンバ関数switch_streams()を呼び出した後、
メンバ関数int yylex(void)を呼び出します。
int lineno() constメンバyylinenoの値を返します。
int debug() constメンバyy_flex_debugの値を返します。
void set_debug(int flag)flagをメンバyy_flex_debugに代入します。
次に、
FlexLexerの持つ抽象仮想メンバ関数を列挙します。
void yy_switch_to_buffer(struct yy_buffer_state* new_buffer) struct yy_buffer_state* yy_create_buffer(istream* s, int size) void yy_delete_buffer(struct yy_buffer_state* b) void yyrestart(istream* s) int yylex() void switch_streams(istream* new_in = 0, ostream* new_out = 0) |
最初の5つのメンバ関数は、
FlexのCインターフェイスにおける同名の関数と同等の機能を実現します。
Cインターフェイスでは、
FILE*となっていた引数の型が、
istream*となっている点に注意してください。
最後のswitch_streams()は、
入出力ストリームの切り替えを行います。
これらの抽象仮想メンバ関数の定義は、
サブクラスyyFlexLexerにおいて与えられ、
そのコードは`lex.yy.cc'の中に生成されます。
yyFlexLexerは、
FlexLexerのサブクラスです。
デフォルトの状態では、
yyFlexLexerのインスタンスを生成して、
yylex()メンバ関数を呼び出すことによって、
スキャナの処理が実行されます。
以下に例を示します。
int main( int /* argc */, char** /* argv */ )
{
FlexLexer* lexer = new yyFlexLexer;
while(lexer->yylex() != 0)
;
return 0;
}
|
これは、 Cインターフェイスにおける、 以下のコードに対応します。
int main( int /* argc */, char** /* argv */ )
{
yylex();
return 0;
}
|
スキャナ定義ファイルの中に`%option yyclass="classname"'を指定すると、
`lex.yy.cc'にclassname::yylex()が生成されます。
クラスclassnameをyyFlexLexerのサブクラスとして定義することによって、
classnameのインスタンスを使ってスキャン処理を実行することができます。
クラスclassnameを定義する際、
以下に示す、
yyFlexLexerの持つprotectedメンバ関数を再定義することによって、
スキャナの振る舞いを変更することができます。
int LexerInput(char* buf, int max_size)これを再定義することによって、
スキャナの入力処理を変更することができます。
最大max_sizeバイトの文字を入力してbufの指す領域にセットし、
実際に入力したバイト数を戻り値とします。
入力を対話的に扱う場合と扱わない場合で、
処理内容を変更する必要がある場合は、
#ifdef YY_INTERACTIVEを使います。
void LexerOutput(const char* buf, int size)これを再定義することによって、 スキャナの出力処理を変更することができます。 bufの指す領域にあるsizeバイトの文字を出力します。
void LexerError(const char* msg)これを再定義することによって、 エラー・メッセージの出力処理を変更することができます。 エラー・メッセージは、 引数msgで渡されます。
スキャン処理に関わるすべてのコンテキスト情報は、
yyFlexLexerのインスタンスの内部に閉じています。
このことは、
C++スキャナ・クラスを使うことによって、
再入可能なスキャナを生成することが可能であることを意味しています。
複数のC++スキャナ・クラスを生成して、
1つの実行プログラムにリンクすることも可能です。
これを行うには、
Flex起動時に`-Pprefix'オプションを指定するか、
スキャナ定義ファイルの中に`%option prefix="prefix"'を指定することによって、
yyFlexLexerの名前を`prefixFlexLexer'に変更します。
prefixFlexLexerクラスを使うソース・ファイルの中では、
以下のようにして`FlexLexer.h'をインクルードすることによって、
prefixFlexLexer
(実際にはyyFlexLexer)
の定義を参照する必要があります。
#undef yyFlexLexer #define yyFlexLexer prefixFlexLexer #include <FlexLexer.h> |
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Akihiro Sagawa on June, 15 2005 using texi2html 1.70.