在上一节中,我们实现了WHILE循环,因为SysY语言没有FOR语句,所以在这一节中,我们将进一步实现简单函数功能。我们在此过程中必须处理的事有:
- 数据类型:
int
- 每个函数的返回类型
- 每个函数的参数数量
- 函数局部变量与全局变量
对于目前的我们来说,这太难了。所以我们在这里要做的是达到我们可以声明不同的函数。只有我们生成的可执行文件中的 main()
函数会运行,但我们将有能力为多个功能生成代码。
首先,我们来看一下简单的函数SysY语法:
函数定义: FuncDef → FuncType Ident ‘(’ ‘)’ Block
函数类型: FuncType → ‘void’
所有函数都将被声明为“void”并且没有参数。我们也不会引入调用函数的能力,所以只有main()
函数
将执行。
修改词法分析
我们需要一个新的令牌T_VOID,同时在match_keyword()
函数中加入void的匹配,它们都很容易添加。
case 'v': if(!strcmp(s, "void")) return (T_VOID);
break;
修改语法分析
分析函数定义代码如下:
// 分析简单函数声明
struct ASTnode *function_declaration()
{
struct ASTnode *tree;
int nameslot;
// 匹配'void'、标识符和'(' ')',
// 但不做任何处理
match(T_VOID, "void");
ident();
nameslot = add_global(Text);
lparen();
rparen();
// 获得代码块的AST树
tree = Block_statement();
// 返回具有函数符号位置和语句块子树的A_FUNCTION节点
return mkastunary(A_FUNCTION, tree, nameslot);
}
这里新建了一个A_FUNCTION类型的AST操作,表示函数定义操作。这将进行语法检查和AST构建,但是这里几乎没有语义错误检查。我们还没有处理函数被重新声明的能力。
修改代码生成器
现在我们有一个A_FUNCTION的AST类型,我们要在code_generator()
中添加一些代码来处理这种类型。
// 在生成主体代码之前生成函数的前言
case A_FUNCTION: arm_function_preamble(Tsym[n->v.id].name);
code_generator(n->left, NOREG, n->op);
arm_function_postamble();
return NOREG;
修改汇编代码
现在我们必须生成代码来设置每个函数的堆栈和帧指针,并在函数结束并返回到函数的调用者。
我们已经在 arm_preamble()
和 arm_postamble()
中有这个代码,但是arm_preamble()
也有 print()
函数的汇编代码。所以,我们要分离出这些片段重新组装。
重组后的代码如下:
// 汇编预处理代码
void arm_preamble()
{
arm_freeall_registers();
fputs(
"\t.text\n"
"\t.global __aeabi_idiv\n"
"\t.section\t.rodata\n"
"\t.align 2\n"
".LC0:\n"
"\t.ascii \"%d\\012\\000\"\n",
Outfile);
}
// 生成函数前言
void arm_function_preamble(char *name)
{
fprintf(Outfile,
"\t.text\n"
"\t.align 2\n"
"\t.globl\t%s\n"
"\t.type\t%s, %%function\n"
"%s:\n"
"\tpush {fp, lr}\n"
"\tadd fp, sp, #4\n", name, name, name);
}
// 生成函数结尾
void arm_function_postamble()
{
fputs("\tmov r3, #0\n" "\tmov r0, r3\n" "\tpop {fp, pc}\n", Outfile);
}
// 汇编尾代码
void arm_postamble()
{
fputs(
".L3:\n"
"\t.word .LC0\n",
//"\t.size main, .-main\n",
Outfile);
fprintf(Outfile, ".L2:\n");
for (int i = 0; i < Globals; i++)
{
fprintf(Outfile, "\t.word %s\n", Tsym[i].name);
}
}
测试结果
修改main()函数:
scan(&Token); // 从输入中获得第一个单词
arm_preamble();
while(Token.token != T_EOF)
{
tree = function_declaration();
code_generator(tree, NOREG, 0);
}
arm_postamble();
输入:
void main()
{
int i;
i=1;
while (i <= 20)
{
print i;
i= i + 1;
}
}
输出(out.s):
.text
.global __aeabi_idiv
.section .rodata
.align 2
.LC0:
.ascii "%d\012\000"
.text
.comm i,4,4
.text
.align 2
.globl main
.type main, %function
main:
push {fp, lr}
add fp, sp, #4
mov r4, #1
ldr r3, .L2+4
str r4, [r3]
L1:
ldr r3, .L2+4
ldr r4, [r3]
mov r5, #20
cmp r4, r5
bgt L2
ldr r3, .L2+4
ldr r4, [r3]
mov r1, r4
ldr r0, .L3
bl printf
ldr r3, .L2+4
ldr r4, [r3]
mov r5, #1
add r4, r4, r5
ldr r3, .L2+4
str r4, [r3]
b L1
L2:
mov r3, #0
mov r0, r3
pop {fp, pc}
.L3:
.word .LC0
.L2:
.word main
.word i
输出(out):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
总结
我们已经实现了函数的声明,但除main()函数以外的所有函数均不能工作。在下一节中,我们将会实现数据类型。