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


Flexとのインターフェイス

この章ではCおよびBisonと一緒にFlexを使う方法を説明します。(7)C、Bisonのそれぞれが非常に多くの細目を含むため、 本章は2つの部分に分割されています。 その両方に、 全般的なインターフェイス概念に関する節と実例を示す節があります。

FlexとC

Flexに対するCの主要なインターフェイスは、 以下に挙げるルーチンと変数によるものです。 以下の節を読む際には、 いくつかの細かな部分でFlexとLexとの間に相違点があるということを意識しておいてください。 Lexが提供していない関数がいくつかありますし、 宣言の内容が違うものもあります。 こうした相違点は、 通常大きな問題にはなりません。 というのは、 相違のある関数は一般的にはあまり使われていないからです。 相違点に関する詳細については、 Flex と Lexおよび FlexとPOSIXを参照してください。

FlexとC(Flex 2.5の補足情報)

Flex 2.5では、 前節(FlexとC)で説明されていない、 以下のマクロもサポートされています。

FlexとCの簡単な実例

ある単語が現れた時に、 それを別の単語に置き換える必要の生じることがよくあります。 例えば、 ある名前が現れるたびに、 それをある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'という名前の実行ファイルが生成されるはずです。 これは、 以下のような変換処理を実行するフィルタです。

したがって、 以下のような内容を持つファイル`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.  

このプログラムがうまく動作するのは、 yyinyyoutがデフォルトではstdinstdoutにそれぞれ割り当てられ、 かつ、 デフォルトのアクションが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'です。

FlexとBison

Bisonは、 Flexと同様、 ある記述情報を受け取って、 それをもとにCのコードを生成するプログラムです。 両者の違いは、 BisonがCやPascalのような言語の文法に関する記述情報を入力として受け取り、 その記述情報からパーサを生成する点にあります。 FlexとBisonを結合することにより、 言語の字句解析と構文解析の両方を処理することができるようになります。 (これは、 コンパイラ・デザインにおいて最も容易に自動化できる部分です。)

生成されるパーサが機能するためには、 Bisonはyylex()という関数を必要とします。 この関数はユーザによって提供され、 呼び出された時に、 パースされている言語のある要素を表す整数値をBisonに返します。 Flexにおいてスキャン処理を行うルーチンはyylex()であり、 デフォルトでは整数値を返します。 これにより、 FlexとBisonを一緒に使うのは非常に簡単になります。

警告: 以下の節では、 読者がBisonの基本的なパーサの宣言を理解しているものと仮定します。 Bisonを使った経験のない人には、 パーサの定義は混乱をもたらす可能性がありますので、 先に進む前に是非Bisonのマニュアルを読んでください。 Bisonに興味の無い人は、 この節全体を飛ばしても構いません。

Flexと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のマニュアルを参照してください。

YYSTYPEとyylval

FlexからBisonに対して、 単なる整数値以上の情報を渡す必要の生じることがよくあります。 例えば、 コンパイラにおいては、 どのような種類のトークンが認識されたかだけではなく、 そのトークンの値についても知る必要のある場合がときどきあります。 文字列、文字、および数値定数などが良い例です。 ここで問題なのは、 どのようにしてFlexにこうした情報を返させるかです。

その答は、 Bisonが持っている%union文です。 これは、 YYSTYPEという型を定義するものです。 YYSTYPEは、 パーサ定義中において使われるすべての正当なデータ型の共用体(union)です。 Bisonがカレントなパース状態に関連づけたデータを保存するために使う、 YYSTYPE型の変数yylvalというものがあり、 Flexからyylvalに値を設定することができるので、 トークンの型だけでなく、 それ以上の情報をBisonに返すことができます。

Bisonにおいて%unionを宣言して`-d'オプションを使うと、 Bisonは`.tab.h'という拡張子を持つファイルを作成して、 そこにトークンの定義情報だけでなく、 YYSTYPEyylvalの宣言も含めます。 したがって、 yylvalにアクセスするためにしなければならないことは、 Flexの定義情報の中にこの`.tab.h'ファイルをインクルードすることだけです。 これは、 追加のCコード・セクションにおける定義の先頭でインクルードしなければなりません (FlexとBisonのインターフェイスを参照)。

注:初期のバージョンのBisonは、 自動的にYYSTYPEyylvalの宣言を生成しません。 この場合には、 より新しいバージョンのBisonを入手するか、 もしくは、 Flexの定義ファイルの先頭においてYYSTYPEyylvalを宣言する必要があります。

FlexとBisonのもう1つの実例

コードを読むのは、 プログラミングの方法を学ぶ良い方法です。 そこで、 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を使うのであれば、 このファイルは必要ありません。

実装に関する注

以下に実装に関する注を示します。

FlexとC++(Flex 2.5の補足情報)

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++クラス (FlexLexeryyFlexLexer) が定義されています。


FlexLexerは、 C++スキャナ・クラスが実装すべきインターフェイスを構成する抽象仮想関数を定義するクラスです。

FlexLexerの持つメンバを以下に示します。

次に、 FlexLexerの持つメンバ関数のうち、 抽象仮想関数ではないものを以下に示します。

次に、 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()が生成されます。 クラスclassnameyyFlexLexerのサブクラスとして定義することによって、 classnameのインスタンスを使ってスキャン処理を実行することができます。 クラスclassnameを定義する際、 以下に示す、 yyFlexLexerの持つprotectedメンバ関数を再定義することによって、 スキャナの振る舞いを変更することができます。


スキャン処理に関わるすべてのコンテキスト情報は、 yyFlexLexerのインスタンスの内部に閉じています。 このことは、 C++スキャナ・クラスを使うことによって、 再入可能なスキャナを生成することが可能であることを意味しています。

複数のC++スキャナ・クラスを生成して、 1つの実行プログラムにリンクすることも可能です。 これを行うには、 Flex起動時に`-Pprefix'オプションを指定するか、 スキャナ定義ファイルの中に`%option prefix="prefix"'を指定することによって、 yyFlexLexerの名前を`prefixFlexLexer'に変更します。 prefixFlexLexerクラスを使うソース・ファイルの中では、 以下のようにして`FlexLexer.h'をインクルードすることによって、 prefixFlexLexer (実際にはyyFlexLexer) の定義を参照する必要があります。

#undef yyFlexLexer
#define yyFlexLexer prefixFlexLexer
#include <FlexLexer.h>


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