在Haskell中从头开始编写解析器

时间:2013-12-18 14:26:44

标签: parsing haskell

是否有从头开始在Haskell中为给定语法编写解析器的好教程?

我找到了:

  1. parsing expressions and statements (HaskellWiki)

  2. Parsing a simple imperative language (HaskellWiki)

  3. Using parsec (Real World Haskell)

  4. 但所有这些都使用了parsec库,虽然这对于工业应用程序可能很有意思,但我特别在寻找没有使用复杂库的“自下而上”的例子。

    我找到的唯一使用'基本'Haskell的是:Parsing with Haskell 它使用了一些非常外来的语法(很难区分程序的一部分或者只有'伪代码')并且没有明确的语法定义。

    任何建议都非常感谢!

2 个答案:

答案 0 :(得分:36)

从头开始构建Parsec实际上非常容易。实际的库代码本身是大量推广和优化的,这会扭曲核心抽象,但是如果你只是从头开始构建东西以了解更多关于正在发生的事情,你可以用几行代码编写它。我将在下面构建一个稍弱的Applicative解析器。

基本上,我们希望生成ApplicativeParser以及原始解析器值

satisfy :: (Char -> Bool) -> Parser Char

以及一些组合器,例如try,如果失败则“撤消”解析器

try :: Parser a -> Parser a

orElse,如果第一个解析器失败,它允许我们继续第二个解析器。通常这实际上是使用中缀组合器(<|>)

编写的
orElse, (<|>) :: Parser a -> Parser a -> Parser a

由于我们Applicative需要跟踪当前流状态并且能够失败,我们将通过组合状态ApplicativeEither应用程序来构建它。

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

,则会orElseempty形成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应用式解析器组合器。