看起来你应该能够摆脱一个额外的unexpected 术语。我假设你有一个看起来像这样的token 类型:
token' = number
<|> identifier
<|> ...
我可能会让每个令牌(number、identifier... 等)管理自己的空格:
number :: Parser Token
number = Number . read <$> many1 digit <* spaces
为什么不在本文末尾添加您意想不到的术语作为总括?
token' = number
<|> identifier
<|> ...
<|> unexpected'
让它消耗一个字符。为了获得更好的错误消息,您甚至可以在值中包含字符。然后,当您使用它创建一个列表时,您将获得一个 Unexpected 值,用于您的词法分析器不知道如何处理的每个字符。
unexpected' :: Parser Token
unexpected' = Unexpected <$ anyChar
最后,整个 lex 只是一个 many token'。在我的测试中,这可以很好地处理中间的无效字符。
*Main> parse (many token') "<foo>" "1 2 abc ~ ~def"
Right [Number 1,Number 2,Identifier "abc",Unexpected,Unexpected,Unexpected,Identifier "def"]
要记住的一件事是 Parsec 默认情况下不会回溯。这意味着如果解析在通过令牌的部分过程中失败,它将不会返回并尝试unexpected:您只会得到一个错误。要启用回溯,您必须在可能出错的解析器上使用try。例如,如果identifier 需要两个字符:
identifier :: Parser Token
identifier = Identifier <$> liftA2 (:) letter (many1 alphaNum) <* spaces
那么它可能会在部分过程中失败而不是回溯。但是,如果你将它包装在 try 中,它应该可以工作:
token' = number
<|> try identifier
<|> ...
try 的问题在于,如果您不小心,它会减慢您的代码速度。但是,如果您不介意速度变慢,您可能只需在各处添加try 并回溯很多就可以了!