如何在parsec中给给定位置失败消息 [英] How to give a fail message to a given position in parsec

查看:250
本文介绍了如何在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会这样,为什么我不能引发自定义错误消息,指向我想要的位置.

解决方案

这是因为解析器会收集在输入中最远位置发生的所有错误.绑定两个解析器时,这些解析器检测到的所有错误都将被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

I need to give a failure message to a given position in parsec.

I tried by setting the position before giving an unexpected error message, but it didn't work:

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

Gives back

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

While the correct response is:

unexpected abccbb
expecting letter or digit

It can be produced (with a wrong position), by omitting setPosition pos0 >> from the code.

My workaround is to do the parsing, save the correct and the actual error position in the user state of parsec, and correct the error position, but I would like a better solution.

As it was asked by AndrewC, it is part of giving error messages with more information to our users. For example, in some places we want special identifiers, but if it was encoded in the parser, parsec would given an error message like "expected a g, got an r, position is in the middle of an identifier". The correct message would be, "identifier expected in the special format, but got 'abccbb', position is before the identifier". If there is a better approach that can be used to give error messages like this, it would be a correct answer to our question. But I 'm also curious about why parsec behaves like that, and why cannot I raise a custom error message , pointing to the position I want to.

解决方案

This is because the parser collects all errors that occurred at the furthest position in the input. When binding two parsers, any errors detected by those parsers are merged by 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

In your example, the many1 reaches the end-of-string, and generates an error at column 7. This error does not result in failure, but it is remembered. When you set the column back to 1, and use unexpected, it creates an error in column 1. The bind operator applies mergeError to the two errors, and the one at column 7 wins.

Using lookAhead, we can write a function isolate to run a parser p without appearing to consume any input or register any errors. The isolate parser returns a tuple containing the result of p and the parser state at the end of p so that we can jump back to that state if we so desire:

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)

With that, we can implement a palindrome parser:

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

This runs the many1 alphaNum parser in an isolated context that does not appear to have consumed any input. If the result is a palindrome, we set the parser state back to where it was at the end of the many1 alphaNum and return its result. Otherwise, we report an unexpected id error, which will be registered at the position where the many1 alphaNum started.

So now,

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

Prints:

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

这篇关于如何在parsec中给给定位置失败消息的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆