非常抱歉我选择的字体有些问题,导致字符绘制的图表中右侧竖线没有对齐,追求视觉效果的同学可以将它们拷贝下来,粘贴之后改用“宋体”或其它等宽,并且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;
}
下集预告:变量取址。
本文详细解析了Jerry语言中的流程控制结构,包括分支和循环语句的实现方式,并介绍了如何通过特定指令码来管理和实现这些控制结构。
1555

被折叠的 条评论
为什么被折叠?



