[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

3. Flex記述言語

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

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

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

各々について、 以下において詳細に説明します。

========================================================================

========================================================================


3.1 コメント

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

 
/*
…
*/

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

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


3.2 オプションのCコード

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

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

 
%{ 
   C code
   …
%}

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

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

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


3.3 定義

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

 
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は囲まないという点があります。 これは、 `^'、`<<EOF>>'、`$'、`/'を定義中に入れることができないことを意味しています。 というのは、 前述の文字は丸括弧( )で囲まれた部分に入れることができないからです。 詳細は、 文字およびFlexと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とPOSIXを参照してください。


3.4 %%

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


3.5 ルール

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

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

 
pattern       actions

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

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

  1. 後続コンテキスト(trailing context)も含めて最も長いものを受け入れる。

  2. マッチするものがすべて同じ長さの場合、 スキャナ定義中に最初に記述されたものを受け入れる。

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

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

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

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

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

 
%%

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

 
%%
foobar

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


3.6 パターン・セクション

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

========================================================================

========================================================================


3.6.1 文字

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


文字

Flexによる解釈

.

ピリオドは改行(`\n')以外の任意の文字を表します。

\

バックスラッシュはエスケープ文字です。 エスケープ・シーケンスはANSI Cのものと同一です。

[ ]

角括弧[ ]は複数の文字を文字クラスにグループ化します。 詳細については、 See section Flexにおける文字のグループ化

^

文字クラスの内部では^は否定を意味します。 詳細については、 See section Flexにおける文字のグループ化。 文字クラスの外部では、 ^は行の先頭を意味し、 ルールの先頭にのみ置くことができます。 例を以下に示します。

[^AB]

否定クラスです。

^foo

行の先頭にある`foo'という文字の並びにのみマッチします。

foo^

この場合、 `^'は普通の文字であるとみなされます。 このような時には、 希望どおりの結果が確実に得られるようにするために、 特別な意味を持つ文字の前にバックスラッシュ`\'を置くのが良いでしょう。 このような文字の並びをエスケープ・シーケンスと呼びます。 エスケープ・シーケンスについてはこの節の最後で説明します。

-

ハイフンは文字クラスの内部において文字の範囲を表します。 文字クラスの外部では、 ハイフンはそれ自身を表します。 詳細については、 See section Flexにおける文字のグループ化

{ }

大括弧{ }は、 定義の参照、複数行のアクションの囲み、またはある範囲にわたる繰り返しの定義を行います。 例を挙げると、 定義FOOがあって、 それをルールの中で参照したい場合に{FOO}を使います。

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

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

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

( )

丸括弧( )を使って優先順位を変更することができます。 また、 定義が展開される時には、 その定義は暗黙のうちに丸括弧( )で囲まれることに注意してください。 このため、 Lexとは非互換なところがでてきます。 この点については、 FlexとPOSIX定義で説明しています。

""

二重引用符記号は文字列を表します。 引用符の内側の文字列だけがマッチの対象になります。 したがって、

 
%%
"string"

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

/

スラッシュは後続コンテキスト(trailing context)を設定します。 これは、 あるパターンの後ろに特定の文字の並びが続く場合のみ、 そのパターンを認識したいという状況です。 つまり、 スラッシュ`/'は「ルック・アヘッド(その先を見る)」演算子として機能するということです。 例を挙げると、

abcDEF

`abcDEF' を認識します。

abc/DEF

`abc'の後ろに`DEF'が続く場合に限り、 `abc'を認識します。 `DEF'の部分は、 あたかもまだ読まれてはいないかのように扱われ、 マッチの対象になりません。

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

 
abc/def/hijkl

は不正です。

< >

かぎ括弧< >はスタート状態を参照します。 また、 EOFシンボル(`<<EOF>>')にも使われます。 完全な説明については、 スタート状態ファイルの終端(End-Of-File)ルールを参照してください。

? + *

`?'、`+'、`*'は、 ある正規表現が現れることのできる回数を設定します。 `?'は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は定義です。 定義に関する詳細については、 定義を参照してください。

|

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

 
   apples|oranges

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

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

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

$

ドル記号は行末を意味します。 例えば、

 
   end$

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

 
   end/\n

とまったく同じです。

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

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

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


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

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


3.6.2 Flexにおける文字列

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

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

例えば、

 
string
StrING
"STRING"
\"string\"

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

 
begin
end
pointer
 …

 
"begin"
"end"
"pointer"
 …

のいずれも正当です。


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

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

 
[a-z]
[A-Z]*

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

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

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

 
%%
[z-a9-0]

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


3.7 正規表現

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

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


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


3.8 スタート状態

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

========================================================================

========================================================================


3.8.1 スタート状態の説明

スタート状態は、 あるルールがアクティブになるのはいつであるかを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ではサポートしていません。


3.8.2 状態の活性化

スタート状態の名前を並べただけではあまり役に立ちません。 つまり、 スタート状態がいつ活性化されるのかということも制御しなければなりません。 活性化は、 アクションの中、 または、 記述情報内の追加的な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)によって、 スキャナの状態が効果的に (もちろん、 その時点においてスキャンしている箇所を維持したまま) リセットされることを意味しています。


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

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


3.8.4 スタート状態の使用例

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

 
/*
 * 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'を実行することにより、 コンパイルすることもできます。


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Akihiro Sagawa on June, 15 2005 using texi2html 1.70.