GCC編譯器原理(三)------編譯原理三:編譯過程(2-2)---編譯之語法分析
2.2 語法分析
語法分析器(Grammar Parser)將對由掃描器產生的記號進行語法分析,從而產生語法樹(Syntax Tree)。整個分析過程采用了上下文無關語法(Context-free Grammar)的分析手段。
由語法分析器生成的語法樹就是以表達式(Expression)為節點的樹。如下所示:
從圖中可以知道,整個語句就是一個賦值表達式:賦值表達式的左邊是一個數組表達式,右邊是一個乘法表達式;數組表達式又由兩個符號表達式組成,等等。符號和數字是最小的表達式,它們不是由其他表達式來組成,所以它們通常作為整個語法樹的葉節點。
在語法分析的同時,很多運算符號的優先級和含義也被確定下來了。比如乘法表達式比加法表達式的優先級高。
另外有些符號具有多重含義,比如 * 在C語言中可以表示乘法表達式,也可以表示指針取內容的表達式,所以語法分析階段必須對這些內容進行區分。如果出現了表達式不合法,比如各種括號不匹配、表達式中缺少操作符等,編譯器就會報告語法分析階段的錯誤。
語法分析工具使用 yacc(Yet Another Compiler Compiler),它像 lex 一樣,可以根據用戶給定的語法規則對輸入的記號序列進行解析,從而構建出一棵語法樹。
2.2.1 yacc 介紹
引用:https://blog.csdn.net/zdy0_2004/article/details/54918450
yacc(Yet Another Compiler Compiler),是Unix/Linux上一個用來生成編譯器的編譯器(編譯器代碼生成器).
使用巴克斯範式(BNF)定義語法,能處理上下文無關文法(context-free)。出現在每個產生式左邊(left-hand side:lhs)的符號是非終端符號,出現在產生式右邊(right-hand side:rhs)的符號有非終端符號和終端符號,但終端符號只出現在右端。
yacc是開發編譯器的一個有用的工具,采用LR(1)(實際上是LALR(1))語法分析方法。
LR(k)分析方法是1965年Knuth提出的,括號中的k(k >=0)表示向右查看輸入串符號的個數。LR分析法正視給出一種能根據當前分析棧中的符號串和向右順序查看輸入串的k個符號就可唯一確定分析器的動作是移進還是規約和用哪個產生式規約。
這種方法具有分析速度快,能準確,即使地指出出錯的位置,它的主要缺點是對於一個使用語言文法的分析器的構造工作量相當大,k愈大構造愈復雜,實現比較困難。
-
一個LR分析器有3個部分組成:
-
總控程序,也可以稱為驅動程序。
- 對所有的LR分析器總控程序都是相同的。
-
分析表或分析函數。
- 不同的文法分析表將不同,同一個文法采用的LR分析器不同時,分析表也不同,分析表又可分為動作(ACTION)表和狀態轉換(GOTO)表兩個部分,它們都可用二維數組表示。
-
分析棧,包括文法符號棧和相應的狀態棧。
- 它們均是先進後出棧。 分析器的動作由棧頂狀態和當前輸入符號所決定(LR(0)分析器不需要向前查看輸入符號)。
-
總控程序,也可以稱為驅動程序。
-
LR分析器工作過程如下 :
- 其中SP為棧指針,S[i]為狀態棧,X[i]為文法符號棧。狀態轉換表內容按關系GOTO[Si,X] = Sj確定,該關系式是指當棧頂狀態為Si遇到當前文法符號為X時應轉向狀態Sj。X為終結符或非終結符。 ACTION[Si,a]規定了棧頂狀態為Si是遇到輸入符號a應執行的動作。
-
執行的動作的分類:
- 移進:當Sj = GOTO[Si,a]成立,則把Sj移入到狀態棧,把a移入到文法符號棧。其中i,j表示狀態號。
- 規約:當在棧頂形成句柄為β時,則用β歸約為相應的非終結符A,即當文法中有 A-->β的產生式,而β的長度為r(即|β| = r),則從狀態棧和文法符號棧中自棧頂向下去掉r個符號,即棧指針SP減去r。並把A移入文法符號棧內,再把滿足Sj = GOTO[Si,A]的狀態移進狀態棧,其中Si為修改指針後的棧頂狀態。
- 接受acc:當規約到文法符號棧只剩文法的開始符號S時,並且輸入符號串已結束即當前輸入符是‘#‘,則為分析成功。
- 報錯:當遇到狀態棧頂為某一狀態下出現不該遇到的文法符號時,則報錯,說明輸入串不是該文法能接受的句子。
2.2.2 YACC 的文件格式
YACC 的文件格式分為三個部分:
- ...definitions...( % {} % )
- % %
- ...rules...
- % %
- ...subroutines...
- 定義部分:定義部分包括標誌(token)定義和C代碼(用"%{"和"%}"括起來)。
- 規則部分:規則中目標或非終端符放在左邊,後跟一個冒號(:),然後是產生式的右邊,之後是對應的動作(用{}包含)
-
第三部分:該部分是函數部分。當yacc解析出錯時,會調用函數yyerror(),用戶可自定義函數的實現。
- 遞歸的處理:遞歸處理有左遞歸和右遞歸。
- If-else 的沖突:當有兩個IF一個ELSE時,該ELSE和哪個IF匹配是一個問題。有兩種匹配方法:與第一個匹配和與第二匹配。現代程序語言都讓ELSE與最近的IF匹配,這也是yacc的缺省行為。
- 出錯處理:當yacc解析出錯時,缺省的行為是調用函數yyerror(),然後從yylex返回一個值。一個更友好的方法是忽略一段錯誤輸入流,繼續開始掃描。這裏要涉及到YACC中錯誤保留字error的應用。
GCC編譯器原理(三)------編譯原理三:編譯過程(2-2)---編譯之語法分析