2013-04-09 44 views
11

编辑:我做了第一个版本,Eike帮助我在其上进行了一些改进。我现在坚持一个更具体的问题,我将描述下面的内容。您可以在history用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后的东西,它不会再产生良好的错误信息: (

我的意思是解析,因为如果它可以解析5,但“验证”的解析动作失败了,但它仍然产生了良好的错误消息。但是,如果它不能解析一个有效的数字(如a)或有效的关键字(如xxxxxx),它会停止生产清除正确的错误信息。

有什么想法?

+0

有变量名也有验证的解析动作。或者像“Word(alphas)”那样捕获所有变量名称,并对其进行解析操作,这总是会引发异常。 – Eike 2013-04-09 12:38:39

+0

另外,你可以做一级验证。有一个解析器'Word(alphas) - “==” - Word(nums)'并且在其上放置一个更复杂的解析操作,它查找合法的变量名称,并确保数字的正确性。 – Eike 2013-04-09 14:02:37

+0

目前,这将是最后的解决方案:) – 2013-04-09 14:09:43

回答

9

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

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

要改善Pyparsing中的错误消息,使用-运算符,它的运算符类似于+运算符,但它不会回溯。你会使用这样的:

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

这里是改善错误报告的小文章,通过Pyparsing的作者:

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

编辑

你也可以产生良好的错误消息在执行验证的分析操作中的无效数字。如果该数字无效,则引发Pyparsing未捕获的异常。这个例外可以包含一个很好的错误信息。

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

  • S =正在分析的原始字符串(参见下面的注释)
  • LOC =匹配子串
  • toks的位置=列表匹配的令牌,打包为ParseResults对象

的还有用于产生良好的错误消息[2]三个有用的辅助方法:

  • lineno(loc, string) - 函数给出字符串中位置的行号;第一行是第一行,换行符开始新行。
  • 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] http://pyparsing.wikispaces.com/HowToUsePyparsing

编辑

这里[3]是该问题目前的改进版本(2013年-4-10)脚本。它正确地得到了示例错误,但是其他错误在错误的位置被指示。我相信我的Pyparsing('1.5.7')版本中存在一些错误,但也许我不明白Pyparsing是如何工作的。问题是:

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

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

+0

它只有一点帮助:不是一个偶数(在字符0),(行:1,col:1):x == 1和y == 1(而错误是在“y”) – 2013-04-09 09:07:47

+0

是的,这是一个棘手的领域,我挣扎 – Eike 2013-04-09 09:31:36

+0

一个问题恕我直言是'operatorPrecedence'它重写'rules'并返回一个复杂的解析器,它可以真正解析expr分裂国家。错误消息的质量主要取决于'operatorPrecedence'的实现,而不是你的代码。 – Eike 2013-04-09 09:42:35