11

编辑:我做了第一个版本,Eike 帮助我在上面做了很多改进。我现在被困在一个更具体的问题上,我将在下面描述。你可以看看历史上的原始问题


我正在使用 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)))
                                                         ^^^

实际上,如果解析器无法解析(并且在这里解析很重要) a 之后的某些内容and,它就不会再产生好的错误消息了:(

我的意思是parse,因为如果它可以解析 5 但解析操作中的“验证”失败,它仍然会产生一个很好的错误消息。但是,如果它无法解析有效数字(如a)或有效关键字(如xxxxxx),它将停止生成正确的错误消息。

任何想法?

4

1 回答 1

12

Pyparsing 总是会有一些不好的错误消息,因为它会回溯。错误消息是在解析器尝试的最后一条规则中生成的。解析器无法知道错误到底在哪里,它只知道没有匹配规则。

对于好的错误消息,您需要一个提前放弃的解析器。这些解析器不如 Pyparsing 灵活,但大多数传统的编程语言都可以用这样的解析器进行解析。(C++ 和 Scala 恕我直言不能。)

要改进 Pyparsing 中的错误消息,请使用-运算符,它的工作方式与+运算符类似,但不会回溯。你会像这样使用它:

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

这是一篇关于改进错误报告的小文章,由 Pyparsing 的作者撰写。

编辑

您还可以为执行验证的解析操作中的无效数字生成良好的错误消息。如果数字无效,则会引发 Pyparsing 未捕获的异常。此异常可以包含一个很好的错误消息。

解析动作可以有三个参数 [1]:

  • s = 被解析的原始字符串(见下面的注释)
  • loc = 匹配子串的位置
  • toks = 匹配标记的列表,打包为ParseResults对象

还有三种有用的帮助方法可用于创建良好的错误消息 [2]:

  • lineno(loc, string)- 函数给出字符串中位置的行号;第一行是第 1 行,换行符开始新行。
  • col(loc, string)- 函数给出字符串中位置的列号;第一列是第 1 列,换行符将列号重置为 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]如何使用Pyparsing

编辑

这里 [3] 是问题当前 (2013-4-10) 脚本的改进版本。它使示例错误正确,但其他错误指示​​在错误的位置。我相信我的 Pyparsing 版本('1.5.7')中存在错误,但也许我只是不明白 Pyparsing 是如何工作的。问题是:

  • ParseFatalException 似乎并不总是致命的。当我使用自己的异常时,脚本按预期工作。
  • -操作员似乎不工作。

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

于 2013-04-09T08:59:22.857 回答