在上一节,我们已经实现了一条表达式的计算,在这一节中,我们要真正实现语句。
我们来看看SysY语言中对语句的定义:
语句: Stmt → LVal ‘=’ Exp ‘;’ | [Exp] ‘;’ | Block
| ‘if’ '( Cond ‘)’ Stmt [ ‘else’ Stmt ]
| ‘while’ ‘(’ Cond ‘)’ Stmt
| ‘break’ ‘;’ | ‘continue’ ‘;’
| ‘return’ [Exp] ‘;’
为了给明显的输出结果,我们增加一条语法:
打印结果:print → Stmt
输入文件由几个语句组成,它们可以是一个语句,也可以是后面跟有更多语句的语句,每个语句均以关键字开头 print,然后是一个表达式,然后是分号。
首先增加单词类型:
// 单词类型
enum
{
T_EOF, T_ADD, T_SUB, T_MUL, T_DIV, T_MOD, T_INT, T_SEM, T_PRINT
};
更改词法分析器
在这里,我添加了从SubC编译器搬来的这段代码,它将字母数字字符读入缓冲,直到遇到非字母数字字符。再加上识别关键词的函数,就成为可以识别语句的词法分析器。
#define TEXTLEN 512 // 扫描缓存长度
char Text[TEXTLEN + 1]; // 扫描缓存
// 扫描输入文件中的标识符并将其存储在buf[]中,返回标识符的长度
int scan_ident(int c, char *buf, int lim)
{
int i = 0;
// 允许数字、字母和下划线
while (isalpha(c) || isdigit(c) || '_' == c)
{
// 标识符长度超过限制,则出错;否则添加到buf[],并获取下一个字符
if (lim - 1 == i)
{
printf("identifier too long on line %d\n", Line);
exit(1);
}
else if (i < lim - 1)
{
buf[i++] = c;
}
c = get_nextchr();
}
// 遇到分隔符,把它放回去。终止buf[],并返回长度
put_backchr(c);
buf[i] = '\0';
return i;
}
// 给定输入中的一个单词,返回匹配的关键字标记号,
// 如果它不是关键字,则返回0
int match_keyword(char *s)
{
switch (*s)
{
case 'p':
if (!strcmp(s, "print"))
return (T_PRINT);
break;
}
return 0;
}
同时修改scan函数为
// 扫描并返回在输入中找到的下一个单词。
// 如果标记有效则返回 1,如果没有标记则返回 0
int scan(struct token *t)
{
int c, tokentype;
c = skip_getchr();
switch (c)
{
case EOF: t->token = T_EOF; return 0;
case '+': t->token = T_ADD; break;
case '-': t->token = T_SUB; break;
case '*': t->token = T_MUL; break;
case '/': t->token = T_DIV; break;
case '%': t->token = T_MOD; break;
case ';': t->token = T_SEM; break;
default: if (isdigit(c))
{
t->intvalue = scan_int(c);
t->token = T_INT;
break;
}
else if(isalpha(c) || '_' == c)
{
scan_ident(c, Text, TEXTLEN);
if (tokentype = match_keyword(Text))
{
t->token = tokentype;
break;
}
printf("Unrecognised symbol %s on line %d\n", Text, Line);
exit(1);
}
printf("Unrecognised character %c on line %d\n", c, Line);
exit(1);
}
return 1;
}
更改语义分析器
到目前为止,我们的语义分析器只能识别一个表达式。而我们的新语法每个表达式都以分号结尾(不是以文件结尾即 T_EOF),因此我们需要在表达式解析器中更改代码以发现T_SEM标记并退出。修改binexpr()函数为
// 返回一个以二元操作符为根的树
struct ASTnode *binexpr(int pretokentype)
{
struct ASTnode *left, *right;
int tokentype;
// 获取左节点的整数,同时获取下一个单词
left = primary();
// 如果下一个单词是文件结尾,则返回左节点
tokentype = Token.token;
if (tokentype == T_SEM) return left;
// 当前单词的优先级高于前一个单词的优先级
while (op_precedence(tokentype) > pretokentype)
{
// 获取下一个单词
scan(&Token);
// 根据优先级递归生成右子树
right = binexpr(OpPrec[tokentype]);
// 从单词类型得到到节点类型,然后合并左、右子树
left = mkastnode(token_op(tokentype), left, right, 0);
// 更新当前单词的详细信息
tokentype = Token.token;
// 如果到文件结尾,则返回左节点
// 此时的左节点已经更新为合并后的树的根节点
if (tokentype == T_SEM) return left;
}
return left;
}
语句分析器
在每个循环中都会先找到"print",然后调用binexpr()解析表达式,最后找到";",如果紧跟着"EOF",就会跳出循环。
在每个表达式树之后,调用代码将树转换为汇编代码,并调用 arm_print_reg()函数以打印出最终值。
// 检查当前单词是否为t,并获取下一个单词
void match(int t, char *what)
{
if (Token.token == t)
{
scan(&Token);
}
else
{
printf("%s expected on line %d\n", what, Line);
exit(1);
}
}
// 检查当前单词是否为";",并获得下一个单词
void semi(void)
{
match(T_SEM, ";");
}
// 分析语句
void statements()
{
struct ASTnode *tree;
int reg;
while (1)
{
// 匹配一个"print"
match(T_PRINT, "print");
// 分析表达式并生成汇编代码
tree = binexpr(0);
reg = code_generator(tree);
arm_print_reg(reg);
arm_freeall_registers();
// 继续匹配直到遇到"EOF"
semi();
if (Token.token == T_EOF) return;
}
}
修改main()函数
main() 用statements()
函数代替binexpr()
函数解析输入文件中的表达式。
// 用法 compiler -o -s outfile infile
int main(int argc, char *argv[])
{
struct ASTnode *n;
if(argc != 5)
{
fprintf(stderr, "compiler -o -s outfile infile\n");
exit(1);
}
init();
if ((Infile = fopen(argv[4], "r")) == NULL)
{
fprintf(stderr, "Unable to open %s: %s\n", argv[4], strerror(errno));
exit(1);
}
if ((Outfile = fopen(argv[3], "w")) == NULL)
{
fprintf(stderr, "Unable to create %s: %s\n", argv[4], strerror(errno));
exit(1);
}
scan(&Token);
arm_preamble();
statements();
arm_postamble();
fclose(Outfile);
}
测试
输入:
print 2 + 3 * 4 / 5 % 6 - 7 + 8;
输出(out.s):
.text
.section .rodata
.align 2
.LC0:
.ascii "%d\012\000"
.text
.align 2
.global main
.type main, %function
main:
push {fp, lr}
add fp, sp, #4
mov r4, #2
mov r5, #3
mov r6, #4
mul r5, r5, r6
mov r6, #5
mov r0, r5
mov r1, r6
bl __aeabi_idiv
mov r5, r0
mov r6, #6
mov r7, r5
mov r0, r7
mov r1, r6
bl __aeabi_idiv
mov r7, r0
mul r7, r7, r6
sub r5, r5, r7
add r4, r4, r5
mov r5, #7
sub r4, r4, r5
mov r5, #8
add r4, r4, r5
mov r1, r4
ldr r0, .L3
bl printf
mov r3, #0
mov r0, r3
pop {fp, pc}
.L3:
.word .LC0
.size main, .-main
输出(out):
5
输入:
print 1 + 4;
print 13 - 11;
print 2 * 0;
print 20 / 20;
print 1024 % 10;
输出(out.s):
.text
.section .rodata
.align 2
.LC0:
.ascii "%d\012\000"
.text
.align 2
.global main
.type main, %function
main:
push {fp, lr}
add fp, sp, #4
mov r4, #1
mov r5, #4
add r4, r4, r5
mov r1, r4
ldr r0, .L3
bl printf
mov r4, #13
mov r5, #11
sub r4, r4, r5
mov r1, r4
ldr r0, .L3
bl printf
mov r4, #2
mov r5, #0
mul r4, r4, r5
mov r1, r4
ldr r0, .L3
bl printf
mov r4, #20
mov r5, #20
mov r0, r4
mov r1, r5
bl __aeabi_idiv
mov r4, r0
mov r1, r4
ldr r0, .L3
bl printf
mov r4, #1024
mov r5, #10
mov r6, r4
mov r0, r6
mov r1, r5
bl __aeabi_idiv
mov r6, r0
mul r6, r6, r5
sub r4, r4, r6
mov r1, r4
ldr r0, .L3
bl printf
mov r3, #0
mov r0, r3
pop {fp, pc}
.L3:
.word .LC0
.size main, .-main
输出(out):
5
2
0
1
4
总结
在这一节中,我们已经实现了句子的编译。在下一节中,我们将往编译器中添加变量。