在上一章中,我们完成了对函数体的解析,现在我们需要进一步解析函数体中的语句,还有程序中的表达式。我们需要将程序中的语句解析成我们的虚拟机能直接执行的语句,所以这一章中会有一点难度:

语句

我们的编译器中识别以下六种语句:

  • 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){
        ...
    }
}

接下来,我们给出具体的实现:

常量

NumIMM指令将其加载到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;
            }
        }

指针取值

说实话有点写不下去了,最近有点忙,心情也不好,写这个的过程中断断续续的,所以先到此为止吧。