整个编译过程,编译器需要不断地保存并修改从源代码中获取的各种数据,符号表的重要性越是接近后端越明显。在不同的编译器实例中,开始使用符号表的“时机”都不相同,选择什么时机去使用符号表取决于编译器结构的设计原则,司马编译器从前段到后端的设计遵循了一个原则,那就是“在保证把维护性能最大化的前提下提高时间性能”,为了达到这个要求,尽可能地推迟了调用符号表API的时机,从语义分析阶段开始调用,这种安排可以让编译器前段跟具体语言分离的更彻底,前面说的“维护性能”就是指这个,越往段越跟具体语言的关系就越紧密。
在设计符号表的过程中遇到过很多问题,最令人头痛的就是参数、表达式、函数、变量、数组之间存在的递归关系。比如如后描述“一个运算数可以是函数,一个变量可以是一个函数的参数,函数可以是函数的参数。数组的下标可以是函数,也可以是一个表达式”。对于这种描述,如何去实现?当需要去记录一个函数的参数时应该怎么去实现?用链式存储结构,如何设计链表节点?
另一个设计的原则是,忽略掉那些具体的,只关注具有共同性质的,这也是任何形式化工具的基本原则。不管“名字”的具体类型,把所有的变量、数组、函数等等这些具体的数据对象看作是属于一个“类型”下的对象可以简化编译器前段的编码工作,尤其体现在接口上面,足够简单的API接口能使代码更容易维护。
typedef struct ANODE {
char* name;
int azonaltype;
int datatype;
char* value;
int belong;
char* scope;
int used;
int needspace;
int line;
int isparam;
int parameters;
int number;
int layer;
int recursion;
int size;
struct {
ANODE* next;
ANODE* exp;
} tack;
struct {
ANODE* left;
ANODE* right;
ANODE* operand;
int functer;
} expression;
ANODE* next;
} AZONAL;
typedef struct SNODE {
AZONAL variable;
AZONAL array;
AZONAL numeral;
AZONAL function;
AZONAL expression;
AZONAL control_flow;
int variable_amount;
int array_amount;
int function_amount;
int expression_amount;
//20090111 added wang quanwei start
int control_flow_amount;
//20090111 added wang quanwei end
int update;
} SYMBOL_TABLE;
上面这段代码是从司马编译器当前阶段源代码中原封不动复制过来的,因为后端现在才刚刚开始,其符号表结构仍有改变的可能。把那些具体的“名字”都看作是一个AZONAL也正体现了前面说的第一个原则,日后可以在不用修改API接口就能给某个语言添加新的数据对象(当然,如果严格按照某语言的标准,改动作是不会被允许的)。AZONAL里面的属性可以用来描述一个变量、一个函数、一个数组、一个结构体,这正是我们想要的,换句话说,AZONAL可能是一个变量、也可能其它数据对象,甚至是一个表达式,我们把表达式存到了符号表,目的是为了后端的代码生成。
司马编译器符号表存储实例。
司马编译器:
开发:
项目地址: