用pyparsing解析数学表达式

时间:2014-05-27 01:26:44

标签: python-3.x pyparsing

我试图使用pyparsing解析数学表达式。我知道我可以从pyparsing网站复制示例计算器,但我想了解它,以便我可以在以后添加它。而我在这里是因为我试图理解这个例子,我无法理解,所以我尽了最大努力,我得到了这个:

symbol = (
    pp.Literal("^") |
    pp.Literal("*") |
    pp.Literal("/") |
    pp.Literal("+") |
    pp.Literal("-")
)
operation = pp.Forward()
atom = pp.Group(
    pp.Literal("(").suppress() + operation + pp.Literal(")").suppress()
) | number
operation << (pp.Group(number + symbol + number + pp.ZeroOrMore(symbol + atom)) | atom)
expression = pp.OneOrMore(operation)


print(expression.parseString("9-1+27+(3-5)+9"))

打印:

[[9, '-', 1, '+', 27, '+', [[3, '-', 5]], '+', 9]]

它有效,有点。我想要优先级并且全部分类到Groups,但经过大量尝试后,我无法找到办法。或多或少是这样的:

[[[[9, '-', 1], '+', 27], '+', [3, '-', 5]], '+', 9]

我希望保持AST外观,我想从中生成代码。

我确实看到了operatorPrecedence课程?类似于Forward,但我不认为我理解它是如何工作的。

编辑:

深入探究operatorPrecedence,我得到了这个:

expression = pp.operatorPrecedence(number, [
    (pp.Literal("^"), 1, pp.opAssoc.RIGHT),
    (pp.Literal("*"), 2, pp.opAssoc.LEFT),
    (pp.Literal("/"), 2, pp.opAssoc.LEFT),
    (pp.Literal("+"), 2, pp.opAssoc.LEFT),
    (pp.Literal("-"), 2, pp.opAssoc.LEFT)
])

哪个不处理括号(我不知道我是否必须对结果进行后期处理),我需要处理它们。

1 个答案:

答案 0 :(得分:9)

此解析问题的实际名称是“中缀表示法”(在最新版本的pyparsing中,我将operatorPrecedence重命名为infixNotation)。要查看中缀表示法解析的典型实现,请查看pyparsing wiki上的fourFn.py示例。在那里,您将看到这个简化的BNF的实现,以实现4函数算法,优先于操作:

operand :: integer or real number
factor :: operand | '(' expr ')'
term :: factor ( ('*' | '/') factor )*
expr :: term ( ('+' | '-') term )*

因此,表达式是由加法或减法运算分隔的一个或多个术语。

术语是由乘法或除法运算分隔的一个或多个因子。

一个因子是最低级别的操作数(在这种情况下,只是整数或实数),或者是一个包含在()中的expr。

请注意,这是一个递归解析器,因为factor在expr的定义中间接使用,但expr也用于定义因子。

在pyparsing中,这看起来大致相同(假设已经定义了整数和实数):

LPAR,RPAR = map(Suppress, '()')
expr = Forward()
operand = real | integer
factor = operand | Group(LPAR + expr + RPAR)
term = factor + ZeroOrMore( oneOf('* /') + factor )
expr <<= term + ZeroOrMore( oneOf('+ -') + term )

现在使用expr,你可以解析其中任何一个:

3
3+2
3+2*4
(3+2)*4

infixNotation pyparsing helper方法负责所有递归定义和分组,并允许您将其定义为:

expr = infixNotation(operand,
        [
        (oneOf('* /'), 2, opAssoc.LEFT),
        (oneOf('+ -'), 2, opAssoc.LEFT),
        ])

但是这会掩盖所有基础理论,所以如果你想了解它是如何实现的,那么看看fourFn.py中的原始解决方案。