为前瞻语言和多个文件编写编译器?

时间:2009-10-16 15:32:58

标签: optimization compiler-construction build-process compiler-theory compiler-optimization

在我的语言中,当定义出现在方法下方时,我可以在我的方法中使用类变量。它也可以调用我的方法等下方的方法。没有'标题'。拿这个C#为例。

class A
{
    public void callMethods() { print(); B b; b.notYetSeen();
    public void print() { Console.Write("v = {0}", v); }
    int v=9;
}

class B
{
    public void notYetSeen() { Console.Write("notYetSeen()\n"); }
}

我应该如何编译?我在想的是:

  • pass1:将所有内容转换为AST
  • pass2:遍历所有类并构建定义类/变量/ etc
  • 的列表
  • pass3:浏览代码并检查是否存在任何错误,例如未定义的变量,错误的使用等,并创建我的输出

但似乎要工作,我必须在执行pass3之前为所有文件传递1和2。在我发现语法错误之前,感觉还有很多工作要做(除了可以在解析时完成的显而易见的事情,例如忘记关闭括号或写入0xLETTERS而不是十六进制值)。我的直觉说还有其他方法。

注意:我正在使用bison / flex来生成我的编译器。

4 个答案:

答案 0 :(得分:3)

我对处理前向引用的语言的理解是它们通常只使用第一遍来构建有效名称列表。只是将一个条目放在一个表中(没有填写定义)的东西,所以当你进行真正的传递以生成定义时,你有一些东西可以指向。

如果您尝试实际构建完整定义,则最终必须重新扫描,每次保存对未定义事物的任何引用,直到下一次传递。如果存在循环引用,即使这样也会失败。

答案 1 :(得分:1)

我将通过第一遍并收集所有类/方法/字段名称和类型,忽略方法体。然后在第二遍中检查方法体。

答案 2 :(得分:0)

我不知道除了遍历源中的所有文件之外还有其他任何方式。

我认为你可以把它归结为两次传递 - 在第一次传递时,构建AST并且每当你找到变量名时,将它添加到包含那些块符号的列表中(添加它可能会很有用)列表到树中相应的范围)。第二步是线性遍历树,并确保使用的每个符号引用该范围内的符号或其上方的范围。

我的描述过于简单,但基本答案是 - 前瞻需要至少两遍。

答案 3 :(得分:0)

通常的方法是将B保存为“未知”。它可能是某种类型(因为你遇到它的地方)。所以你可以为它预留内存(一个指针),即使你不知道它到底是什么。

对于方法调用,你做不了多少。在动态语言中,您只需在某处保存方法的名称,并检查它是否在运行时存在。在静态语言中,您可以将其与未知类型B中的“未知方法”一起保存在编译器中。由于方法调用最终会转换为内存地址,因此您可以再次保留内存。

然后,当您遇到B和方法时,您可以清除未知数。由于你对它们有所了解,你可以说它们是否应该像它们应该的那样,或者第一次使用是否是语法错误。

因此,您不必两次读取所有文件,但它确实使事情变得更简单。

或者,您可以在遇到源时生成这些头文件,并将它们保存在可以再次找到它们的位置。这样,您可以加快编译速度(因为您不必在下一次编译运行中考虑未更改的文件)。

最后,如果您编写一种新语言,则不应再使用bison和flex。现在有更好的工具。例如,ANTLR可以生成一个可以在发生错误后恢复的解析器,因此您仍然可以解析整个文件。或者查看this Wikipedia article以获取更多选项。