编译器实现之旅——第十三章 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 ...
随机推荐
- Go 如何对多个网络命令空间中的端口进行监听
Go 如何对多个网络命令空间中的端口进行监听 需求为 对多个命名空间内的端口进行监听和代理. 刚开始对 netns 的理解不够深刻,以为必须存在一个新的线程然后调用 setns(2) 切换过去,如果有 ...
- CF1860C 题解
显然是一个博弈论题,考虑 dp. 定义状态 \(dp_i\) 表示先手走到 \(i\) 之后是否有必胜策略,不难发现以下几点: 若走到 \(i\) 之后无路可走,那么就必败. 若走到 \(i\) 之后 ...
- JS--正则表达式--手稿
- Python获得操作日志的最后几行记录
该方法一般用于获得操作日志的最后几行记录 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 f = open('seek.txt', 'rb') 4 ...
- sheetjs导出表格时间错误问题
最近使用sheetjs,前端web去导出生成excel,xlsx表格.其中遇到一种问题,那就是时间出错了!比如多出8小时43秒,少了43秒.看到这种问题的时候,我也一脸懵逼.先上图! 不过在有些人电脑 ...
- [oeasy]python0068_ 字体样式_正常_加亮_变暗_控制序列
字体样式 回忆上次内容 上次了解了一个新的转义模式 \33 逃逸控制字符 esc esc 让输出 退出标准输出流 进行控制信息的设置 可以清屏 也可以设置光标输出的位置 还能做什么呢? 可以设置字符的 ...
- [oeasy]python0022_ python虚拟机_反编译_cpu架构_二进制字节码_汇编语言
程序本质 回忆上次内容 python3 的程序是一个 5.3M 的可执行文件 我们通过which命令找到这个python3.8的位置 将这个python3.8复制到我们的用户目录下 这个文件 ...
- MFC 完全自定义控件
头文件 #pragma once #include "pch.h" class CGridCtrl : public CWnd { public: void Create(CWnd ...
- nginx的一些功能
一.四层(tcp/udp)代理 由于nginx默认是不支持四层代理的因此在安装的时候需要加上对应的模块with-stream ./configure --with-stream # 查看当前nginx ...
- innodb存储引擎了解
mysql常用的存储引擎分为innodb和myisam 其中innodb具有支持事务,执行行级锁,支持MVCC,外键,自动增长列,崩溃恢复等特性.并且mysql在5.5.5之后是数据的默认存储引擎 文 ...