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: NUM | exp exp '+' { $$ = $1 + $2; } | exp exp '-' { $$ = $1 - $2; } ... ;
exp部分是這個語法文件的主要定義部分,可是其實到這裏,其理解就容易了,其理解爲:一個表達式能夠爲一個NUM的token,也能夠爲exp exp '+'的形式,此時,會執行一個action,進行加法操做,也能夠爲exp exp '-'的形式,執行減法的action,依次類推。
5. 理解語法規則的流程
若是對於編譯原理中語法分析的過程有很清楚的瞭解,對於上面的規則可能會不能徹底理解其流程。好比這裏的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中的某一個的時候,就會提示語法錯誤。