使用开始和结束符号使用 Megaparsec 解析块注释

Parsing block comments with Megaparsec using symbols for start and end

我想使用 Megaparsec 在 Haskell 中解析与此类似的文本。

# START SKIP
def foo(a,b):
    c = 2*a # Foo 
    return a + b
# END SKIP

,其中 # START SKIP# END SKIP 标记要解析的文本块的开始和结束。

skipBlockComment 相比,我希望解析器 return 开始和结束标记之间的行。

这是我的解析器。

skip :: Parser String
skip = s >> manyTill anyChar e
  where s = string "# START SKIP"
        e = string "# END SKIP"

skip 解析器按预期工作。

为了在开始和结束标记内允许可变数量的白色 space,例如 # START SKIP 我尝试了以下方法:

skip' :: Parser String
skip' = s >> manyTill anyChar e
  where s = symbol "#" >> symbol "START" >> symbol "SKIP"
        e = symbol "#" >> symbol "END" >> symbol "SKIP"

使用skip'解析上面的文字出现如下错误。

3:15:
unexpected 'F'
expecting "END", space, or tab

我想了解此错误的原因以及如何修复它。

正如 Alec 已经评论的那样,问题是一旦 e 遇到 '#',它就被视为 消耗的字符 。 parsec 及其衍生物的工作方式是,一旦您使用了任何字符,您就会致力于该解析分支——即 manyTill anyChar 替代方案不再被考虑,即使 e最终在这里失败了。

尽管如此,您可以通过将结束分隔符包装在 try:

中轻松请求回溯
skip' :: Parser String
skip' = s >> manyTill anyChar e
  where s = symbol "#" >> symbol "START" >> symbol "SKIP"
        e = try $ symbol "#" >> symbol "END" >> symbol "SKIP"

这将在使用 '#' 之前设置一个“检查点”,并且当 e 稍后失败时(在您的示例中,在 "Foo"),它将表现得好像没有字符完全匹配。

事实上,传统的秒差距也会为 skip 提供相同的行为。只是,因为寻找一个字符串并且只有在它完全匹配 时才会成功是一项如此常见的任务,megaparsec 的 string 的实现方式类似于 try . string,即如果失败发生在该固定字符串中,它将始终回溯。

但是,复合解析器默认情况下仍然不会回溯,就像它们在 attoparsec 中所做的那样。主要原因是,如果任何东西都可以回溯到任何一点,你就无法真正在错误消息中显示一个明确的失败点