假设我有以下布尔查询语法:
Query := <Atom> [ <And-Chain> | <Or-Chain> ]
Atom := "(" <Query> ")" | <Var> <Op> <Var>
And-Chain := "&&" <Atom> [ <And-Chain> ]
Or-Chain := "||" <Atom> [ <Or-Chain> ]
Var := "A" | "B" | "C" | ... | "Z"
Op := "<" | ">" | "="
此语法的解析器将具有伪代码:
parse(query) :=
tokens <- tokenize(query)
parse-query(tokens)
assert-empty(tokens)
parse-query(tokens) :=
parse-atom(tokens)
if peek(tokens) equals "&&"
parse-chain(tokens, "&&")
else if peek(tokens) equals "||"
parse-chain(tokens, "||")
parse-atom(tokens) :=
if peek(tokens) equals "("
assert-equal( "(", shift(tokens) )
parse-query(tokens)
assert-equal( ")", shift(tokens) )
else
parse-var(tokens)
parse-op(tokens)
parse-var(tokens)
parse-chain(tokens, connector) :=
assert-equal( connector, shift(tokens) )
parse-atom(tokens)
if peek(tokens) equals connector
parse-chain(tokens, connector)
parse-var(tokens) :=
assert-matches( /[A-Z]/, shift(tokens) )
parse-op(tokens) :=
assert-matches( /[<>=]/, shift(tokens) )
不过,我想确保的是,我的解析器会报告有用的解析错误。例如,给定一个以 开头的查询"( A < B && B < C || ..."
,我想要一个类似的错误:
found "||" but expected "&&" or ")"
这样做的诀窍是它收集来自解析器不同部分的期望。我可以想办法做到这一点,但最终感觉有点笨拙。
就像,在 Java 中,我可能会GreedyError
在尝试查看“&&”或“||”时抛出一个
// in parseAtom
if ( tokens.peek() == "(" ) {
assertEqual( "(", tokens.shift() );
try {
parseQuery(tokens);
caught = null;
}
catch (GreedyError e) {
caught = e;
}
try {
assertEqual( ")", tokens.shift() );
}
catch (AssertionError e) {
throw e.or(caught);
}
}
// ...
// in parseChain
assertEqual( connector, tokens.shift() );
parseAtom(tokens);
if (tokens.peek() == connector) {
parseChain(tokens, connector);
}
else {
new GreedyError(connector);
}
或者,在 Haskell 中,我可能会使用它WriterT
来跟踪我对当前令牌的最后一次失败比较,用于censor
在每次成功的令牌匹配后清除。
但是这两种解决方案都让人觉得有点老套和笨拙。我觉得我错过了一些基本的东西,一些模式,可以优雅地处理这个问题。它是什么?