Flexと Bisonを同時に使う


Flexと Bisonを使う場合には、決まった書き方があります。

lexer.lには次のような #include文を入れておきます。


%{
void yyerror(char*);

#define YY_SKIP_YYWRAP
int yywrap(void) { return 1; }
#include "parser.h" /* parser.hは bisonが生成するファイル */
%}
delim	[ \t]
ws	{delim}+
letter	[A-Za-z]
digit	[0-9]
id	{letter}{{letter}|{digit}}*
number	{digit}+(\.{digit}+)?(E[+\-]?{digit}+)?

%%

{ws}		{ /* ここでは何もしない */ }
{number}	{ sscanf(yytext, "%lf", &yylval); return NUMBER; }
                /* NUMBERという終端記号の種類を返す。その値(“属性”)は 
                   yylvalという大域変数に代入する。 */
[+\-\*\/\(\)]	{ return yytext[0]; }
                /* + - * / ( )の場合は、マッチした文字をそのまま返す。*/
                /* マッチした文字は一般に yytext[0] 〜 yytext[yyleng-1]。*/
"\n"		{ return EOL; }
.		{ yyerror("不正な文字です。"); return EOL; } 

%%
/* なにもなし */

Bisonが生成するparser.hというファイルに NUMBEREOLなどの終端記号を表すマクロの定義が書かれています。

動作の中に return文を入れておくと、その式が、 yylexという関数の戻り値になります。

終端記号の ``種類''(NUMBER, EOLなど)を yylex関数の値として返し、値(“属性”)を yylvalという 大域変数に代入していることに注意します。

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

parser.yの方は、 あまり変わりませんが、 yylex関数は flexの方で用意するので宣言だけしておきます。


%{
#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 EOL
%left '+' '-'
%left '*' '/'
%%
input	:	/* 空 */
	| input line	{}
	;
line	: EOL		{ exit (0); } /* 空行だったら終了 */
	| expr EOL	{ printf ("\t%g\n", $1); }
	;
expr	: NUMBER	{ $$ = $1; }
	| 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 -oparser.c -d parser.y
  flex -olexer.c -I lexer.l
bisonの生成するヘッダーファイルが flexに必要なので、 必ず -dオプションをつけて bisonを先に実行します。 このとき-oオプションで、 Cファイル名(この場合 parser.c)を指定しておきます。 すると、拡張子を除いて同じ名前のヘッダーファイル(この場合 parser.h)ができます。 -oオプションをつけないと、 parser.tab.cparser.tab.hというファイルができます。 あとはこの 2つの Cソースファイルをコンパイルします。
  bcc32 -ecalc lexer.c parser.c
-e は実行ファイルの名前を指定するオプションです。 calc.exeという実行可能ファイルができます。
プログラム言語論のホームページ
Koji Kagawa (kagawa@eng.?????)