专门负责构造表的函数
(lparser.c)
static void constructor (LexState *ls, expdesc *t) {
/* constructor -> '{' [ field { sep field } [sep] ] '}'
sep -> ',' | ';' */
FuncState *fs = ls->fs;
int line = ls->linenumber;
int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); // 生成一条 OP_NEWTABLE 指令
struct ConsControl cc; // 初始化 ConsControl 结构体
cc.na = cc.nh = cc.tostore = 0;
cc.t = t;
init_exp(t, VRELOCABLE, pc);
init_exp(&cc.v, VVOID, 0); /* no value (yet) */ // 将 ConsControl 结构体中的 v 初始化为 VVOID
luaK_exp2nextreg(ls->fs, t); /* fix it at stack top */
checknext(ls, '{');
do {
lua_assert(cc.v.k == VVOID || cc.tostore > 0);
if (ls->t.token == '}') break; // 当解析到 } 时,循环停止
closelistfield(fs, &cc);
field(ls, &cc);
} while (testnext(ls, ',') || testnext(ls, ';'));
check_match(ls, '}', '{', line);
lastlistfield(fs, &cc);
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 */ // 散列表大小
}
// 调用 closelistfield 函数生成上一个表达式的相关指令
static void closelistfield (FuncState *fs, struct ConsControl *cc) {
if (cc->v.k == VVOID) return; /* there is no list item */
luaK_exp2nextreg(fs, &cc->v);
cc->v.k = VVOID;
if (cc->tostore == LFIELDS_PER_FLUSH) {
luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */
cc->tostore = 0; /* no more items pending */
}
}
static void field (LexState *ls, struct ConsControl *cc) {
/* field -> listfield | recfield */
switch(ls->t.token) {
case TK_NAME: { /* may be 'listfield' or 'recfield' */
if (luaX_lookahead(ls) != '=') /* expression? */
listfield(ls, cc);
else
recfield(ls, cc);
break;
}
case '[': {
recfield(ls, cc);
break;
}
default: {
listfield(ls, cc);
break;
}
}
}
field
函数针对具体的类型来做解析:
(1)如果解析到一个变量,那么看紧跟着这个符号的是不是 =
,如果不是,就是一个数组方式的赋值,否则就是散列方式的赋值;
(2)如果看到的是 [
符号,就认为是散列部分的构造;
(3)否则就是数组部分的构造了。数组部分的构造进入 listfield
函数,散列部分进入 recfield
函数;
创建空表
local p = {}
使用 ChunkSpy 反编译出来的指令是:
; function [0] definition (level 1)
; 0 upvalues, 0 params, 2 stacks
.function 0 0 2 2
.local "p" ; 0
; (1) local p = {}
[1] newtable 0 0 0 ; array=0, hash=0
[2] return 0 1
; end of function
对应的是 OPCODE(lopcodes.h)
是 OP_NEWTABLE
,用于创建一个表,将结果存入寄存器:
OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */
参数说明:
参数 A
,创建好的表存入寄存器的索引
参数 B
,表的数组部分大小
参数 C
,表的散列部分大小
创建一个表,添加数组部分
local p = {1, 2}
使用 ChunkSpy 反编译出来的指令是:
; 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) local p = {1, 2}
[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
在 newtable
指令之后,跟着两条 loadk
指令和一条 setlist
指令,loadk
指令用于把表构造表达式中的常量1
和2
加载到函数栈中,而 setlist
指令则使用这两个常量初始化表的数组部分。
setlist
指令的格式:
OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */
参数说明:
参数 A
,OP_NEWTABLE
指令中创建好的表所在的寄存器,它后面紧跟着代写入的数据
参数 B
,待写入数据的数量
参数 C
,FPF索引(即 LFIELDS_PER_FLUSH
常量),即每次写入最多的是 LFIELDS_PER_FLUSH
/* number of list items to accumulate before a SETLIST instruction */
#define LFIELDS_PER_FLUSH 50
用途:当前构造表时内部的数组部分的数据如果超过这个值,就首先调用一次 OP_SETLIST
函数写入寄存器中。
创建一个表,添加散列部分
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) local p = {["a"]=1}
[1] newtable 0 0 1 ; array=0, hash=1
[2] settable 0 256 257 ; "a" 1
[3] return 0 1
; end of function
在 newtable
指令之后,跟着 settable
指令,这个指令用来完成散列部分的初始化,其格式:
OP_SETTABLE,/* A B C R(A)[RK(B)] := RK(C) */
参数说明:
参数 A
,表所在的寄存器
参数 B
,key 存放的位置,注意其格式是 RK,也就说这个值可能来自寄存器,也可能来自常量数组
参数 C
,value 存放的位置,注意其格式是 RK,也就说这个值可能来自寄存器,也可能来自常量数组
在前面的分析中,初始化散列部分的代码会走入 recfield
函数中。散列部分的初始化分为两部分:
(1)key 是一个常量;
(2)key 是一个变量,需要首先去获取这个变量的值;
第一种情况比较简单,分为几个步骤:
(1)得到 key 常量在常量数组中的索引,根据这个值调用 luaK_exp2RK
函数生成 RK 值;
(2)得到 value 表达式的索引,根据这个值调用 luaK_exp2RK
函数生成 RK 值;
(3)将前两步的值以及表在寄存器中的索引,写入 OP_SETTABLE
的参数中;
创建一个表,键为变量
local a = "a"
local p = {[a]=1}
使用 ChunkSpy 反编译出来的指令是:
; 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) local a = "a"
[1] loadk 0 0 ; "a"
; (2) local p = {[a]=1}
[2] newtable 1 0 1 ; array=0, hash=1
[3] settable 1 0 257 ; 1
[4] return 0 1
; end of function
最开始多了loadk
指令,将常量 "a"
加载到寄存器 0 中。然后 settable
指令中的key值小于255,也就是这个值来自于寄存器0。
创建一个表,既添加数组部分,也添加散列部分,散列部分的 key 是整数
local p = {1, [2]=2, 3, [4]=4, 5}
使用 ChunkSpy 反编译出来的指令是:
; function [0] definition (level 1)
; 0 upvalues, 0 params, 4 stacks
.function 0 0 2 4
.local "p" ; 0
.const 1 ; 0
.const 2 ; 1
.const 3 ; 2
.const 4 ; 3
.const 5 ; 4
; (1) local p = {1, [2]=2, 3, [4]=4, 5}
[1] newtable 0 3 2 ; array=3, hash=2
[2] loadk 1 0 ; 1
[3] settable 0 257 257 ; 2 2
[4] loadk 2 2 ; 3
[5] settable 0 259 259 ; 4 4
[6] loadk 3 4 ; 5
[7] setlist 0 3 1 ; index 1 to 3
[8] return 0 1
; end of function
创建一个表,既添加数组部分,也添加散列部分,散列部分的 key 是字符串
local p = {1, ["a"]=2, 3, ["b"]=4, 5, ["c"]=6}
使用 ChunkSpy 反编译出来的指令是:
.function 0 0 2 4
.local "p" ; 0
.const 1 ; 0
.const "a" ; 1
.const 2 ; 2
.const 3 ; 3
.const "b" ; 4
.const 4 ; 5
.const 5 ; 6
.const "c" ; 7
.const 6 ; 8
; (1) local p = {1, ["a"]=2, 3, ["b"]=4, 5, ["c"]=6}
[1] newtable 0 3 3 ; array=3, hash=3
[2] loadk 1 0 ; 1
[3] settable 0 257 258 ; "a" 2
[4] loadk 2 3 ; 3
[5] settable 0 260 261 ; "b" 4
[6] loadk 3 6 ; 5
[7] settable 0 263 264 ; "c" 6
[8] setlist 0 3 1 ; index 1 to 3
[9] return 0 1
; end of function