Простой рекурсивный спуск в PyParsing
Я попытался принять этот код и преобразовать его в нечто для проекта, над которым я работаю, для обработки языка программирования, но у меня проблема с упрощенной версией:
op = oneOf( '+ - / *')
lparen, rparen = Literal('('), Literal(')')
expr = Forward()
expr << ( Word(nums) | ( expr + op + expr ) | ( lparen + expr + rparen) )
Я играл с несколькими различными модификациями этой простой настройки. Обычно, попробуйте что-то вроде:
print(expr.parseString('1+2'))
Вернется ['1']
. Хотя я попадаю в глубокую рекурсию с чем-то вроде:
print(expr.parseString('(1+2)'))
Что мне не хватает в отношении простой рекурсии, что я не могу анализировать произвольно арифметические выражения, такие как 1+(2 * 3-(4*(5+6)-(7))...
?
Ответы
Ответ 1
Ничего себе, я думаю, что пираринг действительно на карте! Спасибо Алексу и Джону за то, что он занялся этим вопросом. Вы оба на отметке с вашими ответами. Но позвольте мне добавить комментарий или два:
-
Если мы подавляем открывающие и закрывающие символы круглых скобок и группируем выражение в скобках с помощью Group, то pyparsing будет структурированным результатом, который ближе к AST.
from pyparsing import Literal,Word,ZeroOrMore,Forward,nums,oneOf,Group
def Syntax():
op = oneOf( '+ - / *')
lpar = Literal( '(' ).suppress()
rpar = Literal( ')' ).suppress()
num = Word(nums)
expr = Forward()
atom = num | Group( lpar + expr + rpar )
expr << atom + ZeroOrMore( op + expr )
return expr
if __name__ == "__main__":
expr = Syntax()
def test(s):
results = expr.parseString( s )
print s,'->', results
test( "(9 + 3)" )
test( "(9 + 3) * (4 / 5)" )
Дарение:
(9 + 3) -> [['9', '+', '3']]
(9 + 3) * (4 / 5) -> [['9', '+', '3'], '*', ['4', '/', '5']]
В противном случае pyparsing просто символизирует, и вам нужно пройти список проанализированных токенов, чтобы найти вложенные выражения.
-
Так как op определяется как только одинOf ( "+ - */" ), приоритет операций не существует. Есть примеры на вики-странице pyparsing для ручного способа определения этого (fourFn.py) или более позднего подхода с использованием помощника operatorPrecedence (simpleArith.py). Опять же, у этого есть pyparsing, добавляющий больше значения, чем просто токенизация.
В OP, пожалуйста, ознакомьтесь с этими примерами, я думаю, они помогут вам продвинуться вперед по вашему проекту.
- Пол
Ответ 2
Это более или менее то, что вы хотите...?
from pyparsing import Literal,Word,ZeroOrMore,Forward,nums,oneOf
def Syntax():
op = oneOf( '+ - / *')
lpar = Literal( '(' )
rpar = Literal( ')' )
num = Word(nums)
expr = Forward()
atom = num | ( lpar + expr + rpar )
expr << atom + ZeroOrMore( op + expr )
return expr
if __name__ == "__main__":
expr = Syntax()
def test(s):
results = expr.parseString( s )
print s,'->', results
test( "(9 + 3)" )
test( "(9 + 3) * (4 / 5)" )
излучающие
(9 + 3) -> ['(', '9', '+', '3', ')']
(9 + 3) * (4 / 5) -> ['(', '9', '+', '3', ')', '*', '(', '4', '/', '5', ')']
? Это "привязывает" рекурсию, отделяя "атом" (число или заключенное в скобки выражение) от "выражения" (одного или нескольких "атомов" с промежуточными операторами).
Ответ 3
Грамматика вроде:
expr :: expr op expr
сложно работать, потому что рекурсия просто продолжает погружение влево.
Обычная арифметическая грамматика будет выглядеть примерно так:
expr :: mulxp | mulxp '+' expr
mulxp :: atom | atom '*' expr
atom :: Word(nums) | '(' + expr + ')'
В принципе, вы никогда не получите S :: S
; в любое время, когда нетерминал появляется в левой и правой сторонах строки в грамматике, должен быть некоторый буквальный посередине, чтобы парсер мог потреблять.
Ответ 4
Используйте operatorPrecedence
для создания выражений, например, как в примере в http://pyparsing.wikispaces.com/file/view/simpleArith.py/30268305/simpleArith.py. Он построит правильные выражения и позаботится о приоритете оператора, находясь на нем:
num = Word(nums)
plusop = oneOf( '+ -')
multop = oneOf('/ *')
expr = operatorPrecedence(num,
[(multop, 2, opAssoc.LEFT),(plusop, 2, opAssoc.LEFT)])
Пример:
>> print parsetime.expr.parseString("1+(2 * 3-(4*(5+6)-(7)))")
[['1', '+', [['2', '*', '3'], '-', [['4', '*', ['5', '+', '6']], '-', '7']]]]