ANTLR4:访问整个 ast 后重新访问解析规则

ANTLR4: Re-visiting parse rules after the whole ast is visited

我目前正在为我自己的语言实现通用函数,但我卡住了,目前遇到以下问题:

可以从另一个源文件(另一个解析器实例)调用通用函数。假设我们在源文件 B 中有一个通用函数,我们从导入源文件 B 的源文件 A 调用它。发生这种情况时,我需要再次对函数主体(源文件 B)进行类型检查具体类型的不同表现形式,源自函数调用(源文件 A)。为此,我可能需要多次访问源文件 B 中的函数体。

源文件B:

type T dyn;

public p printFormat<T>(T element) {
    printf("Test");
}

源文件A:

import "source-b" as b;

f<int> main() {
    b.printFormat<double>(1.123);
    b.printFormat<int>(543);
    b.printFormat<string[]>({"Hello", "World"});
}

我试图通过将用于分析函数体及其子函数的代码放在一个内部函数中来实现这种方法,并在每次遇到从任何地方(也从其他源文件)调用该特定函数时调用它。由于某种原因,这似乎不起作用。我总是遇到分段错误。也许这是因为整棵树已经被访问过一次了?

更多上下文:C++ source code of my visitor

希望得到一些有用的答案或提示,谢谢! ;)

您将会对此博客感兴趣 post https://devblogs.microsoft.com/cppblog/two-phase-name-lookup-support-comes-to-msvc/

您要尝试做的事情需要您对您的语言做出决定。你想做 Java/C# 泛型吗?
在这种情况下,您将应用类型擦除。这意味着您不必跟踪程序中所有不同类型的实例化。你用有限的接口验证一次,以后遇到的使用点(调用点)保证生成有效的实例。

您似乎正在使用模块(与 header 相比),因此您遇到了经典的 export 问题。我建议以某种 compiler-memory-model 二进制形式序列化您的通用符号,而“导入 source-b” 将意味着反序列化该结构。它会将“printFormat”符号存储在您的内部表示(数据模型)中,允许您稍后安排具体的instanciation/code-gen。

如果您选择了模板哲学,那么您就不能那样做,因为您不能 运行 在模板符号的 body 中进行任何类型的语义传递。这就是 two-phase 编译范式。您必须序列化 AST 本身。通过找到一种方法来重用 antlr 节点,并附加一个可以转储和重建它们的序列化程序。或者通过使用您自己的节点 类 复制 AST,并使用 boost::serialization(或一些宏,例如 https://whosebug.com/a/43207178/893406)。

然后,每个调用站点都必须调用具体化(模板实例化),您将在原始通用符号所在的 AST 点插入一个带有某种类型重整的新符号以使其唯一先声明。 (AST 在访问期间是不可变的,所以要小心地将它插入到 AST 的 COPY 中)。 然后,标记一些全局 bool 标志以记住您需要在最后对整个文件重新 运行 全新的解析和语义验证。

所以当你的第一个访问者完成后,你检查那个标志,re-run整个事情,在被黑的 AST 树(复制的 AST)上。

这2个选择,是通用型和存在型的区别。

我认为最好的方法不是使用解析器。解析器应该将一组字符转换为一个 AST。

在你的例子中,你有一个相当复杂但新的语言,使用多个文件。当你 import B 时,你真的想导入 AST。 C++ 历史上搞砸了文字 #include 和带来的解析问题,现在只是获取模块。像 Java 这样的语言取消了这种文本包含,但后来改进了泛型。你有一个干净的石板。你应该设计你的语言,使编译器可以只接受一堆 AST 作为它的输入。

由于编译器会将 AST 作为输入,因此每个 AST 都将是 read-only。你当然可以有一个实例化的缓存,所以你不需要每次在 AST 中遇到它时 re-instantiate printFormat<int>,但这是一个细节。

没有详细说明的是实例化在您的语言中应该如何工作。一个常见的错误是假设 C++ 模板在文本级别像宏一样工作。事实并非如此;他们在语言层面上工作。你的也应该在语言层面工作。如果实例化采用 AST(或至少其子树)然后为实例化生成新的 AST,这对您来说真的很方便,再次 read-only。 C++ 模板 meta-language 实际上是一种函数式语言,这并非巧合。你能做的越多,这类问题就会变得越容易 read-only.