前: Start State Notes (Flex 2.5), 上: Start States


3.8.5 スタート状態の使用例

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

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