[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


Flexの他の特徴

ここでは、 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では、 パターンの複雑さは大した影響をもたらしません)。

ほかにもこれと同じことを行う方法がありますが、 いずれもエレガントではありません。

`-i'オプション

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の説明を参照してください。

`-I'オプション:対話型スキャナ

Flexの問題として、 どのルールを適用するかを決定する前に、 入力情報中の次の1文字を先読みする必要があるということがあります。 対話的ではない使い方をする場合には問題になりませんが、 Flexを使ってユーザから直接入力文字を受け取るような場合には、 問題になることがあります。

このような場合を2つ挙げると、 1つはシェルとやりとりする場合、 もう1つはデータベースのフロント・エンドとやりとりする場合です。 通常のアクションは、 改行が入力の終わりを表すというもので、 改行自身は一種の「中身のない文」として受け付けるのが望ましいのですが、 通常のFlexスキャナではこれは可能ではありません。 Flexが常に先読みをするという事実は、 改行が認識されるためにはユーザが次の行を入力しなければならないということを意味しています (すなわち、 単一の改行は、 それだけでは認識されず、 他の文字が入力される必要があるということです)。 これはシェル上ではまったく望ましくありません。

Flexにはこれを回避する方法があります。 コマンドラインで`-I'オプションを使うと、 Flexは、 必要な場合にしか先読みをしない特別な対話型スキャナを生成します。 この種のスキャナは、 ユーザからの入力を直接受け取るのに適していますが、 若干の性能低下を引き起こすかもしれません。

注:`-I'オプションは、 `-f'`-F'`-Cf'、または`-CF'フラグと一緒に使うことはできません。 つまり、 先読みができないことから来る性能低下に加えて、 パーサも性能向上のために最適化することができないということを意味しています。

`-I'オプションに関連するマイナス面は、 通常はきわめて小さいので、 入力情報がどこから来るのか確かではなく、 性能向上のための最適化を施す可能性を諦めても構わないのであれば、 コマンドラインにおいて`-I'オプションを使う方が良いでしょう。

テーブルの圧縮とスキャナのスピード

テーブルの圧縮とスピードの領域では、 FlexはLexの能力をはるかに上回っています。 Flexは、 使われるオプションに応じて、 Lexよりもはるかに高速なテーブル、 あるいは、 はるかに小さいテーブルを生成することができます。 この節では、 利用可能なオプションと各オプションがスピードにもたらす影響について説明します。 一般的には、 テーブルが圧縮されるほど、 そのスピードは遅くなります。 Flexでは、 こうしたオプションをコマンドラインで指定します。 オプションは以下のとおりです。(13)

注:`-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は、 複数の入力バッファを取り扱うために、 以下のような関数やマクロを提供しています。

上記が、 複数の入力バッファを取り扱うのに必要なすべての機能を提供しています。

バッファを操作する関数(Flex 2.5の補足情報)

Flex 2.5では、 以下のバッファ操作関数もサポートされています。

さらに、 Flex 2.5では、 メモリ上の文字列を操作するための入力バッファを作成する関数が提供されています。 いずれも、 新しく作成された入力バッファに対応するYY_BUFFER_STATE型のハンドルを戻り値とします。 入力バッファを使い終わったら、 このハンドルを引数に指定してyy_delete_buffer()を呼び出す必要があります。

複数バッファを使う実例

複数のバッファを使うというアイデアを理解するための手助けとして、 インクルードすべきファイルを探す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>>が何であり、 何を行うものかという点に関する詳細な議論については、 スタート状態を参照してください。

ファイルの終端(End-Of-File)ルール

ファイルの終端(EOF)が見つかると、 Flexはyywrap()を呼び出し、 ほかに処理できる状態のファイルが存在するか調べます。 yywrap()が0以外の値を返すと、 もうこれ以上ファイルはないということを意味し、 したがって、 これがまさに入力の最後であるということになります。 状況によっては、 この時点でさらに処理を行う必要のある場合があります (例えば、 入力のために別のファイルをセットアップしたいということがあるかもしれません)。 このような場合のために、 Flexは<<EOF>>演算子を提供しています。 これを使うことで、 EOFが見つかった時に実行すべきことを定義することができます。 複数バッファを使う実例を参照してください。 EOFルールを使って、 終わりのないコメントやインクルードされているファイルの終端を見つける、 良い例が示されています。

<<EOF>>演算子の使用にはいくつか制限があります。 制限事項を以下に示します。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]