在上一章的旅程中,我们已经实现了表达式类代码生成器分派函数,而在这一章的旅程中,我们将要实现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语句的代码生成器分派函数的实现的更多相关文章

  1. Gradle 1.12用户指南翻译——第二十三章. Java 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  2. C++ Primer Plus学习:第十三章

    第十三章 类继承 继承的基本概念 类继承是指从已有的类派生出新的类.例: 表 0-1 player.h class player { private: string firstname; string ...

  3. 20190928 On Java8 第二十三章 注解

    第二十三章 注解 定义在 java.lang 包中的5种标准注解: @Override:表示当前的方法定义将覆盖基类的方法.如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示. ...

  4. 20190825 On Java8 第十三章 函数式编程

    第十三章 函数式编程 函数式编程语言操纵代码片段就像操作数据一样容易. 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你 ...

  5. 【C++】《C++ Primer 》第十三章

    第十三章 拷贝控制 定义一个类时,需要显式或隐式地指定在此类型地对象拷贝.移动.赋值和销毁时做什么. 一个类通过定义五种特殊的成员函数来控制这些操作.即拷贝构造函数(copy constructor) ...

  6. PRML读书会第十三章 Sequential Data(Hidden Markov Models,HMM)

    主讲人 张巍 (新浪微博: @张巍_ISCAS) 软件所-张巍<zh3f@qq.com> 19:01:27 我们开始吧,十三章是关于序列数据,现实中很多数据是有前后关系的,例如语音或者DN ...

  7. <构建之法>第十三章到十七章有感以及这个项目读后感

    <构建之法>第十三章到十七章有感 第13章:软件测试方法有哪些? 主要讲了软件测试方法:要说有什么问题就是哪种效率最高? 第14章:质量保障 软件的质量指标是什么?怎么样能够提升软件的质量 ...

  8. 《Linux命令行与shell脚本编程大全》 第二十三章 学习笔记

    第二十三章:使用数据库 MySQL数据库 MySQL客户端界面 mysql命令行参数 参数 描述 -A 禁用自动重新生成哈希表 -b 禁用 出错后的beep声 -B 不使用历史文件 -C 压缩客户端和 ...

  9. 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高

    第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...

  10. Gradle 1.12 翻译——第十三章 编写构建脚本

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

随机推荐

  1. FFmpeg开发笔记(三十五)Windows环境给FFmpeg集成libsrt

    ​<FFmpeg开发实战:从零基础到短视频上线>一书的"10.2  FFmpeg推流和拉流"提到直播行业存在RTSP和RTMP两种常见的流媒体协议.除此以外,还有比较两 ...

  2. Java int/int 保留2位小数

    @Test public void txfloat() { // TODO 自动生成的方法存根 int a=9; int b=7; DecimalFormat df=new DecimalFormat ...

  3. 「Pygors跨平台GUI」2:安装MinGW-w64、MSYS2还是WSL2

    「Pygors系列」一句话导读: MinGW-w64只有编译器,MSYS2带着更新环境,WSL2实用性比较高 历史与渊源   Windows平台 Linux平台 二进制兼容 WSL2:运行Linux程 ...

  4. 解决方案 | Chrome/Edge 总是自动修改我的pdf默认打开方式

    1.问题描述 最近我的pdf文件总是被chrome打开(如图1),而且点击属性,更改别的pdf阅读器也不管用(如图2),此时的chrome就像个流氓软件一样. 图1 被chrome劫持 图2 点击属性 ...

  5. log4cpp的安装及使用

    目录 前言 安装 使用 示例代码 配置文件 编译链接 输出 前言 本文的操作均在ubuntu20.04下进行 安装 本文仅介绍从源码编译安装log4cpp的过程. ①在开始编译前,首先要确保系统中安装 ...

  6. [oeasy]python0116_文字的起源_苏美尔文明_楔形文字_两河流域

    文字起源 回忆上次内容 上次回顾了西里尔字符的编码过程 KOI-7 KOI-8   ISO-8859 系列进行总结 字符扩展 ascii 共 16 种 由iso组织制定 从 iso-8859-1 到 ...

  7. 重磅消息:微软发布多平台应用UI框架 MAUI,网友直呼:牛x

    本文内容来自微软开发博客:https://devblogs.microsoft.com/dotnet/introducing-net-multi-platform-app-ui/ 转载请注明来源,公众 ...

  8. java面试一日一题:垃圾回收器如何组合使用

    问题:请讲下java中垃圾回收器如何组合使用 分析:该问题主要考察对垃圾回收器的深度理解 回答要点: 主要从以下几点去考虑, 1.垃圾回收器有哪些种类,每种的特点 2.组合使用怎么理解 在上篇文章&l ...

  9. 对比python学julia(第二章)--(第二节)勾股树—分形之美

    2.1.问题描述 二话不说,先上图: 图一.勾股定理图形                                                          图二.勾股树         ...

  10. 使用GPU计算时,单精度float32类型和半精度float16类型运算效率的区别

    最近在看资料时发现写着使用float16 半精度类型的数据计算速度要比float32的单精度类型数据计算要快,因为以前没有考虑过数据类型对计算速度的影响,只知道这个会影响最终的计算结果精度.于是,好奇 ...