【问题标题】:Making attoparsec parsers recursive使 attoparsec 解析器递归
【发布时间】:2011-06-19 19:56:54
【问题描述】:

我一直在编写一个 attoparsec 解析器,并且遇到了一个模式,我想将解析器变成递归解析器(递归地将它们与 monad bind >>= 运算符组合)。

所以我创建了一个函数来将解析器转换为递归解析器,如下所示:

recursiveParser :: (a -> A.Parser a) -> a -> A.Parser a
recursiveParser parser a = (parser a >>= recursiveParser parser) <|> return a

如果您有像这样的递归数据类型,这很有用

data Expression = ConsExpr Expression Expression | EmptyExpr

parseRHS :: Expression -> Parser Expression
parseRHS e = ConsExpr e <$> parseFoo

parseExpression :: Parser Expression
parseExpression = parseLHS >>= recursiveParser parseRHS
  where parseLHS = parseRHS EmptyExpr

有没有更惯用的解决方案? recursiveParser 似乎应该是某种折叠...我还在文档中看到了 sepBy,但这种方法似乎更适合我的应用程序。

编辑:哦,实际上现在我想它实际上应该类似于fix...不知道我是怎么忘记的。

EDIT2:Rotsor 在我的示例中用他的替代方案提出了一个很好的观点,但恐怕我的 AST 实际上比这要复杂一些。它实际上看起来更像这样(尽管这仍然是简化的)

data Segment = Choice1 Expression
             | Choice2 Expression
data Expression = ConsExpr Segment Expression 
                | Token String
                | EmptyExpr

其中字符串a -&gt; b 括号在右侧,c:d 括号在左侧,: 绑定比-&gt; 更紧密。

a -&gt; b 计算结果为

(ConsExpr (Choice1 (Token "a")) (Token "b"))

c:d 计算结果为

(ConsExpr (Choice2 (Token "d")) (Token "c"))

我想我可以将foldl 用于其中一个,将foldr 用于另一个,但其中还有更多的复杂性。请注意,它以一种有点奇怪的方式递归,所以"a:b:c -&gt; e:f -&gt; :g:h -&gt;" 实际上是一个有效的字符串,但"-&gt; a""b:" 不是。最后fix 对我来说似乎更简单。我已经像这样重命名了递归方法:

fixParser :: (a -> A.Parser a) -> a -> A.Parser a
fixParser parser a = (parser a >>= fixParser parser) <|> pure a

谢谢。

【问题讨论】:

  • 只是好奇 - 使用 Attoparsec 而不是 Parsec 3 中的 Stream ByteString m Char 有什么好处吗?
  • 老实说,我对 Haskell 解析库不是很熟悉,但是 Attoparsec 似乎非常轻量级和高性能 (serpentine.com/blog/2010/03/03/…),这正是我想要的这个应用程序。此外,我不太关心体面的错误消息,我认为这是 Parsec 的主要好处。但是,如果我必须解析像 C 这样具有挑战性的东西,我肯定会选择 Parsec。
  • 有趣的基准测试。我没有意识到 attoparsec 的性能提升不仅仅来自使用 ByteString - 这是我假设的,因为我认为它是在 Parsec 2 时代创建的。
  • @monadic 是的,看起来 Bryan O'Sullivan 在微调这个方面做得非常出色

标签: parsing haskell attoparsec


【解决方案1】:

为什么不直接解析一个列表并将其折叠成你想要的任何东西呢? 也许我遗漏了一些东西,但这对我来说看起来更自然:

consChain :: [Expression] -> Expression
consChain = foldl ConsExpr EmptyExpr

parseExpression :: Parser Expression
parseExpression = consChain <$> many1 parseFoo

而且它也更短。

如您所见,consChain 现在独立于解析,可以在其他地方使用。此外,如果将结果折叠分开,在这种情况下,有些不直观的递归解析会简化为 manymany1

您可能还想看看many 是如何实现的:

many :: (Alternative f) => f a -> f [a]
many v = many_v
    where many_v = some_v <|> pure []
          some_v = (:) <$> v <*> many_v

它和你的recursiveParser有很多共同点:

  • some_v 类似于 parser a &gt;&gt;= recursiveParser parser
  • many_v 类似于 recursiveParser parser

您可能会问为什么我称您的递归解析器函数不直观。这是因为这种模式允许 parser 参数影响解析行为(a -&gt; A.Parser a,还记得吗?),这可能有用,但不是很明显(我还没有看到这方面的用例)。您的示例未使用此功能这一事实使其看起来多余。

【讨论】:

  • 第二个答案 - 解析一个列表,然后使用智能构造函数将其折叠成一个汇总值是惯用的解决方案。
  • 谢谢,这是我问题的一个很好的答案。我认为许多对我不起作用的原因是我的 AST 实际上涉及一个选择,它不像我在问题中给出的那么简单。这是一个很好的答案
  • @Rehno,您能否用一个额外的用例扩展您的问题,充分利用recursiveParser 的功能?我很好奇。另外,我很乐意尝试改进我的答案!
猜你喜欢
  • 1970-01-01
  • 2012-08-11
  • 2014-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-07
相关资源
最近更新 更多