动手实现编译器(十六)——逻辑运算

在上一节中,我们实现了困难的数组定义部分;在这一节中,我们着手实现比较简单的逻辑运算,此外我们也会实现识别负数。
SysY语言中包含的逻辑运算符有:

  • 一元逻辑运算符 !
  • 二元逻辑运算符 &&||

它们语法定义如下

单目运算符: UnaryOp → ‘!’ 注:’!'仅出现在条件表达式中
逻辑与表达式: LAndExp → EqExp | LAndExp ‘&&’ EqExp
逻辑或表达式: LOrExp → LAndExp | LOrExp ‘||’ LAndExp

修改词法分析

与往常一样,我们从添加新单词开始。 这次有几个:

输入单词类型
||T_LOGOR
&&T_LOGAND
!T_LOGNOT

修改scan()函数,增加对这些单词的解析。

// 扫描并返回在输入中找到的下一个单词。
// 如果标记有效则返回 1,如果没有标记则返回 0
int scan(struct token *t)
{
	/*......其他代码......*/
    case '!':   if((c = get_nextchr()) == '=')
                {
                    t->token = T_NE;
                }
                else
                {
                    putchar(c);
                    t->token = T_LOGNOT;
                }
                break;
    /*......其他代码......*/
    case '&':   if ((c = get_nextchr()) == '&')
                {
                    t->token = T_LOGAND;
                }
                else
                {
                    put_backchr(c);
                }
                break;
    case '|':   if ((c = get_nextchr()) == '|')
                {
                    t->token = T_LOGOR;
                }
                else
                {
                    put_backchr(c);
                }
                break;
    /*......其他代码......*/
}

修改语法分析

现在我们需要解析这些运算符。其中一些运算符是二元的运算符:||&&,剩余的是一元的!。我们已经有优先权二元运算符的框架。我们可以简单地添加新的运算符到框架。
我们首先要修改单词类型和AST节点操作类型的顺序,使其可以区分运算符。

// 单词类型
enum
{
    T_EOF,
    // 运算符
    T_EQU, T_LOGOR, T_LOGAND, T_EQ, T_NE, T_LT, T_GT, T_LE, T_GE,
    T_ADD, T_SUB, T_MUL, T_DIV, T_MOD, T_LOGNOT,

    // 数据类型
    T_VOID, T_KEYINT,

    // 结构类型
    T_INT, T_COMMA, T_SEM, T_PRINT,  T_IDENT,
    T_LBRACE, T_RBRACE, T_LPAREN, T_RPAREN, T_LBRACKET, T_RBRACKET,

    // 关键词类型
    T_IF, T_ELSE, T_WHILE,  T_RETURN
};

// AST节点类型
enum
{
    A_ASSIGN = 1, A_LOGOR, A_LOGAND, A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE,
    A_ADD, A_SUB, A_MUL, A_DIV, A_MOD, A_LOGNOT,
    A_INT, A_IDENT, A_LVIDENT, A_GLUE, 
    A_IF, A_PRINT, A_WHILE, A_FUNCTION, A_FUNCTIONCALL, A_RETURN,
    A_ADDRESS, A_LOGADD
};

现在,为每个二元运算符按照单词类型的顺序给定其优先级。

// 每个AST节点的运算符优先级
int OpPrec[] = {0, 10, 20, 30, 40, 40, 80, 80, 80, 80, 90, 90, 100, 100, 100, 110};

我们这里有一元操作符!,所以我们将其与二元操作符分开

// 返回一个以二元操作符为根的树
struct ASTnode *binexpr(int pretokentype)
{
	/*......其他代码......*/
	
    // 从单词类型得到到节点类型,然后合并左、右子树
    if(token_op(tokentype) != A_LOGNOT)
    {
        left = mkastnode(token_op(tokentype), left, NULL, right, 0);
    }
    else
    {
        left = mkastnode(token_op(tokentype), NULL, NULL, right, 0);
    }
	/*......其他代码......*/
}

接下来,我们来新增解析负数,修改primary()函数,使其可以识别负数,当解析左右值时,若遇到负号,认为其为负数。

    case T_SUB:
        scan(&Token);
        n = mkastleaf(A_INT, Token.intvalue * -1);
        break;
}

修改代码生成器

在代码生成器中code_generator(),增加对!,&&||的解析。

        case A_LOGAND:   code_generator(n->left, label, parentASTop);
                         code_generator(n->right, label, parentASTop);
                         return NOREG;
        case A_LOGOR:    // 逻辑操作取反,并跳转到Ltrue标签
                         n->left->op = operator_negate(n->left->op);
                         code_generator(n->left, label - 1, parentASTop);
                         code_generator(n->right, label, parentASTop);
                         return NOREG;
        case A_LOGNOT:   // 逻辑操作取反,继续执行操作
                         n->right->op = operator_negate(n->right->op);
                         code_generator(n->right, label, parentASTop);
                         return NOREG;

在这里解析!的过程为,对逻辑操作取反,然后正常执行操作。
在这里解析&&的过程为,正常执行逻辑操作,如果左值为假,跳转到Lflase;如果左值为真,继续计算右值。
在这里解析||的过程为,对逻辑操作取反,如果左值为真(注意因为逻辑操作取反,所以得到的答案是假,因此会跳转),跳转到Ltrue(Ltrue = Lflase - 1);如果左值为假(此时得到的答案是真),继续计算右值。
对于此,我们增加一个操作符取反的函数。

// 逻辑关系符号取反
int operator_negate(int op)
{
    switch (op)
    {
        case A_EQ: return A_NE;
        case A_NE: return A_EQ;
        case A_LT: return A_GE;
        case A_GT: return A_LE;
        case A_LE: return A_GT;
        case A_GE: return A_LT;
        default:   fprintf(stderr, "error operator:%d on line %d\n", op, Line);
                   exit(1);
    }
    return -1;
}

最后,我们修改IF语句生成函数if_statement()和WHILE语句生成函数while_statement()的报错条件为

	condAST->op < A_LOGOR || (condAST->op > A_GE && condAST->op != A_LOGNOT)

测试结果

输入:

int a = 4, b = 3; // a = 4, b = 3

/*计算a+b的值,
  并打印在频幕上*/ 

int main()
{
   if(a == 4 && b == 3)
   {
      a = -1;
	print a;
    }
    if((a > b || a < 2) && b > 5)
   {
	a = 2;
	print a;
    }
    else
    {
       b = 1;
	print b;
    }
    return b;
}

输出(out.s):

	.text
	.global __aeabi_idiv
	.section	.rodata
	.align  2
.LC0:
	.ascii  "%d\012\000"
	.text
	.global a
	.data
	.align  2
	.type   i, %object
	.size   i, 4
a:	.word   4
	.text
	.global b
	.data
	.align  2
	.type   i, %object
	.size   i, 4
b:	.word   3
	.text
	.align  2
	.globl	main
	.type	main, %function
main:
	push    {fp, lr}
	add     fp, sp, #4
	sub	sp, sp, #8
	str	r0, [fp, #-8]
	ldr	r3, .L2+0
	ldr	r4, [r3]
	mov	r5, #4
	cmp	r4, r5
	bne	L3
	ldr	r3, .L2+4
	ldr	r4, [r3]
	mov	r5, #3
	cmp	r4, r5
	bne	L3
L2:
	mov	r4, #-1
	ldr	r3, .L2+0
	str	r4, [r3]
	ldr	r3, .L2+0
	ldr	r4, [r3]
	mov     r1, r4
	ldr     r0, .L3
	bl      printf
L3:
	ldr	r3, .L2+0
	ldr	r4, [r3]
	ldr	r3, .L2+4
	ldr	r5, [r3]
	cmp	r4, r5
	bgt	L4
	ldr	r3, .L2+0
	ldr	r4, [r3]
	mov	r5, #2
	cmp	r4, r5
	bge	L5
	ldr	r3, .L2+4
	ldr	r4, [r3]
	mov	r5, #5
	cmp	r4, r5
	ble	L5
L4:
	mov	r4, #2
	ldr	r3, .L2+0
	str	r4, [r3]
	ldr	r3, .L2+0
	ldr	r4, [r3]
	mov     r1, r4
	ldr     r0, .L3
	bl      printf
	b	L6
L5:
	mov	r4, #1
	ldr	r3, .L2+4
	str	r4, [r3]
	ldr	r3, .L2+4
	ldr	r4, [r3]
	mov     r1, r4
	ldr     r0, .L3
	bl      printf
L6:
	ldr	r3, .L2+4
	ldr	r4, [r3]
	mov	r0, r4
	b	L1
L1:
	sub	sp, fp, #4
	pop	{fp, pc}
	.align	2
.L3:
	.word   .LC0
.L2:
	.word   a
	.word   b

输出(out):

-1
1

总结

在本节中,我们实现了逻辑运算和负数识别,在下一节中,我们将会开始着手比较难的局部变量和函数调用参数。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值