kl中的錯(cuò)誤處理
之前我一直說(shuō)錯(cuò)誤處理是kl里的軟肋,由于一直在關(guān)注一些具體功能的改進(jìn),也沒(méi)有對(duì)
這方面進(jìn)行改善。
我這里所說(shuō)的錯(cuò)誤處理,包括語(yǔ)言本身和作為庫(kù)本身兩方面。
語(yǔ)言本身指的是對(duì)于腳本代碼里的各種語(yǔ)法錯(cuò)誤、運(yùn)行時(shí)錯(cuò)誤等的處理。好的處理應(yīng)該
不僅僅可以報(bào)告錯(cuò)誤,而且還能忽視錯(cuò)誤讓處理過(guò)程繼續(xù)。
而把kl解釋器作為一個(gè)庫(kù)使用時(shí),庫(kù)本身也應(yīng)該對(duì)一些錯(cuò)誤情況進(jìn)行報(bào)告。
整體上,kl簡(jiǎn)單地通過(guò)回調(diào)函數(shù)指針來(lái)把錯(cuò)誤信息傳給庫(kù)的應(yīng)用層。而因?yàn)槲蚁M麄€(gè)
kl實(shí)現(xiàn)的幾層(詞法分析、語(yǔ)法分析、符號(hào)表、解釋器等)可以盡可能地獨(dú)立。例如雖然語(yǔ)
法分析依賴于詞法分析(依賴于詞法分析提供的接口),但是因?yàn)樵~法分析并不對(duì)語(yǔ)法分析
依賴,所以完全可以把詞法分析模塊拿出來(lái)單獨(dú)使用。所以,在日志方面,我?guī)缀鯙槊恳粚?br>都附加了個(gè)error_log函數(shù)指針。
而用戶層在通過(guò)kllib層使用整個(gè)庫(kù)時(shí),傳入的回調(diào)函數(shù)會(huì)被間接地傳到詞法分析層。
實(shí)際上,當(dāng)kl作為一個(gè)庫(kù)時(shí),kllib正是用于橋接庫(kù)本身和用戶層的bridge。
另一方面,語(yǔ)言本身在處理錯(cuò)誤的腳本代碼時(shí),錯(cuò)誤分為幾大類型層次:
1.詞法錯(cuò)誤 lex error,如掃描字符串出錯(cuò)
2.語(yǔ)法錯(cuò)誤 syntax error,整理語(yǔ)法樹(shù)時(shí)出錯(cuò)
3.運(yùn)行時(shí)錯(cuò)誤 runtime error,在解釋執(zhí)行代碼時(shí)出錯(cuò)
4.庫(kù)錯(cuò)誤 lib error,發(fā)生在kllib這個(gè)bridge層的錯(cuò)誤
kl在報(bào)告錯(cuò)誤信息時(shí),會(huì)首先附加該錯(cuò)誤是什么類型的錯(cuò)誤。
這里最麻煩的是語(yǔ)法錯(cuò)誤的處理。因?yàn)檎Z(yǔ)法分析時(shí)發(fā)生錯(cuò)誤的可能性最大,錯(cuò)誤類型也
有很多。例如你少寫(xiě)了分號(hào),少寫(xiě)了括號(hào),都會(huì)導(dǎo)致錯(cuò)誤。這個(gè)階段發(fā)生錯(cuò)誤不僅要求能準(zhǔn)
確報(bào)告錯(cuò)誤,還需要忽略錯(cuò)誤讓整個(gè)過(guò)程盡量正確地下去。
語(yǔ)法分析階段最根本的就是符號(hào)推導(dǎo)(單就kl的實(shí)現(xiàn)而言),所謂的符號(hào)推導(dǎo)是這樣一
個(gè)過(guò)程,例如有賦值語(yǔ)句:a = 1;語(yǔ)法分析時(shí),語(yǔ)法分析器希望(所謂的推導(dǎo))等號(hào)后面會(huì)
是一個(gè)表達(dá)式,當(dāng)分析完了表達(dá)式后,又希望接下來(lái)的符號(hào)(token)是分號(hào)作為該語(yǔ)句的結(jié)
束。
所以,klparser.c中的syn_match正是完成這個(gè)過(guò)程。每次你傳入你希望的符號(hào),例如
分號(hào),該函數(shù)就檢查詞法分析中當(dāng)前符號(hào)(token)是否是分號(hào)。當(dāng)然,對(duì)于正確的腳本代碼,
它是一個(gè)分號(hào),但是如果是錯(cuò)誤的代碼,syn_match就會(huì)打印諸如:
>>syntax error->unexpected token-> ....
即當(dāng)前的符號(hào)是不被期望的。
上面完成了錯(cuò)誤的檢測(cè)。對(duì)于錯(cuò)誤的忽略,或者更高級(jí)點(diǎn)地對(duì)錯(cuò)誤的校正,kl中處理得
比較簡(jiǎn)單,即:直接消耗掉這個(gè)不是期望中的符號(hào)。例如:
a = 1 /* 忘加了分號(hào) */
b = 1;
上面兩句代碼被處理時(shí),在處理完a=1后,發(fā)現(xiàn)當(dāng)前的符號(hào)(token)b(是一個(gè)ID token)不
是期望(expect)中的分號(hào),首先報(bào)告b不是期望的符號(hào),然后kl直接掠過(guò)b,獲取下個(gè)符號(hào)=。
然后處理a=1這個(gè)過(guò)程結(jié)束。當(dāng)然,下次處理其他語(yǔ)句時(shí),發(fā)現(xiàn)=符號(hào),又會(huì)繼續(xù)發(fā)生錯(cuò)誤。
錯(cuò)誤信息中比較重要的還有行號(hào)信息。之前kl這方面一直存在BUG,我在寫(xiě)貪食蛇例子
的時(shí)候每次新加代碼都不敢加太多。因?yàn)榻忉屍鲌?bào)告的錯(cuò)誤行號(hào)總是錯(cuò)誤的,我只能靠有沒(méi)
有錯(cuò)誤來(lái)找錯(cuò)誤,而不能通過(guò)錯(cuò)誤信息找錯(cuò)誤。
行號(hào)信息被保存在詞法分析狀態(tài)中(lexState:lineno),語(yǔ)法分析中獲取token時(shí),會(huì)取
出當(dāng)前的行號(hào),保存到語(yǔ)法樹(shù)樹(shù)節(jié)點(diǎn)中。因?yàn)榘ń忉屇K都是基于樹(shù)節(jié)點(diǎn)的,所以詞法分
析語(yǔ)法分析解釋器三層都可以準(zhǔn)確報(bào)告行號(hào)。
但是之前解釋器報(bào)告的行號(hào)始終很詭異。癥結(jié)在于我在載入腳本代碼文件時(shí),以rb方式
載入,即二進(jìn)制形式。于是,在windows下,每行文本尾都會(huì)有\(zhòng)r\n兩個(gè)字符。而在詞法分
析階段對(duì)于行號(hào)的增加是:
case '\n':
case '\r':
ls->lineno ++;
不同OS對(duì)于文本文件的換行所添加的字符都不一樣,例如windows用\r\n,unix系用\n
,貌似Mac用\r。所以,詞法分析這里寫(xiě)應(yīng)該可以準(zhǔn)確地處理行號(hào)。
但是對(duì)于windows,這里就直接將行號(hào)增加了兩次,所以也就導(dǎo)致了行號(hào)出錯(cuò)的問(wèn)題。查
了下文檔,發(fā)現(xiàn)以文本方式打開(kāi)文件("r"),調(diào)用fread函數(shù)讀入文件內(nèi)容時(shí),就會(huì)自動(dòng)把
\r\n替換為\n。
代碼改后,又出問(wèn)題。這個(gè)時(shí)候,通過(guò)fseek和ftell獲取到的文件尺寸,貌似包括了
\r\n,而fread出來(lái)的內(nèi)容卻因?yàn)樘鎿Q\r\n為\n而沒(méi)有這么多。
不過(guò)文件載入不屬于kl庫(kù)本身,kl只接收以字符串形式表示的腳本代碼,所以也算不了
核心問(wèn)題。
同樣,最新代碼可以從google SVN獲取。當(dāng)然,我也在考慮是否換一個(gè)新的項(xiàng)目地址。