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