是否有从头开始在Haskell中为给定语法编写解析器的好教程?
我找到了:
但所有这些都使用了parsec库,虽然这对于工业应用程序可能很有意思,但我特别在寻找没有使用复杂库的“自下而上”的例子。
我找到的唯一使用'基本'Haskell的是:Parsing with Haskell 它使用了一些非常外来的语法(很难区分程序的一部分或者只有'伪代码')并且没有明确的语法定义。
任何建议都非常感谢!
答案 0 :(得分:36)
从头开始构建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
- 生成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
从这里开始,我们可以开始在更高层次的抽象中工作。
{{1}}
答案 1 :(得分:4)
我非常喜欢Graham Hutton的“Haskell编程”。它简单介绍了monad和parser组合器。第八章构建了一个基本的解析器库。
这是链接to Programming in Haskell book site。您还可以找到解析器库和基本表达式解析器的链接。
此外,如果您有兴趣,还可以查看乌特勒支大学开发的uu-parsinglib应用式解析器组合器。