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

Bol*_*eth 5 haskell parsec

我需要给失败消息以秒为单位的给定位置。

在给出意外错误消息之前,我尝试通过设置位置来进行操作,但是它没有用:

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

回馈

Left (line 1, column 7):
unexpected end of input
expecting letter or digit
Run Code Online (Sandbox Code Playgroud)

正确的答案是:

unexpected abccbb
expecting letter or digit
Run Code Online (Sandbox Code Playgroud)

通过省略可以生产(位置错误) setPosition pos0 >>代码。

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

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

pat*_*pat 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
Run Code Online (Sandbox Code Playgroud)

在您的示例中,many1到达字符串末尾,并在第 7 列生成错误。此错误不会导致失败,但会被记住。当您将该列设置回 1 并使用 时unexpected,它会在第 1 列中创建一个错误。绑定运算符适用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)
Run Code Online (Sandbox Code Playgroud)

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

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

这会many1 alphaNum在一个隔离的上下文中运行解析器,该上下文似乎没有消耗任何输入。如果结果是回文,我们将解析器状态设置回其末尾的位置many1 alphaNum并返回其结果。否则报错unexpected id,会注册到many1 alphaNum开始的位置。

所以现在,

main :: IO ()
main = print $ runParser (palindrome <* eof) () "" "Bolton"
Run Code Online (Sandbox Code Playgroud)

印刷:

Left (line 1, column 1):
unexpected "Bolton"
expecting palindrome
Run Code Online (Sandbox Code Playgroud)