任意表达式求值
任意表达式求值的算法是很常用的一个功能,虽然python有自带的eval,但必须基于python语法。而我们很多时候想创建自己的DSL,表达式求值都是核心的功能。
算符优先算符
实现这个功能,其实就是编译器的功能,有很多种实现方法,本文使用的是经典的算符优先算法。这个算符非常简洁优雅,可扩展性很强,不但可以计算表达式,如果适度扩展,完全可以实现图灵完备的DSL。
双栈
算符优先算法的第一个核心是双栈,一个算符栈,一个计算数栈。如果一个算符只能对常数计算就太弱了,所以本文算法支持了外挂的变量系统。
只需对isVar和getValue赋值,即可实现变量功能。
如果不定义isVar,则所有变量直接当作字符串处理
算符定义
算符优先算法的第二个核心是算符定义,虽然是经典算法,但或许有同学忘了也说不定。所以这里简单介绍一下。
任何一个算符(除了永不入栈的右括号)都有两个优先级,一个是栈外优先级,一个是栈内优先级。
这个算符的运算包括几个运算数。
以及实际执行计算的函数。
opDict={}
def addoptr(ch, outLev, inLev, func, parmNum=2):
obj= {'name':ch, 'out':outLev, 'in':inLev, 'func':func, 'parmNum':parmNum}
opDict[ch]= obj
addoptr('#', 1, 1, None)
addoptr('(', 90, 2, None)
addoptr(')', 2, None, None)
addoptr('&', 13, 14, lambda x: x[-1] and x[-2])
addoptr('|', 11, 12, lambda x: x[-1] or x[-2])
addoptr('~', 16, 17, lambda x: not x[-1],1)
addoptr('=', 22, 23, lambda x: x[-1]==x[-2])
addoptr('>', 22, 23, lambda x: x[-2]>x[-1])
addoptr('<', 22, 23, lambda x: x[-2]<x[-1])
addoptr('+', 31, 32, lambda x: x[-1]+x[-2])
addoptr('-', 31, 32, lambda x: x[-2]-x[-1])
addoptr('*', 41, 42, lambda x: x[-1]*x[-2])
addoptr('/', 41, 42, lambda x: x[-2]/x[-1])
注意大部分算符的栈内优先级都高于栈外优先级,是为了在连续遇到时可以优先计算前面的部分,比如3+5+2,第二个加号出现的时候,由于栈外优先级低于站内优先级,所以前面的3+5就被计算求值。
处理逻辑
处理逻辑相当简洁,见代码
def eval(this, cond):
stack=[]
this.pushoptr('#')
while True:
aword,kind= this.readWord(cond)
cond= cond[len(aword):].strip()
if kind=='#':
this.pushoptr('#')
break
elif kind=='optr':
this.pushoptr(aword)
else:
this.pushopnd(aword)
print(aword, cond)
print(this.optrStack)
print(this.opndStack)
return this.popopnd()
逐个从字符串中读取操作数和操作符,这里除了已经定义的操作符和空格之外,全都被定义为操作数。
未实现的功能
目前实现了逻辑、关系、计算表达式,可以直接适用于很多情况。
由于项目所需功能有限,所以并没有实现完整。未实现的功能有:
- 负号/减号 这种一个符号两个含义的操作符暂未支持
>=
,<=
,!=
这种双字符的符号暂未支持- 浮点数暂未支持
如有兴趣,添加这些功能并不难。
使用方法
简单用法(无变量)
a= exprEngine()
b= a.eval('3 + 5 = 8 & ~(7 < 2)')
print(b)
有变量的用法
a= thing()
a.addInfo('水位', '高')
b= a.eval('水位=高&3+5=8&~(7<2)')
#其中 a.eval的函数这样写
en= exprEngine(this.isattr, this.getValue)
return en.eval(cond)
完整代码
# 任意表达式的计算引擎
# 目前不支持负数、浮点数
# 不支持>=和<=等双字节符号
# 使用算符优先算法,双栈
opDict={}
def addoptr(ch, outLev, inLev, func, parmNum=2):
obj= {'name':ch, 'out':outLev, 'in':inLev, 'func':func, 'parmNum':parmNum}
opDict[ch]= obj
addoptr('#', 1, 1, None)
addoptr('(', 90, 2, None)
addoptr(')', 2, None, None)
addoptr('&', 13, 14, lambda x: x[-1] and x[-2])
addoptr('|', 11, 12, lambda x: x[-1] or x[-2])
addoptr('~', 16, 17, lambda x: not x[-1],1)
addoptr('=', 22, 23, lambda x: x[-1]==x[-2])
addoptr('>', 22, 23, lambda x: x[-2]>x[-1])
addoptr('<', 22, 23, lambda x: x[-2]<x[-1])
addoptr('+', 31, 32, lambda x: x[-1]+x[-2])
addoptr('-', 31, 32, lambda x: x[-2]-x[-1])
addoptr('*', 41, 42, lambda x: x[-1]*x[-2])
addoptr('/', 41, 42, lambda x: x[-2]/x[-1])
class exprEngine:
def __init__(this, isVar=None, getValue=None):
this.opndStack=[]
this.optrStack=[]
this.isVar= isVar
this.getValue= getValue
def readWord(this, cond):
cond= cond.strip()
if cond=='':
return '', '#'
if cond[0] in '()&|~+-*/=><':
return cond[0], 'optr'
part= ''
for ch in cond:
if ch in '()&|~+-*/=>< ':
break
part+=ch
return part, 'opnd'
def pushoptr(this, optr):
op= opDict[optr].copy()
if len(this.optrStack)==0:
this.optrStack.append(op)
return
opTop= this.optrStack[-1]
if op['out']> opTop['in']:
this.optrStack.append(op)
elif op['out']< opTop['in']:
this.popoptr()
# 这里递归
this.pushoptr(optr)
elif op['out']== opTop['in']:
# 消括号对,简单弹出
this.optrStack.pop()
def popoptr(this):
opTop= this.optrStack[-1]
a= opTop['parmNum']
if len(this.opndStack)<a:
raise Exception('操作数不足,可能有语法错误!')
ret= opTop['func'](this.opndStack[-a:])
this.opndStack= this.opndStack[:-a]
this.opndStack.append(ret)
this.optrStack.pop()
def pushopnd(this, opnd):
if this.isVar and this.isVar(opnd):
this.opndStack.append(this.getValue(opnd))
else:
if opnd.isdigit():
this.opndStack.append(int(opnd))
else:
this.opndStack.append(opnd)
def popopnd(this):
if len(this.opndStack)==1:
return this.opndStack[0]
else:
print(this.opndStack)
print(this.optrStack)
raise Exception('可能存在语法错误。')
def eval(this, cond):
stack=[]
this.pushoptr('#')
while True:
aword,kind= this.readWord(cond)
cond= cond[len(aword):].strip()
if kind=='#':
this.pushoptr('#')
break
elif kind=='optr':
this.pushoptr(aword)
else:
this.pushopnd(aword)
print(aword, cond)
print(this.optrStack)
print(this.opndStack)
return this.popopnd()
if __name__=='__main__':
# print(opDict)
a= exprEngine()
# a.addInfo('水位', '低')
b= a.eval('3 + 5 = 8 & ~(7 < 2)')
print(b)
为了调试,中间加了一些print输出,正式使用时,自行取消。