ここでは、 Lexが提供していない機能や一般にはあまり使われない機能を説明します。 Flexはほぼ100パーセントLex互換ですが、 Lexよりも後に実装されたため、 性能的により優れており、 また、 広範な用途に使えるスキャナをより簡単に作成することができるよう、 特別な機能を提供しています。
多くの言語は、 その識別子において大文字・小文字を区別しません(Pascal、BASIC、FORTRAN等)。 Lexにも、 大文字・小文字を区別しないスキャナを指定するための方法がありますが、 それらは概して美しくなく、 理解するのも困難です。 個々の文字を置き換えてくれる定義を、 長いリストにして作成することも可能ですし、 すべての識別子を受け付ける1つのルールを作成し、 そのルールにおいて大文字・小文字を変換してから、 トークンの種類を返すようにすることも可能です。 以下のコードは、 この2つの方法を示すものです。 定義を使うのであれば、以下のようになります。
A [aA] B [bB] ... Z [zZ] %% {B}{E}{G}{I}{N} return(BEGIN_SYM); {E}{N}{D} return(END_SYM);
これに似た操作をサブルーチンで実行するのであれば、 以下のようにします。
ALPHA [a-zA-Z] NUM [0-9] ALPHANUM {ALPHA}|{NUM} %% {ALPHA}{ALPHANUM}* return(convert_and_lookup(yytext));
もっともこれは、 関数呼び出しの必要があるため、 効率が悪くなります (Flexでは、 パターンの複雑さは大した影響をもたらしません)。
ほかにもこれと同じことを行う方法がありますが、 いずれもエレガントではありません。
Flexは、 この問題を簡単に解決するための方法を提供しています。 コマンドラインで`-i'オプションを使うことによって、 入力情報の大文字・小文字を区別しないスキャナを生成するよう、 Flexに対して通知することができます。 つまり、 Flexでは上記のようなテクニックを使う必要がないということを意味しています。 例えば、
%% begin return(BEGIN_SYM); end return(END_SYM);
は、 `-i'オプションを使うことによって、 `BEGIN'、`begin'、`BeGiN'、およびこれ以外のすべての大文字・小文字の組み合わせにマッチします。 これは、 Lexにおいて同様のことを行うための方法よりも、 はるかに簡単です。
`-i'オプションには1つ注意すべき点があります。
それは、
スキャナが大文字・小文字を区別しないだけで、
その変換まではしてくれないということです。
つまり、
Pascalにおいてシンボル名をハッシュしたいような場合、
自分でシンボル名を大文字または小文字に変換しなければならないことを意味しています。
そうしないと、
`FOO'と`foo'は異なるものとして扱われます。
これは、
シンボルを保存するルーチンの中で対処することもできますし、
YY_USER_ACTION
を使うことによって対処することもできます。
これを実現する方法の例については、
FlexとCにおけるYY_USER_ACTION
の説明を参照してください。
Flexの問題として、 どのルールを適用するかを決定する前に、 入力情報中の次の1文字を先読みする必要があるということがあります。 対話的ではない使い方をする場合には問題になりませんが、 Flexを使ってユーザから直接入力文字を受け取るような場合には、 問題になることがあります。
このような場合を2つ挙げると、 1つはシェルとやりとりする場合、 もう1つはデータベースのフロント・エンドとやりとりする場合です。 通常のアクションは、 改行が入力の終わりを表すというもので、 改行自身は一種の「中身のない文」として受け付けるのが望ましいのですが、 通常のFlexスキャナではこれは可能ではありません。 Flexが常に先読みをするという事実は、 改行が認識されるためにはユーザが次の行を入力しなければならないということを意味しています (すなわち、 単一の改行は、 それだけでは認識されず、 他の文字が入力される必要があるということです)。 これはシェル上ではまったく望ましくありません。
Flexにはこれを回避する方法があります。 コマンドラインで`-I'オプションを使うと、 Flexは、 必要な場合にしか先読みをしない特別な対話型スキャナを生成します。 この種のスキャナは、 ユーザからの入力を直接受け取るのに適していますが、 若干の性能低下を引き起こすかもしれません。
注:`-I'オプションは、 `-f'、`-F'、`-Cf'、または`-CF'フラグと一緒に使うことはできません。 つまり、 先読みができないことから来る性能低下に加えて、 パーサも性能向上のために最適化することができないということを意味しています。
`-I'オプションに関連するマイナス面は、 通常はきわめて小さいので、 入力情報がどこから来るのか確かではなく、 性能向上のための最適化を施す可能性を諦めても構わないのであれば、 コマンドラインにおいて`-I'オプションを使う方が良いでしょう。
テーブルの圧縮とスピードの領域では、 FlexはLexの能力をはるかに上回っています。 Flexは、 使われるオプションに応じて、 Lexよりもはるかに高速なテーブル、 あるいは、 はるかに小さいテーブルを生成することができます。 この節では、 利用可能なオプションと各オプションがスピードにもたらす影響について説明します。 一般的には、 テーブルが圧縮されるほど、 そのスピードは遅くなります。 Flexでは、 こうしたオプションをコマンドラインで指定します。 オプションは以下のとおりです。(13)
REJECT
を使うことはできない点に注意してください。
注:`-f'フラグと`-F'フラグは、
Flexが生成するテーブルにおいて相違をもたらします。
`-f'フラグはフル・テーブル(full table)を生成し、
`-F'フラグはファスト・テーブル(fast table)を生成します。
ファスト・テーブルとは、
スピードを最大限にするよう最適化されたテーブル形式であり、
一方、
フル・テーブルには最適化は一切施されません。
もたらされる結果はよく似ていますが、
テーブルのサイズは大きく異なるものになる可能性があります。
ALPHA [a-zA-Z] NUM [0-9] ALPHANUM {ALPHA}|{NUM} %% begin return(BEGIN_SYM); ... rules and actions ... end return(END_SYM); {ALPHA}{ALPHANUM}* return(IDENTIFIER);は`-f'フラグを使って処理すべきであり、
{ALPHA}{ALPHANUM}* {ECHO; return(lookup(yytext));}は`-F'フラグを使って処理すべきです。 これらのオプションが指定されている場合は、 アクションの部分に
REJECT
を使うことができない点に注意してください。
[0-9]
の範囲に限定されるのであれば、
0から9までの数は同等クラスの中に置かれることになります。
注:`-Cxx'オプションは、 コマンドライン上には1つだけ指定すべきです。 というのは、 このうち最後に見つかったオプションだけが実際の効果を持つからです。 したがって、
flex -Cf -Cem foo.l
は、 Flexに`-Cem'オプションを使わせることになります。
Flexのデフォルトの動作は、 コマンドライン上で`-Cem'オプションを使った場合に相当します。 この動作では圧縮を最大限に行うことになり、 一般的には最も遅いスキャナが生成されることになります。 こうした小さなテーブルはより速く生成され、 コンパイルもより速く実行されるので、 デフォルトは、 開発段階では非常に便利です。 スキャナのデバッグが終了した後は、 より高速な (そして通常はよりサイズの大きい) スキャナを作成することができます。
翻訳テーブルは、 文字をグループにマップするのに使われます。 このテーブルはLexの持つ機能の1つですが、 POSIXでは定義されていません。 Flexでも翻訳テーブルを使うことはできますが、 サポート対象外の機能です。 Flexにおいては翻訳テーブルは不要です。 というのは、 Flexには`-i'オプションによる同等クラスというものがあり、 これが翻訳テーブルと同等の機能を実現しているからです (`-i'オプションを参照)。 翻訳テーブルの機能は、 互換性のためだけに存在する余分な機能です。 翻訳テーブルを使うことはお勧めできません。 翻訳テーブルを使いたいのであれば、 定義ファイルの先頭の定義セクションにおいて定義しなければなりません。
翻訳テーブルの一般的な形式は以下のとおりです。
%t 1 ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 0123456789 %t %%
これは、 `A'から`Z'までの任意の文字がルールの中で使われている場合、 そのパターンは`A'から`Z'までのどの文字にもマッチするということを意味しています。 したがって、 `A(BC)'と`X(YZ)'はまったく同一であるということになります。
スキャナが、
複数のファイルからの入力を処理することができるということが必要になる状況は、
たくさんあります。
例えば、
多くのPascalの実装では、
コンパイル時に複数のファイルを取り込むことを許していますし、
Cでは、
スキャナもしくはプリプロセッサが#include
文を処理できなければなりません。
このことが意味しているのは、
スキャナは、
カレントなスキャン処理のコンテキストを保存してから新しいコンテキストに変更し、
その後で、
以前の状態と完全に一致する状態に復帰することができなければならないということです。
Flexスキャナは、 スキャン処理のコンテキストを維持するために余分の処理が必要になるような、 大きな入力バッファを使っています。 しかしFlexは、 複数の入力バッファの作成、切り替え、削除が非常に簡単に行えるような特別な機能を提供しています。
Flexは、 複数の入力バッファを取り扱うために、 以下のような関数やマクロを提供しています。
YY_BUFFER_STATE yy_create_buffer( FILE *file, int size)
file
で指定されるファイルのために、
size
で指定される数の文字を格納するのに十分な大きさのバッファを作成します。
この関数は、
後に複数のバッファ間の切り替え、
または新規に作成されたバッファの削除に使うことのできるハンドルを返します。
YY_BUF_SIZE
yy_create_buffer()
に渡すべきサイズが分からない場合に、
これを使うことができます。
void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer)
new_buffer
で指定されるバッファから取られます。
ファイルの終端(EOF)に達するか、
次にyy_switch_to_buffer()
が呼び出されるまで、
new_buffer
からトークンが読み込まれます。
new_buffer
がEOFに達すると、
新しいバッファに切り替えることができます。
void yy_delete_buffer( YY_BUFFER_STATE buffer )
buffer
で指定されるバッファを削除し、
それに割り当てられたメモリを解放します。
YY_CURRENT_BUFFER
上記が、 複数の入力バッファを取り扱うのに必要なすべての機能を提供しています。
Flex 2.5では、 以下のバッファ操作関数もサポートされています。
YY_BUFFER_STATE yy_new_buffer(FILE *file, int size)
yy_create_buffer
の別名です。
void yy_flush_buffer(YY_BUFFER_STATE buffer)
YY_END_OF_BUFFER_CHAR
(`\0')をセットします。
YY_FLUSH_BUFFER
yy_flush_buffer()
を呼び出すよう定義されたマクロです。
さらに、
Flex 2.5では、
メモリ上の文字列を操作するための入力バッファを作成する関数が提供されています。
いずれも、
新しく作成された入力バッファに対応するYY_BUFFER_STATE
型のハンドルを戻り値とします。
入力バッファを使い終わったら、
このハンドルを引数に指定してyy_delete_buffer()
を呼び出す必要があります。
YY_BUFFER_STATE yy_scan_string(const char *str)
yy_scan_bytes()
を呼び出し、
その戻り値を返します。
YY_BUFFER_STATE yy_scan_bytes(const char *bytes, int len)
yy_scan_buffer()
を呼び出し、
その戻り値を返します。
yy_scan_buffer()
の第1引数には、
bytesではなく、
この関数の内部で獲得されたlen + 2
バイトの領域へのポインタが渡される点に注意してください。
yy_scan_buffer()
が呼び出される前に、
bytesから始まるlenバイトのデータが、
新たに獲得した領域にコピーされ、
さらに、
末尾の2バイトにYY_END_OF_BUFFER_CHAR
(`\0')がセットされます。
YY_BUFFER_STATE yy_scan_buffer(char *base, yy_size_t size)
YY_END_OF_BUFFER_CHAR
(`\0')でなければなりません。
この末尾2バイトは、
スキャン処理の対象になりません。
引数で指定されたメモリ領域の末尾2バイトがYY_END_OF_BUFFER_CHAR
でない場合は、
yy_scan_buffer()
はバッファを作成せず、
NULLポインタを返します。
複数のバッファを使うというアイデアを理解するための手助けとして、
インクルードすべきファイルを探すCのスキャナの一部を以下に示します。
これはCの#include
のうち、
引用符で囲まれた文字列のみを受け付けます。
例えば、
#include"file1.c" #include "file2.c" #include " file3.c"
は、 最後の例のファイル名が空白を含むことになりますが、 いずれも正当な入力です。 ここでの例はまた、 EOFルールとスタート状態の使用法を実演する良い例でもあります。
/* * eof_rules.lex : 複数バッファ、EOFルール、スタート状態 * の使い方の例 */ %{ #define MAX_NEST 10 YY_BUFFER_STATE include_stack[MAX_NEST]; int include_count = -1; %} %x INCLUDE %% ^"#include"[ \t]*\" BEGIN(INCLUDE); <INCLUDE>\" BEGIN(INITIAL); <INCLUDE>[^\"]+ { /* インクルード・ファイルの名前を獲得する */ if ( include_count >= MAX_NEST){ fprintf( stderr, "Too many include files" ); exit( 1 ); } include_stack[++include_count] = YY_CURRENT_BUFFER; yyin = fopen( yytext, "r" ); if ( ! yyin ){ fprintf(stderr,"Unable to open \"%s\"\n",yytext); exit( 1 ); } yy_switch_to_buffer( yy_create_buffer(yyin,YY_BUF_SIZE)); BEGIN(INITIAL); } <INCLUDE><<EOF>> { fprintf( stderr, "EOF in include" ); yyterminate(); } <<EOF>> { if ( include_count <= 0 ){ yyterminate(); } else { yy_delete_buffer(include_stack[include_count--] ); yy_switch_to_buffer(include_stack[include_count] ); BEGIN(INCLUDE); } } [a-z]+ ECHO; .|\n ECHO;
スタート状態を使ってファイル名のスキャナを生成する方法や、
バッファの切り替えを発生させる方法に注目してください。
ほかに注目すべき重要な点は、
<<EOF>>
を取り扱うセクション、
および、
古いバッファに復帰する際にBEGIN
を使って確実に正しい状態に遷移するようにする点です。
これを怠ると、
状態はINITIAL
にリセットされ、
#include
の最後の`"'がECHO
されてしまいます。
注:<<EOF>>機能は次の節で説明します。 <<EOF>>が何であり、 何を行うものかという点に関する詳細な議論については、 スタート状態を参照してください。
ファイルの終端(EOF)が見つかると、
Flexはyywrap()
を呼び出し、
ほかに処理できる状態のファイルが存在するか調べます。
yywrap()
が0以外の値を返すと、
もうこれ以上ファイルはないということを意味し、
したがって、
これがまさに入力の最後であるということになります。
状況によっては、
この時点でさらに処理を行う必要のある場合があります
(例えば、
入力のために別のファイルをセットアップしたいということがあるかもしれません)。
このような場合のために、
Flexは<<EOF>>
演算子を提供しています。
これを使うことで、
EOFが見つかった時に実行すべきことを定義することができます。
複数バッファを使う実例を参照してください。
EOFルールを使って、
終わりのないコメントやインクルードされているファイルの終端を見つける、
良い例が示されています。
<<EOF>>
演算子の使用にはいくつか制限があります。
制限事項を以下に示します。
<<EOF>>
ルールが状態により制限されない場合)、
<<EOF>>
が使われていないすべての
(排他的スタート状態を含む)
状態が影響を受けます。
つまり、
"foo"<<EOF>>が不当である一方で、
<<EOF>> /* <<EOF>>が使われていないすべての */ /* 状態におけるEOF */ <indent><<EOF>> /* indent状態におけるEOF */ <comment><<EOF>> /* コメント内のEOF */はすべて正当であることを意味しています。
yy_switch_to_buffer()
、
またはYY_NEW_FILE
を使って)新しい入力ストリームを確立する、
(2) (return
文を使って)復帰する、
(3) (yyterminate()
、
またはexit()
を使って)スキャナの実行を終了させる、
のいずれかを実行しなければなりません。
複数バッファを使う実例を参照してください。
yy_terminate()
とyy_switch_to_buffer()
を使う例が示されています。
また、
yyterminate()
の説明については、
FlexとCを参照してください。