Parsec:"try"和"lookAhead"之间的区别?

r.s*_*cky 21 haskell parsec

parsec中"try"和"lookAhead"函数有什么区别?

J. *_*son 27

组合器trylookAhead它们的相似之处在于它们都让Parsec"倒带",但它们适用于不同的情况.特别是,try倒带失败,同时lookAhead倒带成功.

通过文档,try"假装它在发生错误时没有消耗任何输入",而"在不消耗任何输入的情况下lookAhead p解析p",但"如果p失败并消耗一些输入,那么lookAhead".

因此,如果您认为解析器在某个流状态下运行并且失败或成功,我们可能会将其写为Haskell术语

type Parser a = [Tokens] -> (Either Error a, [Tokens])
Run Code Online (Sandbox Code Playgroud)

然后try确保如果(try p) input ---> (Left err, output)然后input == outputlookAhead具有它使得(lookAhead p) input ---> (Right a, output)然后input == output,但如果(lookAhead p) input ---> (Left err, output)那么它们可被允许不同.


我们可以通过直接查看Parsec的代码来看到这一点,这比我Parser上面的概念要复杂一些.首先我们来看看ParsecT

newtype ParsecT s u m a
    = ParsecT {unParser :: forall b .
                 State s u
              -> (a -> State s u -> ParseError -> m b) -- consumed ok
              -> (ParseError -> m b)                   -- consumed err
              -> (a -> State s u -> ParseError -> m b) -- empty ok
              -> (ParseError -> m b)                   -- empty err
              -> m b
             }
Run Code Online (Sandbox Code Playgroud)

ParsecT是一种基于continuation的数据类型.如果你看看其中一个是如何构建的

ParsecT $ \s cok cerr eok eerr -> ...
Run Code Online (Sandbox Code Playgroud)

你会看到我们如何可以访问State s u,s,它决定和四个功能我们如何向前推进.例如,fail根据条款ParsecTMonad实例使用eerr选项,构建ParseError来自当前输入位置与传递错误消息.

parserFail :: String -> ParsecT s u m a
parserFail msg
    = ParsecT $ \s _ _ _ eerr ->
      eerr $ newErrorMessage (Message msg) (statePos s)
Run Code Online (Sandbox Code Playgroud)

虽然最原始的成功令牌parse(tokenPrim)使用复杂的事件序列,最终最终以cok更新的方式调用State s u.

凭借这种直觉,其来源try特别简单.

try :: ParsecT s u m a -> ParsecT s u m a
try p =
    ParsecT $ \s cok _ eok eerr ->
    unParser p s cok eerr eok eerr
Run Code Online (Sandbox Code Playgroud)

它只是ParsecT根据传递给try的那个构建一个新的,但是"empty err"继续代替消耗的错误.无论下一次看到的解析组合器try p将无法访问其实际的"consumed err"连续性,因此保护尝试不会因错误而改变其状态.

但是lookAhead更复杂

lookAhead :: (Stream s m t) => ParsecT s u m a -> ParsecT s u m a
lookAhead p         = do{ state <- getParserState
                        ; x <- p'
                        ; setParserState state
                        ; return x
                        }
    where
    p' = ParsecT $ \s cok cerr eok eerr ->
         unParser p s eok cerr eok eerr
Run Code Online (Sandbox Code Playgroud)

只检查where-clause,我们看到它取决于修改传递的解析器p以使用"empty ok"continuation代替"consumed ok"continuation.这与try所做的是对称的.此外,它确保解析器状态不受p'通过其do-block 运行此修改时发生的任何事件的影响.