非常抱歉我选择的字体有些问题,导致字符绘制的图表中右侧竖线没有对齐,追求视觉效果的同学可以将它们拷贝下来,粘贴之后改用“宋体”或其它等宽,并且1个中文字符宽度等于2个ASCII字符宽度的字体显示。
Jerry语言比较简单,流程控制仅限于分支和循环(而且只有while循环)。这里并不会对它们的指令生成做优化,只是给出能够运作的最基本解决方案。
首先是分支语句。完整的分支语句指令结构是这样的
+----------------------+
| condition 指令 |
+----------------------+
| 跳转指令 a -------+
+----------------------+ |
| valid 指令 | |
+----------------------+ |
+------- 跳转指令 b | |
| +----------------------+ <-+
| | invalid 指令 |
+-> +----------------------+
其中跳转指令 a的指令码为JNT(JMP_NOT_TOP),即当栈顶值为0时跳转,而跳转指令 b的指令码为JMP,是无条件跳转。各路跳转指令的箭头目标都指向指令间的中缝处有点不太好,所以这里在引入一些NOP指令,让整个结构看起来是这样的
+----------------------+
| condition 指令 |
+----------------------+
| 跳转指令 a -------+
+----------------------+ |
| valid 指令 | |
+----------------------+ |
+------- 跳转指令 b | |
| +----------------------+ |
| | NOP 指令 | <-+
| +----------------------+
| | invalid 指令 |
| +----------------------+
+-> | NOP 指令 |
+----------------------+
对于else分支或者甚至两个分支都为空的分支语句,并不进行特别的优化处理,仅仅是让上图中对应的区块的指令数目为0而已。
现在上代码。
struct List* insIfElseNode(void* node)
{
struct IfElseNode* self = (struct IfElseNode*)node;
AcceptType conditionType = self->condition->typeOf(self->condition);
if (REAL == conditionType) {
// 报错: 实型不可以作为条件
return (struct List*)newArrayList();
}
struct List* conditionIns = self->condition->
createInstruction(self->condition);
struct JumpInstruction* toElse = (struct JumpInstruction*)
allocate(sizeof(struct JumpInstruction));
// 这个指令跳转到 invalid 分支之前的那条 NOP 指令
struct JumpInstruction* getOut = (struct JumpInstruction*)
allocate(sizeof(struct JumpInstruction));
// 这条语句在 valid 分支之后, 跳转到整个分支语句之后的那条 NOP 指令
struct NoParamInstruction* beforeInv = (struct NoParamInstruction*)
allocate(sizeof(struct NoParamInstruction));
// invalid 分支之前的 NOP 指令
struct NoParamInstruction* outlet = (struct NoParamInstruction*)
allocate(sizeof(struct NoParamInstruction));
// 整个语句之后的 NOP 指令
beforeInv->code = outlet->code = NOP;
toElse->code = JMP_NOT_TOP;
toElse->targetIns = (struct AbstractInstruction*)beforeInv;
getOut->code = JMP;
getOut->targetIns = (struct AbstractInstruction*)outlet;
appendIns(conditionIns, toElse);
if(NULL != self->valid) { // 若是 NULL, 则不管它
appendInsList(conditionIns,
self->valid->createInstruction(self->valid));
}
appendIns(appendIns(conditionIns, getOut), beforeInv);
if(NULL != self->invalid) { // 若是 NULL, 则不管它
appendInsList(conditionIns,
self->invalid->createInstruction(self->invalid));
}
return appendIns(conditionIns, outlet);
}
循环语句的结构看起来比分支要简单些,不过,一个循环语句仍然会产生两条跳转语句(同样的,对于循环体为空的循环,现在并不做优化)。它们看起来是这样的
+----------------------+
+-> | NOP 指令 |
| +----------------------+
| | condition 指令 |
| +----------------------+
| | 跳转指令 a -------+
| +----------------------+ |
| | 循环体指令 | |
| +----------------------+ |
+------- 跳转指令 b | |
+----------------------+ |
| NOP 指令 | <-+
+----------------------+
跳转指令 a是一条JNT指令,而跳转指令 b是JMP指令。除此之外,还有一点点不同,那就是——while语句需要管理一个跳出栈,这个跳出栈让循环内部的break语句知道该往哪里跳。所以,在实现循环的指令生成前,先需要做这样一些事情
struct Stack loopStack; // 循环栈
// 入栈
// 参数是循环的出口点
// 如刚才图示中 while 循环之后的那条 NOP 指令
void enterLoop(void* outlet)
{
loopStack.push(&loopStack, outlet);
}
// 出栈
void leaveLoop(void)
{
loopStack.pop(&loopStack);
}
// 返回栈顶出口指令
// 也就是 break 语句跳转的目标指令
// 当栈空时返回 NULL
struct AbstractInstruction* loopOutlet(void)
{
return (struct AbstractInstruction*)(loopStack.peek(&loopStack));
}
有了这些,事情就好办了,while会这样生成指令
struct List* insWhileNode(void* node)
{
struct WhileNode* self = (struct WhileNode*)node;
if (REAL == self->condition->typeOf(self->condition)) {
// 报错: 实型不可以作为条件
return (struct List*)newArrayList();
}
struct List* insList = (struct List*)newArrayList(); // 新建空表
struct NoParamInstruction* beforeAll = (struct NoParamInstruction*)
allocate(sizeof(struct NoParamInstruction));
// 循环之前的 NOP 指令
struct NoParamInstruction* outlet = (struct NoParamInstruction*)
allocate(sizeof(struct NoParamInstruction));
// 循环出口
struct JumpInstruction* terminate = (struct JumpInstruction*)
allocate(sizeof(struct JumpInstruction));
// 跳转指令, 条件失败时跳出循环
struct JumpInstruction* loop = (struct JumpInstruction*)
allocate(sizeof(struct JumpInstruction));
// 跳转指令, 绕回循环开始
terminate->code = JMP_NOT_TOP;
terminate->targetIns = (struct AbstractInstruction*)outlet;
loop->code = JMP;
loop->targetIns = (struct AbstractInstruction*)beforeAll;
beforeAll->code = outlet->code = NOP;
enterLoop(outlet); // 将出口压入循环栈
appendIns(insList, beforeAll);
appendInsList(insList, self->condition->createInstruction(self->condition));
appendIns(insList, terminate);
if (NULL != self->loop) { // 若为 NULL, 则忽略它
appendInsList(insList, self->loop->createInstruction(self->loop));
}
appendIns(insList, loop);
appendIns(insList, outlet);
leaveLoop(); // 离开循环
return insList;
}
聊完了循环自然要谈谈 break,不过仰仗刚才的那些铺垫,这东西非常简单
struct List* insBreakNode(void* node)
{
struct List* insList = (struct List*)newArrayList();
struct AbstractInstruction* outlet = loopOutlet();
if (NULL == outlet) {
// 报错: break 不在循环内部
} else {
struct JumpInstruction* brk = (struct JumpInstruction*)
allocate(sizeof(struct JumpInstruction));
brk->code = JMP;
brk->targetIns = outlet;
appendIns(insList, brk);
}
return insList;
}
下集预告:变量取址。
分享到:
相关推荐
采用递归下降语法制导翻译法,对算术表达式、赋值语句进行语义分析并生成四元式序列。 实验的输入和输出 输入是语法分析提供的正确的单词串,输出为三地址指令形式的四元式序列。 例如:对于语句串 begin a:=2+3*4 x...
语法制导翻译和中间代码生成是编译器设计的关键步骤,它们在编译过程中起着至关重要的作用。首先,我们要回顾编译过程,它通常包括词法分析、语法分析、语义分析和代码生成等阶段。语义分析位于语法分析之后,主要...
例如,当解析到C语言中的if语句时,语法制导翻译会生成对应的条件判断和分支指令。 5. **编译原理实验**: 在学习和实践中,编译原理的实验通常涉及实现上述组件。学生可能需要编写词法分析器、语法分析器,并应用...
《编译原理语法制导翻译》 在计算机科学领域,编译原理是研究如何将高级编程语言转换为机器可理解的指令集的关键分支。语法制导翻译是编译器设计中的一种重要技术,它结合了语法分析和语义处理,以确保程序的正确性...
4. **语法制导翻译**:基于抽象语法树,编译器进行语义分析,检查程序的逻辑正确性。此阶段可能会执行类型检查和常量折叠等操作,并可能引入一些优化。同时,它会根据语法规则生成相应的中间代码。 5. **中间代码...
总结来说,"语义分析器--语法制导翻译绘制图形"项目提供了一个实践编译器设计的平台,涵盖了语义分析、语法制导翻译和图形绘制等多个重要概念。通过深入研究这个项目,开发者可以深化对编译原理的理解,掌握C++编程...
2. 编写语法分析器(通常使用递归下降或LL(1)、LR(1)方法),生成语法树。 3. 实现三地址码生成器,从语法树中提取计算路径并转换为三地址码指令。 4. 编写代码生成器,将三地址码转换为目标机器码。 在压缩包中的...
"基于语法制导翻译的表达式转换编译器"是一种利用特定规则集(语法规则)来指导源代码转换过程的编译器设计方法。本项目专注于中缀表达式到后缀表达式的转换,这是编译器前端词法分析、语法分析和语义分析的一个典型...
在树的每个节点上,编译器执行与该节点对应的语法制导规则,这可能包括计算值、生成目标代码指令、存储或传递数据等。这种做法使得编译器可以精确地按照源代码的语法结构生成目标代码,从而确保程序的正确性。 语法...
中间代码生成_表示形式、语法制导方法》主要探讨了编译器设计中的关键环节——中间代码生成,这是编译过程中的一个重要阶段,目的是为了便于优化和移植。本节内容主要包括三元式、逆波兰式、抽象语法树(AST)以及四...
语法制导翻译与中间代码生成是编译器设计中的关键步骤,主要涉及程序的静态语义审查、类型检查、控制流分析以及中间代码生成。在这个过程中,编译器不仅要确保程序符合语法规则,还要对其含义进行深入理解,以便生成...
它主要涉及三个核心过程:词法分析、语法分析和代码生成,而四元式在这个过程中起到重要作用。 首先,词法分析(Lexical Analysis)是编译器的第一步,它的任务是对源代码进行扫描,将源代码分解成一系列有意义的、...
三地址代码生成器就是在这个阶段介入,它遍历AST,为每个节点生成对应的三地址指令。 在Turbo C3.0这样的编译器环境下,开发者可以实现自己的三地址代码生成器。虽然Turbo C3.0是一款较老的编译器,但其C语言编译器...
2. **语法分析**:根据词法单元流构造抽象语法树(AST),验证程序的语法结构是否符合C语言规范。 3. **语义分析**:对AST进行类型检查,确保操作符与操作数的类型匹配,同时处理作用域和类型定义等问题。 4. **三...
采用递归下降语法制导翻译法,对算术表达式、赋值语句进行语义分析并生成四元式序列。 三、实验的结果验证 1.输入是语法分析后提供的正确的单词串,输出为三地址指令形式的四元式序列。 给出语句串: begin_a:=2+3*...
《编译原理 实验报告》 实验一主要涵盖了词法分析...词法分析器通过正规式和状态图解析源代码,而语法制导定义和三地址代码生成则涉及到语义分析和代码生成。理解这些概念对于深入学习编译原理和开发编译器至关重要。
采用递归下降语法制导翻译法,对算术表达式、赋值语句进行语义分析并生成四元式序列。 实验的输入和输出 输入是语法分析提供的正确的单词串,输出为三地址指令形式的四元式序列。 例如:对于语句串 begin a:=2+3*4;x...
本项目“基于C语言实现简单语言递归下降语法制导翻译”深入探讨了这一主题,主要关注如何使用C语言来设计和实现一个编译器的核心部分——语义分析和中间代码生成。 首先,我们要理解什么是递归下降解析。递归下降是...