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


构造与编译
接下来 , 我们来看一下的构造 , 我们前面详细论述了expr的构造 , 之所以要先论述它 , 是因为它是一切的基础 。我们首先来回顾一下的EBNF范式:
suffixedexp ::= primaryexp {'.' NAME | '[' expr ']' | ':' NAME funcargs | funcargs}primaryexp ::= NAME | '(' expr ')'funcargs ::= '('[explist]')' | constructor | STRING
我们可以简单地归纳 , 的主要构成部分有三块 , 分别是、table访问和函数调用三块 。前两者可以构成赋值语句中 , =左边的 , 函数调用可以作为独立的exp存在 , 回顾一下代码片段3 , 这也是函数 , 首先要调用函数的原因 , 因为我们前面也说过 , 中 , 不论是赋值语句还是函数调用 , 他们首先都要识别出第一个exp , 然后根据后续的token , 判定输入的是个赋值语句(遇到’=‘的时候) , 或是函数调用(遇到’(‘的时候) , 还是不合法 。这也是函数这么去设计的原因 。在分别开始讨论三个构造之前 , 我们先来看一下函数的代码:
// luaparser.c493 static void suffixedexp(struct lua_State* L, LexState* ls, FuncState* fs, expdesc* e) {494primaryexp(L, ls, fs, e);495luaX_next(L, ls);496 497for (;;) {498switch (ls->t.token) {499case '.': {500fieldsel(L, ls, fs, e);501} break;502case ':': {503expdesc key;504luaX_next(L, ls);505checkname(L, ls, &key);506luaK_self(fs, e, &key);507funcargs(fs, e);508} break;509case '[': {510luaK_exp2anyregup(fs, e);511expdesc key;512yindex(L, fs, &key);513luaK_indexed(fs, e, &key);514} break;515case '(': {516luaK_exp2nextreg(fs, e);517funcargs(fs, e);518} break;519default: return;520}521}522 }
代码片段7
1、构造
代码片段7 , 很符合我们的EBNF范式 , 同时我们也可以看到 , 函数 , 首先要识别的就是 , 它的EBNF范式在前面也已经展示过 , 这里展示一下它的实现逻辑:
// luaparser.c147 static void primaryexp(struct lua_State* L, LexState* ls, FuncState* fs, expdesc* e) {148switch (ls->t.token) {149case TK_NAME: {150singlevar(fs, e, ls->t.seminfo.s);151} break;152case '(': {153luaX_next(L, ls);154expr(fs, e);155luaK_exp2nextreg(fs, e);156check(L, ls, ')');157} break;158default: {159luaX_syntaxerror(L, ls, "unsupport syntax");160} break;161}162 }
代码片段8
的两个主要分支 , 一个是变量识别 , 还有一个是括号内的expr处理 , 这两个内容 , 我们在expr构造一节中 , 已经花了大量的篇幅去介绍 , 这里就不再赘述 , 其本质也和expr函数一样 。我们可以通过图6去感知一下 , expr和以及的关系 , 他们其实是通过递归来实现嵌套逻辑的 。我们的 , 依然是输入一些token , 输出一个结构:
a => primaryexp => expdesc(a + b * c) => primaryexp => expdesc(((a))) => primaryexp => expdesc...
虽然EBNF范式 , 已经展示了和的关系 , 但是我们也可以通过一张图来展现:
图78
2、table访问
我们要讨论的第二部分内容 , 就是table的访问 。不论如何 , 首先我们都要先处理 , 这是table的本体(即table自身) , 而访问处理也是针对它来进行的 。从上面的EBNF范式来看 , table的组织 , 可以抽象为以下的形式:
primaryexp { .NAME | '[' expr ']' }
直接通过’.‘来访问 , 和通过方括号来访问 , 现在我们来通过一个例子 , 来梳理一下table访问的流程 , 同时我也希望读者通过这个例子 , 自己去推导其他复杂的情况: