hakeの日記

Windows環境でプログラミングの勉強をしています。

bison & flexメモ その5

C言語でbisonとflexを使う方法のメモ


bison単体で使用する場合と比較して、字句解析の関数yylex()部分をflex側にまかせている。このときbisonが生成するヘッダファイルをflex側で読み込むことで、bison側で定義したトークンの種別をflex側で使用可能になる、同様にトークン種別以外の値を渡す変数yylvalもflex側で使用可能になる。


expr.exeを実行してみると構文エラーの有る場合にはプログラムが終了してしまった。bison単体で作成したexpr.exeでは終了しないで入力待ちになったのだけど何が変わったのか不明。これに対応する為に、bison側の構文規則lineの3行目でyyerrokとyyclearinを追加しエラー時の強制終了の回避と入力バッファのクリアを行っている。

expy.y

%{
#include <stdio.h>
#include <math.h>

/* デバッグ用 */
/* #define YYDEBUG 1 */

/* エラー内容を詳細に報告 */
#define YYERROR_VERBOSE 1

%}

/* ここで宣言した変数は共用体yylvalのメンバとしてアクセス可能 */
%union { 
   float val;
}

/* トークンの宣言 */
/* flex側でも使用可能 */
%token NUMBER
%token PLUS MINUS MULT DIV POW
%token LP RP EOL ERR

/* 演算子の結合方向と優先順位 */
/* 下の方が順位が高い         */
%left  MINUS PLUS
%left  MULT DIV
%left  NEG
%right POW

/* < >の中は%union{ }に書いたメンバ名                  */
/* expとNUMBERの値の受け渡しは、yylval.valを使用する   */
%type  <val> exp NUMBER


/* 以下、%%〜%%間は構文規則 */
%%
/* inputは、空若しくは複数のlineで構成される */
input   :
        | input line
;

/* lineは、改行のみ、若しくは'式'と改行で構成される */
line    : EOL
        | exp EOL    { printf("=> %g\n",$1);}
        | error EOL  { yyerror("parse error");
                       yyerrok;    /* エラーが発生してもプログラムを終了させない */
                       yyclearin;  /* 入力バッファをクリアする */
                     }
;

/* '式'は以下の要素で構成される */
exp     : NUMBER                 { $$ = $1;        }
             /* flex側のyylex()の戻り値がNUMBERの場合には       */
             /* $1には、yylval.valの値(scanfで読んだ値)が入る */
        | exp PLUS  exp          { $$ = $1 + $3;   }
        | exp MINUS exp          { $$ = $1 - $3;   }
        | exp MULT  exp          { $$ = $1 * $3;   }
        | exp DIV   exp          { $$ = $1 / $3;   }
        | MINUS  exp %prec NEG   { $$ = -$2;       }
        | exp POW exp            { $$ = pow($1,$3);}
        | LP exp RP              { $$ = $2;        }
;

%%


void yyerror(char *s)
{
  printf("%s\n",s);
}

int main(){

  /* デバッグ用 */
  /* yydebug = 1; */

  /* 構文解析開始 */
  yyparse();
}

expr.l

%{
/* bison側で作成するヘッダファイル */
#include "expr.tab.h"

/* 字句(トークン)の種類はbison側で定義 */
/* ヘッダファイルに含まれる              */


/* 読み込みファイルが複数ある場合に使用、今は1を返す様にしておく */
int yywrap(void){ return 1;}

%}

%%

[0-9]+     { yylval.val = atof(yytext);
             return(NUMBER);
           }
[0-9]+\.[0-9]+ { 
             /* ここで読んだ値がbison側の構文規則 NUMBERの$1の値になる */
             sscanf(yytext,"%f",&yylval.val);
             return(NUMBER);
           }
"+"        return(PLUS);
"-"        return(MINUS);
"*"        return(MULT);
"/"        return(DIV);
"^"        return(POW);
"("        return(LP);
")"        return(RP);
"\n"       return(EOL);
.          { yyerror("Illegal character"); 
             return(ERR);
           }
%%

Makefile

TARGET = expr
BISON_SRC = $(TARGET).y
BISON_H   = $(TARGET).tab.h
BISON_C   = $(TARGET).tab.c
BISON_O   = $(TARGET).tab.o
BISON_OUT = $(BISON_C) $(BISON_H) $(TARGET).output
FLEX_SRC  = $(TARGET).l
FLEX_C    = lex.yy.c
FLEX_O    = lex.yy.o
C_FILES   = $(BISON_C) $(FLEX_C)
OBJ       = $(FLEX_O) $(BISON_O)

all : $(TARGET)

$(TARGET) : $(OBJ)
	gcc -o $@ $(OBJ)

$(FLEX_C) : $(FLEX_SRC) $(BISON_H)
	flex $(FLEX_SRC)

$(BISON_OUT) : $(BISON_SRC)
	bison -dv $(BISON_SRC)

.c.o :
	gcc -c $<


clean :
	rm -f *.exe
	rm -f *.c
	rm -f *.h
	rm -f *.output
	rm -f *.o

lex.yy.o: lex.yy.c expr.tab.h
expr.tab.o: expr.tab.c expr.tab.h

実行結果

bash-3.1$ ./expr.exe
2+3*4        ←入力文字列
=> 14
(2+3)*4      ←入力文字列
=> 20
@            ←入力文字列
Illegal character
syntax error, unexpected ERR
parse error
1+           ←入力文字列
syntax error, unexpected EOL, expecting NUMBER or MINUS or LP
parse error
1@           ←入力文字列
Illegal character
syntax error, unexpected ERR
parse error
1++          ←入力文字列
syntax error, unexpected PLUS, expecting NUMBER or MINUS or LP
parse error