编译器入门(语义分析)
语义分析,是语法分析与中间代码生成之间的过渡步骤,是基于抽象语法树AST的进一步分析,主要是3个方面:
1,类型检查和自动类型转换,
对抽象语法树的每一个节点的输入进行类型检查。
在类型不一致时添加自动类型转换(type cast),必要时给出warnning信息。
无法自动类型转换的,给出error信息。
确定该节点的输出结果类型,用于父节点的类型分析,依次递归。
2,常量表达式的计算,
常量表达式的结果在这里提前算出来,用一个常数代替,减少后续环节的运算量,提高生成的目标代码的效率。
例如,int sec = 60 * 60,
替换为,int sec = 3600,
这样在生成机器码的时候就不需要使用乘法指令,直接一个mov指令把常数3600
赋值给sec就行了。
3,运算符重载和函数重载,
已经完成了类型检查的情况下,在这里是可以确定具体选择哪一个重载函数的,把它转换为对重载函数的调用。
例如有个Mat矩阵类,该类有几个加法运算的重载函数,那么在这里就可以确定输入参数是不是含有矩阵,是2个矩阵相加还是矩阵加上一个常数,etc.
把这个加法运算符,转化为对Mat类的对应函数的调用。
虚函数调用是没法在这里确定的,因为基类指针的具体子类类型是动态的,只能运行时查询它的虚函数表获得实际调用的函数。
语义分析,是递归遍历AST树,并对它的节点进行分析。
从语义分析开始,后续的所有分析都是以AST树的节点为基础的,不同的阶段使用的分析函数不同而已。
所以,我们可以这么设计AST节点的数据结构:
struct ast_node_s
{
int type; 节点类型
struct ast_node_s* parent; 父节点
struct ast_node_s** childs; 子节点列表
int nb_childs; 子节点个数
uint32_t flags;
各种标示,例如常量、常量字面值等等
} ;
父节点和子节点列表,是构成整个AST树的基础。
运算符的优先级,也是体现在生成语法树时,谁是子节点,谁是父节点,子节点的优先级更高。
节点类型,就是节点的语义,标示着该用哪个函数去处理当前节点。
在语义分析阶段,和三地址码生成阶段,选择的处理函数是不同的。
当然,不同类型的节点,选择的处理函数也是不同。
我们可以根据当前的分析阶段和节点类型,去相应的函数表里查询对应的函数,并调用它。
过了语法分析之后,终于不再有那种互相交织着的递归调用了,不那么拧巴了:(
以后都是对语法树的遍历,或者三地址序列的遍历。