Template Haskell:是否有一个解析字符串并返回Q Exp的函数(或特殊语法)?

20

我正在尝试学习一些模板Haskell和准引用,我正在寻找一个函数,它接受一个String并将其解析为Q Exp,因此类型是:

String -> Q Exp

尝试通过hoogle搜索,但我看到的结果涉及将字符串文字转换为Q Exp,而我找到的最接近的是Language.Haskell.TH.dyn,它可以做到我想要的,但仅适用于单个变量。

还有其他选项吗?例如特殊语法?我只是在熟悉[||]$()的过程中,所以也许有类似于此目的的东西?

以下是我想象中的工作方式示例:

runQ (parse "(1+)") == InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) Nothing

另外,我意识到了这个问题。

runQ [| (1+) |] == InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) Nothing

但是这种方法对于变量字符串不起作用,因为--可以理解的是--里面的字符串被视为字面量。

runQ [| "(1+)" |] == LitE (StringL "(1+)")

编辑(2015-07-25):我已经开始使用,到目前为止它似乎运行良好。但是在我的计算机上需要大约10分钟才能。这很遗憾,因为我的软件包实际上相当小,我希望安装可以快速完成。 有人知道是否有更少依赖的解决方案吗?


3
我相信haskell-src-meta可以提供这个。 - luqui
2
@luqui 我对那个包有点困惑。在描述中,它写道某些功能“尚未完全完成”。这个功能不应该已经存在于GHC中吗?因为它可以接受 [|(1+)|] 并完全能够将其转换为 (InfixE _)。那么为什么需要第三方包,而且可能无法正确解析呢?或者我是否误解了,这也是 GHC 使用的规范代码?或者 GHC 根本没有公开此功能?如果您能提供一些关于这个问题的明确解释,我将不胜感激。 :) - Wizek
1
据我所知,GHC不会公开此代码,但我不是TH专家。 - luqui
1
@luqui 如果是这样的话,将其公开为 -- 比如说 Language.Haskell.TH.Parser.parse :: String -> Q Exp,不是更加优雅吗? - Wizek
1
如果 GHC 暴露它会更加优雅,但它不能拥有那种类型。它需要一个包含所有可能影响解析的选项的类型。当它达到那个级别时,你最终会在 ghctemplate-haskell 包之间遇到循环依赖问题。 - Carl
显示剩余6条评论
1个回答

5

正如其他人已经说过的一样,haskell-src-meta提供了

parsePat :: String -> Either String Pat
parseExp :: String -> Either String Exp
parseType :: String -> Either String Type
parseDecs :: String -> Either String [Dec]

在这里,PatExpTypeDecLanguage.Haskell.TH.Syntax中的相同。


GHC为什么不公开其自己的解析器?

GHC确实公开了它自己的解析器。使用ghci -package ghc(默认情况下,ghc是一个隐藏包)启动GHCi,您可以导入Parser。它具有将String解析为模式、表达式、类型和声明的初步AST(其数据声明在HsSyn中)的函数。

好吧,那么为什么不存在一个库,该库使用此解析器并将其输出转换为template-haskell中的AST(Language.Haskell.TH.Syntax中的AST)?

查看HsSyn,很明显其AST与Language.Haskell.TH.Syntax中的AST不完全相同。打开HsExprExp,并将它们并排放置,您会发现后者充满了例如PostTc id <some-other-type>PostRn id <some-other-type>这样的类型。随着AST从解析器传递到重命名器再到类型检查器,这些部分都会慢慢填充。例如,在我们进行类型检查之前,我们甚至不知道运算符的固定性!

为了实现我们想要的功能,我们需要运行比解析器更多的操作(至少还需要重命名和类型检查等)。想象一下:每次你想解析一个像"1 + 2"这样的小表达式,你仍然需要检查一堆导入。即使这样,转换回Language.Haskell.TH.Syntax也不是一件轻松的事情:GHC有各种奇怪的特性,比如它自己的特殊全局名称和标识符存储方式。

嗯... GHC对准引用做了什么?

那就是很酷的部分!与Exp不同,HsExpr具有HsSplice来表示splice。看一下前两个构造函数的类型:
HsTypedSplice :: id -> LHsExpr id -> HsSplice id.   -- things like [|| 1 + 2 ||]
HsUntypedSplice :: id -> LHsExpr id -> HsSplice id  -- things like [| 1 + 2 |]

请注意,他们不是存储String,而是已经存储了AST!插值在与其余AST同时解析。就像其余AST一样,插值将被传递给重命名器、类型检查器等,其中缺少的信息将被填充。

那么使用GHC的解析器基本上是不可能的吗?

可能不是。但从GHC中分离出来可能会非常困难。如果我们必须使用GHC的解析器,还必须运行类型检查器和重命名器,那么使用一个独立的解析器(例如haskell-src-exts,这是Haskell-src-meta依赖的内容)可能更优雅和简单,它能够一次完成所有操作(例如,优先级是必须提前给出的)。

1
为什么在类型检查之前就无法确定固定性?这难道不是在解析器上方概念层吗,以生成更完整的AST?为什么会在此之前发生花哨的东西呢? - dfeuer
@dfeuer,固定性在重命名阶段(出现在类型检查之前)填充,因此类型检查是固定性完全存在的第一个阶段。我可能没有理解您的问题... - Alec
我以为重命名器与类型检查器有关联,也许我错了。 - dfeuer
@dfeuer 没错,你理解得很正确——重命名器和类型检查器确实会相互影响,但仅在处理顶层拼接时才会如此。关键是,在重命名结束之前,甚至不知道特定块的固定性(因此与中缀运算符相关的AST可能需要重新排列)。 - Alec
我在 https://stackoverflow.com/questions/45674757/how-to-splice-in-literal-strings-of-haskell-code-via-template-haskell 遇到了完全相同的问题,我对 TH 的工作原理感到困惑。AST 是否被转换为文字源代码,然后再被输入到“常规 GHC”中,还是 GHC 直接使用 AST?如果是前者,那么 haskell-src-meta 不是一个相当低效的解决方案吗?因为同一段代码会被 haskell-src-meta 和实际的 GHC 解析两次。 - Saurabh Nanda
@SaurabhNanda 括号内的AST与括号外的AST一起由GHC解析。至关重要的是,所有这些都发生在编译时。您永远不会将运行时字符串提供给准引用(或GHC),但可以将运行时字符串提供给'haskell-src-meta'。 - Alec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接