Bisonで再入可能な構文解析器を作る(C言語版)

前回作った電卓プログラムは、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関数を再入可能にします。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です