- 自制編程語(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年出版。
- 流量的秘密:Google Analytics網(wǎng)站分析與優(yōu)化技巧(第2版)
- Java從入門到精通(第5版)
- Bootstrap Essentials
- Responsive Web Design by Example
- Building Android UIs with Custom Views
- Visual Foxpro 9.0數(shù)據(jù)庫(kù)程序設(shè)計(jì)教程
- 動(dòng)手學(xué)數(shù)據(jù)結(jié)構(gòu)與算法
- Hadoop大數(shù)據(jù)分析技術(shù)
- 微前端設(shè)計(jì)與實(shí)現(xiàn)
- Google Adsense優(yōu)化實(shí)戰(zhàn)
- Android應(yīng)用開發(fā)攻略
- R High Performance Programming
- Head First Go語(yǔ)言程序設(shè)計(jì)
- Python服務(wù)端測(cè)試開發(fā)實(shí)戰(zhàn)
- Python Machine Learning / Second Edition