Lua 最初使用的是 Yacc 生成的语法分析器,后来改为手写的递归下降语法分析器(Recursive descent parser)。因为一般在语言发展的早期,语言的语法还没有稳定下来,变动可能会比较多,用工具可以快速的验证自己的想法。当语法稳定下来之后,一般都会采用手写的语法分析器。因为这样程序员是调试可控的,并且一般也能有更好的性能表现。递归下降的语法分析对编程语言的语法有要求。因 Lua 的语法比较简单,是 LL(1) 文法。所以,手工编写语法分析也就是情理之中的事了。
关于递归下降的语法分析,网上资料不少,比如维基百科:
https://en.wikipedia.org/wiki/Recursive_descent_parser
比如这篇:
http://www.cnblogs.com/Ninputer/archive/2011/06/21/2085527.html
里面都有很不错的示例。
具体内容就不贴出来了,感兴趣的同学可以自行阅读。
说到这儿,再往下说,就是具体的代码了。
对照 Lua 的 EBNF 语法(在 Lua 手册的最后面,有完整的语法表),一点点的套代码吧。
看一个经典的“hello world”程序吧。
程序的内容如下所示:
print("hello world! (using print)\n")
假设把它保存为 hello.lua 文件。
通过执行下面的命令,打印出它的字节码。
luac.exe -l hello.lua
下面是程序的输出:
main <0:@hello.lua> (4 instructions/16 bytes at 0087A0E8)
0 params, 2 stacks, 0 locals, 2 strings, 0 numbers, 0 functions, 2 lines
1 [1] GETGLOBAL 0 ; print
2 [1] PUSHSTRING 1 ; "hello world! (using print)\n"
3 [1] CALL 0 0
4 [1] END
程序输出的意思:
main 表示是顶层主程序,如果是函数定义的话,这里显示为 function
0:@hello.lua 程序从第 0 行开始,文件名为 hello.lua
后面的表示 4 条指令,共 16 个字节,位于内存 0087A0E8 处。
再下一行表示,有多少个参数,占用了多少个栈空间,局部变量个数,多少个字符串,数字,函数,及行数。
再下面就是字节码指令的具体内容(以方便阅读的形式表示出来的)。
每行的内容分别为 指令号,对应源代码的行号,指令,分号后面的为注释。
再来看看指令的具体内容:
GETGLOBAL 取得一个全局变量,由注释可以看到,取得的全局变量为 print
PUSHSTRING 字符串压栈
CALL 函数调用
END 结束
取得全局变量的时候,把取出来的全局变量放到什么地方?放到了数据栈上,就是下面字符串压栈的那个同样的栈。
在字节码运行的时候,Lua 虚拟机根据字节码指令指示,把相关的数据放到数据栈上,而后进行相应的操作。
因为虚拟机对数据栈的操作只会在栈顶进行,所以,我们说 Lua4.0 的虚拟机是栈式虚拟机。
看一看这个语法分析的函数调用流程:
从 luaY_parser 开始看。
Proto *luaY_parser (lua_State *L, ZIO *z) { struct LexState lexstate; struct FuncState funcstate; luaX_setinput(L, &lexstate, z, luaS_new(L, zname(z))); open_func(&lexstate, &funcstate); next(&lexstate); /* read first token */ chunk(&lexstate); //... return funcstate.f; }
程序上来先设置好 LexState, FuncState
调用 next 取得第一个 token,就是 print 。
进入 chunk
static void chunk (LexState *ls) { /* chunk -> { stat [‘;‘] } */ int islast = 0; while (!islast && !block_follow(ls->t.token)) { islast = stat(ls); optional(ls, ‘;‘); //... } }
进入语句 stat
static int stat (LexState *ls) { int line = ls->linenumber; /* may be needed for error messages */ switch (ls->t.token) { // other cases case TK_NAME: case ‘%‘: { /* stat -> namestat */ namestat(ls); return 0; } // other cases } }
里面是个大的 switch case 。
这里触发的是 TK_NAME:
进入 namestat
static void namestat (LexState *ls) { /* stat -> func | [‘%‘] NAME assignment */ FuncState *fs = ls->fs; expdesc v; var_or_func(ls, &v); if (v.k == VEXP) { /* stat -> func */ check_condition(ls, luaK_lastisopen(fs), "syntax error"); /* an upvalue? */ luaK_setcallreturns(fs, 0); /* call statement uses no results */ } else { /* stat -> [‘%‘] NAME assignment */ int left = assignment(ls, &v, 1); luaK_adjuststack(fs, left); /* remove eventual garbage left on stack */ } }
进入 var_or_func,设置表达式描述符 expdesc
static void var_or_func (LexState *ls, expdesc *v) { /* var_or_func -> [‘%‘] NAME var_or_func_tail */ if (optional(ls, ‘%‘)) { /* upvalue? */ pushupvalue(ls, str_checkname(ls)); v->k = VEXP; v->u.l.t = v->u.l.f = NO_JUMP; } else /* variable name */ singlevar(ls, str_checkname(ls), v); var_or_func_tail(ls, v); }
显然,这里不是 upvalue,只是一个简单的变量名。
进入 singlevar, 先取得一个 TString 的值:
static TString *str_checkname (LexState *ls) { TString *ts; check_condition(ls, (ls->t.token == TK_NAME), "<name> expected"); ts = ls->t.seminfo.ts; next(ls); return ts; }
取得一个 TString
static void singlevar (LexState *ls, TString *n, expdesc *var) { int level = search_local(ls, n, var); if (level >= 1) /* neither local (0) nor global (-1)? */ luaX_syntaxerror(ls, "cannot access a variable in outer scope", n->str); else if (level == -1) /* global? */ var->u.index = string_constant(ls->fs, n); }
查看是否是局部变量,显然不是。这里 level 值为 -1。
取得 string 在全局字符串表中的下标:
static int string_constant (FuncState *fs, TString *s) { Proto *f = fs->f; int c = s->u.s.constindex; if (c >= f->nkstr || f->kstr[c] != s) { luaM_growvector(fs->L, f->kstr, f->nkstr, 1, TString *, "constant table overflow", MAXARG_U); c = f->nkstr++; f->kstr[c] = s; s->u.s.constindex = c; /* hint for next time */ } return c; }
回到 var_or_func, 进入 var_or_func_tail
static void var_or_func_tail (LexState *ls, expdesc *v) { for (;;) { switch (ls->t.token) { // other cases: case ‘(‘: case TK_STRING: case ‘{‘: { /* var_or_func_tail -> funcargs */ luaK_tostack(ls, v, 1); /* `v‘ must be on stack */ funcargs(ls, 0); v->k = VEXP; v->u.l.t = v->u.l.f = NO_JUMP; break; } default: return; /* should be follow... */ } } }
因为当前的 token 为 ‘(‘,所以进入上面的 case。
进入 luaK_tostack(位于 lcode.c 文件中),生成字节码 GETGLOBAL 0
进入 funcargs,设置函数 print 的参数。
static void funcargs (LexState *ls, int slf) { FuncState *fs = ls->fs; int slevel = fs->stacklevel - slf - 1; /* where is func in the stack */ switch (ls->t.token) { case ‘(‘: { /* funcargs -> ‘(‘ [ explist1 ] ‘)‘ */ int line = ls->linenumber; int nargs = 0; next(ls); if (ls->t.token != ‘)‘) /* arg list not empty? */ nargs = explist1(ls); check_match(ls, ‘)‘, ‘(‘, line); #ifdef LUA_COMPAT_ARGRET if (nargs > 0) /* arg list is not empty? */ luaK_setcallreturns(fs, 1); /* last call returns only 1 value */ #else UNUSED(nargs); /* to avoid warnings */ #endif break; } // other cases } fs->stacklevel = slevel; /* call will remove function and arguments */ luaK_code2(fs, OP_CALL, slevel, MULT_RET); }
因为有参数,所以进入 explist1
static int explist1 (LexState *ls) { /* explist1 -> expr { ‘,‘ expr } */ int n = 1; /* at least one expression */ expdesc v; expr(ls, &v); while (ls->t.token == ‘,‘) { luaK_tostack(ls, &v, 1); /* gets only 1 value from previous expression */ next(ls); /* skip comma */ expr(ls, &v); n++; } luaK_tostack(ls, &v, 0); /* keep open number of values of last expression */ return n; }
先通过 expr 取得表达示描述信息。
进入 expr, subexpr, simpleexp
因为是一个简单的表达式,只有一个字符串 "hello world! (using print)\n",
static void simpleexp (LexState *ls, expdesc *v) { FuncState *fs = ls->fs; switch (ls->t.token) { // other cases case TK_STRING: { /* simpleexp -> STRING */ code_string(ls, ls->t.seminfo.ts); /* must use `seminfo‘ before `next‘ */ next(ls); break; } // other codes
code_string 设置字节码,PUSHSTRING 1
回到 funcargs。设置函数调用字节码:
luaK_code2(fs, OP_CALL, slevel, MULT_RET);
回到 namestat,设置返回字节码:END
回到 chunk,luaY_parser 语法分析结束。
语法分析过程再跟踪下去基本就是个体力活了,不再举例子了。
对照着 EBNF 及生成的字节码,一步步的跟踪就是了。
其它的语法分析的理论,参见编译原理。
或者是看开头部分的那些简要的描述,也可以了解了大概。
----------------------------------------
到目前为止的问题:
> TString 数据结构和 luaS_new
> 函数原型优化 luaU_optchunk
> 打印函数原型 luaU_printchunk
> dump 函数原型 luaU_dumpchunk
----------------------------------------