如何在parsec中给出给定位置的失败消息

时间:2014-02-24 12:19:36

标签: haskell parsec

我需要向parsec中的给定位置发送失败消息。

我尝试在给出意外错误消息之前设置位置,但它不起作用:

runParser ( do pos0 <- getPosition
               id <- many1 alphaNum
               if (id == reverse id) then return id
                                     else setPosition pos0 >> unexpected id
               eof )
          () "" "abccbb"

回馈

Left (line 1, column 7):
unexpected end of input
expecting letter or digit

正确答案是:

unexpected abccbb
expecting letter or digit

可以通过从代码中省略setPosition pos0 >>来生成(位置错误)。

我的解决方法是进行解析,在parsec的用户状态中保存正确和实际的错误位置,并更正错误位置,但我想要一个更好的解决方案。

正如AndrewC所要求的那样,它是向用户提供更多信息的错误消息的一部分。例如,在某些地方我们需要特殊的标识符,但如果它是在解析器中编码的,那么parsec会给出一个错误消息,例如“期望g,得到一个r,位置在标识符的中间”。正确的信息是“标识符以特殊格式预期,但得到'abccbb',位置在标识符之前”。如果有更好的方法可以用来给出这样的错误信息,那么这将是我们问题的正确答案。但我也很好奇为什么parsec表现得那样,为什么我不能提出一个自定义错误信息,指向我想要的位置。

1 个答案:

答案 0 :(得分:1)

这是因为解析器会收集在输入中最远位置发生的所有错误。绑定两个解析器时,这些解析器检测到的所有错误都将被mergeError合并:

mergeError :: ParseError -> ParseError -> ParseError
mergeError e1@(ParseError pos1 msgs1) e2@(ParseError pos2 msgs2)
    -- prefer meaningful errors
    | null msgs2 && not (null msgs1) = e1
    | null msgs1 && not (null msgs2) = e2
    | otherwise
    = case pos1 `compare` pos2 of
        -- select the longest match
        EQ -> ParseError pos1 (msgs1 ++ msgs2)
        GT -> e1
        LT -> e2

在您的示例中,many1到达字符串的末尾,并在第7列处生成错误。该错误不会导致失败,但是会被记住。将列重新设置为1并使用unexpected时,它将在列1中产生错误。bind运算符将mergeError应用于这两个错误,列7中的一个获胜。

使用lookAhead,我们可以编写函数isolate来运行解析器p,而不会消耗任何输入或注册任何错误。 isolate解析器返回一个元组,其中包含p的结果和p末尾的解析器状态,以便我们可以根据需要跳回到该状态:

isolate :: Stream s m t => ParsecT s u m a -> ParsecT s u m (a, (State s u))
isolate p = try . lookAhead $ do
  x <- p
  s <- getParserState
  return (x, s)

这样,我们可以实现一个palindrome解析器:

palindrome = ( do
                 (id, s) <- isolate $ many1 alphaNum
                 if (id == reverse id) then (setParserState s >> return id)
                   else unexpected $ show id
             ) <?> "palindrome"

这将在似乎没有消耗任何输入的孤立上下文中运行many1 alphaNum解析器。如果结果是回文,则将解析器状态设置回many1 alphaNum末尾的状态,然后返回其结果。否则,我们将报告一个unexpected id错误,该错误将在many1 alphaNum开始的位置记录。

现在,

main :: IO ()
main = print $ runParser (palindrome <* eof) () "" "Bolton"

打印:

Left (line 1, column 1):
unexpected "Bolton"
expecting palindrome