在上一节中,我们实现了困难的数组定义部分;在这一节中,我们着手实现比较简单的逻辑运算,此外我们也会实现识别负数。
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
总结
在本节中,我们实现了逻辑运算和负数识别,在下一节中,我们将会开始着手比较难的局部变量和函数调用参数。