首页 > 解决方案 > 解析器库中的 try 和 <|> 函数如何工作

问题描述

(我使用trifecta解析器库)。我正在尝试制作一个解析器,将整数解析为Right文字序列(允许使用字母、数字符号和“-”)Left

*Lib>  parseString myParser mempty "123 qwe 123qwe 123-qwe-"
Success [Right 123,Left "qwe",Left "123qwe",Left "123-qwe-"]

这就是我发明的:

myParser :: Parser [Either String Integer]
myParser = sepBy1 (try (Right . read <$> (some digit <* notFollowedBy (choice [letter, char '-'])))
           <|>          Left         <$>  some (choice [alphaNum, char '-']))
                  (char ' ')

我的问题是我不明白为什么try需要那里(以及在任何其他类似情况下)。不使用try时,出现错误:

*Lib>  parseString myParser mempty "123 qwe 123qwe 123-qwe-"
Failure (ErrInfo {_errDoc = (interactive):1:12: error: expected: digit
1 | 123 qwe 123qwe 123-qwe-<EOF>
  |            ^                 , _errDeltas = [Columns 11 11]})

所以try将解析光标放回到我们开始失败的地方。想象try不使用:

123qwe              
   ^ failed there, the cursor position remains there

另一方面,<|>就像“要么”。它应该运行第二个解析器Left <$> some (choice [alphaNum, char '-']))(当第一个解析器失败时)并只消耗“qwe”。某处我错了。

标签: parsinghaskell

解决方案


如果第二个解析器有机会运行,它确实会消耗“qwe”部分。但没有给它这样的机会。

查看for的定义(<|>)Parser

Parser m <|> Parser n = Parser $ \ eo ee co ce d bs ->
  m eo (\e -> n (\a e' -> eo a (e <> e')) (\e' -> ee (e <> e')) co ce d bs) co ce d bs

嗯...也许不是一个好主意。但是,让我们继续前进。为了理解所有这些eo,ee等,让我们看看他们Parser定义的解释:

前四个参数是行为延续:

epsilon 成功:解析器没有消耗任何输入,并且有结果以及可能的 Err;位置和块不变(见纯)

epsilon failure:解析器没有消耗任何输入,并且由于给定的 Err 而失败;位置和块不变(见空)

已提交成功:解析器已使用输入并正在产生结果、允许此解析继续的预期字符串集、新位置以及继续的剩余块。

已提交失败:解析器已使用输入并且因给定的 ErrInfo 失败(面向用户的错误消息)

在您的情况下,我们显然已经“提交失败” - 即Right解析器已经消耗了一些输入并且失败了。因此,在这种情况下,它将调用第四个延续 -ce在 的定义中表示(<|>)

现在看看定义的主体:第四个延续m不变地传递给解析器:

m eo (\e -> n (\a e' -> eo a (e <> e')) (\e' -> ee (e <> e')) co ce d bs) co ce d bs
                                                                             ^
                                                                             |
                                                                         here it is

这意味着从解析器返回的解析器将在解析器调用它(<|>)的所有情况下调用第四个延续。m这意味着在解析器因“已提交失败”而失败的所有情况下,它将因m“已提交失败”而失败。这正是您所观察到的。


推荐阅读