编写具有多个预期完成的解析器

时间:2013-03-13 01:29:21

标签: parsing language-agnostic

假设我对布尔查询有以下语法:

 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在每次成功的令牌匹配后清除。

但这两种解决方案都有点被黑客攻击和笨拙。我觉得我缺少一些基本的,一些模式,可以优雅地处理这个问题。它是什么?

1 个答案:

答案 0 :(得分:1)

构建L(AL)R(1)状态机。有关LALR的更多详细信息,请参见here

您要用于错误报告的是每个州的点设置FIRSTOF。当您了解如何生成解析器状态时,此答案将有意义。

如果您拥有这样的一组状态,您可以记录每个状态的FIRSTOF集的并集;然后当处于该状态,并且不可能进行转换时,您的错误消息是“Exepect(firstof currentstate)”。

如果您不想记录第一组,您可以轻松编写一种算法,该算法将遍历状态表以重建它。那算法相当于你的“偷看”。