Provided by: manpages-ja_0.5.0.0.20210215+dfsg-1_all
名称
flex - 高速な字句解析処理系の生成ツール
書式
flex [-bcdfhilnpstvwBFILTV78+? -C[aefFmr] -ooutput -Pprefix -Sskeleton] [--help --version] [filename ...]
概説
本マニュアルは、 テキストのパターンマッチングを行うプログラムを生成するツール flex を扱い ます。 本マニュアルはチュートリアルとリファレンス節とを含みます: 解説 ツールの短い概説 簡単な例 入力ファイルのフォーマット パターン flex が使用する拡張した正規表現 入力のマッチ方法 何がマッチするかを決定する規則 アクション パターンがマッチした時に何を行うかを指定する方法 生成されたスキャナ flex が生成するスキャナに関する詳細; 入力元の制御方法 開始条件 スキャナへの文脈の導入と、 "ミニスキャナ" の制御方法 複数の入力バッファ 複数の入力元を扱う方法; ファイルではなく文字列からスキャンする方法 ファイルの終りのルール ファイルの終りにマッチする特別なルール 雑多なマクロ アクションで使用可能なマクロのまとめ ユーザが使用可能な値 アクションで使用可能な値のまとめ Yacc とのインタフェース lex スキャナと yacc パーサとの結合 オプション flex のコマンドラインオプションと、 "%option" ディレクティブ 性能関連 スキャナを可能な限り高速にする方法 C++ スキャナの生成 C++ スキャナクラス生成のための (実験的な) 機能 Lex および POSIX との非互換性 AT&T lex および POSIX lex 標準と flex との違い 診断 flex (もしくは生成したスキャナ) が出力する エラーメッセージで意味が明確でないもの 関連ファイル flex が使用するファイル 欠陥 / バグ flex の既知の問題 関連項目 ツールに関係する他のドキュメント 作者 連絡方法を含みます
解説
flex は スキャナ を生成するためのツールです。 ここで、スキャナとは、 テキスト内の字句パ ターンを解析するプログラムです。 flex は指定したファイル、もしくはファイル名が与えられな かった場合は 標準入力から、生成するスキャナの記述を読み込みます。 この記述は、 正規表現と C コードのペアの形をとっています。 これは ルール と呼ばれます。 flex は、出力として C ソー スファイルの lex.yy.c を生成しますが、その中に yylex() ルーチンが定義されます。 このファイ ルはコンパイルされ、 -ll ライブラリとともにリンクされて、 実行形式となります。 実行形式が 走り始めると、 正規表現をマッチさせるために 入力が解析されます。 マッチするものを見つける と、対応する C コードが実行されます。
簡単な例
まず簡単な例から、 flex の使い方を見て行きましょう。 次の flex の入力は、"username" という 文字列に出会うとユーザのログイン名に置き換える スキャナを指定しています: %% username printf( "%s", getlogin() ); デフォルトでは、 flex スキャナにマッチしなかったテキストは出力にコピーされますので、 "username" を展開しながら入力を出力にコピーすることが このスキャナの最終的な結果となりま す。 この入力にはただ一つのルールだけがあります。 "username" は パターン であり、"printf" は アクション です。 "%%" はルールの始まりの印です。 別の例を見て見ましょう: %{ int num_lines = 0, num_chars = 0; %} %% \n ++num_lines; ++num_chars; . ++num_chars; %% main() { yylex(); printf( "# of lines = %d, # of chars = %d\n", num_lines, num_chars ); } このスキャナは入力の文字数および行数を数えます (数えた最終結果を報告するだけです)。 最初の 行は 2 つの大域変数 "num_lines" と "num_chars" を宣言します。 これらの変数は、2 番目の "%%" の後に宣言されている yylex() と main() のルーチンからアクセス可能です。 ここには 2 つ のルールがあります。 1 つ目は改行文字 ("\n") にマッチし、行数と文字数のカウントを増加させ ます。 もう 1 つは、改行文字以外の全ての文字 ("." という正規表現で表されています)にマッチ します。 次はもうちょっと複雑な例です: /* scanner for a toy Pascal-like language */ %{ /* need this for the call to atof() below */ #include <math.h> %} DIGIT [0-9] ID [a-z][a-z0-9]* %% {DIGIT}+ { printf( "An integer: %s (%d)\n", yytext, atoi( yytext ) ); } {DIGIT}+"."{DIGIT}* { printf( "A float: %s (%g)\n", yytext, atof( yytext ) ); } if|then|begin|end|procedure|function { printf( "A keyword: %s\n", yytext ); } {ID} printf( "An identifier: %s\n", yytext ); "+"|"-"|"*"|"/" printf( "An operator: %s\n", yytext ); "{"[^}\n]*"}" /* eat up one-line comments */ [ \t\n]+ /* eat up whitespace */ . printf( "Unrecognized character: %s\n", yytext ); %% main( argc, argv ) int argc; char **argv; { ++argv, --argc; /* skip over program name */ if ( argc > 0 ) yyin = fopen( argv[0], "r" ); else yyin = stdin; yylex(); } これは Pascal のような言語の単純なスキャナの原型です。 異なったタイプの トークン を定義 し、これを見付けると報告します。 この例の詳細は、以降の節で説明します。
入力ファイルのフォーマット
flex の入力ファイルは 3 つの部分からなり、 %% だけからなる行により分けられます: 定義 %% ルール %% ユーザコード 定義 部分は、スキャナの宣言を単純化する単純な 名前 の定義の宣言と、後で説明する 開始条件 の宣言とからなります。 名前の定義は次の形式です: 名前 定義 "名前" は語であり、 レターかアンダースコア ('_') から始まって 0 個以上のレター・数 字・'_'・'-' (ダッシュ)が続きます。 定義は、名前に続く最初の非空白文字から始まり、行末まで 続くものとされます。 定義は後で "{名前}" で参照でき、"(定義)" を展開します。 例えば、 DIGIT [0-9] ID [a-z][a-z0-9]* は、 "DIGIT" が単一の数字にマッチする正規表現であると定義し、 "ID" がレターに 0 個以上のレ ターか数字が続く正規表現であると定義します。 後で出て来る参照 {DIGIT}+"."{DIGIT}* は ([0-9])+"."([0-9])* と同じであり、1 個以上の数字に '.' が続き、 0 個以上の数字が続くものにマッチします。 flex の入力の ルール は次の形式の一連のルールからなります: パターン アクション ここで、パターンはインデントされていてはならず、 アクションは同じ行から始まる必要がありま す。 パターンとアクションの詳細は後の解説を見て下さい。 最後に、ユーザコードの部分は単純にそのままの形で lex.yy.c にコピーされます。 スキャナを呼 び出すまたは呼び出される付随ルーチンのために使用されます。 この部分はあっても無くても構い ません; 無い場合には、入力ファイル中の 2 番目の %% も省略できます。 定義とルールの部分では、 インデントされた テキストと %{ と %} との間のテキストはそのままの 形で出力にコピーされます (この際 %{} は削除されます)。 %{} はインデントされていない行に現 れる必要があります。 ルールの部分では、 最初のルールの前に現れるインデントされたもしくは %{} 部分のテキストは、 スキャンルーチンにローカルな変数と、 (宣言の後では)スキャンルーチンに入るたびに実行される コードとを宣言します。 ルール部分の他のインデントされたもしくは %{} 部分のテキストは 出力 にコピーされますが、 意味はちゃんと定義されておらずコンパイル時にエラーとなるかも知れませ ん (この仕様は POSIX 互換のためにあります; 他のこのような仕様は以降を見て下さい)。 定義の部分(ルールの部分ではないです)では、 インデントされていないコメント("/*" から始まる 行) は次の "*/" まで そのままの形でコピーされます。
パターン
入力ファイルのパターンは拡張した正規表現を使って記述します。 以下に示します: x 文字 'x' にマッチ。 . 改行を除く全ての文字(バイト)。 [xyz] "文字クラス"; この場合、'x', 'y', 'z' のいずれにも マッチします。 [abj-oZ] 範囲指定を含む "文字クラス"; この場合、'a', 'b' と 'j' から 'o' までの任意のレターと 'Z' にマッチします。 [^A-Z] "否定文字クラス"; クラスに含まれない任意の文字に マッチします。 この場合、'A' から 'Z' までの大文字 「以外の」文字にマッチします。 [^A-Z\n] 大文字と改行を「除く」全ての文字。 r* 0 もしくはそれ以上の r。r は任意の正規表現。 r+ 1 もしくはそれ以上の r。 r? 0 もしくは 1つの r (「おまけ」の r) r{2,5} 2 つから 5つまでの r。 r{2,} 2 つ以上の r。 r{4} ちょうど 4つ の r。 {名前} "名前" の定義の展開。 (上を参照) "[xyz]\"foo" 文字列 [xyz]"foo \X X が 'a', 'b', 'f', 'n', 'r', 't', 'v' のいずれかの とき、ANSI-C での \X の解釈となります。 それ以外の場合、文字 'X' ('*' のようなオペレータの 意味を打ち消し、その文字自体を指定する際に使います)。 \123 8進数で 123 と表される文字。 \x2a 16進数で 2a と表される文字。 (r) r にマッチ; ()は 優先順位を変えるために使用。 (以下を参照) rs 正規表現 r に正規表現 s が続く; 「連結(concatenation)」 と呼びます。 r|s r もしくは s。 r/s 後ろに s が続く時の r。 s にマッチするテキストはこのルールの "最長適合" を判定する 時には含まれますが、アクションが実行される前に 入力に戻されます。 アクションは r にマッチするテキストだけを見ます。 このパターンは "右文脈(trailing context)" と呼ばれます。 (flex が正確にマッチ不能な r/s の組合せは複数あります; "危険な右文脈" については、 以降の、欠陥 / バグ の節の記述を見て下さい。) ^r 行頭にある r。(スキャンの始まりもしくは スキャンされた改行の右です)。 r$ 行末にある r。"r/\n" と等価(改行の前です)。 "r/\n" と同じです。 flex の "改行" の表現は flex をコンパイルした C コンパイラが解釈する '\n' と完全に一致することに 注意して下さい; 特定のシステム DOS では \r を入力から取り除くか "r$" を表すために明示的に r/\r\n を使用する必要があります。 <s>r 開始条件 s における r。(開始条件については以下を 参照)。 <s1,s2,s3>r 上に同じ。ただし開始条件は s1, s2, s3 のいずれでもよい。 <*>r 任意の開始条件の r。開始条件は排他的なものでもよい。 <<EOF>> ファイルの終了。 <s1,s2><<EOF>> 開始条件が s1 もしくは s2 であるときのファイルの終了。 文字クラス中では、全ての正規表現のオペレータは、 エスケープ ('\') および 文字クラスオペ レータである '-' と ']' とクラスの先頭の '^' を除き 特別な意味を失うことに注意して下さい。 上に挙げた正規表現は優先順位によってグループに分けられています。 一番上のグループが最も高 い優先度で、 一番下のグループの優先順位が最も低くなっています。 グループ内では同じ優先順位 です。例えば、 foo|bar* は (foo)|(ba(r*)) と同じです。なぜなら '*' オペレータは連結より優先度が高く、 連結は選言 ('|') より優先度が 高いからです。このパターンは 文字列 "foo" もしくは 文字列 "ba" に 0 個以上の r がつづくも のの どちらにも マッチします。 "foo" もしくは 0 個以上の "bar" にマッチさせるためには次の 表現を使用して下さい: foo|(bar)* 0 個以上の "foo" または "bar" にマッチするためには次の表現を使用して下さい: (foo|bar)* 文字もしくは文字範囲に加え、文字クラスも文字クラスの 表現 を含みます。 これらの表現は [: および :] のデリミタに囲まれます (文字クラスの '[' と ']' との間に現れる必要があります; 他 の要素が文字クラス中に現れても構いません)。 有効な表現は以下の通りです: [:alnum:] [:alpha:] [:blank:] [:cntrl:] [:digit:] [:graph:] [:lower:] [:print:] [:punct:] [:space:] [:upper:] [:xdigit:] これらの表現は対応する標準 C の isXXX 関数に適合する全ての文字集合を指示します。例えば、 [:alnum:] は isalnum() が真を返す文字を指示します - すなわちすべてのアルファベットと数字で す。 isblank(), が無いシステムでは、flex は [:blank:] を空白とタブと定義します。 例えば以下の表現は全て同じです: [[:alnum:]] [[:alpha:][:digit:]] [[:alpha:]0-9] [a-zA-Z0-9] スキャナが大文字小文字を意識しない場合( -i フラグ指定時) [:upper:] と [:lower:] は [:alpha:] と同じです。 パターンに関する注意点です: - 否定文字クラス、例えば上の "[^A-Z]" は "\n" (もしくはこれを表すエスケープシーケン ス) が明示的に 否定文字クラスに現れている場合 (例えば "[^A-Z\n]") を除き 改行にマッ チします。 これは他の正規表現ツールが否定文字クラスを扱う方法とは異なりますが、 不 幸なことにこの矛盾は歴史的に確立しています。 改行にマッチするとは、 入力に別のク オートが存在しない場合に [^"]* のようなパターンが 入力全体にマッチすることを意味し ます。 - ルールは右文脈('/' オペレータもしくは '$' オペレータ) を高々一つしか持てません。 開 始条件 '^' と "<<EOF>>" パターンは パターンの最初になければならず、 '/', '$' 同様に () 内にいれることは出来ません。 ルールの先頭ではない '^' もしくはルールの終りではな い '$' は 特別な意味を失い、通常の文字として扱われます。 以下は無効です: foo/bar$ <sc1>foo<sc2>bar 前者は "foo/bar\n" と書けます。 以下では '$' と '^' とは通常の文字として扱われます: foo|(bar$) foo|^bar "foo" もしくは "改行が続く bar" を指定したい場合は、 次の表現を使用して下さい (特別 な '|' の動作は後で説明します): foo | bar$ /* action goes here */ 同じ方法で、foo もしくは 行頭の bar を指定可能です。
入力のマッチ方法
生成したスキャナを実行すると、 スキャナは入力を見てパターンにマッチする文字列を探します。 1 より多くのマッチを見付けると、最長テキストのマッチを採用します (右文脈(trailing context rule)の後ろの部分も長さに含みますが、 後ろの部分は入力に戻されます)。 同じ長さのマッチを 2 つ以上見付けた場合、 flex 入力ファイルで最初に記述されたルールを採用します。 マッチが決定すると、マッチに対応するテキスト( トークン と呼ばれます)がグローバル文字ポイン タ yytext により使用可能となり、長さがグローバル整数 yyleng により使用可能となります。 そ の後、マッチしたパターンに対応する アクション が実行され(アクションの詳細な記述は後で行い ます)、 残りの入力が残りのマッチのためにスキャンされます。 マッチが見付からないと、 デフォルトルール が実行されます: 入力の次の文字がマッチしたと見倣 され、 標準出力にコピーされます。最も簡単で正当な flex の入力は以下の通りです: %% これは、入力を単純に出力にコピー(1 度に 1 文字ずつ)するスキャナを生成します。 yytext は 2 つの異なった方法により定義されうることに注意して下さい: 文字 ポインタ もしくは 文字 配列 です。 flex がどちらの定義を使用するかは特別なディレクティブ %pointer もしくは %array を flex の入力の最初の(定義)部分に含めることにより制御できます。 デフォルトは %pointer であり、 -l lex 互換オプションを使用した場合には例外的に yytext は配列になりま す。 %pointer を使用する利点はスキャンが高速であること、 非常に大きなトークンにマッチする 時にも (動的メモリを使用し尽くさない限り)バッファオーバフローとならないことです。 欠点 は、アクションが yytext を修正することが制限されること(次節参照)、 unput() 呼び出しが yytext の現在の内容を破壊することです。 これは異なる lex バージョン間での移植性に関する頭 痛の種です。 %array の利点は yytext の内容を思った通りに変更できること、 unput() を呼び出しても yytext の内容が破壊されないことです(下記参照)。 その上、既存の lex プログラムは yytext を外部から 次の形式の宣言を使用してアクセスしていることがあります: extern char yytext[]; この定義は %pointer 使用時には誤りですが、 %array 使用時には正しいです。 %array は yytext を文字数 YYLMAX (デフォルトは十分大きな値)の配列であると定義します。 この 大きさは、 flex の入力の最初の部分で単純に YYLMAX を異なった値に #define することにより変 更できます。 上記の通り、 %pointer 使用時には yytext は大きなトークンを格納するために動的 に大きくなります。 このことは %pointer を使用したスキャナは非常に大きなトークン (例えばコ メントブロック全体)を格納可能であることを意味しますが、 スキャナが yytext の大きさを変える たびにトークン全体を先頭から再スキャンすることが必要となるため このようなトークンに対する マッチングは遅くなりうることを覚えておいて下さい。 現在、 yytext は unput() が結果として返 すテキストが大きい時には動的には大きくなり ません; 実行時エラーとなります。 また、 %array は C++ スキャナクラスでは使用できないことに注意して下さい( c++ オプションに 関しては下記参照)。
アクション
ルール中のパターンは対応するアクションを持ちます。 アクションは任意の C の文です。 パター ンは最初のエスケープされていない空白文字で終ります; 行の残りがアクションです。 アクション が空である場合、 パターンがマッチした時に入力トークンは単純に捨てられます。 例えば入力から 全ての "zap me" を削除するプログラムの仕様を示します: %% "zap me" (入力の他の全ての文字を出力にコピーします。 なぜならデフォルトルールにマッチするからで す。) 次は、複数の空白や文字を単一の空白に圧縮し行末の空白を捨てるプログラムです: %% [ \t]+ putchar( ' ' ); [ \t]+$ /* ignore this token */ アクションが '{' を含む場合、アクションは対応する '}' まで続き、 複数行に渡る場合もありま す。 flex は C の文字列およびコメントに関して知っており、 それらの中のブレースを誤解するこ とはありませんが、 アクションが %{ で始まることを許し、次の %} までのテキストがアクション であるとします (アクション内部の任意個のブレースには関係ありません)。 垂直バー ('|') のみからなるアクションは "次のルールと同じ" を意味します。説明は以下を見て 下さい。 アクションは任意の C コードを含むことが出来ます。 これには、 yylex() を呼び出したルーチン に対して値を返す return 文も含まれます。 yylex() が呼ばれるたび、最後に残ったトークンから 処理を再開し、 ファイルの終了もしくは return を実行するまで処理を行います。 アクションは自由に yytext を変更できますが、例外は長さを増やすことです (文字を末尾に加える ことになり、 これは入力ストリームの後続する文字を上書きします)。 これは %array 使用時には 当てはまりません(上述); この場合 yytext を自由に変更できます。 アクションは自由に yyleng を変更できますが、アクションが yymore() を使用する時には例外的に 変更してはいけません(後述)。 多くの特別なディレクティブがあり、アクション中に含めることが出来ます: - ECHO yytext をスキャナの出力にコピーします。 - BEGIN 後ろに開始条件の名前を書くと、スキャナを対応する開始条件に設定します(後述)。 - REJECT 入力(もしくは入力の頭)に "2 番目によく(second best)" マッチするルール に進む ようにスキャナに指示します。 "入力のマッチ方法" で示したようにルールは選択され、 yytext と yyleng は適切に設定されます。 選択されるルールは、最初に選択されたルール と同じ長さであるが flex の入力ファイルにて後で出て来るもの、もしくは少ない文字数に マッチするものです。 例えば次の例では入力中の語を数え、 "frob" が見付かるたびにルー チン special() を呼びます: int word_count = 0; %% frob special(); REJECT; [^ \t\n]+ ++word_count; REJECT が無い場合、 入力中の "frob" は語として数えられず、 スキャナは通常通りトーク ン毎に 1 つのアクションだけを行います。 複数の REJECT を使用可能であり、それぞれ現 在有効なルールの次に良い選択を見付けます。 例えば次のスキャナは、"abcd" というトー クンをスキャンし、 出力に "abcdabcaba" を書きます: %% a | ab | abc | abcd ECHO; REJECT; .|\n /* eat up any unmatched character */ (前の 3 つのルールは 4 番目のルールのアクションを共有します。 なぜなら特別な '|' ア クションが使用されているからです。) REJECT はスキャナの性能という点で特にコストのか かる機能です; もしスキャナのアクションの いずれか にでも REJECT が使われたなら、ス キャナの 全ての マッチング速度を低下させるということです。 さらに REJECT をオプショ ン -Cf や -CF と共に用いることは出来ません。 また、他の特別アクションと違い REJECT は 分岐(branch) であることに注意してください; すなわち REJECT 直後のアクションは 実行 されません。 - yymore() 次にルールとマッチしたときには、対応するトークンは、 現在の yytext の内容 と入れ換えるのではなく yytext に 追加 するようスキャナに指示します。 例えば、入力 "mega-kludge" が与えられると、以下は "mega-mega-kludge" を出力に書きます: %% mega- ECHO; yymore(); kludge ECHO; 最初の "mega-" はマッチし出力にエコーされます。 次に "kludge" がマッチしますが、直 前の "mega-" がまだ yytext の先頭に残っており、"kludge" の ECHO ルールは実際には "mage-kludge" を書きます。 yymore() の使用に関し 2 つの注意点があります。 まず、 yymore() は現在のトークンの大きさを 反映する yyleng の値の正確さに依存することであり、 yymore() 使用時には yyleng を変更しては なりません。 次に、 スキャナのアクションに yymore() があると、スキャナのマッチ速度に若干悪 影響があります。 - yyless(n) 現在のトークンから最初の n 文字を除いたものを入力ストリームに戻します。 戻した文字列はスキャナが次のマッチングをとるときに再度スキャンされます。 yytext と yyleng は適切に調整されます(例えば yyleng は n となります)。 例えば、入力 "foobar" が与えられると、以下は "foobarbar" を書きます: %% foobar ECHO; yyless(3); [a-z]+ ECHO; 引数 0 を yyless に与えると、現在の入力文字列全体が再度スキャンされます。 (例えば BEGIN を使用して)次にスキャナが入力する方法を変更していないと、無限ループとなりま す。 yyless はマクロであり、flex 入力ファイルでのみ使用可能であり、 別のソースファイルからは使 用不能であることに注意して下さい。 - unput(c) 文字 c を入力ストリームへ戻します。戻した文字は次にスキャンされる文字にな ります。 次のアクションは現在のトークンを取り上げ、 括弧内に入れて再スキャンしま す。 { int i; /* Copy yytext because unput() trashes yytext */ char *yycopy = strdup( yytext ); unput( ')' ); for ( i = yyleng - 1; i >= 0; --i ) unput( yycopy[i] ); unput( '(' ); free( yycopy ); } unput() は文字を入力ストリームの 先頭 に戻すので、文字列を戻す場合には後ろから前に 向かって戻す必要があります。 unput() 使用時の重要な潜在的な問題は、 %pointer 使用時(デフォルト)に unput() を呼び出す と、 右端の文字から開始し 1 文字ずつ左に向かって消費され、 yytext の内容が 破壊 されること です。 (上記例のように) unput() 呼び出し後も yytext の内容を保存するためには、始めに別の場 所にコピーするか、 スキャナを %array を使うように構築することです(入力のマッチ方法参照)。 最後に、 EOF を戻して入力ストリームにファイルの終りをマークするとは 出来ないことに注意して 下さい。 - input() 次の文字を入力ストリームから読みます。 次の例は C コメントを食べます: %% "/*" { register int c; for ( ; ; ) { while ( (c = input()) != '*' && c != EOF ) ; /* eat up text of comment */ if ( c == '*' ) { while ( (c = input()) == '*' ) ; if ( c == '/' ) break; /* found the end */ } if ( c == EOF ) { error( "EOF in comment" ); break; } } } (スキャナが C++ でコンパイルされたときは、このルーチンは yyinput() という名称にな り、 C++ ストリームの input と名前が衝突することを避けます。) - YY_FLUSH_BUFFER スキャナの内部バッファをフラッシュし、 次にスキャナがトークンをマッ チしようとした時 バッファを YY_INPUT にてリフィルします(生成されたスキャナで後述)。 このアクションは、 複数の入力バッファにおいて後述する より一般的な yy_flush_buffer() 関数の特別なケースです。 - yyterminate() アクションの return 文の代わりに使うことが出来ます。 yyterminate() は スキャナを終了し、"全て終了" を意味する 0 を呼び出し元関数に返します。 デフォルトで は yyterminate() はファイルの終わりに達したときにも呼ばれます。 yyterminate() はマ クロであり、定義しなおすことができます。
生成されたスキャナ
flex の出力は lex.yy.c というファイルであり、スキャンルーチン yylex() と、トークンのマッチ ングに使用する複数のテーブルと、 複数の付属ルーチンとマクロからなります。デフォルトでは、 yylex() は次のように宣言されます: int yylex() { ... various definitions and the actions in here ... } (環境が関数プロトタイプをサポートしている場合、 "int yylex( void )" となります。) この定義 は "YY_DECL" マクロを定義することにより変更できます。 例えば次のように使用することが出来ま す: #define YY_DECL float lexscan( a, b ) float a, b; これはスキャンルーチンの名前を lexscan とし、浮動小数点数を返すようにし、2 つの浮動小数点 数を引数とします。 K&R の非プロトタイプの関数宣言を使用してスキャンルーチンに対して引数を 与える場合、定義をセミコロン(;)で終了する必要があります。 yylex() は呼ばれるたび、グローバル入力ファイル yyin (デフォルトでは標準入力)からトークンを スキャンします。 ファイルの終りになる(この場合 0 を返します)か、 アクションが return 文を 実行するまで、実行を続けます。 スキャナがファイルの終りに到達すると、 yyin が新たなファイルを指さないか (新たなファイルを 指す場合はこのファイルのスキャンを続けます)、 yyrestart() が呼ばれない限り、 後続する呼び 出しは未定義です。 yyrestart() は FILE * ポインタ( YY_INPUT を設定して yyin 以外のソースを スキャンするようにした場合には nil も可です) である引数を 1 つとり、そのファイルからのス キャンのために yyin を初期化します。 本質的に、 yyin を新しい入力ファイルに割り当てること と yyrestar() を使用することとは同じです; 後者は前のバージョンの flex との互換性のために使 用可能であり、 またスキャンの途中で入力ファイルを変えることが可能です。 引数を yyin として 呼び出すことにより、現在の入力バッファを捨てることも出来ます; ただし、 YY_FLUSH_BUFFER (上 述)を使用する方が良いです。 yyrestart() は INITIAL の開始条件を変更し ない ことに注意して 下さい (後述の開始条件参照)。 あるアクション中で return 文を実行することにより yylex() がスキャンを止めた場合、スキャナ は再度呼び出し可能であり、 この場合スキャンの残りの部分から再開します。 デフォルトで(効率のため)、スキャナは単純な getc() コールではなくブロックリードを行い、 yyin から文字を読みます。 入力取得方法は YY_INPUT マクロを定義することにより制御できます。 YY_INPUT 呼び出し手順は "YY_INPUT(buf,result,max_size)" です。 このアクションは、 buf 文字 配列中に最大 max_size 文字を用意し、整数変数 result 中に読めた文字数もしくは定数 YY_NULL (Unix システムでは 0)を入れて返します。 デフォルトの YY_INPUT はグローバルファイルポインタ "yyin" から読みます。 YY_INPUT のサンプル定義です(入力ファイルの定義部に格納): %{ #define YY_INPUT(buf,result,max_size) \ { \ int c = getchar(); \ result = (c == EOF) ? YY_NULL : (buf[0] = c, 1); \ } %} この定義により、入力処理は 1 度に 1 文字ずつ行うように変更されます。 スキャナが YY_INPUT からファイルの終りを通知された場合、 スキャナは yywrap() 関数をチェッ クします。 yywrap() 関数が偽(ゼロ)を返す場合、関数は続行中であるとされ、 yyin を別の入力 ファイルを指すように設定し、スキャンを続行します。 関数が真(非ゼロ)を返す場合、スキャナは 終了し、呼び出し元に 0 を返します。 どちらの場合も開始条件は変化しないことに注意して下さ い; つまり INITIAL には戻り ません。 独自の yywrap() を設定しない場合、 %option noyywrap (この場合スキャナは yywrap() が 1 を返 したかのように動作します)を使用するか、フラグ -ll を指定してデフォルトのルーチン(常に 1 を 返します)を使用しなければなりません。 ファイルではなくメモリ中のバッファからスキャンするための 3 つのルーチンを 使用可能です: yy_scan_string(), yy_scan_bytes(), yy_scan_buffer() 。 これらに関する議論は複数の入力バッ ファの節を参照して下さい。 スキャナは、自己の ECHO 出力を yyout グローバル(デフォルトでは標準出力であり、 別の FILE ポインタに割り当てることで再定義できます)に書きます。
開始条件
flex は、条件的に有効となるルールのための機構を提供します。 パターンのプレフィックスが "<sc>" となっているルールは、 スキャナが "sc" という名前の開始条件にいる場合のみ有効です。 例えば、 <STRING>[^"]* { /* eat up the string body ... */ ... } はスキャナが "STRING" 開始条件にいる時のみ有効であり、 <INITIAL,STRING,QUOTE>\. { /* handle an escape ... */ ... } は現在の開始条件が、 "INITIAL", "STRING", "QUOTE" のいずれかの場合のみ有効です。 開始条件は、入力の定義(先頭)部において、インデントされない行で %s もしくは %x から始まり名 前が続く行において宣言されます。 前者は 内包的 開始条件を、 後者は 排他的 開始条件を、それ ぞれ宣言します。 開始条件を有効にするのは BEGIN アクションです。 次の BEGIN アクションが実 行されるまで、与えられた開始条件のルールは有効であり、 他の開始条件のルールは無効です。 開 始条件が 内包的 な場合、開始条件を持たないルールもまた有効です。 開始条件が 排他的 な場 合、 開始条件を満たすルール だけ が有効です。 同じ排他開始条件に依存するルールの組は、 flex 入力中の別のルールとは独立なスキャナを記述します。 そのため、排他開始条件を使用すれ ば、"ミニスキャナ" (別部分とは文法的に異なる部分(例えばコメント)に対するスキャナ) を簡単に 指定できます。 内包的開始条件と排他的開始条件とがまだ少し曖昧であるなら、 両者の関係を表す例を示して説明 します。以下のルールの組: %s example %% <example>foo do_something(); bar something_else(); は %x example %% <example>foo do_something(); <INITIAL,example>bar something_else(); と等価です。 <INITIAL,example> が無いと、2 番目の例における bar パターンは、開始条件が example の場合、有効となりません(すなわちマッチしません)。 <example> だけを bar につける と、 example だけにおいて有効となり、 INITIAL では有効となりません。一方、最初の例ではどち らの場合でも有効です。 なぜなら最初の例では example 開始条件は 内包的 (%s) 開始条件だから です。 特殊な開始条件指定子 <*> は全ての開始条件にマッチすることに注意して下さい。 このため、上の 例は次のようにも書けます; %x example %% <example>foo do_something(); <*>bar something_else(); デフォルトルール(マッチしなかった文字に対しては ECHO です)は開始条件中でも有効です。 これ は次のものと等価です: <*>.|\n ECHO; BEGIN(0) は、開始条件の無いルールだけが有効である、最初の状態に戻ります。 この状態は開始条 件 "INITIAL" として参照できるため、 BEGIN(INITIAL) は BEGIN(0) と等価です。 (開始条件名を 括る括弧は不要ですが、良いスタイルであるとされています。) BEGIN アクションは、ルール部の先頭のインデントされたコード中に現れても良いです。 例えば以 下の例では、 yylex() が呼ばれグローバル変数 enter_special が真の場合には、スキャナは "SPECIAL" 開始条件に入ります: int enter_special; %x SPECIAL %% if ( enter_special ) BEGIN(SPECIAL); <SPECIAL>blahblahblah ...more rules follow... 開始条件を説明するために、 "123.456" のような文字列を 2 通りの異なった解釈をするスキャナを 示します。 デフォルトではこれは、 整数 "123" とドット ('.') と整数 "456" の 3 トークンに数 えられます。 しかし、この文字列の前に "expect-floats" の文字列がある場合、 これは単一の トークンであるとされ、浮動小数点数 123.456 とされます: %{ #include <math.h> %} %s expect %% expect-floats BEGIN(expect); <expect>[0-9]+"."[0-9]+ { printf( "found a float, = %f\n", atof( yytext ) ); } <expect>\n { /* that's the end of the line, so * we need another "expect-number" * before we'll recognize any more * numbers */ BEGIN(INITIAL); } [0-9]+ { printf( "found an integer, = %d\n", atoi( yytext ) ); } "." printf( "found a dot\n" ); 次は、C のコメントを理解(して捨てる)一方で、 現在の入力行を数えるスキャナです。 %x comment %% int line_num = 1; "/*" BEGIN(comment); <comment>[^*\n]* /* eat anything that's not a '*' */ <comment>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */ <comment>\n ++line_num; <comment>"*"+"/" BEGIN(INITIAL); このスキャナは各ルールで可能な最大のテキストにマッチしようとする場合、 ちょっとした問題が 起こります。 一般的には、高速なスキャナを記述する場合、 各ルールで最大のマッチを得ようとす ることが最も成功します。 開始条件名は実際には整数値であり、格納することが出来ることに注意して下さい。 そのため、上 記例は以下のように拡張できます: %x comment foo %% int line_num = 1; int comment_caller; "/*" { comment_caller = INITIAL; BEGIN(comment); } ... <foo>"/*" { comment_caller = foo; BEGIN(comment); } <comment>[^*\n]* /* eat anything that's not a '*' */ <comment>"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */ <comment>\n ++line_num; <comment>"*"+"/" BEGIN(comment_caller); さらに、現在の開始条件を整数値であるマクロ YY_START にてアクセスできます。 例えば、上記の comment_caller への代入は次のように記述できます。 comment_caller = YY_START; flex は YYSTATE を YY_START のエイリアスとして提供します (AT&T の lex が使用しています)。 開始条件は独自の名前空間を持たないことに注意して下さい; %s や %x の宣言における名前宣言の 扱いは #define と同じです。 最後に、排他的開始条件を使用する、 展開されたエスケープシーケンスを含む(長すぎる文字列の チェックは含みません) C スタイルのクオート文字列へのマッチ方法を示します: %x str %% char string_buf[MAX_STR_CONST]; char *string_buf_ptr; \" string_buf_ptr = string_buf; BEGIN(str); <str>\" { /* saw closing quote - all done */ BEGIN(INITIAL); *string_buf_ptr = '\0'; /* return string constant token type and * value to parser */ } <str>\n { /* error - unterminated string constant */ /* generate error message */ } <str>\\[0-7]{1,3} { /* octal escape sequence */ int result; (void) sscanf( yytext + 1, "%o", &result ); if ( result > 0xff ) /* error, constant is out-of-bounds */ *string_buf_ptr++ = result; } <str>\\[0-9]+ { /* generate error - bad escape sequence; something * like '\48' or '\0777777' */ } <str>\\n *string_buf_ptr++ = '\n'; <str>\\t *string_buf_ptr++ = '\t'; <str>\\r *string_buf_ptr++ = '\r'; <str>\\b *string_buf_ptr++ = '\b'; <str>\\f *string_buf_ptr++ = '\f'; <str>\\(.|\n) *string_buf_ptr++ = yytext[1]; <str>[^\\\n\"]+ { char *yptr = yytext; while ( *yptr ) *string_buf_ptr++ = *yptr++; } 上記例のように同一の開始条件を持つ全てのルールの前に 開始条件を書かねばならないことが多い です。 flex はこれを簡単かつ綺麗にするため開始条件 スコープ を導入しました。 開始条件ス コープは次のように始まります: <SCs>{ ここで SCs は 1 つ以上の開始条件のリストです。 開始条件スコープ内では、 最初の '{' にマッ チするまでの '}' において、全てのルールは自動的に <SCs> のプレフィックスが付きます。 その ため、例えば <ESC>{ "\\n" return '\n'; "\\r" return '\r'; "\\f" return '\f'; "\\0" return '\0'; } は次のものと等価です: <ESC>"\\n" return '\n'; <ESC>"\\r" return '\r'; <ESC>"\\f" return '\f'; <ESC>"\\0" return '\0'; 開始条件スコープはネストすることが出来ます。 開始条件のスタックを制御するために 3 つのルーチンを使用可能です: void yy_push_state(int new_state) 現在の開始条件を開始条件スタックの先頭にプッシュし、 BEGIN new_state を使用したかの ように new_state に切り替えます (開始条件名は整数値でもあることを思い出して下さ い)。 void yy_pop_state() スタックの先頭をポップし、 BEGIN を使用してその開始条件に切り替えます。 int yy_top_state() スタックの内容を変更せずに、スタックの先頭を返します。 開始条件スタックは動的に大きくなり、 また組み込み時のサイズ制限はありません。 メモリを使い 切ると、プログラム実行は中止されます。 開始条件スタックを使用するためには、スキャナは %option stack ディレクティブをインクルード する必要があります (下記オプションを参照して下さい)。
複数の入力バッファ
スキャナによっては(ファイルの "include" をサポートする等) 複数の入力ストリームを扱う必要が あります。 flex スキャナでは大きなバッファリングを行うため、 スキャンコンテキストに影響さ れる YY_INPUT を単純に書き換えるだけでは次の入力がどこから読まれるのかを制御できません。 YY_INPUT が呼ばれるのはスキャナがバッファの終りに到達する時だけですので、 例えば "include" のように入力元を切り替える必要のある文をスキャンした後でも 長時間を費す場合があります。 この様な問題を解決するため、 flex は複数の入力バッファを生成して切り替える機構を提供しま す。 入力バッファは次のように生成されます: YY_BUFFER_STATE yy_create_buffer( FILE *file, int size ) これは FILE ポインタと size を取り、与えられる file に関連し size 文字を保持するに十分な バッファを生成します (疑わしい場合には size には YY_BUF_SIZE を使用して下さい)。 これ は、別のルーチン(下記参照)に渡すための YY_BUFFER_STATE ハンドルを返します。 YY_BUFFER_STATE のタイプは struct yy_buffer_state 構造体へのポインタであるため、 安全のた め YY_BUFFER_STATE 変数を ((YY_BUFFER_STATE) 0) と初期化することが出来、 スキャナではなく ソースファイルにおいて 入力バッファを正しく宣言するためにこの構造体を参照することが出来ま す。 yy_create_buffer 呼び出しにおける FILE ポインタは YY_INPUT から見える yyin の値と同じ ようにだけ使用されることに注意して下さい; YY_INPUT を再定義して yyin を使わないようにする ことにより、 yy_create_buffer に対して安全にニル FILE ポインタを渡せます。 スキャンする バッファを選択するためには次のようにします: void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer ) これはスキャナの入力バッファを切り替え、 トークンが new_buffer から来るようになります。 新 たなファイルをオープンして yyin を指すのではなく、スキャンを継続するために yywrap() から yy_switch_to_buffer() を使用することがあることに注意して下さい。 また、 yy_switch_to_buffer() または yywrap() による入力元の切り替えは開始条件を変更し ない ことに も注意して下さい。 void yy_delete_buffer( YY_BUFFER_STATE buffer ) はバッファに関連づけられたストレージの返還要求に使用します。( buffer はニルでも構いません がこの場合このルーチンは何もしません。) 現在のバッファの内容をクリアするには次のようにしま す: void yy_flush_buffer( YY_BUFFER_STATE buffer ) この関数はバッファの内容を捨てるため、 次にスキャナがこのバッファとトークンのマッチを行う 場合、 スキャナはまず YY_INPUT を使用してこのバッファをフィルします。 yy_new_buffer() は yy_create_buffer() のエイリアスであり、動的オブジェクトの生成と破壊のた めに使用する C++ の new と delete との互換性のために提供しています。 最後に YY_CURRENT_BUFFER マクロは、現在のバッファに対する YY_BUFFER_STATE ハンドルを返しま す。 この機能を使用してインクルードファイルを展開するスキャナの記述例です( <<EOF>> 機能は後述し ます): /* the "incl" state is used for picking up the name * of an include file */ %x incl %{ #define MAX_INCLUDE_DEPTH 10 YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH]; int include_stack_ptr = 0; %} %% include BEGIN(incl); [a-z]+ ECHO; [^a-z\n]*\n? ECHO; <incl>[ \t]* /* eat the whitespace */ <incl>[^ \t\n]+ { /* got the include file name */ if ( include_stack_ptr >= MAX_INCLUDE_DEPTH ) { fprintf( stderr, "Includes nested too deeply" ); exit( 1 ); } include_stack[include_stack_ptr++] = YY_CURRENT_BUFFER; yyin = fopen( yytext, "r" ); if ( ! yyin ) error( ... ); yy_switch_to_buffer( yy_create_buffer( yyin, YY_BUF_SIZE ) ); BEGIN(INITIAL); } <<EOF>> { if ( --include_stack_ptr < 0 ) { yyterminate(); } else { yy_delete_buffer( YY_CURRENT_BUFFER ); yy_switch_to_buffer( include_stack[include_stack_ptr] ); } } ファイルではなくメモリ上の文字列をスキャンするための 入力バッファを設定するための 3 つの ルーチンを使用可能です。 いずれも文字列をスキャンする新しい入力バッファを生成し、対応する YY_BUFFER_STATE ハンドル(終了時には yy_delete_buffer() にて消去します)を返します。新しい バッファに切り替える時には yy_switch_to_buffer() を使用し、次の yylex() の呼び出し時にはこ の文字列をスキャン開始します。 yy_scan_string(const char *str) NUL ターミネートされた文字列をスキャンします。 yy_scan_bytes(const char *bytes, int len) len バイト (NUL が含まれるかも知れません)を位置 bytes からスキャンします。 どちらの関数も文字列もしくはバイト列の コピー を生成してからスキャンします。( yylex() はス キャンするバッファの内容を変更するため、これが望ましいのです。) コピーを避けるためには次の ようにします: yy_scan_buffer(char *base, yy_size_t size) バッファ内で base から size バイトの長さをスキャンします。最後の 2 バイトは YY_END_OF_BUFFER_CHAR (ASCII NUL) である 必要があります。 これらの最後の 2 バイトは スキャンされません; そのためスキャンの内容は base[0] から base[size-2] までで両端を 含みます。 この様になるように base を設定しなかった場合(つまり最後の 2 つの YY_END_OF_BUFFER_CHAR バイトを忘れた場合)、 yy_scan_buffer() は新しいバッファを生成 するのではなくニルポインタを返します。 型 yy_size_t は整数型であり、 バッファの大きさを反映する整数式をこの型にキャストす ることが出来ます。
ファイルの終りのルール
特別ルール "<<EOF>>" は、 ファイルの終了時もしくは yywrap() が非ゼロ(すなわち処理するファ イルが無いことを表す)の時に 行われるべきアクションを表します。 アクションは以下の 4 つのう ちのいずれかで終る必要があります。 - yyin に新しいファイルを割り当てる(前のバージョンの flex では、 割り当て後に特別なア クション YY_NEW_FILE を呼び出す必要がありました; 今では不要です。); - return 文を実行する; - 特別な yyterminate() アクションを実行する; - yy_switch_to_buffer() を使用して新たなバッファに切り替える (上記例で示した通り)。 <<EOF>> ルールを他のパターンと共に使用してはなりません; 他のパターンは開始条件のリストとも にだけ満たされるからです。 満たされない <<EOF>> ルールが与えられた場合、 <<EOF>> アクショ ンをまだ持っていない 全ての 開始条件に適用されます。 <<EOF>> ルールを最初の開始条件だけに 指定するためには次のようにして下さい。 <INITIAL><<EOF>> これらのルールは閉じていないコメントを捕まえる場合等に便利です。 例えば: %x quote %% ...other rules for dealing with quotes... <quote><<EOF>> { error( "unterminated quote" ); yyterminate(); } <<EOF>> { if ( *++filelist ) yyin = fopen( *filelist, "r" ); else yyterminate(); }
雑多なマクロ
マクロ YY_USER_ACTION にはマッチルールアクションに先だって常に行うアクションを定義できま す。 例えば、yytext を小文字に変換するルーチンを呼ぶように #define 出来ます。 YY_USER_ACTION 起動時には、変数 yy_act はマッチしたルールの番号を与えます(ルールは 1 番か ら数えます)。 各ルールがマッチする頻度を知りたい場合を想像して下さい。 以下に仕掛けを示し ます: #define YY_USER_ACTION ++ctr[yy_act] ここで ctr は配列であり、それぞれのルールがマッチした回数を計数します。 マクロ YY_NUM_RULES はルールの総数を表すため( -s を使った時でさえデフォルトルールを含みます)、 正 しい ctr の宣言は次のようになります: int ctr[YY_NUM_RULES]; マクロ YY_USER_INIT には最初のスキャンの前に常に行うアクションを再定義できます (スキャナの 内部初期化の前に行われます)。 例えばデータ表を読み込んだり、ログファイルをオープンするため に使用できます。 マクロ yy_set_interactive(is_interactive) は現在のバッファが 対話的 と見倣されているか否か を制御するために使用します。 対話的なバッファの処理は遅くなりますが、 スキャナの入力元が対 話的でありバッファをフィルするのを待つことに起因する 問題を避けるためには指定しなければな りません(以下の -I %option interactive フラグに関する議論を参照して下さい)。 マクロ起動時 に非ゼロを指定するとバッファは対話的になり、 ゼロを指定すると非対話的になります。 このマク ロの使用は %option interactive , %option always-interactive , %option never-interactive に 優先します(下記オプションを参照して下さい)。 バッファをスキャンして対話的である(もしくはで ない)と判断される前に、 yy_set_interactive() を起動して下さい。 マクロ yy_set_bol(at_bol) は現在のバッファにおける次のトークンに対するマッチのためのスキャ ンが 行頭から始まるか否かを制御します。 非ゼロのマクロ引数は、'^' が付いたルールを有効にし ますが、 ゼロのマクロ引数は '^' が付いたルールを無効にします。 現在のバッファからスキャンされた次のトークンが有効な '^' ルールを持つ時、 マクロ YY_AT_BOL() は真を返します。 そうでない場合は偽を返します。 生成されたスキャナでは、全てのアクションは大きな一つの switch 文に 集められ、 YY_BREAK で 分けられています。 YY_BREAK は再定義可能です。デフォルトではそれぞれのルールのアクションを 分けるための単なる "break" です。 YY_BREAK を再定義することにより、例えば C++ ユーザが #define YY_BREAK を何もしないように定義し (ただし全てのルールが "break" か "return" で終る ように 注意しなければなりません!)、 ルールのアクションが "return" で終ることにより YY_BREAK がアクセスできないことに起因する、 到達できない文があるという警告を避けることが出 来ます。
ユーザが使用可能な値
この節ではユーザがルールのアクション部分で使用可能な値をまとめます。 - char *yytext 現トークンのテキストを保持しています。内容を変更しても構いませんが、 その長さを伸ばしてはいけません(終りに文字を追加してはいけない)。 スキャナの記述の最初の部分に特別な指示である %array が書かれているとき、 yytext は char yytext[YYLMAX] と定義されます。 YYLMAX はマクロで、デフォルトの値 (多くの場 合8KB) を変更したい場合には 最初の部分で再定義可能です。 %array を使うといくらか遅 いスキャナになりますが、 yytext の値は input() と unput() の呼び出しでも破壊されな くなります。 yytext が文字ポインタである場合、 これらの関数呼び出しは yytext を破壊 する可能性があります。 %array と対称な指定 %pointer がデフォルトです。 C++ のスキャナクラスを生成する (オプション -+ ) ときには %array は使えません。 - int yyleng 現トークンの長さを保持しています。 - FILE *yyin はデフォルトで flex が読むファイルです。再定義することは可能ですが、ス キャンを 始める前か EOF に到達した後でのみ再定義は意味を持ちます。 スキャンの途中で 変更すると予想外の結果をもたらします。 というのも flex は入力をバッファリングしてい るからです; そのような場合には、直接再定義せず yyrestart() を使って下さい。 ファイ ルの終わりでスキャンが終了した場合には yyin を新しい入力ファイルに割り当て、 再びス キャナを呼び出してスキャンを続けることが出来ます。 - void yyrestart( FILE *new_file ) を呼ぶことで yyin が新しい入力ファイルを指すように 出来ます。新しいファイルへの変更は すぐに行われます (それまでにバッファに読み込まれ ていた入力は失われます)。 yyin を引数として yyrestart() を呼ぶと、現在の入力バッ ファを捨てて同じ入力ファイルを スキャンし続けることに注意して下さい。 - FILE *yyout は ECHO アクションが行われる対象のファイルです。 ユーザが再割当すること が出来ます。 - YY_CURRENT_BUFFER カレントバッファの YY_BUFFER_STATE ハンドルを返します。 - YY_START 現在の開始条件に対応する整数値を返します。 続いてこの値を BEGIN と共に使う ことで、スキャナをその開始条件へ戻すことが出来ます。
YACC とのインタフェース
flex の主な使用方法の一つは、 yacc パーサジェネレータと共に使用することです。 yacc パーサ は yylex() と言う名前のルーチンを呼び、次の入力トークンを見付けるものとしています。 この ルーチンは、次のトークンの型を返し、 関連する値をグローバルの yylval に格納するものとされ ています。 flex を yacc と共に使うには、 yacc に -d オプションを指定して、 yacc の入力に現 れる全ての %tokens の定義を含む y.tab.h ファイルを生成させます。 このファイルは flex ス キャナにインクルードされます。 例えばトークンの一つが "TOK_NUMBER" である場合、 スキャナの 一部分は次のようになっています: %{ #include "y.tab.h" %} %% [0-9]+ yylval = atoi( yytext ); return TOK_NUMBER;
オプション
flex には以下のようなオプションがあります: -b バックアップ情報を lex.backup に出力します。 このファイルには、スキャナのバックアッ プ(backing-up)を必要とする状態と それに対応する入力文字の一覧がリストされます。 ルールを追加することでバックアップ状態を取り除くこと ができます。バックアップ状態が 全て 取り除かれ、 -Cf または -CF を指定すると、生成されたスキャナの実行速度が向上し ます( -p フラグを見て下さい)。 スキャナをぎりぎりまで最適化しようとしてるユーザのみ が このオプションに関係あります。 (後述の性能関連の節を見て下さい。) -c 何もしません。POSIX 互換のために用意されています。 -d 生成されたスキャナが デバッグ モードで実行されます。 yy_flex_debug が非ゼロの場 合(デフォルト)、 パターンが認識されるたびに、スキャナは次のようなメッセージを 標準 エラー出力 へ出力します。 --accepting rule at line 53 ("the matched text") 行番号はスキャナを定義しているファイル (flexに与えられたファイル) でのルールの位置 です。 スキャナがバックアップしたとき、デフォルトルールを受け入れたとき、 入力バッ ファの最後に到達したとき (あるいは、NULに到達したとき; スキャナには、この二つの区別 はつきません) 、ファイルの最後に到達した ときにもメッセージが出力されます。 -f 高速なスキャナ を指定します。 テーブル圧縮は行われず、標準入出力をバイパスします。 その結果生成されるスキャナは大きくなりますが、高速なものになります。 このオプション は -Cfr と同等です (以下を参照)。 -h flex のオプションの要約からなる "ヘルプ" を 標準出力 に書き出し終了します。 -? と --help とは -h と同じです。 -i 大文字小文字を区別しない スキャナを生成します。 flex の入力パターンに与えられる文字 が大文字であるか小文字であるかは区別されず、 スキャナに入力される文字列は大文字小文 字に関係なくマッチします。 マッチしたテキスト yytext では入力時の大文字小文字が保存 されます (大文字を小文字に変換したりしません)。 -l AT&T の lex の実装に対して最大限の互換性を持たせます。これは 完全な 互換性を意味し ません。 このオプションを使用すると性能に大きな影響があります。 このオプションは、 -+, -f, -F, -Cf, -CF と同時に使用できません。詳しくは、 後述の "Lex および POSIX と の非互換性" の節を御覧下さい。 またこのオプションを使用すると、 YY_FLEX_LEX_COMPAT が生成されたスキャナの名前に #define されます。 -n 何もしません。POSIX 互換のためにだけ用意されたオプションです。 -p 性能情報を標準エラー出力に出力します。 flex 入力ファイルの記述のうち、 生成されるス キャナの性能低下の深刻な原因となる部分について、 コメントされます。 オプションを2回 指定すると、より細かな性能低下についても コメントが出力されます。 REJECT ・ %option yylineno ・可変長右文脈(欠陥/バグの節で後述)は多大なる性能への悪 影響があります; yymore() の使用・ ^ オペレータ・ -I フラグは小さな性能の悪影響があ ります。 -s デフォルトルール (マッチしないスキャナの入力を 標準出力 に出力する) が抑制されま す。ルールにマッチしない入力が表れたとき、スキャナは エラーで異常終了します。 ス キャナのルールの組に抜けが無いかを確認する場合に有効です。 -t lex.yy.c ではなく、標準出力にスキャナを書き出します。 -v 生成するスキャナの特徴の要約を 標準エラー出力 に出力するように flex に指示します。 ほとんどの特徴は通常の flex ユーザには意味がありませんが、最初の行は flex のバー ジョンを表示し( -V で表示されるもと同じです)、次の行はデフォルトを含むスキャナ生成 時のフラグです。 -w 警告メッセージを抑制します。 -B 対話的 なスキャナ (以下の -I の項を参照) ではなく バッチ的 なスキャナを生成するよう flex に指示します。 通常 -B を使用するのは、スキャナを対話的に使用しないことが 分 かっている 時であり、 少しでも 性能を追求したい時です。 より大きい性能を追求する場 合には、 -Cf もしくは -CF オプションを使用すべきです(後述)。 -B を自動的に設定しま す。 -F 高速な スキャナテーブルの表現を使う(標準入出力はバイパスする)ことを指定します。 こ の表現は、完全テーブル表現 (-f) とほぼ同じぐらい高速で、 ある種のパターンに対しては かなり小さく (ある種に対しては大きく) なります。 通常、次のように、パターンの組が "keywords" とその対応 および "identifier" ルールからなる場合: "case" return TOK_CASE; "switch" return TOK_SWITCH; ... "default" return TOK_DEFAULT; [a-z]+ return TOK_ID; この場合、完全テーブル表現を使用する方が良いです。 もし "identifier" ルールからのみ 表現され、 キーワードを検知するためにハッシュ表等を使用する場合は、 -F を使用する方 が良いです。 このオプションは -CFr と等価です (以下を参照)。 これは -+ オプションとは同時に指定 できません。 -I flex に 対話的 なスキャナを生成するように指示します。 対話的なスキャナは、 先読みす ることによりマッチするトークンが完全に決まる場合のみ先読みします。 現在のトークンが 既に明らかな場合でも常に先読みする方法は、 必要時のみ先読みする方法より少し速いで す。 しかし、常に先読みする方法では対話性能に著しく悪影響があります; 例えばユーザが 改行を入力した場合、 別の トークンを入力するまでそれは改行として認識されません。 大 概の場合、次の行全体を入力することになります。 flex のスキャナのデフォルトは 対話的 であり、例外は -Cf や -CF といったテーブル圧縮 オプション(後述)使用時です。 高性能追求時にはこれらのオプションを使用しているべきで すので、 これらのオプションを使用していない場合には、 flex は実行時性能を少し犠牲に して直観的な対話的な振舞いを取っているものとします。 -I オプションを -Cf や -CF と 共に 使用できない ことにも注意して下さい。 実際はこのオプションは不要です; 許される 場合、デフォルトで有効になっています。 isatty() がスキャナの入力に対して偽を返す場合、 -I が指定されていた場合でも、flex はバッチモードへ戻ります。 なにがあっても対話モードを強制するには、 %option always- interactive (後述のオプションを参照) を使用します。 スキャナを対話的で 無い ように強制するには -B (先述)を使用します。 -L flex に #line ディレクティブを lex.yy.c 中に生成しないように指示します。 デフォルト ではこの #line ディレクティブを生成するので、 アクションにおけるエラーメッセージ は、オリジナルの flex 入力ファイル( エラーが入力ファイルのコードに起因する場合)もし くは ファイル lex.yy.c ( flex の誤り -- 以下の電子メールアドレスに報告して下さい) における正しい位置を与えます。 -T flex を トレース モードで実行します。 入力の形式とその結果として出力される非決定 性/決定性有限 オートマトンに関して 標準エラー出力 に多量のメッセージを出力します。 このオプションは主に flex をメンテナンスするために使われます。 -V バージョン番号を 標準出力 に出力して終了します。 --version は -V と同じです。 -7 7 ビットのスキャナを生成します。 すなわち、入力に 7 ビットの文字のみを使用すること を意味します。 -7 を指定する利点は、 -8 オプション(後述)を指定して生成するテーブル の半分まで小さくなりうることです。 欠点は、入力に 8 ビット文字が含まれている時に、 スキャナがハングもしくはクラッシュすることです。 しかしながら、 -Cf や -CF といったテーブル圧縮オプション使用時にはテーブル圧縮の効 果は少なく、 移植性が著しく低下することに注意して下さい。 flex のデフォルトの動作で は、 -Cf や -CF, を指定しない限り 8 ビットスキャナを生成します。 指定時には、 あな たのサイトが常に 8 ビットスキャナを生成するように (USA 以外のサイトでは良くありま す)していない場合には、 7 ビットスキャナを生成します。 flex が 7 ビットもしくは 8 ビットのいずれのスキャナを生成するのかを 知りたい場合には、上述の -v の出力のフラグ の要約を調べて下さい。 -Cfe もしくは -CFe (これらのテーブル圧縮オプションおよび等価クラスは後述) を使用し ても、flex はデフォルトで 8 ビットスキャナを生成することに 注意して下さい。 なぜな ら、完全な 8 ビットテーブルは 7 ビットテーブルと比べても たいして高価にはならないか らです。 -8 8 ビットのスキャナを生成するように flex に指示します。すなわち 8 ビット文字を解釈し ます。 圧縮オプション -Cf と -CF 使用時にのみ必要です。 なぜなら flex はデフォルト では 8 ビットスキャナを生成するからです。 flex のデフォルト動作と 7 ビットおよび 8 ビットスキャナの トレードオフに関して は、上記 -7 の議論を見て下さい。 -+ C++ のスキャナクラスを生成します。 詳しくは C++ スキャナの生成で後述します。 -C[aefFmr] テーブル圧縮の程度と、 より一般的には小さいスキャナと高速なスキャナとのトレードオフ を指定します。 -Ca ("アライン") 生成されるスキャナのテーブルは、 メモリアクセスおよび計算のために アラインされるため、より大きなものになります。 RISC アーキテクチャではロングワード のフェッチおよび操作は ショートワードといったより小さな大きさのものに対するものより 効率的です。 場合によってはスキャナのテーブルサイズが通常の 2倍になることもありま す。 -Ce 等価クラス (同一の字句属性を持つ文字セット)を構築します (例えば、 flex 入力中に 数字が現れるのが文字クラス "[0-9]" のみの場合、 数字 '0', '1', ..., '9' は全て同じ 等価クラスになります)。 多くの場合、等価クラスを用いることで最終的なテーブル/ オブ ジェクトファイルのサイズを劇的(平均して 1/2-1/5)に減らすことが出来ます。 また、その 際の性能コストは非常に低く抑えられます ( 1文字スキャンするごとに 1回の配列検索を行 うだけです)。 -Cf 完全(full) スキャナテーブルを生成することを指示します - flex は、別の状態に関す る類似した遷移関数をうまく利用するという、 テーブル圧縮手法を用いません。 -CF 別の高速スキャナ表現( -F フラグにて記述)を用いることを指定します。 このオプショ ンは -+ と同時に使用できません。 -Cm flex に メタ等価クラス を構築するよう指示します。 メタ等価クラスは一緒に使われ ることの多い等価クラス (等価クラスが使われていないときには文字群) の集合です。 圧縮 テーブルを使っているとき、 メタ等価クラスは多くの場合にかなりの効果的をもたらします が、 やや性能に影響します (1-2 回の条件テストと 1 回の配列検索がスキャンした文字ご とに行われます)。 -Cr 生成されたスキャナは入力に対しては標準入出力ライブラリ(標準入出力)を バイパス します。 スキャナは、 fread() や getc() ではなく、 read() システムコールを使用しま す。 性能改善結果はシステムに依存します。 オプション -Cf もしくは -CF を使用してい ない場合には、 一般にこのオプションは性能をあまり改善しません。 -Cr を指定する と、例えばスキャナを設定する前に標準入出力を使用して yyin を読み取る等した場合奇妙 な動作となり得ます (標準入出力の入力バッファに以前読み込んだものを、スキャナは読め ません)。 -Cr は YY_INPUT を定義した場合意味がありません (前述の生成されたスキャナを参照)。 スキャナの呼出に先だって標準入力を使って yyin から読みだしているときには、予想外の 振る舞いをすることがあります。 -C のみを指定したときには、スキャナはテーブル圧縮は行いますが、 等価クラスもメタ等 価クラスも使いません。 オプション -Cf と -CF はオプション -Cm を同時に指定しても意味をなしません - なぜな ら、テーブル圧縮が行われないときメタ等価クラス は現れないからです。 それ以外のオプ ションは自由に組み合わせることが出来ます。 デフォルトの設定は -Cem です。このとき flex は等価クラスとメタ等価クラスを生成しま す。 この設定は最も高いテーブル圧縮を行います。 テーブルサイズの大きさと実行の高速 性はトレードオフの関係にあり、 一般に 遅いが 小さい -Cem -Cm -Ce -C -C{f,F}e -C{f,F} -C{f,F}a 速いが 大きい となります。 小さいテーブルのスキャナは通常生成もコンパイルも高速であるため、 通常 の開発時は最大の圧縮を行うでしょう。 製品のスキャナでは、 -Cfe が速度と大きさの良いバランスです。 -ooutput lex.yy.c ではなくファイル output にスキャナを書くように flex に指示します。 -o と -t オプションを組み合わせると、 スキャナは 標準出力 に書かれますが、 #line ディレク ティブ( -L にて上述)はファイル output を参照します。 -Pprefix flex の使うデフォルトのプレフィックス yy の代わりに prefix を使います。これはグロー バル変数とファイル名に影響します。 例えば -Pfoo とすると、 yytext の名前は footext となります。 またデフォルトの出力ファイル名を lex.yy.c から lex.foo.c に変えます。 影響を受ける名前の一覧です: yy_create_buffer yy_delete_buffer yy_flex_debug yy_init_buffer yy_flush_buffer yy_load_buffer_state yy_switch_to_buffer yyin yyleng yylex yylineno yyout yyrestart yytext yywrap (C++ スキャナ使用時には yywrap と yyFlexLexer だけが影響を受けます。) スキャナの中 では、グローバル変数および関数を どちらの名前ででも参照できます; 外部的には修正した 名前のみ持ちます。 このオプションを使用することにより、複数の flex プログラムを同一の実行形式に容易に リンクすることが出来ます。 しかし、このオプションは yywrap() の名前をも変えますの で、 独自の(適切に名前を付けた)ルーチンをスキャナのために用意するか、 %option noyywrap を使用して -ll とリンクする 必要があります。 どれもデフォルトでは提供され ません。 -Sskeleton_file flex がスキャナを構築するのに使うデフォルトの スケルトンファイルに優先します。 flex のメンテナンスや開発をする場合以外、このオプションは必要ありません。 flex は、flex のコマンドラインではなく、 スキャナ仕様記述中からオプションを制御する機構を 提供します。 これはスキャナの最初の部分に %option ディレクティブを含めることで実現できま す。 単一の %option ディレクティブにおいて複数のオプションを指定でき、 また複数のディレク ティブを flex 入力ファイルの最初の部分に置くことが出来ます。 ほとんどのオプションが単純な名前であり、 オプションとして前に "no" という語(空白をはさみま せん)を付けて 意味を反転できます。 数値は flex のフラグやその反転と等価です。 7bit -7 オプション 8bit -8 オプション align -Ca オプション backup -b オプション batch -B オプション c++ -+ オプション caseful または case-sensitive -i オプションの逆(デフォルト) case-insensitive または caseless -i オプション debug -d オプション default -s オプションの逆 ecs -Ce オプション fast -F オプション full -f オプション interactive -I オプション lex-compat -l オプション meta-ecs -Cm オプション perf-report -p オプション read -Cr オプション stdout -t オプション verbose -v オプション warn -w オプションの逆 (-w オプションには "%option nowarn" を使用して下さい) array "%array" と等価 pointer "%pointer" と等価(デフォルト) %option には、他では利用できない機能を提供するものもあります: always-interactive 入力を常に "対話的" に扱うスキャナを生成するように flex に指示します。 通常、新たな 入力ファイル毎にスキャナは isatty() を呼び出し、スキャナの入力元が対話的であり 1 度 に 1 文字ずつ読むべきか どうか判定しようとします。 一方このオプションを使用するとこ の様な呼び出しは行いません。 main スキャナに対し、 yylex() を呼び出すだけのデフォルトの main() プログラムを提供するよ うに指示します。 このオプションは noyywrap (後述)も暗黙的に指示します。 never-interactive 入力を "対話的" とはしないスキャナを生成するように flex に指示します (これもまた isatty() を呼び出しません)。 これは always-interactive の逆です。 stack 開始条件スタックの使用を有効にします(前述の開始条件を参照)。 stdinit 設定されている場合 (すなわち %option stdinit) yyin および yyout を、 デフォルトの nil ではなく、 標準入力 と 標準出力 に設定します。 既存の lex プログラムには、 ANSI C 互換ではないものの、この動作に依存しているものがあります。 ANSI C では 標準入力 と 標準出力 がコンパイル時の定数である必要はありません。 yylineno 入力から読み取った現在の行番号をグローバル変数 yylineno に保持するスキャナを生成す るように、 flex に指示します。 このオプションは %option lex-compat から暗黙的に指定 されます。 yywrap セットされていない場合 (すなわち %option noyywrap) 、スキャナはファイルの終りに際し yywrap() を呼ばず単にスキャンすべきファイルがもう無いものとするようになります( ユー ザが yyin を新しいファイルを指すようにし、再度 yylex() を呼び出すまでです)。 flex はルールアクションをスキャンし、 REJECT と yymore() の機能が使われているかどうかを調 べます。 reject と yymore のオプションを使用すると、 オプションで指定した通りにこの判定に 優先します。 オプションの指定は、セットして機能を使用していることを示す(例えば %option reject) 、もしくはアンセットして機能を使用していないことを示す(例えば %option noyymore) も のとします。 次のオプションは文字列の値を取り、'=' で区切ります: %option outfile="ABC" これは -oABC と同じであり、 %option prefix="XYZ" は -PXYZ と同じです。 最後に、 %option yyclass="foo" は C++ スキャナ生成時のみ有効( -+ オプション)です。これは flex に対して、 foo が yyFlexLexer のサブクラスであることを知らせますので、 flex はアクションを yyFlexLexer::yylex() ではなく foo::yylex() のメンバ関数とします。 また、( yyFlexLexer::LexerError() を起動することにより)呼び出すと実行時エラーを除去する yyFlexLexer::yylex() メンバ関数を生成します。 詳細は後述の C++ スキャナの生成を見て下さ い。 生成されたスキャナから不要なルーチンを除きたい lint 純正主義者のために 多くのオプションが 用意されています。 以下をアンセットすると(例えば %option nounput )、対応するルーチンは生成 されるスキャナから除かれます: input, unput yy_push_state, yy_pop_state, yy_top_state yy_scan_buffer, yy_scan_bytes, yy_scan_string ( yy_push_state() 等は %option stack を使用しない場合には現れません)。
性能関連
flex の主なデザインゴールは高性能なスキャナを生成することです。 多くのルールセットを良く扱 うことで最適化されます。 既に概説した -C オプション使用によるテーブル圧縮に起因する速度へ の影響の他に、 性能を悪化させる多くのオプション/アクションがあります。 それらを高価なもの から安価なものへと並べます: REJECT %option yylineno 自由長の右文脈(trailing context) バックアップが必要なパターンの組 %array %option interactive %option always-interactive '^' 行頭オペレータ yymore() 最初の 3 つは非常に高価であり、最後の 2 つは非常に安価です。 unput() は潜在的に非常に大き な仕事をするルーチン呼び出しとして実装されているのに対し、 yyless() は非常に安価なマクロで す; ですからスキャンした余分なテキストを戻すだけの場合には yyless() を使って下さい。 性能が重要な場合には、出来うる限りの努力でもって REJECT を避けて下さい。 これは特に高価な オプションです。 バックアップを取り除くと、乱雑になり、 ひどく苦労して複雑なスキャナを作ることになります。 実際的には -b フラグを指定して lex.backup ファイルを生成することから始めます。例えば、入力 %% foo return TOK_KEYWORD; foobar return TOK_KEYWORD; に対しては、ファイルは次のようになります: State #6 is non-accepting - associated rule line numbers: 2 3 out-transitions: [ o ] jam-transitions: EOF [ \001-n p-\177 ] State #8 is non-accepting - associated rule line numbers: 3 out-transitions: [ a ] jam-transitions: EOF [ \001-` b-\177 ] State #9 is non-accepting - associated rule line numbers: 3 out-transitions: [ r ] jam-transitions: EOF [ \001-q s-\177 ] Compressed tables always back up. 最初の数行は、 'o' に遷移できるが他の文字には遷移できない状態があり、 その状態では現在ス キャンされたテキストは他のルールにはマッチしないことを 表します。 この状態が発生したのは、 入力ファイルの行 2, 3 のルールにマッチしようとした時です。 スキャナがこの様な状態にあり 'o' 以外の文字を読んだ場合には、 マッチするルールを探すためのバックアップが必要となりま す。 少し考えれば、これは "fo" を見た時にある状態に違いないことが分かるでしょう。 この様な 時、'o' 以外のものが現れると、 スキャナは、単に 'f' にマッチする(デフォルトルール)ところま で 戻り(バックアップし)ます。 状態 #8 に関係するコメントは、 "foob" がスキャンされた時に問題があることを表しています。 実際、'a' 以外の文字に出会うと、スキャナは "foo" を受理するところまで戻ります。 同様に状態 #9 に関係するコメントは、 "fooba" がスキャンされ 'r' が続かない場合に関係します。 最後のコメントが通知するのは、 -Cf や -CF を使っているのでなければ バックアップを取り除こ うと努力することは無意味であることです。 なぜなら、圧縮されたスキャナに対してそのようなこ とをしても、 性能上の利益は無いからです。 バックアップを取り除くためには "エラー" ルールを追加します: %% foo return TOK_KEYWORD; foobar return TOK_KEYWORD; fooba | foob | fo { /* false alarm, not really a keyword */ return TOK_ID; } キーワードのリストからバックアップを取り除くには、"全てを捕まえる" ルールを使用することが 出来ます: %% foo return TOK_KEYWORD; foobar return TOK_KEYWORD; [a-z]+ return TOK_ID; 通常、適切な時にはこれは一番良い解決策です。 バックアップメッセージはカスケードすることが多いです。 複雑なルールの組では、数百ものメッ セージを得るのは普通のことです。 しかし、これを解析すれば、バックアップを除去するためには 大抵の場合数ダースのルールにだけ関係あることが分かるでしょう (しかし、間違えることが多 く、誤ったルールが偶然有効なトークンにマッチし得ます。 将来の flex の機能では、 自動的に バックアップを除去するルールを追加するようになるかも知れません)。 バックアップを除去することにより利益があるのは、 全ての バックアップを除去した時だけという ことを覚えておくことは重要です。 たった一つを残しても何も得ることが出来ません。 可変長の 右文脈 (左部分と右部分のいずれかもしくは両方が可変長)は REJECT とほぼ同じだけ の(すなわち相当の)性能劣化となります。 そのため次のようなルール: %% mouse|rat/(cat|dog) run(); は次のように書くか: %% mouse/cat|dog run(); rat/cat|dog run(); 次のように書いた方が良いです: %% mouse|rat/cat run(); mouse|rat/dog run(); 特別な '|' アクションは助けにはなり ません し、かえって状況を悪くします (後述の欠陥/バグを 参照)。 スキャナの性能を向上させるための余地(実現は最も容易)は、 マッチするトークンが長ければス キャナが高速になることにあります。 長いトークンではほとんどの入力処理は(短い)内部ループで 処理され、 アクションのためにスキャナ環境を設定する追加の仕事(例えば yytext) をほとんどし ないからです。 C コメントのスキャナを思い出しましょう: %x comment %% int line_num = 1; "/*" BEGIN(comment); <comment>[^*\n]* <comment>"*"+[^*/\n]* <comment>\n ++line_num; <comment>"*"+"/" BEGIN(INITIAL); 次のように書くと高速になります: %x comment %% int line_num = 1; "/*" BEGIN(comment); <comment>[^*\n]* <comment>[^*\n]*\n ++line_num; <comment>"*"+[^*/\n]* <comment>"*"+[^*/\n]*\n ++line_num; <comment>"*"+"/" BEGIN(INITIAL); 今度は、改行毎に別のアクションの処理を行うのではなく、 改行認識はルール間で "分散" され、 可能な限り長いテキストにマッチするようになっています。 ルールの 追加 はスキャナを遅く しま せん! スキャナの速度は、ルール数とも、 オペレータ '*' や '|' といったものに基づくルールの 複雑さ (この節の始めで扱いました)とも独立です。 最後の高速化の例です: 1 行に 1 つずつであり別の文字は付かないような、 識別子とキーワードを 全てファイルからスキャンすることを考えます。 最初は次のようになるでしょう: %% asm | auto | break | ... etc ... volatile | while /* it's a keyword */ .|\n /* it's not a keyword */ 後戻りを避けるために全てを捕まえるルールを導入します: %% asm | auto | break | ... etc ... volatile | while /* it's a keyword */ [a-z]+ | .|\n /* it's not a keyword */ 1 行に正確に 1 語だけあることが保証されている場合、 改行の認識を別のトークンと併せること で、 マッチの総数を半分に減らすことが出来ます: %% asm\n | auto\n | break\n | ... etc ... volatile\n | while\n /* it's a keyword */ [a-z]+\n | .|\n /* it's not a keyword */ ここで、再度バックアップをスキャナに組み込んだことに 気を付けなければなりません。 実際 我々は 入力ストリームはレターと改行だけであることを知っていますが、 flex はこれが分からな いため、 トークン "auto" などをスキャンした次の文字が改行でもレターでもない場合には バック アップが必要であると考えます。 以前は "auto" ルールに適合しそれで終りでしたが、 今は "auto" ルールは無く、"auto\n" ルールだけがあります。 バックアップの可能性を除去するために は、 最後の改行以外のルールを二重化するか、 そのような入力に出くわさないので分類は不要と分 かっているため、 改行を導入しないもう一つの全てを捕まえるルールを導入することが出来ます: %% asm\n | auto\n | break\n | ... etc ... volatile\n | while\n /* it's a keyword */ [a-z]+\n | [a-z]+ | .|\n /* it's not a keyword */ -Cf を付けてコンパイルすると、実際問題上 flex で得られるほぼ最速になります。 最後の注意事項: flex は NUL にマッチする時には遅く、トークンが複数の NUL を含む時には特に 遅いです。 テキストがしばしば NUL を含むものと予想される場合には、テキストの 短い 部分と マッチするようにルールを書くべきです。 もう一つの性能に関する最終注意事項: 入力のマッチ方法の節で既に示したように、 大きなトーク ンを納めるために yytext のサイズを動的に変更すると処理が遅くなります。 なぜなら、(巨大 な)トークンを再度先頭からスキャンしなおさねばならないからです。 性能が重要な場合、 テキス トの "大きな" 部分にマッチさせるべきですが "巨大な" 部分にマッチさせる べきではありませ ん。 両者の堺目は 8K 文字/トークンです。
C++ スキャナの生成
flex は 2 通りの C++ スキャナ生成方法を提供します。 最初の方法は flex が生成したスキャナを 単に C コンパイラではなく C++ コンパイラで コンパイルするというものです。 この場合コンパイ ルエラーには出会わないはずです (見付けた場合には作者の節で後述する電子メールアドレスに報告 して下さい)。 この場合ルールにおいて C コードではなく C++ コードを書くことが出来ます。 ス キャナのデフォルトの入力元は yyin のままであり、 デフォルトのエコー先は yyout のままである ことに注意して下さい。 どちらも FILE * 変数のままであり、C++ streams ではないです。 flex に C++ スキャナクラスを生成させることも出来ます。 -+ オプションを指定する(もしくは等 価的に %option c++ を使う)とこのように実行され、 flex の実行形式名が '+' で終っている場合 には自動的に指定されます。 このオプションを指定すると flex が生成するスキャナのデフォルト はファイル lex.yy.cc となり lex.yy.c ではありません。 生成されたスキャナは 2 つの C++ クラ スとのインタフェースを定義するヘッダファイル FlexLexer.h をインクルードします。 最初のクラス FlexLexer は一般的なスキャナクラスを定義する抽象基盤クラスを提供します。 以下 のメンバ関数を提供します: const char* YYText() 最後にマッチしたテキストを返します。 yytext と等価です。 int YYLeng() 最後にマッチしたトークンの長さを返します。 yyleng と等価です。 int lineno() const 現在の入力の行番号( %option yylineno 参照)もしくは %option yylineno を使用していな い場合には 1 を返します。 void set_debug( int flag ) スキャナのデバッグフラグをセットします。 yy_flex_debug に代入するのと同じです(オプ ションの節で前述)。 スキャナ構築時に %option debug を使用してデバッグ情報を組み込む 必要があることに注意して下さい。 int debug() const 現在のデバッグフラグの設定を返します。 また次のものと等価なメンバ関数も提供されます yy_switch_to_buffer(), yy_create_buffer() (最 初の引数は istream* オブジェクトポインタであり FILE* ではありません), yy_flush_buffer(), yy_delete_buffer(), yyrestart() (これもまた最初の引数は istream* オブジェクトポインタで す)。 2 番目のクラスは FlexLexer.h で定義される yyFlexLexer であり、 FlexLexer から導出したもの です。 以下の追加のメンバ関数を定義します: yyFlexLexer( istream* arg_yyin = 0, ostream* arg_yyout = 0 ) 与えられた入出力ストリームを使う yyFlexLexer オブジェクトを構築します。 指定しない 場合にはそれぞれストリームのデフォルト cin と cout になります。 virtual int yylex() これは yylex() が通常の flex スキャナに対して行ったのと同様の役割を担います: ルール のアクションが値を返すまで、 入力ストリームをスキャンし、トークンを消費します。 yyFlexLexer からサブクラス S を導出し yylex() から S のメンバ関数および変数をアクセ スしたい場合、 %option yyclass="S" を指定して yyFlexLexer ではなくサブクラスを使用 することを flex に知らせる必要があります。 この場合 yyFlexLexer::yylex() を生成する のではなく、 flex は S::yylex() (および呼び出されたなら yyFlexLexer::LexerError() を呼び出すダミーの yyFlexLexer::yylex() も)を生成します。 virtual void switch_streams(istream* new_in = 0, ostream* new_out = 0) yyin を new_in (非ニルの場合) に再割当し、 yyout を new_out (同様)に再割当します。 yyin が再割当された場合には以前の入力バッファは消去されま す。 int yylex( istream* new_in, ostream* new_out = 0 ) まず入力ストリームを switch_streams( new_in, new_out ) を使用して切り替え、 yylex() の値を返します。 さらに、 yyFlexLexer は次のプロテクトされた仮想関数を定義します。 スキャナにあわせてこれら を導出クラスにおいて再定義出来ます: virtual int LexerInput( char* buf, int max_size ) 最大 max_size 文字を buf に読み込み、読めた文字数を返します。 入力の終りを示すには 0 文字を返します。"対話的" スキャナ( -B と -I フラグを参照)はマクロ YY_INTERACTIVE を定義することに注意して下さい。 LexerInput() を再定義し、 対話的な入力元をスキャン する可能性があるかどうかに依存して 異なるアクションが必要となる場合、 この名前が存 在するかどうかのテストは #ifdef にて可能です。 virtual void LexerOutput( const char* buf, int size ) size 文字をバッファ buf から書き出します。 スキャナのルールが NUL を含むテキストに マッチ可能な場合、 NUL 終端されているこのバッファは "内部に" NUL を含んでいても構い ません。 virtual void LexerError( const char* msg ) 致命的なエラーメッセージを報告します。 デフォルトのこの関数はメッセージをストリーム cerr に書き、終了します。 yyFlexLexer オブジェクトは 全ての スキャン時の状態を含むことに注意して下さい。 それゆえこ の様なオブジェクトをリエントラントなスキャナとして使用できます。 同一の yyFlexLexer クラス の複数のインスタンスを具体化可能であり、 複数の C++ スキャナクラスを組み合わせ上記 -P オプ ションを使用することで同一のプログラムで使用可能です。 最後に %array 機能は C++ スキャナクラスでは使用できないことに注意して下さい; %pointer を使 用しなければなりません(デフォルト)。 単純な C++ スキャナの例を以下に示します: // An example of using the flex C++ scanner class. %{ int mylineno = 0; %} string \"[^\n"]+\" ws [ \t]+ alpha [A-Za-z] dig [0-9] name ({alpha}|{dig}|\$)({alpha}|{dig}|[_.\-/$])* num1 [-+]?{dig}+\.?([eE][-+]?{dig}+)? num2 [-+]?{dig}*\.{dig}+([eE][-+]?{dig}+)? number {num1}|{num2} %% {ws} /* skip blanks and tabs */ "/*" { int c; while((c = yyinput()) != 0) { if(c == '\n') ++mylineno; else if(c == '*') { if((c = yyinput()) == '/') break; else unput(c); } } } {number} cout << "number " << YYText() << '\n'; \n mylineno++; {name} cout << "name " << YYText() << '\n'; {string} cout << "string " << YYText() << '\n'; %% int main( int /* argc */, char** /* argv */ ) { FlexLexer* lexer = new yyFlexLexer; while(lexer->yylex() != 0) ; return 0; } 複数の(異なった)字句解析クラスを生成したい場合、 -P フラグ (もしくは prefix= オプション) を使用して各 yyFlexLexer を xxFlexLexer 等の別の名前にします。 次に字句解析クラスのソース ごとに <FlexLexer.h> をインクルードします。 以下のように yyFlexLexer をリネームします: #undef yyFlexLexer #define yyFlexLexer xxFlexLexer #include <FlexLexer.h> #undef yyFlexLexer #define yyFlexLexer zzFlexLexer #include <FlexLexer.h> これはあるスキャナに対し %option prefix="xx" を使用しもう一方に対し %option prefix="zz" を 使用した場合です。 重要: 現在のスキャンクラスの形式は 実験的 であり、メジャーリリースが変わると大きく変更され る可能性があります。
LEX および POSIX との非互換性
flex は AT&T Unix の lex ツールのリライトですが(2 つの実装はいかなるコードも共有しませ ん)、 いくばくかの拡張と非互換性を持っており、 どちらの実装でも受理可能なスキャナを書きた い方は これを意識しなければなりません。 flex は POSIX lex 仕様に完全合致しますが、例外は %pointer (デフォルト)使用と unput() 呼び出しにより yytext の内容を破壊することであり、これ は POSIX 仕様に反します。 この節では、 flex と AT&T lex と POSIX 仕様との間の全ての既知の非互換性を扱います。 flex の -l オプションはオリジナルの AT&T lex 実装との最大の互換性を有効にしますが、 生成さ れたスキャナの性能は大きく低下します。 -l オプションを使用しても発生しうる非互換性は後で述 べます。 flex は以下の例外を除き lex と完全互換です: - ドキュメントに記載されていない lex スキャナ内部の変数 yylineno は -l もしくは %option yylineno を使用しないとサポートされません。 yylineno はスキャナ毎(単一のグローバル変数)ではなく、バッファ毎に管理されるべきで す。 yylineno は POSIX 仕様ではありません。 - input() ルーチンは再定義できませんが、 ルールにマッチしたものに後続する文字を読むた めに呼ばれえます。 input() がファイルの終りに到達すると、通常の yywrap() 処理は終了 します。``実際の'' ファイルの終りは EOF として返されます。 実際には入力は YY_INPUT マクロを定義することにより制御されます。 input() を再定義できないという flex の制限は、最初に yyin を設定する以外のスキャナ 入力制御方法を単に規定していないという、 POSIX 仕様と合致します。 - unput() ルーチンは再定義できません。この制限は POSIX に合致しています。 - flex スキャナは lex スキャナとは異なりリエントラントではありません。 実際、対話的な スキャナにおいて、 割り込みハンドラにてロングジャンプを用いてスキャナから脱出し、 その後スキャナを再度呼び出す場合、以下のメッセージを得るでしょう: fatal flex scanner internal error--end of buffer missed スキャナに再度入るためには、まず以下のようにして下さい yyrestart( yyin ); この呼び出しにより入力バッファは捨てられることに注意して下さい; 通常これは対話的ス キャナでは問題ではありません。 また、C++ スキャナクラスはリエントラント です ので、C++ を使用できるのなら、C++ を 使用すべきです。 前述の "C++ スキャナの生成" を参照して下さい。 - output() はサポートされていません。 ECHO マクロからの出力はファイルポインタ yyout (デフォルトでは 標準出力 )に対して行われます。 output() は POSIX 仕様にはありません。 - lex は排他的開始条件 (%x) をサポートしませんが、これは POSIX 仕様にあります。 - 定義を展開する時、 flex では括弧で括ります。 lex では以下は: NAME [A-Z][A-Z0-9]* %% foo{NAME}? printf( "Found it\n" ); %% 文字列 "foo" にはマッチしません。 なぜなら展開されたマクロはルール "foo[A-Z][A- Z0-9]*?" と等価になり、 優先度にて `?' は "[A-Z0-9]*" と結び付きます。 flex では ルールが展開されると "foo([A-Z][A-Z0-9]*)?" となり、 文字列 "foo" がマッチします。 ^ で始まるか $ で終る定義は、展開時に括弧で括らず、 これらのオペレータが定義におい て特別な意味を失わないようにすることに 注意して下さい。 しかし <s>, /, <<EOF>> オペ レータは flex の定義では使用できません。 -l を使用すると、 lex の振舞いと同じく定義を括弧で括りません。 POSIX 仕様では、定義を括弧で括ります。 - lex の実装によっては、 ルールのパターンの右側に空白がある場合、 ルールのアクション を別の行から始めることを許します: %% foo|bar<space here> { foobar_action(); } flex はこの機能をサポートしません。 - lex の %r (Ratfor スキャナの生成)オプションはサポートされていません。 これは POSIX 仕様には含まれません。 - スキャナを %array を使用して構築したのではない限り、 unput() 呼び出し後には、次の トークンにマッチするまで yytext は未定義です。 これは lex にも POSIX 仕様にも当ては まりません。 -l オプションを指定するとこの非互換性を取り除きます。 - {} (数値範囲)オペレータの優先度が異なります。 lex は "abc{1,3}" を "1 度か 2 度か 3 度の 'abc' にマッチ" と解釈しますが、 flex は "'ab' に 1 度か 2 度か 3 度の 'c' が 続くものにマッチ" と解釈します。 後者が POSIX 仕様に合致します。 - ^ オペレータの優先度が異なります。 lex は "^foo|bar" を "行頭の 'foo' か任意位置の 'bar' にマッチ" と解釈しますが、 flex は "行頭の 'foo' か 'bar' にマッチ" と解釈し ます。 後者が POSIX 仕様に合致します。 - lex でサポートされている %a 等の特別なテーブルサイズの宣言は flex スキャナでは不要 です; flex はこれらを無視します。 - flex と lex のどちらでもスキャナを使用可能に書けるように、 FLEX_SCANNER という名前 を定義します。 スキャナを生成した flex のバージョンを表す YY_FLEX_MAJOR_VERSION と YY_FLEX_MINOR_VERSION を、スキャナは含みます (例えば 2.5 リリースではこれらはそれぞ れ 2 と 5 になります)。 以下の flex の機能は lex および POSIX 仕様には含まれません: C++ スキャナ %option 開始条件スコープ 開始条件スタック 対話的/非対話的スキャナ yy_scan_string() 等 yyterminate() yy_set_interactive() yy_set_bol() YY_AT_BOL() <<EOF>> <*> YY_DECL YY_START YY_USER_ACTION YY_USER_INIT #line ディレクティブ アクションの周りの %{} 単一行における複数のアクション さらにほぼ全ての flex フラグです。 リストの最後の機能の意味は、 flex では複数のアクション をセミコロンで区切って同一行に記述可能ですが、 lex では次の foo handle_foo(); ++num_foos_seen; は (驚くべきことに) 次のように切り詰められるということです。 foo handle_foo(); flex はアクションを切り詰めません。 ブレースで括られないアクションは単純に行末で終了しま す。
診断
warning, rule cannot be matched 常に同じテキストにマッチするルールが前にあるので、 与えら れたルールがマッチしません。 例えば以下の "foo" は "全てを捕まえる" ルールの後ろにあります ので 決してマッチしません: [a-z]+ got_identifier(); foo got_foo(); スキャナ中で REJECT を使用するとこの警告を抑制します。 warning, -s option given but default rule can be matched (おそらくある特定の開始条件のもと では) デフォルトルール (任意の一文字にマッチする) しか特定の入力に 対してはマッチしないこ とがあります。 -s を指定しているので、おそらくそうなりません。 reject_used_but_not_detected undefined あるいは yymore_used_but_not_detected undefined - これらのエラーは コンパイル時に起きます。スキャナが REJECT もしくは yymore() を使っていま すが flex がそのことに気づかなかったということです。 つまり、 flex は最初の 2 つの部分を探 しても これらのアクションの出現を見つけられなかったのですが、 実際には何らかの方法 (例えば #include ファイルを介して)でこれらが記述されていた、ということです。 %option reject か %option yymore を使用して、flex にこれらの機能を実際に使用していることを教えて下さい。 flex scanner jammed - -s でコンパイルされたスキャナが、どのルールにもマッチしない 入力文字 列に遭遇しました。 内部的な問題に起因してこのエラーが起こることもあります。 token too large, exceeds YYLMAX - スキャナが %array を使っている場合に、あるルールが定数 YYLMAX (デフォルトで 8K バイト) より大きな文字列とマッチしました。 flex の入力ファイルの定 義部で YYLMAX を #define することで値を大きくできます。 scanner requires -8 flag to use the character 'x' - スキャナの記述に 8 ビットの文字 'x' を 識別する部分があり、 -Cf もしくは -CF のテーブル圧縮オプションのためにデフォルトの 7 ビッ トになっている にもかかわらず、 -8 オプションをつけていないということです。 詳細は -7 フラ グのオプションの議論を参照して下さい。 flex scanner push-back overflow - unput() でテキストを戻しすぎたため、スキャナのバッファは 戻したテキストと現トークンを yytext に保てません。 この場合、理想的にはスキャナが動的に バッファの大きさを変えるべきですが、 現在のところそうなってはいません。 input buffer overflow, can't enlarge buffer because scanner uses REJECT - スキャナは非常に 大きなトークンのマッチを調べていて、入力バッファを 拡張する必要が起きました。しかしなが ら、バッファの拡張は REJECT を使うスキャナでは働きません。 fatal flex scanner internal error--end of buffer missed - スキャナが使用しているフレームか ら(を越えて)ロングジャンプした後、 再度スキャナに入った場合に起こります。 再度スキャナに入 る前に: yyrestart( yyin ); を使うか、前述のように C++ スキャナクラスを使用するようにして下さい。 too many start conditions in <> construct! - 存在するより多くの開始条件を <> 中に記載しま した (少なくとも一つを二度記載しました)。
関連ファイル
-ll スキャナがリンクしなければならないライブラリ。 lex.yy.c 生成されたスキャナ(システムによっては lexyy.c という名前になります)。 lex.yy.cc -+ を使った時に作成された C++ スキャナクラス。 <FlexLexer.h> C++ スキャナベースクラス FlexLexer とその導出クラス yyFlexLexer を定義するヘッダ ファイル。 flex.skl スケルトンスキャナ。 このファイルは flex の実行時ではなく、flex を構築する時のみ利 用されます。 lex.backup -b フラグ用のバックアップ情報(システムによっては lex.bck という名前になります)。
欠陥 / バグ
右文脈(trailing context)パターンの中には、正しくマッチせず 警告メッセージ ("dangerous trailing context") を出すものがあります。 これらのパターンは、 ルールの最初の部分が 2番目 の頭の部分とマッチするようなものです。 例えば "zx*/xy*" の場合、'x*' は右文脈の頭の 'x' と マッチします。 (POSIX ドラフトではそのようなパターンにマッチするテキストは 未定義であると 述べていることに注意して下さい。) 右文脈の中には、実際には固定長であるのにそうとは解釈されないものがあり、 上に述べた性能の 低下が起こります。 特に、 '|' や {n} (例えば "foo{3}") は常に可変長であると解釈されます。 右文脈と特別なアクション '|' を組み合わせると 固定の 右文脈がよりコストのかかる 可変の 右 文脈となります。例えば、次のようなものです: %% abc | xyz/def %array もしくは -l オプションを指定しない場合、 unput() を使うと yytext と yyleng を破壊し ます。 NUL のパターンマッチングは他の文字の比較よりかなり遅くなっています。 入力バッファの動的な大きさの再調整は時間がかかります。これは現トークン (一般に巨大)までの マッチした全テキストの再スキャンを伴うためです。 入力のバッファリングと先読みのため、 <stdio.h> ルーチンと 混合して使うことが出来ません。例 えば、 getchar() と flex のルールはうまく行きません。代わりに input() を使って下さい。 -v オプションで表示される全テーブルエントリには、 どのルールがマッチしたのかを決定するのに 必要なテーブルエントリ数が 含まれていません。エントリの数はスキャナが REJECT を使っていな いときには DFA 状態数に等しく、 使っているときには DFA 状態数よりいくらか大きくなります。 REJECT がオプション -f もしくは -F とともに使えません。 flex の内部アルゴリズムについてのドキュメントが必要です。
関連項目
lex(1), yacc(1), sed(1), awk(1) John Levine, Tony Mason, and Doug Brown, Lex & Yacc, O'Reilly and Associates. 第 2 版を入 手すること。 M. E. Lesk and E. Schmidt, LEX - Lexical Analyzer Generator Alfred Aho, Ravi Sethi and Jeffrey Ullman, Compilers: Principles, Techniques and Tools, Addison-Wesley (1986). flex で使用しているパターンマッチング技法を解説している(決定性オー トマトン)。
作者
Vern Paxson が多くのアイディアとインスピレーションを得る助けを Van Jacobson から受けまし た。 オリジナルバージョンは Jef Poskanzer が作成しました。 高速テーブル表現は Van Jacobson のデザインの部分実装です。 この実装は Kevin Gong と Vern Paxson が行いました。 多くの flex ベータテスタ、フィードバッカ、コントリビュータ、特に Francois Pinard, Casey Leedom, Robert Abramovitz, Stan Adermann, Terry Allen, David Barker-Plummer, John Basrai, Neal Becker, Nelson H.F. Beebe, benson@odi.com, Karl Berry, Peter A. Bigot, Simon Blanchard, Keith Bostic, Frederic Brehm, Ian Brockbank, Kin Cho, Nick Christopher, Brian Clapper, J.T. Conklin, Jason Coughlin, Bill Cox, Nick Cropper, Dave Curtis, Scott David Daniels, Chris G. Demetriou, Theo Deraadt, Mike Donahue, Chuck Doucette, Tom Epperly, Leo Eskin, Chris Faylor, Chris Flatters, Jon Forrest, Jeffrey Friedl, Joe Gayda, Kaveh R. Ghazi, Wolfgang Glunz, Eric Goldman, Christopher M. Gould, Ulrich Grepel, Peer Griebel, Jan Hajic, Charles Hemphill, NORO Hideo, Jarkko Hietaniemi, Scott Hofmann, Jeff Honig, Dana Hudes, Eric Hughes, John Interrante, Ceriel Jacobs, Michal Jaegermann, Sakari Jalovaara, Jeffrey R. Jones, Henry Juengst, Klaus Kaempf, Jonathan I. Kamens, Terrence O Kane, Amir Katz, ken@ken.hilco.com, Kevin B. Kenny, Steve Kirsch, Winfried Koenig, Marq Kole, Ronald Lamprecht, Greg Lee, Rohan Lenard, Craig Leres, John Levine, Steve Liddle, David Loffredo, Mike Long, Mohamed el Lozy, Brian Madsen, Malte, Joe Marshall, Bengt Martensson, Chris Metcalf, Luke Mewburn, Jim Meyering, R. Alexander Milowski, Erik Naggum, G.T. Nicol, Landon Noll, James Nordby, Marc Nozell, Richard Ohnemus, Karsten Pahnke, Sven Panne, Roland Pesch, Walter Pelissero, Gaumond Pierre, Esmond Pitt, Jef Poskanzer, Joe Rahmeh, Jarmo Raiha, Frederic Raimbault, Pat Rankin, Rick Richardson, Kevin Rodgers, Kai Uwe Rommel, Jim Roskind, Alberto Santini, Andreas Scherer, Darrell Schiebel, Raf Schietekat, Doug Schmidt, Philippe Schnoebelen, Andreas Schwab, Larry Schwimmer, Alex Siegel, Eckehard Stolz, Jan-Erik Strvmquist, Mike Stump, Paul Stuart, Dave Tallman, Ian Lance Taylor, Chris Thewalt, Richard M. Timoney, Jodi Tsai, Paul Tuinenga, Gary Weik, Frank Whaley, Gerhard Wilhelms, Kent Williams, Ken Yap, Ron Zellar, Nathan Zelle, David Zuhn, および私の最低のメールアーカイブ能力から滑り落ちた方々、 それらの方々の協力にも同様 に感謝します。 Keith Bostic, Jon Forrest, Noah Friedman, John Gilmore, Craig Leres, John Levine, Bob Mulcahy, G.T. Nicol, Francois Pinard, Rich Salz, Richard Stallman には多くの悩みの分散に 関して感謝します。 Esmond Pitt と Earle Horton には 8 ビット文字サポートに関して; Benson Margulies と Fred Burke には C++ サポートに関して; Kent Williams と Tom Epperly には C++ クラスサポートに関 して; Ove Ewerlid には NUL のサポートに関して; Eric Hughes には複数バッファのサポートに関 して、それぞれ感謝します。 この作品は当初、私が CA Berkeley の Lawrence Berkeley Laboratory における Real Time Systems Group にいた時に作成されました。 私に協力してくれた方々に感謝します。 コメントは vern@ee.lbl.gov に送って下さい。