Bison Manual基礎筆記2

Bison示例1:雙精度逆波蘭計算器http://www.gnu.org/software/bison/manual/bison.html#RPN-Calchtml

/* filename: input.y */
/* 雙精度逆波蘭記號(一個使用後綴操做符的計算器) */
 /* Reverse polish notation calculator.  */
%{
	#define YYSTYPE double
	#include <stdio.h>
	#include <math.h>
	int yylex (void); // 因爲分析器須要調用這兩個函數,因此須要先聲明
	void yyerror (char const *);
%}

%token NUM

%% /* Grammar rules and actions follow.  */

input:    /* empty */
	| input line
	;

line:     '\n'
	| exp '\n'      { printf ("\t%.10g\n", $1); }
	;

exp:      NUM           { $$ = $1;           }
	| exp exp '+'   { $$ = $1 + $2;      }
	| exp exp '-'   { $$ = $1 - $2;      }
	| exp exp '*'   { $$ = $1 * $2;      }
	| exp exp '/'   { $$ = $1 / $2;      }
	/* Exponentiation */
	| exp exp '^'   { $$ = pow ($1, $2); }
	/* Unary minus    */
	| exp 'n'       { $$ = -$1;          }
	;
%%
/*
	詞法分析起在棧上返回一個雙精度浮點數(注:指yylval)而且返回記號NUM, 或者返回不是數字的字符的數字碼.它跳過全部的空白和製表符, 而且返回0做爲輸入的結束.
*/
#include <ctype.h>

int yylex (void) {
	int c;
	/* Skip white space.  */
	while ((c = getchar ()) == ' ' || c == '\t');
	/* Process numbers.  */
	if (c == '.' || isdigit (c))
	{
		ungetc (c, stdin);
		scanf ("%lf", &yylval);
		return NUM;
	}
	/* Return end-of-input.  */
	if (c == EOF)
	return 0;
	/* Return a single char.  */
	return c;
}

int main (void) {
	return yyparse ();
}

	
#include <stdio.h>
     
/* Called by yyparse on error.  */
void yyerror (char const *s) {
	fprintf (stderr, "%s\n", s);
}

/*
	samples:
	1. 1+1 -> 1 1 +, 2
	2. 1+2+3	-> 1 2 + 3 +, 6
	3. (1+2)*3	-> 1 2 + 3 *,9
	4. 1 + 2*3 -> 1 2 3 * +, 7
	5. (10-5) * (3 + 2) - 5*5	-> 10 5 - 3 2 + * 5 5 * -, 0
*/

測試運行:

#ls
input.y
#bison input.y 
#ls
input.tab.c  input.y
#gcc input.tab.c -lm
#ls
a.out  input.tab.c  input.y
#./a.out 
1 1 +
	2
1 2 3 * +
	7
10 5 - 3 2 + * 5 5 * -
	0
1 2 3 + +
	6
1 2 3 +
syntax error
#

理解:

關於bison語法文件的結構、註釋等和Lex都很相似,就不說明了。須要說明的主要有:git

1. #define YYSTYPE double函數

這裏YYSTYPE爲bison的預約義宏,其用於指定tokens和groupings的C數據類型。若是不定義YYSTYPE,其默認爲int類型。這裏的類型是何意思?由於語法規則中有一些token和grouping,可是它們都須要有類型的,好比假設爲C++定義語法,其中一個整數和一個字符串均可以做爲token,可是從語法上,二者相加是不行的。一樣,groupings爲多個token的組合結果,也是須要有類型的。總之,上面的例子很簡單,將全部的輸入都定義爲double類型。學習

2. yylex()和yyerror()的聲明測試

這裏須要進行前向聲明,是由於yyparser(bison語法分析過程)須要調用這些方法。spa

3. %token NUMdebug

Bison聲明。爲Bison提供關於token類型的信息。每個不是一個單獨的文字字符的終結字符必須在這裏被聲明。(一般,單個文字字符不須要被聲明)。在這個例子中,全部的算術操做符都是終結字符(token),可是沒有被聲明,由於都是單個文字字符(+-*/等),因此這裏只須要聲明的是數字常量類型,聲明爲NUM。code

能夠看到Bison的輸出文件對應有下面的內容:htm

/* Tokens.  */

#ifndef YYTOKENTYPE

# define YYTOKENTYPE

   /* Put the tokens into the symbol table, so that GDB and other debuggers

      know about them.  */

   enum yytokentype {

     NUM = 258

   };

#endif

因此,後面的代碼部分yylex()中能夠返回NUM,或者直接返回字符,其實都是返回token(用一個整數表示)。

4. 語法規則rules和行爲actions遞歸

語法規則和行爲是Bison和Lex區別比較大的一部分,因此要重點理解。

這裏的input(徹底的輸入)、line(輸入的一行)、exp(表達式)都是這個「語言」的groupings。這些非終結字符都有幾個可選的規則,經過"|"(或)鏈接。當一個grouping被識別的時候,」語言「的語義就被決定了,從而執行後面的actions,相似於Lex,actions爲C代碼。一樣,Bison也提供了本身的一些定義,這裏的"$$"表明將要構建的規則的grouping的語義值。大部分actions的主要工做就是賦值給"$$"。而後,這個規則的每一部分使用$1,$2等等來引用。

理解input:

input:    /* empty */
             | input line
     ;

其理解爲:一個完整的輸入要麼是一個空字符串,要麼是一個完整的輸入加上一個輸入行。注意的是這裏的」完整的輸入「就是它本身。這是一個左遞歸形式的定義,由於input老是在這個序列的最左邊。

空字符串表示其接受空字符串做爲其輸入,就運行語法解析器後直接退出,這個比較容易理解。那麼另外一個規則是「input line",其表示的含義是」當讀取必定的行數後,儘可能再讀取一行「。這樣就造成了一個循環的遞歸,因此parser函數會一直處理input,知道一個語法錯誤被發現或者詞法分析器告知沒有更多輸入的tokens。

理解line:

line:     '\n' 
	| exp '\n'  { printf ("\t%.10g\n", $1); }
;

這裏的理解爲:

這個規則的一個選擇是一個爲換行字符的token,這說明這個語法分析其接受一個空行,同時會忽略它,由於沒有指定action。另外一個選擇是一個表達式加上一個新行(exp '\n'),這個選擇使得計算器有用。這個exp grouping的語義值就是$1,因此,其行爲是打印這個值,其表示計算結果。(這個action不是很經常使用,由於並無賦值給$$。由於這裏每次只計算一行輸入,不須要保存其結果。)

理解exp:
exp:      NUM
             | exp exp '+'     { $$ = $1 + $2;    }
             | exp exp '-'     { $$ = $1 - $2;    }
             ...
             ;

exp部分是這個語法文件的主要定義部分,可是其實到這裏,其理解就容易了,其理解爲:一個表達式能夠爲一個NUM的token,也能夠爲exp exp '+'的形式,此時,會執行一個action,進行加法操做,也能夠爲exp exp '-'的形式,執行減法的action,依次類推。

5. 理解語法規則的流程

PS:注意:下面的分析純屬我的想法,包括其中的用詞,由於沒有系統學習過編譯原理,用詞也是根據我的感受,根據印象中看過的一些詞和本身的理解這麼描述的,只是爲了表述本身的理解。總之,請忽略下面的內容。

若是對於編譯原理中語法分析的過程有很清楚的瞭解,對於上面的規則可能會不能徹底理解其流程。好比這裏的input的定義何用?上面的exp和line的定義都比較容易理解其做用。可是input好像沒有做用,事實上,去掉input的定義測試能夠發現:

#./a.out 
1 2 +
	3
3 4 +
syntax error
#

爲什麼?下面來大體的理解一下語法分析器是如何工做的,爲什麼去掉input的定義就會提示語法錯誤,在這種狀況下,直接輸入ctrl+D也會提示語法錯誤。

首先沒有去掉input的時候,語法分析器會在這些規則中進行「匹配」,剛開始爲空,這時候,語法分析器就匹配了一個「input"=empty,而後其action爲空,因此繼續分析。讀取到1,爲一個exp,action是將其結果保存($$=$1)(語法分析是一個堆棧的過程,理解其爲一個堆棧入棧),繼續分析,讀取到2,爲另外一個exp,一樣保存其結果(此時,堆棧裏面爲1,2),而後繼續分析,讀取到一個+,此時,進行匹配,發現存在一個匹配爲exp exp '+'的規則,其結果爲exp,其action是進行計算保存結果(此時堆棧爲123)。(分析的過程也理解爲一個堆棧,那麼分析棧中的內容爲:input, exp, exp, '+',因爲exp,exp,'+’結果爲exp,因此至關於input, exp),繼續讀取到回車,那麼,因爲exp '\n'能夠匹配,結果爲line,那麼就執行其操做,進行打印結果,這時候並無入棧保存數據。(此時分析棧內容爲input, line,能夠匹配結果爲input。),而後繼續分析,分析棧依次匹配爲:

input, exp    (3)

input, exp, exp    (4)

input, exp ('+') (exp exp '+‘匹配爲一個exp)

input (回車) (input: input line)

...

可見,上面的過程當中,分析棧中的內容老是能夠繼續被匹配下去,最後老是能夠被匹配爲一個input。

可是一旦去掉了input的定義,若是直接輸入ctrl+D,那麼內容爲empty,沒法匹配,因此會提示語法錯誤。對於上面的例子,那麼其過程爲:

exp (1)

exp (2)

exp ('+') (exp: exp exp '+')

line ('\n') (line: exp '\n') 輸出結果

line exp (3)

到了這裏,line exp,不管後面是什麼,都再也不可能被匹配爲line或者被匹配爲exp了。總之,當語法分析器發現」沒有機會「再被匹配爲rule中左邊定義的這些groupings中的某一個的時候,就會提示語法錯誤。

相關文章
相關標籤/搜索