我正在攻读我大学的编译器课程。我选择使用Haskell + Parsec来完成项目。词法分析器和解析器必须是分开的。我正在使用Parsec将字符串转换为令牌列表,然后将其传递到另一个将令牌列表转换为AST的Parsec解析器。
问题是词法分析器应该继续尝试lex,即使出现错误。为了尝试这样做,我向我的令牌数据类型引入了一个代表“意外令牌”的令牌,我尝试用< |>来填充我的代码。出现意外情况,以便在出现错误时生成此令牌。这是很多样板,也很难说出放置这些的位置。
我首选的解决方案是以某种方式使Parsec自动执行此操作:如果存在ParseError,则在该位置生成意外的标记,并在稍后继续解析一个位置。我该怎么做?
以下是我现有的一些代码片段:http://lpaste.net/8144414997276000256 出于某种原因,我仍然可以得到一个解析错误,即使Unexpected令牌应该捕获未处理的案例。
答案 0 :(得分:1)
似乎你应该能够获得一个额外的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
并回溯很多就可以侥幸成功!