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

朱尔斯

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

我发现:

  1. 解析表达式和语句(HaskellWiki)

  2. 解析一种简单的命令式语言(HaskellWiki)

  3. 使用parsec(真实世界Haskell)

但是所有这些都使用了parsec库,尽管这对于工业应用程序可能很有趣,但我正在专门寻找不使用复杂库而“自下而上”的示例。

我发现的唯一使用“基本” Haskell的代码是:用Haskell进行解析,它使用一些非常陌生的语法(很难区分程序的什么部分或仅是“伪代码”的部分),并且没有明确的语法定义。

任何建议都将受到高度赞赏!

J·亚伯拉罕森

从头开始构建Parsec实际上非常容易。实际的库代码本身已进行了广泛的通用化和优化,从而扭曲了核心抽象,但是,如果您是从头开始构建内容以了解更多信息,则可以用几行代码编写它。我将Applicative在下面构建一个稍弱的解析器。

从本质上讲,我们要生产ApplicativeParser有一种原始的解析器值一起

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

以及一些组合器(例如)try,如果解析器失败,它们将“撤消”解析器

try :: Parser a -> Parser a

并且orElse它允许我们如果第一个解析器失败,继续进行第二语法分析器。通常这实际上是用infix组合器编写的(<|>)

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递给-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实例orElseempty

instance Alternative Parser where
  empty = P $ \stream -> (stream, Left "empty")
  (<|>) = orElse

  many = manyParser
  some = someParser

我们可以编写manyParsersomeParser作为组合器来重复运行解析器。

-- | 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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

从头开始编写getElementByName

来自分类Dev

从头开始在Ansible中配置服务器

来自分类Dev

从头开始或从迭代器创建流

来自分类Dev

在MATLAB中从头开始编写基本神经网络

来自分类Dev

如何从头开始编译C编译器,然后从头开始编译Unix / Linux

来自分类Dev

从头开始的甘特图

来自分类Dev

从头开始编写非常基本的RTOS的最佳参考

来自分类Dev

如何使用python从头开始编写Midi文件

来自分类Dev

从头开始编写Win32安装程序

来自分类Dev

从头开始编写/绘制我自己的ListBox / ListView控件

来自分类Dev

如何在Xcode中从头开始创建新的Perfect Project(Swift服务器)?

来自分类Dev

Java Android-XmlPullParser-如何重新从头开始解析?

来自分类Dev

Haskell-编写一个小解析器

来自分类Dev

用 Haskell 为 Person 编写解析器

来自分类Dev

在Haskell中合并解析器

来自分类Dev

如何从头开始显示在线计数器

来自分类Dev

从头开始在Rails上设置Like计数器

来自分类Dev

从头开始实现 TF-IDF 向量化器

来自分类Dev

Eclipse断点调试从头开始,而不是从头开始

来自分类Dev

添加块-从头开始

来自分类Dev

从头开始的Subversion控制

来自分类Dev

从头开始嵌套评论

来自分类Dev

从头开始安装Ubuntu

来自分类Dev

从头开始实现互斥

来自分类Dev

从头开始异步处理

来自分类Dev

如何使HLS从头开始

来自分类Dev

从头开始安装Ubuntu

来自分类Dev

从头开始学习PTX

来自分类Dev

Docker,从头开始镜像