我正在写DSL,很有趣。我决定使用attoparsec,因为我对此很熟悉。
我想用这样的相对文件名实现包含的解析:
include /some/dir/file.ext
或网址:
include http://blah.com/my/file.ext
因此,在解析时,我希望读取引用的资源并解析整个内容,并将其内容附加到“外部”解析状态。
问题是,尽管这些语句的解析很容易,但是我无法在Attoparsec解析器中运行IO(据我所知)。
我如何使用Attoparsec实现这一目标?我是否使用一些字符串过滤将初始输入切碎,然后将每个“块”分别解析为parse
和feed
?本质上是两遍解析方法?
答案 0 :(得分:4)
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
周围以进行{ {1}}直接在解析器中。我可能会为原型执行此操作,但似乎尽量将解析和扩展的关注点分开。