Как проверить динамически определенные элементы грамматики в PyParsing
Я реализую синтаксический анализатор для довольно сложной грамматики, используя PyParsing. (Что, если я могу добавить, действительно приятно использовать!)
Грамматика несколько "динамична" в том, что позволяет определять (различные) алфавиты, которые, в свою очередь, определяют элементы, разрешенные в других определениях. В качестве примера:
alphabet: a b c
lists:
s1 = a b
s2 = b c x
Здесь alphabet предназначено для определения того, какие элементы допускаются в определениях lists. Например, s1 было бы допустимым, но s2 содержит недопустимое x.
Простой синтаксический анализатор PyParsing без такой проверки может выглядеть следующим образом:
from pyparsing import Literal, lineEnd, Word, alphanums,
OneOrMore, Group, Suppress, dictOf
def fixedToken(literal):
return Suppress(Literal(literal))
Element = Word(alphanums)
Alphabet = Group(OneOrMore(~lineEnd + Element))
AlphaDef = fixedToken("alphabet:") + Alphabet
ListLine = OneOrMore(~lineEnd + Element)
Lists = dictOf(Word(alphanums) + fixedToken("="), ListLine)
Start = AlphaDef + fixedToken("lists:") + Lists
if __name__ == "__main__":
data = """
alphabet: a b c
lists:
s1 = a b
s2 = b c x
"""
res = Start.parseString(data)
for k, v in sorted(res.items()):
print k, "=", v
Это разберет и даст результат:
Alphabet= set(['a', 'c', 'b'])
s1 = ['a', 'b']
s2 = ['b', 'c', 'x']
Однако я хотел бы, чтобы синтаксический анализатор вызвал исключение ParseException (или подобное) для s2, так как оно содержит недопустимое x. В идеале, я хотел бы иметь возможность сделать определение ListLine, чтобы сказать что-то вроде: OneOrMore(oneOf(Alphabet)) - но очевидно, что это потребует некоторой динамической интерпретации, которая может быть сделана только после того, как Alphabet имеет фактически был проанализирован и собран.
Одно из решений, которое я нашел, состояло в том, чтобы добавить действия разбора к 1. запомните алфавит и 2. проверьте строки:
# ...
Alphabet = Group(OneOrMore(~lineEnd + Element))
def alphaHold(toks):
alphaHold.alpha = set(*toks)
print "Alphabet=", alphaHold.alpha
Alphabet.addParseAction(alphaHold)
AlphaDef = fixedToken("alphabet:") + Alphabet
ListLine = OneOrMore(~lineEnd + Element)
def lineValidate(toks):
unknown = set(toks).difference(alphaHold.alpha)
if len(unknown):
msg= "Unknown element(s): {}".format(unknown)
print msg
raise ParseException(msg)
ListLine.addParseAction(lineValidate)
# ...
Это дает почти желаемый результат:
Alphabet= set(['a', 'c', 'b'])
Unknown element(s): set(['x'])
s1 = ['a', 'b']
Но, к сожалению, PyParsing улавливает исключения, вызванные действиями синтаксического анализа, поэтому этот подход терпит неудачу по техническим причинам. Есть ли другой способ достичь этого в рамках PyParsing, который я мог бы пропустить?
1 ответ:
Вы уже довольно близки к тому, чтобы это сработало. Существует ряд случаев, когда синтаксический анализатор pyparsing динамически настраивается на основе текста, который был ранее проанализирован. Фокус в том, чтобы использовать выражение-заполнитель
Forward, а затем вставить нужные значения в заполнитель как часть действия синтаксического анализа (Очень близко к тому, что у вас есть сейчас). Вот так:Element = Forward() Alphabet = OneOrMore(~lineEnd + oneOf(list(alphas))) def alphaHold(toks): Element << oneOf(toks.asList()) Alphabet.setParseAction(alphaHold)Отсюда, я думаю, что остальная часть вашего кода работает довольно хорошо, как есть. На самом деле, вам даже не понадобится функция проверки строк, как и pyparsing, будет соответствовать только допустимым именам элементов, использующих этот метод.
Вы можете обнаружить, что отчеты об ошибках pyparsing немного расплывчаты. Вы можете сделать вещи немного лучше, используя " - "вместо" + " в некоторых разумных местах. Поскольку pyparsing использует ParseExceptions для всех своих внутренних сигналов совпадений/несоответствий выражений, он не распознает автоматически, когда вы вошли в определенное выражение, но затем имеет недопустимое значение совпадение по содержимому выражения. Вы можете сказать pyparsing, чтобы обнаружить это с помощью оператора' -', например:
ListDef = listName + '=' - OneOrMore(~lineEnd + Element)Как только pyparsing получает имя и знак'=', то любой найденный недопустимый элемент немедленно вызовет
ParseSyntaxException, который остановит сканирование текста pyparsing в этой точке и сообщит об исключении в местоположении недопустимого элемента.
Comments