我使用
pyparsing
编写了一个小解析器来解析类似 Google 的搜索字符串,例如 foo AND (bar OR baz)
(完整代码如下)。像谷歌一样,我想让解析器完全容错。它应该忽略错误并尽可能多地解析。
我想知道是否应该以某种方式调整我的语法(这对我来说看起来非常困难)或添加一些预处理以使搜索字符串在解析时始终有效(但有很多极端情况;请参阅下面的测试中的无效表达式)。
我还考虑过使用 pyparsing 的
search_string
而不是 parse_string
,它似乎永远不会引发异常,但输出通常对我的用例来说并不是真正有用(例如 foo AND OR bar
=> [[TermNode(WORD, foo)], [BinaryNode(OR, TermNode(WORD, ND), TermNode(WORD, bar))]]
)
import pyparsing as pp
from typing import Literal
class TermNode:
def __init__(self, term_type: Literal["WORD", "PHRASE"], value: "Node"):
self.term_type = term_type
self.value = value
def __repr__(self):
return f"TermNode({self.term_type}, {self.value})"
class UnaryNode:
def __init__(self, operator: Literal["NOT"], operand: "Node"):
self.operator = operator
self.operand = operand
def __repr__(self):
return f"UnaryNode({self.operator}, {self.operand})"
class BinaryNode:
def __init__(self, operator: Literal["AND", "OR"], left: "Node", right: "Node"):
self.operator = operator
self.left = left
self.right = right
def __repr__(self):
return f"BinaryNode({self.operator}, {self.left}, {self.right})"
Node = TermNode | UnaryNode | BinaryNode
not_ = pp.Keyword("NOT")
and_ = pp.Keyword("AND")
or_ = pp.Keyword("OR")
lparen = pp.Literal("(")
rparen = pp.Literal(")")
extra_chars = "_-'"
word = ~(not_ | and_ | or_) + pp.Word(pp.alphanums + pp.alphas8bit + extra_chars).set_parse_action(lambda t: TermNode("WORD", t[0]))
phrase = pp.QuotedString(quoteChar='"').set_parse_action(lambda t: TermNode("PHRASE", t[0]))
term = (phrase | word)
or_expression = pp.Forward()
parens_expression = pp.Forward()
parens_expression <<= (pp.Suppress(lparen) + or_expression + pp.Suppress(rparen)) | term
not_expression = pp.Forward()
not_expression <<= (not_ + not_expression).set_parse_action(lambda t: UnaryNode("NOT", t[1])) | parens_expression
and_expression = pp.Forward()
and_expression <<= (not_expression + and_ + and_expression).set_parse_action(lambda t: BinaryNode("AND", t[0], t[2])) | (not_expression + and_expression).set_parse_action(lambda t: BinaryNode("AND", t[0], t[1])) | not_expression
or_expression <<= (and_expression + or_ + or_expression).set_parse_action(lambda t: BinaryNode("OR", t[0], t[2])) | and_expression
#or_expression.parse_string('', parse_all=True)
or_expression.run_tests("""\
###
# Valid expressions
###
# Word term
foobar
# Umlaute in word term
Gürtel
# Phrase term
"foo bar"
# Special characters in phrase
"foo!~ bar %"
# Implicit AND
foo bar
# Explicit AND
foo AND bar
# Explicit OR
foo OR bar
# NOT
NOT foo
# Parenthesis
foo AND (bar OR baz)
# Complex expression 1
NOT foo AND ("bar baz" OR qux)
# Complex expression 2
foo AND (NOT "bar baz" (moo OR zoo) AND yoo)
# Complex expression 3
foo (bar NOT "baz moo") zoo
###
# Invalid expressions
###
# Unary before binary operator
foo NOT AND bar
# Invalid redundant operators
foo AND OR bar
# Unknown char outside quoted terms
foo ~ bar
# Binary operator at start of line
AND foo
# Binary operator at start of parens expression
(AND bar)
# Binary operator at end of line
foo AND
# Binary operator at end of parens expression
(foo AND)
# Unary operator at end of line
foo NOT
# Unary operator at end of parens expression
(foo NOT)
# Unbalanced parens
((foo)
# Unbalanced quotes
""foo"
""");
看起来您已经使用 pyparsing 为您的解析器奠定了坚实的基础。为了使其更具容错性并优雅地处理无效表达式,您可以考虑以下方法:
错误处理:您可以修改解析器以通过忽略错误或提供有关表达式的哪些部分未正确解析的反馈来优雅地处理错误,而不是引发无效表达式的异常。
预处理:正如您所提到的,在解析搜索字符串之前对其进行预处理可以帮助使其更加一致有效。您可以实施一些预处理步骤来清理输入字符串,例如删除冗余运算符或平衡括号。
改编语法:您可能需要调整语法以使其更加宽容和灵活,从而允许输入语法的变化。这可能涉及使某些元素可选或为不同的输入模式提供替代方案。
部分解析:如果输入的某些部分无效,您仍然可以尝试解析并提取表达式的有效部分。这对于只有部分输入不正确的情况很有用。
反馈机制:向用户提供关于输入的哪些部分被成功解析以及哪些部分由于错误而被忽略的反馈。这可以帮助用户理解为什么他们的输入没有产生预期的结果。
通过组合这些方法,您可以创建一个容错解析器,它可以处理各种输入变化,即使存在错误也可以提供有用的输出。