首页 > 解决方案 > 将类传递给“if/else”结构的 Pyparsing setParseAction() 类时出错

问题描述

我正在尝试使用 pyparsing 为python 中的迷宫机器人求解器创建一个非常简单的语言。

因为 pyparsing 看起来很强大但不容易操作,所以我从一个简单的例子开始,包括赋值和if [expression] then [code] elsif [expression] then [code] else [code]结构

simple_example_assignement = '''
SET x 3 + 2
SET y 2
'''

simple_example_condition = '''
IF x NOT MATCH 3 THEN { SET b 2 } 
ELSIF y NOT MATCH 2 THEN {SET d 4} 
ELSE { SET c 3}
'''

赋值期间算术表达式求值的代码

# store variable value for evaluation
vars = {}

class EvalAddOp():
    """Class to evaluate addition and subtraction expressions."""

    def __init__(self, tokens):

        self.value = tokens[0]

        print(self.value)

    def eval(self, vars_):

        if type(self.value[0]) in [EvalAddOp]:
            print("ENTER EVAL ADD OPP")
            sum = self.value[0].eval(vars_)
        else:
            sum = self.checkNum(self.value[0], vars_)

        return self.ops(sum, vars_)

    def checkNum(self, val, _vars):
        print(type(val), " = ", val)
        if type(val) in [int, float]:
            return val
        elif type(val) in [EvalAddOp]:
            return val.eval(_vars)
        else:
            return _vars[val]

    def ops(self, sum, vars_):
        for op, val in operatorOperands(self.value[1:]):
            if op == '+':
                sum += self.checkNum(val, vars_)
            if op == '-':
                sum -= self.checkNum(val, vars_)
        return sum

def eval_expression(expr):
    if isinstance(expr, str):
        if expr[0] in '"\'':  # string literal
            return expr[1:-1]  # remove quotes
        else:
            return vars.get(expr)
    elif isinstance(expr, EvalAddOp):
        return expr.eval(vars)

    return expr
integer = Word(nums).setParseAction(lambda t: int(t[0]))
variable = Word(alphas, exact=1)
operand = integer | variable
plusop = oneOf('+ -')
signop = oneOf('+ -')
multop = oneOf('* /')
matching = Keyword('MATCH')

arithmeticExpression = infixNotation(operand,
                                     [(signop, 1, opAssoc.RIGHT),
                                      (multop, 2, opAssoc.LEFT),
                                      (plusop, 2, opAssoc.LEFT, EvalAddOp), ]
                                     )

确定分配和条件语句解析的代码:

expression = Forward()
exprOperators = Forward()
code_block = Forward()

literal = quotedString ^ pyparsing_common.number

commonExpression = literal ^ variable ^ arithmeticExpression
matchingExpression = Group(commonExpression + exprOperators + commonExpression)

expression << matchingExpression ^ commonExpression

exprOperators << infixNotation(matching,[("NOT", 1, opAssoc.RIGHT)])

# Assignment rules
set_assignment = Group(Keyword('SET') + variable + commonExpression)

# If/Else rules
simple_if_stmt = Keyword('IF') + expression + Keyword('THEN') + code_block
else_if_stmt = Keyword('ELSIF') + expression + Keyword('THEN') + code_block
else_stmt = Keyword('ELSE') + code_block
simple_if_group = Group(simple_if_stmt + Optional(OneOrMore(else_if_stmt)) + Optional(else_stmt)).setParseAction(IfEval)

# all possible statements in the example prorgam
stmt = set_assignment ^ simple_if_group

# Code to evaluate
code_block << Group(Literal('{').suppress() + OneOrMore(stmt) + Literal('}').suppress()).setName('code block')

program = Dict(OneOrMore(stmt))

我尝试使用setParseActiononsimple_if_group变量附加一个 Action,调用 class IfEval。大多数示例都将函数附加为 Action,但在 If/Else 结构的情况下,我认为以后评估条件更好的是结构化的类......我不确定这是不是好方法,所以我接受任何建议

class IFEval():

    def __init__(self):
        self.ifStructure = {}

    def __len__(self):
        return len(self.ifStructure)

    def __getitem__(self, item):
        return self.ifStructure["item"]

    def __setitem__(self, key, value):
        self.ifStructure[key] = value

    def __delitem__(self, key):
        pass

    def __copy__(self):
        return self.ifStructure[:]

@traceParseAction
def IfEval(s, l, tokens):

    if_stmt = IFEval()
    if Keyword("IF").parseString(tokens[0][1]):
        if_stmt["then_codeblock"] = tokens[0][3]

    if Keyword("ELSIF").parseString(tokens[0][4]):
        if_stmt["elsif_codeblock"] = tokens[0][6]

    if Keyword("ELSE").parseString(tokens[0][8]):
        if_stmt["else_codeblock"] = tokens[0][9]

    return if_stmt

SET使用没有问题的作品的作业:

parsed = program.parseString(simple_example_assignement)

for _, name, value in parsed:
    vars[name] = eval_expression(value)
print(vars)

[3, '+', 2]
<class 'int'>  =  3
<class 'int'>  =  2
{'y': 2, 'x': 5}

现在,甚至在评估之前,我正在尝试解析第二个将setParseAction方法调用到IFEval类的示例:

parsed = program.parseString()

返回 str() 错误?可能是因为当您尝试使用类而不是方法时,我不明白 parseAction 函数的作用:

>>entering IfEval(line: 'IF x NOT MATCH 3 THEN { SET b 2 } ', 21, ([(['IF', (['x', (['NOT', 'MATCH'], {}), 3], {}), 'THEN', ([(['SET', 'b', 2], {})], {}), 'ELSIF', (['y', (['NOT', 'MATCH'], {}), 2], {}), 'THEN', ([(['SET', 'd', 4], {})], {}), 'ELSE', ([(['SET', 'c', 3], {})], {})], {})], {}))
<<leaving IfEval (exception: 'str' object is not callable)
Traceback (most recent call last):
  File "/home/reyman/Projets/cours/exercice/labyrinthe_matplot_python/parsingLanguage.py", line 246, in <module>
    parsed = program.parseString(conditional_test)
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1666, in parseString
    loc, tokens = self._parse( instring, 0 )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1412, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 3805, in parseImpl
    return self.expr._parse( instring, loc, doActions, callPreParse=False )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1412, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 4033, in parseImpl
    loc, tmptokens = self_expr_parse( instring, preloc, doActions )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1412, in _parseNoCache
    loc,tokens = self.parseImpl( instring, preloc, doActions )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 3555, in parseImpl
    return e._parse( instring, loc, doActions )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1445, in _parseNoCache
    tokens = fn( instring, tokensStart, retTokens )
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1082, in wrapper
    ret = func(*args[limit[0]:])
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 4588, in z
    ret = f(*paArgs)
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1082, in wrapper
    ret = func(*args[limit[0]:])
  File "/home/reyman/Projets/cours/exercice/labyrinthe_matplot_python/parsingLanguage.py", line 99, in IfEval
    if Keyword("IF").parseString(tokens[0][1]):
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1664, in parseString
    instring = instring.expandtabs()
TypeError: 'str' object is not callable

标签: python-3.xpyparsing

解决方案


(这是一个巨大的问题!如果您可以将问题简化为小问题,那么您将在 SO 上得到更好的回应。)

查看回溯的最后两行:

  File "/home/reyman/Projets/cours/exercice/labyrinthe_matplot_python/parsingLanguage.py", line 99, in IfEval
    if Keyword("IF").parseString(tokens[0][1]):
  File "/home/reyman/.pyenv/versions/labyrinthes/lib/python3.5/site-packages/pyparsing.py", line 1664, in parseString
    instring = instring.expandtabs()
TypeError: 'str' object is not callable

你传递了一个ParseResultsto parseString,而不是一个字符串。然后当 pyparsing 尝试在输入参数上调用 str 函数时,ParseResults 将这些解释为尝试读取属性,它默认返回 ''。再剖析一下:

instring.expandtabs()

^^^^^^^^
a ParseResults, not a str
         ^^^^^^^^^^
         an undefined attribute in the ParseResults, so returns ''
                   ^^
                   trying to "call" the str - but str's aren't callable, so exception!

traceParseAction对于采用简单标记的解析操作是可以的,但是这个操作有些复杂,我建议您print(tokens.dump())将其作为解析操作的第一行,以便更好地可视化您所获得的结构类型。

您检测 IF、ELSIF 和 ELSE 的方法也容易出错,最好在语法中使用结果名称。代替:

simple_if_stmt = Keyword('IF') + expression + Keyword('THEN') + code_block
else_if_stmt = Keyword('ELSIF') + expression + Keyword('THEN') + code_block
else_stmt = Keyword('ELSE') + code_block
simple_if_group = Group(simple_if_stmt + Optional(OneOrMore(else_if_stmt)) + Optional(else_stmt)).setParseAction(IfEval)

做:

from pyparsing import *

# fake expressions, not intended to replace those in the original problem
ident = pyparsing_common.identifier
integer = pyparsing_common.integer
expression = ident + "MATCH" + (ident | integer)
code_block = originalTextFor(nestedExpr('{', '}'))

simple_if_stmt = Group(Keyword('IF') + expression('condition') 
                        + Keyword('THEN') + code_block('code'))
else_if_stmt = Group(Keyword('ELSIF') + expression('condition') 
                        + Keyword('THEN') + code_block('code'))
else_stmt = Group(Keyword('ELSE') + code_block('code'))

simple_if_group = Group(simple_if_stmt('if_') 
                        + Optional(OneOrMore(else_if_stmt('elsif*'))) 
                        + Optional(else_stmt('else_')))

现在这是一个使用这些结果名称的解析操作:

def IfStatement(s, l, tokens):

    # peel of outer grouping layer
    tokens = tokens[0]

    # dump out inner structure of parsed results
    print(tokens.dump())

    print('IF:', tokens.if_.condition, '->', tokens.if_.code)

    if 'elsif' in tokens:
        for elsif in tokens.elsif:
            print('ELSIF:', elsif.condition, '->', elsif.code)

    if 'else_' in tokens:
        print('ELSE:', '->', tokens.else_.code)

    print()

simple_if_group.addParseAction(IfStatement)

对于这个示例(注意从小开始,然后变得更复杂):

sample = """\
IF X MATCH Y THEN { this is some code }

IF X MATCH Y THEN { this is some code }
ELSE { do this instead }

IF X MATCH Y THEN { this is some Y code }
ELSIF X MATCH Z THEN { this is some Z code }
ELSIF X MATCH A THEN { this is some A code }
ELSE { do this instead }
"""

result = OneOrMore(simple_if_group).parseString(sample)

我们得到:

[['IF', 'X', 'MATCH', 'Y', 'THEN', '{ this is some code }']]
- if_: ['IF', 'X', 'MATCH', 'Y', 'THEN', '{ this is some code }']
  - code: '{ this is some code }'
  - condition: ['X', 'MATCH', 'Y']
IF: ['X', 'MATCH', 'Y'] -> { this is some code }

[['IF', 'X', 'MATCH', 'Y', 'THEN', '{ this is some code }'], ['ELSE', '{ do this instead }']]
- else_: ['ELSE', '{ do this instead }']
  - code: '{ do this instead }'
- if_: ['IF', 'X', 'MATCH', 'Y', 'THEN', '{ this is some code }']
  - code: '{ this is some code }'
  - condition: ['X', 'MATCH', 'Y']
IF: ['X', 'MATCH', 'Y'] -> { this is some code }
ELSE:  -> { do this instead }

[['IF', 'X', 'MATCH', 'Y', 'THEN', '{ this is some Y code }'], ['ELSIF', 'X', 'MATCH', 'Z', 'THEN', '{ this is some Z code }'], ['ELSIF', 'X', 'MATCH', 'A', 'THEN', '{ this is some A code }'], ['ELSE', '{ do this instead }']]
- else_: ['ELSE', '{ do this instead }']
  - code: '{ do this instead }'
- elsif: [['ELSIF', 'X', 'MATCH', 'Z', 'THEN', '{ this is some Z code }'], ['ELSIF', 'X', 'MATCH', 'A', 'THEN', '{ this is some A code }']]
  [0]:
    ['ELSIF', 'X', 'MATCH', 'Z', 'THEN', '{ this is some Z code }']
    - code: '{ this is some Z code }'
    - condition: ['X', 'MATCH', 'Z']
  [1]:
    ['ELSIF', 'X', 'MATCH', 'A', 'THEN', '{ this is some A code }']
    - code: '{ this is some A code }'
    - condition: ['X', 'MATCH', 'A']
- if_: ['IF', 'X', 'MATCH', 'Y', 'THEN', '{ this is some Y code }']
  - code: '{ this is some Y code }'
  - condition: ['X', 'MATCH', 'Y']
IF: ['X', 'MATCH', 'Y'] -> { this is some Y code }
ELSIF: ['X', 'MATCH', 'Z'] -> { this is some Z code }
ELSIF: ['X', 'MATCH', 'A'] -> { this is some A code }
ELSE:  -> { do this instead }

推荐阅读