在上一节中,我们实现了比较运算符的计算,现在,我们可以容易地实现IF条件语句。
首先我们看IF语句的SysY语言的定义:
语句: Stmt → LVal ‘=’ Exp ‘;’ | ‘if’ '( Cond ‘)’ Stmt [ ‘else’ Stmt ]
修改词法分析
首先,我们要增加新的单词类型,表示"{","}","(",")",“if”,"else"六种符号。
// 单词类型
enum
{
T_EOF,
T_ADD, T_SUB, T_MUL, T_DIV, T_MOD, T_EQ, T_NE, T_LT, T_GT, T_LE, T_GE,
T_INT, T_SEM, T_PRINT, T_EQU, T_IDENT, T_KEYINT, T_IF, T_ELSE,
T_LBRACE, T_RBRACE, T_LPAREN, T_RPAREN
};
然后,我们要修改扫描函数,使之能识别到新的单词类型。
// 加入"if"和"else"的匹配
int match_keyword(char *s)
{
switch (*s)
{
case 'e': if(!strcmp(s, "else")) return (T_ELSE);
break;
case 'i': if(!strcmp(s, "if")) return (T_IF);
if(!strcmp(s, "int")) return (T_KEYINT);
break;
case 'p': if(!strcmp(s, "print")) return (T_PRINT);
break;
}
return 0;
}
// scan()函数中加入解析"{","}","(",")"
case '{': t->token = T_LBRACE; break;
case '}': t->token = T_RBRACE; break;
case '(': t->token = T_LPAREN; break;
case ')': t->token = T_RPAREN; break;
同时添加新的单词匹配函数:
// 检查当前单词是否为"{",并获取下一个单词
void lbrace()
{
match(T_LBRACE, "{");
}
// 检查当前单词是否为"}",并获取下一个单词
void rbrace()
{
match(T_RBRACE, "}");
}
// 检查当前单词是否为"(",并获取下一个单词
void lparen()
{
match(T_LPAREN, "(");
}
// 检查当前单词是否为")",并获取下一个单词
void rparen()
{
match(T_RPAREN, ")");
}
修改语法分析
为了后面的代码更容易实现,我暂时强制规定语法:
编译单元: CompUnit → Block
语句块: Block → ‘{’ { BlockItem } ‘}’
语句块项: BlockItem → Stmt
这些规则的目的是强制代码以{Stmt}
的语句块形式书写,目的是为了避免复合语句的else的歧义性。由于这是一个正在进行的项目,因此我会新增一些规则,同时在后面会撤消这些规则并将其重构。后面的章节中,我会使{Stmt}
为可选。
分析复合语句块
将原来分析句子的statements()
函数改为分析语句块的Block_statement()
函数
// 分析语句块并返回其AST
struct ASTnode *Block_statement()
{
struct ASTnode *left = NULL;
struct ASTnode *tree;
// 匹配"{"
lbrace();
while (1)
{
switch (Token.token)
{
case T_PRINT: tree = print_statement(); break;
case T_KEYINT: var_declaration(); tree = NULL; break;
case T_IDENT: tree = assignment_statement(); break;
case T_IF: tree = if_statement(); break;
case T_RBRACE: rbrace(); return (left); //遇到"}"时,返回AST树
default: fprintf(stderr, "Syntax error, token:%d on line %d\n", Token.token, Line);
exit(1);
}
// 对于每个新树,如果左子树为空,则将其保存在左子树中,
// 否则将左子树和新树合并
if (tree)
{
if (left == NULL) left = tree;
else left = mkastnode(A_GLUE, left, NULL, tree, 0);
}
}
}
语句块的开头与“{”匹配,而我们仅在将结尾的“}”与匹配时退出。然后print_statement()
、assignment_statement()
、if_statement()
和Block_statement()
一样都返回AST树。在我们的旧代码中,printstatement()
本身调用code_generator()
来求值表达式然后调用arm_print_reg()
,类似地assignment_statement()
也调用code_generator()
来执行赋值。这意味着原来我们在这里有AST树,在那儿也有其他树。现在只生成一个AST树,并调用code_generator()
一次性为其生成汇编代码。
在这里,我们用了一种新的AST节点类型A_GLUE
。它的作用是把很多语句的AST树组装成一棵AST树。
考虑如下代码:
stmt1;
stmt2;
stmt3;
stmt4;
将会生成这样的AST树:
A_GLUE
/ \
A_GLUE stmt4
/ \
A_GLUE stmt3
/ \
stmt1 stmt2
我们后序遍历时,可以得到正确的顺序。
分析IF语句
IF语句有三个子树,所以我们要定义有三个子节点的AST节点类型
- 执行条件的子树
- 紧随其后的复合语句
- ‘else’ 关键字后的可选复合语句
// AST节点类型
enum
{
A_ADD = 1, A_SUB, A_MUL, A_DIV, A_MOD, A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE,
A_INT, A_IDENT, A_ASSIGN, A_LVIDENT, A_GLUE, A_IF, A_PRINT
};
// 抽象语法树结构体
struct ASTnode
{
int op; // 节点的操作类型
struct ASTnode *left;
struct ASTnode *mid;
struct ASTnode *right;
union {
int intvalue; // 对于立即数,储存数值
int id; // 对于标识符,储存符号表位置
} v;
};
因此,AST树如下:
IF
/ | \
/ | \
/ | \
/ | \
/ | \
/ | \
condition statements statements
修改生成树节点代码:
// 生成并返回一个通用的AST节点
struct ASTnode *mkastnode(int op, struct ASTnode *left, struct ASTnode *mid, struct ASTnode *right, int intvalue)
{
struct ASTnode *n;
n = (struct ASTnode *) malloc(sizeof(struct ASTnode));
if (n == NULL)
{
fprintf(stderr, "Unable to malloc in mkastnode()\n");
exit(1);
}
n->op = op;
n->left = left;
n->mid = mid;
n->right = right;
n->v.intvalue = intvalue;
return n;
}
// 生成AST叶子节点
struct ASTnode *mkastleaf(int op, int intvalue)
{
return mkastnode(op, NULL, NULL, NULL, intvalue);
}
// 生成只有一个左孩子的一元AST节点
struct ASTnode *mkastunary(int op, struct ASTnode *left, int intvalue)
{
return mkastnode(op, left, NULL, NULL, intvalue);
}
还要修改调用这些函数的语句。分析IF语句的代码:
struct ASTnode *Block_statement();
// 分析包含可选ELSE的IF语句,并返回AST
struct ASTnode *if_statement()
{
struct ASTnode *condAST, *trueAST, *falseAST = NULL;
// 匹配if和"("
match(T_IF, "if");
lparen();
// 分析以下表达式和后面的')',确保树的操作是一个比较
condAST = binexpr(0);
if (condAST->op < A_EQ || condAST->op > A_GE)
{
fprintf(stderr, "Bad comparison operator on line %d\n", Line);
exit(1);
}
rparen();
// 获得语句块的AST
trueAST = Block_statement();
// 如果有"else",获得它的语句块的AST
if (Token.token == T_ELSE)
{
scan(&Token);
falseAST = Block_statement();
}
// 生成和返回AST
return mkastnode(A_IF, condAST, trueAST, falseAST, 0);
}
这里我限制了binexpr()
函数只有一个根,而且这个根是A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE
中的一个,因为我暂时不想处理类似if(x - 2)
这样的语句。因为if(a + b < c)
,IF语句的条件语句以")“结尾,所以要在binexpr()
中增加返回条件,使之遇到”)"返回。
if (tokentype == T_SEM || tokentype == T_RPAREN)
return left;
修改其他语句分析代码,使其不生成汇编代码但生成对应的AST树:
// 分析打印语句
struct ASTnode *print_statement()
{
struct ASTnode *tree;
// 匹配第一个"print"单词
match(T_PRINT, "print");
// 分析表达式并生成汇编代码
tree = binexpr(0);
// 生成一个printAST树
tree = mkastunary(A_PRINT, tree, 0);
// 匹配接下来的";"
semi();
return tree;
}
// 分析赋值语句
struct ASTnode * assignment_statement()
{
struct ASTnode *left, *right, *tree;
int id;
// 检查标识符
ident();
// 检查它是否已定义,然后为它创建一个叶节点
if ((id = find_global(Text)) == -1)
{
fprintf(stderr, "Undeclared variable on line %d\n", Line);
exit(1);
}
right = mkastleaf(A_LVIDENT, id);
// 匹配等号
match(T_EQU, "=");
// 分析接下来的表达式
left = binexpr(0);
// 生成赋值AST树
tree = mkastnode(A_ASSIGN, left, NULL, right, 0);
// 匹配";"
semi();
return tree;
}
修改代码生成器
现在AST节点有多个子节点,代码生成器将变得更加复杂。对于比较运算符,我们需要知道是否要在 IF语句(相反的比较中为跳转)或正则表达式(正常的比较中将寄存器设置为1或0)的一部分中进行比较。
为此我进行了修改code_generator()
以便我们可以传递父 AST 节点操作:
// 给定AST,生成汇编代码,返回值为结果所在寄存器号
int code_generator(struct ASTnode *n, int reg, int parentASTop)
{
int leftreg, rightreg;
// 特定的AST节点处理
switch (n->op)
{
case A_IF: return code_IF_generator(n);
// 执行每个子语句,并在每个子语句执行之后释放寄存器
case A_GLUE: code_generator(n->left, NOREG, n->op);
arm_freeall_registers();
code_generator(n->right, NOREG, n->op);
arm_freeall_registers();
return NOREG;
}
// 一般AST节点处理
// 获取左右子树值
if (n->left) leftreg = code_generator(n->left, NOREG, n->op);
if (n->right) rightreg = code_generator(n->right, leftreg, n->op);
switch (n->op)
{
case A_ADD: return (arm_add(leftreg, rightreg));
case A_SUB: return (arm_sub(leftreg, rightreg));
case A_MUL: return (arm_mul(leftreg, rightreg));
case A_DIV: return (arm_div(leftreg, rightreg));
case A_MOD: return (arm_mod(leftreg, rightreg));
case A_EQ:
case A_NE:
case A_LT:
case A_GT:
case A_LE:
case A_GE:
// 如果父AST节点是A_IF,则生成一个比较后跟一个跳转。
// 否则,比较寄存器并根据比较结果将寄存器设置为1或0
if(parentASTop == A_IF) return arm_compare_and_jump(n->op, leftreg, rightreg, reg);
else return arm_compare_and_set(n->op, leftreg, rightreg);
case A_INT: return (arm_load_int(n->v.intvalue));
case A_IDENT: return (arm_load_global(n->v.id));
case A_LVIDENT:return (arm_stor_global(reg, n->v.id));
case A_ASSIGN: return rightreg;
case A_PRINT: arm_print_reg(leftreg);
arm_freeall_registers();
return NOREG;
default: fprintf(stderr, "Unknown AST operator %d\n", n->op);
exit(1);
}
}
生成IF汇编代码
代码如下:
int code_generator(struct ASTnode *n, int reg, int parentASTop);
// 为IF语句和可选的ELSE子句生成代码
int code_IF_generator(struct ASTnode *n)
{
// 生成两个标签:一个用于错误的复合语句,
// 一个用于整个IF语句的结尾。
// 当没有 ELSE 子句时,Lfalse就是结束标签
int Lfalse, Lend;
Lfalse = label();
if (n->right) Lend = label();
// 生成条件代码,然后零跳转到错误标签。
// 我们通过发送Lfalse标签作为寄存器
code_generator(n->left, Lfalse, n->op);
arm_freeall_registers();
// 生成为真的复合语句代码
code_generator(n->mid, NOREG, n->op);
arm_freeall_registers();
// 如果有可选的ELSE子句,则生成跳转到最后
if (n->right) arm_jump(Lend);
arm_label(Lfalse);
// 可选的ELSE子句:生成为假的复合语句和结束标签
if (n->right)
{
code_generator(n->right, NOREG, n->op);
arm_freeall_registers();
arm_label(Lend);
}
return (NOREG);
}
生成汇编代码
对于正常的比较功能,传递AST操作以选择相关的set指令:
// 比较指令表,即mov指令的条件
// AST类型顺序: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE
char *cmplist1[] ={"eq", "ne", "lt", "gt", "le", "ge"};
char *cmplist2[] ={"ne", "eq", "ge", "le", "gt", "lt"};
// 比较两个寄存器并设置寄存器值
int arm_compare_and_set(int ASTop, int r1, int r2)
{
if(ASTop < A_EQ || ASTop > A_GE)
{
fprintf(stderr, "Bad ASTop in arm_compare_and_set() on line %d", Line);
exit(1);
}
fprintf(Outfile, "\tcmp\tr%d, r%d\n", r1, r2);
fprintf(Outfile, "\tmov%s\tr%d, #1\n", cmplist1[ASTop - A_EQ], r1);
fprintf(Outfile, "\tmov%s\tr%d, #0\n", cmplist2[ASTop - A_EQ], r1);
fprintf(Outfile, "\tuxtb\tr%d, r%d\n", r1, r1);
arm_free_register(r2);
return r1;
}
生成标签函数和跳转到指定标签函数:
// 生成标签
void arm_label(int l)
{
fprintf(Outfile, "L%d:\n", l);
}
// 跳转到指定标签
void arm_jump(int l)
{
fprintf(Outfile, "\tb\tL%d\n", l);
}
比较寄存器如果为假则跳转,因此使用AST比较节点类型,进行相反的比较:
// 倒置跳转指令,即b指令的条件
// AST类型顺序: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE
char *invcmplist[] = { "ne", "eq", "ge", "le", "gt", "lt" };
// 比较寄存器如果为假则跳转
int arm_compare_and_jump(int ASTop, int r1, int r2, int label)
{
if (ASTop < A_EQ || ASTop > A_GE)
{
fprintf(stderr, "Bad ASTop in arm_compare_and_jump() on line %d", Line);
exit(1);
}
fprintf(Outfile, "\tcmp\tr%d, r%d\n", r1, r2);
fprintf(Outfile, "\tb%s\tL%d\n", invcmplist[ASTop - A_EQ], label);
arm_freeall_registers();
return (NOREG);
}
测试结果
输入:
{
int a;
int b;
a = 4; b = 8;
if (a <= b)
{
if(a == b)
{
print 10;
}
print a;
}
else
{
print b;
}
if (a > b)
{
print a;
}
else
{
if(a != b)
{
print 10;
}
print b;
}
}
输出(out.s):
.text
.global __aeabi_idiv
.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
.text
.comm a,4,4
.text
.comm b,4,4
mov r4, #4
ldr r3, .L2+0
str r4, [r3]
mov r4, #8
ldr r3, .L2+4
str r4, [r3]
ldr r3, .L2+0
ldr r4, [r3]
ldr r3, .L2+4
ldr r5, [r3]
cmp r4, r5
bgt L1
ldr r3, .L2+0
ldr r4, [r3]
ldr r3, .L2+4
ldr r5, [r3]
cmp r4, r5
bne L3
mov r4, #10
mov r1, r4
ldr r0, .L3
bl printf
L3:
ldr r3, .L2+0
ldr r4, [r3]
mov r1, r4
ldr r0, .L3
bl printf
b L2
L1:
ldr r3, .L2+4
ldr r4, [r3]
mov r1, r4
ldr r0, .L3
bl printf
L2:
ldr r3, .L2+0
ldr r4, [r3]
ldr r3, .L2+4
ldr r5, [r3]
cmp r4, r5
ble L4
ldr r3, .L2+0
ldr r4, [r3]
mov r1, r4
ldr r0, .L3
bl printf
b L5
L4:
ldr r3, .L2+0
ldr r4, [r3]
ldr r3, .L2+4
ldr r5, [r3]
cmp r4, r5
beq L6
mov r4, #10
mov r1, r4
ldr r0, .L3
bl printf
L6:
ldr r3, .L2+4
ldr r4, [r3]
mov r1, r4
ldr r0, .L3
bl printf
L5:
mov r3, #0
mov r0, r3
pop {fp, pc}
.L3:
.word .LC0
.size main, .-main
.L2:
.word a
.word b
输出(out):
4
10
8
总结
我们已经使用IF语句实现我们的第一个控制结构,在此过程中我不得不重写一些现有的内容,而且由于我脑子里还没有完整的架构计划,因此将来可能需要重写更多内容。
这一节的难点在于,对于IF决策我们必须执行与对普通比较运算符相反的比较,我的解决方案是传入每个AST节点其父节点的节点类型,比较节点现在可以查看父节点是否为A_IF节点。
在下一节中,我们将添加另一个控制结构:WHILE循环。现在,我们要整理一下代码,去掉不需要的函数或者模块。