語(yǔ)法制導(dǎo)翻譯、中間代碼生成、目標(biāo)代碼生成在很多時(shí)候并不存在嚴(yán)格的劃分。對(duì)于目標(biāo)
代碼是某個(gè)簡(jiǎn)單的虛擬機(jī)代碼而言,中間代碼完全可以就是目標(biāo)代碼。中間代碼生成中結(jié)
合了語(yǔ)法制導(dǎo)翻譯,講述了大部分常規(guī)的編程語(yǔ)言結(jié)構(gòu)是怎樣被翻譯成一種接近目標(biāo)代碼
的形式(所謂的中間代碼形式)。本身,匯編代碼就是對(duì)應(yīng)于機(jī)器碼的一種字符表示形式,
而中間代碼的大部分表示形式--三地址碼,也是一種接近匯編代碼的形式。
簡(jiǎn)單來(lái)說(shuō),詞法分析階段將字符整理為單詞;語(yǔ)法分析則將這些代碼整理為一種層次結(jié)構(gòu)
(識(shí)別了程序代碼要表達(dá)的意思);那么,在接下來(lái)的階段里,則是將這些層次結(jié)構(gòu)翻譯
為線(xiàn)性結(jié)構(gòu)。也就是類(lèi)似于匯編代碼這種格式。這種格式容易被機(jī)器識(shí)別:機(jī)器只需要順
序地一條一條地取指令并執(zhí)行之即可。這種簡(jiǎn)單直接性也使得要實(shí)現(xiàn)類(lèi)似的虛擬機(jī)變得容
易。
翻譯過(guò)程并不需要先生成語(yǔ)法樹(shù),在語(yǔ)法分析階段的語(yǔ)法識(shí)別過(guò)程中,即可以對(duì)應(yīng)地翻譯。
因?yàn)闊o(wú)論是自頂向下還是自底向上的語(yǔ)法分析,都可以先去識(shí)別葉子節(jié)點(diǎn)。在自頂向下中,
可以使用語(yǔ)法樹(shù)(并不真實(shí)存在)的后續(xù)遍歷,使得葉子節(jié)點(diǎn)先于父節(jié)點(diǎn)翻譯;而在自底
向上的分析中,因?yàn)槠浔旧砭褪窍茸R(shí)別葉子節(jié)點(diǎn)(所謂的規(guī)約),所以可以更自然地翻譯。
因?yàn)槲乙彩窍雽?shí)踐下這些東西,所以還是使用lex/yacc來(lái)進(jìn)行練習(xí),省得自己去寫(xiě)詞法和
語(yǔ)法分析。不過(guò)在使用yacc的過(guò)程中,經(jīng)常會(huì)出現(xiàn)一些shift/reduce conflicts的警告/錯(cuò)
誤,解決這些問(wèn)題也費(fèi)了不少時(shí)間。不過(guò),也可能是我對(duì)LALR細(xì)節(jié)不熟悉,加之于文法本
身寫(xiě)的有問(wèn)題,才弄得如此折騰。現(xiàn)在我覺(jué)得上下文無(wú)關(guān)文法在整個(gè)編譯原理理論中非常
重要。一個(gè)好的文法可以準(zhǔn)確無(wú)誤地描述一種編程語(yǔ)言的語(yǔ)法,還可以指導(dǎo)編譯器的開(kāi)發(fā)。
當(dāng)然,大部分常規(guī)的語(yǔ)言都可以找到現(xiàn)成的文法。
例子程序構(gòu)造了一個(gè)簡(jiǎn)單的翻譯程序,支持簡(jiǎn)單的算術(shù)表達(dá)式、整數(shù)變量、if、while、以
及僅用于if和while的邏輯表達(dá)式。為了省力,虛擬機(jī)用的是《編譯原理與實(shí)踐》中現(xiàn)成的。
目標(biāo)代碼也就直接是該虛擬機(jī)對(duì)應(yīng)的代碼。該虛擬機(jī)主要有5個(gè)寄存器:指令指針寄存器、
2個(gè)累加寄存器、全局變量基址寄存器、臨時(shí)變量基址寄存器。這里的臨時(shí)變量不同于編
程語(yǔ)言說(shuō)的臨時(shí)變量,它是表達(dá)式計(jì)算的臨時(shí)值,例如a+b+c,a+b的結(jié)果值就可以被實(shí)現(xiàn)
為存儲(chǔ)到一個(gè)臨時(shí)值中。
對(duì)于算術(shù)表達(dá)式,其實(shí)翻譯起來(lái)很簡(jiǎn)單。主要是if/while和邏輯表達(dá)式的翻譯。邏輯表達(dá)
式的翻譯例子中我甚至沒(méi)有處理短路代碼:a && func(1)中如果已經(jīng)計(jì)算出a為false了,
就沒(méi)必要計(jì)算func(1)了。這可能是受限于yacc,暫不深究。對(duì)于if和while,則主要涉及
到所謂的“回填”技術(shù)。
回填主要是應(yīng)對(duì)向前跳轉(zhuǎn)這種問(wèn)題。例如在if的代碼生成中,需要測(cè)試if的邏輯表達(dá)式的
真假,如果為假,則需要跳轉(zhuǎn)到then-part之后。這里的then-part也就是if為真時(shí)要執(zhí)行
的代碼序列。而這個(gè)跳轉(zhuǎn)指令具體要跳到哪里,則需要在生成完then-part之后才能確定。
回填的解決方法,就是預(yù)留下這個(gè)跳轉(zhuǎn)指令的位置,等到then-part生成完了,得到了具
體的跳轉(zhuǎn)位置,再回去填寫(xiě)那個(gè)跳轉(zhuǎn)指令。
在這個(gè)問(wèn)題上,yacc也讓我折騰了一番。在if文法中:
selection_statement
: IF '(' logical_or_expr ')' {
// 本來(lái)想在這里預(yù)留這個(gè)跳轉(zhuǎn)指令的位置
} statement %prec IFX {
}
結(jié)果,yacc又給我conflicts和never reduced之類(lèi)的警告,并且最終生成的代碼也不正常
(果然是無(wú)法忽略的警告)。看起來(lái),yacc似乎不支持在文法內(nèi)部添加action。通過(guò)一個(gè)
空文法符號(hào)效果一樣。對(duì)于這個(gè)問(wèn)題,我甚至莫名其妙地在某個(gè)晚上的夢(mèng)里當(dāng)面問(wèn)了yacc
的作者。他肯定地告訴我:支持,當(dāng)然支持(中文)。今天仔細(xì)地在yacc文檔里找了下,
還真支持。而且對(duì)于空符號(hào)的使用,似乎也有些規(guī)則:$Sign: {action }。
后來(lái)解決這個(gè)問(wèn)題的方法,算是我取巧了:
selection_statement
: IF '(' logical_or_expr IfBracket statement %prec IFX { ....}
IfBracket
: ')' {
// 邪惡地利用了這個(gè)括號(hào)
}
另外,因?yàn)樾枰С智短椎膇f/while,還專(zhuān)門(mén)使用了一個(gè)棧,用于保存這些需要回填的預(yù)留地址。
下載例子