parsec中“try”和“lookAhead”函数有什么区别?
答案 0 :(得分:27)
组合子try
和lookAhead
的相似之处在于它们都让Parsec“倒带”,但它们适用于不同的情况。特别是,try
会在lookAhead
倒带成功时倒带失败。
通过文档,try
“假装在发生错误时没有消耗任何输入”lookAhead p
“解析p
而不消耗任何输入”,但是“if { {1}}失败并消耗了一些输入,p
“。
因此,如果您认为解析器在某个流状态下运行并且失败或成功,我们可能会将其写为Haskell术语
lookAhead
然后type Parser a = [Tokens] -> (Either Error a, [Tokens])
确保如果try
那么(try p) input ---> (Left err, output)
和input == output
具有lookAhead
然后(lookAhead p) input ---> (Right a, output)
,但是如果input == output
然后他们可能会有所不同。
我们可以通过直接查看Parsec的代码来看到这一点,这比我上面的(lookAhead p) input ---> (Left err, output)
概念要复杂一些。首先,我们检查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
}
是一种基于continuation的数据类型。如果你看看其中一个是如何构建的
ParsecT
您将看到我们如何访问ParsecT $ \s cok cerr eok eerr -> ...
,State s u
以及确定我们如何向前发展的四个函数。例如,s
的{{1}}实例的fail
子句使用ParsecT
选项,从当前输入位置构造Monad
并传递错误消息
eerr
虽然最原始的成功令牌解析(ParseError
)使用复杂的事件序列,但最终会使用更新的parserFail :: String -> ParsecT s u m a
parserFail msg
= ParsecT $ \s _ _ _ eerr ->
eerr $ newErrorMessage (Message msg) (statePos s)
调用tokenPrim
。
凭借这种直觉,cok
的来源特别简单。
State s u
它只是根据传递给try的那个构建一个新的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
继续代替消耗的错误。无论下一个解析组合器看到ParsecT
将无法访问其实际的"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
以使用where
延续来代替p
延续。这与"empty ok"
所做的是对称的。此外,它确保解析器状态不受通过其"consumed ok"
- 块运行此修改后的try
时发生的任何事情的影响。