PyParsing 查看前瞻和贪婪表达式

12 投票
1 回答
3208 浏览
提问于 2025-04-17 12:51

我正在用PyParsing写一个查询语言的解析器,但遇到了一些麻烦,我觉得这可能和“前瞻”有关。在这个查询中,有一种子句类型是用来把字符串分成三部分(字段名、操作符、值),其中字段名是一个单词,操作符可以是一个或多个单词,而值可以是一个单词、一个带引号的字符串,或者是一个用括号括起来的这些内容的列表。

我的数据看起来像这样:

author is william
author is 'william shakespeare'
author is not shakespeare
author is in (william,'the bard',shakespeare)

而我目前为这个子句写的解析器是:

fieldname = Word(alphas)

operator = OneOrMore(Word(alphas))

single_value = Word(alphas) ^ QuotedString(quoteChar="'")
list_value = Literal("(") + Group(delimitedList(single_value)) + Literal(")")
value = single_value ^ list_value

clause = fieldname + originalTextFor(operator) + value

显然,这个解析器失败了,因为operator这个部分是贪婪的,它会尽量多地获取内容,结果把value也吃掉了。从阅读其他类似的问题和文档中,我了解到我需要用NotAnyFollowedBy来管理这个前瞻,但我还没能弄明白怎么才能让它正常工作。

1 个回答

13

这里是一个很好的地方来理解解析器的工作原理。更准确地说,就是让解析器像你一样思考。问问自己,“在‘author is shakespeare’这句话中,怎么知道‘shakespeare’不是操作符的一部分?”你知道‘shakespeare’是值,因为它在查询的最后面,后面没有其他内容。所以,操作符的词不仅仅是字母组成的词,它们是那些后面没有字符串结束的字母词。现在把这种前瞻逻辑加到你对operator的定义中:

operator = OneOrMore(Word(alphas) + ~FollowedBy(StringEnd()))

我觉得这样会让你的解析效果更好。

还有一些其他的小建议:

  • 我只在可能产生歧义的情况下使用‘^’操作符,比如当我解析一个可能是整数或十六进制的字符串时。如果我用Word(nums) | Word(hexnums),那么“123ABC”可能会被错误地处理成仅仅是前面的“123”。通过把‘|’改成‘^’,所有的选择都会被测试,并选择最长的匹配。在我解析十进制或十六进制整数的例子中,我也可以通过先测试Word(hexnums)来反转选择,得到同样的结果。在你的查询语言中,引用字符串和非引用单词值之间没有混淆的可能(一个以'"开头,另一个则没有),所以没有必要使用‘^’,用‘|’就足够了。对于value = singleValue ^ listValue也是一样。

  • 给查询字符串的关键部分添加结果名称,这样后续处理会更方便:

    clause = fieldname("fieldname") + originalTextFor(operator)("operator") + value("value")

    现在你可以通过名称来访问解析后的值,而不是通过解析的位置(当你开始处理更复杂的可选字段时,这会变得棘手且容易出错):

    queryParts = clause.parseString('author is william')

    print queryParts.fieldname

    print queryParts.operator

撰写回答