本文主要給大家介紹了lua中解析復制類型代碼的過程,非常的細致全面,有需要的小伙伴可以參考下
我們來看看lua vm在解析下面源碼並生成bytecode時的整個過程:
?
1 2 3 foo = "bar" local a, b = "a", "b" foo = a首先我們先使用ChunkySpy這個工具來看看vm最終會具體生成什麼樣的vm instructions
在這裡,開頭為[數字]的行是vm真正生成的字節碼,我們看到一共生成了六行字節碼。首先loadk將常量表中下標為1的常量即"bar"賦給寄存器0;然後setglobal將寄存器0的內容賦給全局變量表中下標為0的全局變量即foo;loadk再將"a"和"b"分別賦值給了寄存器0、1,在這裡寄存器0和1分別表示當前函數的local變量即變量a和b;最後setglobal將變量a的值賦給了全局變量foo;最後一個return01是vm在每一個chunk最後都會生成了,並沒有什麼用。現在應該比較清除的了解了lua vm生成的字節碼的含義了,接下來我們看看vm是怎樣且為什麼生成這些個字節碼的。
當我們用luaL_dofile函數執行這個lua腳本源碼時會有兩個階段,第一個是將腳本加載進內存,分詞解析並生成字節碼並將其整個包裹為main chunk放於lua stack棧頂,第二是調用lua_pcall執行這個chunk,這裡我們只會分析第一個過程。
前面幾篇文章說了,當dofile時會跑到一個叫做luaY_parser的函數中,
?
1 2 3 4 5 6 7 8 9 10 Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) { struct LexState lexstate; struct FuncState funcstate; -- ... ... funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */ luaX_next(&lexstate); /* read first token */ chunk(&lexstate); -- ... ... return funcstate.f; }函數luaY_parser前面兩行定義了LexState和FuncState結構體變量,其中LexState不僅用於保存當前的詞法分析狀態信息,而且也保存了整個編譯系統的全局狀態,FuncState結構體來保存當前函數編譯的狀態數據。在lua源碼中都會有一個全局的函數執行體,即為main func,在開始解析的時候當前的函數必然是main func函數,此時第三行的funcstate表示了這個函數的狀態,由於lua規定這個函數必然會接收不定參數因此第五行將is_vararg標識設為VARARG_ISVARARG。接著第六行luaX_next解析文件流分離出第一個token,將其保存在lexstate的t成員中,此時t為“foo”全局變量。接著調用了chunk函數,這裡開始了遞歸下降解析的全部過程:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 static void chunk (LexState *ls) { /* chunk -> { stat [`;'] } */ int islast = 0; enterlevel(ls); while (!islast && !block_follow(ls->t.token)) { islast = statement(ls);//遞歸下降點 testnext(ls, ';'); lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && ls->fs->freereg >= ls->fs->nactvar); ls->fs->freereg = ls->fs->nactvar; /* free registers */ } leavelevel(ls); }lua是有作用域層次概念的,因此當進入一個層次時會調用enterlevel函數,離開當前層次則會調用leavelevel函數。首先進入while循環,當前token為“foo”,這既不是終結標志也不是一個block開始的詞素,因此會進入statement函數,statement函數主體是一個長長的switch...case...代碼結構,根據第一個token進入不同的調用解析分支。在我們這個例子中會進入default分支:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int statement (LexState *ls) { -- ... ... switch (ls->t.token) { case TK_IF: { /* stat -> ifstat */ ifstat(ls, line); return 0; } case TK_WHILE: { /* stat -> whilestat */ whilestat(ls, line); return 0; } -- ... ... default: { exprstat(ls); return 0; /* to avoid warnings */ } } }進入exprstate函數:
?
1 2 3 4 5 6 7 8 9 10 11 12 static void exprstat (LexState *ls) { /* stat -> func | assignment */ FuncState *fs = ls->fs; struct LHS_assign v; primaryexp(ls, &v.v); if (v.v.k == VCALL) /* stat -> func */ SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ else { /* stat -> assignment */ v.prev = NULL; assignment(ls, &v, 1); } }第四行的LHS_assign結構體是為了處理多變量賦值的情況的,例如a,b,c = ...。在LHS_assign中成員v類型為expdesc描述了等號左邊的變量,詳情可見上篇文章裡對expdesc的介紹。接下來進入primaryexp,來獲取並填充“foo”變量的expdesc信息,這會接著進入prefixexp函數中
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static void prefixexp (LexState *ls, expdesc *v) { /* prefixexp -> NAME | '(' expr ')' */ switch (ls->t.token) { case '(': { int line = ls->linenumber; luaX_next(ls); expr(ls, v); check_match(ls, ')', '(', line); luaK_dischargevars(ls->fs, v); return; } case TK_NAME: { singlevar(ls, v); return; } default: { luaX_syntaxerror(ls, "unexpected symbol"); return; } } }由於當前token是“foo”,因此進入TK_NAME分支,調用singlevar。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static void singlevar (LexState *ls, e