第一次接触C#的编译,从现在看来确实和过程化语言的编译在Symbol Table的构建上有很大的差异。
MONO的C#编译器中,仿照System.Reflection以及System.Reflection.Emit中的构架,建立了自己的TypeManager,用相同的机制来完成对源代码中的类型和方法的解析以及代码生成。使用RootContext类型来统筹和驱动整个编译的过程。语法分析阶段产生的Parse Tree实际和类型系统被整合在了一起。可以看到ParseTree中的Class类型,实际间接继承自TypeContainer类型。
相对的,在ROTOR的编译器中。Parse Tree的结点在Allnodes.h中定义,而另外有独立的SYM系统,使用的方式似乎更为复杂一些。
在过程化语言的编译过程中,因为类型可能和变量重名,我们通常准备两个Environment(即查找用的Symbol Table),一个对应类型,另一个对应变量和函数。
而在面向对象的语言里,对于一个Identifier的查找,与过程化语言很不一样。
初步考虑仍然使用两套Environment,其中之一为一个类型系统(TypeManager),保存所有已经被解析之后的代码中的类型信息,按照以下的树型方式保存:
Namespace
|-- Class/Struct/Interface
|-- Method
|-- Property
|-- Member
为了提高作Name Resolving和Type Checking的效率,底层的数据结构可能需要结合HashTable和Tree
因为C#支持引用先于定义(C++的前置声明对编译器来说就是福音啊),因此解析时可能要按照以下的顺序:
解析所有类型的名称 (此时类型的其它信息一概未知,但光有名字的信息已经足以在下步做类型检查了)
|---> 解析所有的数据成员和方法的声明 (使用上一步中的信息对成员的类型、方法返回类型、方法参数类型做类型检查)
|---> 解析方法的定义
在解析方法的定义中,将使用到另一套Environment(VariableEnv),这个Environment中,包含了所有的本地变量的Binding,Binding中的信息非常简单,只需要表明该Variable的类型即可。VariableEnv和过程化语言编译中使用的Symbol Table类似,有成熟的算法可以参考,Block之中作用域的扩展和相互屏敝也类似。
对于以下的代码:
在解析t.Foo();的时候,首先在VariableEnv查找t的Binding,从而得知类型为MyClass,然后在TypeManager中查找MyClass的方法中,是否有Foo()的定义。
初步设想,当中间代码生成结束之后,把得到的Intermediate Representation(IR)挂接到TypeManager中的Method部分中去,TypeManager中的信息已经足够可以用来做后阶段真正的IL生成了。此时,从Parse Tree到IR的转换也就完成了。
题外话:方法重载的检查其实相对简单,当在解析方法定义的时候,根据参数的类型,在TypeManager中查找相应的方法即可。即使在面向过程的语言之中,对以下的代码:
加入到Environment中的时候,我们只要保证foo->foo(int i)的Binding不会屏敝掉之前加入的foo->foo()的Binding即可。这个既可以通过存放的数据结构来实现也可以通过查找的算法来加以保证。
MONO的C#编译器中,仿照System.Reflection以及System.Reflection.Emit中的构架,建立了自己的TypeManager,用相同的机制来完成对源代码中的类型和方法的解析以及代码生成。使用RootContext类型来统筹和驱动整个编译的过程。语法分析阶段产生的Parse Tree实际和类型系统被整合在了一起。可以看到ParseTree中的Class类型,实际间接继承自TypeContainer类型。
相对的,在ROTOR的编译器中。Parse Tree的结点在Allnodes.h中定义,而另外有独立的SYM系统,使用的方式似乎更为复杂一些。
在过程化语言的编译过程中,因为类型可能和变量重名,我们通常准备两个Environment(即查找用的Symbol Table),一个对应类型,另一个对应变量和函数。
而在面向对象的语言里,对于一个Identifier的查找,与过程化语言很不一样。
初步考虑仍然使用两套Environment,其中之一为一个类型系统(TypeManager),保存所有已经被解析之后的代码中的类型信息,按照以下的树型方式保存:
Namespace
|-- Class/Struct/Interface
|-- Method
|-- Property
|-- Member
为了提高作Name Resolving和Type Checking的效率,底层的数据结构可能需要结合HashTable和Tree
因为C#支持引用先于定义(C++的前置声明对编译器来说就是福音啊),因此解析时可能要按照以下的顺序:
解析所有类型的名称 (此时类型的其它信息一概未知,但光有名字的信息已经足以在下步做类型检查了)
|---> 解析所有的数据成员和方法的声明 (使用上一步中的信息对成员的类型、方法返回类型、方法参数类型做类型检查)
|---> 解析方法的定义
在解析方法的定义中,将使用到另一套Environment(VariableEnv),这个Environment中,包含了所有的本地变量的Binding,Binding中的信息非常简单,只需要表明该Variable的类型即可。VariableEnv和过程化语言编译中使用的Symbol Table类似,有成熟的算法可以参考,Block之中作用域的扩展和相互屏敝也类似。
对于以下的代码:
MyClass t = new MyClass();
t.Foo();
t.Foo();
在解析t.Foo();的时候,首先在VariableEnv查找t的Binding,从而得知类型为MyClass,然后在TypeManager中查找MyClass的方法中,是否有Foo()的定义。
初步设想,当中间代码生成结束之后,把得到的Intermediate Representation(IR)挂接到TypeManager中的Method部分中去,TypeManager中的信息已经足够可以用来做后阶段真正的IL生成了。此时,从Parse Tree到IR的转换也就完成了。
题外话:方法重载的检查其实相对简单,当在解析方法定义的时候,根据参数的类型,在TypeManager中查找相应的方法即可。即使在面向过程的语言之中,对以下的代码:
public void foo();
public void foo(int i);
public void foo(int i);
加入到Environment中的时候,我们只要保证foo->foo(int i)的Binding不会屏敝掉之前加入的foo->foo()的Binding即可。这个既可以通过存放的数据结构来实现也可以通过查找的算法来加以保证。