C语言解释器的实现--表达式解析(四)
1. BNF定义
虽然不想多提理论知识,但是有些东西还是避免不了。在解析表达式的时候,我们必须知道它的BNF定义,这样解析起来就非常方便了。所谓的BNF定义,相信大家看一眼就知道了:
exp_additive -> exp_multiplicative ( "+"|"-" ) exp_multiplicative
exp_multiplicative -> exp_cast ( "*"|"/"|"%" ) exp_cast
exp_cast -> ...
意思是:
加法表达式可以表示为 “乘法表达式 + 乘法表达式”
乘法表达式可以表示为 “类型转换表达式 *或/或% 类型转换表达式”
...
知道了整个C语言的BNF定义,我们就可以很简单的按照这个定义来解析了。整个C的BNF定义可以查看以下的链接:
http://lists.canonical.org/pipermail/kragen-hacks/1999-October/000201.html
2. 表达式解析
知道了上面的BNF定义,那么我们的解析代码就可以这么写:
void exp_additive(){
char op;
exp_multiplicative();
while(
(op = OPERATOR( '+' )) ||
op = OPERATOR( '-' )) ){
get_token();
exp_multiplicative();
...
}
}
void exp_multiplicative(){
char op;
exp_cast();
while(
(op = OPERATOR( '*' )) ||
(op = OPERATOR( '/' )) ||
(op = OPERATOR( '%' )) ){
get_token();
exp_cast();
...
}
}
过程是这样的:
a. 调用exp_additive时,先调用exp_multiplicative
b. 然后判断后面是否是 + 或 -,如果是,再次调用exp_multiplicative
这样就完成了加法表达式的解析。如果非要问为什么这么写就能解析出表达式,那么我们可以举个例子:
a = a * b + c * d;
那么,他的语法树应该是这样的:
(图4.2 语法树)
我们向下递归调用的过程,其实就是构造这个语法树的过程。但是我们不会真的创建出这个语法树,而是保存了一个与它等价的一种形式--后缀表达式,其实后缀表达式就是语法树的后续遍历。
3. 后缀表达式
什么是后缀表达式?我们还是从例子出发,上面的表达式,转化成后缀表达式就是这样子的:
a a b * c d * + =
为什么要写成这种奇怪的形式?我们不是吃饱了撑着,从左往右分别查看这个表达式您就知道原因了。
a
a
b
* 得到*号,那么拿前面的两个变量a b求和
c
d
* 得到*号,那么拿前面的两个变量c d求和
+ 的到+号,获取前面的两个变量 a*b c*d 的结果,求和
= 得到=号,将前面的结果赋给a
为了生成后缀表达式,我们要改造上面的解析函数。
void exp_additive(){
char op;
exp_multiplicative();
while(
(op = OPERATOR( '+' )) ||
op = OPERATOR( '-' )) ){
get_token();
exp_multiplicative();
EXP_OPR( op ); <--将运算符入栈
}
}
void exp_multiplicative(){
char op;
exp_cast();
while(
(op = OPERATOR( '*' )) ||
(op = OPERATOR( '/' )) ||
(op = OPERATOR( '%' )) ){
get_token();
exp_cast();
EXP_OPR( op ); <--将运算符入栈
}
}
那么解析完成以后,我们的栈中就会形成后缀表达式了。有了表达式的后缀形式,我们就可以很轻松的产生后缀表达式的中间代码了。
4.后缀表达式到中间代码
首先我们先说明一下我们的中间代码是怎样的一种形式,这里暂且叫它为三元表达式,是因为这个种中间代码的形式是固定的。例如,紧接上节的例子,表达式 a = a * b + c * d;的中间代码最终应该是这样子的:
@1 = a * b;
@2 = c * d;
@3 = @1 + @2;
@4 = a = @3;
其中以@开头的都是我们为之产生的中间变量。生成上述的中间代码后,将会对我们后续的解析提供很大的帮助,应为它结构固定,所以我们不用再去解析源程序,而是通过这个中间代码产生最终的执行代码。这里先声明下,我所说的执行代码,不是真正意义上得可执行代码,而是能够被我的软件解析的命令序列。其实它已经非常接近汇编代码。但是我们的目标是解析执行,并不产生汇编代码,所以产生简单的命令序列已经可以完成目标了。
我们前面解析表达式,产生后缀形式,为的就是生产这种中间表达式。表达式"a = a * b + c * d;"的后缀形式是"a a b * c d * + =;" 我们要根据这个后缀形式产生中间代码的过程如下:
5.中间代码的表示
typedef struct _code code_t;
typedef struct _code * pcode_t;
struct _code{
char opr;
struct{
int i, n, t;
}lab;
v_t var[4];
code_t * next;
};
它是一个链表,每个节点保存了一个形如"@1 = a * b;"的中间代码。其中,opr表示运算符"*";lab表示该节点为一个LAB,留到后面章节讲解;var表示运算变量,如上面表达式的"@1, a, b"。
这样子,当一个表达式解析完成后,会生成一个链表,表示该表达式的中间代码。