この章では字句スキャン処理の概念を説明し、Flexのようなツールの 必要性を示します。 本章の後半ではFlexを使うことのできる状況について説明します。
UnixおよびCの世界では、ファイルは通常個々のバイトが連続したものとして 扱われます。 個々のバイトを集めてどのようにグループ化するかという点は、 プログラマが決めることです。 このような抽象化は非常に強力です。 というのは、どのようなファイルであってもこの抽象化方法によって表現できるから です。 しかしこの方法には短所もあり、プログラマはほとんど常に生のファイルに対して 構造をあてはめなければなりません。 言い換えると、ファイルをより意味のある部分に分割しなければならないということ です。 たとえば、コンパイラのある部分はファイルから連続した文字を受け取り、 構文チェッカが理解することのできる構成要素、 たとえば、数値、キーワード、文字列などにグループ化します。 このようなことを行う理由は、コンパイラの言語パーサが処理を行うのは、連続した 文字に対してではなく、その言語のシンボルが連続したものに対してだからです。
データベースアプリケーションや、バイナリファイルを扱うアプリケーションは、 扱うデータに対してある固定されたフォーマットを持っていることが多く、 そのフォーマットを使って入力データから意味を導き出します。 テキストを入力するプログラムは通常これとは反対で、入力を単語やシンボルに 分割しなければならないことが多いのですが、通常これらの単語やシンボルが どのように配置されているかを示す決まった構造というものは存在しません。 したがって、テキスト処理を行うプログラムは、入力された情報を意味のある シンボルに分割するために、字句解析もしくは字句スキャンと呼ばれる 処理を行う部分を持っていることが多く、そこで入力情報の分割が行われます。 このようなことを行う関数群のことを字句アナライザもしくは字句スキャナ、 あるいは短く「スキャナ」と呼びます。
一般的に、スキャナを作成するのは、 プログラマにとって難しいことでもおもしろいことでもないのですが、 時間のかかる作業になることはあります。 普及しているプログラミング言語のほとんどは、 スキャナの作成を支援する機能が不十分です。 というのは、連続した文字を単語、トークン、シンボルに分割する組み込みの機能 を持っていないからです。 通常はこのような仕事を行うライブラリルーチンが存在しますが、 柔軟でなかったり、使いにくかったり、あるいは、 ルーチンとのやり取りにあまりに多くのコードが必要になったりすることが多いために、 実装上の細かな点によって根本的な問題が不明瞭にされてしまいます。
1つの良い例が、C言語で許されているすべての数値型 (浮動小数点、整数、16進数、8進数) を処理するスキャナをC言語で記述する場合です。 これは非常に難しいということはありませんが、 でき上がったコードは通常美しいとはとてもいえないものになってしまうでしょうし、 その保守や拡張は容易でないことが多いのです。
ほとんどのプログラマが即座に断言するように、 他人の書いたコードを保守するのは通常あまり楽しい作業ではありません。 さらに、美しくないコードを保守するのは、楽しいというにはほど遠いものです。 このように、スキャナを書くことが退屈で、その保守が難しいとなると、 スキャナの作成をより容易にしてくれる方法を考えようとした理由が読者にもおわかり いただけるでしょう。
Flexはプログラマに対して、字句解析処理部分をきれいに記述し、 その記述にしたがった効率的な字句スキャナを生成する方法を提供します。 プログラマはFlexに対して、必要なスキャナに関する記述情報を提供します。 Flexはその記述情報を使って、C言語で書かれたスキャナを生成します。 記述に使われる言語は高級言語であり、 スキャナの記述に関してはC言語よりもはるかに適しています。 その高級言語を使うことで、プログラマは、文字をどのようにグループ化し、 グループ化が完了したときにどのようなアクションを発生させるかを指定できます。
注意:本書のほとんどの部分はFlex、Lexの両方を対象にしています。 Lexは(Flexには劣りますが) ほとんどのUnixシステム上にある標準のスキャナ生成ユーティリティです。 両者の間に違いがある場合には、Flexを優先させています。 Lexについては標準Lexで簡単に説明しています。
ここでも1つの良い例がコンパイラです。 前述のように、コンパイラの構文チェッカは、文字が連続したものではなく、 言語文法の構成要素を表すトークンが連続したものを、 入力として受け取る必要があります。 Flexはこのような場合に最適です。 Flexによって生成されたスキャナが構文チェッカとファイルの仲介役となり、 構文チェッカが次の意味のあるトークンを要求するのを待ちます。 Flexはファイルを読み、プログラマが与える記述にしたがって文字をグループ化して、 マッチしたトークンを返します。 この処理は、スキャナもしくはパーサが終了するまで続きます。
Cのコンパイラを作成する場合、 このようなことを行うために必要となるFlexの記述情報は、 コードの行数にして100〜300行ぐらいになるかもしれません。 この記述情報のほとんどは、 シンボルテーブルの管理、識別子の検索、型のマッピング、ある数の値などの追加情報の返却を行うための補助的なCコードになるでしょう。 こうしたコード自体は記述情報の一部ではありませんが、 通常、コンパイラによって必要とされるものです。
概念的にはFlexは、原材料(文字)を取り込み、 消費者(パーサ等)がすぐに使うことができる完成品(トークン)を製造する工場のようなものです。
Flexはコンパイラにしか使えないということはありません。 読者のコンピュータ上にある、ファイルを読み込んだり、 なんらかの形で文字のグループを処理する必要のあるすべてのプログラム、 特に、変換フィルタや言語ツールのことを考えてみてください。 こうしたプログラムのほとんどすべてを、 Flex単体、もしくはFlexと他のツールの組み合わせによって作成することができます。
良い例の1つが文字数のカウントです。
たとえば、ファイルの中の全行数、個々の文字の出現回数、
foo
という単語の出現回数を調べるプログラムを作成したい場合です。
標準的なツール(grep
、sed
、awk
、perl
等)
で作成することも、C言語のプログラムを書いて作成することもできますが、
Flexで作成することも可能です。
もう1つの例として、特定のキーワードを探す必要のある、メールリーダがあります。
この場合でも、標準ツールで実現することも、FlexとC言語で実現することもできます。
Flexを使うことで、プログラマは、 スキャナの開発やファイルを構成する文字の処理にかかる時間を大幅に減らすことができます。 ほとんどの場合、Flexに対する入力情報は、 既存のプログラミング言語で記述されたコードと比較して、 より理解しやすく、少なくとも同程度の移植性があり、保守もより簡単です。 それだけでなく、Flexでスキャナを開発するのにかかる時間は、 既存のプログラミング言語で同等のスキャナを開発する場合と比べきわめて短くてすむので、 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
に出力します。
Flexのコマンドラインオプションは以下のような意味を持ちます。
-b
オプションを指定するとlex.backtrack
というファイルが生成されます。
このファイルはスキャナの記述情報を最適化する際に使用されます。
詳細については、See Optimizing for Speed。
-c
オプションはC言語によるアクションが使用されることを意味します。
stderr
に出力します。
-f
(小文字)オプションと-F
(大文字)オプションとは異なる効果を持つ点に注意してください。
-v
オプションによる出力を抑制するために使用されます。
POSIXでのデフォルトは、
テーブルサイズが指定されないかぎりこのような出力を抑制します。
Flexではテーブルサイズは意味を持たないので、このフラグは冗長です。
-p
オプションが指定されると、
Flexは性能レポートをstderr
に出力します。
スキャナの性能を向上させる方法に関する解説については、
See Optimizing for Speed。
stdout
に出力することです。
-s
オプションはこのようなアクションを抑制し、
その代わりに入力情報がマッチしなかった時点でスキャナを異常終了させます。
lex.yy.c
にではなくstdout
に出力します。
-F
(大文字)は-f
(小文字)とは異なる効果を持つ点に注意してください。
-f
と-F
の相違点に関する情報については、
See Table Compression and Scanner Speed。
#line
指示子を書き込みます。
このオプションによって#line
指示子の書き込みは行われなくなります。
stderr
に出力するようになります。
こうしたメッセージは、
Flexを非常によく理解しているユーザー以外には無意味でしょう。
Flex 2.5では、前節(Command Line Switches)で説明されていない、 以下のオプションもサポートされています。
-f
、-F
、-Cf
、-CF
、
-+
オプションと同時に指定することはできません。
FlexとLexの(非)互換性の問題については、Flex and Lexを参照してください。
-I
オプションの否定です。
-8
オプションの否定です。
内部的に生成されるテーブルのサイズは-8
オプションが指定された場合と
比較して半分になりますが、生成されるスキャナは、8ビット文字を含む入力を処理
できなくなります。
-Cf
、-CF
が指定されていない場合は、
明示的に-7
を指定しないかぎり、8ビットスキャナが生成されます。
-h
オプションと同じです)。
long int
の配列として定義
するようFlexに通知します(デフォルトではshort int
型の配列となります)。
RISCマシンによっては、long int
のほうが高速に処理されるため、スキャナ
の性能向上が期待できますが、その反面、テーブルのサイズは大きくなります。
read()
システムコールを使います。
デフォルトでは、対話型スキャナの場合はgetc()
が、
バッチ(非対話型)スキャナの場合はfread()
が使われます。
lex.yy.c
に出力されます。
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
-+
オプションが指定されている場合は、
影響を受けるのはyywrap
とyyFlexLexer
の2つだけです。
このオプションにより、
yywrap()
の名前が変更されてしまう点に注意してください。
プログラムをリンクするためには、
prefixwrap
という名前の関数を作成する必要があります。
この関数を作成したくない場合には、
スキャナ定義ファイルの中で、%option noyywrap
を指定して、
リンク時に-lfl
オプションを指定します。
%option
指示子については、%option (Flex 2.5)を参照してください。
-h
オプションと同じです)。
-V
オプションと同じです)。
この章では、スキャナ定義の構成要素を説明し、その使用例を示します。 Flexを効率的に使用するためには、 定義の個々の要素を完全に理解することが非常に重要です。 したがって、初めてFlexを使うユーザーは、 時間をかけて本章を読むことをおすすめします。
Flexスキャナ定義のほとんどの要素は、任意です。 全体的な定義フォーマットは以下のようになります。
定義と初期Cコード %% ルール %% 他のCコード
それぞれについて、以下において詳細に説明します。
Cのコードを記述できるところには、どこにでもコメントを記述できます。 コメントの書式は、Cのコメントの規則に従います。 コメントは、記述情報に影響を及ぼすことはありません。 Cスタイルのコメントは以下のようになります。
/* ... */
これに加えて、Flexでは#
で始まるコメントも許されます。
しかし、このようなコメントはlex.yy.c
にはコピーされないので、
この形式のコメントの使用はおすすめできません。
注意:C以外の言語(たとえばPascal) のコードを生成するLexも存在します。 このようなLexではコメントの書式はおそらく異なるでしょう。 Flexの場合はCのコードしか生成しません。
プログラマは、2つの異なる方法を用いて、
スキャナの中に直接Cのコードを含めることができます。
第1の方法は、「定義と初期Cコード」セクション
(最初の%%
より前の部分)にコードを含めることです。
第2の方法は、「他のCコード」セクション
(2番目の%%
より後ろの部分)にコードを含めることです。
どちらの場合も、コードはそのままlex.yy.c
にコピーされるので、
正当なコードでなければなりません。
第1のセクション中のCコードは以下の形式になります。
%{ Cコード ... %}
ここで%{...%}
というペアが、
Cコードブロックの先頭と末尾を示すために使われています。
この形式のコードと定義は、「定義と初期Cコード」セクションのどこにでも
自由に記述できます。
定義については次の節で説明します。
Cのコードが最初のカラムから始まるのでなければ、
%{...%}
というペアは必要ありません。
しかし普通は、わかりやすくするために記述しておいたほうがよいでしょう。
もう1つのポイントは、#ifdef
などのように最左端のカラムから始まらなければ
ならず、かつ、通常はスキャナ記述情報の先頭に置かれる必要のあるものが存在する
という点です。
こうした場合、%{...%}
に囲まれていないと、
Flexはそれを定義の一部であるとみなすでしょう。
これが、常に%{...%}
を使うもう1つの理由です。
最後の(「他のCコード」)セクション内のコードは、そのままコピーされます。 ここには特別な宣言は必要ありません。
定義セクションにおいて、プログラマは、ある文字のグループに一意な識別子を与え、 その識別子がその文字グループに置き換えられるようにすることができます。 定義は以下のような形式になります。
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を参照してください。
2つのパーセント記号が、スキャナ記述情報のルールセクションの先頭と末尾
を示します。
すべてのFlex記述情報は、少なくともルールセクションの先頭を示す
%%
を含んでいなければなりません。
ルールはFlexの心臓部です。 ルールを書くことによって、プログラマは、 スキャナが何を実行するべきであるかをFlexに通知します。
通常、ルールは2つの部分から構成されます。
pattern actions
このうちpatternが何を認識するべきかを定義し、 actionsがその何かを認識したときに何を実行するべきであるか をスキャナに知らせます。 patternの部分は空白によって区切られます。 これは、空白をマッチさせたい場合には、 それを引用符で囲む必要があるということを意味しています。
スキャナは、マッチするものを2つ以上見つけた場合、 以下の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
に出力します。
パターンセクションは、正規表現と呼ばれる仕組みを使って実際の マッチング処理を実行します。 正規表現は、文字列、文字、文字集合(クラス)、 および演算子から構成されています。 正規表現を構成する要素については次節以降で説明します。 また正規表現自体については、Regular Expressionsにおいて解説します。
いくつかの文字は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 POSIXとDefinitionsで説明しています。
""
- 二重引用符記号は文字列を表します。 引用符の内側の文字列だけがマッチの対象になります。 したがって、
%% "string"は
"string"
にではなく、string
にマッチします。/
- スラッシュは後続コンテキストを設定します。 これは、あるパターンの後ろに特定の文字の並びが続く場合のみ、 そのパターンを認識します。 つまり、スラッシュ
/
は「ルックアヘッド(先読み)」演算子として 機能します。 例をあげると、
abcDEF
abcDEF
を認識します。abc/DEF
abc
の後ろにDEF
が続く場合に限り、abc
を認識します。DEF
の部分は、まだ読まれてはいないかのように扱われ、 マッチの対象になりません。注意:1つのルールの中では
/
は1つだけ許されます。 つまり、abc/def/hijklは不正です。
< >
- 山かっこ< >はスタート状態を参照します。 また、EOFシンボル(
<<EOF>>
)にも使われます。 完全な説明については、 Start StatesとEnd-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");は、
apples
とoranges
の両方に対して同一のアクションを実行します。$
- ドル記号は行末を意味します。 たとえば、
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
詳細については、DefinitionsとFlex and POSIXを参照してください。
文字列とは、(常に、というわけではありませんが) 多くの場合、引用符によって囲まれる文字のグループです。 エスケープシーケンスが使われないかぎり、 文字列には改行や表示不可の文字を含めることはできません。
-i
オプション(詳細については、See Case Insensitive Scanners)
を使わないかぎり、大文字/小文字の区別も含めた字義どおりの文字列
に対してマッチが行われます。
引用符付きの文字列については、引用符は認識される文字列には含まれません。
たとえば、
string StrING "STRING" \"string\"
はすべて正当な文字列であり、最後のものは引用符も含めてマッチされます。 Flexにおいては文字列には引用符は必須ではありません。 したがって、キーワードのグループにマッチさせる場合、
begin end pointer ...
と
"begin" "end" "pointer" ...
のいずれも正当です。
Flexでは、文字をグループ化して文字クラスにすることができます。
文字クラスは、文字のグループを角かっこ[ ]で囲むことによって作成されます。
どのような文字でも正当です(表示不可の文字についてはエスケープシーケンス
を使います)。
また、文字の範囲をハイフン-
を使って指定できます。
文字クラスがルールの中で使われている場合には、
Flexはそのクラスの任意のメンバとマッチさせ、
あたかも単一文字が使われているかのように振る舞います。
たとえば、
[a-z] [A-Z]*
において、最初の例はa
からz
までの任意の単一文字にマッチします。
第2の例はA
からZ
までの任意の文字が0個以上並んだものにマッチします。
否定文字クラスを表す正規表現を書くこともできます。
否定文字クラスは、(\n
も含めて)文字クラスのメンバ以外
であればなんにでもマッチします。
これを行うには、否定すべきクラスの先頭に^
を置きます(クラスの外部では
^
は異なる意味を持つことに注意してください)。
以下に、正当なクラスの例をいくつかあげます。
[abc]
a
、b
、c
のいずれかにマッチします。
[abc\n]
a
、b
、c
、\n
のいずれかにマッチします。
[a-z]
a
からz
までの範囲にある任意の文字、
すなわち、任意の英小文字にマッチします。
[^a-z]
a
からz
までの範囲にある文字以外の任意の文字にマッチします。
[ABcd]
A
、B
、c
、d
のいずれかにマッチします。
注意:Flex、およびいくつかのバージョンのLexは、 クラス内における逆方向の範囲を扱うことができません。 したがって、
%% [z-a9-0]
はエラーメッセージを出力します。 逆方向の範囲は指定しないでください。
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:]]と 書くほうがよいでしょう)。
Flexの文字、文字列、クラス、定義、および演算子を組み合わせることで、
正規表現として知られているものが作られます。
(基本単位が数と演算子である)数学表現と同じように、基本的な要素は単純なもの
(文字、演算子、文字列、クラス、および定義)ですが、
要素を組み合わせることでより複雑な表現式を作ることができます。
たとえば、c
は単一文字の正規表現で、c
にマッチします。
cc
は2つの正規表現をつないだものを含む正規表現で、
cc
にマッチします。
c*
は、単一文字の正規表現c
と、それに続く演算子*
から構成される正規表現で、0個以上のc
にマッチします。
正規表現の真のパワーは、個々の要素よりもむしろ、
組み合わせ可能な方法の中にあります。
次の表は、Flexで利用可能な正規表現をすべて示したものです。
ここで、c
は(エスケープシーケンスを含む)任意の単一文字を、
r
は任意の正規表現を、s
は文字列を表します。
表はグループ別に編成してあり、優先度のもっとも高いものがいちばん上にあります。
Unixにおいてパターン検索が必要な場合には正規表現がよく使われますが、
アプリケーションが異なると、
正規表現もよく似てはいるもののまったく同一ではないという点に注意してください。
たとえば、Flex、egrep
、Emacs
はいずれもパターン検索のテンプレート
として正規表現を使いますが、それぞれが理解する正規表現は少しずつ異なります。
特に、Flexでは定義が使われますが、egrep
やEmacs
では使えませんし、
egrep
やEmacs
は単語の先頭と末尾にマッチさせるための\<
と
\>
とを提供していますが、Flexは提供していません。
さらに、Emacs
はバッファの先頭に対するマッチングや「ファジー」なマッチング
などを行うための、
特別な\letter
シーケンスをほかにも数多く提供しています。
なんらかの条件に基づいて、パターンマッチング処理のルールを活性化することが 便利なときがあります。 たとえば、いくつかのコンピュータ言語では、重複しているスキャンルールの あいまいさを取り除くのを支援するために、パース状態を使います。 また、ある特定の入力が見つかった後でだけ、あるルールを活性化したいという 場合があります。 このような状況に対処するために、Flexはスタート条件または スタート状態と呼ばれる単純なシステムを提供しています。
スタート状態は、あるルールがアクティブになるのはいつであるかを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
状態が活性化されている場合はone
をtwo
に置き換え、state1
状態が活性化されているか否かにかかわらず
three
をfour
に置き換えます。
デフォルトのルールにより、その他のテキストはstdout
に出力されます。
これに対して、
%x state1 %% <state1>"one" printf("two"); "three" printf("four");
は、state1
状態が活性化されているときはone
をtwo
に置き換え、
state1
状態が活性化されていないときのみthree
をfour
に置き換えます。
デフォルトのルールにより、その他のテキストはstdout
に出力されます。
このことは、排他的スタート状態が使われる場合には、マッチしないテキストが
stdout
に出力されてはならないのであれば、すべての可能な入力にマッチする
ルールを、個々の排他的スタート状態が持たなければならないことを意味
しています。
包含的スタート状態の場合は、あらゆる状態において有効な、
スタート状態への参照を持たないルールを1つ持つ必要があります。
注意: 排他的スタート状態はPOSIXの一部であるにもかかわらず、 Lexではサポートしていません。
スタート状態の名前を並べただけではあまり役に立ちません。
つまり、スタート状態がいつ活性化されるのかということも制御しなければなりません。
活性化は、アクションの中、または、記述情報内の追加の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)
によって、スキャナの状態が効果的に
(もちろん、その時点においてスキャンしている箇所を維持したまま)
リセットされることを意味しています。
以下に、スタート状態の使用に関する注意をいくつか示します。
1つのルールにおいては、単一のスタート状態、またはカンマで区切られた スタート状態のリストのみを使用することができます。 また、こうしたスタート状態の指定はルールの先頭になければなりません。 次に示すものは正当です。
%x state1 %s state2 %% <state1> "something" <state2> "another thing" <state, state2> "something else"
しかし、 次に示すものは不当です。
%x state1 %s state2 %% wrong<state1> <state1><state2>"wrong" <state2>"wrong"<state1>
排他的スタート状態は、他のすべての状態を「無効」にするので強力です。 これは、スキャナの内部においてもう1つのスキャナを効果的に定義できることを 意味しています。 これによりたとえば、スタート状態しだいで、CとPascalの両方をスキャンする スキャナを定義することが理論的には可能になります。 以下のようなコードが持つ効果を想像してみてください。
%x PASCAL %x C %% <PASCAL>begin return(OPEN_BLOCK); <PASCAL>end return(CLOSE_BLOCK); <C>{ return(OPEN_BLOCK); <C>} return(CLOSE_BLOCK);
前述のとおり、スタート状態はそれ自身の名前空間を持っていません。
その理由は、スタート状態が#define
とほとんど同じ方法で整数値として
定義されているからです。
このことは、整数値と同様、スタート状態の「スタック」のようなものを作成可能
であることを意味しています。
たとえば、
%{ int last_state[MAX_STATES] int state_count = 0; %} %x FOO BAR baz %% FOO { last_state[state_count] = FOO; state_count++; BEGIN(baz); } BAR { last_state[state_count] = BAR; state_count++; BEGIN(baz); } <baz>rule 1 ... <baz>rule n <baz>END { statecount--; BEGIN(last_state[statecount]); }
はFOO
とBAR
の両方によってbaz
状態を活性化させ、
<baz>END
というルールによって1つ前の状態に戻します。
こうした「状態スタック」は将来、Flexの特徴的な機能になるかもしれません。
4
Flex 2.5では、以下の新機能を利用できます。
スタート状態<*>
には特殊な意味があり、すべてのスタート状態にマッチします。
たとえば、
%x state1 %% <state1>"one" printf("two"); <*>"three" printf("four");
は、
%s state1 %% <state1>"one" printf("two"); "three" printf("four");
と同じ意味になります。
マクロYY_START
により、現在のスタート状態を参照できます。
前節(Start State Notes)の「スタート状態の名前」に示した例では、
現在のスタート状態を配列last_state
に格納する処理を以下のように
記述していますが、
FOO { last_state[state_count] = FOO; ... }
この代入は、
last_state[state_count] = YY_START;
のように書き換えることができます。
Lexとの互換性のために、YYSTATE
が、
YY_START
の別名として定義されています。
スタート状態のスコープを定義できます。 これにより、同じスタート状態において複数のルールが存在するときに、 その個々のルールにスタート状態を指定する必要がなくなります。
スタート状態のスコープの形式は、以下のとおりです。
<start_states>{ ... }
ここで、start_statesは、単一のスタート状態、または、
カンマで区切られたスタート状態のリストです。
スタート状態のスコープの境界は、{
と}
によって指定されます。
スタート状態のスコープを入れ子にすることも可能です。
Activating Statesに示した例を、スタート状態のスコープを使って書き直すと
以下のようになります。
%x COMMENT %% "{" BEGIN(COMMENT); <COMMENT>{ "$R" "$I" "$M" ... "}" BEGIN(INITIAL); }
スキャナ定義ファイルで%option stack
を指定すると、
スタート状態スタックを利用できます。
スタート状態スタックを操作するために、以下の関数が提供されています。
void yy_push_state(int new_state)
void yy_pop_state()
int yy_top_state()
前節(Start State Notes)の「スタート状態の名前」に示した例を、 スタート状態スタックを使って書き直すと以下のようになります。
%option stack %x FOO BAR baz %% FOO { yy_push_state(baz); } BAR { yy_push_state(baz); } <baz>rule 1 ... <baz>rule n <baz>END { yy_pop_state(); }
プログラミングにおいて、何かをする方法を学ぶのに最良の方法は、 実際にそれをやってみることです。 そのことに留意し、スタート状態をどのように使うことができるかを示す例を 以下にあげます。
/* * 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
を
実行することにより、コンパイルすることもできます。
Flex 2.5では、スキャナ定義ファイルの中でさまざまなオプションを指定できます。
オプションを指定するには、スキャナ定義ファイルの先頭
(最初の%%
よりも前の部分)に、%option
指示子を記述します。
ほとんどの%option
指示子は、以下の形式で指定されます。
%option option_name
オプションoption_nameの指定を無効にするためには、
オプション名の前にno
を付けます。
%option nooption_name
以下に、コマンドラインオプションと同等の効果を持つ%option
指示子
を示します。
各コマンドラインオプションの意味については、Command Line Switchesと
Command Line Switches (Flex 2.5)を参照してください。
%option 7bit
%option 8bit
%option align
%option backup
%option batch
%option c++
%option caseful
%option case-sensitive
%option case-insensitive
%option caseless
%option debug
%option default
%option ecs
%option fast
%option full
%option interactive
%option lex-compat
%option meta-ecs
%option output="file"
%option perf-report
%option prefix="prefix"
%option read
%option stdout
%option verbose
%option warn
次に、コマンドラインオプションでは代替できない%option
指示子を示します。
%option array
yytext
をchar
の配列として定義します。
これは、
%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
yytext
をchar
に対するポインタとして定義します。
これは、%pointer
と同じです。
%option reject
REJECT
が使われていることを、Flexに通知します。
Flexは通常、定義ファイルの中でREJECT
が使われているか否かを自分で
調査しますが、この%option
指示子の指定は、Flex自身による判定結果に
優先します。
%option stack
%option
指示子を指定しなければなりません。
%option stdinit
yyin
をstdin
で、yyout
をstdout
で、
それぞれ初期化します。
この%option
指示子が指定されない場合、
あるいは、%option nostdinit
が指定された場合、
yyin
とyyout
は、(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
のように定義されます。 この結果、ファイルの終端を検出したときに、スキャナは、 ほかにスキャンすべきファイルは存在しないと判断するようになります。
この章ではCおよびBisonと一緒にFlexを使う方法を説明します。 6 C、Bisonのそれぞれについて非常に細かい説明が必要なため、 本章は2つの部分に分かれています。 その両方に、全般的なインターフェイス概念に関する節とプログラム例を 示す節があります。
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_size
はbuffer
のサイズです。
以下に、一度に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
という文字の並びを入力として与えると、
stdout
にfoofoobar
という文字の並びが書き込まれます。
%% 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文字以外のすべてを戻します。
戻された文字の並びは、次のトークンをマッチするのに使われ、yyleng
と
yytext
には、この変化を反映した値が設定されます。
引数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++コンパイラを使ってコンパイルされる場合は、
この関数input
はyyinput
という名前になります。
これは、input
という名前が同一名のC++ストリームと衝突するからです。
また、Flexではinput()
はyytext
の内容を破壊しますが、
Lexではyytext
は変更されずそのまま残ります。
これは将来のリリースで修正される予定です。
unput(c)
unput()
は、
文字c
が次にスキャンされる文字になるように、
文字c
を入力ストリームに置く関数です。
たとえば、
%% foo unput('b');
はfoo
をb
で置き換えます。
これは、foo
にマッチしてb
を戻し、このb
が次にスキャンされる
文字になるからです。
デフォルトのルールにより、b
はstdout
に書き込まれます。
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
を使った後は、yytext
とyyleng
は新しい値を取ります。
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;
になるようにしてください。
Flex 2.5では、前節(Flex and C)で説明されていない、 以下のマクロもサポートされています。
yy_set_interactive(is_interactive)
yy_set_interactive()
による指定は、%option always-interactive
や
%option never-interactive
による指定に優先します。
このマクロは、バッファからのスキャン処理が始まるよりも前に
呼び出されなければなりません。
yy_set_bol(at_bol)
^
を含むルールが適用されるのは、バッファ内の
現在位置が実際に行の先頭である場合だけですが、現在位置が行の先頭に
あるか否かという情報は、バッファのコンテキスト情報として保持されています。
マクロyy_set_bol()
は、バッファ内の現在位置が行の先頭に
あるか否かを表すコンテキスト情報を設定します。
引数にゼロ以外の値を渡すと、バッファ内の現在位置は行の先頭である、
というコンテキスト情報がセットされます。
したがって、次にトークンのマッチ処理が行われるときには、
行頭を表す^
を含むルールの適用が試みられます。
逆に、引数にゼロを渡すと、バッファ内の現在位置は行の先頭ではないことになり、
次にトークンのマッチ処理が行われるときには、
行頭を表す^
を含むルールの適用が試みられなくなります。
YY_AT_BOL()
^
を含むルールの
適用が試みられるようなコンテキスト情報がセットされている場合には、
ゼロ以外の値を返します。
それ以外の場合は、ゼロを返します。
ある単語が現れたときに、それを別の単語に置き換える必要が生じること がよくあります。 たとえば、ある名前が現れるたびに、それをある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.
このプログラムがうまく動作するのは、yyin
とyyout
がデフォルトでは
stdin
、stdout
にそれぞれ割り当てられ、かつ、
デフォルトのアクションが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
です。
Bisonは、Flexと同様、ある記述情報を受け取って、 それをもとにCのコードを生成するプログラムです。 両者の違いは、 BisonがCやPascalのような言語の文法に関する記述情報を入力として受け取り、 その記述情報からパーサを生成する点にあります。 FlexとBisonを結合することにより、言語の字句解析と構文解析の両方を 処理できるようになります(これは、コンパイラデザインにおいてもっとも 容易に自動化できる部分です)。
生成されるパーサが機能するためには、
Bisonはyylex()
という関数を必要とします。
この関数はユーザーによって提供され、呼び出されたときに、
パースされている言語のある要素を表す整数値をBisonに返します。
Flexにおいてスキャン処理を行うルーチンはyylex()
であり、
デフォルトでは整数値を返します。
これにより、FlexとBisonを一緒に使うのは非常に簡単になります。
警告: 以下の節では、読者がBisonの基本的なパーサの宣言を 理解しているものと仮定します。 Bisonを使った経験のない人には、パーサの定義は混乱をもたらす可能性 がありますので、先に進む前にぜひBisonのマニュアル11を読んでください。 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のマニュアルを参照してください。
FlexからBisonに対して、単なる整数値以上の情報を渡す必要の生じること がよくあります。 たとえば、コンパイラにおいては、どのような種類のトークンが認識されたか だけではなく、そのトークンの値についても知る必要のある場合がよくあります。 文字列、文字、および数値定数などが良い例です。 ここで問題なのは、どのようにしてFlexにこうした情報を返させるかです。
その答えは、Bisonが持っている%union
文です。
これは、YYSTYPE
という型を定義するものです。
YYSTYPE
は、パーサ定義中において使われるすべての正当なデータ型の
共用体(union
)です。
Bisonが現在のパース状態に関連づけたデータを保存するために使う、
YYSTYPE
型の変数yylval
というものがあり、Flexからyylval
に値を設定することができるので、トークンの型だけでなく、それ以上の情報を
Bisonに返すことができます。
Bisonにおいて%union
を宣言して-d
オプションを使うと、Bisonは
.tab.h
という拡張子を持つファイルを作成して、そこにトークンの定義情報
だけでなく、YYSTYPE
とyylval
の宣言も含めます。
したがって、yylval
にアクセスするためにしなければならないことは、
Flexの定義情報の中にこの.tab.h
ファイルをインクルードすることだけです。
これは、追加のCコードセクションにおける定義の先頭でインクルード
しなければなりません(see Interfacing Flex and Bison)。
注意:初期のバージョンのBisonは、自動的にYYSTYPE
と
yylval
の宣言を生成しません。
この場合には、より新しいバージョンのBisonを入手するか、もしくは、Flexの
定義ファイルの先頭においてYYSTYPE
とyylval
を宣言する必要
があります。
コードを読むのは、プログラミングのしかたを学ぶ良い方法です。 そこで、Flex、Bisonのインターフェイス例をもう1つ示すことにします。 次の例では、拡張してデータベースを操作するために使うことができるような、 小規模な言語のための簡単なパーサを作ります。
データベースとのインターフェイス言語は、英語の非常に小さなサブセットになります。 文法はおおよそ以下のようになります。
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のマニュアル
を参照してください。
すでに、小規模な言語については説明しました。 次にそれを実装してみることにしましょう。 以下のファイルがこれを実現します。
注意:これはあくまでも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
を使うのであれば、このファイルは必要ありません。
以下に実装に関する注意を示します。
YYSTYPE
と yylval
yylval
がFlexからアクセスされる方法に注目してください。
Bison文法においてパースツリーの上位にデータを渡す方法については、
Bisonのマニュアルに説明されていますが、
Flexに対してはなんの影響も持ちません。
整数値、浮動小数点数値、および他の任意の型のデータも同様の方法で
返すことができます。
yylex()
を呼び出し、
スキャナがトークン定義を提供しています。
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++クラス
(FlexLexer
とyyFlexLexer
)が定義されています。
FlexLexer
は、C++スキャナクラスが実装すべきインターフェイスを構成する
抽象仮想関数を定義するクラスです。
FlexLexer
の持つメンバを以下に示します。
char* yytext
int yyleng
int yylineno
%option yylineno
が指定されている場合は、入力された行数を保持します。
それ以外の場合は、固定値1を持ちます。
int yy_flex_debug
次に、FlexLexer
の持つメンバ関数のうち、
抽象仮想関数ではないものを以下に示します。
const char* YYText()
yytext
の値を返します。
int YYLeng()
yyleng
の値を返します。
int yylex(istream* new_in, ostream* new_out = 0)
switch_streams()
を呼び出した後、メンバ関数int yylex(void)
を呼び出します。
int lineno() const
yylineno
の値を返します。
int debug() const
yy_flex_debug
の値を返します。
void set_debug(int 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.cc
にclassname::yylex()
が生成されます。
クラスclassnameをyyFlexLexer
のサブクラスとして定義すること
によって、classnameのインスタンスを使ってスキャン処理を実行できます。
クラスclassnameを定義する際、以下に示す、yyFlexLexer
の持つ
protected
メンバ関数を再定義することによって、スキャナの振る舞いを
変更できます。
int LexerInput(char* buf, int max_size)
#ifdef YY_INTERACTIVE
を使います。
void LexerOutput(const char* buf, int size)
void LexerError(const char* 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>
ここでは、Lexが提供していない機能や一般にはあまり使われない機能を説明します。 Flexはほぼ100パーセントLex互換ですが、Lexよりも後に実装されたため、 性能的により優れており、 また、広範な用途に使えるスキャナをより簡単に作成することができるよう、 特別な機能を提供しています。
多くの言語は、その識別子において大文字/小文字を区別しません(Pascal、 BASIC、FORTRANなど)。 Lexにも、大文字/小文字を区別しないスキャナを指定するための方法がありますが、 それらは概して美しくなく、理解するのも困難です。 個々の文字を置き換えてくれる定義を、長いリストにして作成できますし、 すべての識別子を受け付ける1つのルールを作成し、そのルールにおいて 大文字/小文字を変換してから、トークンの種類を返すようにすることも可能です。 以下のコードは、この2つの方法を示すものです。 定義を使うのであれば、次のようになります。
A [aA] B [bB] ... Z [zZ] %% {B}{E}{G}{I}{N} return(BEGIN_SYM); {E}{N}{D} return(END_SYM);
これに似た操作をサブルーチンで実行するのであれば、以下のようにします。
ALPHA [a-zA-Z] NUM [0-9] ALPHANUM {ALPHA}|{NUM} %% {ALPHA}{ALPHANUM}* return(convert_and_lookup(yytext));
もっともこれは、関数呼び出しの必要があるため、効率が悪くなります (Flexでは、パターンの複雑さは大して影響しません)。
ほかにもこれと同じことを行う方法がありますが、いずれもエレガントではありません。
-i
オプションFlexは、
この問題を簡単に解決するための方法を提供しています。
コマンドラインで-i
オプションを使うことによって、入力情報の
大文字/小文字を区別しないスキャナを生成するよう、Flexに対して
通知することができます。
つまり、Flexでは上記のようなテクニックを使う必要がないということを
意味しています。
たとえば、
%% begin return(BEGIN_SYM); end return(END_SYM);
は、
-i
オプションを使うことによって、BEGIN
、begin
、
BeGiN
、およびこれ以外のすべての大文字/小文字の組み合わせにマッチします。
これは、Lexにおいて同様のことを行うための方法よりも、はるかに簡単です。
-i
オプションには1つ注意すべき点があります。
それは、スキャナが大文字/小文字を区別しないだけで、その
変換まではしてくれないということです。
つまり、Pascalにおいてシンボル名をハッシュしたいような場合、自分でシンボル名
を大文字または小文字に変換しなければならないことを意味しています。
そうしないと、FOO
とfoo
は異なるものとして扱われます。
これは、シンボルを保存するルーチンの中で対処できますし、
YY_USER_ACTION
を使うことによって対処することもできます。
これを実現する方法の例については、Flex and Cにおける
YY_USER_ACTION
の説明を参照してください。
-I
オプション:対話型スキャナFlexの問題として、どのルールを適用するかを決定する前に、 入力情報中の次の1文字を先読みする必要があるということがあります。 対話的ではない使い方をする場合には問題になりませんが、 Flexを使ってユーザーから直接入力文字を受け取るような場合には、 問題になることがあります。
このような例を2つあげると、1つはシェルとやり取りする場合、 もう1つはデータベースのフロントエンドとやり取りする場合です。 通常のアクションは、改行が入力の終わりを表すというもので、 改行自身は一種の「中身のない文」として受け付けるのが望ましいのですが、 通常のFlexスキャナではこれは可能ではありません。 Flexが常に先読みをするという事実は、改行が認識されるためにはユーザーが次の行を 入力しなければならないということを意味しています (すなわち、単一の改行は、それだけでは認識されず、 他の文字が入力される必要があるということです)。 これはシェル上ではまったく望ましくありません。
Flexにはこれを回避する方法があります。
コマンドラインで-I
オプションを使うと、Flexは、
必要な場合にしか先読みをしない特別な対話型スキャナを生成します。
この種のスキャナは、ユーザーからの入力を直接受け取るのに適していますが、
若干の性能低下を引き起こすかもしれません。
注意:-I
オプションは、-f
、-F
、-Cf
、
または-CF
フラグと一緒に使うことはできません。
つまり、先読みができないことからくる性能低下に加えて、パーサも
性能向上のために最適化することができないということを意味しています。
-I
オプションにおけるマイナス面は、通常はごくわずかなので、
入力情報がどこからくるのかが確かではなく、性能向上のための最適化
を施す可能性を諦めてもかまわないのであれば、コマンドラインで
-I
オプションを使ったほうがよいでしょう。
テーブルの圧縮とスピードの面では、 FlexはLexの能力をはるかに上回っています。 Flexは、使われるオプションに応じて、Lexよりもはるかに高速なテーブル、 あるいは、はるかに小さなテーブルを生成できます。 この節では、利用可能なオプションと各オプションがスピードにもたらす影響 について説明します。 一般に、テーブルが圧縮されるほど、そのスピードは遅くなります。 Flexでは、こうしたオプションをコマンドラインで指定します。 オプションは以下のとおりです。13
REJECT
を使うことはできない点に注意してください。
注意:-f
フラグと-F
フラグでは、
生成するテーブルに相違があります。
-f
フラグはフルテーブル(full table)を生成し、
-F
フラグはファストテーブル(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
が使われると、
Flexは同等クラス(equivalence classes)を作成します。
同等クラスとは、同一の方法で使われる文字のグループです。
たとえば、使われる数字が集合[0-9]
の範囲に限定されるのであれば、
0から9までの数は同等クラスの中に置かれることになります。
-Cf
あるいは-CF
を指定して生成されたスキャナと比較して、
サイズもはるかに小さくなる可能性があります。
サイズまたはスピードの一方を優先させる必要がないのであれば、
これは良い組み合わせです。
-C
オプション単体では、同等クラスやメタ同等クラスを使わずに
テーブルを圧縮します。
注意:-Cxx
オプションは、コマンドライン上には1つだけ
指定すべきです。
というのは、このうち最後に見つかったオプションだけが実際の効果を持つからです。
したがって、
flex -Cf -Cem foo.l
は、Flexに-Cem
オプションを使わせることになります。
Flexのデフォルトの動作は、
コマンドライン上で-Cem
オプションを使った場合に相当します。
この動作では圧縮を最大限に行うことになり、一般的にはもっとも遅いスキャナ
が生成されることになります。
こうした小さなテーブルはより速く生成され、コンパイルもより速く実行されるので、
デフォルトは、開発段階では非常に便利です。
スキャナのデバッグが終了した後は、より高速な(そして通常はよりサイズの大きい)
スキャナを作成することができます。
翻訳テーブルは、文字をグループにマップするのに使われます。
このテーブルはLexの持つ機能の1つですが、POSIXでは定義されていません。
Flexでも翻訳テーブルを使うことはできますが、サポート対象外の機能です。
Flexにおいては翻訳テーブルは不要です。
というのは、Flexには-i
オプションによる同等クラスというものがあり、
これが翻訳テーブルと同等の機能を実現しているからです
(see -i
オプション)。
翻訳テーブルの機能は、互換性のためだけに存在する余分な機能です。
したがって、翻訳テーブルを使うことはおすすめできません。
翻訳テーブルを使いたいのであれば、定義ファイルの先頭の定義セクション
において定義しなければなりません。
翻訳テーブルの一般的な形式は以下のとおりです。
%t 1 ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 0123456789 %t %%
これは、
A
からZ
までの任意の文字がルールの中で使われている場合、
そのパターンはA
からZ
までのどの文字にもマッチする
ということを意味しています。
したがって、A(BC)
とX(YZ)
はまったく同一であるということになります。
スキャナが、複数のファイルからの入力を処理できるということが必要になる状況は、
たくさんあります。
たとえば、多くのPascalの実装では、コンパイル時に複数のファイルを取り込むこと
を許していますし、Cでは、スキャナもしくはプリプロセッサが#include
文を
処理できなければなりません。
このことが意味しているのは、スキャナは、現在のスキャン処理の
コンテキストを保存してから新しいコンテキストに変更し、その後で、
以前の状態と完全に一致する状態に復帰できなければならないということです。
Flexスキャナは、 スキャン処理のコンテキストを維持するために余分の処理が必要になるような、 大きな入力バッファを使っています。 しかしFlexは、複数の入力バッファの作成、切り替え、削除が非常に簡単に 行えるような特別な機能を提供しています。
Flexは、複数の入力バッファを取り扱うために、以下のような関数やマクロを 提供しています。
YY_BUFFER_STATE yy_create_buffer( FILE *file, int size)
file
で指定されるファイルのために、size
で指定される数の文字を
格納するのに十分な大きさのバッファを作成します。
この関数は、後に複数のバッファ間の切り替え、または新規に作成されたバッファ
の削除に使うことのできるハンドルを返します。
YY_BUF_SIZE
yy_create_buffer()
に渡すべきサイズがわからない場合に、
これを使うことができます。
void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer)
new_buffer
で指定されるバッファから
取られます。
ファイルの終端(EOF)に達するか、次にyy_switch_to_buffer()
が呼び出されるまで、new_buffer
からトークンが読み込まれます。
new_buffer
がEOFに達すると、新しいバッファに切り替えることができます。
void yy_delete_buffer( YY_BUFFER_STATE buffer )
buffer
で指定されるバッファを削除し、
それに割り当てられたメモリを解放します。
YY_CURRENT_BUFFER
これらが、複数の入力バッファを取り扱うのに必要なすべての機能を提供しています。
Flex 2.5では、以下のバッファ操作関数もサポートされています。
YY_BUFFER_STATE yy_new_buffer(FILE *file, int size)
yy_create_buffer
の別名です。
void yy_flush_buffer(YY_BUFFER_STATE buffer)
YY_END_OF_BUFFER_CHAR
(\0
)をセットします。
YY_FLUSH_BUFFER
yy_flush_buffer()
を呼び出すよう
定義されたマクロです。
さらに、Flex 2.5では、メモリ上の文字列を操作するための入力バッファを作成する
関数が提供されています。
いずれも、新しく作成された入力バッファに対応するYY_BUFFER_STATE
型の
ハンドルを戻り値とします。
入力バッファを使い終わったら、このハンドルを引数に指定して
yy_delete_buffer()
を呼び出す必要があります。
YY_BUFFER_STATE yy_scan_string(const char *str)
yy_scan_bytes()
を呼び出し、その戻り値を返します。
YY_BUFFER_STATE yy_scan_bytes(const char *bytes, int len)
yy_scan_buffer()
を呼び出し、その戻り値を返します。
yy_scan_buffer()
の第1引数には、bytesではなく、
この関数の内部で獲得されたlen + 2
バイトの領域へのポインタ
が渡される点に注意してください。
yy_scan_buffer()
が呼び出される前に、bytesから始まるlenバイト
のデータが、新たに獲得した領域にコピーされ、さらに、末尾の2バイトに
YY_END_OF_BUFFER_CHAR
(\0
)がセットされます。
YY_BUFFER_STATE yy_scan_buffer(char *base, yy_size_t size)
YY_END_OF_BUFFER_CHAR
(\0
)
でなければなりません。
この末尾2バイトは、スキャン処理の対象にはなりません。
引数で指定されたメモリ領域の末尾2バイトがYY_END_OF_BUFFER_CHAR
でない場合
は、yy_scan_buffer()
はバッファを作成せず、NULLポインタを返します。
複数のバッファを使うというアイデアを理解するための助けとして、
インクルードすべきファイルを探すCのスキャナの一部を以下に示します。
これはCの#include
のうち、引用符で囲まれた文字列のみを受け付けます。
たとえば、
#include"file1.c" #include "file2.c" #include " file3.c"
は、 最後の例のファイル名が空白を含むことになりますが、いずれも正当な入力です。 ここでの例はまた、EOFルールとスタート状態の使用法を示す良い例でもあります。
/* * eof_rules.lex : 複数バッファ、EOFルール、スタート状態 * の使い方の例 */ %{ #define MAX_NEST 10 YY_BUFFER_STATE include_stack[MAX_NEST]; int include_count = -1; %} %x INCLUDE %% ^"#include"[ \t]*\" BEGIN(INCLUDE); <INCLUDE>\" BEGIN(INITIAL); <INCLUDE>[^\"]+ { /* インクルードファイルの名前を獲得する */ if ( include_count >= MAX_NEST){ fprintf( stderr, "Too many include files" ); exit( 1 ); } include_stack[++include_count] = YY_CURRENT_BUFFER; yyin = fopen( yytext, "r" ); if ( ! yyin ){ fprintf(stderr,"Unable to open \"%s\"\n",yytext); exit( 1 ); } yy_switch_to_buffer( yy_create_buffer(yyin,YY_BUF_SIZE)); BEGIN(INITIAL); } <INCLUDE><<EOF>> { fprintf( stderr, "EOF in include" ); yyterminate(); } <<EOF>> { if ( include_count <= 0 ){ yyterminate(); } else { yy_delete_buffer(include_stack[include_count--] ); yy_switch_to_buffer(include_stack[include_count] ); BEGIN(INCLUDE); } } [a-z]+ ECHO; .|\n ECHO;
スタート状態を使ってファイル名のスキャナを生成する方法や、
バッファの切り替えを発生させる方法に注目してください。
ほかに注目すべき重要な点は、<<EOF>>
を取り扱うセクション、および
古いバッファに復帰する際にBEGIN
を使って確実に正しい状態に
遷移するようにする点です。
これを怠ると、状態はINITIAL
にリセットされ、
#include
の最後の"
がECHO
されてしまいます。
注意:<<EOF>>機能は次の節で説明します。 <<EOF>>がなんであり、何を行うものかという点に関する詳細な解説については、 See Start States。
ファイルの終端(EOF)が見つかると、Flexはyywrap()
を呼び出し、
ほかに処理できる状態のファイルが存在するか調べます。
yywrap()
が0以外の値を返すと、もうこれ以上ファイルはないということを
意味し、したがって、これがまさに入力の最後であるということになります。
状況によっては、この時点でさらに処理を行う必要のある場合があります(たとえば、
入力のために別のファイルをセットアップしたいということがあるかもしれません)。
このような場合のために、Flexは<<EOF>>
演算子を提供しています。
これを使うことで、EOFが見つかったときに実行すべきことを定義できます。
See 複数バッファを使うプログラム例。
EOFルールを使って、終わりのないコメントやインクルードされているファイルの終端
を見つける、良い例が示されています。
<<EOF>>
演算子の使用にはいくつか制限があります。
制限事項を以下に示します。
EOFルールは、スタート状態とのみ一緒に使うことができます。
スタート状態が指定されていない場合(すなわち、<<EOF>>
ルールが
状態により制限されない場合)、<<EOF>>
が使われていないすべての
(排他的スタート状態を含む)状態が影響を受けます。
つまり、
"foo"<<EOF>>
が不当である一方で、
<<EOF>> /* <<EOF>>が使われていないすべての */ /* 状態におけるEOF */ <indent><<EOF>> /* indent状態におけるEOF */ <comment><<EOF>> /* コメント内のEOF */
はすべて正当であることを意味しています。
1つ注意しなければならない点は、EOFルールは入力の最後で呼び出されるという点です。
したがって、EOFルールのアクションは、(1) (yy_switch_to_buffer()
、
またはYY_NEW_FILE
を使って)新しい入力ストリームを確立する、
(2) (return
文を使って)復帰する、(3) (yyterminate()
、
またはexit()
を使って)スキャナの実行を終了させる、
のいずれかを実行しなければなりません。
See 複数バッファを使うプログラム例。
yy_terminate()
とyy_switch_to_buffer()
を使う例が示されています。
また、yyterminate()
の説明については、Flex and Cを参照してください。
デバッグをしている間は、スキャナの性能は通常それほど重要ではなく、 Flexのデフォルトの設定で十分です。 しかしデバッグ終了後は、スピード、またはサイズの面でスキャナを 最適化したくなることもあるでしょう。 ここでは、スキャナを最適化するのによく使われる手法をいくつか紹介します。
多くのプログラムは、字句解析の処理に多くの時間を費やします。 したがって、スキャナの最適化はかなり大きな性能改善に結びつくことが多いのです。 Flexによるスキャナは、Lexによるスキャナと比較するとかなり高速になる傾向 がありますが、特定の構成もしくはアクションによって、性能に大きな影響を及ぼす ことができます。 注意すべき点は以下のとおりです。
-f
オプションまたは-F
オプションを使ってください。
テーブルの圧縮とスピードに関連するオプションに関する詳細な解説については、
See Table Compression and Scanner Speed。
-f
オプションまたは-F
オプションを使わない場合は、
ルールからバックトラッキングを削除しようとするのは時間の無駄です。
スキャナからバックトラッキングを削除することに関する詳細な情報については、
See Removing Backtracking。
REJECT
と同じくらい悪影響を及ぼすもので、
可能な場合にはいつでも避けるべきです。
この例を示すと、以下のようになります。
%% linux|hurd/(OS|"Operating system")
これは、以下のように分割すべきです。
linux/OS|"Operating system" hurd/OS|"Operating system"
こうすることによって、問題は解消されます。
^
演算子は、性能に不利な影響を及ぼします。
スピードがもっとも重要な場合には、使わないでください。
yymore
を使うと性能を低下させます。
スピードがもっとも重要な場合には、使わないでください。
yytext
環境をセットアップする必要がないからです。
スキャナの実行時間のほとんどは、
内部の高速なマッチングループの中で費やされることになります。
NUL
NUL
を含むトークンをマッチするのに時間がかかります。
この場合には、
短いテキストにマッチするようにルールを記述したほうがよいでしょう。
スキャナからバックトラッキングを削除することは、 スキャナの性能にかなりの影響をもたらします。 残念ながら、バックトラッキングの削除はかなり複雑な作業になる可能性があります。 たとえば、
%% hurd return(GNU_OS); hurdle return(JUMP); hurdled return(JUMPED);
では、
バックトラッキングが発生します。
スキャナがhu
をマッチし、次の文字がr
ではない場合、
マッチされなかったテキストをECHO
するデフォルトのルールを使って
h
とu
を処理するために、スキャナはバックトラッキングを
行わなければなりません。
同じことがd
とe
についても適用されます(これは、何かに
マッチするようスキャナが試み続けるということが、もはやできないからです。
この場合、スキャナはデフォルトのルールを適用し、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個のルールにしぼることが可能なので、
バックトラックデータを調べようと努力するだけの価値はあります。
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テーブルと比較して、
より多くのスペースを必要とする可能性があるからです。
ここでは、Flexの使用例をさらにいくつか紹介します。 ここでの例も、必ずしも最適な実装ではありませんが、 一般的なFlexの使い方を示してくれるはずです。
以下の定義は、与えられたファイルの中の単語数、文字数、行数をカウントするのに 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"); }
ここでは、Pascalのような言語用の字句スキャナを作る方法を示します。 このスキャナ定義では、個々のキーワードがルールとしてリストされています。 (一般的には、すべてのキーワードをテーブルに格納してからテーブル検索を使う 手法がよく見られますが)ここでの方法は、キーワードと識別子とを区別するための 方法としては、一般にもっとも簡単なものです。 また、識別子用にただ1つのルールがあるという点に注意してください。 多くの場合、このルールはシンボルテーブルを管理するためのサブルーチンを 呼び出します。
もう1つ注意すべき点は、_FILE
と_BEGIN
が先頭にアンダースコアを
持つという点です。
Flex、またはCで定義済みの名前は、追加のくふうなしでは使えないということ
を示すために、このようにしてあります。
これよりももっと一般的に使われる手法は、すべてのトークンの先頭
もしくは末尾になんらかの文字列を付加するというもので、こうすることによって
問題は発生しなくなります。
TOK
やSYM
が一般的によく使われる拡張子です。
/* * 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); }
ここでは、スタート状態を使って、
Flexにより生成されるスキャナの内部に小規模のパーサを作る方法の例を示します。
このコードはThe New Hackers Dictionary(prep.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種類を検索して、コマンドを出力します。
...enumerated: 0.some text 1.some more text
また、プログラム例は以下のようになります。14
...example: some text
ここでの例は、パースされているものがなんであるかを示すヒントとして
このようなパターンを使い、その特定のセクション用の部分的なパーサを
(ほとんどの場合、排他的)スタート状態を使って作ります。
ASCII版の専門用語ファイルを持っているのであれば、スキャナのどの部分がその
ファイル中の何にマッチするかを検証してみる価値があります。
たとえば、HEADING
状態において@item
を生成するルールが、
すべての専門用語のエントリを処理するルールでもあるということは、
おそらく一見しただけでは明らかではないでしょう。
ここで非常に簡単にではありますが、FlexとLexの両方を概観してみます。 Flex、Lexそれぞれの性能と、Lexのようなユーティリティに関するPOSIX標準 への準拠度についても、いくつか一般的なコメントを示します。
Flexは、Lexのより優れた再実装であり、Lexと同様、パターンとアクションの 記述情報を入力として受け取って、そのパターンにマッチする能力を持つCの スキャナに変換するものです。 しかしながら、Flexはより少ない時間でテーブルを生成しますし、Flexにより 生成されるテーブルは、Lexにより生成されるテーブルと比較して、はるかに 効率的なものです(Flexが正確には何を生成するのかという説明については、 本書の冒頭で言及した書籍を参照してください)。
Flexは、LexおよびPOSIXと十分に互換性があり、 それ独自の特別な機能もいくつか追加しています。
Flexは、大体のところLexおよびPOSIXの両方と互換性があります。 将来は(Flex、POSIXのどちらかが変わることによって)、 さらにPOSIXとの互換性を高めていくでしょう。 しかし、Flex、Lex、POSIXには、異なる部分もいくつかあります。 それを以下に示します。
^
、$
、/
、<<EOF>>
、
および<start state>
は使うことができないということを意味しています。
このことがもたらす主要な問題の1つに、マッチの優先順位に影響を与え、
FlexとLexの間でスキャン処理に微妙な差異が出てくるということがあります。
この問題の例については、パターンセクション
を参照してください。
input()
input()
は再定義可能ではありません。
Flexで入力を制御するためには、input()
を再定義する代わりに、
YY_INPUT
という拡張機能を使います(これは現在のところPOSIXではサポート
されていません)。
また、Lexとは異なり、Flexのinput()
はyytext
の値を変更する
という点に注意してください。
output()
output()
ルーチンをサポートしていません。
ECHO
の出力はyyout
経由で行われます。
このyyout
のデフォルトはstdout
です。
これを使うようにoutput()
を書くことも可能ですが、現在のPOSIXの
ドラフト仕様は、output()
が正確には何をすべきなのかを示していません。
%r
)をサポートしていません。
yylineno
yywrap()
yywrap()
はマクロです。
POSIXのドラフト仕様では、これは関数であるべきとされているので、
おそらく将来は変更されることになるでしょう。18
unput()
unput()
はyytext
とyyleng
の値を破壊しますが、
次のトークンがマッチされるまでは、これは不当です。
LexとPOSIXでは、yytext
とyyleng
はunput()
の影響を受けません。
19
abc{1,3}
は
「ab
の後ろに1個、2個、または3個のc
が続くもの」
にマッチすべきとなっています。
Flexはこのとおりに動きますが、Lexはこれを「1個、2個、または3個のabc
」
と解釈します。
yytext
yytext
の正しい定義はextern char *yytext
ですが、
Lexではextern char yytext[]
です。20
配列によるアクセス方法は、性能にかなりの影響を及ぼすので、
Flexではextern char *yytext
を使い続けるでしょう。
最新のPOSIXドラフト仕様は、%array
と%pointer
を導入すること
によって、両方の方法をサポートしています。
これは、FlexとLexのいずれにもまだ組み込まれていません。
21
%p
、%a
など)がありますが、
Flexでは必要ありません。
互換性のために認識はされますが、無視されるだけです。
FLEX_SCANNER
FLEX_SCANNER
が#define
によって定義されています。
{...}
を使うことなく、
単一行において複数の文を置くことができます。
これに対してLexは、そのような行を単一文に切り詰めてしまいます。
#
で始めることができますが、
LexとPOSIXではできません。
ただし、この形式のコメントを使うことはおすすめできません。
yyterminate()
、yyrestart()
、<<EOF>>
、YY_DECL
、#line
指示子
#line
指示子の説明に関しては、
See Flex コマンドラインオプションの要約。
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
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のインストールなどはほんの些細なことでしょう。
ここでは、読者がプログラムの中で使うことのできる、ちょっとしたFlex定義 を一覧にして示します。 多くは、本書を読んだ後では、かなり自明のものになるはずです。 しかし、読者がこうしたコードを最初から作らずに済むように、 ここでまとめています。
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
の中にある例と
ほとんど同じです。
より長いテキストブロックにマッチするため、はるかに高速ですし、
ルールの中で改行のみにマッチさせる必要もありません。
文字列は、それが入力として与えられたときに破棄されないという点で、
コメントとは若干異なります。
しかし、基本的なアプローチは同じです。
第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 Cのyymore
の項を
参照してください。
ここでは、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()
の仕様です。
ときには、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
行数をカウントしたいのであれば、ファイルの先頭のオプションのCコードセクション に変数を定義して、改行をチェックします。 スタート状態の中でも改行をチェックするのを忘れないようにしてください。 さもないと、行数のカウントはうまくいきません。 たとえば、以下のようにします。
%{ int linenumber = 0; %} %x COMMENT STRING %% "/*" BEGIN(COMMENT); <COMMENT>\n line_number += 1; <COMMENT>[^\n]* <COMMENT>"*/" BEGIN(INITIAL); \" BEGIN(STRING) <STRING>\\\n line_number += 1; <STRING>[^\n\\\"]* <STRING>\" BEGIN(INITIAL); \n line_number += 1;
yyin
とyyout
をリダイレクトすることによって、スキャナを
(したがってBisonによるパーサをも)ソケットにアタッチできます。
これは、fdopen()
を呼び出すことによって行います。
たとえば、以下のようになります。
yyin = fdopen(connection, "r"); yyout = fdopen(connection, "w");
ここでconnection
は、確立されたソケットコネクションの
ファイルディスクリプタです。
ここでは、Flexの使用に関連するすべての情報を要約します。 本章は、クイックリファレンスとして使うことができます。
Flexには、以下のコマンドラインオプションがあります。
lex.backtrack
というファイルを生成します。
なぜこの情報が重要なのか、また、この情報をどのように使うかという点に関する
詳細については、Optimizing for SpeedとRemoving Backtrackingを参照
してください。
-C
オプションに移されました。
このフラグを見つけると、Flexはユーザーがテーブル圧縮を希望しているものと想定し、
警告メッセージを出力します。
将来、この警告メッセージは出力されないようになるかもしれません。
24
yyout
に書き込むスキャナを生成します。
あるルールがマッチするたびに、バックトラッキングに関する情報、検出された
バッファの終端、NUL
に関する情報に加えて、以下のような情報が
書き込まれます。
--accepting rule at line 行番号 ("マッチしたテキスト")
この中の行番号は(-L
オプションが使われていない場合には)、
生成されたファイルlex.yy.c
ではなく、スキャナを生成するのに使われた
記述ファイルの行番号を指します。
-Cf
と同等です(詳細については、
See Table Compression and Scanner Speed)。
yytext
には大文字/小文字が混在した文字の並びが入ることになります。
stderr
に書き込みます。
報告される情報は、性能を低下させるようなスキャナ記述情報の機能に
関するコメントによって構成されます。
stdout
に書き込むことです。
-s
オプションはこのアクションを抑制し、その代わりに、
入力がマッチしないとすぐにスキャナを異常終了させます。
lex.yy.c
にではなく、stdout
に書き込みます。
stdout
に出力します。
要約情報の第1行にはFlexのバージョン番号、次の行には日付と時刻、
さらに次の行には実際に使われているオプションが示されます。
要約情報のこれ以外の部分は、Flexやその他の同様のプログラムの動作の詳細
を理解している人以外にはほとんど意味を持ちません。
-CF
と同等です。
詳細については、See スキャナの最適化。
注意:-I
オプションは、-Cf
、-f
、-CF
、
-F
の各オプションと一緒に使うことはできません。
#line
指示子を生成します。
-L
オプションは、この#line
指示子を生成する機能を抑制します。
stderr
に)書き込みます。
この情報は、Flexの内部的な動作を理解していない人には、
ほとんど意味を持たないでしょう。
Flex 2.5では、前節で説明されていない、以下のオプションもサポートされています。
-f
、-F
、-Cf
、-CF
、
-+
オプションと同時に指定することはできません。
-I
オプションの否定です。
-8
オプションの否定です。
-h
オプションと同じです)。
long int
の配列として定義します
(デフォルトではshort int
型の配列となります)。
read()
システムコールを使います。
デフォルトでは、対話型スキャナの場合はgetc()
が、
バッチ(非対話型)スキャナの場合はfread()
が使われます。
lex.yy.c
に出力されます。
yy
が付けられます。
このオプションが指定されると、yy
の代わりに、
prefixにより指定される文字列が接頭辞として使用されます。
また、-o
オプションが指定されない場合のスキャナファイル名
lex.yy.c
も、lex.prefix.c
となります。
-h
オプションと同じです)。
-V
オプションと同じです)。
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文字を除くすべての文字を戻します。
戻された文字の並びは、次のトークンをマッチするのに使われます。
yyleng
とyytext
の内容には、この変更が反映されます。
input()
unput(c)
yyterminate()
yylex()
)の実行を終了させます。
終了したスキャナは0を返します。
この後yyrestart()
が呼び出されない間は、
yylex()
を呼び出してもすぐに復帰してしまいます。
yyrestart(file)
yyin
)を表す引数を1つ取ります。
EOFを処理するのに使うことができますし、また、Flexに割り込みをかけ、
その後で再開することを可能にするために使うこともできます
(Flexが再入可能ではないので、このようなことが必要になります)。
ECHO
yytext
の内容をyyout
にコピーするマクロです。
REJECT
BEGIN(state)
BEGIN
の後ろの名前は、スタート状態の名前です。
これは、スキャナ記述の先頭の定義セクションにおいて宣言されているもの
でなければなりません。
YY_USER_INIT
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()
yy_delete_buffer()
yy_switch_to_buffer()
YY_BUFFER_STATE
YYSTYPE
%union
の型です。
これは、FlexとBisonの間のインターフェイスで使われます。
yylval
Flex 2.5では、前節で説明されていない、以下の関数やマクロもサポートされています。
yy_set_interactive()
yy_set_bol()
^
を含むルールの適用が試みられます。
逆に、引数にゼロを渡すと、バッファ内の現在位置は行の先頭ではないこと
になり、次にトークンのマッチ処理が行われるときには、
行頭を表す^
を含むルールの適用が試みられなくなります。
YY_AT_BOL()
^
を含むルール
の適用が試みられるようなコンテキスト情報がセットされている場合には、
ゼロ以外の値を返します。
それ以外の場合は、ゼロを返します。
yy_new_buffer()
yy_create_buffer
の別名です。
yy_flush_buffer()
YY_END_OF_BUFFER_CHAR
(\0
)をセットします。
YY_FLUSH_BUFFER
yy_flush_buffer()
を呼び出すよう
定義されたマクロです。
yy_scan_string()
yy_scan_bytes()
yy_scan_buffer()
yy_push_state()
yy_pop_state()
yy_top_state()
yyFlexLexer::yylex()
yyFlexLexer::LexerInput()
yyFlexLexer
のサブクラスにおいて再定義することによって、
C++スキャナの入力処理を変更できます。
yyFlexLexer::LexerOutput()
yyFlexLexer
のサブクラスにおいて再定義することによって、
C++スキャナの出力処理を変更できます。
yyFlexLexer::LexerError()
yyFlexLexer
のサブクラスにおいて再定義することによって、
C++スキャナのエラーメッセージ出力処理を変更できます。
Flexにおける基本的な構成要素の1つに文字があります。
基本的にFlexは、演算子、特殊文字、エスケープシーケンスを除いて、
文字をそのまま受け付けます。
エスケープシーケンスは、ANSI Cに見られるものと同一です。
Flexの演算子と特殊文字は以下のとおりです。
文字
\
[ ]
^
^
は否定を意味します。
詳細については、See Flexにおける文字のグループ化。
一方、文字クラスの外部では、行の先頭を意味し、(エスケープされていない場合は)
ルールの先頭にのみ置くことができます。
-
{ }
( )
""
/
/
が一種の「ルックアヘッド(先読み)」演算子
として機能することを意味します。
< >
EOF
シンボル(<<EOF>>
)で使われます。
これに関する完全な説明については、
Start StatesとEnd-Of-File Rulesを参照してください。
? + *
?
、+
、*
の各文字は、ある正規表現が
何回出現できるかを指定するのに使われます。
?
は、ゼロ回もしくは1回(つまり、オプションであるということ)を、
+
は1回以上を、*
はゼロ回以上をそれぞれ意味します。
|
$
ここにあげた文字を、その文字自身として表したい場合には、
その文字を引用符で囲む(たとえば"*"
)か、または、
エスケープシーケンスとして表す必要があります。
詳細については、See Characters。
Flexにおけるルールには2つの部分があります。 パターンマッチング用の表現式とアクション部です。 この2つは、次のように配置されます。
pattern actions
Flexがマッチするパターンは、正規表現を使って作られます。
そしてその正規表現は、文字、文字列、定義、スタート状態、および演算子
から作られます。
次の表は、種々の正当な正規表現を示します。
この中で、c
は(エスケープシーケンスを含む)任意の単一文字を、
r
は任意の正規表現を、s
は文字列を、それぞれ表します。
また、グループ別に構成され、優先度のもっとも高いものがいちばん上にあります。
これは、sed
、grep
、Emacsや正規表現を使う他の一般的な
プログラムにおいて使われる正規表現と完全に同じではないことに注意してください。
ルールのアクション部は、任意の正当なCコードです。
単一行に複数の文を書くこともできますし、
かっこの対{...}
で囲むことで、
複数の文のブロックを複数行にわたって書くことも可能です。
#line
指示子、制御: Switches Summary
#line
指示子とLex: Flex and POSIX
%array
: %option (Flex 2.5)
%option
: %option (Flex 2.5)
%pointer
: %option (Flex 2.5)
%s
、詳細
: Start States Explained
%x
、EOFを使用したサンプル
: Handling Comments
%x
、より長いテキストにマッチするサンプル
: Handling Comments
%x
、詳細
: Start States Explained
%x
、複数行文字列を使用したサンプル
: Handling Strings
-+
: Switches Summary (Flex 2.5)
--help
: Switches Summary (Flex 2.5)
--version
: Switches Summary (Flex 2.5)
-7
: Switches Summary (Flex 2.5)
-8
: Switches Summary
-?
: Switches Summary (Flex 2.5)
-B
: Switches Summary (Flex 2.5)
-b
: Switches Summary
-c
: Switches Summary
-C[efmF]
: Switches Summary
-Ca
: Switches Summary (Flex 2.5)
-Cem
、詳細
: Table Compression and Scanner Speed
-Ce
、詳細
: Table Compression and Scanner Speed
-CF
、詳細
: Table Compression and Scanner Speed
-Cf
、詳細
: Table Compression and Scanner Speed
-Cm
、詳細
: Table Compression and Scanner Speed
-Cr
: Switches Summary (Flex 2.5)
-d
: Switches Summary
-F
: Switches Summary
-f
: Switches Summary
-F
、詳細
: Table Compression and Scanner Speed
-f
、詳細
: Table Compression and Scanner Speed
-h
: Switches Summary (Flex 2.5)
-I
: Switches Summary
-i
: Switches Summary
-I
、詳細
: Interactive Scanners
-i
、詳細
: The -i Switch
-l
: Switches Summary (Flex 2.5)
-L
: Switches Summary
-n
: Switches Summary
-o
: Switches Summary (Flex 2.5)
-P
: Switches Summary (Flex 2.5)
-p
: Switches Summary
-S
: Switches Summary
-s
: Switches Summary
-T
: Switches Summary
-t
: Switches Summary
-V
: Switches Summary (Flex 2.5)
-v
: Switches Summary
-w
: Switches Summary (Flex 2.5)
.l
、lexファイル
: Invoking Flex
7bit
、%option
: %option (Flex 2.5)
8bit
、%option
: %option (Flex 2.5)
<*>
: Start State Notes (Flex 2.5)
<<EOF>>
、詳細
: End-Of-File Rules
<<EOF>>
とLex
: Flex and POSIX
align
、%option
: %option (Flex 2.5)
always-interactive
、%option
: %option (Flex 2.5)
array
、%option
: %option (Flex 2.5)
backup
、%option
: %option (Flex 2.5)
batch
、%option
: %option (Flex 2.5)
BEGIN
、スタート状態の活性化
: Activating States
BEGIN
、詳細
: Flex and C
BEGIN
、要約
: Summary or Flex Variables and Functions
c++
、%option
: %option (Flex 2.5)
case-insensitive
、%option
: %option (Flex 2.5)
case-sensitive
、%option
: %option (Flex 2.5)
caseful
、%option
: %option (Flex 2.5)
caseless
、%option
: %option (Flex 2.5)
debug()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
debug
、%option
: %option (Flex 2.5)
default
、%option
: %option (Flex 2.5)
ECHO
: Summary or Flex Variables and Functions
ECHO
、サンプル
: Table Compression and Scanner Speed
ECHO
とoutput()
: Flex and POSIX
ecs
、%option
: %option (Flex 2.5)
fast
、%option
: %option (Flex 2.5)
FlexLexer
: Flex and C plus plus (Flex 2.5)
front.lex
: The Implementation
front.y
: The Implementation
full
、%option
: %option (Flex 2.5)
input()
、LexとFlexの相違点
: Flex and POSIX
input()
、サンプル
: Handling Comments
input()
、詳細
: Flex and C
input()
、要約
: Summary or Flex Variables and Functions
interactive
、%option
: %option (Flex 2.5)
lex-compat
、%option
: %option (Flex 2.5)
lex.yy.c
: Invoking Flex
lex.yy.cc
: Flex and C plus plus (Flex 2.5)
LexerError()
、yyFlexLexer
: Flex and C plus plus (Flex 2.5)
LexerInput()
、yyFlexLexer
: Flex and C plus plus (Flex 2.5)
LexerOutput()
、yyFlexLexer
: Flex and C plus plus (Flex 2.5)
lineno()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
main
、%option
: %option (Flex 2.5)
meta-ecs
、%option
: %option (Flex 2.5)
never-interactive
、%option
: %option (Flex 2.5)
output()
: Flex and POSIX
output
、%option
: %option (Flex 2.5)
perf-report
、%option
: %option (Flex 2.5)
pointer
、%option
: %option (Flex 2.5)
prefix
、%option
: %option (Flex 2.5)
read
、%option
: %option (Flex 2.5)
reject
、%option
: %option (Flex 2.5)
REJECT
、詳細
: Flex and C
set_debug()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
stack
、%option
: %option (Flex 2.5)
stdinit
、%option
: %option (Flex 2.5)
stdout
、%option
: %option (Flex 2.5)
switch_streams()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
unput()
、詳細
: Flex and C
unput()
、要約
: Summary or Flex Variables and Functions
unput
、%option
: %option (Flex 2.5)
verbose
、%option
: %option (Flex 2.5)
warn
、%option
: %option (Flex 2.5)
Yacc
: Flex and Bison
YY_AT_BOL()
、詳細
: Flex and C (Flex 2.5)
YY_AT_BOL()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
YY_BREAK
、詳細
: Flex and C
YY_BREAK
、要約
: Summary or Flex Variables and Functions
YY_BUF_SIZE
: Buffer Manipulation
YY_BUFFER_STATE
: Summary or Flex Variables and Functions
yy_create_buffer()
: Buffer Manipulation
yy_create_buffer()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
YY_CURRENT_BUFFER
: Buffer Manipulation
YY_DECL
、詳細
: Flex and C
YY_DECL
、要約
: Summary or Flex Variables and Functions
yy_delete_buffer()
: Buffer Manipulation
yy_delete_buffer()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yy_flex_debug
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yy_flush_buffer()
、詳細
: Buffer Manipulation Functions (Flex 2.5)
yy_flush_buffer()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
YY_FLUSH_BUFFER
、詳細
: Buffer Manipulation Functions (Flex 2.5)
YY_FLUSH_BUFFER
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
YY_INPUT
、再定義: Flex and C
YY_INPUT
、要約
: Summary or Flex Variables and Functions
YY_INPUT
の再定義: Flex and C
yy_new_buffer()
、詳細
: Buffer Manipulation Functions (Flex 2.5)
yy_new_buffer()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
YY_NEW_FILE
、詳細
: Flex and C
YY_NEW_FILE
、要約
: Summary or Flex Variables and Functions
yy_pop_state()
、詳細
: Start State Notes (Flex 2.5)
yy_pop_state()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_pop_state
、%option
: %option (Flex 2.5)
yy_push_state()
、詳細
: Start State Notes (Flex 2.5)
yy_push_state()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_push_state
、%option
: %option (Flex 2.5)
yy_scan_buffer()
、詳細
: Buffer Manipulation Functions (Flex 2.5)
yy_scan_buffer()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_scan_buffer
、%option
: %option (Flex 2.5)
yy_scan_bytes()
、詳細
: Buffer Manipulation Functions (Flex 2.5)
yy_scan_bytes()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_scan_bytes
、%option
: %option (Flex 2.5)
yy_scan_string()
、詳細
: Buffer Manipulation Functions (Flex 2.5)
yy_scan_string()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_scan_string
、%option
: %option (Flex 2.5)
yy_set_bol()
、詳細
: Flex and C (Flex 2.5)
yy_set_bol()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_set_interactive()
、詳細
: Flex and C (Flex 2.5)
yy_set_interactive()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
YY_START
: Start State Notes (Flex 2.5)
yy_switch_to_buffer()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yy_switch_to_buffer()
、詳細
: Buffer Manipulation
yy_switch_to_buffer()
、要約
: Summary or Flex Variables and Functions
yy_top_state()
、詳細
: Start State Notes (Flex 2.5)
yy_top_state()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yy_top_state
、%option
: %option (Flex 2.5)
YY_USER_ACTION
、詳細
: Flex and C
YY_USER_ACTION
、要約
: Summary or Flex Variables and Functions
YY_USER_INIT
、詳細
: Flex and C
YY_USER_INIT
、要約
: Summary or Flex Variables and Functions
YYBREAK
とともに使用されるbreak
: Flex and C
yyclass
、%option
: %option (Flex 2.5)
yyFlexLexer
: Flex and C plus plus (Flex 2.5)
yyFlexLexer::LexerError()
、詳細
: Flex and C plus plus (Flex 2.5)
yyFlexLexer::LexerError()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yyFlexLexer::LexerInput()
、詳細
: Flex and C plus plus (Flex 2.5)
yyFlexLexer::LexerInput()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yyFlexLexer::LexerOutput()
、詳細
: Flex and C plus plus (Flex 2.5)
yyFlexLexer::LexerOutput()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yyFlexLexer::yylex()
、詳細
: Flex and C plus plus (Flex 2.5)
yyFlexLexer::yylex()
、要約
: Summary or Flex Variables and Functions (Flex 2.5)
yyin
: Flex and C
yyin
、リセットの例
: Example-Counting Words
yyin
、要約
: Summary or Flex Variables and Functions
yyin
とソケット
: Miscellaneous
yyleng
: Flex and C
YYLeng()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yyleng
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yyleng
、unput()
: Flex and POSIX
yyleng
、要約
: Summary or Flex Variables and Functions
yyless()
、詳細
: Flex and C
yyless()
、要約
: Summary or Flex Variables and Functions
yylex()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yylex()
、再定義: Flex and C
yylex()
、詳細
: Flex and C
yylex()
、要約
: Summary or Flex Variables and Functions
yylex()
とyyterminate()
: Flex and C
yylex()
の再定義: Flex and C
yylineno
: Flex and POSIX
yylineno
、%option
: %option (Flex 2.5)
yylineno
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yylval
、説明
: YYSTYPE and yylval
yylval
、要約
: Summary or Flex Variables and Functions
yymore()
、詳細
: Flex and C
yymore()
、要約
: Summary or Flex Variables and Functions
yymore
、%option
: %option (Flex 2.5)
yyout
: Flex and C
yyout
、要約
: Summary or Flex Variables and Functions
yyout
とソケット
: Miscellaneous
yyrestart()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yyrestart()
、詳細
: Flex and C
yyrestart()
、要約
: Summary or Flex Variables and Functions
yyrestart()
とLex
: Flex and POSIX
YYSTYPE
: Notes on the Implementation
YYSTYPE
、Bisonとの関連
: YYSTYPE and yylval
YYSTYPE
、Bisonファイルにおける%union
型
: Summary or Flex Variables and Functions
yyterminate()
、詳細
: Flex and C
yyterminate()
、要約
: Summary or Flex Variables and Functions
yyterminate()
とLex
: Flex and POSIX
YYText()
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yytext
、FlexLexer
: Flex and C plus plus (Flex 2.5)
yytext
、unput()
: Flex and POSIX
yytext
、詳細
: Flex and C
yytext
、表示
: Flex and C
yytext
、要約
: Summary or Flex Variables and Functions
yytext
の長さへのアクセス: Flex and C
yytext
非互換性
: Flex and POSIX
yywrap()
、詳細
: Flex and C
yywrap()
、要約
: Summary or Flex Variables and Functions
yywrap()
とEOF
: Flex and C
yywrap()
とPOSIX
: Flex and POSIX
yywrap
、%option
: %option (Flex 2.5)
yyrestart()
: Flex and C
-t
: Switches Summary
ECHO
: Flex and C
スタート状態スコープ
: Start State Notes (Flex 2.5)
スタート状態スタック
: Start State Notes (Flex 2.5)
【訳注】Flex 2.5では、
-l
オプションを指定して生成されたスキャナは、
Lexの場合と同じように、定義を展開するときに丸かっこ( )で囲みません。
【訳注】Flex 2.5では、
-l
オプションを指定して生成されたスキャナは、Lexの場合と同じように、
定義を展開するときに丸かっこ( )で囲みません。
【訳注】Flex 2.5では、-l
オプションを指定して生成されたスキャナ
は、Lexの場合と同じように、定義を展開するときに丸かっこ( )で囲みません。
【訳注】Flex 2.5は、スタート状態スタックをサポートしています。 次節(Start State Notes (Flex 2.5))を参照してください。
Flex 2.5.4に付属のドキュメントflex.texi
には、
関数input()
についても同様のことが記載されていますが、
実際に%option noinput
を指定してみると、
生成されるスキャナの中に、関数input()
が組み込まれます。
【訳注】この章の最後で、C++の使い方についても説明します。
【訳注】Flex 2.5では、%pointer
と%array
により、
yytext
の型を選択できるようになりました。
%pointer
を指定した場合はchar *yytext
、
%array
を指定した場合はchar yytext[YYLMAX]
となります。
デフォルトは%pointer
です。
%array
を指定した場合の配列のサイズは、
YYLMAX
を再定義することによって変更可能です。
【訳注】Flex 2.5では、%option noyywrap
が指定されないかぎり、
yywrap()
は関数です。
再定義をするのに、#undef
で定義解除する必要はありません。
【訳注】Flex 2.5では、%array
が指定された場合は、
unput()
はyytext
の内容を破壊しません。
【訳注】Flex 2.5では、yyin
を変更した後に
YY_NEW_FILE
を実行する必要はなくなりました。
【訳注】 日本語訳:『Bison入門』、アスキー出版局、ISBN 4-7561-3065-8
Flexの仕様では、
Flexの起動時に-d
オプションを指定するか、スキャナ定義ファイルの中に
%option debug
を指定すると、スキャナ実行時にデバッグ情報が出力されること
になっていますが、Flex 2.5.4でC++スキャナを生成した場合は、いずれの方法でも
デバッグ情報は出力されません。
デバッグ情報を出力するためには、C++プログラムから明示的にこのset_debug()
メンバ関数を(引数にゼロ以外の値を指定して)呼び出す必要があります。
【訳注】Flex 2.5では、
ここに列挙されているもの以外に、-Ca
オプションをサポートしています。
これについては、Command Line Switches (Flex 2.5)を参照してください。
【訳注】some text の部分に、インデントされたテキストが記されます。
【訳注】Flex 2.5では、
-l
オプションを指定して生成されたスキャナは、
Lexの場合と同じように、定義を展開するときに丸かっこ( )で囲みません。
【訳注】Rational Fortranの略。
【訳注】Flex 2.5では、Flex起動時に-l
オプションを指定するか、
スキャナ定義ファイルの中に%option yylineno
を指定することによって、
変数yylineno
を利用できます。
【訳注】Flex 2.5では、
%option noyywrap
が指定されないかぎり、yywrap()
は関数です。
【訳注】Flex 2.5では、%array
を指定すれば、
unput()
はyytext
の内容を破壊しません。
【訳注】Flex 2.5では、
%pointer
と%array
により、yytext
の型を選択できるように
なりました。
デフォルトは%pointer
です。
【訳注】Flex 2.5は、
%pointer
と%array
をサポートしています。
【訳注】Flex 2.5では、
Flex起動時に-Pprefix
オプションを指定するか、スキャナ定義ファイル
の中に%option prefix="prefix"
を指定することによって、
接頭辞yy
を別の文字列に変更できます。
【訳注】Flex 2.5では、-Pprefix
オプションや
%option prefix="prefix"
を指定することにより、
関数名を変更できます。
【訳注】Flex 2.5では、この警告メッセージは出力されません。
【訳注】Flex 2.5では、yyin
を変更した後に
YY_NEW_FILE
を実行する必要はなくなりました。