Ошибка при передаче класса в Pyparsing setParseAction () класс для структуры "if / else" - PullRequest
0 голосов
/ 05 ноября 2018

Я пытаюсь использовать 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))

Я пытаюсь присоединить действие, используя setParseAction для переменной simple_if_group, вызывая класс 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

1 Ответ

0 голосов
/ 08 ноября 2018

(Это огромный! Вопрос - вы получите лучший ответ на SO, если сможете сократить свою проблему до чего-то небольшого.)

Глядя на последние 2 строки вашей трассировки:

  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

Вы передали ParseResults в 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 }
...