6.3.1 创建表
创建一个空表,测试代码为:
local p = {} --filename
使用ChunkSpy反编译出来的结果:
; function [0] definition (level 1) ; 0 upvalues, 0 params, 2 stacks .function 0 0 2 2 .local "p" ; 0 [1] newtable 0 0 0 ; array=0, hash=0 [2] return 0 1 ; end of function
解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示。与上一节的区别在于最后的 simpleexp最终调用的是 constructor 函数,这个函数就是专门负责构造表的 。
chunk -> { stat [';'] } statement -> localstat localstat -> LOCAL NAME {',' NAME} [ '=' explist1] explist1 -> expr {',' expr} expr -> subexpr subexpr -> simpleexp simpleexp -> constructor constructor -> '{' [fieldlist] '}'
EBNF 对应函数
static void chunk (LexState *ls); static int statement (LexState *ls); static void localstat (LexState *ls); static int explist1 (LexState *ls, expdesc *v); static void expr (LexState *ls, expdesc *v); static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit); static void simpleexp (LexState *ls, expdesc *v); static void constructor (LexState *ls, expdesc *t);
对应的OPCODE
typedef enum { /*name args description */ // ...... // 创建一个表,将结果存入寄存器: // A:创建好的表存入寄存器的索引;B:数组部分大小;C:散列部分大小 OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */ // ...... } OpCode;
LFIELDS_PER_FLUSH
//opcodes.h /* number of list items to accumulate before a OP_SETLIST instruction */ #define LFIELDS_PER_FLUSH 50
解析表的expdesc,会存放在 ConsControl 中
//lparser.c struct ConsControl { expdesc v; /* 表构造过程中最后一个表达式的信息 */ expdesc *t; /* 构造表相关的表达式信息,是由外部传入*/ int nh; /* 散列部分数据数量 */ int na; /* 数组部分数据数量 */ int tostore; /* 待存储的数组元素个数,当 > LFIELDS_PER_FLUSH时,调用OP_SETLIST */
};
核心函数constructor
static void constructor (LexState *ls, expdesc *t) { /* constructor -> ?? */ FuncState *fs = ls->fs; int line = ls->linenumber; // 生成一条 OP_NEWTABLE 指令,创建的表最终会根据指令中的参数A存储的寄存器地址, // 赋值给本函数楼内的寄存器,所以这条指令是需要重定向的,即VRELOCABLE int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); // 初始化 cc struct ConsControl cc; cc.na = cc.nh = cc.tostore = 0; cc.t = t; init_exp(t, VRELOCABLE, pc); // cc.v 存储的是表构造过程中最后一个表达式的信息,初始化为VVOID init_exp(&cc.v, VVOID, 0); /* no value (yet) */ // 将寄存器地址修正为前面创建的 OP_NEWTABLE 指令的 参数A luaK_exp2nextreg(ls->fs, t); /* fix it at stack top (for gc) */ // ...... SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */ SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ }
测试代码修改为:
local p = {1, 2} --filename
ChunkSpy反编译出来的结果,与前面相比,在 newtable指令之后,还跟着两条 loadk 指令和一条 setlist 指令
; function [0] definition (level 1) ; 0 upvalues, 0 params, 3 stacks .function 0 0 2 3 .local "p" ; 0 .const 1 ; 0 .const 2 ; 1 [1] newtable 0 2 0 ; array=2, hash=0 [2] loadk 1 0 ; 1 [3] loadk 2 1 ; 2 [4] setlist 0 2 1 ; index 1 to 2 [5] return 0 1 ; end of function
OP_SETLIST
//opcodes.h typedef enum { // ...... // A:OP_NEWTABLE指令中创建好的表所在的寄存器;B:数据;C:FPF(LFIELDS_PER_FLUSH) OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */ // ...... } OpCode;
前面 constructor 函数省略的涉及散列、数组部分解析构造的内容如下:
static void constructor (LexState *ls, expdesc *t) { // ...... do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); if (ls->t.token == '}') break; // 遇到},结束循环 //生成上一个表达式的相关指令,它会调用 luaK_exp2nextreg 函数 closelistfield(fs, &cc); // switch(ls->t.token) { case TK_NAME: { /* may be listfields or recfields */ luaX_lookahead(ls); if (ls->lookahead.token != '=') /* expression? */ listfield(ls, &cc); //数组方式赋值 else recfield(ls, &cc); // 散列方式赋值 break; } case '[': { /* constructor_item -> recfield */ recfield(ls, &cc); //散列方式赋值 break; } default: { /* constructor_part -> listfield */ listfield(ls, &cc); break; } } // ...... }
本例为数组部分构造,会进入listfield(lparser.c)
static void listfield (LexState *ls, struct ConsControl *cc) { expr(ls, &cc->v); // 解析表达式,得到cc->v // 检查当前表中数组部分的数据梳理是否超过限制 luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); cc->na++; cc->tostore++; }
closelistfield(lparser.c)从这个函数的命名可以看出,它做的工作是针对数组部分的
static void closelistfield (FuncState *fs, struct ConsControl *cc) { if (cc->v.k == VVOID) return; /* there is no list item */ // 将 cc->v 的信息存入寄存器中 luaK_exp2nextreg(fs, &cc->v); cc->v.k = VVOID; if (cc->tostore == LFIELDS_PER_FLUSH) { // 生成一个OP_SETLIST指令,用于将当前寄存器上的数据写入表的数组部分 // 其位置是紧跟着 OP_NEWTABLE 指令中的参数A在栈上的位置, // A存放的是新创建的表在栈上的位置 // OP SE T LIST指令中的数据量限制为 LFIELDS_PER_FLUSH,是为了避免占用过多的寄存器 luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); /* flush */ cc->tostore = 0; /* no more items pending */ } }
再次修改测试代码为:
local p = {["a"] = 1}
ChunkSpy看到的指令:
; function [0] definition (level 1) ; 0 upvalues, 0 params, 2 stacks .function 0 0 2 2 .local "p" ; 0 .const "a" ; 0 .const 1 ; 1 [1] newtable 0 0 1 ; array=0, hash=1 [2] settable 0 256 257 ; "a" 1 [3] return 0 1 ; end of function
settable指令用来完成散列部分初始化,格式为:
typedef enum { // ...... //A:表所在寄存器;B:key存放的位置;C:value存放的位置 OP_SETTABLE,/* A B C R(A)[RK(B)] := RK(C) */ // ...... } OpCode;
settable会调用recfield
static void recfield (LexState *ls, struct ConsControl *cc) { /* recfield -> (NAME | `['exp1`]') = exp1 */ FuncState *fs = ls->fs; int reg = ls->fs->freereg; expdesc key, val; int rkkey; if (ls->t.token == TK_NAME) { luaY_checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); checkname(ls, &key); } else /* ls->t.token == '[' */ yindex(ls, &key); cc->nh++; checknext(ls, '='); rkkey = luaK_exp2RK(fs, &key); // 根据key常量的索引生成RK值 expr(ls, &val); // 根据val常量的索引生成RK值,写入OP_SETTABLE 的参数 luaK_codeABC(fs, OP_SETTABLE, cc->t->u.s.info, rkkey, luaK_exp2RK(fs, &val)); fs->freereg = reg; /* free registers */ }
再次修改测试代码为:
local a = "a" local p = {[a] = 1}
ChunkSpy看到的指令与上一个例子的区别于,多了一条 loadk 指令将常量"a"加载到局部变量 a 中,因为是从寄存器中而不是从常量数组中获取 key 的数据 。
; function [0] definition (level 1) ; 0 upvalues, 0 params, 2 stacks .function 0 0 2 2 .local "a" ; 0 .local "p" ; 1 .const "a" ; 0 .const 1 ; 1 [1] loadk 0 0 ; "a" [2] newtable 1 0 1 ; array=0, hash=1 [3] settable 1 0 257 ; 1 [4] return 0 1 ; end of function
6.3.2 查询表
typedef enum { // ...... // A:存放结果的寄存器;B:表所在的寄存器;C:key存放的位置 OP_GETTABLE,/* A B C R(A) := R(B)[RK(C)] */ // ...... } OpCode;
修改前面的 lua 代码
local p = {["a"] = 1} local b = p["a"]
ChunkSpy 看到的指令
; function [0] definition (level 1) ; 0 upvalues, 0 params, 2 stacks .function 0 0 2 2 .local "p" ; 0 .local "b" ; 1 .const "a" ; 0 .const 1 ; 1 [1] newtable 0 0 1 ; array=0, hash=1 [2] settable 0 256 257 ; "a" 1 [3] gettable 1 0 256 ; "a" [4] return 0 1 ; end of function
6.3.3 元表的实现原理
初始化元方法对应的只读字符串
void luaT_init (lua_State *L) { static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__gc", "__mode", "__eq", "__add", "__sub", "__mul", "__div", "__mod", "__pow", "__unm", "__len", "__lt", "__le", "__concat", "__call" }; int i; for (i=0; i<TM_N; i++) { G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]); luaS_fix(G(L)->tmname[i]); /* never collect these names */ } }
函数调用堆栈
void luaT_init (lua_State *L); static void f_luaopen (lua_State *L, void *ud); int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud); LUALIB_API lua_State *luaL_newstate (void); int myloadfile(const char *filename); int main(void);
Lua虚拟机从一个表中查询数据的过程
void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { int loop; for (loop = 0; loop < MAXTAGLOOP; loop++) { const TValue *tm; if (ttistable(t)) { /* `t' is a table? */ Table *h = hvalue(t); const TValue *res = luaH_get(h, key); /* do a primitive get */ if (!ttisnil(res) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ setobj2s(L, val, res); return; } /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) luaG_typeerror(L, t, "index"); if (ttisfunction(tm)) { callTMres(L, val, tm, t, key); return; } t = tm; /* else repeat with `tm' */ } luaG_runerror(L, "loop in gettable"); }