在上一章中,我们完成了对函数体的解析,现在我们需要进一步解析函数体中的语句,还有程序中的表达式。我们需要将程序中的语句解析成我们的虚拟机能直接执行的语句,所以这一章中会有一点难度:
语句
我们的编译器中识别以下六种语句:
if(...) <statement>; [else <statement>;]while (...) <statement>;{<statement>;}return xxx;<;(empty statement)>expression(这个我们稍后单独讨论)
现在我们要将这些语句转换成对应的汇编代码:
IF语句
IF语句的作用是跳转,根据条件表达式决定跳转的位置:
if (...) <statement> [else <statement>]
if (<cond>) <cond>
JZ a
<true_statement> ===> <true_statement>
else: JMP b
a: a:
<false_statement> <false_statement>
b: b:
对应的流程就是:
- 先执行条件表达式
<cond> - 如果条件失败,则跳转到
a的位置,执行else语句 - 如果条件成功,则在执行
<true_statement>之后,无条件跳转到b,结束判断。
我们的实现如下:
if(token==If){
match(If);
match('(');
expression();
match(')');
*++text = JZ;
b = ++text;
statement();
if(token==Else){
match(Else);
*b = (int)(text + 2);
*++text = JMP;
b = ++text;
statement();
}
*b = (int)(text);
}
我们可以从栈的视角进行解释,text始终是指向下一条要执行的指令的。我们希望JZ后面跟着的是else的代码块的入口或是if判断之后的地址。
这里我们使用了延迟绑定地址的方式,我们来看看以下两种情况:
- 只有
if,我们将在执行完if_statement之后,将JZ之后的跳转地址设置成下一条指令的地址text - 有
else,我们希望能够跳转到else的起始地址,我们需要在当前text指向的地址基础上+2,因为我们需要跳过JMP和出口地址所占用的指令空间。同时我们需要将if_statement之后的地址设置成判断的出口,即else_statement的后一条指令
While语句
While语句的汇编代码更加简单,如下:
a: a:
while (<cond>) <cond>
JZ b
<statement> <statement>
JMP a
b: b:
我们的实现如下:
else if(token==While){
match(While);
a = text;
match('(');
expression();
match(')');
*++text = JZ;
b = ++text;
statement();
*++text = JMP;
*++text = (int)a;
*b = (int)(text);
}
在跳转之前用a保存expression开始的指令,用于重新计算cond。然后延迟绑定地址b,作为while逻辑的出口
Return 语句
遇到Return语句则代表函数将要退出了,这一部分很简单,实现如下:
else if(token==Return){
match(Return);
if(token!=';'){
expression();
}
*++text = LEV;
}
其他语句
其他语句并不生成汇编代码,所以简单的匹配消耗即可:
else if(token=='{'){
match('{');
while(token!='}'){
statement();
}
match('}');
}else if(token==';'){
match(';');
}else{
expression();
match(';');
}
expresion
我们已经完成了对语句的解释,现在我们需要实现对表达式的解释,在此之前我们要明确什么是表达式?怎么解析我们的表达式,并计算他们。
怎么计算表达式
对于表达式中的运算符,每一个符号都有自己的优先级,在进行运算的时候,我们希望运算符优先级高的子式先被计算。例如在2 + 3 * 4中,我们希望*先被计算,然后再是+。对于我们生成的汇编代码而言,我们应该优先为优先级高的运算符生成目标代码,所以如何确定一个表达式的优先运算顺序十分重要。
这里我们使用递归下降的方法实现对表达式运算符的解析,我们在一开始定义标识符时,实际上就对运算符的优先级进行了排序:
enum {
Num = 128, Fun, Sys, Glo, Loc, Id,
Char, Else, Enum, If, Int, Return, Sizeof, While,
Assign, Cond, Lor, Lan, Or, Xor, And, Eq, Ne, Lt, Gt, Le, Ge, Shl, Shr, Add, Sub, Mul, Div, Mod, Inc, Dec, Brak
};
我们首先明确,一元运算符的优先级始终是高于二元运算符的,所以这里我们需要,首先完成对一元运算符的解析,这里先罗列几个较为特殊的:
Num当我们遇到一个数字时 要返回它的数值"..."当我们遇到一个字符串时 要返回它的指针PTR当我们遇到一个指针类型时 我们要正确的解析它的解引用Func当我们遇到一个函数调用时,我们要正确的执行它,并返回返回值Id当我们遇到一个变量时,需要返回它的存储的值- …
然后,对一元预算符的判断结束后,我们需要对二元运算符的解析,但是对于二元运算符,我们需要考虑运算符号的优先级。我们给每一个运算符都设置一个当前的level,每次只对高于/等于当前level的运算符进行解析,每次解析完一个运算符之后,都将当前的优先级提高。这样我们就实现了对运算符的递归下降分析,我们的框架代码如下:
int expression(int level){
// 解析一元运算符
{
... // 解析顺序按一元运算符优先级进行解析
}
// 解析二元运算符
while(token>=level){
...
}
}
接下来,我们给出具体的实现:
常量
Num用IMM指令将其加载到ax中即可:
if(token==Num){
*++text = IMM;
*++text = token_val;
expr_type = INT;
}
然后是字符串常量
else if(token=='"'){
*++text = IMM;
*++text = token_val;
expr_type = PTR;
// while(token=='"') match('"');
// 对data段进行地址对齐
data = (char*)(((int)(data) + sizeof(int)) & (-sizeof(int)));
}
这里有两点需要注意,如果你想支持"Hello""World" = "HelloWorld"的语法,那么你要设置while(token=='"') match('"');,这里我不想支持就不开了。
然后是data = (char*)(((int)(data) + sizeof(int)) & (-sizeof(int)));,这一部分的作用是将数据段进行对齐,我们希望每次的data指针都是四字节对齐的,这样可以方便我们进行索引,或是避免了字符串访问越界的可能。
sizeof
这个关键字我们也将其作为一元运算符进行处理,我们需要根据后面参数的类型,并返回它的大小。这里我们只支持Int Char Ptr三种类型,其中Ptr类型的大小同int
实现如下:
else if(token==Sizeof){
match(Sizeof);
match('(');
if(token==Int){
match(Int);
expr_type = INT;
}else if(token==Char){
match(Char);
expr_type = CHAR;
}
while(token==Mul){
match(Mul);
expr_type = expr_type + PTR;
}
match(')');
*++text = IMM;
*++text = (expr_type==CHAR) ? sizeof(char) : sizeof(int);
expr_type = INT;
}
变量与函数调用
由于我们将函数和变量的值的词法分析都是以Id开头,所以我们对他们的目标代码生成也放在一起:
else if(token==Id){
match(Id);
id = current_id;
// 函数调用
if(token=='('){
match('(');
tmp = 0;
while(token!=')'){
expression(Assign);
*++text = PUSH;
tmp++;
if(token==',') match(',');
}
match(')');
if(id->class==Sys){
*++text = id->value;
}else if(id->class==Fun){
*++text = CALL;
*++text = id->value;
}else{
printf("%d: 错误的函数调用\n",line);
exit(-1);
}
// 清除栈上的变量
if(tmp>0){
*++text = ADJ;
*++text = tmp;
}
expr_type = id->type;
}else if(id->class == Num){
*++text = IMM;
*++text = id->value;
expr_type = INT;
}else{
if(id->class = Glo){
*++text = IMM;
*++text = id->value;
}else if(id->class == Loc){
*++text = LEA;
*++text = -id->value;
}else{
printf("%d: 未知变量类型\n", line);
exit(-1);
}
expr_type = id->type;
*++text = (expr_type==CHAR) ? LC : LI;
}
}
指针取值
说实话有点写不下去了,最近有点忙,心情也不好,写这个的过程中断断续续的,所以先到此为止吧。