Python 使用pyparsing改进错误消息
编辑:我制作了第一个版本,Eike帮助我在这方面取得了相当大的进步。我现在陷入了一个更具体的问题,我将在下面描述它。你可以看一下表格中的原始问题Python 使用pyparsing改进错误消息,python,pyparsing,Python,Pyparsing,编辑:我制作了第一个版本,Eike帮助我在这方面取得了相当大的进步。我现在陷入了一个更具体的问题,我将在下面描述它。你可以看一下表格中的原始问题 我使用pyparsing来解析一种用于从数据库请求特定数据的小型语言。它具有许多关键字、运算符和数据类型以及布尔逻辑 我试图改进当用户出现语法错误时发送给他的错误消息,因为当前的错误消息不是很有用。我设计了一个小示例,与我使用上述语言所做的类似,但要小得多: #!/usr/bin/env python
我使用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
,加上和
和括号
现在,当使用和
和括号时,错误报告不是很好。考虑下面的例子:
>>> 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)))
^^^
实际上,如果解析器在和之后不能解析(这里的解析很重要),那么它就不会再产生好的错误消息:(
我的意思是parse,因为如果它可以解析5,但在解析操作中“验证”失败,它仍然会生成一条很好的错误消息。但是,如果它不能解析有效的数字(如a
)或有效的关键字(如xxxxxx
),它就会停止生成正确的错误消息
有什么想法吗?Pyparsing总是会有一些不好的错误消息,因为它会回溯。错误消息是在解析器尝试的最后一个规则中生成的。解析器不知道错误真正在哪里,它只知道没有匹配规则
要获得好的错误消息,您需要一个尽早放弃的解析器。这些解析器不如Pyparsing灵活,但大多数传统编程语言都可以使用这种解析器进行解析。(C++和Scala IMHO不能。)
要改进Pyparsing中的错误消息,请使用-
运算符,它与+
运算符类似,但不会回溯。您可以这样使用它:
assignment = Literal("let") - varname - "=" - expression
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)))
这是Pyparsing的作者写的一篇文章
编辑
您还可以为执行验证的解析操作中的无效数字生成正确的错误消息。如果数字无效,则会引发Pyparsing未捕获的异常。此异常可能包含正确的错误消息
解析操作可以有三个参数[1]:
- s=正在解析的原始字符串(请参见下面的注释)
- loc=匹配子字符串的位置
- toks=匹配令牌的列表,打包为
ParseResults
对象
还有三种有用的帮助方法可用于创建良好的错误消息[2]:
lineno(loc,string)
-函数用于给出字符串中位置的行号;第一行是第1行,换行符开始新行
col(loc,string)
-函数用于给出字符串中位置的列号;第一列是第1列,换行符将列号重置为1
line(loc,string)
-用于检索表示lineno(loc,string)
的文本行。在打印异常诊断消息时非常有用
然后,验证分析操作将如下所示:
assignment = Literal("let") - varname - "=" - expression
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]
[2]
编辑
这里[3]是问题当前(2013-4-10)脚本的一个改进版本。它正确地显示了示例错误,但在错误的位置指示了其他错误。我相信我的Pyparsing(“1.5.7”)版本中存在错误,但可能我不理解Pyparsing是如何工作的。问题是:
- ParseFatalException似乎并不总是致命的。当我使用自己的异常时,脚本会按预期工作
-
操作员似乎不工作
[3] 它只起到了很小的作用:不是偶数(在字符0处),(行:1,列:1):x==1,y==1(而错误在“y”上)是的,这是一个棘手的领域,我也在努力获取好的错误消息。我遇到的一个问题是operatorPrecedence
它重写规则并返回一个复杂的解析器,它可以真正解析表达式。错误消息的质量主要取决于operatorPrecedence
的实现,而不是取决于您的代码。基本上你必须设计你的语言,以获得良好的错误报告。要获得良好的错误消息,有一种非常愚蠢的语言是有帮助的:var a as Int;let a=2;
我的语言本质上非常简单:7个比较运算符(=,!=,=,and in),一些布尔运算符(not,and,or,xor),它们只是关键字OP值“组合在一起。这真的非常简单。值可能有点棘手,但我插入了一个完整的验证框架,它工作得很好,除了值不好时,在我的OP中会出现错误:/对变量名也有一个验证解析操作。或者有一个全面的变量名,如Word(alphas)
,并在其上放置一个总是引发异常的解析操作。或者,您可以在一个级别上执行验证。拥有一个解析器Word(alphas)-“==”-Word(nums)
并对其执行更复杂的解析操作,以查找合法的变量名,并确保数字的正确性。目前,这将是最后的解决方案:)原因很简单:解析器回溯:“a=0”
是一个完整的程序。解析器恰好在最后一次测试这个假设,而且它也失败了,因为在“a=0”
之后有更多的文本。这就是解析器希望“文本结束”的原因。它基本上是操作员接收
的一个实现细节。尽可能插入-
以防止回溯,这将导致错误