非常抱歉我选择的字体有些问题,导致字符绘制的图表中右侧竖线没有对齐,追求视觉效果的同学可以将它们拷贝下来,粘贴之后改用“宋体”或其它等宽,并且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; }
下集预告:变量取址。