Как проверить динамически определенные элементы грамматики в 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, который я мог бы пропустить?

611   1  

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

    Ничего не найдено.