Node:Introduction, Next:, Previous:Top, Up:Top

Flex入門

この章では字句スキャン処理の概念を説明し、Flexのようなツールの 必要性を示します。 本章の後半ではFlexを使うことのできる状況について説明します。

UnixおよびCの世界では、ファイルは通常個々のバイトが連続したものとして 扱われます。 個々のバイトを集めてどのようにグループ化するかという点は、 プログラマが決めることです。 このような抽象化は非常に強力です。 というのは、どのようなファイルであってもこの抽象化方法によって表現できるから です。 しかしこの方法には短所もあり、プログラマはほとんど常に生のファイルに対して 構造をあてはめなければなりません。 言い換えると、ファイルをより意味のある部分に分割しなければならないということ です。 たとえば、コンパイラのある部分はファイルから連続した文字を受け取り、 構文チェッカが理解することのできる構成要素、 たとえば、数値、キーワード、文字列などにグループ化します。 このようなことを行う理由は、コンパイラの言語パーサが処理を行うのは、連続した 文字に対してではなく、その言語のシンボルが連続したものに対してだからです。

データベースアプリケーションや、バイナリファイルを扱うアプリケーションは、 扱うデータに対してある固定されたフォーマットを持っていることが多く、 そのフォーマットを使って入力データから意味を導き出します。 テキストを入力するプログラムは通常これとは反対で、入力を単語やシンボルに 分割しなければならないことが多いのですが、通常これらの単語やシンボルが どのように配置されているかを示す決まった構造というものは存在しません。 したがって、テキスト処理を行うプログラムは、入力された情報を意味のある シンボルに分割するために、字句解析もしくは字句スキャンと呼ばれる 処理を行う部分を持っていることが多く、そこで入力情報の分割が行われます。 このようなことを行う関数群のことを字句アナライザもしくは字句スキャナ、 あるいは短く「スキャナ」と呼びます。

一般的に、スキャナを作成するのは、 プログラマにとって難しいことでもおもしろいことでもないのですが、 時間のかかる作業になることはあります。 普及しているプログラミング言語のほとんどは、 スキャナの作成を支援する機能が不十分です。 というのは、連続した文字を単語、トークン、シンボルに分割する組み込みの機能 を持っていないからです。 通常はこのような仕事を行うライブラリルーチンが存在しますが、 柔軟でなかったり、使いにくかったり、あるいは、 ルーチンとのやり取りにあまりに多くのコードが必要になったりすることが多いために、 実装上の細かな点によって根本的な問題が不明瞭にされてしまいます。

1つの良い例が、C言語で許されているすべての数値型 (浮動小数点、整数、16進数、8進数) を処理するスキャナをC言語で記述する場合です。 これは非常に難しいということはありませんが、 でき上がったコードは通常美しいとはとてもいえないものになってしまうでしょうし、 その保守や拡張は容易でないことが多いのです。

ほとんどのプログラマが即座に断言するように、 他人の書いたコードを保守するのは通常あまり楽しい作業ではありません。 さらに、美しくないコードを保守するのは、楽しいというにはほど遠いものです。 このように、スキャナを書くことが退屈で、その保守が難しいとなると、 スキャナの作成をより容易にしてくれる方法を考えようとした理由が読者にもおわかり いただけるでしょう。


Node:Problem Solving, Next:, Up:Introduction

問題解決手段としてのFlex

Flexはプログラマに対して、字句解析処理部分をきれいに記述し、 その記述にしたがった効率的な字句スキャナを生成する方法を提供します。 プログラマはFlexに対して、必要なスキャナに関する記述情報を提供します。 Flexはその記述情報を使って、C言語で書かれたスキャナを生成します。 記述に使われる言語は高級言語であり、 スキャナの記述に関してはC言語よりもはるかに適しています。 その高級言語を使うことで、プログラマは、文字をどのようにグループ化し、 グループ化が完了したときにどのようなアクションを発生させるかを指定できます。

注意:本書のほとんどの部分はFlex、Lexの両方を対象にしています。 Lexは(Flexには劣りますが) ほとんどのUnixシステム上にある標準のスキャナ生成ユーティリティです。 両者の間に違いがある場合には、Flexを優先させています。 Lexについては標準Lexで簡単に説明しています。

ここでも1つの良い例がコンパイラです。 前述のように、コンパイラの構文チェッカは、文字が連続したものではなく、 言語文法の構成要素を表すトークンが連続したものを、 入力として受け取る必要があります。 Flexはこのような場合に最適です。 Flexによって生成されたスキャナが構文チェッカとファイルの仲介役となり、 構文チェッカが次の意味のあるトークンを要求するのを待ちます。 Flexはファイルを読み、プログラマが与える記述にしたがって文字をグループ化して、 マッチしたトークンを返します。 この処理は、スキャナもしくはパーサが終了するまで続きます。

Cのコンパイラを作成する場合、 このようなことを行うために必要となるFlexの記述情報は、 コードの行数にして100〜300行ぐらいになるかもしれません。 この記述情報のほとんどは、 シンボルテーブルの管理、識別子の検索、型のマッピング、ある数の値などの追加情報の返却を行うための補助的なCコードになるでしょう。 こうしたコード自体は記述情報の一部ではありませんが、 通常、コンパイラによって必要とされるものです。

概念的にはFlexは、原材料(文字)を取り込み、 消費者(パーサ等)がすぐに使うことができる完成品(トークン)を製造する工場のようなものです。


Node:General Programming, Previous:Problem Solving, Up:Introduction

一般的なプログラミングツールとしてのFlex

Flexはコンパイラにしか使えないということはありません。 読者のコンピュータ上にある、ファイルを読み込んだり、 なんらかの形で文字のグループを処理する必要のあるすべてのプログラム、 特に、変換フィルタや言語ツールのことを考えてみてください。 こうしたプログラムのほとんどすべてを、 Flex単体、もしくはFlexと他のツールの組み合わせによって作成することができます。

良い例の1つが文字数のカウントです。 たとえば、ファイルの中の全行数、個々の文字の出現回数、 fooという単語の出現回数を調べるプログラムを作成したい場合です。 標準的なツール(grepsedawkperl等) で作成することも、C言語のプログラムを書いて作成することもできますが、 Flexで作成することも可能です。 もう1つの例として、特定のキーワードを探す必要のある、メールリーダがあります。 この場合でも、標準ツールで実現することも、FlexとC言語で実現することもできます。

Flexを使うことで、プログラマは、 スキャナの開発やファイルを構成する文字の処理にかかる時間を大幅に減らすことができます。 ほとんどの場合、Flexに対する入力情報は、 既存のプログラミング言語で記述されたコードと比較して、 より理解しやすく、少なくとも同程度の移植性があり、保守もより簡単です。 それだけでなく、Flexでスキャナを開発するのにかかる時間は、 既存のプログラミング言語で同等のスキャナを開発する場合と比べきわめて短くてすむので、 Flexは、プロトタイピングや、 一度しか使わないプログラムやフィルタの開発に最適です。


Node:Invoking Flex, Next:, Previous:Introduction, Up:Top

Flexの起動

この章ではFlexを起動する基本的な方法を説明し、 Flexで使用可能なコマンドラインオプションを簡単に説明します。

Flexは記述情報を含むファイルを入力として受け取り、 スキャナ機能を持つCのファイルに変換します。 Flexを起動するためのコマンド行は以下のようになります。

flex [-bcdfinpstvFILT8] [-C[efmF]] [-Sskeleton] [file ...]

一般的には、 単にflexに続けて処理すべきファイル名を入力することで実行されます。

flex myfile.l

記述情報ファイル名の末尾の.lは、 myfileがFlexもしくはLexの記述ファイルであることを示す慣例的な方法です。 名前付けの慣例としてもう1つよく見られるのが、末尾に.lexを使うことです。 たとえば、以下のようになります。

flex myfile.lex

Flexは記述情報ファイル(myfile.l)を読み込み、 そこに記述されたパターンを認識するスキャナ機能を持つlex.yy.cという名前のC言語ファイルを生成します。 記述情報の中になんらかのエラーがあれば、 Flexは対応するエラーメッセージをstderrに出力します。


Node:Command Line Switches, Next:, Up:Invoking Flex

コマンドラインオプション

Flexのコマンドラインオプションは以下のような意味を持ちます。

-b
-bオプションを指定するとlex.backtrackというファイルが生成されます。 このファイルはスキャナの記述情報を最適化する際に使用されます。 詳細については、See Optimizing for Speed
-c
このオプションはPOSIXとの互換性のために提供されているだけで、 実際には何もしません。 POSIXでは、 -cオプションはC言語によるアクションが使用されることを意味します。
-d
このオプションを指定するとデバッグが可能になります。 これにより生成されるスキャナは、 実行中にスキャナの状態情報をstderrに出力します。
-f
Flexに対してファストスキャナ(fast scanner)とフルスキャナ(full scanner)のどちらを生成するかを指示します。 詳細については、See Table Compression and Scanner Speed-f(小文字)オプションと-F(大文字)オプションとは異なる効果を持つ点に注意してください。
-i
Flexに対して大文字、小文字を区別しないスキャナを生成するよう指示します。 詳細については、See Case Insensitive Scanners
-n
このオプションはFlexにとってはなんの意味も持たず、 POSIXとの互換性のためにのみ提供されています。 POSIXでは、 このフラグは-vオプションによる出力を抑制するために使用されます。 POSIXでのデフォルトは、 テーブルサイズが指定されないかぎりこのような出力を抑制します。 Flexではテーブルサイズは意味を持たないので、このフラグは冗長です。
-p
-pオプションが指定されると、 Flexは性能レポートをstderrに出力します。 スキャナの性能を向上させる方法に関する解説については、 See Optimizing for Speed
-s
Flexスキャナがマッチするものを見つけられなかった場合のデフォルトのアクションは、 そのマッチされなかった入力情報をstdoutに出力することです。 -sオプションはこのようなアクションを抑制し、 その代わりに入力情報がマッチしなかった時点でスキャナを異常終了させます。
-t
このオプションが指定された場合、 Flexは生成されたスキャナをファイルlex.yy.cにではなくstdoutに出力します。
-v
Flexに対して冗長モードで動作するよう指示します。
-F
Flexに対してファストスキャナを生成するよう指示します。 詳細については、See スキャナの最適化-F(大文字)は-f(小文字)とは異なる効果を持つ点に注意してください。 -f-Fの相違点に関する情報については、 See Table Compression and Scanner Speed
-I
このオプションはFlexに対して対話型スキャナを生成するよう指示します。 詳細については、See Interactive Scanners
-L
デフォルトでは、デバッグを支援するために、 Flexは生成されたスキャナのコード中に#line指示子を書き込みます。 このオプションによって#line指示子の書き込みは行われなくなります。
-T
Flexに対してトレースモードで動作するよう指示します。 Flexは多くのメッセージをstderrに出力するようになります。 こうしたメッセージは、 Flexを非常によく理解しているユーザー以外には無意味でしょう。
-8
このオプションは、 Flexに対して8ビット入力を受け付けるスキャナを生成するよう指示します。
-C[efmF]
これらのオプションは、 スキャン処理用のテーブルをどのように圧縮するかを制御します。 詳細については、See スキャナの最適化
-Sskeleton_file
Flexに対して、 生成するスキャナのベースとしてskeleton_fileにより指定されるスケルトンファイルを使用するよう指示します。 主に、Flex自体をデバッグするために使用されます。


Node:Command Line Switches (Flex 2.5), Previous:Command Line Switches, Up:Invoking Flex

コマンドラインオプション(Flex 2.5の補足情報)

Flex 2.5では、前節(Command Line Switches)で説明されていない、 以下のオプションもサポートされています。

-h
Flexに対してコマンドラインオプションの要約情報を出力するよう指示します。
-l
AT&Tにより実装されたlexとの互換性を最大限に提供します。 このオプションは、性能面でかなりの悪影響を及ぼします。 また、このオプションを、-f-F-Cf-CF-+オプションと同時に指定することはできません。 FlexとLexの(非)互換性の問題については、Flex and Lexを参照してください。
-w
このオプションが指定されると、Flexは警告メッセージを出力しません。
-B
Flexに対してバッチスキャナ(batch scanner)を生成するよう指示します。 これは、対話型スキャナを生成するよう指示する-Iオプションの否定です。
-V
Flexに対してバージョン番号を出力するよう指示します。
-7
Flexに対して7ビットスキャナを生成するよう指示します。 これは-8オプションの否定です。 内部的に生成されるテーブルのサイズは-8オプションが指定された場合と 比較して半分になりますが、生成されるスキャナは、8ビット文字を含む入力を処理 できなくなります。 -Cf-CFが指定されていない場合は、 明示的に-7を指定しないかぎり、8ビットスキャナが生成されます。
-+
Flexに対してC++スキャナクラスを生成するよう指示します。 C++スキャナについては、Flex and C++ (Flex 2.5)を参照してください。
-?
Flexに対してコマンドラインオプションの要約情報を出力するよう指示します (-hオプションと同じです)。
-Ca
このオプションは、スキャン処理用のテーブルをlong intの配列として定義 するようFlexに通知します(デフォルトではshort int型の配列となります)。 RISCマシンによっては、long intのほうが高速に処理されるため、スキャナ の性能向上が期待できますが、その反面、テーブルのサイズは大きくなります。
-Cr
このオプションを指定して生成されたスキャナは、 入力にread()システムコールを使います。 デフォルトでは、対話型スキャナの場合はgetc()が、 バッチ(非対話型)スキャナの場合はfread()が使われます。
-ofile
このオプションが指定されると、Flexは生成されたスキャナをfileによって 指定されたファイルに出力します。 デフォルトでは、スキャナはファイルlex.yy.cに出力されます。
-Pprefix
Flexによって生成されるスキャナのソースファイルの中では、 大域変数や大域関数の名前の先頭に接頭辞yyが付けられます。 このオプションが指定されると、yyの代わりに、 prefixによって指定される文字列が接頭辞として使用されます。 また、-oオプションが指定されない場合のスキャナファイル名 lex.yy.cも、lex.prefix.cとなります。

以下に、このオプションにより影響を受ける名前の一覧を示します。

yy_create_buffer
yy_delete_buffer
yy_scan_buffer
yy_scan_string
yy_scan_bytes
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

-+オプションが指定されている場合は、 影響を受けるのはyywrapyyFlexLexerの2つだけです。

このオプションにより、 yywrap()の名前が変更されてしまう点に注意してください。 プログラムをリンクするためには、 prefixwrapという名前の関数を作成する必要があります。 この関数を作成したくない場合には、 スキャナ定義ファイルの中で、%option noyywrapを指定して、 リンク時に-lflオプションを指定します。 %option指示子については、%option (Flex 2.5)を参照してください。

--help
Flexに対してコマンドラインオプションの要約情報を出力するよう指示します (-hオプションと同じです)。
--version
Flexに対してバージョン番号を出力するよう指示します (-Vオプションと同じです)。


Node:Flex Descriptions, Next:, Previous:Invoking Flex, Up:Top

Flex記述言語

この章では、スキャナ定義の構成要素を説明し、その使用例を示します。 Flexを効率的に使用するためには、 定義の個々の要素を完全に理解することが非常に重要です。 したがって、初めてFlexを使うユーザーは、 時間をかけて本章を読むことをおすすめします。

Flexスキャナ定義のほとんどの要素は、任意です。 全体的な定義フォーマットは以下のようになります。

定義と初期Cコード
%%
ルール
%%
他のCコード

それぞれについて、以下において詳細に説明します。


Node:Comments, Next:, Up:Flex Descriptions

コメント

Cのコードを記述できるところには、どこにでもコメントを記述できます。 コメントの書式は、Cのコメントの規則に従います。 コメントは、記述情報に影響を及ぼすことはありません。 Cスタイルのコメントは以下のようになります。

/*
...
*/

これに加えて、Flexでは#で始まるコメントも許されます。 しかし、このようなコメントはlex.yy.cにはコピーされないので、 この形式のコメントの使用はおすすめできません

注意:C以外の言語(たとえばPascal) のコードを生成するLexも存在します。 このようなLexではコメントの書式はおそらく異なるでしょう。 Flexの場合はCのコードしか生成しません。


Node:Optional C Code, Next:, Previous:Comments, Up:Flex Descriptions

オプションのCコード

プログラマは、2つの異なる方法を用いて、 スキャナの中に直接Cのコードを含めることができます。 第1の方法は、「定義と初期Cコード」セクション (最初の%%より前の部分)にコードを含めることです。 第2の方法は、「他のCコード」セクション (2番目の%%より後ろの部分)にコードを含めることです。 どちらの場合も、コードはそのままlex.yy.cにコピーされるので、 正当なコードでなければなりません。

第1のセクション中のCコードは以下の形式になります。

%{
   Cコード
   ...
%}

ここで%{...%}というペアが、 Cコードブロックの先頭と末尾を示すために使われています。 この形式のコードと定義は、「定義と初期Cコード」セクションのどこにでも 自由に記述できます。 定義については次の節で説明します。

Cのコードが最初のカラムから始まるのでなければ、 %{...%}というペアは必要ありません。 しかし普通は、わかりやすくするために記述しておいたほうがよいでしょう。 もう1つのポイントは、#ifdefなどのように最左端のカラムから始まらなければ ならず、かつ、通常はスキャナ記述情報の先頭に置かれる必要のあるものが存在する という点です。 こうした場合、%{...%}に囲まれていないと、 Flexはそれを定義の一部であるとみなすでしょう。 これが、常に%{...%}を使うもう1つの理由です。

最後の(「他のCコード」)セクション内のコードは、そのままコピーされます。 ここには特別な宣言は必要ありません。


Node:Definitions, Next:, Previous:Optional C Code, Up:Flex Descriptions

定義

定義セクションにおいて、プログラマは、ある文字のグループに一意な識別子を与え、 その識別子がその文字グループに置き換えられるようにすることができます。 定義は以下のような形式になります。

definition_name    definition

definition_name最初のカラムから始まらなければならず、 そうしないとその定義はlex.yy.cにそのままコピーされてしまうということに注意してください。 以下に一般的な定義をいくつかあげます。

DIGIT     [0-9]
LETTER    [a-z]
IDENT     [a-z_][a-z0-9_]*
ALPHANUM  {LETTER}|{DIGIT}

definition nameは、 そのグループの一意な識別子でなければなりません。 また、definitionはルールセクション(後述)において正当なものであれば なんでもかまいません。 ルールセクションや(上の例のALPHANUMの定義において示されるように) 別の定義中において使われる場合には、 定義は{ }によって囲まれていなければなりません。

FlexとLexの非常に重要な相違点に、定義を展開するとき、 Flexは字義どおりに丸かっこ( )で囲むのに対して、 Lexは囲まないという点があります。1 これは、^<<EOF>>$/を定義中に入れることが できないことを意味しています。 というのは、前述の文字は丸かっこ( )で囲まれた部分に入れることができない からです。 詳細は、CharactersおよびFlex and Lexにおいて説明します。

たとえば、

FUNCTION ^[a-zA-Z_][a-zA-Z0-9_]*"("
%%
{FUNCTION}  printf("got a function\n");

は、以下のようなプログラミングスタイルを使っている場合の、 Cの関数宣言にマッチするように見えます。

int
foo()
{
   ...
}

しかし実際にはうまくいきません。 というのは、{FUNCTION}が展開されると、

(^[a-zA-Z_][a-zA-Z0-9_]*)

のようになりますが、これは不正だからです。 このような種類の問題に関する説明については、 Flex and POSIXを参照してください。


Node:%%, Next:, Previous:Definitions, Up:Flex Descriptions

%%

2つのパーセント記号が、スキャナ記述情報のルールセクションの先頭と末尾 を示します。 すべてのFlex記述情報は、少なくともルールセクションの先頭を示す %%を含んでいなければなりません。


Node:Rules, Next:, Previous:%%, Up:Flex Descriptions

ルール

ルールはFlexの心臓部です。 ルールを書くことによって、プログラマは、 スキャナが何を実行するべきであるかをFlexに通知します。

通常、ルールは2つの部分から構成されます。

pattern       actions

このうちpatternが何を認識するべきかを定義し、 actionsがその何かを認識したときに何を実行するべきであるか をスキャナに知らせます。 patternの部分は空白によって区切られます。 これは、空白をマッチさせたい場合には、 それを引用符で囲む必要があるということを意味しています。

スキャナは、マッチするものを2つ以上見つけた場合、 以下の2つのルールを使ってどれを受け入れるかを決めます。

  1. 後続コンテキスト(trailing context)も含めてもっとも長いものを受け入れる。
  2. マッチするものがすべて同じ長さの場合、 スキャナ定義中に最初に記述されたものを受け入れる。

actionsは、 空(コードなし)にするか、もしくは、1つ以上のCの文を含む単一のコード行、 {...}または%{...%}で囲まれた1行以上のコード、 単一の垂直棒(|)のいずれかを記述できます。 以下にいくつか例をあげます。

hi         |
bonjour    |
hello      printf("hello!\n");
goodbye    {  printf("goodbye!\n"); }
konnichiwa {
               line 1
               ...
               line n
           }
sayonara   printf("lex will not "); printf("print this\n");

どの行も複数の文を含むことができます。 |は、そのルールにマッチするものが見つかった場合、 次に現れるルールのアクション部に記述されているアクションが 実行されるべきであることをFlexに通知します。

注意:ほとんどのバージョンのLexは、 {}のペアの外部では単一の文しか許しません (たとえば上記のsayonaraルールは許されません)。 また、C以外の言語をターゲットにしているLexでは、{}のペアは、 たとえばPascalの場合のbegin...endのように、 異なるシンボルに置き換える必要があるかもしれません。

ルールにマッチしなかった入力に対するデフォルトのアクションは、 それをstdoutに出力することであり、 一方、マッチしたパターンに対するデフォルトのアクションは、 それを破棄することであるという点に注意してください。 これは、もっとも単純なFlexの定義が

%%

であることを意味しています。 これは、入力を変更せずそのままstdoutへ出力するものです。 別の単純な例として以下のようなものがあります。

%%
foobar

この場合、入力の中からfoobarという文字の並びをすべて取り除き、 取り除いた結果をstdoutに出力します。


Node:Pattern Matching, Next:, Previous:Rules, Up:Flex Descriptions

パターンセクション

パターンセクションは、正規表現と呼ばれる仕組みを使って実際の マッチング処理を実行します。 正規表現は、文字列、文字、文字集合(クラス)、 および演算子から構成されています。 正規表現を構成する要素については次節以降で説明します。 また正規表現自体については、Regular Expressionsにおいて解説します。


Node:Characters, Next:, Up:Pattern Matching

文字

いくつかの文字はFlexにとって特別の意味があり、 その文字を単独で使ったのでは、その文字自体を表すことができません。 以下に、Flexにおける特殊文字とその意味を表にして示します。

文字
Flexによる解釈
.
ピリオドは改行(\n以外の任意の文字を表します。
\
バックスラッシュはエスケープ文字です。 エスケープシーケンスはANSI Cのものと同じです。
[ ]
角かっこ[ ]は複数の文字を文字クラスにグループ化します。 詳細については、See Flexにおける文字のグループ化
^
文字クラスの内部では「^」は否定を意味します。 詳細については、See Flexにおける文字のグループ化。 文字クラスの外部では、「^」は行の先頭を意味し、 ルールの先頭にのみ置くことができます。 例を以下に示します。
[^AB]
否定クラスです。
^foo
行の先頭にあるfooという文字の並びにのみマッチします。
foo^
この場合、^は普通の文字であるとみなされます。 このようなときには、希望どおりの結果が確実に得られるようにするために、 特別な意味を持つ文字の前にバックスラッシュ\を置くのがよいでしょう。 このような文字の並びをエスケープシーケンスと呼びます。 エスケープシーケンスについてはこの節の最後で説明します。

-
ハイフンは文字クラスの内部において文字の範囲を表します。 文字クラスの外部では、ハイフンはそれ自身を表します。 詳細については、See Flexにおける文字のグループ化
{ }
中かっこ{ }は、定義の参照、複数行のアクションの囲み、 またはある範囲にわたる繰り返しの定義を行います。 例をあげると、定義FOOがあって、 それをルールの中で参照したい場合に{FOO}を使います。

与えられたパターンのある範囲にわたる繰り返しを定義するには、 以下のような{ repetition list }を使います。

%%
f{2,5}  /* fの2回以上5回以下の繰り返し */
        /* にマッチ                      */
f{2,}   /* fの2回以上の繰り返しにマッチ */
f{2}    /* fの2回の繰り返しにマッチ     */

この用法の解釈において、 FlexとLexの間にはいくつかの相違点があります。 詳細については、Flex and POSIXを参照してください。

( )
丸かっこ( )を使って優先順位を変更できます。 また、定義が展開されるときには、その定義は暗黙のうちに 丸かっこ( )で囲まれることに注意してください。2 このため、Lexとは非互換なところがでてきます。 この点については、Flex and POSIXDefinitionsで説明しています。
""
二重引用符記号は文字列を表します。 引用符の内側の文字列だけがマッチの対象になります。 したがって、
%%
"string"

"string"にではなく、stringにマッチします。

/
スラッシュは後続コンテキストを設定します。 これは、あるパターンの後ろに特定の文字の並びが続く場合のみ、 そのパターンを認識します。 つまり、スラッシュ/は「ルックアヘッド(先読み)」演算子として 機能します。 例をあげると、
abcDEF
abcDEF を認識します。
abc/DEF
abcの後ろにDEFが続く場合に限り、abcを認識します。 DEFの部分は、まだ読まれてはいないかのように扱われ、 マッチの対象になりません。

注意:1つのルールの中では/は1つだけ許されます。 つまり、

abc/def/hijkl

は不正です。

< >
山かっこ< >はスタート状態を参照します。 また、EOFシンボル(<<EOF>>)にも使われます。 完全な説明については、 Start StatesEnd-Of-File Rulesを参照してください。
? + *
?+*は、 ある正規表現が出現できる回数を設定します。 ?は0回もしくは1回(その正規表現が現れることが必須ではないということ) を意味します。 +は1回以上を意味します。 *は0回以上を意味します。 たとえば、
a?
0個もしくは1個のaにマッチします。
a+
1個以上のaにマッチします。
a*
0個以上のaにマッチします。
(ABC)+
ABCという文字の並びが1回以上続くものにマッチします。
[abQrS]?
0個もしくは1個の、(5つの文字abQrSから構成される) この文字クラスのメンバにマッチします。 文字クラスに関する詳細については、 Flexにおける文字のグループ化を参照してください。
{NUM}*
0個以上のNUMにマッチします。 ここでのNUMは定義です。 定義に関する詳細については、Definitionsを参照してください。

|
OR演算子および特別なアクションを表します。 たとえば、
   apples|oranges

applesもしくはorangesのいずれかにマッチし、

apples         |
oranges        printf("fruit!\n");

は、 applesorangesの両方に対して同一のアクションを実行します。

$
ドル記号は行末を意味します。 たとえば、
   end$

はその直後が行末である場合にのみendという文字の並びにマッチします。 これは、後ろに続くのが行末のマーカである場合のみendにマッチする

   end/\n

とまったく同じです。

こうした文字のいずれかをその文字自身として表したい場合には、 引用符で囲むか、(後に示す表で説明する) エスケープシーケンスとして表さなければなりません。

Flexには3種類のエスケープシーケンスがあります。 それは、バックスラッシュ\に続けて8進数を使うもの、\xに続けて 16進数を使うもの、\letterという表記法によってある1文字または 特別な表示不可の文字を表すものの3つです。 Cをよく知っている人であれば、この3つがANSI Cのエスケープシーケンスであること に気がつくでしょう。 数値によるエスケープシーケンスは、100パーセント移植性があるわけではなく、 保守を困難にするので避けるべきです。

以下に、文字の使用に関する要約を示します。 ここでは、cが単一の文字を、NNNが8進定数を、 HHが16進定数を表します。

注意:いくつかのバージョンのLexでは、\0を正しく認識 またはマッチしません。 これは、\0がNUL、つまりC文字列の終端文字だからです。 Flexでは、NULをマッチの対象にしても問題はありませんが、 性能には若干影響します。

さらに付け加えると、^演算子と<<EOF>>はルールの先頭にのみ 置くことができます。 また、これらと$/は丸かっこ( )の内部に置くことはできません。 このことはまた、定義の正当性にも影響を及ぼします。 というのは、展開されるときに定義は字義どおりに丸かっこ( )で囲まれるからです。 3 詳細については、DefinitionsFlex and POSIXを参照してください。


Node:Strings, Next:, Previous:Characters, Up:Pattern Matching

Flexにおける文字列

文字列とは、(常に、というわけではありませんが) 多くの場合、引用符によって囲まれる文字のグループです。 エスケープシーケンスが使われないかぎり、 文字列には改行や表示不可の文字を含めることはできません。

-i オプション(詳細については、See Case Insensitive Scanners) を使わないかぎり、大文字/小文字の区別も含めた字義どおりの文字列 に対してマッチが行われます。 引用符付きの文字列については、引用符は認識される文字列には含まれません

たとえば、

string
StrING
"STRING"
\"string\"

はすべて正当な文字列であり、最後のものは引用符も含めてマッチされます。 Flexにおいては文字列には引用符は必須ではありません。 したがって、キーワードのグループにマッチさせる場合、

begin
end
pointer
 ...

"begin"
"end"
"pointer"
 ...

のいずれも正当です。


Node:Character Classes, Next:, Previous:Strings, Up:Pattern Matching

Flexにおける文字のグループ化

Flexでは、文字をグループ化して文字クラスにすることができます。 文字クラスは、文字のグループを角かっこ[ ]で囲むことによって作成されます。 どのような文字でも正当です(表示不可の文字についてはエスケープシーケンス を使います)。 また、文字の範囲をハイフン-を使って指定できます。 文字クラスがルールの中で使われている場合には、 Flexはそのクラスの任意のメンバとマッチさせ、 あたかも単一文字が使われているかのように振る舞います。 たとえば、

[a-z]
[A-Z]*

において、最初の例はaからzまでの任意の単一文字にマッチします。 第2の例はAからZまでの任意の文字が0個以上並んだものにマッチします。

否定文字クラスを表す正規表現を書くこともできます。 否定文字クラスは、(\nも含めて)文字クラスのメンバ以外 であればなんにでもマッチします。 これを行うには、否定すべきクラスの先頭に^を置きます(クラスの外部では ^は異なる意味を持つことに注意してください)。 以下に、正当なクラスの例をいくつかあげます。

[abc]
abcのいずれかにマッチします。
[abc\n]
abc\nのいずれかにマッチします。
[a-z]
ASCII値がaからzまでの範囲にある任意の文字、 すなわち、任意の英小文字にマッチします。
[^a-z]
aからzまでの範囲にある文字以外の任意の文字にマッチします。
[ABcd]
ABcdのいずれかにマッチします。

注意:Flex、およびいくつかのバージョンのLexは、 クラス内における逆方向の範囲を扱うことができません。 したがって、

%%
[z-a9-0]

はエラーメッセージを出力します。 逆方向の範囲は指定しないでください。


Node:Character Class Expressions (Flex 2.5), Previous:Character Classes, Up:Pattern Matching

Flexにおける文字のグループ化(Flex 2.5の補足情報)

Flex 2.5では、文字クラスの中に文字クラス式を含めることができます。

文字クラス式は、形式的には、ある文字集合を識別する名前を [::]で囲んだものです。 Flex 2.5では、以下の文字クラス式が有効です。

[:alnum:] [:alpha:] [:blank:]
[:cntrl:] [:digit:] [:graph:]
[:lower:] [:print:] [:punct:]
[:space:] [:upper:] [:xdigit:]

文字クラス式[:XXX:]は、ANSI CのisXXX()関数 がゼロ以外の値を返す文字の集合に対応します。 唯一の例外は[:blank:]で、isblankは(POSIXでは定義されているものの) ANSI Cでは定義されていないため、Flexでは、マクロIS_BLANK

#define IS_BLANK(c) ((c) == ' ' || (c) == '\t')

のように定義して、これが真となる文字の集合(すなわち、スペースとタブ) を文字クラス式[:blank:]に対応させています。

文字クラス式を使えば、

[a-zA-Z]
[0-9]

[[:alpha:]]
[[:digit:]]

と書くことができます。

また、文字クラスの中に複数の文字クラス式を含めることができるので、

[a-zA-Z0-9]

を、

[[:alpha:][:digit:]]

と書くこともできます(もっとも、この例の場合は、[[:alnum:]]と 書くほうがよいでしょう)。


Node:Regular Expressions, Next:, Previous:Pattern Matching, Up:Flex Descriptions

正規表現

Flexの文字、文字列、クラス、定義、および演算子を組み合わせることで、 正規表現として知られているものが作られます。 (基本単位が数と演算子である)数学表現と同じように、基本的な要素は単純なもの (文字、演算子、文字列、クラス、および定義)ですが、 要素を組み合わせることでより複雑な表現式を作ることができます。 たとえば、cは単一文字の正規表現で、cにマッチします。 ccは2つの正規表現をつないだものを含む正規表現で、 ccにマッチします。 c*は、単一文字の正規表現cと、それに続く演算子* から構成される正規表現で、0個以上のcにマッチします。 正規表現の真のパワーは、個々の要素よりもむしろ、 組み合わせ可能な方法の中にあります。

次の表は、Flexで利用可能な正規表現をすべて示したものです。 ここで、cは(エスケープシーケンスを含む)任意の単一文字を、 rは任意の正規表現を、sは文字列を表します。 表はグループ別に編成してあり、優先度のもっとも高いものがいちばん上にあります。

Unixにおいてパターン検索が必要な場合には正規表現がよく使われますが、 アプリケーションが異なると、 正規表現もよく似てはいるもののまったく同一ではないという点に注意してください。 たとえば、Flex、egrepEmacsはいずれもパターン検索のテンプレート として正規表現を使いますが、それぞれが理解する正規表現は少しずつ異なります。 特に、Flexでは定義が使われますが、egrepEmacsでは使えませんし、 egrepEmacsは単語の先頭と末尾にマッチさせるための\<\>とを提供していますが、Flexは提供していません。 さらに、Emacsはバッファの先頭に対するマッチングや「ファジー」なマッチング などを行うための、 特別な\letterシーケンスをほかにも数多く提供しています。


Node:Start States, Next:, Previous:Regular Expressions, Up:Flex Descriptions

スタート状態

なんらかの条件に基づいて、パターンマッチング処理のルールを活性化することが 便利なときがあります。 たとえば、いくつかのコンピュータ言語では、重複しているスキャンルールの あいまいさを取り除くのを支援するために、パース状態を使います。 また、ある特定の入力が見つかった後でだけ、あるルールを活性化したいという 場合があります。 このような状況に対処するために、Flexはスタート条件または スタート状態と呼ばれる単純なシステムを提供しています。


Node:Start States Explained, Next:, Up:Start States

スタート状態の説明

スタート状態は、あるルールがアクティブになるのはいつであるかをFlexに通知する ブール値のようなものです。 スタート状態は、定義セクションにおいて(排他的スタート状態の場合)%x 、 または(包含的スタート状態の場合)%sを使って宣言されます。

%x start_state_name
%s start_state_name

start_state_nameは一意な名前でなければならない点に注意してください。 つまり、他のスタート状態や定義が同じ名前を持ってはならないということです。 スタート状態は、1つの状態の名前、または、 カンマで区切られた複数の状態の名前を山かっこ< >で囲むことによって、 ルールセクションで参照されます。 スタート状態の参照はルールの先頭になければならず、 1つのルール中には1対の山かっこ< >のみ許されます。 このことは、

%x state1
%s state2
%x state3 state4
%%
<state1>"foo"
<state2,state3,state4>"bar"

が正当であり、

integer [-+]?[0-9]*
%x integer
%s state1,state2,state3
%%
<integer>"foo"
"bar"<state1>
<state1>"bar"<state2,state3>

はすべて不当であることを意味しています。 integerについては同じ名前を持つ定義が存在し、それ以外のものについては スタート状態の参照の位置が正しくないか、複数の参照が存在するからです。

これまでのところでは、Flexが異なる2種類のスタート状態をサポートしている事実 から目をそらしてきました。 2つのスタート状態とは、包含的スタート状態%s)と 排他的スタート状態%x)のことです。 これら2つの相違点は、排他的スタート状態が活性化された場合は、 その状態に属するルールだけが活性化されるのに対して、 包含的スタート状態の場合は、その状態に属するルールとスタート状態への参照 を持たないルールの両方が活性化されるという点にあります。 この違いを示す例をあげると、以下のようになります。

%s state1
%%
<state1>"one" printf("two");
"three"       printf("four");

この場合、state1状態が活性化されている場合はonetwo に置き換え、state1状態が活性化されているか否かにかかわらず threefourに置き換えます。 デフォルトのルールにより、その他のテキストはstdoutに出力されます。 これに対して、

%x state1
%%
<state1>"one" printf("two");
"three"       printf("four");

は、state1状態が活性化されているときはonetwoに置き換え、 state1状態が活性化されていないときのみthreefour に置き換えます。 デフォルトのルールにより、その他のテキストはstdoutに出力されます。

このことは、排他的スタート状態が使われる場合には、マッチしないテキストが stdoutに出力されてはならないのであれば、すべての可能な入力にマッチする ルールを、個々の排他的スタート状態が持たなければならないことを意味 しています。 包含的スタート状態の場合は、あらゆる状態において有効な、 スタート状態への参照を持たないルールを1つ持つ必要があります。

注意: 排他的スタート状態はPOSIXの一部であるにもかかわらず、 Lexではサポートしていません。


Node:Activating States, Next:, Previous:Start States Explained, Up:Start States

状態の活性化

スタート状態の名前を並べただけではあまり役に立ちません。 つまり、スタート状態がいつ活性化されるのかということも制御しなければなりません。 活性化は、アクションの中、または、記述情報内の追加のCコードを記述する領域 の中において、BEGINを使うことで実現されます。 使い方は以下のとおりです。

BEGIN(start_state_name);

例をあげると、以下のようになります。

%x COMMENT
%%
"{"            BEGIN(COMMENT);
<COMMENT>"$R"
<COMMENT>"$I"
<COMMENT>"$M"
    ...
<COMMENT>"}"   BEGIN(INITIAL);

この場合、Pascalのコメントの先頭部分を見つけるとCOMMENT状態に移行し、 コンパイラオプションを認識するようになります。 BEGINは最初の%%の直後(最初のルールの前)において使うこともでき、 この場合はyylex()は常に指定された状態で開始されます。

上の例においては、 定義されていないINITIALという状態があることに注意してください。 この状態は常に利用可能で、 活性化された状態が1つも存在しない時のスキャナの初期状態を表します。 つまり、BEGIN(INITIAL)によって、スキャナの状態が効果的に (もちろん、その時点においてスキャンしている箇所を維持したまま) リセットされることを意味しています。


Node:Start State Notes, Next:, Previous:Activating States, Up:Start States

スタート状態に関する注意

以下に、スタート状態の使用に関する注意をいくつか示します。


Node:Start State Notes (Flex 2.5), Next:, Previous:Start State Notes, Up:Start States

スタート状態に関する注意(Flex 2.5の補足情報)

Flex 2.5では、以下の新機能を利用できます。


Node:Start State Example, Previous:Start State Notes (Flex 2.5), Up:Start States

スタート状態の使用例

プログラミングにおいて、何かをする方法を学ぶのに最良の方法は、 実際にそれをやってみることです。 そのことに留意し、スタート状態をどのように使うことができるかを示す例を 以下にあげます。

/*
 * dates.lex: 日付の異なる形式を識別するために
 *            スタート状態を使用する例
 */

%{
#include <ctype.h>

char month[20],dow[20],day[20],year[20];

%}
skip of|the|[ \t,]* /* この文字の並びを無視する */

mon  (mon(day)?)    /* 曜日の名前の長い形式と短い形式の */
tue  (tue(sday)?)   /* どちらにもマッチするように設定する */
wed  (wed(nesday)?)
thu  (thu(rsday)?)
fri  (fri(day)?)
sat  (sat(urday)?)
sun  (sun(day)?)
 /* 以下はすべての可能な曜日を表す */

day_of_the_week ({mon}|{tue}|{wed}|{thu}|{fri}|{sat}|{sun})

jan  (jan(uary)?)   /* すべての月について同様のことを行う */
feb  (feb(ruary)?)
mar  (mar(ch)?)
apr  (apr(il)?)
may  (may)
jun  (jun(e)?)
jul  (jul(y)?)
aug  (aug(ust)?)
sep  (sep(tember)?)
oct  (oct(ober)?)
nov  (nov(ember)?)
dec  (dec(ember)?)
 /* 以下はすべての可能な月の名前を表す */

first_half  ({jan}|{feb}|{mar}|{apr}|{may}|{jun})
second_half ({jul}|{aug}|{sep}|{oct}|{nov}|{dec})
month       {first_half}|{second_half}
 /*
  * 日、月、年の数値形式
  * これらは重複しているため、正しくパースするには、
  * スタート状態と日付の形式に関するある程度の知識
  * が必要であることに注意
  */

nday         [1-9]|[1-2][0-9]|3[0-1]
nmonth       [1-9]|1[0-2]
nyear        [0-9]{1,4}
 /* 年と日のための拡張子 */

year_ext    (ad|AD|bc|BC)?
day_ext     (st|nd|rd|th)?
  /*
   * このプログラムの最後にあるルールを使ってすべての無関係な
   * テキストを処理するために、非排他的なスタート状態を使う。
   * こうしないと、個々のスタート状態のブロックにルールを追加
   * しなければならなくなる。規模の大きいスキャナにおいては、
   * これは実行可能な選択肢であることが多い。なぜなら、ルール
   * の追加はスキャナのスピードに影響を与えないからである。
   * ここでは、簡潔さを優先させることにする
   */

%s LONG SHORT
%s DAY MONTH   /* 長い形式の日付のために追加した状態 */
%s YEAR_FIRST YEAR_LAST YFMONTH YLMONTH
%%

 /*
  * 曜日は常に最初に置かれ、後ろに続く日付の修飾子として
  * 機能するものと仮定される。よって、曜日は複数の日付形式
  * の間で共用可能である
  */

<LONG>{day_of_the_week} strcpy(dow,yytext);
 /*
  * 月-日-年という形式の日付を処理する
  * パース状態は
  * LONG->[月にマッチ]->DAY->LONG
  * のように遷移する
  */

<LONG>{month}         strcpy(month,yytext); BEGIN(DAY);
<DAY>{nday}{day_ext}  strcpy(day,yytext);   BEGIN(LONG);
 /*
  * 日-月-年という形式の日付を処理する
  * パース状態は
  * LONG->[日にマッチ]->MONTH->LONG
  * のように遷移する
  */

<LONG>{nday}{day_ext} strcpy(day,yytext);   BEGIN(MONTH);
<MONTH>{month}        strcpy(month,yytext); BEGIN(LONG);
 /*
  * 日付の年の部分は最後に置かれるものと考えられる。したがって、
  * 年を見つけたらパースされた日付を表示できる。もち
  * ろん、日付として不当なものであればゴミが出力されることになる
  */

<LONG>{nyear}{year_ext} {
                          printf("Long:\n");
                          printf("  DOW   : %s \n",dow);
                          printf("  Day   : %s \n",day);
                          printf("  Month : %s \n",month);
                          printf("  Year  : %s \n",yytext);
                          strcpy(dow,"");
                          strcpy(day,"");
                          strcpy(month,"");
                        }
 /*
  * 日-月-年という形式の日付を処理する
  * SHORT状態で数値形式の日を見つけた場合は、年が日付の最後の部分
  * になると仮定する
  * パース状態は
  * SHORT->[日にマッチ]->YEAR_LAST->YLMONTH->SHORT
  * のように遷移する
  */

<SHORT>{nday}        strcpy(day,yytext);  BEGIN(YEAR_LAST);
<YEAR_LAST>{nmonth}  strcpy(month,yytext);BEGIN(YLMONTH);
<YLMONTH>{nyear}     strcpy(year,yytext); BEGIN(SHORT);
 /*
  * 年-月-日という形式の日付を処理する
  * SHORT状態で数値形式の年を見つけた場合は、日が日付の最後の部分
  * になると仮定する
  * パース状態は
  * SHORT->[年にマッチ]->YEAR_FIRST->YFMONTH->SHORT
  * のように遷移する
  */

<SHORT>{nyear}        strcpy(year,yytext); BEGIN(YEAR_FIRST);
<YEAR_FIRST>{nmonth}  strcpy(month,yytext);BEGIN(YFMONTH);
<YFMONTH>{nday}       strcpy(day,yytext);  BEGIN(SHORT);
 /*
  * 数値形式の日付では、年は最初になることも最後になることも可能。
  * したがって、パースしたものをいつ表示すべきかを示すのに改行を使う
  */

<SHORT>\n               {
                          printf("Short:\n");
                          printf("  Day   : %s \n",day);
                          printf("  Month : %s \n",month);
                          printf("  Year  : %s \n",year);
                          strcpy(year,"");
                          strcpy(day,"");
                          strcpy(month,"");
                        }
 /*
  * 以下により、短い(数字)形式と長い(英数字)形式とを切り換える
  */

long\n     BEGIN(LONG);
short\n    BEGIN(SHORT);
 /*
  * 以下のルールは、無関係なテキストを見つけて破棄する
  * (マッチされたテキストはデフォルトではECHOされないが、
  *   マッチされなかったテキストはECHOされる。ピリオドは
  *   改行以外のすべての文字を見つける。改行は\nによって
  *   見つけられる)
  */

{skip}*
\n
.

この例は、さまざまな形式の日付をスキャンし、構成単位に分割します。 たとえば、以下のものを正しくスキャンし、日付の個々の部分を識別します。

short
1989:12:23
1989:11:12
23:12:1989
11:12:1989
1989/12/23
1989/11/12
23/12/1989
11/12/1989
1989-12-23
1989-11-12
23-12-1989
11-12-1989
long
Friday the 5th of January, 1989
Friday, 5th of January, 1989
Friday, January 5th, 1989
Fri, January 5th, 1989
Fri, Jan 5th, 1989
Fri, Jan 5, 1989
FriJan 5, 1989
FriJan5, 1989
FriJan51989
Jan51989

ファイルの最初の部分では、月および日付の異なる部分に使われる数字形式を単に 定義しています。 この例では、ある特定の方法でスキャン処理が進行するよう強制するために、 スタート状態を使います。 たとえば、行の先頭で1989を見つければ、それが日と月の組み合わせ ではなく年であり、したがって、日付の次の部分が月に違いないことがわかり、 スキャン処理がそのとおりに進むよう強制します。 このことにより、非常に単純な状態駆動のパーサを効果的に作成したことになり、 日付をその構成要素にうまく分割できるようになります(このスキャナの内部 で起こっていることをフローチャートに描いてみれば、 このことが明瞭に見てとれるでしょう)。

本書中の他のプログラム例と同様に、この例も

flex -i dates.lex
cc -o dates lex.yy.c -lfl

を実行することによりコンパイルできます。 また、examplesサブディレクトリにおいて単にmake datesを 実行することにより、コンパイルすることもできます。


Node:%option (Flex 2.5), Previous:Start States, Up:Flex Descriptions

%option(Flex 2.5の補足情報)

Flex 2.5では、スキャナ定義ファイルの中でさまざまなオプションを指定できます。 オプションを指定するには、スキャナ定義ファイルの先頭 (最初の%%よりも前の部分)に、%option指示子を記述します。

ほとんどの%option指示子は、以下の形式で指定されます。

%option option_name

オプションoption_nameの指定を無効にするためには、 オプション名の前にnoを付けます。

%option nooption_name

以下に、コマンドラインオプションと同等の効果を持つ%option指示子 を示します。 各コマンドラインオプションの意味については、Command Line SwitchesCommand Line Switches (Flex 2.5)を参照してください。

%option 7bit
-7オプション
%option 8bit
-8オプション
%option align
-Caオプション
%option backup
-bオプション
%option batch
-Bオプション
%option c++
-+オプション
%option caseful
-iオプションの否定
%option case-sensitive
-iオプションの否定
%option case-insensitive
-iオプション
%option caseless
-iオプション
%option debug
-dオプション
%option default
-sオプションの否定
%option ecs
-Ceオプション
%option fast
-Fオプション
%option full
-fオプション
%option interactive
-Iオプション
%option lex-compat
-lオプション
%option meta-ecs
-Cmオプション
%option output="file"
-ofileオプション
%option perf-report
-pオプション
%option prefix="prefix"
-Pprefixオプション
%option read
-Crオプション
%option stdout
-tオプション
%option verbose
-vオプション
%option warn
-wオプションの否定

次に、コマンドラインオプションでは代替できない%option指示子を示します。

%option array
yytextcharの配列として定義します。 これは、 %arrayと同じです。
%option always-interactive
入力を常に対話的に扱うスキャナを生成するよう指示します。 これと%option never-interactiveのどちらも指定されない場合、 生成されたスキャナは、ファイルをオープンするたびにisatty()を呼び出して、 入力を対話的に(1文字ずつ)読み込むべきか否かを決定します。
%option main
生成されるスキャナに、以下のようなmain()関数を組み込むよう指示します。
int main()
	{
	yylex();
	return 0;
	}

これは、暗黙のうちに%option noyywrapを指定します。

%option never-interactive
入力を常に対話的に扱わないスキャナを生成するよう指示します。 これと%option always-interactiveのどちらも指定されない場合、 生成されたスキャナは、ファイルをオープンするたびにisatty()を呼び出して、 入力を対話的に(1文字ずつ)読み込むべきか否かを決定します。
%option pointer
yytextcharに対するポインタとして定義します。 これは、%pointerと同じです。
%option reject
スキャナ定義ファイルの中でREJECTが使われていることを、Flexに通知します。 Flexは通常、定義ファイルの中でREJECTが使われているか否かを自分で 調査しますが、この%option指示子の指定は、Flex自身による判定結果に 優先します。
%option stack
スタート状態スタック(Start State Notes (Flex 2.5)を参照) を使用するためには、この%option指示子を指定しなければなりません。
%option stdinit
yyinstdinで、yyoutstdoutで、 それぞれ初期化します。 この%option指示子が指定されない場合、 あるいは、%option nostdinitが指定された場合、 yyinyyoutは、(FILE *)0(NULLポインタ)で初期化されます。
%option unput
%option nounputが指定されると、生成されるスキャナの中に、 関数unput()が組み込まれません。 5
%option yy_pop_state
%option noyy_pop_stateが指定されると、 生成されるスキャナの中に、関数yy_pop_state()が組み込まれません。 ただし、%option stackが指定されていない場合は、 %option noyy_pop_stateの指定の有無にかかわらず、 関数yy_pop_state()は組み込まれません。
%option yy_push_state
%option noyy_push_stateが指定されると、 生成されるスキャナの中に、関数yy_push_state()が組み込まれません。 ただし、%option stackが指定されていない場合は、 %option noyy_push_stateの指定の有無にかかわらず、 関数yy_push_state()は組み込まれません。
%option yy_scan_buffer
%option noyy_scan_bufferが指定されると、 生成されるスキャナの中に、関数yy_scan_buffer()が組み込まれません。
%option yy_scan_bytes
%option noyy_scan_bytesが指定されると、 生成されるスキャナの中に、関数yy_scan_bytes()が組み込まれません。
%option yy_scan_string
%option noyy_scan_stringが指定されると、 生成されるスキャナの中に、関数yy_scan_string()が組み込まれません。
%option yy_top_state
%option noyy_top_stateが指定されると、 生成されるスキャナの中に、関数yy_top_state()が組み込まれません。 ただし、%option stackが指定されていない場合は、 %option noyy_top_stateの指定の有無にかかわらず、 関数yy_top_state()は組み込まれません。
%option yyclass="classname"
これは、-+オプションが指定されている場合(すなわち、C++スキャナを 生成する場合)にのみ有効です。 これにより、classnameによって指定される名前のクラスが、 yyFlexLexerのサブクラスとして定義されます。 実際にスキャン処理を実行するコードは、クラスclassnameの メンバ関数yylex()classname::yylex())に実装されます。 詳細については、Flex and C++ (Flex 2.5)を参照してください。
%option yylineno
入力の行数をカウントして大域変数yylinenoに保持するスキャナを 生成するよう指示します。
%option yymore
スキャナ定義ファイルの中でyymore()が使われていることをFlexに通知します。 Flexは通常、定義ファイルの中でyymore()が使われているか否かを自分で調査 しますが、この%option指示子の指定は、Flex自身による判定結果に優先します。
%option yywrap
%option noyywrapが指定されると、yywrap()はマクロとして、
#define yywrap() 1

のように定義されます。 この結果、ファイルの終端を検出したときに、スキャナは、 ほかにスキャンすべきファイルは存在しないと判断するようになります。


Node:Interfacing to Flex, Next:, Previous:Flex Descriptions, Up:Top

Flexとのインターフェイス

この章ではCおよびBisonと一緒にFlexを使う方法を説明します。 6 C、Bisonのそれぞれについて非常に細かい説明が必要なため、 本章は2つの部分に分かれています。 その両方に、全般的なインターフェイス概念に関する節とプログラム例を 示す節があります。


Node:Flex and C, Next:, Up:Interfacing to Flex

FlexとC

Flexに対するCの主要なインターフェイスは、 以下にあげるルーチンと変数によるものです。 以下の節を読む際には、いくつかの細かな部分でFlexとLexとの間に相違点がある ということを意識しておいてください。 Lexが提供していない関数がいくつかありますし、宣言の内容が違うものもあります。 こうした相違点は、通常大きな問題にはなりません。 というのは、相違のある関数は一般的にはあまり使われていないからです。 相違点に関する詳細については、Flex and LexおよびFlex and POSIX を参照してください。

関数
説明とプログラム例
yylex()
yylex()は実際のスキャン処理を行う関数です。 ファイル(デフォルトはstdin)を読み込み、パターンマッチングを行い、 パターンに関連付けされたアクションを実行します。 デフォルトでは、入力の終端に達するまでマッチングを行い、終端に達したところで ゼロを返します(returnを使って、呼び出し側のプログラムにほかの値を返す ことは可能です。 これは、Flex and 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コード領域 になければならないという点に注意してください。

yyin
yyinは、 yylex()が文字を読み込む元となるファイルです。 デフォルトはstdinですが、fopen()を使って変更できます。 yyinを読み込むデフォルトの方法は、 複数文字から成るブロックを一度に読むというものです。 これは、YY_INPUTマクロによって変更できます。 YY_INPUTマクロは、ファイルではなく文字列をスキャンするためのスキャナ を生成するのに便利です。 YY_INPUTを定義する方法は以下のとおりです。
YY_INPUT(buffer,result,max_size)

ここで、bufferは入力バッファ、resultは読み込まれた文字数 がセットされる変数、max_sizebufferのサイズです。 以下に、一度に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コード領域になければなりません。

yyout
yyoutはスキャナがECHOの出力を書き込むファイルです。 デフォルトはstdoutですが、 これもfopen()を呼び出すことで変更できます。
yytext
yytextは最後にマッチされた文字列、 つまり最後に認識されたトークンを含む大域変数です。 yytextの正しい外部定義は、Lexの場合のcharの配列とは異なり、 charに対するポインタ型である点に注意してください。 7 つまり、yytext
extern char yytext[];

ではなく、常に

extern char *yytext;

のように宣言されなければならないということです。

このようになっている理由は性能です。 yytextが配列であると、スキャナ中でそれを操作するコードは、 コピー処理をたくさん行う必要があります。 これに対してyytextがポインタである場合には、 このようなことは必要ありません。

通常は、yytextは変更すべきではありません。 yytextの内容が変更される必要がある場合には、 代わりのバッファが使われるべきです(examplesサブディレクトリの yymore2.lexファイルでは、yytextを直接操作する技法が 示されています。 ただし、このようなやり方はおすすめできません)。

yyleng
yylengは、最後に認識されたトークンの長さを保持する大域変数です。
yywrap()
yywrapは、yyinの終端に達したときに呼び出される関数です。 この関数がTRUE(ゼロ以外)を返すとスキャナは終了し、FALSE(ゼロ) を返すと、yyinが次の入力ファイルを指すように設定されたものと仮定して、 スキャン処理が続行されます。

現在のところyywrap()は、常に1を返すよう定義されているマクロです。 このため、再定義するには、まず最初に#undefで定義解除 しなければなりません。 Lexでは、yywrap()は関数です。 Flexも将来のある時点で、これを関数として定義することになるでしょう。 8

yymore()
yymore() は、 次に認識されるトークンでyytextの内容を更新するのではなく、その時点の yytextの内容の後ろにそのトークンを追加するようFlexに通知する関数です。 したがって、以下の例に対してfoobarという文字の並びを入力として与えると、 stdoutfoofoobarという文字の並びが書き込まれます。
%%
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文字以外のすべてを戻します。 戻された文字の並びは、次のトークンをマッチするのに使われ、yylengyytextには、この変化を反映した値が設定されます。 引数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++コンパイラを使ってコンパイルされる場合は、 この関数inputyyinputという名前になります。 これは、inputという名前が同一名のC++ストリームと衝突するからです。 また、Flexではinput()yytextの内容を破壊しますが、 Lexではyytextは変更されずそのまま残ります。 これは将来のリリースで修正される予定です。

unput(c)
unput()は、 文字cが次にスキャンされる文字になるように、 文字cを入力ストリームに置く関数です。 たとえば、
%%
foo  unput('b');

foobで置き換えます。 これは、fooにマッチしてbを戻し、このbが次にスキャンされる 文字になるからです。 デフォルトのルールにより、bstdoutに書き込まれます。

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_FILE
yyinが新しいファイルを指すよう変更され、 処理が継続されるべきであるということを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();
}

ECHO
yytextの内容をyyoutに書き込むマクロです。
REJECT
REJECTは、 その時点においてマッチしているものを受け入れず、次にもっともよくマッチするもの を受け入れるようスキャナに通知するマクロです。 スキャナはマッチするものの中で最長のものを探し、マッチするものが2つあって その長さが同じ場合は、記述ファイルにおいて最初に定義されているほうを選択します。 つまり、認識されるテキストの長さは、同一の長さになることも 短くなることもあるということを意味しています。 REJECTを使った後は、yytextyylengは新しい値を取ります。 REJECTに関して知っておくべき重要な点が2つあります。 1つは、REJECTは分岐命令であり、決して返ってこないので、 REJECTの後ろに記述されたアクションは実行されないということです。 もう1つは、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を使わずに、 より効率的なスキャナにすることができるからです。 これは、スタート状態の使用例に示しています。

BEGIN
BEGINは、 スキャナをある特定のスタート状態にするためのマクロです。 BEGINに続く名前はスタート状態の名前です。 たとえば、
%x FLOAT
%%
floats   BEGIN(FLOAT)
<FLOAT>some_rule some_action
       ...

は、 floatsという単語がマッチしたときに、 スタート状態をFLOATに設定します (詳細については、see Start States Explained)。

YY_USER_ACTION
YY_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_INIT
YY_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_BREAK
YY_BREAKはマクロです。 インターフェイス的な機能というよりも、むしろ生成されるコードを変更するために 使うことができるものです。

スキャナ中において、すべてのアクションは1つの大きなswitch文の要素 であり、デフォルトでCのbreak;文に置き換えられるYY_BREAK によって区切られます。 ルールのアクション部が多くのreturn文を含んでいる場合、コンパイラが statement not reachedというエラーメッセージをたくさん出力 するかもしれません。 YY_BREAKを再定義することによって、 この警告メッセージの出力を止めることが可能です。 再定義は、セミコロンを含む正当なCの文でなければなりません
注意:YY_BREAKを再定義して空にするのであれば、 アクションの最後は必ずreturn;break;になるようにしてください。


Node:Flex and C (Flex 2.5), Next:, Previous:Flex and C, Up:Interfacing to Flex

FlexとC(Flex 2.5の補足情報)

Flex 2.5では、前節(Flex and 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()
次にトークンのマッチ処理が行われるときに、行頭を表す^を含むルールの 適用が試みられるようなコンテキスト情報がセットされている場合には、 ゼロ以外の値を返します。 それ以外の場合は、ゼロを返します。


Node:An Example of Flex and C, Next:, Previous:Flex and C (Flex 2.5), Up:Interfacing to Flex

FlexとCの簡単なプログラム例

ある単語が現れたときに、それを別の単語に置き換える必要が生じること がよくあります。 たとえば、ある名前が現れるたびに、それをある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.

このプログラムがうまく動作するのは、yyinyyoutがデフォルトでは stdinstdoutにそれぞれ割り当てられ、かつ、 デフォルトのアクションが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です。


Node:Flex and Bison, Next:, Previous:An Example of Flex and C, Up:Interfacing to Flex

FlexとBison

Bisonは、Flexと同様、ある記述情報を受け取って、 それをもとにCのコードを生成するプログラムです。 両者の違いは、 BisonがCやPascalのような言語の文法に関する記述情報を入力として受け取り、 その記述情報からパーサを生成する点にあります。 FlexとBisonを結合することにより、言語の字句解析と構文解析の両方を 処理できるようになります(これは、コンパイラデザインにおいてもっとも 容易に自動化できる部分です)。

生成されるパーサが機能するためには、 Bisonはyylex()という関数を必要とします。 この関数はユーザーによって提供され、呼び出されたときに、 パースされている言語のある要素を表す整数値をBisonに返します。 Flexにおいてスキャン処理を行うルーチンはyylex()であり、 デフォルトでは整数値を返します。 これにより、FlexとBisonを一緒に使うのは非常に簡単になります。

警告: 以下の節では、読者がBisonの基本的なパーサの宣言を 理解しているものと仮定します。 Bisonを使った経験のない人には、パーサの定義は混乱をもたらす可能性 がありますので、先に進む前にぜひBisonのマニュアル11を読んでください。 Bisonに興味のない人は、この節全体を飛ばしてもかまいません。


Node:Interfacing Flex and Bison, Next:, Up:Flex and Bison

FlexとBisonのインターフェイス

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のマニュアルを参照してください。


Node:YYSTYPE and yylval, Previous:Interfacing Flex and Bison, Up:Flex and Bison

YYSTYPEとyylval

FlexからBisonに対して、単なる整数値以上の情報を渡す必要の生じること がよくあります。 たとえば、コンパイラにおいては、どのような種類のトークンが認識されたか だけではなく、そのトークンの値についても知る必要のある場合がよくあります。 文字列、文字、および数値定数などが良い例です。 ここで問題なのは、どのようにしてFlexにこうした情報を返させるかです。

その答えは、Bisonが持っている%union文です。 これは、YYSTYPEという型を定義するものです。 YYSTYPEは、パーサ定義中において使われるすべての正当なデータ型の 共用体(union)です。 Bisonが現在のパース状態に関連づけたデータを保存するために使う、 YYSTYPE型の変数yylvalというものがあり、Flexからyylval に値を設定することができるので、トークンの型だけでなく、それ以上の情報を Bisonに返すことができます。

Bisonにおいて%unionを宣言して-dオプションを使うと、Bisonは .tab.hという拡張子を持つファイルを作成して、そこにトークンの定義情報 だけでなく、YYSTYPEyylvalの宣言も含めます。 したがって、yylvalにアクセスするためにしなければならないことは、 Flexの定義情報の中にこの.tab.hファイルをインクルードすることだけです。 これは、追加のCコードセクションにおける定義の先頭でインクルード しなければなりません(see Interfacing Flex and Bison)。

注意:初期のバージョンのBisonは、自動的にYYSTYPEyylvalの宣言を生成しません。 この場合には、より新しいバージョンのBisonを入手するか、もしくは、Flexの 定義ファイルの先頭においてYYSTYPEyylvalを宣言する必要 があります。


Node:Another Example of Flex and Bison, Next:, Previous:Flex and Bison, Up:Interfacing to Flex

FlexとBisonのもう1つのプログラム例

コードを読むのは、プログラミングのしかたを学ぶ良い方法です。 そこで、Flex、Bisonのインターフェイス例をもう1つ示すことにします。 次の例では、拡張してデータベースを操作するために使うことができるような、 小規模な言語のための簡単なパーサを作ります。


Node:The Database Language, Next:, Up:Another Example of Flex and Bison

インターフェイス言語

データベースとのインターフェイス言語は、英語の非常に小さなサブセットになります。 文法はおおよそ以下のようになります。

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のマニュアル を参照してください。


Node:The Implementation, Next:, Previous:The Database Language, Up:Another Example of Flex and 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を使うのであれば、このファイルは必要ありません。


Node:Notes on the Implementation, Previous:The Implementation, Up:Another Example of Flex and Bison

実装に関する注意

以下に実装に関する注意を示します。


Node:Flex and C plus plus (Flex 2.5), Previous:Another Example of Flex and Bison, Up:Interfacing to Flex

FlexとC++(Flex 2.5の補足情報)

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++クラス (FlexLexeryyFlexLexer)が定義されています。

FlexLexerは、C++スキャナクラスが実装すべきインターフェイスを構成する 抽象仮想関数を定義するクラスです。

FlexLexerの持つメンバを以下に示します。

char* yytext
最後に認識された文字列(トークン)を保持します。
int yyleng
最後に認識された文字列(トークン)の長さを保持します。
int yylineno
%option yylinenoが指定されている場合は、入力された行数を保持します。 それ以外の場合は、固定値1を持ちます。
int yy_flex_debug
この値がゼロ以外の場合、C++スキャナはデバッグ出力を行います。

次に、FlexLexerの持つメンバ関数のうち、 抽象仮想関数ではないものを以下に示します。

const char* YYText()
メンバyytextの値を返します。
int YYLeng()
メンバyylengの値を返します。
int yylex(istream* new_in, ostream* new_out = 0)
new_innew_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に代入します。12

次に、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.ccclassname::yylex()が生成されます。 クラスclassnameyyFlexLexerのサブクラスとして定義すること によって、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>


Node:Other Flex Features, Next:, Previous:Interfacing to Flex, Up:Top

Flexの他の特徴

ここでは、Lexが提供していない機能や一般にはあまり使われない機能を説明します。 Flexはほぼ100パーセントLex互換ですが、Lexよりも後に実装されたため、 性能的により優れており、 また、広範な用途に使えるスキャナをより簡単に作成することができるよう、 特別な機能を提供しています。


Node:Case Insensitive Scanners, Next:, Up:Other Flex Features

大文字/小文字を区別しないスキャナ

多くの言語は、その識別子において大文字/小文字を区別しません(Pascal、 BASICFORTRANなど)。 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では、パターンの複雑さは大して影響しません)。

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


Node:The -i Switch, Up:Case Insensitive Scanners

-iオプション

Flexは、 この問題を簡単に解決するための方法を提供しています。 コマンドラインで-iオプションを使うことによって、入力情報の 大文字/小文字を区別しないスキャナを生成するよう、Flexに対して 通知することができます。 つまり、Flexでは上記のようなテクニックを使う必要がないということを 意味しています。 たとえば、

%%
begin        return(BEGIN_SYM);
end          return(END_SYM);

は、 -iオプションを使うことによって、BEGINbeginBeGiN、およびこれ以外のすべての大文字/小文字の組み合わせにマッチします。 これは、Lexにおいて同様のことを行うための方法よりも、はるかに簡単です。

-iオプションには1つ注意すべき点があります。 それは、スキャナが大文字/小文字を区別しないだけで、その 変換まではしてくれないということです。 つまり、Pascalにおいてシンボル名をハッシュしたいような場合、自分でシンボル名 を大文字または小文字に変換しなければならないことを意味しています。 そうしないと、FOOfooは異なるものとして扱われます。 これは、シンボルを保存するルーチンの中で対処できますし、 YY_USER_ACTIONを使うことによって対処することもできます。 これを実現する方法の例については、Flex and Cにおける YY_USER_ACTIONの説明を参照してください。


Node:Interactive Scanners, Next:, Previous:Case Insensitive Scanners, Up:Other Flex Features

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

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

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

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

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

-Iオプションにおけるマイナス面は、通常はごくわずかなので、 入力情報がどこからくるのかが確かではなく、性能向上のための最適化 を施す可能性を諦めてもかまわないのであれば、コマンドラインで -Iオプションを使ったほうがよいでしょう。


Node:Table Compression and Scanner Speed, Next:, Previous:Interactive Scanners, Up:Other Flex Features

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

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

-fまたは-Cf
フルテーブル(full table)を生成します。 このテーブルはまったく圧縮されず、サイズが大きくなりますが、スピードは 速くなります。 このオプションが指定された場合は、アクションの部分にREJECT を使うことはできない点に注意してください。

注意:-fフラグと-Fフラグでは、 生成するテーブルに相違があります。 -fフラグはフルテーブル(full table)を生成し、 -Fフラグはファストテーブル(fast table)を生成します。 ファストテーブルとは、スピードを最大限にするよう最適化されたテーブル形式であり、 一方、フルテーブルには最適化は一切施されません。 結果はよく似ていますが、テーブルサイズは大きく異なる可能性があります。

-Fまたは-CF
ファストテーブル(fast 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
このオプションを使うと、性能にはわずかしか影響を及ぼさずに、 テーブルのサイズをかなり小さくすることができます。 -Ceが使われると、 Flexは同等クラスequivalence classes)を作成します。 同等クラスとは、同一の方法で使われる文字のグループです。 たとえば、使われる数字が集合[0-9]の範囲に限定されるのであれば、 0から9までの数は同等クラスの中に置かれることになります。
-Cfe、-CFe
同等クラスを持つファストテーブルです。 このオプションによって生成されたスキャナもまた高速であり、かつ、 -Cfあるいは-CFを指定して生成されたスキャナと比較して、 サイズもはるかに小さくなる可能性があります。 サイズまたはスピードの一方を優先させる必要がないのであれば、 これは良い組み合わせです。
-Cm
メタ同等クラスmeta-equivalence classes)を使用します。 これは、一緒に使われることが多い文字の集合、または (同等クラスが使われている場合には)同等クラスです。 同等クラスを使う場合よりも性能はさらに悪くなりますが、これは多くの場合、 テーブルサイズを小さくするのに非常に効果的な方法です。
-Cem
デフォルトのテーブル圧縮です。 このオプションで生成されるスキャナは、Flexが生成するスキャナの中で 事実上もっとも小さく、かつ、もっとも性能の劣るものになります。
-C
-Cオプション単体では、同等クラスやメタ同等クラスを使わずに テーブルを圧縮します。

注意:-Cxxオプションは、コマンドライン上には1つだけ 指定すべきです。 というのは、このうち最後に見つかったオプションだけが実際の効果を持つからです。 したがって、

flex -Cf -Cem foo.l

は、Flexに-Cemオプションを使わせることになります。

Flexのデフォルトの動作は、 コマンドライン上で-Cemオプションを使った場合に相当します。 この動作では圧縮を最大限に行うことになり、一般的にはもっとも遅いスキャナ が生成されることになります。 こうした小さなテーブルはより速く生成され、コンパイルもより速く実行されるので、 デフォルトは、開発段階では非常に便利です。 スキャナのデバッグが終了した後は、より高速な(そして通常はよりサイズの大きい) スキャナを作成することができます。


Node:Translation Tables, Next:, Previous:Table Compression and Scanner Speed, Up:Other Flex Features

翻訳テーブル

翻訳テーブルは、文字をグループにマップするのに使われます。 このテーブルはLexの持つ機能の1つですが、POSIXでは定義されていません。 Flexでも翻訳テーブルを使うことはできますが、サポート対象外の機能です。 Flexにおいては翻訳テーブルは不要です。 というのは、Flexには-iオプションによる同等クラスというものがあり、 これが翻訳テーブルと同等の機能を実現しているからです (see -iオプション)。 翻訳テーブルの機能は、互換性のためだけに存在する余分な機能です。 したがって、翻訳テーブルを使うことはおすすめできません。 翻訳テーブルを使いたいのであれば、定義ファイルの先頭の定義セクション において定義しなければなりません。

翻訳テーブルの一般的な形式は以下のとおりです。

%t
1 ABCDEFGHIJKLMNOPQRSTUVWXYZ
2 0123456789
%t
%%

これは、 AからZまでの任意の文字がルールの中で使われている場合、 そのパターンはAからZまでのどの文字にもマッチする ということを意味しています。 したがって、A(BC)X(YZ)はまったく同一であるということになります。


Node:Multiple Input Buffers, Next:, Previous:Translation Tables, Up:Other Flex Features

複数の入力バッファ

スキャナが、複数のファイルからの入力を処理できるということが必要になる状況は、 たくさんあります。 たとえば、多くのPascalの実装では、コンパイル時に複数のファイルを取り込むこと を許していますし、Cでは、スキャナもしくはプリプロセッサが#include文を 処理できなければなりません。 このことが意味しているのは、スキャナは、現在のスキャン処理の コンテキストを保存してから新しいコンテキストに変更し、その後で、 以前の状態と完全に一致する状態に復帰できなければならないということです。

Flexスキャナは、 スキャン処理のコンテキストを維持するために余分の処理が必要になるような、 大きな入力バッファを使っています。 しかしFlexは、複数の入力バッファの作成、切り替え、削除が非常に簡単に 行えるような特別な機能を提供しています。


Node:Buffer Manipulation, Next:, Up:Multiple Input Buffers

バッファを操作する関数

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
使用中のカレントバッファを返すマクロです。

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


Node:Buffer Manipulation Functions (Flex 2.5), Next:, Previous:Buffer Manipulation, Up:Multiple Input Buffers

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

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

YY_BUFFER_STATE yy_new_buffer(FILE *file, int size)
yy_create_bufferの別名です。
void yy_flush_buffer(YY_BUFFER_STATE buffer)
引数で指定されたバッファの内容を破棄し、バッファの先頭2バイトに 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)
NULLで終わる文字列をスキャンするための入力バッファを作成します。 実際には、引数strに指定された文字列の長さを調べて、 次に説明するyy_scan_bytes()を呼び出し、その戻り値を返します。
YY_BUFFER_STATE yy_scan_bytes(const char *bytes, int len)
bytesから始まる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)
baseから始まるsizeバイトのメモリ領域をスキャンするためのバッファ を作成します。 メモリ領域の末尾2バイトは、YY_END_OF_BUFFER_CHAR\0) でなければなりません。 この末尾2バイトは、スキャン処理の対象にはなりません。

引数で指定されたメモリ領域の末尾2バイトがYY_END_OF_BUFFER_CHARでない場合 は、yy_scan_buffer()はバッファを作成せず、NULLポインタを返します。


Node:Example of Multiple Buffers, Previous:Buffer Manipulation Functions (Flex 2.5), Up:Multiple Input Buffers

複数バッファを使うプログラム例

複数のバッファを使うというアイデアを理解するための助けとして、 インクルードすべきファイルを探す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 Start States


Node:End-Of-File Rules, Previous:Multiple Input Buffers, Up:Other Flex Features

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

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

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


Node:Optimization, Next:, Previous:Other Flex Features, Up:Top

スキャナの最適化

デバッグをしている間は、スキャナの性能は通常それほど重要ではなく、 Flexのデフォルトの設定で十分です。 しかしデバッグ終了後は、スピード、またはサイズの面でスキャナを 最適化したくなることもあるでしょう。 ここでは、スキャナを最適化するのによく使われる手法をいくつか紹介します。


Node:Optimizing for Speed, Next:, Up:Optimization

スピードの最適化

多くのプログラムは、字句解析の処理に多くの時間を費やします。 したがって、スキャナの最適化はかなり大きな性能改善に結びつくことが多いのです。 Flexによるスキャナは、Lexによるスキャナと比較するとかなり高速になる傾向 がありますが、特定の構成もしくはアクションによって、性能に大きな影響を及ぼす ことができます。 注意すべき点は以下のとおりです。

  1. テーブルの圧縮
    どのような圧縮も結果的にスキャナを遅くします。 したがって、スピードが心配であれば、常にコマンドラインで -fオプションまたは-Fオプションを使ってください。 テーブルの圧縮とスピードに関連するオプションに関する詳細な解説については、 See Table Compression and Scanner Speed
  2. REJECT
    スピードに対してもっとも大きな影響を及ぼします。 これが使われるとすべてのマッチ処理が遅くなります。 というのは、スキャナは、マッチする前の状態に自身を復旧する必要があるからで、 このようなことが必要のない場合と比較して、 より多くの内部的な保守作業を行わなければならないからです。 スピードが重要な場合には、使わないようにしてください。
  3. バックトラッキング
    スキャナがあるテキストにマッチするために「逆行」しなければならないことを、 バックトラッキングといいます。 これは、スキャナの性能に悪い影響を及ぼしますので、 スピードがもっとも重要である場合には避けるべきです。 圧縮されたテーブルは常にバックトラッキングを発生させるので、 -fオプションまたは-Fオプションを使わない場合は、 ルールからバックトラッキングを削除しようとするのは時間の無駄です。 スキャナからバックトラッキングを削除することに関する詳細な情報については、 See Removing Backtracking
  4. 可変長後続コンテキスト(variable trailing context)
    可変長後続コンテキストとは、 あるルールの先頭部分と後続部分の両方が固定長でないような場合を指します。 性能の観点からはREJECTと同じくらい悪影響を及ぼすもので、 可能な場合にはいつでも避けるべきです。 この例を示すと、以下のようになります。
    %%
    linux|hurd/(OS|"Operating system")
    

    これは、以下のように分割すべきです。

    linux/OS|"Operating system"
    hurd/OS|"Operating system"
    

    こうすることによって、問題は解消されます。

  5. 行の先頭を表す演算子
    ^演算子は、性能に不利な影響を及ぼします。 スピードがもっとも重要な場合には、使わないでください。
  6. yymore()
    yymoreを使うと性能を低下させます。 スピードがもっとも重要な場合には、使わないでください。
  7. テキスト長
    スキャナの性能は、マッチするテキストの長さによっても影響を受けます。 常に長い文字列にマッチするような場合には、スキャナは高速に実行されます。 というのは、yytext環境をセットアップする必要がないからです。 スキャナの実行時間のほとんどは、 内部の高速なマッチングループの中で費やされることになります。
  8. NUL
    Flexは、 NULを含むトークンをマッチするのに時間がかかります。 この場合には、 短いテキストにマッチするようにルールを記述したほうがよいでしょう。


Node:Removing Backtracking, Up:Optimizing for Speed

バックトラッキングの削除

スキャナからバックトラッキングを削除することは、 スキャナの性能にかなりの影響をもたらします。 残念ながら、バックトラッキングの削除はかなり複雑な作業になる可能性があります。 たとえば、

%%
hurd     return(GNU_OS);
hurdle   return(JUMP);
hurdled  return(JUMPED);

では、 バックトラッキングが発生します。 スキャナがhuをマッチし、次の文字がrではない場合、 マッチされなかったテキストをECHOするデフォルトのルールを使って huを処理するために、スキャナはバックトラッキングを 行わなければなりません。 同じことがdeについても適用されます(これは、何かに マッチするようスキャナが試み続けるということが、もはやできないからです。 この場合、スキャナはデフォルトのルールを適用し、yyext環境をリセット しなければなりませんが、いずれも時間のかかる処理です)。

コマンドラインオプション-bを使うことで、バックトラッキングを 発生させている原因に関する情報を知ることができます。 これにより、バックトラッキングに関する情報を含むlex.backtrackという ファイルが生成されます。 上記の例の場合、このファイルは以下のような情報を含みます。

State #6 is non-accepting -
 associated rule line numbers:
        2       3       4
 out-transitions: [ r ]
 jam-transitions: EOF [ \000-q  s-\177 ]
State #7 is non-accepting -
 associated rule line numbers:
        2       3       4
 out-transitions: [ d ]
 jam-transitions: EOF [ \000-c  e-\177 ]
State #9 is non-accepting -
 associated rule line numbers:
        3       4
 out-transitions: [ e ]
 jam-transitions: EOF [ \000-d  f-\177 ]

Compressed tables always backtrack.

バックトラッキング情報はセクションに分割され、個々のセクションにおいて、 バックトラッキングを引き起こしている1つの状態のことが記述されています。 個々のセクションの最初の行から、状態番号を知ることができます。 2行目からは、記述ファイルの何行目が関連しているのかを知ることができます。 3行目からは、バックトラッキングを発生させた文字を知ることができます。 よって、最初のブロックからは、文字rでバックトラッキングが発生し、 それは記述ファイルの2、3、4行目に関連していることを見てとることができます。 最後の行は、圧縮されたテーブルは常にバックトラッキングを発生させるので、 テーブル圧縮を引き起こすようなコマンドラインオプションを使う場合には、 バックトラッキングを削除しようとして時間を費やすべきではないことを 思い出させるためのものです。

バックトラッキングを削除するためには、バックトラッキングが関与している 状態をキャッチするルールを加える必要があります。 これは、スキャナのスピードには影響を与えないということに注意 してください。 スキャナのスピードは、ルールの数や複雑さとはまったくといえるほど無関係です。

バックトラッキングを削除するためにルールを追加する方法は2種類あります。 第1の方法は、以下のようなルールを追加することです。

%%
hurd     return(GNU_OS);
hurdle   return(JUMP);
hurdled  return(JUMPED);
hu       return(OTHER);
hur      return(OTHER);
hurdl    return(OTHER);

別の方法として、すべてをキャッチするようなルールを追加することもできます。

%%
hurd     return(GNU_OS);
hurdle   return(JUMP);
hurdled  return(JUMPED);
[a-z]+   return(OTHER);

この第2の方法を適用できる場合は、常にこれを使うべきです。 上記のどちらかと-bオプションを一緒に使うと、

Compressed tables always backtrack.

というメッセージだけが出力されるようになります。 これは、バックトラッキング状態が存在しないことを示唆しています。

これに付随する問題の1つとして、複雑なスキャナではバックトラッキング問題は カスケードする傾向があるので、lex.backtrack内の情報が混乱を もたらすものになる可能性があります。 しかし、バックトラッキングの原因は通常2、3個のルールにしぼることが可能なので、 バックトラックデータを調べようと努力するだけの価値はあります


Node:Optimizing for Size, Previous:Optimizing for Speed, Up:Optimization

サイズの最適化

Flexは、サイズの小さいスキャナよりも、むしろ非常に高速なスキャナを作成すること を目標としていますが、いずれにしても、作成されるテーブルのサイズは Lexによるそれと比較しても、通常はかなり小さなものになります。

デフォルトでは、Flexは可能な限りサイズの小さなスキャナを作成します。 これは、コマンドラインで-Cemを使うのと同等です。 デフォルトで使うのであれば、コマンドラインオプションを気にする必要はありません。

さらにテーブルのサイズを小さくするには、より大きなテキストグループに マッチするルールを使い、字句の値を認識するためにCのサブルーチンを 使うのがもっとも良い方法です。 この良い例がコンパイラで、以下のようなルールを与えることができます。

%%
begin    return(BEGINSYM);
end      return(ENDSYM);
program  return(PROGSYM);
    ...

あるいは、以下のようにテーブル検索を使うことも可能です。

[a-zA-Z][a-zA-Z0-9]*  return(lookup(yytext));

ここでは、一般的なルールが指定されていて、lookup()がテキストを キーワードにマッチさせ、そのトークンがなんであるかを示す整数値を返します。 これにより、サイズのより小さなテーブルが生成されますが、性能は悪くなる 傾向があります。 また、数が少なく複雑ではないルール集合については、テーブルサイズを 縮小することの効果は、シンボルマッピング用の情報をプログラム中の他の領域に 格納しなければならないという事実によって、相殺されるかもしれません。 というのは、シンボルマッピング用の情報は、Flexテーブルと比較して、 より多くのスペースを必要とする可能性があるからです。


Node:More Examples, Next:, Previous:Optimization, Up:Top

Flexを使うその他のプログラム例

ここでは、Flexの使用例をさらにいくつか紹介します。 ここでの例も、必ずしも最適な実装ではありませんが、 一般的なFlexの使い方を示してくれるはずです。


Node:Example-Counting Words, Next:, Up:More Examples

単語数、文字数、行数のカウント

以下の定義は、与えられたファイルの中の単語数、文字数、行数をカウントするのに Flexを使う方法を示す、簡単な例です。 実際にFlexに関係のある部分は、非常に少ないことに注意してください。 以下のコードのほとんどは、コマンドラインパラメータを処理したり、 カウントの合計を保持したりするものです。

/*
 * wc.lex : wcのようなユーティリティを、
 *          Flexを使って作成する簡単な例
 */

%{
int  numchars = 0;
int  numwords = 0;
int  numlines = 0;
int  totchars = 0;
int  totwords = 0;
int  totlines = 0;
%}
/*
 * ルールはここから始まる
 */

%%

[\n]        { numchars++;  numlines++;         }
[^ \t\n]+   { numwords++;  numchars += yyleng; }
.           { numchars++;                      }
%%

/*
 * 追加のCコードがここから始まる。
 * ここで、すべての引数処理などを行うコードが提供される
 */
void main(int argc, char **argv)
{
  int  loop;
  int  lflag = 0; /* 行数をカウントする場合は1         */
  int  wflag = 0; /* 単語数をカウントする場合は1       */
  int  cflag = 0; /* 文字数をカウントする場合は1       */
  int  fflag = 0; /* ファイル名が指定されている場合は1 */
  for(loop=1; loop<argc; loop++){
     char *tmp = argv[loop];
     if(tmp[0] == '-'){
     switch(tmp[1]){
       case 'l':
          lflag = 1;
          break;
       case 'w':
          wflag = 1;
          break;
       case 'c':
          cflag = 1;
          break;
       default:
          fprintf(stderr,"unknown option -%c\n",tmp[1]);
     }
    } else {
      fflag = 1;
      numlines = numchars = numwords = 0;
      if((yyin = fopen(tmp,"rb")) != 0){
        (void) yylex();
        fclose(yyin);
        totwords += numwords;
        totchars += numchars;
        totlines += numlines;
        printf("file  : %25s :",tmp) ;
        if(lflag){
          fprintf(stdout,"lines %5d ",numlines);
        }
        if(cflag){
          fprintf(stdout,"characters %5d ",numchars);
        }
        if(wflag){
          fprintf(stdout,"words %5d ",numwords);
        }
        fprintf(stdout,"\n");
      }else{
        fprintf(stderr,"wc : file not found %s\n",tmp);
      }
    }
  }
  if(!fflag){
    fprintf(stderr,"usage : wc [-l -w -c] file [file...]\n");
    fprintf(stderr,"-l = count lines\n");
    fprintf(stderr,"-c = count characters\n");
    fprintf(stderr,"-w = count words\n");
    exit(1);
  }
  for(loop=0;loop<79; loop++){
    fprintf(stdout,"-");
  }
  fprintf(stdout,"\n");
  fprintf(stdout,"total : %25s  ","") ;
  if(lflag){
    fprintf(stdout,"lines %5d ",totlines);
  }
  if(cflag){
    fprintf(stdout,"characters %5d ",totchars);
  }
  if(wflag){
     fprintf(stdout,"words %5d ",totwords);
  }
  fprintf(stdout,"\n");
}


Node:Example-Pascal Lexical Scanner, Next:, Previous:Example-Counting Words, Up:More Examples

Pascalのサブセット用の字句スキャナ

ここでは、Pascalのような言語用の字句スキャナを作る方法を示します。 このスキャナ定義では、個々のキーワードがルールとしてリストされています。 (一般的には、すべてのキーワードをテーブルに格納してからテーブル検索を使う 手法がよく見られますが)ここでの方法は、キーワードと識別子とを区別するための 方法としては、一般にもっとも簡単なものです。 また、識別子用にただ1つのルールがあるという点に注意してください。 多くの場合、このルールはシンボルテーブルを管理するためのサブルーチンを 呼び出します。

もう1つ注意すべき点は、_FILE_BEGINが先頭にアンダースコアを 持つという点です。 Flex、またはCで定義済みの名前は、追加のくふうなしでは使えないということ を示すために、このようにしてあります。 これよりももっと一般的に使われる手法は、すべてのトークンの先頭 もしくは末尾になんらかの文字列を付加するというもので、こうすることによって 問題は発生しなくなります。 TOKSYMが一般的によく使われる拡張子です。

/*
 * pascal.lex : PASCALスキャナの例
 */

%{
#include <stdio.h>
#include "y.tab.h"
int line_number = 0;
void yyerror(char *message);

%}

%x COMMENT1 COMMENT2
white_space       [ \t]*
digit             [0-9]
alpha             [A-Za-z_]
alpha_num         ({alpha}|{digit})
hex_digit         [0-9A-F]
identifier        {alpha}{alpha_num}*
unsigned_integer  {digit}+
hex_integer       ${hex_digit}{hex_digit}*
exponent          e[+-]?{digit}+
i                 {unsigned_integer}
real              ({i}\.{i}?|{i}?\.{i}){exponent}?
string            \'([^'\n]|\'\')+\'
bad_string        \'([^'\n]|\'\')+
%%
"{"                  BEGIN(COMMENT1);
<COMMENT1>[^}\n]+
<COMMENT1>\n            ++line_number;
<COMMENT1><<EOF>>    yyerror("EOF in comment");
<COMMENT1>"}"        BEGIN(INITIAL);
"(*"                 BEGIN(COMMENT2);
<COMMENT2>[^)*\n]+
<COMMENT2>\n            ++line_number;
<COMMENT2><<EOF>>    yyerror("EOF in comment");
<COMMENT2>"*)"       BEGIN(INITIAL);
<COMMENT2>[*)]
/* FILEとBEGINは、FlexやCにおいてはすでに定義されているため
 * 使うことができない点に注意。これは、すべてのトークンの
 * 先頭にTOK_やその他の接頭辞を付加することによって、より
 * すっきりと克服することができる
 */

and                  return(AND);
array                return(ARRAY);
begin                return(_BEGIN);
case                 return(CASE);
const                return(CONST);
div                  return(DIV);
do                   return(DO);
downto               return(DOWNTO);
else                 return(ELSE);
end                  return(END);
file                 return(_FILE);
for                  return(FOR);
function             return(FUNCTION);
goto                 return(GOTO);
if                   return(IF);
in                   return(IN);
label                return(LABEL);
mod                  return(MOD);
nil                  return(NIL);
not                  return(NOT);
of                   return(OF);
packed               return(PACKED);
procedure            return(PROCEDURE);
program              return(PROGRAM);
record               return(RECORD);
repeat               return(REPEAT);
set                  return(SET);
then                 return(THEN);
to                   return(TO);
type                 return(TYPE);
until                return(UNTIL);
var                  return(VAR);
while                return(WHILE);
with                 return(WITH);

"<="|"=<"            return(LEQ);
"=>"|">="            return(GEQ);
"<>"                 return(NEQ);
"="                  return(EQ);

".."                 return(DOUBLEDOT);
{unsigned_integer}   return(UNSIGNED_INTEGER);
{real}               return(REAL);
{hex_integer}        return(HEX_INTEGER);
{string}             return{STRING};
{bad_string}         yyerror("Unterminated string");
{identifier}         return(IDENTIFIER);

[*/+\-,^.;:()\[\]]   return(yytext[0]);

{white_space}        /* 何もしない */
\n                   line_number += 1;
.                    yyerror("Illegal input");
%%
void yyerror(char *message)
{
   fprintf(stderr,"Error: \"%s\" in line %d.  Token = %s\n",
           message,line_number,yytext);
   exit(1);
}


Node:Example-Jargon Converter, Previous:Example-Pascal Lexical Scanner, Up:More Examples

専門用語の変換

ここでは、スタート状態を使って、 Flexにより生成されるスキャナの内部に小規模のパーサを作る方法の例を示します。 このコードはThe New Hackers Dictionaryprep.ai.mit.edu、 およびその他の多くのインターネットFTPサイトから入手可能なテキスト形式 のもの)を入力として受け取り、すぐに製版および印刷できる状態のTexinfo フォーマットのドキュメントに変換するものです。 このコードはjargon2910.asciiを使ってテスト済みです。

典型的な使い方は以下のとおりです。

j2t < jargon > jargon.texi
tex jargon.texi
lpr -d jargon.dvi

このプログラムは、 使用に耐えるinfoファイルに変換可能なファイルは作成しませんが、 こうした機能は大した困難もなく追加することが可能です。 この例は非常に長いものですが、大して複雑でもないので、 尻込みしないで研究してみてください。

/*
 * j2t.lex : スタート状態を利用(ひょっとして悪用!)する例
 */

%{
#define MAX_STATES 1024
#define TRUE  1
#define FALSE 0
#define CHAPTER   "@chapter"
#define SECTION   "@section"
#define SSECTION  "@subsection"
#define SSSECTION "@subsubsection"
int  states[MAX_STATES];
int  statep = 0;

int  need_closing = FALSE;

char buffer[YY_BUF_SIZE];

extern char *yytext;
/*
 * このプログラムが生成する*.texinfoファイルの先頭部分を作る。
 * これは標準的なTexinfoヘッダである
 */

void print_header(void)
{
   printf("\\input texinfo @c -*-texinfo-*-\n");
   printf("@c           %c**start of header\n",'%');
   printf("@setfilename       jargon.info\n");
   printf("@settitle          The New Hackers Dictionary\n");
   printf("@synindex          fn cp\n");
   printf("@synindex          vr cp\n");
   printf("@c           %c**end of header\n",'%');
   printf("@setchapternewpage odd\n");
   printf("@finalout\n");
   printf("@c @smallbook\n");
   printf("\n");
   printf("@c ====================================================\n\n");
   printf("@c This file was produced by j2t.  Any mistakes are *not*\n");
   printf("@c the fault of the jargon file editors.\n");
   printf("@c ====================================================\n\n");
   printf("@titlepage\n");
   printf("@title    The New Hackers Dictionary\n");
   printf("@subtitle Version 2.9.10\n");
   printf("@subtitle Generated by j2t\n");
   printf("@author Eric S. Raymond, Guy L. Steel, and Mark Crispin\n");
   printf("@end titlepage\n");
   printf("@page\n");
   printf("@c ====================================================\n");
   printf("\n\n");
   printf("@unnumbered Preface\n");
   printf("@c          *******\n");
}
/*
 * 生成されるTexinfoファイルの末尾の部分を作成する
 */

void print_trailer(void)
{
   printf("\n");
   printf("@c ====================================================\n");
   printf("@contents\n");   /* 目次を表示する */
   printf("@bye\n\n");
}
/*
 * 後でそれを見つけることができるよう、節または章に下線を引く
 */

void write_underline(int len, int space, char ch)
{
  int loop;

  printf("@c ");
  for(loop=3; loop<space; loop++){
    printf(" ");
  }

  while(len--){
    printf("%c",ch);
  }
  printf("\n\n");
}
/*
 * Texinfoにおいて特殊な意味を持つ文字をチェックし、エスケープする
 */

char *check_and_convert(char *string)
{
  int  buffpos = 0;
  int  len,loop;
  len = strlen(string);
  for(loop=0; loop<len; loop++){
    if(string[loop] == '@' ||
       string[loop] == '{' ||
       string[loop] == '}')
    {
      buffer[buffpos++] = '@';
      buffer[buffpos++] = string[loop];
    } else {
      buffer[buffpos++] = string[loop];
    }
  }
  buffer[buffpos] = '\0';
  return(buffer);
}
/*
 * 章、節、項のヘッダを書き出す
 */

void write_block_header(char *type)
{
  int loop;
  int len;
  (void)check_and_convert(yytext);
  len = strlen(buffer);
  for(loop=0; buffer[loop] != '\n';loop++)
  buffer[loop] = '\0';
  printf("%s %s\n",type,buffer);
  write_underline(strlen(buffer),strlen(type)+1,'*');
}

%}
/*
 * Flexの記述情報がここから始まる
 */

%x HEADING EXAMPLE ENUM EXAMPLE2
%x BITEM BITEM_ITEM
%s LITEM LITEM2

%%

^#[^#]*"#"  /* ヘッダとトレーラをスキップする */
                     /*
                      * 章は、その下にアスタリスクを持ち、コロンで終わる
                      */
^[^\n:]+\n[*]+\n      write_block_header(CHAPTER);
^"= "[A-Z]" ="\n"="*  { /* 個々のカテゴリごとに節を作成する */
                        if(need_closing == TRUE){
                          printf("@end table\n\n\n");
                        }
                        need_closing = TRUE;
                        write_block_header(SECTION);
                        printf("\n\n@table @b\n");
                      }
"Examples:"[^\.]+     ECHO;

"*"[^*\n]+"*"         { /* @emph{}(強調された)テキスト */
                        yytext[yyleng-1] = '\0';
                        (void)check_and_convert(&yytext[1]);
                        printf("@i{%s}",buffer);
                      }
"{{"[^}]+"}}"         { /* 特別な強調 */
                        yytext[yyleng-2] = '\0';
                        (void)check_and_convert(&yytext[2]);
                        printf("@strong{%s}",buffer);
                      }
"{"[^}]+"}"           { /* 特別な強調 */
                        yytext[yyleng-1] = '\0';
                        (void)check_and_convert(&yytext[1]);
                        printf("@b{%s}",buffer);
                      }
 /* 特殊なTexinfo文字をエスケープする */
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"@"  printf("@@");
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"{"  printf("@{");
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"}"  printf("@}");
 /*
  * @exampleコードを再生成する
  */

":"\n+[^\n0-9*]+\n"     "[^ ]   {
                        int loop;
                        int len;
                        int cnt;
                        printf(":\n\n@example \n");
                        strcpy(buffer,yytext);
                        len = strlen(buffer);
                        cnt = 0;
                        for(loop=len; loop > 0;loop--){
                          if(buffer[loop] == '\n')
                             cnt++;
                          if(cnt == 2)
                              break;
                        }
                        yyless(loop+1);
                        statep++;
                        states[statep] = EXAMPLE2;
                        BEGIN(EXAMPLE2);
                      }
<EXAMPLE,EXAMPLE2>^\n  {
                      printf("@end example\n\n");
                      statep--;
                      BEGIN(states[statep]);
                    }
 /*
  * @enumerateリストを再生成する
  */

":"\n+[ \t]*[0-9]+"."   {
                      int loop;
                      int len;
                      printf(":\n\n@enumerate \n");
                      strcpy(buffer,yytext);
                      len = strlen(buffer);
                      for(loop=len; loop > 0;loop--){
                        if(buffer[loop] == '\n')
                           break;
                      }
                      yyless(loop);
                      statep++;
                      states[statep] = ENUM;
                      BEGIN(ENUM);
                    }
<ENUM>"@"           printf("@@");
<ENUM>":"\n+"     "[^0-9]    {
                    printf(":\n\n@example\n");
                    statep++;
                    states[statep] = EXAMPLE;
                    BEGIN(EXAMPLE);
                  }

<ENUM>\n[ \t]+[0-9]+"." {
                    printf("\n\n@item ");
                   }
<ENUM>^[^ ] |
<ENUM>\n\n\n[ \t]+[^0-9] {
                    printf("\n\n@end enumerate\n\n");
                    statep--;
                    BEGIN(states[statep]);
                  }
 /*
  * 1種類の@itemizeリストを再生成する
  */

":"\n+":"         {
                    int loop;
                    int len;

                    printf(":\n\n@itemize @bullet \n");
                    yyless(2);
                    statep++;
                    states[statep] = LITEM2;
                    BEGIN(LITEM2);
                  }
<LITEM2>^":".+":" {
                    (void)check_and_convert(&yytext[1]);
                    buffer[strlen(buffer)-1]='\0';
                    printf("@item @b{%s:}\n",buffer);
                  }
<LITEM2>\n\n\n+[^:\n] {
                    printf("\n\n@end itemize\n\n");
                    ECHO;
                    statep--;
                    BEGIN(states[statep]);
                  }
 /*
  * リビジョンヒストリ部からリストを作成する。
  * ここで"Version"が必要なのは、そうしないと他のルール
  * と衝突するからである
  */

:[\n]+"Version"[^:\n*]+":" {
                    int loop;
                    int len;
                    printf(":\n\n@itemize @bullet \n");
                    strcpy(buffer,yytext);
                    len = strlen(buffer);
                    for(loop=len; loop > 0;loop--){
                      if(buffer[loop] == '\n')
                         break;
                    }
                    yyless(loop);
                    statep++;
                    states[statep] = LITEM;
                    BEGIN(LITEM);
                  }
<LITEM>^.+":"     {
                    (void)check_and_convert(yytext);
                    buffer[strlen(buffer)-1]='\0';
                    printf("@item @b{%s}\n\n",buffer);
                  }
<LITEM>^[^:\n]+\n\n[^:\n]+\n  {
                    int loop;

                    strcpy(buffer,yytext);
                    for(loop=0; buffer[loop] != '\n'; loop++);
                    buffer[loop] = '\0';
                    printf("%s\n",buffer);
                    printf("@end itemize\n\n");
                    printf("%s",&buffer[loop+1]);
                    statep--;
                    BEGIN(states[statep]);
                  }
 /*
  * @itemize @bulletリストを再生成する
  */

":"\n[ ]*"*"      {
                    int loop;
                    int len;
                    printf(":\n\n@itemize @bullet \n");
                    len = strlen(buffer);
                    for(loop=0; loop < len;loop++){
                      if(buffer[loop] == '\n')
                         break;
                    }
                    yyless((len-loop)+2);
                    statep++;
                    states[statep] = BITEM;
                    BEGIN(BITEM);
                  }
<BITEM>^" "*"*"   {
                    printf("@item");
                    statep++;
                    states[statep] = BITEM_ITEM;
                    BEGIN(BITEM_ITEM);
                  }
<BITEM>"@"          printf("@@");
<BITEM>^\n        {
                    printf("@end itemize\n\n");
                    statep--;
                    BEGIN(states[statep]);
                  }
<BITEM_ITEM>[^\:]* {
                     printf(" @b{%s}\n\n",check_and_convert(yytext));
                   }
<BITEM_ITEM>":"   {
                    statep--;
                    BEGIN(states[statep]);
                  }
 /*
  * @chapter、@sectionなどを再作成する
  */

^:[^:]*           {
                    (void)check_and_convert(&yytext[1]);
                    statep++;
                    states[statep] = HEADING;
                    BEGIN(HEADING);
                  }
<HEADING>:[^\n]   {
                    printf("@item @b{%s}\n",buffer);
                    write_underline(strlen(buffer),6,'~');
                    statep--;
                    BEGIN(states[statep]);
                  }
<HEADING>:\n"*"*  {
                    if(need_closing == TRUE){
                      printf("@end table\n\n\n");
                      need_closing = FALSE;
                    }
                    printf("@chapter %s\n",buffer);
                    write_underline(strlen(buffer),9,'*');
                    statep--;
                    BEGIN(states[statep]);
                  }
<HEADING>:\n"="*  {
                    if(need_closing == TRUE){
                     printf("@end table\n\n\n");
                      need_closing = FALSE;
                    }
                    printf("@section %s\n",buffer);
                    write_underline(strlen(buffer),9,'=');
                    statep--;
                    BEGIN(states[statep]);
                  }
<HEADING>"@"        printf("@@");
<HEADING>:\n"-"*  {
                    if(need_closing == TRUE){
                      printf("@end table\n\n\n");
                      need_closing = FALSE;
                    }
                    printf("@subsection %s\n",buffer);
                    write_underline(strlen(buffer),12,'-');
                    statep--;
                    BEGIN(states[statep]);
                  }
 /*
  * @exampleテキストを再作成する
  */

^"     "          {
                    printf("@example\n");
                    statep++;
                    states[statep] = EXAMPLE;
                    BEGIN(EXAMPLE);
                  }
<EXAMPLE>^"     "
.                 ECHO;

%%
 /*
  * 初期化して実行する
  */

int main(int argc, char *argv[])
{
  states[0] = INITIAL;
  statep    = 0;
  print_header();
  yylex();
  print_trailer();
  return(0);
}

このプログラムは、ASCIIの専門用語ファイルを読み込んで、 いくつかのよく見られるパターンを検索します。 このパターンは、オリジナルのTexinfo形式の専門用語ファイルを単なるASCIIテキスト に変換した際に作成されたものです。 この変換の過程で、多くのマークアップ情報が失われているために、 ある出力結果の元になったオリジナルの情報がであったか、 あるいは、そのオリジナルの候補が2つ3つあったとしても、 そのうちのどれがその出力結果をもたらしたかを正確に決定することが困難 であるという事情のため、この検索作業はいくらか複雑なものになります。 よく見られるパターンをいくつかあげると、以下のようになります。

章、節、項
これらの先頭にはいずれも同じパターンがきます。
:some text:\n

この後ろに、 (章の場合は)アスタリスクによる下線、 (節の場合は)等号による下線、 (項の場合は)マイナス記号による下線が続きます。

強調
これは少し難しいのですが、 一般的には強調は(イタリックの場合は)*...*、 (強調文字(strong)の場合は){{...}}、 (太字(bold)の場合は){...}の対によって示されます。 ここでは、この3種類を検索して、コマンドを出力します。
例および列挙されたリスト
ともにコロンで始まり、その後ろに、1つ以上の改行、少なくとも5つの空白、 そして最後に数字もしくはなんらかのテキストが続きます。 たとえば、列挙されたリストは以下のようになります。
...enumerated:

      0.some text
      1.some more text

また、プログラム例は以下のようになります。14

...example:

      some text

項目化されマークを付けられたリスト
例および列挙されたリストとよく似ていますが、違いは、項目の先頭にコロン またはアスタリスクがあり、末尾にコロンがあるという点です。

ここでの例は、パースされているものがなんであるかを示すヒントとして このようなパターンを使い、その特定のセクション用の部分的なパーサを (ほとんどの場合、排他的)スタート状態を使って作ります。 ASCII版の専門用語ファイルを持っているのであれば、スキャナのどの部分がその ファイル中の何にマッチするかを検証してみる価値があります。 たとえば、HEADING状態において@itemを生成するルールが、 すべての専門用語のエントリを処理するルールでもあるということは、 おそらく一見しただけでは明らかではないでしょう。


Node:Flex and Lex, Next:, Previous:More Examples, Up:Top

FlexとLex

ここで非常に簡単にではありますが、FlexとLexの両方を概観してみます。 Flex、Lexそれぞれの性能と、Lexのようなユーティリティに関するPOSIX標準 への準拠度についても、いくつか一般的なコメントを示します。


Node:Flex, Next:, Up:Flex and Lex

Flex

Flexは、Lexのより優れた再実装であり、Lexと同様、パターンとアクションの 記述情報を入力として受け取って、そのパターンにマッチする能力を持つCの スキャナに変換するものです。 しかしながら、Flexはより少ない時間でテーブルを生成しますし、Flexにより 生成されるテーブルは、Lexにより生成されるテーブルと比較して、はるかに 効率的なものです(Flexが正確には何を生成するのかという説明については、 本書の冒頭で言及した書籍を参照してください)。

Flexは、LexおよびPOSIXと十分に互換性があり、 それ独自の特別な機能もいくつか追加しています。


Node:Flex and POSIX, Next:, Up:Flex

FlexとPOSIX

Flexは、大体のところLexおよびPOSIXの両方と互換性があります。 将来は(Flex、POSIXのどちらかが変わることによって)、 さらにPOSIXとの互換性を高めていくでしょう。 しかし、Flex、Lex、POSIXには、異なる部分もいくつかあります。 それを以下に示します。

排他的スタート状態
FlexとPOSIXは排他的スタート状態をサポートしていますが、 Lexはサポートしていません。
定義
LexとFlexでは定義の展開の方法が違います。 Flex(およびPOSIXのドラフト仕様)は、定義を展開するときに丸かっこ( )で 囲みますが、Lexは囲みません。15 このことは、Flex定義では演算子^$/<<EOF>>、 および<start state>は使うことができないということを意味しています。

このことがもたらす主要な問題の1つに、マッチの優先順位に影響を与え、 FlexとLexの間でスキャン処理に微妙な差異が出てくるということがあります。 この問題の例については、パターンセクション を参照してください。

input()
FlexおよびPOSIXのドラフト仕様では、input()は再定義可能ではありません。 Flexで入力を制御するためには、input()を再定義する代わりに、 YY_INPUTという拡張機能を使います(これは現在のところPOSIXではサポート されていません)。 また、Lexとは異なり、Flexのinput()yytextの値を変更する という点に注意してください。
output()
Flexはoutput()ルーチンをサポートしていません。 ECHOの出力はyyout経由で行われます。 このyyoutのデフォルトはstdoutです。 これを使うようにoutput()を書くことも可能ですが、現在のPOSIXの ドラフト仕様は、output()が正確には何をすべきなのかを示していません。
Ratforスキャナ
Flex、POSIXのどちらも、LexのRatfor16 スキャナオプション(%r)をサポートしていません。
yylineno
これは、 FlexやPOSIXには存在しない、ドキュメント化されていないLexの機能です。 17 しかし、Flexで行数をカウントする機能を実装するのは難しくありません。 定義中に行数カウント機能を組み込む方法の例については、 See Miscellaneous
yywrap()
現在のところyywrap()はマクロです。 POSIXのドラフト仕様では、これは関数であるべきとされているので、 おそらく将来は変更されることになるでしょう。18
unput()
現在のところunput()yytextyylengの値を破壊しますが、 次のトークンがマッチされるまでは、これは不当です。 LexとPOSIXでは、yytextyylengunput()の影響を受けません。 19
数値範囲
POSIX によると、abc{1,3}は 「abの後ろに1個、2個、または3個のcが続くもの」 にマッチすべきとなっています。 Flexはこのとおりに動きますが、Lexはこれを「1個、2個、または3個のabc」 と解釈します。
yytext
Flexにおいてyytextの正しい定義はextern char *yytextですが、 Lexではextern char yytext[]です。20 配列によるアクセス方法は、性能にかなりの影響を及ぼすので、 Flexではextern char *yytextを使い続けるでしょう。

最新のPOSIXドラフト仕様は、%array%pointerを導入すること によって、両方の方法をサポートしています。 これは、FlexとLexのいずれにもまだ組み込まれていません。 21

テーブルサイズ
Lexにはテーブルサイズ宣言子(%p%aなど)がありますが、 Flexでは必要ありません。 互換性のために認識はされますが、無視されるだけです。
FLEX_SCANNER
スキャナがFlexとLexのどちらにより生成されたかによって、 コードをインクルードしたりしなかったりすることができるように、 FLEX_SCANNER#defineによって定義されています。
アクション
Flexでは、中かっこの対{...}を使うことなく、 単一行において複数の文を置くことができます。 これに対してLexは、そのような行を単一文に切り詰めてしまいます。
コメント
Flexではコメントを#で始めることができますが、 LexとPOSIXではできません。 ただし、この形式のコメントを使うことはおすすめできません。
yyterminate()yyrestart()<<EOF>>YY_DECL#line 指示子
これらはいずれもLexではサポートされていませんし、 POSIXにおいて明示的に定義されてもいません。 #line指示子の説明に関しては、 See Flex コマンドラインオプションの要約


Node:Flex and POSIX (Flex 2.5), Previous:Flex and POSIX, Up:Flex

FlexとPOSIX(Flex 2.5の補足情報)

Flex 2.5でサポートされている新しい機能のうち、 POSIXの仕様(および、Lex)に存在しないものを以下に列挙します。

C++スキャナ
%option指示子
スタート状態スコープ
スタート状態スタック
yy_scan_string()、yy_scan_bytes()、yy_scan_buffer()
yy_set_interactive()
yy_set_bol()
YY_AT_BOL()
<*>
YY_START


Node:Lex, Previous:Flex, Up:Flex and Lex

標準Lex

Lexはスキャナを作成するための標準的なUnixユーティリティであり、 長い歴史を持っています。 LexはFlexと非常によく似ていますが、スキャナを生成するのにより多くの時間 がかかりますし、Lexの生成するスキャナはFlexの生成するスキャナよりも通常は 遅いものです。 Lexは、特に多くのPOSIX機能を提供していないという理由から、置き換える必要が 大いにあります。 FlexはこうしたPOSIX機能を提供しています。 より多くのコンピュータシステムがPOSIX互換になるにつれて、Flexの提供する 多くの機能をサポートしなければならなくなり、このために、おそらくはFlexが Lexの代わりにインストールされるようになるでしょう(たとえば、4.4 BSDリリース はFlexを使うことになります)。 しかし、Lexがインストールされている少数のシステムがあるために、 しばらくの間はLexの存在は確実に維持されるでしょう。

FlexとLexの大きな違いは、Flexが性能を考慮して書かれたという点にあります。 一般的には、Flexを持っているのであればそれを使うべきです。 両者の性能差は、無視するにはあまりにも大きすぎます。 しかし、移植性がもっとも重要なのであれば、スキャナ定義は可能な限りLex のものに近づけるべきです。 というのは、Lexは事実上すべてのUnixマシンに入っていることが保証されていますが、 Flexは入っていない可能性があるからです(しかし、Flexのインストールはふつう 取るに足らない作業です)。 このような場合に残念なのは、FlexとPOSIXが持っている排他的スタート状態のような、 より便利な拡張機能を使うことができなくなるということです。

この問題を回避するためのもう1つの方法は、Flexでスキャナを作成して、 作成されたスキャナを配布することです。 スキャナというものは一度書かれるとほとんど変更されることがないので、 多くの場合この方法は実行可能です。 仮に変更が必要になったとしても、プログラムの他の部分も相当変更しなければ ならない可能性があり、よってプログラムを更新するための努力全体から見れば、 Flexのインストールなどはほんの些細なことでしょう。


Node:Useful Code, Next:, Previous:Flex and Lex, Up:Top

役に立つコード集

ここでは、読者がプログラムの中で使うことのできる、ちょっとしたFlex定義 を一覧にして示します。 多くは、本書を読んだ後では、かなり自明のものになるはずです。 しかし、読者がこうしたコードを最初から作らずに済むように、 ここでまとめています。


Node:Handling Comments, Next:, Up:Useful Code

コメントの処理

Flex and Cにおいて述べたように、 コメントはinput()を使って処理できます。 これを行うためのコードは次のようになります。
%%
"/*" {
        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");
        }
      }

これは、FlexとLexの両方で正当なコードです。 コメントは排他的スタート状態を使って処理することも可能で、 こちらのほうがより美しく、より効率的です。 スタート状態を使ってコメントを処理するコードは、次のようになります。

%x COMMENT
%%
"/*"                  BEGIN(COMMENT);
<COMMENT>[^\n]
<COMMENT>\n
<COMMENT><<EOF>>      yyerror("EOF in comment");
<COMMENT>"*/"         BEGIN(INITIAL);

改行の1つ前までと改行とを別々に処理したほうがよいのは、そうしないと、 内部のマッチ処理用のバッファをオーバーフローさせてしまうようなルールを作ること になってしまうからです。 Lexは排他的スタート状態をサポートしていないので、このコードはLexでは動きません。 この例はわかりやすいのですが、実際には単一文字をマッチするのに多くの時間を 無駄に消費するため、非効率的です。 もっと長いテキストにマッチするように変更することで、スピードをかなり向上させる ことができます。 たとえば、以下のように書き直すことができます。

%x COMMENT
%%
"/*"                     BEGIN(COMMENT);
<COMMENT>[^*\n]*
<COMMENT>[^*\n]*\n
<COMMENT>"*"+[^*/\n]*    /* 余分な「*」を探す */
<COMMENT>"*"+[^*/\n]*\n
<COMMENT><<EOF>>         yyerror("EOF in comment");
<COMMENT>"*"+"/"         BEGIN(INITIAL);

これは、Flexと一緒に配布されているflexdoc.1の中にある例と ほとんど同じです。 より長いテキストブロックにマッチするため、はるかに高速ですし、 ルールの中で改行のみにマッチさせる必要もありません。


Node:Handling Strings, Next:, Previous:Handling Comments, Up:Useful Code

文字列リテラルの処理

文字列は、それが入力として与えられたときに破棄されないという点で、 コメントとは若干異なります。 しかし、基本的なアプローチは同じです。 第1の方法としては、input()を使って文字列を処理できます。 コードは次のようになります。

/*
 * string1.lex: input()を使って文字列を処理する
 */

%{
#include <stdio.h>
#include <malloc.h>
#include <ctype.h>

#define ALLOC_SIZE 32 /* バッファの(再)割り当て用 */

#define isodigit(x) ((x) >= '0' && (x) <= '7')
#define hextoint(x) (isdigit((x)) ? (x) - '0'\
                                   : ((x) - 'A') + 10)
void yyerror(char *message)
{
  printf("\nError: %s\n",message);
}

%}
%%

\" {
   int  inch,count,max_size;
   char *buffer;
   int  temp;
   buffer   = malloc(ALLOC_SIZE);
   max_size = ALLOC_SIZE;
   inch     = input();
   count    = 0;
   while(inch != EOF && inch != '"' && inch != '\n'){
      if(inch == '\\'){
        inch = input();
        switch(inch){
        case '\n': inch = input(); break;
        case 'b' : inch = '\b';    break;
        case 't' : inch = '\t';    break;
        case 'n' : inch = '\n';    break;
        case 'v' : inch = '\v';    break;
        case 'f' : inch = '\f';    break;
        case 'r' : inch = '\r';    break;
        case 'X' :
        case 'x' : inch = input();
                   if(isxdigit(inch)){
                     temp = hextoint(toupper(inch));
                     inch = input();
                     if(isxdigit(inch)){
                       temp = (temp << 4) +
                             hextoint(toupper(inch));
                     } else {
                       unput(inch);
                     }
                     inch = temp;
                   } else {
                     unput(inch);
                     inch = 'x';
                   }
           break;
        default:
           if(isodigit(inch)){
              temp = inch - '0';
              inch = input();
              if(isodigit(inch)){
                temp = (temp << 3) + (inch - '0');
              } else {
                unput(inch);
                goto done;
              }
              inch = input();
              if(isodigit(inch)){
                temp = (temp << 3) + (inch - '0');
              } else {
                unput(inch);
              }
           done:
              inch = temp;
           }
        }
     }
      buffer[count++] = inch;
      if(count >= max_size){
         buffer = realloc(buffer,max_size + ALLOC_SIZE);
         max_size += ALLOC_SIZE;
      }
      inch = input();
   }
   if(inch == EOF || inch == '\n'){
     yyerror("Unterminated string.");
   }
   buffer[count] = '\0';
   printf("String = \"%s\"\n",buffer);
   free(buffer);
 }
.
\n
%%

このスキャナは、複数行にわたる文字列や、さまざまなエスケープシーケンスを 処理します。 また、文字列がどのような長さでもかまわないように、動的バッファを使っています。 これと同じことをスタート状態を使って行うコードは、次のようになります。

/*
 * string2.lex: スタート状態を使って文字列をスキャンする例
 */

%{
#include <ctype.h>
#define isodigit(x) ((x) >= '0' && (x) <= '7')
#define hextoint(x) (isdigit((x)) ? (x) - '0' \
                                  : ((x) - 'A') + 10)
char *buffer      = NULL;
int  buffer_size  = 0;
void yyerror(char *message)
{
  printf("\nError: %s\n",message);
}

%}

%x STRING

hex (x|X)[0-9a-fA-F]{1,2}
oct [0-7]{1,3}
%%

\"                {
                    buffer      = malloc(1);
                    buffer_size = 1; strcpy(buffer,"");
                    BEGIN(STRING);
                  }
<STRING>\n        {
                     yyerror("Unterminated string");
                     free(buffer);
                     BEGIN(INITIAL);
                  }
<STRING><<EOF>>   {
                     yyerror("EOF in string");
                     free(buffer);
                     BEGIN(INITIAL);
                  }
<STRING>[^\\\n"]  {
                    buffer_size += yyleng;
                    buffer = realloc(buffer,buffer_size+1);
                    strcat(buffer,yytext);
                  }
<STRING>\\\n      /* エスケープされた改行を無視する */
<STRING>\\{hex} {
                    int temp =0,loop = 0, foo;
                    for(loop=yyleng-2; loop>0; loop--){
                      temp <<= 4;
                      foo    = toupper(yytext[yyleng-loop]);
                      temp += hextoint(foo);
                    }
                    buffer = realloc(buffer,buffer_size+1);
                    buffer[buffer_size-1] = temp;
                    buffer[buffer_size]   = '\0';
                    buffer_size += 1;
                  }
<STRING>\\{oct} {
                    int temp =0,loop = 0;
                    for(loop=yyleng-1; loop>0; loop--){
                      temp  <<= 3;
                      temp  += (yytext[yyleng-loop] - '0');
                    }
                    buffer = realloc(buffer,buffer_size+1);
                    buffer[buffer_size-1] = temp;
                    buffer[buffer_size]   = '\0';
                    buffer_size += 1;
                  }
<STRING>\\[^\n]   {
                    buffer = realloc(buffer,buffer_size+1);
                    switch(yytext[yyleng-1]){
                    case 'b' : buffer[buffer_size-1] = '\b';
                               break;
                    case 't' : buffer[buffer_size-1] = '\t';
                               break;
                    case 'n' : buffer[buffer_size-1] = '\n';
                               break;
                    case 'v' : buffer[buffer_size-1] = '\v';
                               break;
                    case 'f' : buffer[buffer_size-1] = '\f';
                               break;
                    case 'r' : buffer[buffer_size-1] = '\r';
                               break;
                    default  : buffer[buffer_size-1] =
                                    yytext[yyleng-1];
                    }
                    buffer[buffer_size] = '\0';
                    buffer_size += 1;
                  }
<STRING>\"        {
                    printf("string = \"%s\"",buffer);
                    free(buffer);
                    BEGIN(INITIAL);
                  }
%%

このスキャナは、string1.lexよりもモジュール化されていて、 おそらくよりわかりやすいでしょう。 エラーのルールは、INITIAL状態に戻るようになっていることに 注意してください。 こうしないと、スキャナは不当な文字列と正当な文字列とを結合してしまいます。 ここでも、Flexのバッファ(YY_BUF_SIZE)が十分に大きいということを あてにせず、動的バッファを使いました。 内部バッファが十分に大きいという確信が持てるのであれば、yytext だけを使うことも可能です。 この場合には、yytextの右端が確実に最初の位置に留まるようにすること が重要です。 より詳しい情報については、Flex and Cyymoreの項を 参照してください。


Node:Handling Numbers, Next:, Previous:Handling Strings, Up:Useful Code

数字の処理

ここでは、Cに見られるさまざまな数値形式に対してよく使われる定義をいくつか示し、 さらにその使い方の例を1つ示します。 注目すべき主要な点は、数の値を獲得するためにscanf()を使っている点と、 オーバーフローが発生しないようlong型の値をスキャンするデフォルトの ルールです。 一般的には、yytextを数に変換する最良の方法は、sscanf()を 使うことです。

/*
 * numbers.lex : 数をスキャンするための定義およびテクニックの例
 */

%{
#include <stdio.h>
#define UNSIGNED_LONG_SYM   1
#define SIGNED_LONG_SYM     2
#define UNSIGNED_SYM        3
#define SIGNED_SYM          4
#define LONG_DOUBLE_SYM     5
#define FLOAT_SYM           6

union _yylval {
  long double    ylong_double;
  float          yfloat;
  unsigned long  yunsigned_long;
  unsigned       yunsigned;
  long           ysigned_long;
  int            ysigned;
} yylval;

%}
digit          [0-9]
hex_digit      [0-9a-fA-F]
oct_digit      [0-7]
exponent       [eE][+-]?{digit}+
i              {digit}+
float_constant ({i}\.{i}?|{i}?\.{i}){exponent}?
hex_constant   0[xX]{hex_digit}+
oct_constant   0{oct_digit}*
int_constant   {digit}+
long_ext       [lL]
unsigned_ext   [uU]
float_ext      [fF]
ulong_ext      [lL][uU]|[uU][lL]

%%

{hex_constant}{ulong_ext} {  /* 0xの部分をスキップする */
                             sscanf(&yytext[2],"%lx",
                                    &yylval.yunsigned_long);
                             return(UNSIGNED_LONG_SYM);
                          }
{hex_constant}{long_ext}  {
                             sscanf(&yytext[2],"%lx",
                                    &yylval.ysigned_long);
                             return(SIGNED_LONG_SYM);
                          }
{hex_constant}{unsigned_ext}  {
                             sscanf(&yytext[2],"%x",
                                    &yylval.yunsigned);
                             return(UNSIGNED_SYM);
                          }
{hex_constant}   { /* オーバーフローを回避するために%lxを使う */
                             sscanf(&yytext[2],"%lx",
                                    &yylval.ysigned_long);
                             return(SIGNED_LONG_SYM);
                          }
{oct_constant}{ulong_ext} {
                             sscanf(yytext,"%lo",
                                    &yylval.yunsigned_long);
                             return(UNSIGNED_LONG_SYM);
                          }
{oct_constant}{long_ext}  {
                             sscanf(yytext,"%lo",
                                    &yylval.ysigned_long);
                             return(SIGNED_LONG_SYM);
                          }
{oct_constant}{unsigned_ext}  {
                             sscanf(yytext,"%o",
                                    &yylval.yunsigned);
                             return(UNSIGNED_SYM);
                          }
{oct_constant} { /* オーバーフローを回避するために%loを使う */
                             sscanf(yytext,"%lo",
                                    &yylval.ysigned_long);
                             return(SIGNED_LONG_SYM);
                          }
{int_constant}{ulong_ext} {
                             sscanf(yytext,"%ld",
                                    &yylval.yunsigned_long);
                             return(UNSIGNED_LONG_SYM);
                          }
{int_constant}{long_ext}  {
                             sscanf(yytext,"%ld",
                                    &yylval.ysigned_long);
                             return(SIGNED_LONG_SYM);
                          }
{int_constant}{unsigned_ext}  {
                             sscanf(yytext,"%d",
                                    &yylval.yunsigned);
                             return(UNSIGNED_SYM);
                          }
{int_constant} { /* オーバーフローを回避するために%ldを使う */
                             sscanf(yytext,"%ld",
                                    &yylval.ysigned_long);
                             return(SIGNED_LONG_SYM);
                          }
{float_constant}{long_ext}  {
                             sscanf(yytext,"%lf",
                             &yylval.ylong_double);
                             return(LONG_DOUBLE_SYM);
                          }
{float_constant}{float_ext}  {
                             sscanf(yytext,"%f",
                                    &yylval.yfloat);
                             return(FLOAT_SYM);
                          }
{float_constant} { /* オーバーフローを回避するために%lfを使う */
                             sscanf(yytext,"%lf",
                                    &yylval.ylong_double);
                             return(LONG_DOUBLE_SYM);
                          }
%%

int main(void)
{
  int code;

  while((code = yylex())){
    printf("yytext          : %s\n",yytext);
    switch(code){
    case UNSIGNED_LONG_SYM:
       printf("Type of number  : UNSIGNED LONG\n");
       printf("Value of number : %lu\n",
              yylval.yunsigned_long);
       break;
    case SIGNED_LONG_SYM:
       printf("Type of number  : SIGNED LONG\n");
       printf("Value of number : %ld\n",
              yylval.ysigned_long);
       break;
    case UNSIGNED_SYM:
       printf("Type of number  : UNSIGNED\n");
       printf("Value of number : %u\n",
              yylval.yunsigned);
       break;
    case SIGNED_SYM:
       printf("Type of number  : SIGNED\n");
       printf("Value of number : %d\n",
              yylval.ysigned);
       break;
    case LONG_DOUBLE_SYM:
       printf("Type of number  : LONG DOUBLE\n");
       printf("Value of number : %lf\n",
              yylval.ylong_double);
       break;
    case FLOAT_SYM:
       printf("Type of number  : FLOAT\n");
       printf("Value of number : %f\n",
              yylval.yfloat);
       break;
    default:
       printf("Type of number  : UNDEFINED\n");
       printf("Value of number : UNDEFINED\n");
       break;
    }
  }
  return(0);
}

16進定数については、変換する前に先頭の0xをスキップする必要がある点 に注意してください。 これはsscanf()の仕様です。


Node:Multiple Scanners, Next:, Previous:Handling Numbers, Up:Useful Code

複数のスキャナ

ときには、1つのプログラムの中で複数のスキャナを持つ必要のある場合がありますが、 こうすると、2回以上現れる関数や変数について、リンカが文句をいってきます。 これを回避するためには、スキャナとそれに関連するすべてのものの名前を変更する 必要があります。 すべてのスキャナ関数、マクロ、およびデータの名前はyyもしくは YYで始まりますので、これはきわめて簡単です。 しなければならないのは、名前の接頭辞を変更することだけです。 これはsedを使って簡単に行うことができますが、ここではおもしろ半分で、 これを行うflexスキャナを示しましょう。22

/*
 * replace.lex : flexにより生成されたスキャナや
 *               bisonにより生成されたパーサの
 *               一部の名前を変更する簡単なフィルタ
 */

%{
#include <stdio.h>

char lower_replace[1024];
char upper_replace[1024];

%}
%%

"yy"   fprintf(yyout,"%s",lower_replace);
"YY"   fprintf(yyout,"%s",upper_replace);
%%

int main(argc,argv)
int argc;
char **argv;
{
   if(argc < 3){
     printf("Usage %s lower UPPER\n",argv[0]);
     exit(1);
   }
   strcpy(lower_replace,argv[1]);
   strcpy(upper_replace,argv[2]);
   yylex();
   return(0);
}

すべてのスキャナ関数の名前を変更するには、コマンドライン上で 次のように実行するだけです。

replace myscan_ MYSCAN_ < lex.yy.c > myscan.c

これにより、好きなだけ多くのスキャナを含めることができるようになります。 ほとんど同じことを、排他的スタート状態と複数のバッファを使って 実現することも可能ですが、その方法は多少複雑になります。

注意:いくつかのFlex内部ルーチンは、 将来Flexライブラリ(-lfl)の中に移されるでしょう。 そうなると、このテクニックは機能しなくなります。 しかし、この変更が行われるときには、変更する必要のある関数名を変更する方法を、 Flex自身がサポートするようになるでしょう。 23


Node:Miscellaneous, Previous:Multiple Scanners, Up:Useful Code

その他


Node:Summary, Next:, Previous:Useful Code, Up:Top

まとめ

ここでは、Flexの使用に関連するすべての情報を要約します。 本章は、クイックリファレンスとして使うことができます。


Node:Switches Summary, Next:, Up:Summary

Flexのコマンドラインオプション一覧

 Flexには、以下のコマンドラインオプションがあります。

-b
このオプションは、バックトラッキングを必要とする状態をもたらすルール に関する情報を含む、lex.backtrackというファイルを生成します。 なぜこの情報が重要なのか、また、この情報をどのように使うかという点に関する 詳細については、Optimizing for SpeedRemoving Backtrackingを参照 してください。
-c
このオプションは、POSIXとの互換性のためだけに提供されており、 実際には何もしません。 以前は、テーブル圧縮を制御するために使われていましたが、 その機能は-Cオプションに移されました。 このフラグを見つけると、Flexはユーザーがテーブル圧縮を希望しているものと想定し、 警告メッセージを出力します。 将来、この警告メッセージは出力されないようになるかもしれません。 24
-d
デバッグに使われます。 実行中に自身の状態情報をyyoutに書き込むスキャナを生成します。 あるルールがマッチするたびに、バックトラッキングに関する情報、検出された バッファの終端、NULに関する情報に加えて、以下のような情報が 書き込まれます。
--accepting rule at line 行番号 ("マッチしたテキスト")

この中の行番号は(-Lオプションが使われていない場合には)、 生成されたファイルlex.yy.cではなく、スキャナを生成するのに使われた 記述ファイルの行番号を指します。

-f
フルスキャナ(full scanner)を生成します。 圧縮は一切行われません。 これは、-Cfと同等です(詳細については、 See Table Compression and Scanner Speed)。
-i
大文字/小文字の区別を無視するスキャナを作成します。 ルールのマッチ処理において大文字/小文字の区別は無視されますが、 個々の文字は大文字または小文字に変換されないので、 yytextには大文字/小文字が混在した文字の並びが入ることになります。
-n
このオプションは、Flexに対してはまったく意味を持たず、 単にPOSIXとの互換性のためだけに提供されています。
-p
性能に関する情報をstderrに書き込みます。 報告される情報は、性能を低下させるようなスキャナ記述情報の機能に 関するコメントによって構成されます。
-s
マッチするものがなかった場合のFlexスキャナのデフォルトのアクションは、 マッチしなかった入力情報をstdoutに書き込むことです。 -sオプションはこのアクションを抑制し、その代わりに、 入力がマッチしないとすぐにスキャナを異常終了させます。
-t
このオプションが指定されると、Flexは生成されたスキャナを ファイルlex.yy.cにではなく、stdoutに書き込みます。
-v
冗長モードで動作します。 Flexは、生成されたスキャナに関する統計情報の要約を生成して、 stdoutに出力します。 要約情報の第1行にはFlexのバージョン番号、次の行には日付と時刻、 さらに次の行には実際に使われているオプションが示されます。 要約情報のこれ以外の部分は、Flexやその他の同様のプログラムの動作の詳細 を理解している人以外にはほとんど意味を持ちません。
-F
ファストスキャナ(fast scanner)を生成します。 これは、-CFと同等です。 詳細については、See スキャナの最適化
-I
このオプションは、シェル上や、型を持つ入力情報を受け付ける必要のあるプログラム 内で使うことのできる対話型スキャナを生成します。 詳細については、See Interactive Scanners

注意:-Iオプションは、-Cf-f-CF-Fの各オプションと一緒に使うことはできません。

-L
デフォルトではFlexは、エラーがスキャナ定義のどこで発生したのかを 追跡できるように、生成されたスキャナのコード中に#line指示子を生成します。 -Lオプションは、この#line指示子を生成する機能を抑制します。
-T
Flexをトレースモードで実行します。 Flexは、入力情報、スキャン処理テーブル、同等クラス(equivalence class)、 およびメタ同等クラス(meta-equivalence class)に関するメッセージを生成して、 (stderrに)書き込みます。 この情報は、Flexの内部的な動作を理解していない人には、 ほとんど意味を持たないでしょう。
-8
8ビットの入力情報を受け付けることのできるスキャナを生成します。 7ビットの入力情報しか受け付けないスキャナに8ビットの入力情報を与えた場合の 結果は、予測不能です。
-C[efmF]
スキャン処理テーブルをどのように圧縮するかを指定します。 詳細については、See スキャナの最適化を参照してください。
-Sskeleton_file
生成されるスキャナのベースとして、skeleton_fileで指定される ファイルを使うようにします。 これを使うことはほとんどありませんが、MS-DOS上ではこれによって 標準のスキャナスケルトンへのパスを設定できます。


Node:Switches Summary (Flex 2.5), Next:, Previous:Switches Summary, Up:Summary

Flexのコマンドラインオプション一覧(Flex 2.5の補足情報)

Flex 2.5では、前節で説明されていない、以下のオプションもサポートされています。

-h
コマンドラインオプションの要約情報を出力します。
-l
AT&Tにより実装されたlexとの互換性を最大限に提供します。 このオプションは、性能面でかなりの悪影響を及ぼします。 また、このオプションを、-f-F-Cf-CF-+オプションと同時に指定することはできません。
-w
警告メッセージを出力しません。
-B
バッチスキャナを生成します。 これは、対話型スキャナを生成するよう指示する-Iオプションの否定です。
-V
バージョン番号を出力します。
-7
7ビットスキャナを生成します。 これは、-8オプションの否定です。
-+
C++スキャナクラスを生成します。
-?
コマンドラインオプションの要約情報を出力します (-hオプションと同じです)。
-Ca
スキャン処理用のテーブルをlong intの配列として定義します (デフォルトではshort int型の配列となります)。
-Cr
このオプションを指定して生成されたスキャナは、 入力にread()システムコールを使います。 デフォルトでは、対話型スキャナの場合はgetc()が、 バッチ(非対話型)スキャナの場合はfread()が使われます。
-ofile
このオプションが指定されると、Flexは生成されたスキャナをfileにより 指定されるファイルに出力します。 デフォルトでは、スキャナはファイルlex.yy.cに出力されます。
-Pprefix
Flexにより生成されるスキャナのソースファイルの中では、 大域変数や大域関数の名前の先頭に接頭辞yyが付けられます。 このオプションが指定されると、yyの代わりに、 prefixにより指定される文字列が接頭辞として使用されます。 また、-oオプションが指定されない場合のスキャナファイル名 lex.yy.cも、lex.prefix.cとなります。
--help
コマンドラインオプションの要約情報を出力します (-hオプションと同じです)。
--version
バージョン番号を出力します(-Vオプションと同じです)。


Node:Summary or Flex Variables and Functions, Next:, Previous:Switches Summary (Flex 2.5), Up:Summary

Flex変数およびFlex関数の一覧

Flexに対する主要なCインターフェイスは、 以下のルーチンおよび変数を通じて実現されます。 個々のルーチン、変数に関する完全な説明については、See Interfacing to Flex

yylex()
主要なインターフェイスです。 これが実際のスキャン処理を行う関数です。
yyin
yylex()が文字を読み込む元となるファイルです。 デフォルトはstdinです。
yyout
スキャナの出力ファイルです。 デフォルトはstdoutです。
yytext
最後にマッチした文字列を保持する大域変数です。 つまり、最後に認識されたトークンを保持しています。
yyleng
最後に認識されたトークンの長さを保持する大域変数です。
yywrap()
この関数は、yyinの終端に達したときに呼び出されます。 これがTRUE(ゼロ以外)を返すとスキャナは実行を終了しますが、 FALSE(ゼロ)を返すと、yyinが次の入力ファイルを指すよう 設定されたものと想定し、スキャン処理は継続されます。
yymore()
次に認識されるトークンでyytextの内容を上書きするのではなく、 そのトークンをyytextの末尾に付加するようFlexに通知する関数です。
yyless(n)
yymore()とほぼ反対のことを行います。 この関数は、最初のn文字を除くすべての文字を戻します。 戻された文字の並びは、次のトークンをマッチするのに使われます。 yylengyytextの内容には、この変更が反映されます。
input()
入力から次の1文字を返します。 これは、標準のFlex記述言語や特にLex記述言語を使ったのでは うまく処理できないようなスキャナにおいてよく使われます。
unput(c)
この関数は、文字cを入力ストリームに戻します。 この後、この文字は次にスキャンされる文字になります。
yyterminate()
この関数は、アクションの中で使われると、 スキャナ(yylex())の実行を終了させます。 終了したスキャナは0を返します。 この後yyrestart()が呼び出されない間は、 yylex()を呼び出してもすぐに復帰してしまいます。
yyrestart(file)
この関数は、スキャナの実行を再開するようFlexに通知します。 これは、スキャンすべきファイル(通常はyyin)を表す引数を1つ取ります。 EOFを処理するのに使うことができますし、また、Flexに割り込みをかけ、 その後で再開することを可能にするために使うこともできます (Flexが再入可能ではないので、このようなことが必要になります)。
ECHO
yytextの内容をyyoutにコピーするマクロです。
REJECT
カレントトークンを認識しないで、次にもっともよくマッチするものを選択するよう、 スキャナに通知します。 スキャナは、マッチするもののうちもっとも長いものを探します。 マッチするものが2つあってその長さが同じ場合には、 スキャナ記述の中で最初に定義されているものを選択します。
BEGIN(state)
スキャナをある特定のスタート状態に置くために使われます。 BEGINの後ろの名前は、スタート状態の名前です。 これは、スキャナ記述の先頭の定義セクションにおいて宣言されているもの でなければなりません。
YY_USER_INIT
スキャナが初期化されるに実行されるべきアクションを定義します。 詳細については、Flex and Cを参照してください。
YY_USER_ACTION
マッチが発生した後で、ルールセクションに定義されたアクションが実行される に、実行されるべきアクションを定義します。 たとえば、yytextの内容を小文字から大文字へ変換することなどに 使うことができます。 デフォルトのルールでは何も実行されません。 詳細については、Flex and Cを参照してください。
YY_BREAK
実際にはインターフェイス機能ではなく、むしろ生成されるコードを変更するために 使うことができるものです。

スキャナの中では、 すべてのアクションは1つの大きなswitch文の構成要素であり、 個々のアクションの区切りは、デフォルトでbreak;文に変換される YY_BREAKで与えられます。 もし、ほとんどのルールのアクション部がreturn;文を含んでいると、 コンパイラはstatement not reachedというエラーをたくさん表示すること になるでしょう(表示するはずです)。 YY_BREAKを再定義することによって、 この警告メッセージを表示させないようにすることが可能です。

注意:YY_BREAKを再定義する場合は、 アクションが必ずreturn;break;で終わるようにしてください。

YY_DECL
スキャン処理を実行する関数の名前を定義するマクロです。 デフォルトはyylexですが、再定義できます。 再定義した名前は、関数のプロトタイプとして正当なものでなければなりません。
YY_INPUT
入力ルーチンの名前を定義するマクロです。 必要に応じて、この名前は再定義できます。 たとえば、文字列や、標準的ではないなんらかの入力デバイスを入力として、 スキャン処理を行う場合に役に立ちます。
YY_NEW_FILE
yyinが新しいファイルを指すよう設定されたこと、および、 処理が継続されるべきであることをFlexに通知するマクロです。 25
YY_CURRENT_BUFFER
カレント入力バッファを返すマクロです。
yy_create_buffer()
新しい入力バッファを作成するのに使われます。 この関数と、この後の2つの関数を使うことにより、複数のバッファを作成し、 バッファ間で切り替えることが可能になります。 See バッファを操作する関数
yy_delete_buffer()
以前に作成された入力バッファを削除するのに使われます。
yy_switch_to_buffer()
複数の入力バッファの間で切り替えを行うのに使われます。
YY_BUFFER_STATE
バッファを処理するのに使われるです。 バッファのカレントコンテキストを保持します。 複数のバッファ間で切り替えを行うときには、この型の変数が必要になります。
YYSTYPE
Bisonファイル中の%unionの型です。 これは、FlexとBisonの間のインターフェイスで使われます。
yylval
Bisonパーサの現在のパース状態に関連するデータを保持する、 Bisonパーサ中の変数です。 この変数を使うことで、データをFlexとBisonの間で渡すことができます。


Node:Summary or Flex Variables and Functions (Flex 2.5), Next:, Previous:Summary or Flex Variables and Functions, Up:Summary

Flex変数およびFlex関数の一覧(Flex 2.5の補足情報)

Flex 2.5では、前節で説明されていない、以下の関数やマクロもサポートされています。

yy_set_interactive()
カレントバッファを、対話的なものとみなすか、非対話的なものとみなすかを 制御します。 引数にゼロ以外の値を渡すと、カレントバッファは対話的なものとみなされ、 ゼロを渡すと、非対話的なものとみなされます。
yy_set_bol()
バッファ内の現在位置が行の先頭にあるか否かを表すコンテキスト情報を 設定します。 引数にゼロ以外の値を渡すと、バッファ内の現在位置は行の先頭である、 というコンテキスト情報がセットされます。 したがって、次にトークンのマッチ処理が行われるときには、 行頭を表す^を含むルールの適用が試みられます。 逆に、引数にゼロを渡すと、バッファ内の現在位置は行の先頭ではないこと になり、次にトークンのマッチ処理が行われるときには、 行頭を表す^を含むルールの適用が試みられなくなります。
YY_AT_BOL()
次にトークンのマッチ処理が行われるときに、行頭を表す^を含むルール の適用が試みられるようなコンテキスト情報がセットされている場合には、 ゼロ以外の値を返します。 それ以外の場合は、ゼロを返します。
yy_new_buffer()
yy_create_bufferの別名です。
yy_flush_buffer()
引数で指定されたバッファの内容を破棄し、バッファの先頭2バイトに YY_END_OF_BUFFER_CHAR\0)をセットします。
YY_FLUSH_BUFFER
引数にカレントバッファを指定してyy_flush_buffer()を呼び出すよう 定義されたマクロです。
yy_scan_string()
NULLで終わる文字列をスキャンするための入力バッファを作成します。 実際には、引数で渡された文字列のコピーがスキャンされます。
yy_scan_bytes()
引数で指定されたメモリ領域をスキャンするためのバッファを作成します。 実際には、メモリ領域上のデータのコピーがスキャンされます。
yy_scan_buffer()
引数で指定されたメモリ領域をスキャンするためのバッファを作成します。 メモリ領域上のデータはコピーされません。
yy_push_state()
現在のスタート状態をスタート状態スタックにプッシュし、 引数で指定された状態に遷移します。
yy_pop_state()
スタート状態スタックからスタート状態をポップし、 そのポップされたスタート状態に遷移します。
yy_top_state()
スタート状態スタックの先頭にあるスタート状態を返します (スタート状態スタックの内容は変更されません)。
yyFlexLexer::yylex()
C++スキャナにおいて実際にスキャン処理を行う関数です。
yyFlexLexer::LexerInput()
yyFlexLexerのサブクラスにおいて再定義することによって、 C++スキャナの入力処理を変更できます。
yyFlexLexer::LexerOutput()
yyFlexLexerのサブクラスにおいて再定義することによって、 C++スキャナの出力処理を変更できます。
yyFlexLexer::LexerError()
yyFlexLexerのサブクラスにおいて再定義することによって、 C++スキャナのエラーメッセージ出力処理を変更できます。


Node:Summary of Flex Characters, Next:, Previous:Summary or Flex Variables and Functions (Flex 2.5), Up:Summary

Flex文字の一覧

Flexにおける基本的な構成要素の1つに文字があります。 基本的にFlexは、演算子、特殊文字、エスケープシーケンスを除いて、 文字をそのまま受け付けます。 エスケープシーケンスは、ANSI Cに見られるものと同一です。 Flexの演算子と特殊文字は以下のとおりです。

文字
Flexの解釈
\
バックスラッシュは、ANSI Cのエスケープシーケンスで使われるのと同様の エスケープ文字です。
[ ]
角かっこ[ ]は、文字の集合を文字クラスにグループ化するのに使われます。 詳細については、See Flexにおける文字のグループ化
^
文字クラスの中では、^は否定を意味します。 詳細については、See Flexにおける文字のグループ化。 一方、文字クラスの外部では、行の先頭を意味し、(エスケープされていない場合は) ルールの先頭にのみ置くことができます。
-
ハイフンは、文字クラスの中で文字の範囲を設定するのに使われます。 文字クラスの外部では、ハイフン自身を表します。 詳細については、See Flexにおける文字のグループ化
{ }
中かっこ{ }は、定義の参照、複数行にわたるアクションの先頭と末尾の指定、 またはパターンの繰り返し回数の範囲の定義を行います。
( )
丸かっこ( )は、優先順位の変更に使われます。 また、定義は展開されるときに、暗黙のうちに丸かっこで囲まれることに 注意してください。
""
二重引用符は、文字列の範囲を示します。 引用符で囲まれた範囲の中にある文字だけがマッチします。
/
スラッシュは、後続コンテキスト(trailing context)を設定します。 これは、あるパターンを認識するのを、その後ろに別のパターンが続く場合に 限定したい、という場合です。 これは、スラッシュ/が一種の「ルックアヘッド(先読み)」演算子 として機能することを意味します。
< >
山かっこ< >は、スタート状態の参照、またはスタート状態のグループの参照を行い、 さらにEOFシンボル(<<EOF>>)で使われます。 これに関する完全な説明については、 Start StatesEnd-Of-File Rulesを参照してください。
? + *
?+*の各文字は、ある正規表現が 何回出現できるかを指定するのに使われます。 ?は、ゼロ回もしくは1回(つまり、オプションであるということ)を、 +は1回以上を、*はゼロ回以上をそれぞれ意味します。
|
OR演算子を表します。 また、カレントルールに対するマッチが発生した場合、 次に記述されているルールのアクションを実行するようFlexに通知する、 特別なアクションを表します。
$
ドル記号は行末を意味します。

ここにあげた文字を、その文字自身として表したい場合には、 その文字を引用符で囲む(たとえば"*")か、または、 エスケープシーケンスとして表す必要があります。 詳細については、See Characters


Node:Summary of Flex Rules, Previous:Summary of Flex Characters, Up:Summary

Flexルールのまとめ

Flexにおけるルールには2つの部分があります。 パターンマッチング用の表現式とアクション部です。 この2つは、次のように配置されます。

pattern actions

Flexがマッチするパターンは、正規表現を使って作られます。 そしてその正規表現は、文字、文字列、定義、スタート状態、および演算子 から作られます。 次の表は、種々の正当な正規表現を示します。 この中で、cは(エスケープシーケンスを含む)任意の単一文字を、 rは任意の正規表現を、sは文字列を、それぞれ表します。 また、グループ別に構成され、優先度のもっとも高いものがいちばん上にあります。

これは、sedgrep、Emacsや正規表現を使う他の一般的な プログラムにおいて使われる正規表現と完全に同じではないことに注意してください。

ルールのアクション部は、任意の正当なCコードです。 単一行に複数の文を書くこともできますし、 かっこの対{...}で囲むことで、 複数の文のブロックを複数行にわたって書くことも可能です。


Node:Index, Previous:Summary, Up:Top

索引

Short Contents

Table of Contents


Footnotes

  1. 【訳注】Flex 2.5では、 -lオプションを指定して生成されたスキャナは、 Lexの場合と同じように、定義を展開するときに丸かっこ( )で囲みません。

  2. 【訳注】Flex 2.5では、 -lオプションを指定して生成されたスキャナは、Lexの場合と同じように、 定義を展開するときに丸かっこ( )で囲みません。

  3. 【訳注】Flex 2.5では、-lオプションを指定して生成されたスキャナ は、Lexの場合と同じように、定義を展開するときに丸かっこ( )で囲みません。

  4. 【訳注】Flex 2.5は、スタート状態スタックをサポートしています。 次節(Start State Notes (Flex 2.5))を参照してください。

  5. Flex 2.5.4に付属のドキュメントflex.texiには、 関数input()についても同様のことが記載されていますが、 実際に%option noinputを指定してみると、 生成されるスキャナの中に、関数input()が組み込まれます。

  6. 【訳注】この章の最後で、C++の使い方についても説明します。

  7. 【訳注】Flex 2.5では、%pointer%arrayにより、 yytextの型を選択できるようになりました。 %pointerを指定した場合はchar *yytext%arrayを指定した場合はchar yytext[YYLMAX]となります。 デフォルトは%pointerです。 %arrayを指定した場合の配列のサイズは、 YYLMAXを再定義することによって変更可能です。

  8. 【訳注】Flex 2.5では、%option noyywrapが指定されないかぎり、 yywrap()は関数です。 再定義をするのに、#undefで定義解除する必要はありません。

  9. 【訳注】Flex 2.5では、%arrayが指定された場合は、 unput()yytextの内容を破壊しません。

  10. 【訳注】Flex 2.5では、yyinを変更した後に YY_NEW_FILEを実行する必要はなくなりました。

  11. 【訳注】 日本語訳:『Bison入門』、アスキー出版局、ISBN 4-7561-3065-8

  12. Flexの仕様では、 Flexの起動時に-dオプションを指定するか、スキャナ定義ファイルの中に %option debugを指定すると、スキャナ実行時にデバッグ情報が出力されること になっていますが、Flex 2.5.4でC++スキャナを生成した場合は、いずれの方法でも デバッグ情報は出力されません。 デバッグ情報を出力するためには、C++プログラムから明示的にこのset_debug() メンバ関数を(引数にゼロ以外の値を指定して)呼び出す必要があります。

  13. 【訳注】Flex 2.5では、 ここに列挙されているもの以外に、-Caオプションをサポートしています。 これについては、Command Line Switches (Flex 2.5)を参照してください。

  14. 【訳注】some text の部分に、インデントされたテキストが記されます。

  15. 【訳注】Flex 2.5では、 -lオプションを指定して生成されたスキャナは、 Lexの場合と同じように、定義を展開するときに丸かっこ( )で囲みません。

  16. 【訳注】Rational Fortranの略。

  17. 【訳注】Flex 2.5では、Flex起動時に-lオプションを指定するか、 スキャナ定義ファイルの中に%option yylinenoを指定することによって、 変数yylinenoを利用できます。

  18. 【訳注】Flex 2.5では、 %option noyywrapが指定されないかぎり、yywrap()は関数です。

  19. 【訳注】Flex 2.5では、%arrayを指定すれば、 unput()yytextの内容を破壊しません。

  20. 【訳注】Flex 2.5では、 %pointer%arrayにより、yytextの型を選択できるように なりました。 デフォルトは%pointerです。

  21. 【訳注】Flex 2.5は、 %pointer%arrayをサポートしています。

  22. 【訳注】Flex 2.5では、 Flex起動時に-Pprefixオプションを指定するか、スキャナ定義ファイル の中に%option prefix="prefix"を指定することによって、 接頭辞yyを別の文字列に変更できます。

  23. 【訳注】Flex 2.5では、-Pprefixオプションや %option prefix="prefix"を指定することにより、 関数名を変更できます。

  24. 【訳注】Flex 2.5では、この警告メッセージは出力されません。

  25. 【訳注】Flex 2.5では、yyinを変更した後に YY_NEW_FILEを実行する必要はなくなりました。