OK,書接上文,今次這篇博客是准備說語法分析。
其實詞法分析和語法分析可以說是一體的。詞法分析用於分析輸入的單詞,將其一一分門別類。語法分析分析已經分門別類好的單詞,看其組成的句子是否符合語言的文法。
首先,先確定一個簡單的文法:
S -> if ( M ) { N } S -> N M - > P == P p -> id | digit N -> int id ; | int id = num ; | Q; Q - > id = E E - > E + T | T T - > id | digit| ( E )
這是一個非常簡單的java風格文法,大家一眼就能看出來它的意思了。這段文法會匹配如下的句子:
if(1 == 1) { int p = 1; }
就如從正則表達式可以構造出詞法分析器一樣,也存在語法分析器的生成器之類的工具,可以用來構造預測分析器。相關的算法有LL(1)、LR(1)等。本文涉及的文法比較簡單,符合LL(1)文法,不需要用到LR(1)這樣強大而復雜的推理。因而本文准備用遞歸下降算法來構造預測分析器。
LL(1)文法代表從左到右分析、最左推導和超前查看一個符號。為了確認一個文法是否是LL(1)文法需要構造LL(1)分析表,而為了構造LL(1)分析表又需要計算文法的FIRST和FOLLOW集合(詳細的算法可以自行百度,這里就不列出)。
記得第一次看LL(1)描述時看的頭暈眼花,雲里霧里的。后來自己干脆動手寫程序時,就突然一下子明白了,為了要有這些步驟,這些步驟到底用來做什么的?
直接用例子來說明,以S -> if ( M ) { N }、S -> N 來說,我們要如何寫匹配這段文法的程序了?
1.獲取下一個字符t;
2.根據t,判斷是走if ( M ) { N }這條路了,還是走N這條路;
3.如果t == 'if',而N的首字符沒有'if'的情況,那就妥妥的走if ( M ) { N },反之也一樣;
4.如果N -> if blabla,那么我就傻眼了,到底應該怎么判斷了?
如果真出現4中的情況,那么這個文法就不是LL(1)文法。因為從左到右只超前讀一個字符無法推導出一個唯一的結果。
事實上,無論是消除左遞歸、計算FIRST集合、計算FOLLOW集合都是為了確認當前文法在出現分岔路時,每個分支的首個終結字符不相同。
本文的文法比較簡單,直接消除左遞歸后得到滿足LL(1)的文法:
S -> if (M) {N} S -> N M -> P == P P -> id | digit N -> N1 N11 | E N1 -> int id N11 -> = num | null E -> TE1 E1 -> + T | T T -> id | digit | (E)
然后直接根據這個就可以得到下面的程序啦:
def S(): #下一個字符,用於預測 token = seeToPretict(); if(token == 'if'): eat('if'); eat('('); M(); eat(')'); eat('{'); N(); eat('}'); pass elif(token == 'id' or token == 'digit'): N(); else : #報錯 Syntaxerror(); print 'complete'; pass def M(): P(); eat('=='); P(); pass def P(): #下一個字符 token = getToken(); if(token != 'id' and token != 'digit'): syntaxerror(); pass def N(): token = seeToPretict(); if(token == 'int'): N1(); N11(); eat(';'); elif(token == 'id' or token == 'digit'): E(); eat(';'); else: syntaxerror(); pass def N1(): eat('int'); eat('id'); def N11(): token = seeToPretict(); if(token == '='): eat('='); eat('digit'); pass def E(): T(); E1(); def E1(): token = seeToPretict(); if(token == '+'): eat('+'); T(); elif(token == 'id' or token == 'digit'): T(); else: syntaxerror(); def T(): token = getToken(); if(token != 'id' and token != 'digit'): syntaxerror();