假设我对布尔查询有以下语法:
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中一样,在试图寻找“&amp;&amp;”时,我可能会抛出一个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
在每次成功的令牌匹配后清除。
但这两种解决方案都有点被黑客攻击和笨拙。我觉得我缺少一些基本的,一些模式,可以优雅地处理这个问题。它是什么?
答案 0 :(得分:1)
构建L(AL)R(1)状态机。有关LALR的更多详细信息,请参见here。
您要用于错误报告的是每个州的点设置FIRSTOF。当您了解如何生成解析器状态时,此答案将有意义。
如果您拥有这样的一组状态,您可以记录每个状态的FIRSTOF集的并集;然后当处于该状态,并且不可能进行转换时,您的错误消息是“Exepect(firstof currentstate)”。
如果您不想记录第一组,您可以轻松编写一种算法,该算法将遍历状态表以重建它。那算法相当于你的“偷看”。