Bisonについて


Bisonは BNFとそれに対する動作記述から、 構文解析系(パーサ)を自動生成するプログラムです。 Bisonのソースファイル(通常 .yという拡張子をつける)は、 次のように書きます。 (これは通常の四則演算の式の文法です。 この例では単項の「-」はサポートしていないので、 -1+22*(-3)のような式は構文解析できません。 単項の「-」を扱う方法は yacc (bison)の入門書を調べて下さい。)
%{
  /* C宣言部 ─ 動作記述の中で用いる関数の定義や宣言 */
#define YYSTYPE double    /* トークンの属性の型を宣言 */

#include <stdio.h>
#include <stdlib.h> /* exit関数を使うため */
#include <ctype.h>  /* isdigit関数を使うため */
void yyerror(char* s) {   /* エラーがあった時に呼ばれる関数を定義しておく */
   printf("%s\n", s);
}
%}
  /* Bison宣言部 */
  /* 終端記号(と非終端記号)の宣言 */
  /* 終端記号は定数マクロとして定義される */
%token NUMBER 
%token EOL
  /* 曖昧な文法に対して演算子の優先順位と結合性を宣言できる */
%left '+' '-'
%left '*' '/'
  /* leftは左結合を表す、ちなみに右結合は right、非結合は nonassocとなる。 */
  /* 優先順位が高い演算子ほど下に書く。 */

%%  
  /* 文法記述とそれに対する動作(還元時に実行されるプログラム) */
  /* 最初に start symbolを書く。 */
input   :   /* 空 */
        | input line    {}
        ;
  /* 通常の BNFの → の代わりに : を書く。各BNFは ; で区切る。 */
line    : EOL           { exit(0); }
        | expr EOL      { printf("%g\n", $1); }
        ;
  /* $$は左辺の属性値(意味値)、$nは右辺の n番目の文法要素の属性値を表す。 */
expr    : NUMBER        { $$ = $1; }
        | expr '+' expr { $$ = $1 + $3; }
        | expr '-' expr { $$ = $1 - $3; }
        | expr '*' expr { $$ = $1 * $3; }
        | expr '/' expr { $$ = $1 / $3; }
        | '(' expr ')'  { $$ = $2; }
        ;
%%
  /* 追加の Cプログラム */
  /* yylex(字句解析)関数を flexを使わず定義している。yylexの戻り値は、トークンの種類。*/
int yylex(void) {
  int c;

  do {
     c = getchar ();
  } while (c == ' ' || c == '\t');

  if (isdigit (c) || c == '.') {
    ungetc(c, stdin);
    scanf("%lf", &yylval);
    /* トークンの属性値は yylvalという 大域変数に代入して返す。*/
    return NUMBER;
    /* NUMBERというトークンを返す。*/
  } else if (c == '\n') {
    return EOL;
  } else if (c == EOF) {
    return 0; /* 終了を表す。*/
  }
  /* 上のどの条件にも合わなければ、文字をそのまま返す。*/
  return c;  
}

int main(void) {
  printf("Ctrl-cで終了します。\n");
  yyparse(); /* Bisonが生成した関数 */
  return 0;
}

/* この例では、mainを自前で用意しているが、通常は他のファイルに
   main関数など他の関数を定義する。*/

説明

Bisonの核心部分は BNFとそれに付随するアクションです。 アクションは還元時に実行されるプログラムのことです。 アクションの中身は通常属性値(意味値)の計算です。 属性値は解析木の各節(枝分れの部分)に関連付けられる“値” です。

終端記号の属性値は、 字句解析器から yylvalという大域変数に代入されて受け渡されます。

非終端記号の属性値は、各部分木の属性値から計算されます。 $$が還元される生成式の左辺の属性値、$1, $2, …が 右辺の1番目、2番目、…の文法要素の属性値を表します。 例えば、$$ = $1 * $3というアクションでは、 1番目と3番目の部分木の属性値の積が、節の非終端記号の属性値となります。

例えば、 1 + 2 * ( 3 + 4 ) EOL EOLというトークン列から、上の Bisonプログラムは以下のような解析木を生成します。 青字で示されているのが各節の属性値です。


注1 この節の属性値はないが、属性値を計算する副作用として “15”が出力される。
注2 この節の属性値はないが、属性値を計算する副作用として、プログラムが強制終了(exit)される。
注3 この節は実際には生成されない。(その前にプログラムが終了する。)

生成

このファイル(ファイル名を myparser.yとする)から Cソースファイルを生成するには
  bison myparser.y
というコマンドを実行します。 これで myparser.tab.cという名前 (.yファイルの名前の後ろに .tabがつく)の Cソースファイルができます。 また、-oというオプションで、 Cのファイル名を指定することができます。例えば、
  bison -ofoo.c myparser.y

foo.cという名前の cソースファイルができます。

この例の場合は、この Cソースファイルを普通にコンパイルすると、 (警告(Waring)がいくつか出ますが) 実行可能ファイルができます。

プログラム言語論のホームページ
Koji Kagawa