編譯原理(二)——語法分析(一)

版權聲明:本文爲原創文章,未經博主允許不得用於商業用途。

語法分析

上下文無關文法(CFG)

1.1 基本定義

CFG包含如下四個組成部分:

  • 終結符號:組成串的基本符號(詞法單元名,id,運算符)
  • 非終結符號:表示串的集合的語法符號(如expr,stmt)
  • 開始符號:某個被指定的非終結符(如expr)
  • 產生式:定義了使用非終結符和終結符狗構造串的方法。
    • 形式:頭(左)部\rightarrow體(右)部
    • 頭部是一個非終結符,右部是一個符號串

1.2 例子(c語言產生式)

在這裏插入圖片描述

其中expressionexpression表示表達式,termterm表示術語(保留字,關鍵字)。

經過簡化,此文法可以表示成:,其中’|'爲文法描述符,不是文法符號。

1.3 推導

推導可以看作是對產生式的解讀,如果產生式AγA\rightarrow \gamma,則αAβαγβ\alpha A\beta\Rightarrow \alpha \gamma\beta表示“通過一步推導出”。因此推導可以看作是按照產生式規則替換。同理,定義\overset{*}{\Rightarrow}表示經過零步或多步推導出。

在進行推導時,如果每次都解析最左(右)邊的非終結符號,則稱爲最左(右)推導

  • 句型:一個文法可以推導出的所有串的集合
  • 句子:不包含非終結符號的句型
  • 語言:所有句子的集合

1.4 語法分析樹

推導過程可以使用樹狀結構表示,其中:

  • 根結點的標號是文法的開始符號
  • 每個葉子結點的標號是非終結符號、終結符號或ε
  • 每個內部結點的標號是非終結符號
  • 每個內部結點表示某個產生式的一次應用
    • 結點的標號爲產生式頭,其子結點從左到右是產生式的體

一棵語法分析樹可以對應多個推導序列,不過只具有一種最左(右)推導(即前序遍歷和後序遍歷唯一)。

1.5 上下文無關文法和正則表達式

上下文無關文法比正則表達式的表達能力更強:即CFG可以對兩個個體的計數(最多兩個個體)而RE(NFA)不能。

例如{anbnn1}\{a^nb^n|n\geq 1\}(括號匹配)
  • CFG:SaSbabS\rightarrow aSb|ab
  • 正則表達式:反證法,若可以表示,若存在一個具有2n個狀態的NFA可以表示此語言,則對於an+1bn+1a^{n+1}b^{n+1},必有一個狀態s0s_0有一條收到a時指向之前k個狀態的邊,因此其可以接受an+kbn+1a^{n+k}b^{n+1},不在此語言中。矛盾。

設計文法

消除二義性

對於一些語法,同一個句型可以對應多個語法分析樹,如典型的if-else-then語法:

文法1:

stmtif expr then stmt  if expr then stmt else stmt  other stmt\rightarrow \bold{if}\ expr\ \bold{then}\ stmt\ |\ \bold{if}\ expr\ \bold{then}\ stmt\ \bold{else}\ stmt\ |\ other

對於if E1 then if E2 then S1 else S2if\ E_1\ then\ if\ E_2\ then\ S_1\ else\ S_2有兩種解讀,即S2S_2對應的層次可以是外層if語句,也可以是內層if語句。

可以通過規定else和最近的未匹配then匹配消除二義性,體現在文法層面如下:

文法2:

stmtmatched_stmt  open_stmt stmt\rightarrow matched\_stmt\ |\ open\_stmt

match_stmtif expr then matched_stmt else matched_stmt  other match\_stmt\rightarrow \bold{if}\ expr\ \bold{then}\ matched\_stmt\ \bold{else}\ matched\_stmt\ |\ other

open_stmtif expr then stmt  if expr then matched_stmt else open_stmt open\_stmt\rightarrow \bold{if}\ expr\ \bold{then}\ stmt\ |\ \bold{if}\ expr\ \bold{then}\ matched\_stmt\ \bold{else}\ open\_stmt

消除左遞歸

  • 左遞歸:文法中存在非終結符A使得A+AαA\overset{+}{\Rightarrow}A\alpha
  • 立即左遞歸:文法中存在非終結符A使得AAαA\rightarrow A\alpha

自頂向下的語法分析無法處理左遞歸,因爲在DFS時可以無限替換下去。

可以通過替換法消除左遞歸:

  • 立即左遞歸變爲立即右遞歸:
    • 輸入:AAαβA\rightarrow A\alpha|\beta
    • 轉化爲:AβAA\rightarrow \beta A'AαAϵA'\rightarrow \alpha A'|\epsilon
  • 多步左遞歸:
    • 算法思路:按照順序替換產生式,直到立即左遞歸出現後,消除立即左遞歸。(相當於求了產生關係的閉包)
    • 在這裏插入圖片描述

提取左公因子

在CFG分析句型時,每次都通過查看下一個符號選擇產生式,因此如果兩個產生式具有相同的前綴時,則無法判斷應該選擇哪一個。

因此需要提取產生是的左公因子(相同前綴),並且將公因子轉化爲新的產生式消除相同前綴。

  • 原語法:Aαβ1αβ2A\rightarrow \alpha \beta_1|\alpha\beta_2
  • 提取左公因子後:AαA,Aβ1β2A\rightarrow \alpha A',A'\rightarrow \beta_1|\beta_2

自頂向下的語法分析

自頂向下語法分析按照先根次序深度優先的創建節點(對應最左推導)建立語法分析樹,一次讀入一個字符,知道完成整個輸入串的解析。

在這裏插入圖片描述

此算法類似貪心算法,顯然當遇到一個錯誤時,不一定是語法錯誤,也可能是之前的產生式選擇錯誤,因此可以通過回溯改良算法。

FIRST和FOLLOW

通常在選擇產生式時使用FIRST和FOLLOW兩個函數。

  • FIRST(α)FIRST(\alpha)定義爲可以從α\alpha推導出的首符號的集合。
    • α\alpha爲終結符,則加入α\alpha
    • α\alpha爲非終結符,且αβ1β2...βk\alpha \rightarrow \beta_1\beta_2...\beta_k
      • θFIRST(βi),ϵFIRST(β1),FIRST(β2),...,FIRST(βi1)\theta\in FIRST(\beta_i),\epsilon\in FIRST(\beta_1),FIRST(\beta_2),...,FIRST(\beta_{i-1}),加入θ\theta
      • ϵFIRST(β1),FIRST(β2),...,FIRST(βk)\epsilon\in FIRST(\beta_1),FIRST(\beta_2),...,FIRST(\beta_{k}),加入ϵ\epsilon
    • α\alpha爲非終結符,且αϵ\alpha \rightarrow \epsilon,加入ϵ\epsilon
  • FIRST(α1α2...αn)FIRST(\alpha_1\alpha_2...\alpha_n)計算如下:
    • 加入FIRST(α1)FIRST(\alpha_1)中所有非ϵ\epsilon符號
    • ϵFIRST(αi),ϵFIRST(α1),FIRST(α2),...,FIRST(αi1)\epsilon\notin FIRST(\alpha_i),\epsilon\in FIRST(\alpha_1),FIRST(\alpha_2),...,FIRST(\alpha_{i-1}),加入FIRST(βi)FIRST(\beta_i)
    • ϵFIRST(α1),FIRST(α2),...,FIRST(αn)\epsilon\in FIRST(\alpha_1),FIRST(\alpha_2),...,FIRST(\alpha_{n}),加入ϵ\epsilon
  • FOLLOW(α)FOLLOW(\alpha)定義了可以跟隨在α\alpha右邊的終結符號集合
    • 例如:SaαbβS\rightarrow a\alpha b\beta,則終結符b在FOLLOW(α)FOLLOW(\alpha)
    • 計算:將右端結束標記$加入FOLLOW(B)中,保證非空
      • 存在SαBβS\rightarrow \alpha B\beta,則將FIRST(β)FIRST(\beta)中的非ϵ\epsilon符號加入
      • 存在AαB or AαBβ,ϵFIRST(β)A\rightarrow \alpha B\ or\ A\rightarrow \alpha B\beta,\epsilon\in FIRST(\beta),則將FOLLOW(A)FOLLOW(A)中所有符號加入

LL(1)文法

LL(1)文法中的LL和1分別代表從左向右掃描輸入字符、最左推導、每一步只需向前看一個輸入字符。

對於任意一個產生式AαβA\rightarrow \alpha |\beta,滿足如下兩個條件:

  • FIRST(α)FIRST(β)=ΦFIRST(\alpha)\cap FIRST(\beta)=\Phi
  • ϵFIRST(β),FIRST(α)FOLLOW(A)=Φ\epsilon\in FIRST(\beta),FIRST(\alpha)\cap FOLLOW(A)=\Phi,反之亦然

對於滿足上述條件的LL(1)文法,可以構造出不需要回溯的遞歸下降語法分析器。

預測分析表

爲了避免重複計算,爲語言構造預測分析表M,其中行表示非終結符號,列表示輸入符號(終結符號),每個單元記錄非終結符是否可以通過產生式推導出終結符。

構造算法:

  • 對於文法G中的每個產生式AαA\rightarrow\alpha
    • 對於FIRST(α)FIRST(\alpha)中的每個終結符號a,將AαA\rightarrow \alpha寫入M[A,a]M[A,a]單元中
    • 如果ϵFirst(α)\epsilon\in First(\alpha),對於FOLLOW(A)FOLLOW(A)中的每個終結符號b,將AαA\rightarrow \alpha寫入M[A,b]M[A,b]單元中
  • 算法執行後,在空白單元格填入error

由於LL(1)語法不存在二義性,因此在執行遞歸算法時對於每個輸入符號可以根據M唯一的選擇產生式。

特別的,如果出現衝突,則說明G不滿足LL(1)的條件,即具有二義性。

非遞歸預測分析

由於LL(1)文法時最左推導的,因此每次分析時只需記住尚未分析的產生式右側部分即可。

可以通過一個棧實現預測分析。具體過程如下:

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章