%{ /* 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 * $3というアクションでは、 1番目と3番目の部分木の属性値の積が、非終端記号の属性値となります。
bison myparser.yというコマンドを実行します。 これで myparser.tab.cという名前 (.yファイルの名前の後ろに .tabがつく)の Cソースファイルができます。 また、-oというオプションで、 Cのファイル名を指定することができます。例えば、
bison -ofoo.c myparser.yで foo.cという名前の cソースファイルができます。
この例の場合は、この Cソースファイルを普通にコンパイルすると、 (警告(Waring)がいくつか出ますが) 実行可能ファイルができます。