Add *, / and ()
这节实现了简单的加减乘除计算器。有基本的词法、语法、代码生成的三个过程。
知识点
一.C语言
- ispunct:判断字符是否为标点符号(ctype.h)
二.汇编
- x86
cdq
CDQ(Convert Double to Quad,将双字数据扩展为四字),通常出现在除法运算之前.将符号 EAX 位扩展到 EDX 中(有点绕不太明白了)。
idiv
有符号除(imul有符号乘)
1 2 3 4
mov eax, 1234 ;idiv书写时只涉及一个寄存器,所以被除数存放在eax/rax中 mov ecx, 17 ;ecx除数 cdq ; EDX = signbit(EAX) idiv ecx ; EAX = 1234/17 EDX = 1234%17
- RISC-V
mul、div
mul、div在RISCV-V中都是有符号的
1 2
mul a0, a0, a1 ; a0 = a0*a1 div a0, a0, a1 ; a0 = a0/a1,貌似没有哪个寄存器存放余数
sd、ld指令
sd(store doubleword)将寄存器的值存储到内存中.ld则是sd的反操作。
1 2 3 4 5 6
;1.sd ; sd x, offset(y),x和y是寄存器,分别表示源操作数和基地址。offset是一个12位的立即数,表示从基地址y偏移的字节数。 sd a0, 0(sp) ; a0的数据存储到sp中无偏移动 ;2.ld ld a1, 0(sp) ; sp中无偏移数据加载到a1
栈操作
RISC-V没有压栈弹栈指令。RISC-V中压栈和弹栈除了需要手动操作栈指针(压栈 sp-8(64位),弹栈 sp+8),还要用sd/ld指令将数据加载到栈中或从栈取数据到寄存器。
1 2 3 4 5 6 7
;压栈(将a0的数据压入栈中) addi sp, sp, -8 ;下压栈 sd a0, 0(sp) ;弹栈(将压入栈中数据弹出到) addi sp, sp, +8 ;上增栈 ld a1,0(sp)
三.语法分析方面(expr)
他跳过了BNF只给了注释默认读者明白 expr、mul、primary的遍历逻辑。这个写法拿个yacc、bison或者ANTLR(calculator)的语法分析源码对照基本可以知道一二.因为他手写的是递归下降parser所以在解析表达式时优先级越高的越靠后。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// expr = mul ("+" mul | "-" mul)* //组成:乘数、(0或多个 加或减号、乘数)
// mul = primary ("*" primary | "/" primary)* //组成:基数、(0或多个 乘或除号、基数)
// primary = "(" expr ")" | num //组成:1,括号包围表达式;2.单纯的数字
static Node *expr(void) {
Node *node = mul();
for (;;) {
if (consume('+'))
node = new_binary(ND_ADD, node, mul());
else if (consume('-'))
node = new_binary(ND_SUB, node, mul());
else
return node;
}
}
static Node *mul(void) {//叫mul实际还负责div,有点迷惑人的😂
Node *node = primary();
for (;;) {
if (consume('*'))
node = new_binary(ND_MUL, node, primary());
else if (consume('/'))
node = new_binary(ND_DIV, node, primary());
else
return node;
}
}
static Node *primary(void) {
if (consume('(')) {
Node *node = expr();
expect(')');
return node;
}
return new_num(expect_number());
}
代码生成(gen)
分别递归左右节点完了看看根节点是否要做加减乘除。当然他这里是粗暴的代码生成:负责计算的节点可能是数字也可能是个表达,所以他通过2个寄存器来运算这就涉及到寄存器的保存和恢复了(如果单单是数字可以只用一个寄存器)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void gen(Node *node) {
if (node->kind == ND_NUM) {
printf(" push %ld\n", node->val);
return;
}
gen(node->lhs);
gen(node->rhs);
printf(" pop rdi\n");
printf(" pop rax\n");
switch (node->kind) {
case ND_ADD:
printf(" add rax, rdi\n");
break;
case ND_SUB:
printf(" sub rax, rdi\n");
break;
case ND_MUL:
printf(" imul rax, rdi\n");
break;
case ND_DIV:
printf(" cqo\n");
printf(" idiv rdi\n");
break;
}
printf(" push rax\n");
}
This post is licensed under CC BY 4.0 by the author.