嵌入助手

2007年3月26日

最近几周,我一直在玩和研究编译器-编译器工具。这些工具的一个共同特点是它们有一个语法文件,其核心是描述语言语法产生式规则的描述。除了描述语法之外,该文件还向解析器提供有关如何处理语言的信息,因为它识别语言元素。在大多数编译器-编译器工具中,这些指令表示为语法中的动作 - 这些动作通常被编码为高级语言中的代码片段。

例如,在我的 HelloAntlr 示例中,您会看到嵌入的 Java 代码片段,用于从源文件创建和填充配置。(嵌入 Java 不是唯一的方法,树遍历是另一种方法。)

这种在另一个领域特定语言 (DSL) 中嵌入通用语言 (GPL) 的方法非常普遍。这里的大多数读者在使用 Velocity、JSP、ERB 等模板系统创建 HTML 页面时都会遇到它。同样,我们有不同的表示(HTML),我们可以在其中嵌入 GPL 片段以提供动态数据和更复杂的处理。

当我在这样的环境中工作时,我喜欢将模板中的 Java(或我使用的任何 GPL)数量降到最低。一种常见的技术是在 Java 中创建一个单独的辅助类,并确保模板中所有嵌入的 Java 都只是对该辅助类的简单方法调用。

我喜欢这样做主要是因为我认为,如果你在 DSL 中嵌入大量的 GPL,最终会掩盖 DSL 的流程。使用模板语言进行 HTML 的主要目的是专注于 HTML,因此你粘贴的每一部分 Java 都会妨碍你。对于语法文件来说尤其如此,因为动作中的大量代码使得难以理解产生式。

使用嵌入助手带来的另一个好处是,它使工具更容易完成工作。无论是简单的语法高亮显示,还是 PostIntelliJ IDE 的全部功能,这些工具通常在混合语言文件中效果不佳。例如,AntlrWorks 会突出显示并提供 Antlr 语法的完成,但嵌入的 Java 只是纯文本。

使用这样的助手时,我的正常风格是在主机(DSL)文件的早期包含代码来设置助手。通常这涉及在主机中声明一个字段,并在其中构造一个新的助手,或者使其能够让调用者传递一个助手。(我承认,我很乐意在我的 Antlr 语法中使用公共字段来实现这一点。)之后,主机中所有嵌入的 Java 都是对助手的简单调用。我从主机文件的角度命名这些调用,以指示从助手那里需要什么。

助手和主机文件之间紧密耦合,通常它们之间存在双向链接,并且有很多来回。助手了解主机的所有肮脏细节 - 我很乐意让 HTML 助手输出 HTML,而语法助手会查看解析树。

通常,我将类上的“助手”一词视为一个危险信号,因为它通常表明抽象考虑不周。在这里,我很乐意使用这个词,因为助手实际上只是作为对主机文件的支持。