使用非数字参数将函数解析添加到简单的 pyparsing
Add function parsing to simple pyparsing with non-numeric arguments
我正在尝试向表达式添加函数,并能够接受非数字参数。在 and https://pyparsing.wikispaces.com/file/view/fourFn.py 之后,我可以使用数字输入来管理函数。但是无法为非数字输入升级它们。这是我的测试代码,试图将 ident 与 expr 一起传递给函数输入:
# fourFn.py
#
# Demonstration of the pyparsing module, implementing a simple 4-function expression parser,
# with support for scientific notation, and symbols for e and pi.
# Extended to add exponentiation and simple built-in functions.
# Extended test cases, simplified pushFirst method.
#
# Copyright 2003-2006 by Paul McGuire
#
from pyparsing import Literal,CaselessLiteral,Word,Combine,Group,Optional,\
ZeroOrMore,Forward,nums,alphas,delimitedList
import math
import operator
import pprint
exprStack = []
def pushFirst( strg, loc, toks ):
exprStack.append( toks[0] )
def pushUMinus( strg, loc, toks ):
if toks and toks[0]=='-':
exprStack.append( 'unary -' )
#~ exprStack.append( '-1' )
#~ exprStack.append( '*' )
bnf = None
def BNF():
"""
expop :: '^'
multop :: '*' | '/'
addop :: '+' | '-'
integer :: ['+' | '-'] '0'..'9'+
atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
factor :: atom [ expop factor ]*
term :: factor [ multop factor ]*
expr :: term [ addop term ]*
"""
global bnf
if not bnf:
point = Literal( "." )
#e = CaselessLiteral( "E" )
fnumber = Combine( Word( "+-"+nums, nums ) +
Optional( point + Optional( Word( nums ) ) )
)
ident = Word(alphas, alphas+nums)
plus = Literal( "+" )
minus = Literal( "-" )
mult = Literal( "*" )
div = Literal( "/" )
lpar = Literal( "(" ).suppress()
rpar = Literal( ")" ).suppress()
addop = plus | minus
multop = mult | div
expop = Literal( "^" )
#pi = CaselessLiteral( "PI" )
expr = Forward()
#function_calla = Group(ident + lpar + ident + rpar)
function_call = Group(ident + lpar + Group(Optional(delimitedList(ident|expr))) + rpar)
atom = (Optional("-") + ( fnumber | ident + lpar + expr + rpar |function_call).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
bnf = expr
return bnf
def asw(x):
print "X is ",x
return 1
# map operator symbols to corresponding arithmetic operations
epsilon = 1e-12
opn = { "+" : operator.add,
"-" : operator.sub,
"*" : operator.mul,
"/" : operator.truediv,
"^" : operator.pow }
fn = { "sin" : math.sin,
"cos" : math.cos,
"tan" : math.tan,
"abs" : abs,
"trunc" : lambda a: int(a),
"round" : round,
"asw" : asw,
"sgn" : lambda a: abs(a)>epsilon and cmp(a,0) or 0}
def evaluateStack( s ):
print "s=",s
op = s.pop()
print "op=",op,type(op)
if op == 'unary -':
return -evaluateStack( s )
#if type(op)!=str:
# return evaluateStack( s )
if op in "+-*/^":
op2 = evaluateStack( s )
op1 = evaluateStack( s )
return opn[op]( op1, op2 )
elif op == "PI":
return math.pi # 3.1415926535
elif op == "E":
return math.e # 2.718281828
elif op in fn:
return fn[op]( evaluateStack( s ) )
elif op[0].isalpha():
return 0
else:
return float( op )
if __name__ == "__main__":
exprStack = []
res= BNF().parseString( "asw(aa)").asList()
print "res=",res
val = evaluateStack( exprStack[:] )
print val
数字输入的结果是:
C:\temp>python test.py
res= ['asw', '11']
s= ['11', 'asw']
op= asw <type 'str'>
s= ['11']
op= 11 <type 'str'>
X is 11.0
1
非数字输入将导致:
C:\temp>python test.py
res= [['asw', ['aa']]]
s= [(['asw', (['aa'], {})], {})]
op= ['asw', ['aa']] <class 'pyparsing.ParseResults'>
Traceback (most recent call last):
File "test.py", line 117, in <module>
val = evaluateStack( exprStack[:] )
File "test.py", line 99, in evaluateStack
if op in "+-*/^":
TypeError: 'in <string>' requires string as left operand, not ParseResults
我哪里错了?对 pyparsing 很陌生,仍在努力弄清楚。
我很确定你的罪魁祸首是你在最后将 function_call
添加到 atom
中,并使 function_call
成为一个组。
function_call = (ident + lpar + Group(Optional(delimitedList(expr))) + rpar)
atom = (Optional("-") + ( fnumber | function_call | ident).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus)
记住'|'生成 MatchFirst
表达式,它们将匹配第一个匹配的给定表达式。如果您首先定义 atom
以匹配 ident
,那么您将永远不会匹配 function_call
,因为您的函数以标识符开头。在确定它实际上是一个单独的标识符之前,您必须首先检查您正在解析的标识符是否是函数调用的开始。
此外,function_call
不能是组。这个解析器的工作方式是解析所有参数并将其压入堆栈,最后将函数名压入堆栈。但这仅在 function_call
不是 Group 时才有效 - 否则您会将整个解析函数 ParseResults 推入堆栈。
最后,请注意,我将 function_call
的参数简化为 expr
,而不是 ident | expr
。一个单独的 ident
是 一个 expr
,所以交替是没有必要的 - 事实上它又把事情搞砸了,出于同样的原因列出 [= atom
中的 function_call
之前的 16=] 是个问题。
我正在尝试向表达式添加函数,并能够接受非数字参数。在
# fourFn.py
#
# Demonstration of the pyparsing module, implementing a simple 4-function expression parser,
# with support for scientific notation, and symbols for e and pi.
# Extended to add exponentiation and simple built-in functions.
# Extended test cases, simplified pushFirst method.
#
# Copyright 2003-2006 by Paul McGuire
#
from pyparsing import Literal,CaselessLiteral,Word,Combine,Group,Optional,\
ZeroOrMore,Forward,nums,alphas,delimitedList
import math
import operator
import pprint
exprStack = []
def pushFirst( strg, loc, toks ):
exprStack.append( toks[0] )
def pushUMinus( strg, loc, toks ):
if toks and toks[0]=='-':
exprStack.append( 'unary -' )
#~ exprStack.append( '-1' )
#~ exprStack.append( '*' )
bnf = None
def BNF():
"""
expop :: '^'
multop :: '*' | '/'
addop :: '+' | '-'
integer :: ['+' | '-'] '0'..'9'+
atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
factor :: atom [ expop factor ]*
term :: factor [ multop factor ]*
expr :: term [ addop term ]*
"""
global bnf
if not bnf:
point = Literal( "." )
#e = CaselessLiteral( "E" )
fnumber = Combine( Word( "+-"+nums, nums ) +
Optional( point + Optional( Word( nums ) ) )
)
ident = Word(alphas, alphas+nums)
plus = Literal( "+" )
minus = Literal( "-" )
mult = Literal( "*" )
div = Literal( "/" )
lpar = Literal( "(" ).suppress()
rpar = Literal( ")" ).suppress()
addop = plus | minus
multop = mult | div
expop = Literal( "^" )
#pi = CaselessLiteral( "PI" )
expr = Forward()
#function_calla = Group(ident + lpar + ident + rpar)
function_call = Group(ident + lpar + Group(Optional(delimitedList(ident|expr))) + rpar)
atom = (Optional("-") + ( fnumber | ident + lpar + expr + rpar |function_call).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
bnf = expr
return bnf
def asw(x):
print "X is ",x
return 1
# map operator symbols to corresponding arithmetic operations
epsilon = 1e-12
opn = { "+" : operator.add,
"-" : operator.sub,
"*" : operator.mul,
"/" : operator.truediv,
"^" : operator.pow }
fn = { "sin" : math.sin,
"cos" : math.cos,
"tan" : math.tan,
"abs" : abs,
"trunc" : lambda a: int(a),
"round" : round,
"asw" : asw,
"sgn" : lambda a: abs(a)>epsilon and cmp(a,0) or 0}
def evaluateStack( s ):
print "s=",s
op = s.pop()
print "op=",op,type(op)
if op == 'unary -':
return -evaluateStack( s )
#if type(op)!=str:
# return evaluateStack( s )
if op in "+-*/^":
op2 = evaluateStack( s )
op1 = evaluateStack( s )
return opn[op]( op1, op2 )
elif op == "PI":
return math.pi # 3.1415926535
elif op == "E":
return math.e # 2.718281828
elif op in fn:
return fn[op]( evaluateStack( s ) )
elif op[0].isalpha():
return 0
else:
return float( op )
if __name__ == "__main__":
exprStack = []
res= BNF().parseString( "asw(aa)").asList()
print "res=",res
val = evaluateStack( exprStack[:] )
print val
数字输入的结果是:
C:\temp>python test.py
res= ['asw', '11']
s= ['11', 'asw']
op= asw <type 'str'>
s= ['11']
op= 11 <type 'str'>
X is 11.0
1
非数字输入将导致:
C:\temp>python test.py
res= [['asw', ['aa']]]
s= [(['asw', (['aa'], {})], {})]
op= ['asw', ['aa']] <class 'pyparsing.ParseResults'>
Traceback (most recent call last):
File "test.py", line 117, in <module>
val = evaluateStack( exprStack[:] )
File "test.py", line 99, in evaluateStack
if op in "+-*/^":
TypeError: 'in <string>' requires string as left operand, not ParseResults
我哪里错了?对 pyparsing 很陌生,仍在努力弄清楚。
我很确定你的罪魁祸首是你在最后将 function_call
添加到 atom
中,并使 function_call
成为一个组。
function_call = (ident + lpar + Group(Optional(delimitedList(expr))) + rpar)
atom = (Optional("-") + ( fnumber | function_call | ident).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus)
记住'|'生成 MatchFirst
表达式,它们将匹配第一个匹配的给定表达式。如果您首先定义 atom
以匹配 ident
,那么您将永远不会匹配 function_call
,因为您的函数以标识符开头。在确定它实际上是一个单独的标识符之前,您必须首先检查您正在解析的标识符是否是函数调用的开始。
此外,function_call
不能是组。这个解析器的工作方式是解析所有参数并将其压入堆栈,最后将函数名压入堆栈。但这仅在 function_call
不是 Group 时才有效 - 否则您会将整个解析函数 ParseResults 推入堆栈。
最后,请注意,我将 function_call
的参数简化为 expr
,而不是 ident | expr
。一个单独的 ident
是 一个 expr
,所以交替是没有必要的 - 事实上它又把事情搞砸了,出于同样的原因列出 [= atom
中的 function_call
之前的 16=] 是个问题。