YACC (Yet Another Compiler Compiler)

yacc(Yet Another Compiler Compiler)html

 

是Unix/Linux上一個用來生成編譯器的編譯器(編譯器代碼生成器)。yacc生成的編譯器主要是用C語言寫成的語法解析器(Parser),須要與詞法解析器Lex一塊兒使用,再把兩部份產生出來的C程序一併編譯。yacc原本只在Unix系統上纔有,但現時已廣泛移植往Windows及其餘平臺。算法

 

分析程序生成器(parser generator)是一個指定某個格式中的一種語言的語法做爲它的輸入,併爲該種語言產生分析過程以做爲它的輸出的程序。在歷史上,分析程序生成器被稱做編譯-編譯程序( compiler- compiler ),這是因爲按照規律可將全部的編譯步驟做爲包含在分析程序中的動做來執行。如今的觀點是將分析程序僅考慮爲編譯處理的一個部分,因此這個術語也就有些過期了。合併 LALR(1) 分析算法是一種經常使用的分析生成器,它被稱做 Yacc( yet another compiler- compiler )。給出 Yacc 的概貌來,將使用Yacc爲 TINY 語言開發一個分析程序。 數組

  做爲 Yacc 對說明文件中的 %token NUMBER 聲明的對應。Yacc 堅持定義全部的符號記號自己,而不是從別的地方引入一個定義。可是卻有可能經過在記號聲明中的記號名以後書寫一個值來指定將賦給記號的數字值。 函數

  yacc的輸入是巴科斯範式(BNF)表達的語法規則以及語法規約的處理代碼,Yacc輸出的是基於表驅動的編譯器,包含輸入的語法規約的處理代碼部分。 工具

  yacc是開發編譯器的一個有用的工具,採用LALR(1)語法分析方法。 學習

  Yacc最初由AT&T的Steven C. Johnson爲Unix操做系統開發,後來一些兼容的程序如Berkeley Yacc,GNU bison,MKS yacc和Abraxas yacc陸續出現。它們都在原先基礎上作了少量改進或者增長,可是基本概念是相同的。 spa

  因爲所產生的解析器須要詞法分析器配合,所以Yacc常常和詞法分析器的產生器——通常就是Lex——聯合使用。IEEE POSIX P1003.2 標準定義了Lex和Yacc的功能和需求。 操作系統

  http://dickey.his.com/byacc/byacc.html Berkeley Yacc 通常認爲是目前最好的yacc變種。與bison相比,避免了對特定編譯器的依賴。 .net

  http://www.informatik.uni-freiburg.de/proglang/software/essence/ Essence,Scheme的LR(1)語法解析器的生成器 設計

  http://download.plt-scheme.org/scheme/plt/collects/parser-tools/ 用於DrScheme的語法解析工具

  http://www.ssw.uni-linz.ac.at/Research/Projects/Coco/ Coco/R Java和C#的掃描和解析器

  http://mhss.nease.net/unix/yacc.html Yacc: 另外一個編譯器的編譯器,Stephen C. Johnson

 

 

YACC理論

 

YACC
理論
yacc 的文法由一個使用BNF 文法(BackusNaur
form)的變量描述。 BNF 文法規則最初由 John
Backus 和 Peter Naur 發明,而且用於描述Algol60 語言。 BNF 可以用於表達上下文無關語言。現代
程序語言中的大多數結構能夠用BNF 文法來表達。例如,數值相乘和相加的文法是:
E >
E + E
E >
E * E
E >
id
上面舉了三個例子,表明三條規則(依次爲 r1,r2,r3)。像 E (表達式)這樣出如今左邊的結構叫
非終結符(nonterminal)。像 id(標識符)這樣的結構叫終結符(terminal,由lex 返回的標記),它
們只出如今右邊。這段文法表示,一個表達式能夠是兩個表達式的和、乘積,或者是一個標識符。
咱們能夠用這種文法來構造下面的表達式:
E >
E * E (r2)
>
E * z (r3)
>
E + E * z (r1)
>
E + y * z (r3)
>
x + y * z (r3)
每一步咱們都擴展了一個語法結構,用對應的右式替換了左式。右面的數字表示應用了哪條規則。
爲了剖析一個表達式,咱們實際上須要進行倒序操做。不是從一個簡單的非終結符開始,根據語法
生成一個表達式,而是把一個表達式逐步簡化成一個非終結符。這叫作「自底向上」或者「移進歸
約」分析法,這須要一個堆棧來保存信息。下面就是用相反的順序細述了和上例相同的語法:
1 . x + y * z 移進
2 x . + y * z 歸約 (r3)
3 E . + y * z 移進
4 E + . y * z 移進
5 E + y . * z 歸約 (r3)
6 E + E . * z 移進
7 E + E * . z 移進
8 E + E * z . 歸約 (r3)
9 E + E * E . 歸約(r2) 進行乘法運算
10 E + E . 歸約(r1) 進行加法運算
11 E . 接受
點左面的結構在堆棧中,而點右面的是剩餘的輸入信息。咱們以把標記移入堆棧開始。當堆棧頂部
和右式要求的記號匹配時,咱們就用左式取代所匹配的標記。概念上,匹配右式的標記被彈出堆
棧,而左式被壓入堆棧。咱們把所匹配的標記認爲是一個句柄,而咱們所作的就是把句柄向左式歸
約。這個過程一直持續到把全部輸入都壓入堆棧中,而最終堆棧中只剩下最初的非終結符。在第1
步中咱們把x 壓入堆棧中。第2 步對堆棧應用規則 r3,把x 轉換成 E 。而後繼續壓入和歸約,直到
堆棧中只剩下一個單獨的非終結符,開始符號。在第9 步中,咱們應用規則 r2 ,執行乘法指令。同
樣,在第10 步中執行加法指令。這種狀況下,乘法就比加法擁有了更高的優先級。
考慮一下,若是咱們在第6 步時不是繼續壓入,而是立刻應用規則r1 進行歸約。這將致使加法比
乘法擁有更高的優先級。這叫作「移進
歸約」衝突(shiftreduce
conflict )。咱們的語法模糊不
清,對一個表達式能夠引用一條以上的適用規則。在這種狀況下,操做符優先級就能夠起做用了。
舉另外一個例子,能夠想像在這樣的規則中
E >
E + E
是模糊不清的,由於咱們既能夠從左面又能夠人右面遞歸。爲了挽救這個危機,咱們能夠重寫語法
規則,或者給yacc 提供指示以明確操做符的優先順序。後面的方法比較簡單,咱們將在練習段中
進行示範。
下面的語法存在「歸約
歸約」衝突 (reducereduce
conflict)。當堆棧中存在id 是,咱們既能夠歸約
爲 T,也能夠歸約爲 E 。
E >
T
E >
id
T >
id
當存在衝突時, yacc 將執行默認動做。當存在「移進
歸約」衝突時,y acc 將進行移進。當存在
「歸約
歸約」衝突時, yacc 將執行列出的第一條規則。對於任何衝突,它都會顯示警告信息。只
有經過書寫明確的語法規則,才能消滅警告信息。後面的章節中咱們將會介紹一些消除模糊性的方
法。

 

練習,第一部分 ... 定義 ... %% ... 規則 ... %% ... 子程序 ... yacc 的輸入文件分紅三段。「 定義」段由一組標記聲明和括在「%{」和「%}」之間的C 代碼組 成。B NF 語法定義放在「規則」段中,而用戶子程序添加在「子程序」段中。 構造一個小型的加減法計算器能夠最好的說明這個意思。咱們要以檢驗lex 和yacc 之間的聯繫開始 咱們的學習。下面是yacc 輸入文件的定義段: %token INTEGER 上面的定義聲明瞭一個INTEGER 標記。當咱們運行yacc 時,它會在y.tab.c 中生成一個剖析器, 同時會產生一個包含文件 y.tab.h : #ifndef YYSTYPE #define YYSTYPE int #endif #define INTEGER 258 extern YYSTYPE yylval; lex 文件要包含這個頭文件,而且使用其中對標記值的定義。爲了得到標記,y acc 會調用 yylex。 yylex 的返回值類型是整型,能夠用於返回標記。而在變量yylval 中保存着與返回的標記相對應的 值。例如, [09]+ { yylval = atoi(yytext); return INTEGER; } 將把整數的值保存在yylval 中,同時向yacc 返回標記 INTEGER。y ylval 的類型由 YYSTYPE 決定。因爲它的默認類型是整型,因此在這個例子中程序運行正常。 0255 之間的標記值約定爲字 符值。例如,若是你有這樣一條規則 [+] return *yytext; /* 返回操做符 */ 減號和加號的字符值將會被返回。注意咱們必須把減號放在第一位心避免出現範圍指定錯誤。 因爲lex 還保留了像「文件結束」和「錯誤過程」這樣的標記值,生成的標記值一般從258 左右開 始。下面是爲咱們的計算器設計的完整的lex 輸入文件: %{ #include <stdlib.h> void yyerror(char *); #include "y.tab.h" %} %% [09]+ { yylval = atoi(yytext); return INTEGER; } [+/ n] return *yytext; [ /t] ; /* skip whitespace */ . yyerror("invalid character"); %% int yywrap(void) { return 1; } yacc 在內部維護着兩個堆棧;一個分析棧和一個內容棧。分析棧中保存着終結符和非終結符, 而且表明當前剖析狀態。內容棧是一個YYSTYPE 元素的數組,對於分析棧中的每個元素都保存 着一個對應的值。例如,當yylex 返回一個INTEGER 標記時,y acc 把這個標記移入分析棧。同 時,相應的yylval 值將會被移入內容棧中。分析棧和內容棧的內容老是同步的,所以從棧中找到對 應於一個標記的值是很容易實現的。下面是爲個人計算器設計的yacc 輸入文件: %{ int yylex(void); void yyerror(char *); %} %token INTEGER %% program: program expr '/n' { printf("%d/n", $2); } | ; expr: INTEGER { $$ = $1; } | expr '+' expr { $$ = $1 + $3; } | expr '' expr { $$ = $1 $ 3; } ; %% void yyerror(char *s) { fprintf(stderr, "%s/n", s); return 0; } int main(void) { yyparse(); return 0; } 規則段的方法相似前面討論過的BNF 文法。規則第一條叫command 規則。其中的左式,或都 稱爲非終結符,從最左而開始,後面緊跟着一個本身的克隆。後面跟着的是右式。與規則相應的動 做寫在後面的花括號中。 經過利用左遞歸,咱們已經指定一個程序由0 個或更多個表達式構成。每個表達式由換行結 束。當探測到換行符時,程序就會打印出表達式的結果。當程序應用下面這個規則時 expr: expr '+' expr { $$ = $1 + $3; } 在分析棧中咱們其實用左式替代了右式。在本例中,咱們彈出「expr '+' expr」 而後壓入 「expr」。 咱們經過彈出三個成員,壓入一個成員縮小的堆棧。在咱們的C 代碼中能夠用經過相對 地址訪問內容棧中的值,「 $1」表明右式中的第一個成員,「 $2」表明第二個,後面的以此類推。「 $ $ 」表示縮小後的堆棧的頂部。在上面的動做中,把對應兩個表達式的值相加,彈出內容棧中的三 個成員,而後把造獲得的和壓入堆棧中。這樣,分析棧和內容棧中的內容依然是同步的。 當咱們把INTEGER 歸約到expr 時,數字值開始被輸入內容棧中。當NTEGER 被移分析棧中 以後,咱們會就應用這條規則 expr: INTEGER { $$ = $1; } INTEGER 標記被彈出分析棧,而後壓入一個 expr。 對於內容棧,咱們彈出整數值,而後又把 它壓回去。也能夠說,咱們什麼都沒作。事實上,這就是默認動做,不須要專門指定。當遇到換行 符時,與expr 相對應的值就會被打印出來。當遇到語法錯誤時,y acc 會調用用戶提供的yyerror 函 數。若是你須要修改對yyerror 的調用界面,改變yacc 包含的外殼文件以適應你的需求。你的 yacc 文件中的最後的函數是main ... 萬一你奇怪它在哪裏的話。這個例子仍舊有二義性的語法。 yacc 會 顯示「移進歸 約」警告,可是依然可以用默認的移進操做處理語法。

相關文章
相關標籤/搜索