是的, 还有几个节点的指令生成没有测试呢, 包括流控制语句, IO. 不过呢, 流控制是相对比较容易优化的, 进行"生成指令再对比"的测试不太好维护, 而 IO 指令很简单: 所以要不这样, 把指令写出来, 然后开个虚拟机运行, 如果运行没问题就算测试通过了.
语义错误处理
一些常见的语义错误处理在之前的之前的内容中都以注释形式出现, 不过如果留心的话, 上次更新的代码这些错误处理都是完备的, 比如在 jerry-ui.h 和 jerry-ui.c 两个文件中给出了这些错误的 UI, 而 const.h 中更新了很多字符串字面常量宏作为错误信息. 当然你也可以随意修改, 随便它们怎么运作. 这里只是稍带提一下, 以免等会儿突然见到会显得很突兀.
/* 一般语义错误 */
void semanticErr(ErrMsg err, int line)
{
fprintf(stderr, "Error occurs at line %d:\n"
" %s\n",
line, err);
failed = 1;
}
/* 符号错误* /
void symError(ErrMsg err, struct VariableNode* var)
{
fprintf(stderr, "Error occurs at variable `%s', line %d:\n"
" %s\n",
var->ident, var->line, err);
failed = 1;
}
参数处理
参数处理目前采用比较笨的方法, 就是逐个参数进行比较. 比如, 现在允许参数有
/* 输出帮助信息 */ #define OPTION_HELP_INFO "--help" /* 指定目标文件 */ #define OPTION_OUTPUT_SPECIFY "-o"
参数解析循环
static int showHelp(int argc, char* argv[]); static int setOutputFileName(int argc, char* argv[]); static int setFileToProcess(int argc, char* argv[]); void handleParam(int argc, char* argv[], FILE** out) { int i = 1; if (1 == argc) { cmdParamError("Jerry: too few arguments."); } while (i < argc) { if (0 == strcmp(OPTION_HELP_INFO, argv[i])) { i += showHelp(argc - i - 1, argv + i + 1); } else if (0 == strcmp(OPTION_OUTPUT_SPECIFY, argv[i])) { i += setOutputFileName(argc - i - 1, argv + i + 1); } else { i += setFileToProcess(argc - i, argv + i); } ++i; } *out = fopen(outFileName, "wb"); if (NULL == *out) { cmdParamError("output file access error."); } }
每个分支匹配一个参数, 然后传入剩余的参数到具体的处理函数中去. 而每个处理函数的返回值则是它所消耗的参数个数, 以便于循环中调整参数指针.
指令写出
其它边边角角的东西不细说了, 现在切换到 jerry-compiler.c 文件中. main 函数中并没有写出的操作, 在调用了 handleParam 之后只是为语法分析进行铺垫工作和资源分配之类的. 嗯, 另有一个地方比 main 函数更适合完成这项工作, 那就是压在语法分析器栈栈底的无名英雄 FakeDefaultAnalyser 的 consumeNonTerminal 函数.
很显然, 当这个函数被调用时, 如果没有任何错误, 那么一定是其它所有分析器都完成任务正常退出了, 因此, 传递给它的语法节点就是表征整个源文件的语法树. 那么只需要将这个节点的指令序列输出就搞定了.
void fakeDefaultAnalyserConsumeNT(void* self, struct AbstractSyntaxNode* node) { if (NULL == node) { printf("Nothing returned.\n"); } else { node = (struct AbstractSyntaxNode*)newBasicBlockNode(node); initStack(&loopStack); initialSymTabManager(); struct List* insList = node->createInstruction(node); finalizeSymTabManager(); loopStack.finalize(&loopStack); if (isFailed()) { while (0 != insList->count(insList)) { revert(insList->popElementAt(insList, 0)); } fprintf(stderr, "Error in source code.\n"); } else { // 这里写出指令 } }现在要做的是, 首先给每条指令烙上偏移值, 以便于让跳转指令运作 --- 跳转指令都会变为一条整数参数指令.
InstructionCode code; int segOffset = 0; struct Iterator* iterator; struct NoParamInstruction* endProg = (struct NoParamInstruction*) allocate(sizeof(struct NoParamInstruction)); endProg->code = END_PROGRAM; // 加入最后一条结束指令 insList->addTo(insList, endProg, insList->count(insList)); for_each (iterator, insList) { code = ((struct AbstractInstruction*) (iterator->current(iterator)))->code; ((struct AbstractInstruction*) (iterator->current(iterator)))->segOffset = segOffset; segOffset += sizeof(InstructionCode); // 计入参数偏移量 if (isJump(code) || isIntParamIns(code)) { segOffset += INT_SIZE; } else if (isRealParamIns(code)) { segOffset += REAL_SIZE; } }然后写出总的指令大小, 或者可以称之为山寨版的代码段总长度
if (1 != fwrite(&segOffset, sizeof(segOffset), 1, treeout)) { fprintf(stderr, "An error occurs while writing instructions to file.\n"); exit(1); }最后一步, 将每个指令写出
for_each (iterator, insList) { // 实现在下面 writeInstruction(iterator->current(iterator)); } // 回收指令空间 while (0 != insList->count(insList)) { revert(insList->popElementAt(insList, 0)); } /* 逐条指令写出 */ void writeInstruction(void* instruction) { InstructionCode iCode = ((struct AbstractInstruction*)instruction)->code; if (1 != fwrite(&iCode, sizeof(InstructionCode), 1, treeout)) { fprintf(stderr, "Error occur when flush instructions to file.\n"); exit(1); } // 带参数指令写出参数 if (isIntParamIns(iCode)) { if (1 != fwrite(&(((struct IntParamInstruction*)instruction)->param), INT_SIZE, 1, treeout)) { fprintf(stderr, "Error occur when flush instructions to file.\n"); exit(1); } } else if (isRealParamIns(iCode)) { if (1 != fwrite(&(((struct RealParamInstruction*)instruction)->param), REAL_SIZE, 1, treeout)) { fprintf(stderr, "Error occur when flush instructions to file.\n"); exit(1); } } else if (isJump(iCode)) { // 将指针参数转换为段间偏移量, 写出该偏移量整数 int offset = ((struct JumpInstruction*)instruction) ->targetIns->segOffset - ((struct JumpInstruction*)instruction)->segOffset; if (1 != fwrite(&offset, INT_SIZE, 1, treeout)) { fprintf(stderr, "Error occur when flush instructions to file.\n"); exit(1); } } }
请 update Jerry 以获取最新源代码.