PyParsing простые языковые выражения
Вопрос
Я пытаюсь написать что-то, что будет анализировать некоторый код.Я могу успешно анализировать foo(spam)
и spam+eggs
, но foo(spam+eggs)
(рекурсивный спуск?моя терминология из компиляторов немного подзабыта) терпит неудачу.
У меня есть следующий код:
from pyparsing_py3 import *
myVal = Word(alphas+nums+'_')
myFunction = myVal + '(' + delimitedList( myVal ) + ')'
myExpr = Forward()
mySubExpr = ( \
myVal \
| (Suppress('(') + Group(myExpr) + Suppress(')')) \
| myFunction \
)
myExpr << Group( mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr ) )
# SHOULD return: [blah, [foo, +, bar]]
# but actually returns: [blah]
print(myExpr.parseString('blah(foo+bar)'))
Решение
Несколько проблем:delimitedList ищет список myVal, разделенный запятыми, т.е.идентификаторы, как единственная приемлемая форма списка аргументов, поэтому, конечно, он не может соответствовать 'foo+bar' (а не списку myVal, разделенному запятыми!);исправление, которое показывает другое - myVal и myFunction начинаются одинаково, поэтому их порядок в mySubExpr имеет значение;исправление, которое открывает еще одно — ДВА уровня вложенности вместо одного.Эти версии кажутся нормальными...:
myVal = Word(alphas+nums+'_')
myExpr = Forward()
mySubExpr = (
(Suppress('(') + Group(myExpr) + Suppress(')'))
| myVal + Suppress('(') + Group(delimitedList(myExpr)) + Suppress(')')
| myVal
)
myExpr << mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr )
print(myExpr.parseString('blah(foo+bar)'))
излучает ['blah', ['foo', '+', 'bar']]
по желанию.Я также удалил лишние обратные косые черты, поскольку логическое продолжение строки в любом случае происходит внутри круглых скобок;они были безобидны, но мешали читабельности.
Другие советы
Я обнаружил, что это хорошая привычка при использовании '<< оператор с Forwards должен всегда заключать RHS в круглые скобки.Это:
myExpr << mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr )
лучше так, как:
myExpr << ( mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr ) )
Это результат моего неудачного выбора '<<'в качестве оператора "вставки" для вставки выражения в переадресацию.Круглые скобки не нужны в данном конкретном случае, но в данном:
integer = Word(nums)
myExpr << mySubExpr + ZeroOrMore( oneOf('+ - / * =') + mySubExpr ) | integer
мы понимаем, почему я говорю "неудачный".Если я упрощу это до "A << B | C", мы легко видим, что приоритет операций приводит к выполнению оценки как "(A << B) | C", поскольку '<<' имеет более высокий приоритет, чем '|'.Результатом является то, что пересылка A получает только вставленное в нее выражение B.Часть "| C" действительно выполняется, но происходит то, что вы получаете "A | C", который создает объект MatchFirst, который затем немедленно отбрасывается, поскольку ему не присвоено ни одно имя переменной.Решением было бы сгруппировать оператор в круглых скобках как "A << (B| C)".В выражениях, составленных только с использованием операций '+', нет реальной необходимости в круглых скобках, поскольку '+' имеет более высокий приоритет, чем '<<'.Но это просто удачное кодирование, и оно вызывает проблему, когда кто-то позже добавляет альтернативное выражение, используя '|' , и не осознает последствий приоритета.Поэтому я предлагаю просто принять стиль "А << (выражение)", чтобы помочь избежать этой путаницы.
(Когда-нибудь я напишу pyparsing 2.0, который позволит мне нарушить совместимость с существующим кодом, и изменю это, чтобы использовать '<<=' оператор, который устраняет все эти проблемы с приоритетом, поскольку '<<=' имеет более низкий приоритет, чем любой из других операторов, используемых pyparsing.)