官术网_书友最值得收藏!

  • 自制編程語(yǔ)言
  • (日)前橋和彌
  • 1658字
  • 2021-11-24 18:03:50

2.4 少許理論知識(shí)——LL(1)與LALR(1)

2.3.2節(jié)中手寫的解析器會(huì)對(duì)記號(hào)進(jìn)行預(yù)讀,并按照語(yǔ)法圖的流程讀入所有記號(hào)。這種類型的解析器叫作LL(1)解析器。LL(1)解析器所能解析的語(yǔ)法,叫作LL(1)語(yǔ)法。

采用LL(1)語(yǔ)法,當(dāng)然能制作出對(duì)應(yīng)的編程語(yǔ)言來(lái)。比如Pascal的語(yǔ)法就是LL(1)。

但是看了代碼清單2-9就能明白,LL(1)解析器在語(yǔ)法上需要非終結(jié)符與解析器內(nèi)部的函數(shù)一一對(duì)應(yīng)。也就是說(shuō),只看第一個(gè)進(jìn)入的記號(hào),還無(wú)法判斷需不需要繼續(xù)往下讀取,也不能知道當(dāng)前非終結(jié)符究竟是什么。

比如在Pascal中,goto語(yǔ)句使用的標(biāo)簽只能是數(shù)字,這樣限制的原因是,如果像C語(yǔ)言一樣允許英文字母作為標(biāo)識(shí)符的話,讀入第一個(gè)記號(hào)時(shí),就沒(méi)有辦法區(qū)分這個(gè)記號(hào)究竟是賦值語(yǔ)句的一部分,還是標(biāo)簽語(yǔ)句的一部分。因?yàn)闊o(wú)論賦值語(yǔ)句還是標(biāo)簽語(yǔ)句,開始的標(biāo)識(shí)符是一樣的。由此可知,LL(1)語(yǔ)法所做出的解析器都比較簡(jiǎn)單,語(yǔ)法能表達(dá)的范圍比較狹窄。

那么,在把計(jì)算器的BNF改寫為語(yǔ)法圖的過(guò)程中,一些敏銳的讀者可能已經(jīng)有了這樣的疑問(wèn):

不管是用BNF還是語(yǔ)法圖,都應(yīng)該只是表面上有區(qū)別,語(yǔ)法實(shí)現(xiàn)部分應(yīng)該是一樣的啊。但你寫的代碼怎么連算法都不一樣?我有種上當(dāng)了的感覺(jué)。

實(shí)際上確有此事,在把BNF置換為圖2-5所示的語(yǔ)法圖時(shí),我運(yùn)用了一個(gè)小手法。在BNF中語(yǔ)法規(guī)則是這樣的:

      expression /* 表達(dá)式的規(guī)則 */
              | expression ADD term /* 或 表達(dá)式 + 項(xiàng)目 */

而在實(shí)現(xiàn)遞歸下降分析時(shí),如果仍然按這個(gè)規(guī)則在parse_expression()剛開始就調(diào)用parse_expression(),會(huì)造成死循環(huán),一個(gè)記號(hào)也讀不了。

BNF這樣的語(yǔ)法稱為左遞歸,原封照搬左遞歸的語(yǔ)法規(guī)則,是無(wú)法實(shí)現(xiàn)遞歸下降分析的。

所以yacc生成的解析器稱為LALR(1)解析器,這種解析器能解析的語(yǔ)法稱為LALR(1)語(yǔ)法。LALR(1)解析器是LR解析器的一種。

LL(1)的第一個(gè)L,代表記號(hào)從程序源代碼的最左邊開始讀入。第二個(gè)L則代表最左推導(dǎo)(Leftmost derivation),即讀入的記號(hào)從左端開始置換為分析樹。而與此相對(duì)的LR解析器,從左端開始讀入記號(hào)與LL(1)解析器一致,但是發(fā)生歸約時(shí)(參看2.2.3節(jié)圖2-3),記號(hào)從右邊開始?xì)w約,這稱為最右推導(dǎo)(Rightmost derivation),即LR解析器中R字母的意思。

遞歸下降分析會(huì)按自上而下的順序生成分析樹,所以稱作遞歸“下降”解析器或遞歸“向下”解析器。而LR解析器則是按照自下而上的順序,所以也稱為“自底向上”解析器。

此外,LL(1)、LALR(1)等詞匯中的(1),代表的是解析時(shí)所需前瞻符號(hào)(lookahead symbol),即記號(hào)的數(shù)量。

LALR(1)開頭的LA兩個(gè)字母,是Look Ahead的縮寫,可以通過(guò)預(yù)讀一個(gè)記號(hào)判明語(yǔ)法規(guī)則中所包含的狀態(tài)并生成語(yǔ)法分析表。LALR也是由此得名的。

本章中實(shí)際制作的計(jì)算器是采用LL(1)語(yǔ)法作為解析器的,因?yàn)楸容^簡(jiǎn)單,所以適合手寫。如果是LALR(1)等LR語(yǔ)法的話,則更適合用yacc等工具自動(dòng)生成(這話可能已經(jīng)說(shuō)了太多遍了)。不過(guò),最近像ANTLR、JavaCC等一些采用LL(k),即預(yù)讀任意個(gè)記號(hào)的LL解析器也開始普及起來(lái)。

補(bǔ)充知識(shí) Pascal/C中的語(yǔ)法處理訣竅

前面提到Pascal采用的是LL(1)語(yǔ)法,但是在Pascal中,同時(shí)存在賦值語(yǔ)句和過(guò)程調(diào)用(C語(yǔ)言中是函數(shù)調(diào)用)。按照之前的介紹,這兩者都由同一類標(biāo)識(shí)符開始的,LL(1)解析器似乎無(wú)法區(qū)分。

在這個(gè)問(wèn)題上,Pascal并沒(méi)有從一開始就強(qiáng)行將其區(qū)分,而是逆轉(zhuǎn)思路,引入了一個(gè)同時(shí)代表“賦值語(yǔ)句或過(guò)程調(diào)用”的非終結(jié)符,然后在下一個(gè)記號(hào)讀入后再將其分開。這樣不用更改Pascal語(yǔ)法設(shè)計(jì),僅僅變化一下語(yǔ)法規(guī)則就解決了問(wèn)題。

在C語(yǔ)言中,如果是通過(guò)typedef命名的一些類型,其標(biāo)識(shí)符yacc(LALR(1)解析器)是無(wú)法解析的。比如C語(yǔ)言中可以簡(jiǎn)單地聲明為:

      Hoge *hoge_p = NULL;

其中的星號(hào)究竟是乘法運(yùn)算符還是指針?lè)?hào),單看Hoge這個(gè)標(biāo)識(shí)符很難直觀得出結(jié)論。

對(duì)此,C語(yǔ)言用了一個(gè)小訣竅,即在標(biāo)識(shí)符作為類型名被聲明的時(shí)候,會(huì)由語(yǔ)法分析器通知詞法分析器:此后凡遇到這個(gè)標(biāo)識(shí)符,不要將其作為標(biāo)識(shí)符,而作為類型名返回。

通過(guò)很多類似的訣竅,終于可以讓LL(1)/LALR(1)解析器解析Pascal/C語(yǔ)言了。C語(yǔ)言圖書K&R注4的附錄中,就記錄了BNF要經(jīng)過(guò)一些修正才可以輸入yacc的內(nèi)容。

注4即被稱為“C語(yǔ)言圣經(jīng)”的The C Programming Language一書,作者名縮寫為K&R[2]。中文版為《C程序設(shè)計(jì)語(yǔ)言(第2版·新版)》,機(jī)械工業(yè)出版社于2004年出版。

主站蜘蛛池模板: 兰西县| 萨迦县| 建阳市| 通城县| 郁南县| 丘北县| 天台县| 长岭县| 赫章县| 沛县| 台中县| 怀集县| 焉耆| 巴林右旗| 河北省| 阳东县| 延安市| 张北县| 扶风县| 凉山| 新平| 玛多县| 施甸县| 普安县| 林口县| 叶城县| 沾化县| 康平县| 威远县| 滦南县| 竹山县| 黄梅县| 盈江县| 玉山县| 崇阳县| 阆中市| 东兴市| 玛多县| 礼泉县| 邯郸县| 盈江县|