上 构建Lua解释器Part7:构建完整的语法分析器( 六 )


编译流程
现在 , 我们进入到编译流程的探讨之中了 , 实际上 , 这部分并不好写 , 我写了几版 , 都觉得不够清晰或者细节太多而删除重写 , 经过反复思索 , 确定了通过EBNF范式描述语法 , 然后代码只展示结构和主要流程 , 一些操作细节通过图的方式展现 , 这样有助于我们快速理解知识点和需要注意的事项 , 最后还是强烈建议读者结合代码一起阅读 , 这样才能真正做到理解 。
的构造
我们前面也介绍过 , 是构成代码逻辑 , 最基础的单独单元 , lua中同样有多种 , 比如、、等等 , 每个执行不同的逻辑 , 并且在编译器中有独立的处理逻辑分支 , 而每个中 , 头一个token决定了我们应该进入哪个处理分支之中 。我们现在以官方lua源码的逻辑来举例:
// lua-5.3.5 lparser.cstatic void statement (LexState *ls) {int line = ls->linenumber;/* may be needed for error messages */enterlevel(ls);switch (ls->t.token) {case ';': {/* stat -> ';' (empty statement) */luaX_next(ls);/* skip ';' */break;}case TK_IF: {/* stat -> ifstat */ifstat(ls, line);break;}case TK_WHILE: {/* stat -> whilestat */whilestat(ls, line);break;}case TK_DO: {/* stat -> DO block END */luaX_next(ls);/* skip DO */block(ls);check_match(ls, TK_END, TK_DO, line);break;}case TK_FOR: {/* stat -> forstat */forstat(ls, line);break;}case TK_REPEAT: {/* stat -> repeatstat */repeatstat(ls, line);break;}case TK_FUNCTION: {/* stat -> funcstat */funcstat(ls, line);break;}case TK_LOCAL: {/* stat -> localstat */luaX_next(ls);/* skip LOCAL */if (testnext(ls, TK_FUNCTION))/* local function? */localfunc(ls);elselocalstat(ls);break;}case TK_DBCOLON: {/* stat -> label */luaX_next(ls);/* skip double colon */labelstat(ls, str_checkname(ls), line);break;}case TK_RETURN: {/* stat -> retstat */luaX_next(ls);/* skip RETURN */retstat(ls);break;}case TK_BREAK:/* stat -> breakstat */case TK_GOTO: {/* stat -> 'goto' NAME */gotostat(ls, luaK_jump(ls->fs));break;}default: {/* stat -> func | assignment */exprstat(ls);break;}}lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&ls->fs->freereg >= ls->fs->nactvar);ls->fs->freereg = ls->fs->nactvar;/* free registers */leavelevel(ls);}
代码片段1
在代码片段1中 , ls->t.token就是我们获得的 , 在一个语句中的首个token , 我们可以很容易地看到 , 首个token决定了我们要进入哪个case中 , 执行哪个编译流程 。在就目前的完成情况来说 , 当前只实现了部分的逻辑 , 下面节取自的代码:
// luaparser.cstatic void statement(struct lua_State* L, LexState* ls, FuncState* fs) {switch (ls->t.token) {case TK_NAME: {exprstat(L, ls, fs);} break;default:luaX_syntaxerror(L, ls, "unsupport syntax");break;}}
代码片段2
可以看到 , 如果输入的chunk(一个lua文件里的所有代码 , 或者在交互模式下的一行代码称之为chunk[7]) , 不符合的语法结构 , 那么将抛出语法错误 。我们现在通过一张图来看一下什么样的chunk , 可以进入到之中:
图10
可以看到 , 上面两个chunk对应了两个(我们前面提到过由于在内定义 , 因此是个exp , 而在有些说法里 , expr也是 , 在这里的情况 , 本质就是一个[8]) , 而这些中 , 的首个token是类型的 , 因此直接进入到的编译流程之中 , 图10也展示了的两种形态 , 一种是 , 还有一种则是赋值 。
说到这里 , 就要引入的EBNF描述了 , 它描述了的语法结构 , 并且EBNF范式与递归下降的逻辑实现 , 能够很好地对应起来 。