动手实现编译器(八)——IF条件语句

在上一节中,我们实现了比较运算符的计算,现在,我们可以容易地实现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循环。现在,我们要整理一下代码,去掉不需要的函数或者模块。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值