語法分析


語法分析的作用是處理詞法分析得到的記號流建立語法樹(又稱分析樹), 並且建立符號表處理語法錯誤。

本文約定大寫英文字母A、B、C等表示非終結符;小寫英文字母a、b、c等表示終結符;小寫希臘字母α、β、δ等表示任意記號序列

上下文無關文法

上下文無關文法(Context Free Grammar,CFG)可以表示大多數程序設計語言的語法,又足夠簡單讓我們實現相應的分析器。

文法由四元組定義:

  • 非終結符(Nonterminals)集合

  • 終結符(Terminals)集合

  • 開始符號(Start Symbol)

  • 產生式(productions)

所謂產生式是替換規則, 在進行推導時產生式左側的記號序列可以由右側記號替換。如產生式E=>E+E中表示表達式可以表示為兩個表達式相加。

產生式的推導具有自反性和傳遞性。若產生式右側為空串則稱為空產生式。

所謂上下文無關文法是指所有產生式左側僅有一個非終結符, 上下文有關文法中存在形如aAb=>α的產生式。

上下文無關文法允許我們任意情況下將左側記號替換為右側序列,讓我們更容易構建分析器。BNF(巴克斯-諾爾范式)經常用來表達上下文無關文法。

定義算術表達式中的產生式:

  • E=>(E)

  • E => E+E

  • E => -E

展示一下算術表達式-(a+b)的推導過程:

 E => -E => -(E) => -(E+E) => -(E+b) => -(a+b)

或者以分析樹的形式表示:

自底向上看分析樹我們對原表達式采用了從右到左的分析順序,這種順序稱為最右推導或正規推導。對應地, 有最左推導的定義。

我們嘗試對算術表達式a*b+c按照不同優先級不同推導方式不同分別建立語法樹:

  • 最左推導乘法優先: E => E+E => E*E+E => a*E+E => a*b+E => a*b+c

  • 最左推導加法優先:E => E*E => E*(E+E) => a*(E+E) => a*(b+E) => a*(b+c)

  • 最右推導乘法優先:E => E+E => E*E+E => E*E+c => E*b+C => a*b+c

  • 最右推導加法優先:E => E*E => E*(E+E) => E*(E+c) => E*(b+c) => a*(b+c)

可以看出推導的結果僅與文法和句子有關與推導方法無關。自底向上看語法樹, 高優先級的生成式會被先處理。

若某一文法對一個句子有多棵分析樹則稱文法是二義的。避免二義性需要規定產生式的優先級和結合性或者修改文法。

作為表達能力更強的上下文無關文法一定可以表示正規式, 但是正規式不一定可以表示上下文無關文法。

將正規式轉換為上下文無關文法的流程如下:

  • 構造正規式的NFA;

  • 若0為初態,則A0為開始符號;

  • 對於move(i,a)=j,引入產生式Ai => aAj;

  • 對於move(i,ε)=j,引入產生式 A i=> Aj;

  • 若i是終態,則引入產生式Ai => ε。

示例, 從正規式r=(a|b)*abb的NFA構造CFG:

A0 => aA0|bA0|aA1
A1 => bA2
A2 => bA3
A3 => ε 

自頂向下語法分析

語法分析是對輸入序列進行最左推導,嘗試建立它的語法樹,最終得到一條合法句子或發現錯誤的過程。

自上而下分析是一個試探回溯的過程, 嘗試一切可能建立與輸入序列匹配的語法樹。

消除左遞歸

若存在形如A=>Aα|β的生成式時我們稱文法存在直接左遞歸, 直接左遞歸會使最左推導語法分析陷入死循環中必須加以消除。

A=>Aα|β中A不是β的前綴(即β不以A開頭)則可以將產生式用兩個產生式替換:

A  => βA'
A' => αA' | ε

這樣原來的左遞歸被替換為右遞歸, 而右遞歸不會導致最左推導陷入死循環。

文法E => E+T | T 存在直接左遞歸, 根據代換規則替換為兩個產生式:

E => TE'
E'=> +TE' | ε

用原文法分析a+b: E => E+T => a+T => a+b, 當然我們根據經驗避免了左遞歸。

使用消除左遞歸后的文法分析a+b: E => TE' => T+TE' => T+T => a+T => a+b

有些文法不含直接左遞歸,但通過推導可以得到左遞歸產生式:

  1. S => Qc | c
  2. Q => Rb | b
  3. R => Sa | a

經過推導可以得到左遞歸產生式: S => Qc => Rbc => Sabc

消除間接左遞歸的基本思想是將間接左遞歸轉換為直接左遞歸, 再將直接左遞歸轉換為右遞歸。

首先產生式將產生式重新排列為可以迭代的順序: A[i] => A[i+1]α | β; A[i+1] => A[i+2]α | β; ...。上文中的示例已經滿足該條件不必調整。

將3.代入2.中得到4.: Q => (Sa | a)b | b => Sab | ab | b

將4.代入1.中得到5.: S => (Sab | ab | b)c | c => Sabc | abc | bc | c

消除5.中的直接左遞歸得到6.: S => abcS’| bcS'| cS'; S' => abcS' | ε

從開始符號S出發不會經過QR, 可以將1,2,3, 4刪去僅保留5,6作為新文法。

遞歸下降分析

遞歸下降分析法是指為記號流中特定非終結符編寫一個子程序, 當發現該終結符時即調用該子程序進行分析。

我們規定*優先從左向右處理記號流, 嘗試分析1*1+1*1

自左向右掃描記號流, 發現相應運算符即交由子程序處理。注意因為*優先,則+更靠近分析樹根部, 所以應優先調用+的子程序, 其次為*子程序, 最后對的1處理完成后返回。

調用棧即代表語法樹:

![](http://images2015.cnblogs.com/blog/793413/201611/793413-20161129172840427-2027606437.png)

LL文法分析

LL分析器是一種處理上下文無關文法的自頂向下的語法分析器, 它從左到右處理輸入,再對句型執行最左推導出語法樹故稱為LL分析器。

LL分析器是一種預測分析器, 與普通遞歸下降分析器不同,預測分析器會向后探查來決定當前語法樹結構從而避免回溯。

若一個LL分析器向前探查k個記號(token), 那我們稱該分析器為LL(k)分析器, 其中最常用是設計簡單且可以處理大多數情況的LL(1)分析器。

給定上下文無關文法:

(1) S => E
(2) S => (S + E)
(3) E => i

試圖對((i+i)+i)進行最左推導:

S => (S+E) => ((S+E)+E) => ((E+E)+E) => ((i+E)+E) => ((i+i)+E) => ((i+i)+i)

從狀態2(S+E)推導狀態3時我們有兩個選擇S=>(S+E)或者S=>E。在示例中選擇任何一種推導都無關緊要, 但在某些情況下做出錯誤選擇后不得不進行回溯。

LL分析法可以通過探查后面的記號來做出決定, 理論上說對任何上下文無關語言總存在k,使得LL(k)無法識別的記號流可以被LL(k+1)識別。

LL分析器依賴分析表來進行決策, 分析表根據后面的記號和當前狀態決定下一步狀態轉移:

- + ( ) i
S - 2 - 1
E - - - 3


LL分析器維護一個記號棧, 用於保存待處理的記號. LL分析器首先在棧中壓入語法樹的根元素, 然后根據記號流中的下一個記號對棧頂元素進行推導, 當棧頂元素無需繼續處理時彈出棧頂。當棧為空時語法分析結束。

我們嘗試使用LL(1)分析器對(i+i)進行分析:

  1. 記號棧首先壓入語法樹的根元素S, 記號棧為:[S]

  2. 從記號流讀入(,讀取棧頂S, 根據分析表應用規則(2)將S改寫為(S+E), 記號棧為: [(,S,+,E,)]

  3. 從記號流讀入i, 棧頂(無需推導彈出, 處理新棧頂S: 根據分析表應用規則(1)將S改寫為E,記號棧為:
    [E,+,E,)]

  4. 處理棧頂E, 應用規則(3)將記號棧改寫為: [i, +, E, ) ]

  5. 根據讀入的i+依次丟棄棧頂i+

  6. 根據棧頂E和讀入的i, 改寫記號棧: [i,)]

  7. 讀入), 彈出棧頂分析完畢。

自底向上語法分析

實際應用中語法分析器更多地從記號流開始建立分析樹, 而非從根元素開始建立語法樹。加上自底向上分析法更強大, 大多數實用編譯器均采用自底向上分析法。

LR分析器是典型的自底向上分析法, 它從左向右處理記號流並嘗試進行最右推導。

給定上下文無關文法:

(1) S => E
(2) S => S + E
(3) E => i

LR分析器可以執行幾種特定動作:

  • r: reduce應用語法規則化簡

  • p: push將下一個記號入棧

  • a: accept分析完成, 記號流被文法接受

  • e: error發現語法錯誤

若記號流分析完畢沒有發現錯誤,則認為記號流被文法接受。

LR分析器同樣需要一個記號棧和一個分析表。分析表根據當前狀態和下一個記號決定動作, 為了便於描述, 我們根據記號棧來描述狀態:

- i +
[] r(3),r(1) p
[ S ] e p
[S, +] r(3) e
[S, +, E] r(2) e

嘗試對i+i進行LR分析

  1. 讀入i壓棧,依次逆用(3), (1)此時記號棧為: [S]

  2. 讀入+壓棧, 此時記號棧為: [S, +]

  3. 讀入i壓棧, 逆用(3)此時記號棧為: [S,+, E]

  4. 逆用(2), 此時記號棧為[S]

至此分析結束, 得到語法樹:

S => S + E => S + i => E + i => i + i


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM