專門負責構(gòu)造表的函數(shù)
(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 結(jié)構(gòu)體
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 結(jié)構(gòu)體中的 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; // 當解析到 } 時探越,循環(huán)停止
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 */ // 數(shù)組大小
SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ // 散列表大小
}
// 調(diào)用 closelistfield 函數(shù)生成上一個表達式的相關(guān)指令
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
函數(shù)針對具體的類型來做解析:
(1)如果解析到一個變量狡赐,那么看緊跟著這個符號的是不是 =
,如果不是钦幔,就是一個數(shù)組方式的賦值枕屉,否則就是散列方式的賦值;
(2)如果看到的是 [
符號鲤氢,就認為是散列部分的構(gòu)造搀庶;
(3)否則就是數(shù)組部分的構(gòu)造了。數(shù)組部分的構(gòu)造進入 listfield
函數(shù)铜异,散列部分進入 recfield
函數(shù)哥倔;
創(chuàng)建空表
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
對應(yīng)的是 OPCODE(lopcodes.h)
是 OP_NEWTABLE
,用于創(chuàng)建一個表揍庄,將結(jié)果存入寄存器:
OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */
參數(shù)說明:
參數(shù) A
咆蒿,創(chuàng)建好的表存入寄存器的索引
參數(shù) B
,表的數(shù)組部分大小
參數(shù) C
蚂子,表的散列部分大小
創(chuàng)建一個表沃测,添加數(shù)組部分
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
指令用于把表構(gòu)造表達式中的常量1
和2
加載到函數(shù)棧中蒂破,而 setlist
指令則使用這兩個常量初始化表的數(shù)組部分。
setlist
指令的格式:
OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */
參數(shù)說明:
參數(shù) A
别渔,OP_NEWTABLE
指令中創(chuàng)建好的表所在的寄存器附迷,它后面緊跟著代寫入的數(shù)據(jù)
參數(shù) B
,待寫入數(shù)據(jù)的數(shù)量
參數(shù) C
哎媚,F(xiàn)PF索引(即 LFIELDS_PER_FLUSH
常量)喇伯,即每次寫入最多的是 LFIELDS_PER_FLUSH
/* number of list items to accumulate before a SETLIST instruction */
#define LFIELDS_PER_FLUSH 50
用途:當前構(gòu)造表時內(nèi)部的數(shù)組部分的數(shù)據(jù)如果超過這個值,就首先調(diào)用一次 OP_SETLIST
函數(shù)寫入寄存器中拨与。
創(chuàng)建一個表稻据,添加散列部分
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) */
參數(shù)說明:
參數(shù) A
,表所在的寄存器
參數(shù) B
淤毛,key 存放的位置今缚,注意其格式是 RK,也就說這個值可能來自寄存器钱床,也可能來自常量數(shù)組
參數(shù) C
荚斯,value 存放的位置埠居,注意其格式是 RK查牌,也就說這個值可能來自寄存器事期,也可能來自常量數(shù)組
在前面的分析中,初始化散列部分的代碼會走入 recfield
函數(shù)中纸颜。散列部分的初始化分為兩部分:
(1)key 是一個常量兽泣;
(2)key 是一個變量,需要首先去獲取這個變量的值胁孙;
第一種情況比較簡單唠倦,分為幾個步驟:
(1)得到 key 常量在常量數(shù)組中的索引,根據(jù)這個值調(diào)用 luaK_exp2RK
函數(shù)生成 RK 值涮较;
(2)得到 value 表達式的索引稠鼻,根據(jù)這個值調(diào)用 luaK_exp2RK
函數(shù)生成 RK 值;
(3)將前兩步的值以及表在寄存器中的索引狂票,寫入 OP_SETTABLE
的參數(shù)中候齿;
創(chuàng)建一個表,鍵為變量
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掂器。
創(chuàng)建一個表亚皂,既添加數(shù)組部分,也添加散列部分国瓮,散列部分的 key 是整數(shù)
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
創(chuàng)建一個表灭必,既添加數(shù)組部分,也添加散列部分乃摹,散列部分的 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