我需要向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表现得那样,为什么我不能提出一个自定义错误信息,指向我想要的位置。
答案 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