从头开始构建 Parsec 实际上非常容易。实际的库代码本身是高度概括和优化的,这会扭曲核心抽象,但如果您只是从头开始构建东西以了解更多关于正在发生的事情,您只需几行代码即可编写它。我将在下面构建一个稍弱的Applicative 解析器。
本质上,我们想要生成Applicative、Parser 以及原始解析器值
satisfy :: (Char -> Bool) -> Parser Char
还有一些组合符,例如 try,如果解析器失败,它们会“撤消”解析器
try :: Parser a -> Parser a
和orElse 如果第一个解析器失败,我们可以继续使用第二个解析器。通常这实际上是用中缀组合符(<|>)
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-生成Parser,获取结果流,如果成功,将其传递给x-produce 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)
通常在这一点上,如果我们还提供一个立即失败的解析器 empty,Parser 将与 orElse 形成一个 Alternative 实例
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