包教包會!十分鐘搞定自頂向下分析——編譯原理速成計劃

一、總述

  第一次學自頂向下分析的我曾經也面對書上各種奇怪的集而苦惱,總感覺非常難而且容易算漏,但是經過我對整個流程進行了一次代碼實現後我對它有了更深刻的理解,並總結出瞭如何快速做出這些題目以及這些流程背後的原理,來跟大家一起分享~
  爲了照顧要期末考試的童鞋萌需要用最短時間學會咋做題,所以只想知道怎麼做出題目,但是對原理不太深究的童鞋可以略過原理部分,直接看如何做題的部分,基本能在十分鐘內學會做這種類型的題,想明白爲什麼自頂向下分析需要做這些東西,以及它到底在幹什麼的童鞋可能需要多花一點點時間看前面的部分噢~
 

二、流程介紹

  可能看着很麻煩,其實非常簡單!!肯定能學會!!請大家務必看下去!!!

  首先第一步要做的是剔除多餘規則和有害規則,然後提取左公因子(消除回溯)和消除左遞歸,接着求出哪些非終結符能推出空(這一步用可以爲後面求集帶來方便),接着求FIRST集和FOLLOW集,然後求SELECT集(也可以不用求,有隻用FIRST集和FOLLOW集的方法,視你的教材而定),判斷是否是LL(1)文法,接下來得到預測分析表,就能通過預測分析表來確定給定的句子能不能被接受啦~

 

三、原理說明+如何做題

0.我們想幹啥

  • 我們想要一個LL(1)文法,然後判斷一個句子是否能被LL(1)文法接受。
    LL(1)的含義爲:第一個L表明自頂向下分析時從左向右掃描輸入串,第二個L表明分析過程將最左推導,1表明只需向右看一個符號便可決定如何推導,即選擇那個產生式進行推導。

  說人話就是,我們現在有個非終結符,非終結符就是能變出其它東西的東西,一般用大寫字母,終結符就是已經不能變了的東西,一般不是大寫字母就是。然後我們現在有個句子,我們希望能從開始符出發,然後用給定的產生式搗鼓出給定句子。那LL(1)文法又是個啥呢,就是我現在從開始符出發得到當前這個東西,那既然想推出來,不一樣的地方肯定是個非終結符,因爲終結符就已經塵埃落定了,不一樣就廢了,一定要是非終結符,才能通過變身換取一絲餘地,而LL(1)文法就是說,我只需要知道一個非終結符一個終結符就能知道該用哪個式子來變身。所以我們需要先判斷是否是LL(1)文法,然後再用得到的變身法則來變身看能不能變出來。
 

1.剔除多餘規則和有害規則

原理說明
  • 多餘規則
    啥是多餘規則?沒有用的規則就是多餘規則!啥是沒有用的規則?兩種情況!
    – 第一種是開始符出發永遠到不了的非終結符,這種非終結符的產生式就是無用規則,人家永遠都用不着你,備胎都不選你,你在這呆着幹啥呢
    – 第二種是永遠推不出終結符,永遠停不了的非終結符。你小子永遠終結不了,但是我語法要判斷的肯定是一個有限的句子,那要你何用?

  • 有害規則
    有害規則就是,不但幫不上忙,還倒打一耙的規則。通常遇到的有害規則就是A->A這種東西,爲啥說它有害呢?因爲會有二義性,我們現在想要得到一個句子,根據我們在0中提到的,我希望只需要知道一個非終結符一個終結符就能知道該用哪個式子來變身,那麼如果有了A->A的話,那我既可以選擇繼續當自己,也可以繼續選擇變身,因爲自己變自己肯定對結果沒影響,大不了啥都沒改嘛,也可以選擇變身,而這就造成了二義性,不能確定選哪一條。

如何做題

  直接找開始符一定遇不到的非終結符,以及永遠停止不了的非終結符,把這些非終結符的式子直接刪了,然後把自己推出自己的全刪了(注意,一定是自己出自己,要完全一樣,比如A->A纔是有害規則,像A->aA這種就不算有害規則了!)

 

2.提取左公因子(消除回溯)和消除左遞歸

原理說明
  • 啥是回溯?大白話說就是,對於一種情況下有好多種選擇,然後你先選擇其中一條路,如果走不通了就回來換一條路走。對於LL(1)文法這是不行的,我們想要的是百分百的確定!對於A>abcabdA->abc|abd,這種就是左公因子,第一個式子和第二個式子都是ab打頭,這樣的話就會阻礙我們後面的分析,如果A遇到a,到底是選擇這倆式子中的哪個呢?所以需要提取一波,引入一個新的符號A’,那麼就變成了A>abA,A>cdA->abA',A'->c|d
  • 啥是左遞歸?對於A>AbA->Ab,這種就是左遞歸,這個壞處就是打頭的是你自己,但是LL(1)文法其中一個L就是從左到右掃描字符,如果用A->Ab,右邊開始變了,從左往右掃描哪裏知道右邊的情況,所以需要轉換。如何轉換看下面的如何做題部分吧!
如何做題

提取左公因子:
只用記住公式
A>aBaCA->aB|aC這種,引入一個新的非終結符A’,變A>aAA>BCA->aA',A'->B|C

消除左遞歸:
消除左遞歸要求文法
1.無迴路(這個非終結符不能+推導(至少一次推導)出自己)
2.無空產生式(這個非終結符不能推導出空)

  • 對於直接左遞歸
    只用記住公式
    對於PPα1Pα2Pαmβ1β2βnP→P α_1|P α_2 |…| Pα_m |β_1| β_2 |…|β_n
    要引入新的非終結符P’,變成
    Pβ1Pβ2PβnPPα1Pα2PαmPε P→β1P’ |β2 P’|…|βnP’ \\P’ → α_1P’ | α_2 P’|…| α_mP’| ε 注意這個ε千萬別落
  • 對於間接左遞歸
    間接左遞歸就是A能+推導出Ab(類似這種)
    比如SAa,ASbbS → Aa,A → Sb|b,將S右部代替A的右部的S,會變出AAabbA → Aab|b,所以這種就是不斷的代入,然後用消除直接左遞歸的方法消除。要注意弄完還得剔除多餘規則。
    比如:
    在這裏插入圖片描述

 

3.哪些非終結符能推出空

原理說明

  能推出空集的就兩種情況,一種是本身就有這種產生式,另一種就是推出的某個產生式,右部所有的都是非終結符,而且這些非終結符都能推出空

如何做題

  先找到所有能推出空的式子,然後對應的非終結符都能推出空了,然後在其他式子裏找有沒有這傢伙,有的話就刪掉,如果某個非終結符能推出的式子都被刪光了,那麼這傢伙也能推出空。

舉個栗子
S>ABA>εB>ε S->AB \\A->ε \\B->ε
  可見A能推出空,然後把第一條裏的A刪掉,變S>BS->B,發現B也能推出空,把第一條裏的B刪掉,發現第一條裏的右邊都被刪光了,那麼S也能推出空,就這麼一步步下來就行了,可以理解成BFS~每次把被刪除的扔到隊列裏,然後每次取出隊首刪一波。

 

4.求FIRST集

原理說明

  先說FIRST集是啥,FIRST集就是對於某個非終結符來說,它可能推出的所有可能句子的第一個非終結符或者空。比如說A>Bc,B>abA->Bc,B->a|b,那麼A可能推出A>acA->acA>bcA->bc,FIRST(A)={a,b}
  爲啥需要這玩意兒?因爲大家想想嗷,我們想要的是知道非終結符和終結符就知道這玩意兒要用哪條式子,現在先要求不那麼高,先知道這玩意兒有沒有可能推出來,還有的用處在判斷是否是LL(1)文法的時候會介紹。所以當我知道了這個非終結符能推出的可能的句子的第一個字符,如果當前句子要推的非終結符不可能出來的話,那就直接game over了,不可能推出這個句子了,否則還有一線希望。同時FIRST集也是我們確定是哪個產生式的利器,這個後面會提到。

如何做題

四種情況(針對每一條生成式):

  • 如果遇到了終結符,加入左部非終結符的first集然後停止

  • 如果遇到了推不出空的非終結符,將這個非終結符的first集加入左部非終結符的first集然後停止

  • 如果遇到了推得出空的非終結符,將這個非終結符的first集除了空以外的元素加入左部非終結符的first集然後停止

  • 如果右邊全能推出空,或者直接就是空,那麼將空加入左部非終結符的first集

  • 注意:由於第二、三種情況中,遇到的非終結符的first集可能還沒求完,所以應該反覆掃描這些產生式重複以上動作,直到first集不會變動爲止
    在這裏插入圖片描述
      比如對於這個題,先看第一條式子,可以發現a ^ (都是終結符,都符合第一種情況,所以遇到就停,沒有其他情況了,所以first(S)={a^(}first(S)=\{a,\hat{},(\},接下來繼續看第二條式子!S剛纔可以發現,推不出空串,所以就符合第二種情況,將S的first集全部加入T就行,所以first(T)={a^(}first(T)=\{a,\hat{},(\},對於第三條式子,它的第一個式子符合第一種情況得到,,第二個式子符合第四種情況,加入空串,所以就是first(T)={,ε}first(T')=\{,,ε\}

  第三種情況也很常見,就靠大家自己摸索啦~,比如S->ABCD,ABC都能推出空串,D不能推出空串的話,那就是把ABCD的first集都給S,如果ABCD全能推出空串,那還得在得到所有first集的基礎上加入空。

 

5.求FOLLOW集

原理說明

  既然已經有了first集,我已經能知道我能推出啥式子了不就夠了嘛,爲什麼還要來個這個FOLLOW呢?因爲非終結符可能推出空!回想我們在第0點說過的目標,我們希望能在知道非終結符和終結符的情況下就能確定哪個式子,但是如果當前非終結符能推出空串的話,那麼這個字符有可能不是這個非終結符推出來的,而是它後面的那一坨推出來的,比如S->AB,讓你推出的句子是b,然後B->b,假如A的first集沒有b,你不能說這個句子拼不出來,因爲有可能A主動讓賢讓B繼續頂上,所以FOLLOW集的存在,就是爲了這種推出空的情況~那FOLLOW集咋求呢?顧名思義,FOLLOW集就是能直接跟在當前非終結符屁股後面的第一個終結符,所以對於每一個非終結符,要做的就是找到所有包含它的右部,然後把他後面的那部分的FIRST集弄出來,舉個栗子,如果S->ABCD,如果想求出B的FOLLOW集,那就找CD的first集。
  還有一種情況,如果它後面的部分能推出空,那麼它左部的follow集也要加入它的follow集。爲啥呢,因爲比如S->ABCD,對於B的follow集,如果CD能推出空,那麼能跟在S後面的字符同樣也能跟在B後頭

如何做題

首先開局直接將#放入開始符S的follow集中,這個東西表示句子的結束

接下來兩種情況(針對每一個非終結符):

  • 找到包含這個非終結符的右部,把這個非終結符右部的first集求出來,比如S->ABCD,對於B來說CD就是它的右部,注意不要加入空
  • 如果包含這個非終結符的右部能推出空,那麼左部的follow集也加入當前first集
  • 注意:由於第二種情況中,左部非終結符的follow集可能還沒求完,所以應該反覆掃描這些產生式重複以上動作,直到follow集不會變動爲止
    在這裏插入圖片描述
    再拿這個做例子
    上來直接把#推到S的follow集,follow(S)={#}follow(S)=\{\#\}
    對於S,出現它的右部是第2、3條式子,它在這兩條式子的右部都是T’,所以剛纔我們直接將T’first集弄過來,first(T)={,ε}first(T')=\{,,ε\},空不要,因爲右部能推出空集,所以我們要加入左部T的follow集,發現T的follow集還不知道,所以先待命,目前是follow(S)={#,}follow(S)=\{\#,,\}
    然後對於T,T出現的只有第一條,S->(T),右部是),非終結符的first集就是自己,所以follow(T)={)}follow(T)=\{)\}
    然後對於T’,T’出現在2 3條,右邊都是啥都沒有,所以直接加入follow(T)和follow(T’),follow(T’)就是自己,啥都不用動,加入T的follow集就是剛纔說的),所以follow(T)={)}follow(T')=\{)\}
    待命的S現在可以加入T的follow記集了,然後就會變成follow(S)={#,)}follow(S)=\{\#,,,)\}

 

6.求SELECT集(可以不弄,視你的教材決定)

原理說明

要注意我們需要乾的事情是什麼,是希望能夠知道非終結符和終結符就能確定哪一條式子,而select集的作用就是,比如select(S->A)={a},那麼當現在遇到的非終結符是A,終結符是a,就能確定現在要用的式子是S->A,所以select集是針對每一條式子來說,每一條式子的select集首先是右部的first集,如果右部能推出空集,那麼很顯然左部的follow集也能作爲當前非終結符能形成的句子的第一個字符。

如何做題

兩步(對於每一條產生式):

  • 加入產生式右部的first集
  • 如果右部能推出空串,去掉空,然後加入左部的follow集
    舉個例子,就是如果S->AB,那麼就是求出AB的first集,如果AB能推出空(其實就是每個非終結符都能推出空的話),那麼就加入S的follow集,就得到了這條產生式的select集

 

7.判斷是不是LL(1)文法

原理說明

再次重申我們需要乾的事情是什麼,是希望能夠知道一個非終結符一個終結符就能確定哪一條式子。

用select集的方法:
而select集又是得到一個非終結符和一個終結符就能確定式子,所以如果同一個非終結符得到的式子的select集有交集的話,那麼它們相遇的時候就不能唯一確定一個式子,變成倆小夥子都行了,出現了二義性。所以我們就只用判斷是否同一個非終結符的任意兩個式子的select集交集是否爲空,如果全爲空則是LL(1),否則不是。

不用select集的方法:
LL(1)文法要保證的就是說,一個非終結符一個終結符就能確定哪一條式子,所以首先對於同一個非終結符,它的所有產生式右部的first集是否有交集,因爲如果有交集的話,那麼就不能判斷到底用哪個式子了。還有一種情況就是,如果它的某一種產生式能推出空,那麼就需要判斷A的first集和follow集是否交集爲空。因爲如果A能推出空的話也有兩種情況,一種是A不推出空得到的式子,這些式子的第一個字符就是first(A),也有可能推出空,這種情況就是Afollow集的情況, 兩者有交叉,就不知道是A選了自己不推空串的產生式繼續發光發熱, 選擇空讓位,選自己推出空了,說白了也是避免二義。

如何做題

用select集的方法:
對於相同左部的式子,如果存在任何兩個式子的select集交集不爲空,就不是LL(1)文法,否則是。

不用select集的方法:
首先判斷相同左部的式子,右部的first集是否存在交集。如果某個非終結符能推出空串,那麼判斷該非終結符的first集和follow集是否存在交集。
如果存在交集,則不是LL(1)文法,否則是。
 

8.預測分析表

原理說明

這個其實就是寫一個表來說明哪個非終結符遇到哪個終結符用哪個式子,如果有select集就方便了,每個式子,左部和select集中的所有元素相遇就說明用這個式子。沒有的話,就需要對於每個式子來說,先求出first集,如果這個能推出空,再給這個左部非終結符的follow集-該非終結符的表格相應位置加入這個式子。

如何做題

用select集的方法:
對着select集,表格中非終結符那一欄找到左部對應的非終結符位置,然後在終結符那一欄找到select集裏的元素,然後對相應的位置填上這個式子。

不用select集的方法:
對於每個式子,求出右部的first集,然後對左部和first集內的元素相應的位置填上這個式子,如果右部能推出空集,那麼在左部的follow集和左部的相應位置填上這個式子。

 

9.判斷句子是否能被接受

原理說明+如何做題

起步就只有一個開始符,然後對照要匹配的句子。然後一個個字符對過來,如果遇到非終結符對終結符,就去預測分析表找對應的式子代進去,然後繼續比對。 這裏的做題格式就要根據你的課本要求而定啦~

 

四、結語

  如果你能看到這裏那我相信你一定已經學會了如何進行自頂向下分析~這篇文章花費了我大量的時間和精力,非常感謝你的陪伴,希望能給你帶來幫助~

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