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


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のサブセット用の字句スキャナ

ここでは、 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種類を検索して、 コマンドを出力します。
実例、および列挙されたリスト
ともにコロンで始まり、 その後ろに、 1つ以上の改行、 少なくとも5つの空白、 そして最後に数字もしくは何らかのテキストが続きます。 例えば、 列挙されたリストは以下のようになります。
...enumerated:

      0.some text
      1.some more text

また、 実例は以下のようになります。(14)
...example:

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

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


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