前回作った電卓プログラムは、Bisonが吐き出した main.tab.c というCのコードをコンパイルしてリンクしています。
中を見ると、yyparse関数の中でグローバル変数 “yychar” を初期化したり、字句解析器によって切り出されたトークンを代入したりしているので、このyyparseは再入可能ではありません。つまり、スレッドセーフではないということになります。
(他にも、グローバル変数yylvalを介して 字句解析器とやりとりしてたりします。)
Bisonが再入可能なコードを吐くようにするやりかたは次の2つ。
- 再入可能なCのコードを出力させる
- C++のコードを出力させる
今回は1つ目の方法をとります。
Bisonコードの宣言部に、
%define api.pure full
を入れます。
こうすることによって、
今まで グローバル変数だった yylval(トークンの値)などの変数 が yyparse関数のローカル変数になり、
yylval は yylex(&yylval) として、yylexに参照として渡されることになります。
しかしこのままだと Flex が出力したCコードをコンパイルするときに
error: ‘yylval’ undeclared
などと怒られてしまいます。
(yylval は Bisonが出力したCコードの中で定義されていました)
Flex が出力する Cコードの中で yylval変数 が使えるようにするために yylex の定義を変えてやる必要があります。
それには Flex 側で YY_DECLマクロを定義します。
YY_DECL がないと、Flexはデフォルトで次のように YY_DECL を定義します。
#define YY_DECL int yylex (void)
そしてこの YY_DECL を使って、yylexが定義されます。
YY_DECL
{
(yylexの処理)
}
Bisonの%code宣言でマクロを定義すると、Bisonが出力するヘッダファイルに入れてくれるので、ここでYY_DECLを定義するのがよさそうです。
main.y の宣言部
%define api.pure full
%code provides {
#define YY_DECL int yylex(YYSTYPE *yylval)
}
あと、yylval の型が YYSTYPE から YYSTYPE* に変わったので、
Flexソースに書いたアクションも書き直す必要があります。
yylval.int_val
としていたところを、
yylval->int_val
のように修正します。
修正後のそれぞれのソースは以下のようになりました。
main.y
%{
#include "stdio.h"
extern void yyerror(char *s);
%}
%define api.pure full
%code provides {
#define YY_DECL int yylex(YYSTYPE *yylval)
}
%union {
int int_val;
}
%token <int_val> CONST_N
%token DELIM
%type <int_val> expr
%left OP_PLUS
%left OP_MUL
%%
program: expr { printf("%dn", $1); }
expr
: CONST_N { $$ = $1; }
| expr OP_PLUS expr { $$ = $1 + $3; }
| expr OP_MUL expr { $$ = $1 * $3; }
%%
void yyerror(char *s) {
printf("aaa%sn", s);
}
main.l
%{
#include "main.tab.h"
%}
%%
"+" { return OP_PLUS; }
"*" { return OP_MUL; }
";" { return DELIM; }
([[:digit:]]{-}[0])[[:digit:]]* { yylval->int_val = atoi(yytext); return CONST_N; }
([[:blank:]]|n)+ {}
. return yytext[0];
これで 構文解析部分が再入可能となったように見えますが、まだ yyparse関数は再入可能になっていません。
中で呼び出している yylex関数が再入可能でないからです。
というわけで次回 yylex関数を再入可能にします。