Flex と Bison を同時に使う


Flex と Bison を併用するとき、Flex から生成される関数 (yylex) は、トークン (終端記号) の情報を戻り値とし、それを Bison が利用します。 トークンが 1 文字の場合は、通常、戻り値は文字コードそのものです。トーク ンが 2 文字以上の場合は、Bison で %token により宣言されたマクロ を使用します。

ここでは 2文字以上の演算子の例として、*+ という演算子を x *+ yx * 256 + y を表し、+, -と同じ優先順位を持つ、 左結合の演算子として導入します。仮に FOO 演算子と呼ぶことにします.

Flex のソースファイル(mylexer.l)には次のような #include 文を入れておきます。インクルードされるファイル (myparser.h) は Bison が生成するファイルで、%token に より宣言されたマクロの定義が書かれています。


%{
void yyerror(char*);

#define YY_SKIP_YYWRAP
int yywrap(void) { return 1; }
#include "myparser.h" /* myparser.h は bison が生成するファイル */
%}
%option always-interactive
%%
[ \t]+                            { /* ここでは何もしない */ }
[0-9]+(\.[0-9]+)?(E[+\-]?[0-9]+)? { 
  /* NUMBER というトークンを返す。その値(“属性”)は
  yylval という大域変数に代入する。 */
                                    sscanf(yytext, "%lf", &yylval); return NUMBER;
                                  }
[+\-*/()\n]                      { 
  /* + - * / ( )の場合は、マッチした文字をそのまま返す。*/
  /* マッチした文字は一般に yytext[0] 〜 yytext[yyleng-1]。*/
                                    return yytext[0]; 
                                  }   
"*+"                              { 
  /* 複数のルールにマッチする場合は,長い方が優先される */
                                    return FOO; 
                                  }
.                                 { yyerror("不正な文字です。"); return '\n'; } 
%%
/* なにもなし */

動作の中に return 文を入れておくと、 その式の値が Flex の生成する yylex 関数の戻り値になります。 yylex 関数は呼び出されるたびに、次のトークンを返します。

トークンは、1文字からなるトークンの場合は通常、 文字コードそのまま、2文字以上からなるトークンの場合は %token で宣言されたマクロです。

トークンの “種類”(NUMBER など)を yylex 関数の値として返し、値(“属性”)を yylval という大域変数に代入していることに注意します。 これが通常の yylex 関数の書き方です。

一般に正規表現にマッチした文字は、 yytext という配列に保持されています。また yyleng という変数にマッチした文字の数が保持されています。 だからマッチした文字は一般に yytext[0]yytext[yyleng-1] ということになります。(通常の C 言語の文字列とは異なり、 最後 (yytext[yyleng]) にヌル文字 '\0' は入っていないので注意が必要です。)

例えば、入力が “(12*+34)*56\n” という文字列の場合、 yylex() の戻り値とそのときの yylval の値は次のようになります。

1回目2回目3回目4回目5回目6回目7回目8回目
yylex()'('NUMBERFOO NUMBER')''*'NUMBER'\n'
yylval 12.0  34.0  56.0 

Bison のソースファイル (myparser.y)の方は、 単独で使う場合とあまり変わりませんが、 yylex 関数は Flex の方で用意するのでプロトタイプ宣言だけしておきます。

生成規則の中で、トークン(終端記号)として文字リテラル ('+', '-' など) と %token で宣言したマクロを用いることができます。


%{
#define YYSTYPE double  /* トークンの属性の型を宣言 */
#include <stdio.h>
#include <stdlib.h> /* exit 関数を使うため */

void yyerror(char* s) {
   printf("%s\n", s);
}

int yylex(void); /* yylex のプロトタイプ宣言 */
%}

%token NUMBER 
%token FOO 
  /* 演算子の優先順位 */
%left '+' '-' FOO
%left '*' '/' 
%%
input :   /* 空 */
      | input line    {}
      ;
line  :  '\n'          { exit (0); } /* 空行だったら終了 */
      |  expr '\n'     { printf ("\t%g\n", $1); }
      ;
expr  :  NUMBER        { $$ = $1; }
      |  expr FOO expr { $$ = $1 * 256 + $3; }
      |  expr '+' expr { $$ = $1 + $3; }
      |  expr '-' expr { $$ = $1 - $3; }
      |  expr '*' expr { $$ = $1 * $3; }
      |  expr '/' expr { $$ = $1 / $3; }
      |  '(' expr ')'    $$ = $2; }
      ;
%%
int main(void) {
   printf("Ctrl-cで終了します。\n");
   yyparse();
   return 0;
}

C ソースファイルはそれぞれ次のコマンドで生成します。

  bison -omyparser.c -d myparser.y
  flex -omylexer.c -I mylexer.l

必ず -d オプションをつけて Bison を実行します。 このとき -o オプションで、 C ファイル名 (この場合 myparser.c) を指定しておきます。 すると、拡張子を除いて同じ名前のヘッダーファイル (この場合 myparser.h) も生成されます。 (-o オプションをつけないと、 myparser.tab.cmyparser.tab.h という名前のファイルが生成されます。)

あとはこの 2 つの C ソースファイルをまとめてコンパイルします。

Borland C Compiler の場合は、

  bcc32 -ecalc mylexer.c myparser.c

-e は実行ファイルの名前を指定するオプションです。

Microsoft Visual Studio の場合は、

  cl /Fecalc mylexer.c myparser.c

/Fe は実行ファイルの名前を指定するオプションです。

これで calc.exe という名前の実行可能ファイルが生成されます。 次のコマンドで実行できます.

  calc

コンパイラのホームページ
Koji Kagawa