从头开始构建Parsec实际上非常容易。实际的库代码本身已进行了广泛的通用化和优化,从而扭曲了核心抽象,但是,如果您是从头开始构建内容以了解更多信息,则可以用几行代码编写它。我将Applicative
在下面构建一个稍弱的解析器。
从本质上讲,我们要生产Applicative
,Parser
有一种原始的解析器值一起
satisfy :: (Char -> Bool) -> Parser Char
以及一些组合器(例如)try
,如果解析器失败,它们将“撤消”解析器
try :: Parser a -> Parser a
并且orElse
它允许我们如果第一个解析器失败,继续进行第二语法分析器。通常这实际上是用infix组合器编写的(<|>)
orElse, (<|>) :: Parser a -> Parser a -> Parser a
由于我们Applicative
需要跟踪当前的流状态并能够失败,因此我们将结合状态Applicative
和Either
应用程序来构建它。
type Error = String
newtype Parser a = P { unP :: String -> (String, Either Error a) }
instance Functor Parser where
fmap f (P st) = P $ \stream -> case st stream of
(res, Left err) -> (res, Left err)
(res, Right a ) -> (res, Right (f a))
instance Applicative Parser where
pure a = P (\stream -> (stream, Right a))
P ff <*> P xx = P $ \stream0 -> case ff stream0 of -- produce an f
(stream1, Left err) -> (stream1, Left err)
(stream1, Right f ) -> case xx stream1 of -- produce an x
(stream2, Left err) -> (stream2, Left err)
(stream2, Right x ) -> (stream2, Right (f x)) -- return (f x)
如果我们仔细地(<*>)
使用Applicative
实例中的方法,我们将看到它只是将流传f
递给-production Parser
,并获取结果流,如果成功,则将其传递给x
-production Parser
,如果它们都成功,它将返回其应用程序(f x)
。这意味着如果我们有一个产生函数的解析器和一个产生参数的解析器,我们可以对它们进行排序(<*>)
-- given
parseChar :: Char -> Parser Char
parseHi :: Parser (Char, Char) -- parses 'h' then 'i'
parseHi = pure (,) <$> parseChar 'h' <*> parseChar 'i'
我们也可以使用这种机制Applicative
来构建所需的组合器。这是satisfy
-- | Peek at the next character and return successfully if it satisfies a predicate
satisfy :: (Char -> Bool) -> Parser Char
satisfy f = P $ \stream -> case stream of
[] -> ([], Left "end of stream")
(c:cs) | f c -> (cs, Right c)
| otherwise -> (cs, Left "did not satisfy")
这是 try
-- | Run a parser but if it fails revert the stream to it's original state
try :: Parser a -> Parser a
try (P f) = P $ \stream0 -> case f stream0 of
(_ , Left err) -> (stream0, Left err)
(stream1, Right a ) -> (stream1, Right a )
这是 orElse
orElse :: Parser a -> Parser a -> Parser a
orElse (P f1) (P f2) = P $ \stream0 -> case f1 stream0 of
(stream1, Left err) -> f2 stream1
(stream1, Right a ) -> (stream1, Right a)
通常,在这一点上,我们还注意到,如果我们还提供了一个立即失败的解析器,则会与Parser
形成一个Alternative
实例orElse
,empty
instance Alternative Parser where
empty = P $ \stream -> (stream, Left "empty")
(<|>) = orElse
many = manyParser
some = someParser
我们可以编写manyParser
并someParser
作为组合器来重复运行解析器。
-- | 0 or more
manyParser :: Parser a -> Parser [a]
manyParser (P f) = P go where
go stream = case f stream of
(_ , Left err) -> (stream, Right []) -- throws away the error
(stream', Right a ) -> case go stream' of
(streamFin, Left err) -> (streamFin, Left err)
(streamFin, Right as) -> (streamFin, Right (a : as))
-- | 1 or more
someParser :: Parser a -> Parser [a]
someParser (P f) = P $ \stream -> case f stream of
(stream', Left err) -> (stream', Left err)
(stream', Right a ) ->
let (P fmany) = manyParser (P f)
in case fmany stream' of
(stream'', Left err) -> (stream'', Left err)
(stream'', Right as) -> (stream'', Right (a:as))
从这里我们可以开始在更高的抽象层次上工作。
char :: Char -> Parser Char
char c = satisfy (== c)
string :: String -> Parser String
string [] = pure []
string (c:cs) = (:) <$> char c <*> string cs
oneOf :: [Char] -> Parser Char
oneOf cs = satisfy (`elem` cs)
parens :: Parser a -> Parser a
parens parseA = dropFirstAndLast <$> char '(' <*> parseA <*> char ')'
where
dropFirstAndLast _ a _ = a
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句