Подтвердить что ты не робот

Улучшение сообщений об ошибках с помощью pyparsing

Изменить: Я сделал первую версию, и Эйке помог мне немного продвинуться. Я теперь придерживаюсь более конкретной проблемы, о которой я расскажу ниже. Вы можете посмотреть исходный вопрос в истории


Я использую pyparsing для синтаксического анализа небольшого языка, используемого для запроса конкретных данных из базы данных. Он содержит множество ключевых слов, операторов и типов данных, а также логическую логику.

Я пытаюсь улучшить сообщение об ошибке, отправленное пользователю, когда он делает синтаксическую ошибку, поскольку текущий не очень полезен. Я разработал небольшой пример, похожий на то, что я делаю, с языком, упомянутым выше, но намного меньшим:

#!/usr/bin/env python                            

from pyparsing import *

def validate_number(s, loc, tokens):
    if int(tokens[0]) != 0:
        raise ParseFatalException(s, loc, "number musth be 0")

def fail(s, loc, tokens):
    raise ParseFatalException(s, loc, "Unknown token %s" % tokens[0])

def fail_value(s, loc, expr, err):
    raise ParseFatalException(s, loc, "Wrong value")

number =  Word(nums).setParseAction(validate_number).setFailAction(fail_value)
operator = Literal("=")

error = Word(alphas).setParseAction(fail)
rules = MatchFirst([
    Literal('x') + operator + number,
])

rules = operatorPrecedence(rules | error , [
    (Literal("and"), 2, opAssoc.RIGHT),
])

def try_parse(expression):
    try:
        rules.parseString(expression, parseAll=True)
    except Exception as e:
        msg = str(e)
        print("%s: %s" % (msg, expression))
        print(" " * (len("%s: " % msg) + (e.loc)) + "^^^")

Таким образом, в основном, единственное, что мы можем сделать с этим языком, - это запись серии x = 0, соединенная вместе с and и скобкой.

Теперь есть случаи, когда используются and и скобки, где отчет об ошибках не очень хорош. Рассмотрим следующие примеры:

>>> try_parse("x = a and x = 0") # This one is actually good!
Wrong value (at char 4), (line:1, col:5): x = a and x = 0
                                              ^^^
>>> try_parse("x = 0 and x = a")
Expected end of text (at char 6), (line:1, col:1): x = 0 and x = a
                                                         ^^^
>>> try_parse("x = 0 and (x = 0 and (x = 0 and (x = a)))")
Expected end of text (at char 6), (line:1, col:1): x = 0 and (x = 0 and (x = 0 and (x = a)))
                                                         ^^^
>>> try_parse("x = 0 and (x = 0 and (x = 0 and (x = 0)))")
Expected end of text (at char 6), (line:1, col:1): x = 0 and (x = 0 and (x = 0 and (xxxxxxxx = 0)))
                                                         ^^^

На самом деле, кажется, что если синтаксический анализатор не может разбираться (и синтаксический анализ здесь важен), то после and он больше не создает хороших сообщений об ошибках: (

И я имею в виду parse, поскольку, если он может разобрать 5, но "проверка" не выполняется в синтаксическом анализе, он все равно создает хорошее сообщение об ошибке. Но если он не может проанализировать допустимое число (например, a) или допустимое ключевое слово (например, xxxxxx), он перестает создавать правильные сообщения об ошибках.

Любая идея?

4b9b3361

Ответ 1

Pyparsing всегда будет иметь несколько плохих сообщений об ошибках, потому что он отступает. Сообщение об ошибке генерируется в последнем правиле, которое пытается выполнить парсер. Парсер не может знать, где ошибка на самом деле, он знает только, что не существует соответствующего правила.

Для хороших сообщений об ошибках вам нужен парсер, который рано встает. Эти синтаксические анализаторы менее гибки, чем Pyparsing, но большинство обычных языков программирования могут анализироваться с помощью таких синтаксических анализаторов. (С++ и Scala IMHO не могут.)

Чтобы улучшить сообщения об ошибках в Pyparsing, используйте оператор -, он работает как оператор +, но он не отступает. Вы бы использовали его следующим образом:

assignment = Literal("let") - varname - "=" - expression

Вот небольшая статья об улучшении отчетности об ошибках, автор Pyparsing:

http://pyparsing.wikispaces.com/message/view/home/30875955#30901387

Edit

Вы также можете генерировать хорошие сообщения об ошибках для недопустимых чисел в действиях синтаксического анализа, которые выполняют проверку. Если номер недействителен, вы вызываете исключение, которое не попадает в Pyparsing. Это исключение может содержать хорошее сообщение об ошибке.

Действия анализа могут иметь три аргумента [1]:

  • s = исходная строка, обрабатываемая (см. примечание ниже)
  • loc = расположение соответствующей подстроки
  • toks = список совпадающих токенов, упакованных как объект ParseResults

Существует также три полезных вспомогательных метода для создания хороших сообщений об ошибках [2]:

  • lineno(loc, string) - функция, указывающая номер строки в строке; первая строка - строка 1, новые строки запускают новые строки.
  • col(loc, string) - функция для указания номера столбца местоположения внутри строки; первый столбец - столбец 1, новые строки reset номер столбца - 1.
  • line(loc, string) - функция для получения строки текста, представляющей lineno(loc, string). Полезно при печати диагностических сообщений для исключений.

Ваше действие синтаксического анализа будет выглядеть следующим образом:

def validate_odd_number(s, loc, toks):
    value = toks[0]
    value = int(value)
    if value % 2 == 0:
        raise MyFatalParseException(
            "not an odd number. Line {l}, column {c}.".format(l=lineno(loc, s),
                                                              c=col(loc, s)))

[1] http://pythonhosted.org/pyparsing/pyparsing.pyparsing.ParserElement-class.html#setParseAction

[2] http://pyparsing.wikispaces.com/HowToUsePyparsing

Edit

Здесь [3] - улучшенная версия вопроса current (2013-4-10) script. Он правильно отображает примеры ошибок, но другая ошибка указана в неправильном положении. Я считаю, что есть ошибки в моей версии Pyparsing ( "1.5.7" ), но, возможно, я просто не понимаю, как работает Pyparsing. Проблемы:

  • ParseFatalException, похоже, не всегда является фатальным. script работает как ожидалось, когда я использую свое собственное исключение.
  • Оператор -, похоже, не работает.

[3] http://pastebin.com/7E4kSnkm