[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


役に立つコードの抜粋

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

コメントの処理

FlexとCにおいて述べたように、 コメントはinput()を使って処理することができます。 これを行うためのコードは以下のようになります。

%%
"/*" {
        int a,b;
        
        a = input();
        while(a != EOF){
          b = input();
          if(a == '*' && b == '/'){
            break;
           }else{
            a = b;
           }
         }
        if(a == EOF){
          error_message("EOF in comment");
        }
      }

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

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

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

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

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

文字列リテラルの処理

文字列は、 それが入力として与えられた時に破棄されないという点で、 コメントとは若干異なります。 しかし、 基本的なアプローチは同じです。 第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とCyymoreの項を参照してください。

数字の処理

ここでは、 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)

その他


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]