前兩個月在公司接到項目抛人,要做一個簡易的Python腳本解釋器。工具穷遂,構(gòu)架都是從0碼起函匕,也找不到開源的項目參考,只好閉門造車?yán)豺胶凇;撕荛L時間找能寫Parser 的庫中剩,比較起來pyparsing比較好理解忌穿,所以就從這里碼起了。
當(dāng)時看了一些所謂教你做編譯器的教程结啼,滿心歡喜的看完四則運(yùn)算掠剑,期待后面的內(nèi)容能講到條件句的解析。結(jié)果郊愧,講到四則運(yùn)算之后就沒有然后了朴译,感覺這后面可能是個大坑井佑。之后查文檔,自己寫代碼眠寿,重復(fù)修改躬翁,終于做出條件句的分詞器。
個人感覺對條件句做分詞盯拱,基本用到了pyparsing所有常用功能盒发。所以把我的代碼放上來,能幫后面做相同工作的同學(xué)省些上手的時間狡逢。
解析條件句主要考慮以下幾塊:
1.條件語句:
主要是大于小于等于這種邏輯判斷
2.執(zhí)行語句
要支持賦值宁舰,function,四則運(yùn)算這些格式
3.IF∩莼搿/÷琛IFELSE格式
搭好架子
IF(條件){執(zhí)行}
IF(條件){執(zhí)行}ELSE{執(zhí)行}
4.{執(zhí)行}內(nèi)支持嵌套
那么我們就開始了
碼之前先把庫加上
from pyparsing import *
1.條件語句
先想想條件句長什么樣,可能是和數(shù)值比較(A>5)雀彼,也可能是和變量比較(A==B)
所以符號右側(cè)可能是字母也是數(shù)字印荔。
在這里我們用Word(alphanums)涵蓋可能是數(shù)字也可能是字母的情況。
我們把符號兩邊比較的東西叫做Variable详羡,那么它的語法是
Variable=Word(alphanums)
這樣不管要比的是∪月伞ABC還是123就都不怕了
下面來定義邏輯運(yùn)算符,我們把符號稱作Operator,它的語法定義如下
Operator=Literal("==")^Literal("!=")^Literal(">")^Literal("<")^Literal(">=")^Literal("<=")
這一行說的是Operator可以是“==”实柠,“>”,"<"...這些符號里的任意一個水泉。因為是特殊符號,所以要用Literal()擴(kuò)好窒盐,中間用^連接草则,相當(dāng)于OR。
這些組件都做好之后蟹漓,就可以把它們拼起來解析完整的條件句啦炕横,我們叫它condition。
condition=Variable+Operator+Variable
現(xiàn)在可以用它解析∑狭!A==C這樣的條件句份殿,但是如果有用戶寫成 A∷越弧==∏涑啊B可能會出錯,所以你可以預(yù)留出一些可能出現(xiàn)的空格夫壁,像這樣:
condition=Variable+Optional(" ")+Operator+Optional(" ")+Variable
Optional( )里面可以寫一些可有可無的內(nèi)容拾枣,不影響整體。
此時,解析C==0的結(jié)果是這樣的
[ 'C', ‘==’梅肤, ‘0’ ]
如果你覺得這樣太瑣碎司蔬,可以在condition最外層加一個combine(),就像這樣
condition=Combine(Variable+Optional(" ")+Operator+Optional(" ")+Variable)
此時得到的結(jié)果就是一個完整的語句
["C==0"]
值此條件句就完成了,你可以把它封到功能里隨用隨調(diào)
def Condition():
? ? ? ? ? ? Variable=Word(alphanums)#Variable pattern: number or alphabets
? ? ? ? ? ? Operator=Literal("==")^Literal("!=")^Literal(">")^Literal("<")^Literal(">=")^Literal("<=")#logical operator defined? ? ? ? ? ?
? ? ? ? ? ? condition=Combine(Variable+Optional(" ")+Operator+Optional(" ")+Variable)#condition pattern
??????????? return condition
2. 執(zhí)行語句
這里需要支持function調(diào)用姨蝴,設(shè)值俊啼,四則運(yùn)算這么幾類語句,用到的功能和上面條件句大同小異似扔,所以就不一一解釋了
def command():
### function:XXX()
llp="("
rrp=")"
command=Combine(Word(alphanums)+llp+Word(nums)+rrp)
###? setValue:C=0
Variable=Word(alphanums)
Value=Word(alphanums)
setValue=Combine(Variable+"="+Value)
### operation: A=3+B
Operator=Literal("+")^Literal("-")^Literal("*")^Literal("/")
operation=Combine(Variable+"="+Variable+Operator+Variable)
commandLine=OneOrMore(command^setValue^operation)
return commandLine
需要說明的是OneOrMore()這個功能吨些,從字面上看是一個或多個的意思。
commandLine=OneOrMore(command^setValue^operation)
這句可以解釋為你在IF執(zhí)行的部分可以是一句過多句命令或賦值或運(yùn)算
這樣 IF? C<2:
????????????? speed(0);
????????????? B=6
????????????? C=2+B
縮進(jìn)的三行內(nèi)容就都可以被提取出來
3.IF/IFELSE 格式
先寫IF的格式
ifSt="if"+condition()+Suppress(":")
這里用到Suppress()炒辉,是希望分詞器不要抓取 “『朗:”,因為不是什么關(guān)鍵內(nèi)容黔寇,可以忽略
這樣你的輸出是 [ 'if' ,'C==0']
同理
elseSt="else"+Suppress(":")
到這里你就可以做出基本的IF/IFELSE語法了
indentStack=[1]
indentBody=indentedBlock(command),indentStack)
if_Grammar=ifSt+indentBody+Optional(elseSt+indentBody)
你會發(fā)現(xiàn)我把command()放到縮進(jìn)功能indentedBlock里了偶器,else變成可選的一項,這樣語法可以同時應(yīng)付IF,IFELSE
4. {執(zhí)行}內(nèi)支持嵌套
這里要用到遞歸缝裤,修改上面的代碼
indentStack=[1]
IF_ALL=Forward()
indentBody=indentedBlock(execution+Group(Optional(IF_ALL)),indentStack)
IF_ALL<<ifSt+indentBody+Optional(elseSt+indentBody)
Forward()為你可能出現(xiàn)嵌套的位置去占一個位,所以要把它加在縮進(jìn)的位置里屏轰,但嵌套不是每次都會出現(xiàn),所以第三行IF_ALL外面套了Optional().
為了讓輸出的結(jié)果也體現(xiàn)出嵌套的格式憋飞,需要用Group()把他們括起來霎苗,這樣你的輸出是:
[ IF 1 [ IF2?? [ IF 3]? ]]
為了實現(xiàn)完整的遞歸,記得寫
IF_ALL<<ifSt+indentBody+Optional(elseSt+indentBody)
這樣榛做,當(dāng)前IF可以嵌套在更高一層IF里
最后別忘了把語法封到function里唁盏,名為IF_grammar()
使用你編寫的語法
input='''
if M>=1:
A=3+2
if N==2:
B=2
'''
parseTree=IF_ALL().scanString(input)
parseTree=list(parseTree)
這樣你可以看到解析的列表。