hakeの日記

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

bison & flexメモ その4

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


基本的なフォーマットは以下、flexと同じような感じ

%{
初期Cコード
%}

BISON宣言

%%
構文規則
%%
他のCコード


下の例はドキュメントにある簡易電卓のソース。
mainからyyparse()を呼び出すことで構文解析を開始する。
yyparse()からは、yylex()が呼ばれて入力から字句を読み取りトークン種別(int値)を返す。数値の様にトークン種別以外を返す場合には%union{ }で記述したメンバ変数を介して行われる。構文解析時のreduce動作(例:$$ = $1 + $2)の場合にも同じ変数が使用される。メンバ変数が複数存在する場合は共用体で在るために同時に使用することはできない(と思う)、この様な場合は共用体のメンバとして構造体を使用する(と思う)
以下、ソースの説明

%union { 
   float val;
}

トークンNUMBERの値や式(exp)の受け渡しで使用される共用体、この例では実数1種しか使用しないのでvalのみである。使用する場合の共用体の名前はyylvalになる。
今回の様に変数がひとつの場合は、%union{ }を使用せずにYYSTYPEを定義することも可能

#define YYSTYPE float

この場合は、yylvalそのものがfloat型の変数になる。

%token NUMBER
%token PLUS MINUS MUL DIV POW
%token LP RP EOL ERR

使用するトークン種別の定義

%left  PLUS MINUS
%left  MUL DIV
%left  NEG
%right POW

演算子トークンの優先順位(下が高い)と結合方向の定義

%type  <val> exp NUMBER

トークンNUMBERと非終端記号expは数値を持つのでその型を定義する。< >の中が型で、%union{ }で定義したメンバ変数名。

%%
記号0: 記号1 記号2 記号3 …   { アクション }
;
%%
||>
記号0は非終端記号、記号1〜は終端記号(トークン)または非終端記号。
アクションの中では$$が記号0に、$1〜が記号1〜に対応する。


>||
int yylex (){ }

字句を解析して、トークン種別を戻り値とする。入力の最後まで解析終了した場合は、0を返す。トークン種別の他に値を渡す場合は、共用体yylvalを使用する。


expr.y

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

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

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


%}


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

/* トークンの宣言 */
%token NUMBER
%token PLUS MINUS MUL DIV POW
%token LP RP EOL ERR

/* 演算子の結合方向と優先順位 */
/* 下の方が順位が高い         */
%left  PLUS MINUS
%left  MUL 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("value => %g\n",$1);}  /* exprの値を表示 */
        | exp error EOL        { yyerror("parse error 2");} /* errorはbisonの予約語 */
;

/* '式'は以下の要素で構成される */
exp     : NUMBER                { $$ = $1;        } 
             /* yylex()の戻り値がNUMBERの場合には                    */
             /* $1には、yylval.valの値(scanfで読んだ値)が入る      */

        | exp PLUS  exp         { $$ = $1 + $3;   }
        | exp MINUS exp         { $$ = $1 - $3;   }
        | exp MUL   exp         { $$ = $1 * $3;   }
        | exp DIV   exp         { $$ = $1 / $3;   }
        | MINUS  exp %prec NEG  { $$ = -$2;       }
             /* %prec NEG は、ここの'-'はNEGの優先順位を持つという意味  */

        | exp POW exp           { $$ = pow($1,$3);}
        | LP  exp RP            { $$ = $2;        }
        | ERR                   { yyerror("Illegal charactor");}
;

%%


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


/* 字句を読み取る */
int yylex (){
  int c;

  /* 空白を読み飛ばす  */
  c = getchar ();
  while (c  == ' ' || c == '\t')
    c = getchar ();

  /* 数値を処理する   */
  if (c == '.' || isdigit(c)){
      ungetc(c, stdin);

      /* %union{ }内の変数は、共用体yylvalのメンバとしてアクセス可能  */
      /* ここで読んだ値が構文規則 NUMBERの$1の値になる */
      scanf("%f", &yylval.val);
      return NUMBER;
    }

  /* ファイルの終わりを処理する  */
  if (c == EOF) return 0;

  /* 数値以外の記号の場合 */
  switch(c){
  case '+' : return PLUS;
  case '-' : return MINUS;
  case '*' : return MUL;
  case '/' : return DIV;
  case '^' : return POW;
  case '(' : return LP;
  case ')' : return RP;
  case '\n': return EOL;
  }

  /* その他の文字の場合はエラーを返す */
  return ERR;
}


int main(){

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

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

Makefile

TARGET = expr
YACC_SRC = $(TARGET).y
YACC_C     = $(TARGET).tab.c
C_FILES = $(YACC_C)


all : $(TARGET)

$(TARGET) : $(YACC_C)
	gcc -o $(TARGET) $(C_FILES)


$(YACC_C) : $(YACC_SRC)
	bison -v $(YACC_SRC)


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

コンパイル時にflexの場合と同様に警告がでるが無視

expr.y:72:6: warning: conflicting types for 'yyerror'
expr.y:48:7: note: previous implicit declaration of 'yyerror' was here

実行結果

bash-3.1$ ./expr.exe
1+2*3             ←入力文字列
value => 7
3*(1+2)           ←入力文字列
value => 9
2^3               ←入力文字列
value => 8

構文エラーがある場合

"syntax error,〜"は、yyparse()の中で自動で出力されるメッセージ

bash-3.1$ ./expr.exe
1+               ←入力文字列
syntax error, unexpected EOL, expecting NUMBER or MINUS or LP or ERR
parse error 2
1++2             ←入力文字列
syntax error, unexpected PLUS, expecting NUMBER or MINUS or LP or ERR
parse error 2


以下、syntax errorにならないのはreduceの際にエラー情報が渡されないから?
@                ←入力文字列
Illegal charactor
value => 2
1@               ←入力文字列
syntax error, unexpected ERR
parse error 2
1@1              ←入力文字列
parse error 2
1@@1             ←入力文字列
parse error 2
@@               ←入力文字列
Illegal charactor
parse error 2