自己动手写RISC-V的C编译器-02语法描述方法和递归下降解析
2022/8/9 6:23:52
本文主要是介绍自己动手写RISC-V的C编译器-02语法描述方法和递归下降解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本节增加对
*
、/
、+
、-
、()
运算的支持
使用生成规则表示运算符优先级
expr = mul("+" mul | "-" mul)* mul = num("*" num | "/" num)*
上面的表达式可以很容易的推导出对于对于运算1*2+3
的语法树
由expr
开始推导乘除法一定会在加减法的更下一层,所以很自然的得出乘法优先级大于加减法优先级。接下来我们对表达式进行拓展,使编译器能解析()、*、/
expr = mul("+" mul | "-"mul)* mul = primary("*" primary | "/" primary)* primary = num | "(" expr ")"
运算1*(2+3)
的语法树如下:
数据结构设计
通过上面的解释,我们发现将一串暂时无意义的字符串变成一个有规则的树形结构可以更好的表示算式的优先级,上面的树形结构就是所谓AST(抽象语法树) 所以解析算式我们采用AST
节点数据结构设计
和上一篇类似,我们依然采用enum
类型来自动生成每个节点的类型编号。
typedef enum { nd_num, nd_add, nd_sub, nd_mul, nd_div, } node_kind;
AST的节点就是一个二叉树的子节点,所以他应该包括三部分节点类型、左子树、右子树、如果是数字节点那还应该包括数字的值。
typedef struct node node; struct node { node_kind kind; node *lch; node *rch; int val; };
使用三个函数来构建二叉树的基本结构
node *new_node(node_kind kind) { // 用于构建一个子节点并返回该节点的指针 node *nd = calloc(1, sizeof(node)); nd->kind = kind; return nd; } node *new_binary(node_kind kind, node *lch, node *rch) { // 用域构建一个二叉树 node *nd = new_node(kind); nd->lch = lch; nd->rch = rch; return nd; } node *new_num(int val) { // 构建一个数字子节点 node *nd = new_node(nd_num); nd->val = val; return nd; }
递归下降解析
构建AST
有了上面的运算规则就可以着手编写程序对算式进行解析了,首先先构建语法树。构建语法树需要用到三个函数expr
、mul
、primary
分别用于解析算式、乘法算式、数字或括号表达式
node *expr(token **rest, token *tok); node *mul(token **rest, token *tok); node *primary(token **rest, token *tok);
下面通过代码具体看看如何构建出一颗AST
// expr = mul("+" mul | "-"mul)* // mul = primary("*" primary | "/" primary)* // primary = num | "(" expr ")" node *expr(token **rest, token *tok){ node *nd = mul(&tok, tok); while (true) { if (equal(tok, "+")) { nd = new_binary(nd_add, nd, mul(&tok, tok->next)); continue; } if (equal(tok, "-")) { nd = new_binary(nd_sub, nd, mul(&tok, tok->next)); continue; } *rest = tok; return nd; } } node *mul(token **rest, token *tok) { node *nd = primary(&tok, tok); while (true) { if (equal(tok, "*")) { nd = new_binary(nd_mul, nd, primary(&tok, tok->next)); continue; } if (equal(tok, "/")) { nd = new_binary(nd_div, nd, primary(&tok, tok->next)); continue; } *rest = tok; return nd; } } node *primary(token **rest, token *tok) { if (tok->kind == num) { node *nd = new_num(tok->val); *rest = tok->next; return nd; } if (equal(tok, "(")) { node *nd = expr(&tok, tok->next); *rest = skip(tok, ")"); return nd; } error_at(tok->loc, "expexted an expression"); return NULL; }
通过遍历AST生成汇编代码
以以下AST为例
先遍历到最右子节点,将3
压入栈。之后遍历到节点2
,并放入a0
中,将3
从栈中弹出并放入a1
中。根据2
和3
的父节点类型选择相应的汇编代码,最后完成汇编代码的生成。
int depth; void push() { printf(" addi sp, sp, -8\n"); printf(" sd a0, 0(sp)\n"); depth ++; } void pop(char *reg) { printf(" ld %s, 0(sp)\n", reg); printf(" addi sp, sp, 8\n"); depth --; } void gen_expr(node *nd) { if (nd->kind == nd_num) { printf(" li a0, %d\n", nd->val); return; } gen_expr(nd->rch); push(); gen_expr(nd->lch); pop("a1"); switch (nd->kind) { case nd_add: printf(" add a0, a0, a1\n"); return; case nd_sub: printf(" sub a0, a0, a1\n"); return; case nd_mul: printf(" mul a0, a0, a1\n"); return; case nd_div: printf(" div a0, a0, a1\n"); return; default: break; } }
这篇关于自己动手写RISC-V的C编译器-02语法描述方法和递归下降解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享