在 Attoparsec 中解析时实现 "includes"

Implementing "includes" when parsing in Attoparsec

我写 DSL 是为了好玩。我决定使用 attoparsec 因为我很熟悉它。

我想像这样实现包含相对文件名的解析:

include /some/dir/file.ext

或网址:

include http://blah.com/my/file.ext

所以当我解析时,我希望读取引用的资源并解析整个内容,将其内容附加到 "outer" 解析状态。

问题是虽然这些语句的解析很容易,但我无法在我的 Attoparsec 解析器中 运行 IO(据我所知)。

如何使用 Attoparsec 来实现这一点?我是否使用一些字符串过滤将初始输入切碎,然后将每个 "block" 相应地解析为 parsefeed?本质上是一种两遍解析方法?

Attoparsec 是纯的(Data.Attoparsec.Internal.Types.Parser 不是转换器,也不包含 IO)所以你是对的,你不能直接从解析器中扩展包含。

将解析器分成两个阶段似乎是正确的方法:一个阶段就像 C 预处理器一样,接受一个包含 include 语句和其他内容交错的文件。 “其他东西”只需要基本上在词法上有效,而不是你的完整解析器——就像 C 预处理器只关心标记和匹配括号,而不是匹配其他括号或任何语义。然后替换包含,生成一个完全扩展的文件,您可以将其提供给现有的解析器。

如果一个包含的文件在某种意义上必须在语法上是“独立的”,那么你可以先解析整个文件,与include交错,然后替换他们。例如:

-- Whatever items you’re parsing.
data Item

-- A reference to an included path.
data Include = Include FilePath

parse :: Parser [Either Include Item]

-- Substitute includes; also calls ‘parse’
-- recursively until no includes remain.
substituteIncludes :: [Either Include Item] -> IO [Item]

比如说,如果你只是使用 attoparsec 来对不能跨越文件边界的词法标记进行词法分析,或者你正在进行完整的解析但想 禁止一个包含例如不匹配的括号。


另一种选择是通过使用不同的解析库(例如 megaparsec)将 IO 直接嵌入到您的解析器中,它提供了一个 ParsecT 转换器,您可以将 IO 环绕到直接在您的解析器中执行 IO 。我可能会为原型这样做,但尽可能将解析和扩展的关注点分开似乎更整洁。