前两个月在公司接到项目,要做一个简易的Python脚本解释器。工具,构架都是从0码起,也找不到开源的项目参考,只好闭门造车啦。花了很长时间找能写Parser 的库,比较起来pyparsing比较好理解,所以就从这里码起了。
当时看了一些所谓教你做编译器的教程,满心欢喜的看完四则运算,期待后面的内容能讲到条件句的解析。结果,讲到四则运算之后就没有然后了,感觉这后面可能是个大坑。之后查文档,自己写代码,重复修改,终于做出条件句的分词器。
个人感觉对条件句做分词,基本用到了pyparsing所有常用功能。所以把我的代码放上来,能帮后面做相同工作的同学省些上手的时间。
解析条件句主要考虑以下几块:
1.条件语句:
主要是大于小于等于这种逻辑判断
2.执行语句
要支持赋值,function,四则运算这些格式
3.IF / IFELSE格式
搭好架子
IF(条件){执行}
IF(条件){执行}ELSE{执行}
4.{执行}内支持嵌套
那么我们就开始了
码之前先把库加上
from pyparsing import *
1.条件语句
先想想条件句长什么样,可能是和数值比较(A>5),也可能是和变量比较(A==B)
所以符号右侧可能是字母也是数字。
在这里我们用Word(alphanums)涵盖可能是数字也可能是字母的情况。
我们把符号两边比较的东西叫做Variable,那么它的语法是
Variable=Word(alphanums)
这样不管要比的是 ABC还是123就都不怕了
下面来定义逻辑运算符,我们把符号称作Operator,它的语法定义如下
Operator=Literal("==")^Literal("!=")^Literal(">")^Literal("<")^Literal(">=")^Literal("<=")
这一行说的是Operator可以是“==”,“>”,"<"...这些符号里的任意一个。因为是特殊符号,所以要用Literal()扩好,中间用^连接,相当于OR。
这些组件都做好之后,就可以把它们拼起来解析完整的条件句啦,我们叫它condition。
condition=Variable+Operator+Variable
现在可以用它解析 A==C这样的条件句,但是如果有用户写成 A == B可能会出错,所以你可以预留出一些可能出现的空格,像这样:
condition=Variable+Optional(" ")+Operator+Optional(" ")+Variable
Optional( )里面可以写一些可有可无的内容,不影响整体。
此时,解析C==0的结果是这样的
[ 'C', ‘==’, ‘0’ ]
如果你觉得这样太琐碎,可以在condition最外层加一个combine(),就像这样
condition=Combine(Variable+Optional(" ")+Operator+Optional(" ")+Variable)
此时得到的结果就是一个完整的语句
["C==0"]
值此条件句就完成了,你可以把它封到功能里随用随调
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. 执行语句
这里需要支持function调用,设值,四则运算这么几类语句,用到的功能和上面条件句大同小异,所以就不一一解释了
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执行的部分可以是一句过多句命令或赋值或运算
这样 IF C<2:
speed(0);
B=6
C=2+B
缩进的三行内容就都可以被提取出来
3.IF/IFELSE 格式
先写IF的格式
ifSt="if"+condition()+Suppress(":")
这里用到Suppress(),是希望分词器不要抓取 “ :”,因为不是什么关键内容,可以忽略
这样你的输出是 [ 'if' ,'C==0']
同理
elseSt="else"+Suppress(":")
到这里你就可以做出基本的IF/IFELSE语法了
indentStack=[1]
indentBody=indentedBlock(command),indentStack)
if_Grammar=ifSt+indentBody+Optional(elseSt+indentBody)
你会发现我把command()放到缩进功能indentedBlock里了,else变成可选的一项,这样语法可以同时应付IF,IFELSE
4. {执行}内支持嵌套
这里要用到递归,修改上面的代码
indentStack=[1]
IF_ALL=Forward()
indentBody=indentedBlock(execution+Group(Optional(IF_ALL)),indentStack)
IF_ALL<<ifSt+indentBody+Optional(elseSt+indentBody)
Forward()为你可能出现嵌套的位置去占一个位,所以要把它加在缩进的位置里,但嵌套不是每次都会出现,所以第三行IF_ALL外面套了Optional().
为了让输出的结果也体现出嵌套的格式,需要用Group()把他们括起来,这样你的输出是:
[ IF 1 [ IF2 [ IF 3] ]]
为了实现完整的递归,记得写
IF_ALL<<ifSt+indentBody+Optional(elseSt+indentBody)
这样,当前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)
这样你可以看到解析的列表。