编译器实现之旅——第十三章 if语句和while语句的代码生成器分派函数的实现
在上一章的旅程中,我们已经实现了表达式类代码生成器分派函数,而在这一章的旅程中,我们将要实现if语句和while语句的代码生成器分派函数。if语句和while语句是两种典型的带有跳转指令的语句。观察CMM指令集的实现不难发现,跳转指令,实际上就是通过强行修改IP的值,使得虚拟机下一次看到的CS[IP]并不是当前CS[IP]的下一条语句。所以,跳转指令的摆放位置及其参数,是我们在实现if语句和while语句的分派函数的过程中,需要思考的问题。那么接下来,就让我们先从if语句开始吧。
1. IfStmt节点的分派函数的实现
IfStmt节点,即if语句节点,其可能含有两个或三个子节点:第一子节点代表了if语句的条件表达式部分;而第二子节点代表了如果满足条件,需要执行的部分;第三子节点则代表了else部分。不难想到:如果一个IfStmt节点只含有两个子节点,即不含有else部分,那么其代码模型如下所示:
if 第一子节点生成的代码(其结果最终存放于AX中)
JZ END
第二子节点生成的代码
END: ...
现在的问题是:我们的CMM指令集根本就没有"JZ END"这一说,JZ只能后接一个数字,这怎么办呢?很简单,我们将"END"转换为第二子节点生成的代码的条数即可。例如:第二子节点一共生成了5条指令,那么,我们就需要生成一条"JZ 6"指令,请注意,JZ后面的数字是代码条数5再加上1,如果不加1的话,就会跳转到第二子节点生成的最后一条代码上,这样的边界问题是一定要注意的。
而如果一个IfStmt节点含有三个子节点呢?此时的代码模型就变为了这样:
if 第一子节点生成的代码(其结果最终存放于AX中)
JZ ELSE
第二子节点生成的代码
JMP END
else
ELSE: 第三子节点生成的代码
END: ...
此时的情况就要稍复杂一些,"JMP END"需要被转换为第三子节点生成的代码的条数加1;而由于"JMP END"的存在,"JZ ELSE"就需要被转换为第二子节点生成的代码的条数加1,再加1,以同时越过第二子节点生成的所有代码以及一条多出来的"JMP END"指令。
有了上面代码模型的铺垫,我们就不难得到IfStmt节点的分派函数的实现了。请看:
vector<pair<__Instruction, string>> __CodeGenerator::__generateIfStmtCode(__AST *root, const string &curFuncName) const
{
/*
__TokenType::__IfStmt
|
|---- __Expr
|
|---- __StmtList
|
|---- [__StmtList]
*/
auto codeList = __generateExprCode(root->__subList[0], curFuncName);
auto ifCodeList = __generateStmtListCode(root->__subList[1], curFuncName);
if (root->__subList.size() == 2)
{
/*
if ...
JZ END
...
END: ...
*/
codeList.emplace_back(__Instruction::__JZ, to_string(ifCodeList.size() + 1));
codeList.insert(codeList.end(), ifCodeList.begin(), ifCodeList.end());
}
else
{
/*
if ...
JZ ELSE
...
JMP END
else
ELSE: ...
END: ...
*/
auto elseCodeList = __generateStmtListCode(root->__subList[2], curFuncName);
ifCodeList.emplace_back(__Instruction::__JMP, to_string(elseCodeList.size() + 1));
codeList.emplace_back(__Instruction::__JZ, to_string(ifCodeList.size() + 1));
codeList.insert(codeList.end(), ifCodeList.begin(), ifCodeList.end());
codeList.insert(codeList.end(), elseCodeList.begin(), elseCodeList.end());
}
return codeList;
}
2. WhileStmt节点的分派函数的实现
WhileStmt节点,即while语句节点,其实现与上文中的if语句是类似的。稍有不同的是,while语句中需要用到一次JMP的参数为负数的向前跳转,但其原理是不变的;并且,while语句也没有子节点数量的差异,一定具有两个子节点。接下来,就让我们来看看while语句的代码模型吧:
START: while 第一子节点生成的代码(其结果最终存放于AX中)
JZ END
第二子节点生成的代码
JMP START
END: ...
和我们讨论if语句时一样,我们现在需要思考的是:如何转换这两条"JZ END"和"JMP START"指令?首先看"JZ END"指令,和上文中那个"加1,再加1"的"JZ ELSE"指令类似,由于"JMP START"指令的存在,"JZ END"指令的"END"需要被转换为第二子节点生成的代码的条数加1,再加1;而"JMP START"指令现在对我们来说也不是一个难点了,其需要被转换为负的第二子节点生成的代码的条数再减1(请注意,这里并不是减1,再减1)。
有了上面代码模型的铺垫,我们就不难得到WhileStmt节点的分派函数的实现了。请看:
vector<pair<__Instruction, string>> __CodeGenerator::__generateWhileStmtCode(__AST *root, const string &curFuncName) const
{
/*
__TokenType::__WhileStmt
|
|---- __Expr
|
|---- __StmtList
*/
auto codeList = __generateExprCode(root->__subList[0], curFuncName);
auto stmtCodeList = __generateStmtListCode(root->__subList[1], curFuncName);
/*
START: while ...
JZ END
...
JMP START
END: ...
*/
codeList.emplace_back(__Instruction::__JZ, to_string(stmtCodeList.size() + 2));
codeList.insert(codeList.end(), stmtCodeList.begin(), stmtCodeList.end());
codeList.emplace_back(__Instruction::__JMP, "-" + to_string(codeList.size()));
return codeList;
}
至此,if语句和while语句的代码生成器分派函数的实现就全部完成了。接下来,我们将要实现的是一类看似平淡无奇,实则暗藏玄机的操作。请看下一章:《变量存取的代码生成器分派函数的实现》。
编译器实现之旅——第十三章 if语句和while语句的代码生成器分派函数的实现的更多相关文章
- Gradle 1.12用户指南翻译——第二十三章. Java 插件
其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...
- C++ Primer Plus学习:第十三章
第十三章 类继承 继承的基本概念 类继承是指从已有的类派生出新的类.例: 表 0-1 player.h class player { private: string firstname; string ...
- 20190928 On Java8 第二十三章 注解
第二十三章 注解 定义在 java.lang 包中的5种标准注解: @Override:表示当前的方法定义将覆盖基类的方法.如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示. ...
- 20190825 On Java8 第十三章 函数式编程
第十三章 函数式编程 函数式编程语言操纵代码片段就像操作数据一样容易. 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你 ...
- 【C++】《C++ Primer 》第十三章
第十三章 拷贝控制 定义一个类时,需要显式或隐式地指定在此类型地对象拷贝.移动.赋值和销毁时做什么. 一个类通过定义五种特殊的成员函数来控制这些操作.即拷贝构造函数(copy constructor) ...
- PRML读书会第十三章 Sequential Data(Hidden Markov Models,HMM)
主讲人 张巍 (新浪微博: @张巍_ISCAS) 软件所-张巍<zh3f@qq.com> 19:01:27 我们开始吧,十三章是关于序列数据,现实中很多数据是有前后关系的,例如语音或者DN ...
- <构建之法>第十三章到十七章有感以及这个项目读后感
<构建之法>第十三章到十七章有感 第13章:软件测试方法有哪些? 主要讲了软件测试方法:要说有什么问题就是哪种效率最高? 第14章:质量保障 软件的质量指标是什么?怎么样能够提升软件的质量 ...
- 《Linux命令行与shell脚本编程大全》 第二十三章 学习笔记
第二十三章:使用数据库 MySQL数据库 MySQL客户端界面 mysql命令行参数 参数 描述 -A 禁用自动重新生成哈希表 -b 禁用 出错后的beep声 -B 不使用历史文件 -C 压缩客户端和 ...
- 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高
第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...
- Gradle 1.12 翻译——第十三章 编写构建脚本
有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...
随机推荐
- 嵌入式基础测试手册——基于NXP iMX6ULL开发板(3)
基于测试板卡:创龙科技TLIMX6U-EVM是一款基于NXP i.MX 6ULL的ARM Cortex-A7高性能低功耗处理器设计的评估板,由核心板和评估底板组成.核心板经过专业的PCB Layout ...
- CF1864C 题解
\(x = 2^k\) 是好做的,每次以 \(2^{k-1}\) 为因数即可. 对于其他情况,考虑每次让 \(x\) 减去其二进制下最低位的 \(1\) 直至变成 \(2^k\). 这种策略下显然每个 ...
- SpringBoot整合Flyway数据库版本管理
项目结构 添加依赖 <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-co ...
- Java 、C# Excel模板,数据一对多,主从表关系,导入到数据库
思路 单表导入的比较容易,但是有的时候,可能会出现,一对多数据导入的,这个情况怎么办呢?先理解上面的图,后台获取数据的时候,除了"风险防控措施"外,其他字段先分组,"黄色 ...
- JavaSE进阶核心之class类
Java顶级对象之Object 什么是Object类 Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入 Object类是所有java类的 ...
- mac mysql 设置默认编码格式utf-8
导读 博主百度一番,发现更改mysql默认编码格式,归结以下几个步骤. 详细步骤 切换当前目录 cd / cd private/etc 新建my.cnf文件 在当前目录下:private/etc su ...
- 记一次 redis 事件注册不当导致的内存泄露
线上的程序跑着跑着内存越来越大,并且没有下降的趋势,重启一下程序也只能短暂恢复.通过 htop 命令再按一下 M 键按内存占用大小排个序,程序会占好几个G.那好,让我们来分析一下. 收集dump 通过 ...
- Vue export & export default & import 总结
Vue export & export default & import 总结 by:授客 QQ:1033553122 1. 实践环境 Vue 2.9.6 2. 简介 在ES6 ...
- vs 常用的调试技巧
本地调试,一般打断点, 然后下一步,或者步入,或者运行到上一步. 有专用的对战窗口. 条件断点,输入当前变量的名称,然后打印变量值变量名和对战的一些信息,当然也可以选择进入断点后是否进一步运行 线程调 ...
- MySQL 跨服务器关联查询
如果您需要在 MySQL 中关联查询位于不同服务器的表(跨服务器关联查询),您可以考虑使用 MySQL 的联机查询(Federated MySQL).联机查询允许您在一个服务器上访问和查询另一个服务器 ...