[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
ここでは、 Lexが提供していない機能や一般にはあまり使われない機能を説明します。 Flexはほぼ100パーセントLex互換ですが、 Lexよりも後に実装されたため、 性能的により優れており、 また、 広範な用途に使えるスキャナをより簡単に作成することができるよう、 特別な機能を提供しています。
========================================================================
5.1 大文字・小文字を区別しないスキャナ | ||
5.2 `-I'オプション:対話型スキャナ | ユーザから入力を受け取るスキャナ | |
5.3 テーブルの圧縮とスキャナのスピード | ||
5.4 翻訳テーブル | ||
5.5 複数の入力バッファ | ||
5.6 ファイルの終端(End-Of-File)ルール | EOFを処理するための特殊なルール |
========================================================================
多くの言語は、 その識別子において大文字・小文字を区別しません(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では、 パターンの複雑さは大した影響をもたらしません)。
ほかにもこれと同じことを行う方法がありますが、 いずれもエレガントではありません。
========================================================================
5.1.1 `-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
の説明を参照してください。
Flexの問題として、 どのルールを適用するかを決定する前に、 入力情報中の次の1文字を先読みする必要があるということがあります。 対話的ではない使い方をする場合には問題になりませんが、 Flexを使ってユーザから直接入力文字を受け取るような場合には、 問題になることがあります。
このような場合を2つ挙げると、 1つはシェルとやりとりする場合、 もう1つはデータベースのフロント・エンドとやりとりする場合です。 通常のアクションは、 改行が入力の終わりを表すというもので、 改行自身は一種の「中身のない文」として受け付けるのが望ましいのですが、 通常のFlexスキャナではこれは可能ではありません。 Flexが常に先読みをするという事実は、 改行が認識されるためにはユーザが次の行を入力しなければならないということを意味しています (すなわち、 単一の改行は、 それだけでは認識されず、 他の文字が入力される必要があるということです)。 これはシェル上ではまったく望ましくありません。
Flexにはこれを回避する方法があります。 コマンドラインで`-I'オプションを使うと、 Flexは、 必要な場合にしか先読みをしない特別な対話型スキャナを生成します。 この種のスキャナは、 ユーザからの入力を直接受け取るのに適していますが、 若干の性能低下を引き起こすかもしれません。
注:`-I'オプションは、 `-f'、`-F'、`-Cf'、または`-CF'フラグと一緒に使うことはできません。 つまり、 先読みができないことから来る性能低下に加えて、 パーサも性能向上のために最適化することができないということを意味しています。
`-I'オプションに関連するマイナス面は、 通常はきわめて小さいので、 入力情報がどこから来るのか確かではなく、 性能向上のための最適化を施す可能性を諦めても構わないのであれば、 コマンドラインにおいて`-I'オプションを使う方が良いでしょう。
テーブルの圧縮とスピードの領域では、 FlexはLexの能力をはるかに上回っています。 Flexは、 使われるオプションに応じて、 Lexよりもはるかに高速なテーブル、 あるいは、 はるかに小さいテーブルを生成することができます。 この節では、 利用可能なオプションと各オプションがスピードにもたらす影響について説明します。 一般的には、 テーブルが圧縮されるほど、 そのスピードは遅くなります。 Flexでは、 こうしたオプションをコマンドラインで指定します。 オプションは以下のとおりです。
このオプションは、
Flexがフル・テーブル(full table)を生成すべきであることを指定します。
このテーブルはまったく圧縮されず、
サイズが大きくなりますが、
スピードは速くなります。
このオプションが指定された場合は、
アクションの部分にREJECT
を使うことはできない点に注意してください。
注:`-f'フラグと`-F'フラグは、 Flexが生成するテーブルにおいて相違をもたらします。 `-f'フラグはフル・テーブル(full table)を生成し、 `-F'フラグはファスト・テーブル(fast table)を生成します。 ファスト・テーブルとは、 スピードを最大限にするよう最適化されたテーブル形式であり、 一方、 フル・テーブルには最適化は一切施されません。 もたらされる結果はよく似ていますが、 テーブルのサイズは大きく異なるものになる可能性があります。
このオプションはファスト・テーブル(fast table)形式を用いてテーブルを生成するようFlexに通知します。 一般的には、 このテーブルのスピードは先に説明したフル・テーブル(full table)とほとんど同じですが、 使われるパターンに応じて、 そのサイズは小さくも大きくもなる可能性があります。 原則として、 すべての識別子をキャッチするルールのほかにキーワードの一覧も持つファイルに対しては、 `-f'オプションを使うべきです。 例えば、
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
を使うことができない点に注意してください。
このオプションを使うと、
性能にはわずかしか影響を及ぼさずに、
テーブルのサイズをかなり小さくすることができます。
`-Ce'が使われると、
Flexは同等クラス(equivalence classes)を作成します。
同等クラスとは、
同一の方法で使われる文字のグループです。
例えば、
使われる数字が集合[0-9]
の範囲に限定されるのであれば、
0から9までの数は同等クラスの中に置かれることになります。
同等クラスを持つファスト・テーブルです。 このオプションによって生成されたスキャナもまた高速であり、 かつ、 `-Cf'あるいは`-CF'を指定して生成されたスキャナと比較して、 サイズもはるかに小さくなる可能性があります。 サイズ、 またはスピードの一方が他方に比べてはるかに重要であるということがないのであれば、 これは良い組み合わせです。
Flexに対してメタ同等クラス(meta-equivalence classes)を使うよう通知します。 これは、 一緒に使われることが多い文字の集合、 または (同等クラスが使われている場合には) 同等クラスです。 同等クラスを使う場合よりも性能はさらに悪くなりますが、 これは多くの場合、 テーブル・サイズを小さくするのに非常に効果的な方法です。
デフォルトのテーブル圧縮です。 このオプションで生成されるスキャナは、 Flexが生成するスキャナの中で事実上最も小さく、 かつ、 最も性能の劣るものになります。
`-C'オプション単体では、 同等クラスやメタ同等クラスを使わずにテーブルを圧縮するよう、 Flexに対して通知します。
注:`-Cxx'オプションは、 コマンドライン上には1つだけ指定すべきです。 というのは、 このうち最後に見つかったオプションだけが実際の効果を持つからです。 したがって、
flex -Cf -Cem foo.l |
は、 Flexに`-Cem'オプションを使わせることになります。
Flexのデフォルトの動作は、 コマンドライン上で`-Cem'オプションを使った場合に相当します。 この動作では圧縮を最大限に行うことになり、 一般的には最も遅いスキャナが生成されることになります。 こうした小さなテーブルはより速く生成され、 コンパイルもより速く実行されるので、 デフォルトは、 開発段階では非常に便利です。 スキャナのデバッグが終了した後は、 より高速な (そして通常はよりサイズの大きい) スキャナを作成することができます。
翻訳テーブルは、 文字をグループにマップするのに使われます。 このテーブルはLexの持つ機能の1つですが、 POSIXでは定義されていません。 Flexでも翻訳テーブルを使うことはできますが、 サポート対象外の機能です。 Flexにおいては翻訳テーブルは不要です。 というのは、 Flexには`-i'オプションによる同等クラスというものがあり、 これが翻訳テーブルと同等の機能を実現しているからです (see section `-i'オプション)。 翻訳テーブルの機能は、 互換性のためだけに存在する余分な機能です。 翻訳テーブルを使うことはお勧めできません。 翻訳テーブルを使いたいのであれば、 定義ファイルの先頭の定義セクションにおいて定義しなければなりません。
翻訳テーブルの一般的な形式は以下のとおりです。
%t 1 ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 0123456789 %t %% |
これは、 `A'から`Z'までの任意の文字がルールの中で使われている場合、 そのパターンは`A'から`Z'までのどの文字にもマッチするということを意味しています。 したがって、 `A(BC)'と`X(YZ)'はまったく同一であるということになります。
スキャナが、
複数のファイルからの入力を処理することができるということが必要になる状況は、
たくさんあります。
例えば、
多くのPascalの実装では、
コンパイル時に複数のファイルを取り込むことを許していますし、
Cでは、
スキャナもしくはプリプロセッサが#include
文を処理できなければなりません。
このことが意味しているのは、
スキャナは、
カレントなスキャン処理のコンテキストを保存してから新しいコンテキストに変更し、
その後で、
以前の状態と完全に一致する状態に復帰することができなければならないということです。
Flexスキャナは、 スキャン処理のコンテキストを維持するために余分の処理が必要になるような、 大きな入力バッファを使っています。 しかしFlexは、 複数の入力バッファの作成、切り替え、削除が非常に簡単に行えるような特別な機能を提供しています。
========================================================================
5.5.1 バッファを操作する関数 | ||
5.5.2 複数バッファを使う実例 |
========================================================================
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
使用中のカレントなバッファを返すマクロです。
上記が、 複数の入力バッファを取り扱うのに必要なすべての機能を提供しています。
複数のバッファを使うというアイデアを理解するための手助けとして、
インクルードすべきファイルを探す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>>が何であり、 何を行うものかという点に関する詳細な議論については、 See section スタート状態。
ファイルの終端(EOF)が見つかると、
Flexはyywrap()
を呼び出し、
ほかに処理できる状態のファイルが存在するか調べます。
yywrap()
が0以外の値を返すと、
もうこれ以上ファイルはないということを意味し、
したがって、
これがまさに入力の最後であるということになります。
状況によっては、
この時点でさらに処理を行う必要のある場合があります
(例えば、
入力のために別のファイルをセットアップしたいということがあるかもしれません)。
このような場合のために、
Flexは<<EOF>>
演算子を提供しています。
これを使うことで、
EOFが見つかった時に実行すべきことを定義することができます。
See section 複数バッファを使う実例。
EOFルールを使って、
終わりのないコメントやインクルードされているファイルの終端を見つける、
良い例が示されています。
<<EOF>>
演算子の使用にはいくつか制限があります。
制限事項を以下に示します。
パターンと一緒に使用することは不可
EOFルールは、
スタート状態とのみ一緒に使うことができます。
スタート状態が指定されていない場合
(すなわち、
<<EOF>>
ルールが状態により制限されない場合)、
<<EOF>>
が使われていないすべての
(排他的スタート状態を含む)
状態が影響を受けます。
つまり、
"foo"<<EOF>> |
が不当である一方で、
<<EOF>> /* <<EOF>>が使われていないすべての */ /* 状態におけるEOF */ <indent><<EOF>> /* indent状態におけるEOF */ <comment><<EOF>> /* コメント内のEOF */ |
はすべて正当であることを意味しています。
アクションの終端
1つ注意しなければならない点は、
EOFルールは入力の最後で呼び出されるという点です。
したがって、
EOFルールのアクションは、
(1) (yy_switch_to_buffer()
、
またはYY_NEW_FILE
を使って)新しい入力ストリームを確立する、
(2) (return
文を使って)復帰する、
(3) (yyterminate()
、
またはexit()
を使って)スキャナの実行を終了させる、
のいずれかを実行しなければなりません。
See section 複数バッファを使う実例。
yy_terminate()
とyy_switch_to_buffer()
を使う例が示されています。
また、
yyterminate()
の説明については、
FlexとCを参照してください。
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Akihiro Sagawa on June, 15 2005 using texi2html 1.70.