【发布时间】: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 -> b 括号在右侧,c:d 括号在左侧,: 绑定比-> 更紧密。
即a -> b 计算结果为
(ConsExpr (Choice1 (Token "a")) (Token "b"))
而c:d 计算结果为
(ConsExpr (Choice2 (Token "d")) (Token "c"))
我想我可以将foldl 用于其中一个,将foldr 用于另一个,但其中还有更多的复杂性。请注意,它以一种有点奇怪的方式递归,所以"a:b:c -> e:f -> :g:h ->" 实际上是一个有效的字符串,但"-> 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