開篇
上一節(jié)分析了
lua_arith
的大部分代碼喇潘,由于篇幅原因模暗,留到本節(jié)將繼續(xù)講解剩余的部分:
luaO_arith(L, op, L->top - 2, L->top - 1, L->top - 2);
L->top--; /* remove second operand */
lua_unlock(L);
解析
現(xiàn)在我們來(lái)看運(yùn)算規(guī)則 luaO_arith
:
// lobject.c 123
void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2,
TValue *res) {
switch (op) {
case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR:
case LUA_OPSHL: case LUA_OPSHR:
case LUA_OPBNOT: { /* operate only on integers */
lua_Integer i1; lua_Integer i2;
if (tointeger(p1, &i1) && tointeger(p2, &i2)) {
setivalue(res, intarith(L, op, i1, i2));
return;
}
else break; /* go to the end */
}
case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */
lua_Number n1; lua_Number n2;
if (tonumber(p1, &n1) && tonumber(p2, &n2)) {
setfltvalue(res, numarith(L, op, n1, n2));
return;
}
else break; /* go to the end */
}
default: { /* other operations */
lua_Number n1; lua_Number n2;
if (ttisinteger(p1) && ttisinteger(p2)) {
setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2)));
return;
}
else if (tonumber(p1, &n1) && tonumber(p2, &n2)) {
setfltvalue(res, numarith(L, op, n1, n2));
return;
}
else break; /* go to the end */
}
}
/* could not perform raw operation; try metamethod */
lua_assert(L != NULL); /* should not fail when folding (compile time) */
luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD));
}
TValue
注意到參數(shù) 3, 4
是兩個(gè)操作數(shù)爸邢,且它們都是 TValue *
類型,我們找到 TValue
的定義:
// lobject.h 100
/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc; /* collectable objects : 可回收的對(duì)象 */
void *p; /* light userdata : 輕量級(jí) userdata */
int b; /* booleans : 布爾值*/
lua_CFunction f; /* light C functions : 輕量級(jí) C 函數(shù) */
lua_Integer i; /* integer numbers : 整型數(shù) */
lua_Number n; /* float numbers : 浮點(diǎn)數(shù) */
} Value;
#define TValuefields Value value_; int tt_
typedef struct lua_TValue {
TValuefields;
} TValue;
TValue
是一個(gè)結(jié)構(gòu)體別名,用它可以定義一個(gè)結(jié)構(gòu)體宴杀,其中包含一個(gè)聯(lián)合體 value_
和一個(gè)整型數(shù) tt_
;而 value_
包含了所有 Lua
的值類型:可回收的對(duì)象
拾因、輕量級(jí) userdata
旺罢、布爾值
、輕量級(jí) C 函數(shù)
绢记、整型數(shù)
和 浮點(diǎn)數(shù)
扁达。也就是說(shuō),除了 GCObject
蠢熄,其他 5 個(gè)數(shù)據(jù)類型是不用 Lua
回收機(jī)制的跪解。
我們來(lái)看 GCObject
:
// lobject.h 72
/*
** Common type for all collectable objects
*/
typedef struct GCObject GCObject;
/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
/*
** Common type has only the common header
*/
struct GCObject {
CommonHeader;
};
知道 GCObject
首先是個(gè)結(jié)構(gòu)體,其次它包含了三個(gè)域 :GCObject *next; lu_byte tt; lu_byte marked
签孔;這里 GCObject *
又被替換為結(jié)構(gòu)體類型 struct GCObject *
叉讥。再看 lu_byte
:
// llimits.h 35
/* chars used as small naturals (so that 'char' is reserved for characters) */
typedef unsigned char lu_byte;
可知,lu_byte
就是簡(jiǎn)單的類型別名饥追,用一個(gè) unsigned char
類型來(lái)表示节吮。
綜上對(duì) GCObject
的分析,可得:所有的 GCObject
都用一個(gè)單向鏈表串了起來(lái)判耕,每個(gè) GCObject
對(duì)象都攜帶 tt, marked
兩個(gè)標(biāo)記(其中 tt
用來(lái)識(shí)別其類型透绩,marked
域用于標(biāo)記清除的工作,這些是后話了壁熄,現(xiàn)在我也不懂)帚豪。
現(xiàn)在是不是可以理解 Lua 官方對(duì)值和類型的說(shuō)明了:
Lua is a dynamically typed language. This means that variables do not have types; only values do. There are no type definitions in the language. All values carry their own type.
因?yàn)?TValue
攜帶了所有的 Lua
值類型,這就是變量沒(méi)有類型草丧,只有值才有類型的原因狸臣!
Operator Rules
解釋完 TValue
,我們來(lái)歸納一下 switch
之后的運(yùn)算符規(guī)則:
-
LUA_OPBAND昌执、 LUA_OPBOR烛亦、 LUA_OPBXOR诈泼、 LUA_OPSHL、 LUA_OPSHR煤禽、 LUA_OPBNOT
遵循[1]
操作铐达; -
LUA_OPDIV、 LUA_OPPOW
遵循[2]
操作檬果; -
LUA_OPADD瓮孙、 LUA_OPSUB、 LUA_OPMUL选脊、 LUA_OPMOD杭抠、 LUA_OPUNM
遵循default
[3]
操作。
下面以 [1]
作為切入口來(lái)解析具體的運(yùn)算服規(guī)則恳啥,由于其他操作活類似或略有不同偏灿,因此不再多費(fèi)唇舌。
注意到 [1]
中的元素都是單目運(yùn)算符钝的,其運(yùn)算規(guī)則為:
{
lua_Integer i1; lua_Integer i2;
if (tointeger(p1, &i1) && tointeger(p2, &i2)) {
setivalue(res, intarith(L, op, i1, i2));
return;
}
else break; /* go to the end */
}
按步驟來(lái)說(shuō):
- 聲明兩個(gè)
lua_Integer
類型數(shù)據(jù)i1, i2
翁垂; - 將兩個(gè)操作數(shù)進(jìn)行類型轉(zhuǎn)換,并將結(jié)果分別存儲(chǔ)到
i1, i2
扁藕; - 如果轉(zhuǎn)換成功,則執(zhí)行運(yùn)算后直接返回疚脐。
- 如果轉(zhuǎn)換失敗亿柑,則跳出
switch
。
接下來(lái)我們逐個(gè)分析幾個(gè)要點(diǎn):
lua_Integer
tointeger
ntarith
setivalue
1. lua_Integer
lua_Integer
定義在 lua.h 93
行:typedef LUA_INTEGER lua_Integer;
棍弄。這里是為 LUA_INTEGER
定義了別名 lua_Integer
望薄。而 LUA_INTEGER
在 luaconf.h 524 ~ 574
行之間有詳細(xì)的說(shuō)明,它指代的是整型數(shù)據(jù)類型呼畸。
2. tointeger
tointeger
定義在 lvm.h 43
行:
#define tointeger(o,i) \
(ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I))
再看 ttisinteger
和 ivalue
是這么定義的:
/* raw type tag of a TValue */
#define rttype(o) ((o)->tt_)
#define checktag(o,t) (rttype(o) == (t))
#define ttisinteger(o) checktag((o), LUA_TNUMINT)
#define check_exp(c,e) (lua_assert(c), (e))
#define val_(o) ((o)->value_)
#define ivalue(o) check_exp(ttisinteger(o), val_(o).i)
現(xiàn)在我們知道 o
是 Lua
原始值 TValue *o
, rttype(o)
獲取的是原始值的 tt_
標(biāo)記痕支,而 tt_
則是原始值類型標(biāo)記,因此通過(guò) checktag(o,t)
可以檢查原始值類型標(biāo)記 tt_
是否與預(yù)期 t
符合蛮原,已達(dá)到檢查值類型的目的卧须。
接著看 ivalue(o)
:首先它通過(guò) ttisinteger(o)
檢查當(dāng)前原始值的類型標(biāo)記 tt_
是不是整型作為斷言的判斷條件,其次通過(guò) val_(o)
取出原始值的真實(shí)值 value_
中的整型域作為斷言的消息儒陨,最后通過(guò) check_exp(c,e)
進(jìn)行斷言操作花嘶。
我們回過(guò)頭看 ttisinteger
,就可以拆成 tt + isinteger
蹦漠,意思是原始值的標(biāo)記 tt_
標(biāo)記的是不是整型類型椭员。那么 LUA_TNUMINT
指代的就是 Lua
中的整型類型了。來(lái)看看是不是這樣:
#define LUA_TNUMBER 3
/* Variant tags for numbers */
#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) /* float numbers */
#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) /* integer numbers */
一看不得了笛园,我們順便知道了 LUA_TNUMBER
定義了 LUA_TNUMFLT
和 LUA_TNUMINT
隘击,也就是 number
類型包含了整型和浮點(diǎn)型侍芝,這也跟官方的介紹不謀而合:
The type number represents both integer numbers and real (floating-point) numbers.
然而,有點(diǎn)經(jīng)驗(yàn)的同學(xué)可能有疑問(wèn)了:在 Lua 5.2
版本中埋同,所有的 number
不都是浮點(diǎn)數(shù)嗎州叠?那這又是什么鬼?
一個(gè)可能是我們理解錯(cuò)了莺禁,一個(gè)可能就是在 Lua 5.3
版本中對(duì)其做出了修改留量。我們看版本變更日志是否有提及此事:
8.1 – Changes in the Language
- The main difference between Lua 5.2 and Lua 5.3 is the introduction of an integer subtype for numbers. Although this change should not affect "normal" computations, some computations (mainly those that involve some kind of overflow) can give different results.
You can fix these differences by forcing a number to be a float (in Lua 5.2 all numbers were float), in particular writing constants with an ending .0 or using x = x + 0.0 to convert a variable. (This recommendation is only for a quick fix for an occasional incompatibility; it is not a general guideline for good programming. For good programming, use floats where you need floats and integers where you need integers.)
- The conversion of a float to a string now adds a .0 suffix to the result if it looks like an integer. (For instance, the float 2.0 will be printed as 2.0, not as 2.) You should always use an explicit format when you need a specific format for numbers.
簡(jiǎn)單翻譯:
- Lua 5.3 與 Lua 5.2 版本主要的差別就是 numbers 新增了子類型 integer。盡管這個(gè)改動(dòng)并不影響“普通”的計(jì)算哟冬,但部分計(jì)算(主要是一些涉及溢出的計(jì)算)可能會(huì)與之前的版本的計(jì)算結(jié)果有所不同楼熄。
一個(gè)修正的方案是將 number 強(qiáng)轉(zhuǎn)為 float (在 Lua 5.2 中所有的 numbers 都是 float)。你可以在常熟后加上 .0 或者使用 x = x + 0.0 的方法來(lái)轉(zhuǎn)換浩峡。(但這些建議是為了兼容而作的妥協(xié)可岂,而并非良好的編程綱領(lǐng),好的方式是:當(dāng)用 float 則用 float翰灾,當(dāng)用 integer 則用 integer缕粹,涇渭分明,分而治之纸淮。)- 當(dāng)將 float 轉(zhuǎn)換為 string 時(shí)平斩,如果結(jié)果看上去像是 integer,會(huì)在結(jié)果最后加上 .0咽块。(舉個(gè)例子绘面,浮點(diǎn)型 2.0 打印出來(lái)是 2.0,而不是 2侈沪。)
果不其然揭璃,我們的分析是正確的,大家可以用兩個(gè)版本分別驗(yàn)證一下:
Lua 5.2.4
> a = 1 + 3.0
> print(a)
4
---------------
Lua 5.3.3
> a = 1 + 3.0
> print(a)
4.0
另提一點(diǎn)亭罪, 5.3 版本的解釋器已經(jīng)支持直接計(jì)算了:
Lua 5.2.4
> 1 + 3.0
stdin:1: unexpected symbol near '1'
Lua 5.3.3
> 1 + 3.0
4.0
現(xiàn)在剩下一個(gè) luaV_tointeger
還在漿糊之中瘦馍,我們?cè)傩刑骄浚?/p>
// lvm.c 94
/*
** try to convert a value to an integer, rounding according to 'mode':
** mode == 0: accepts only integral values
** mode == 1: takes the floor of the number
** mode == 2: takes the ceil of the number
*/
// 注意:integral 是完整的、整體的意思应役,而非整數(shù)情组!
int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) {
TValue v;
again:
if (ttisfloat(obj)) {
lua_Number n = fltvalue(obj); // 取原始值 value_ 的 n 域
lua_Number f = l_floor(n); // 對(duì) n 進(jìn)行 floor 操作,賦值給 f
// 如果 n != f箩祥,那么 obj 就是不可轉(zhuǎn)換為類整數(shù)的浮點(diǎn)數(shù) (如2.01)
if (n != f) { /* not an integral value? */
// 而如果 mode = 0呻惕,即要求操作數(shù)可轉(zhuǎn)換為類整數(shù),那么就直接返回 0
if (mode == 0) return 0; /* fails if mode demands integral value */
// 如果 mode > 1滥比,即要求將操作數(shù)進(jìn)行向上取整
else if (mode > 1) /* needs ceil? */
f += 1; /* convert floor to ceil (remember: n != f) */
}
// 然后執(zhí)行 number 轉(zhuǎn)換為 integer 操作
return lua_numbertointeger(f, p);
}
else if (ttisinteger(obj)) {
// 如果操作數(shù)本身就是整型數(shù)據(jù)亚脆,那么直接講原始值的 value_ 的 整數(shù)域賦值給 *p 即可
*p = ivalue(obj);
return 1;
}
else if (cvt2num(obj) &&
luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) {
// 如果操作數(shù)是字符串,且可完全轉(zhuǎn)換為 number盲泛,則將操作數(shù)替換為轉(zhuǎn)換后的值后濒持,重頭開始轉(zhuǎn)換
obj = &v;
goto again; /* convert result from 'luaO_str2num' to an integer */
}
// 轉(zhuǎn)換失敗
return 0; /* conversion failed */
}
/********* 補(bǔ)充定義 **********/
// fltvalue
#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n)
// l_floor
#define l_floor(x) (l_mathop(floor)(x))
// lua_numbertointeger
#define lua_numbertointeger(n,p) \
((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \
(n) < -(LUA_NUMBER)(LUA_MININTEGER) && \
(*(p) = (LUA_INTEGER)(n), 1))
// svalue
// 從原始值中獲取實(shí)際的字符串(字節(jié)數(shù)組)
/* get the actual string (array of bytes) from a Lua value */
#define svalue(o) getstr(tsvalue(o))
// LUA_FLOORN2I
// 默認(rèn) LUA_FLOORN2I = 0
/*
** You can define LUA_FLOORN2I if you want to convert floats to integers
** by flooring them (instead of raising an error if they are not
** integral values)
*/
#if !defined(LUA_FLOORN2I)
#define LUA_FLOORN2I 0
#endif
現(xiàn)在键耕,我們重新理解 luaV_tointeger(o,i,LUA_FLOORN2I)
就很簡(jiǎn)單了,就是將原始值 o
轉(zhuǎn)換為整型數(shù)據(jù)柑营,然后賦值給 i
屈雄,當(dāng)然如果轉(zhuǎn)換成功則返回 1
,失敗則返回 0
官套。
回過(guò)頭再看 tointeger 的定義:
#define tointeger(o,i) \
(ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I))
現(xiàn)在可以輕松地解釋清楚了:如果原始值 o
的當(dāng)前類型標(biāo)記是整型酒奶,則取它的整型數(shù)據(jù)部分,并返回 1
奶赔;否則惋嚎,嘗試進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換成功則取轉(zhuǎn)換結(jié)果并返回 1
站刑,失敗則返回 0
另伍;其中 1
代表成功,0
代表失敗绞旅。完美摆尝!
3. intarith
intarith
可以拆解為 int(eger)
和 arith(metic)
,顧名思義:整數(shù)運(yùn)算的意思因悲。我們來(lái)看定義:
static lua_Integer intarith (lua_State *L, int op, lua_Integer v1,
lua_Integer v2) {
switch (op) {
case LUA_OPADD: return intop(+, v1, v2);
case LUA_OPSUB:return intop(-, v1, v2);
case LUA_OPMUL:return intop(*, v1, v2);
case LUA_OPMOD: return luaV_mod(L, v1, v2);
case LUA_OPIDIV: return luaV_div(L, v1, v2);
case LUA_OPBAND: return intop(&, v1, v2);
case LUA_OPBOR: return intop(|, v1, v2);
case LUA_OPBXOR: return intop(^, v1, v2);
case LUA_OPSHL: return luaV_shiftl(v1, v2);
case LUA_OPSHR: return luaV_shiftl(v1, -v2);
case LUA_OPUNM: return intop(-, 0, v1);
case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1);
default: lua_assert(0); return 0;
}
}
/********** 補(bǔ)充定義 **********/
#define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2))
#if !defined(l_castU2S)
#define l_castU2S(i) ((lua_Integer)(i))
#endif
#if !defined(l_castS2U)
#define l_castS2U(i) ((lua_Unsigned)(i))
#endif
/* unsigned integer type */
typedef LUA_UNSIGNED lua_Unsigned;
#define LUA_UNSIGNED unsigned LUAI_UACINT
#define LUAI_UACINT LUA_INTEGER
intop(op, v1, v2)
堕汞,先將 v1, v2
轉(zhuǎn)換為無(wú)符號(hào)整型,計(jì)算出結(jié)果之后晃琳,再將結(jié)果轉(zhuǎn)換為有符號(hào)整型讯检。
luaV_mod
、luaV_div
蝎土、luaV_shiftl
則對(duì)應(yīng) 取模视哑、除法绣否、左移
操作誊涯,這里就不展開了,讀者自行探究蒜撮。
4. setivalue
現(xiàn)在來(lái)看最后一個(gè)要點(diǎn):setivalue(v1, v2)
暴构。
我們知道 ivalue
是取原始值的整型部分?jǐn)?shù)值,那么不妨把 setivalue
拆解為 set
和 ivalue
段磨,解釋為將 v2
賦值給 v1
的整型部分取逾。來(lái)看看是不是這樣?
#define setivalue(obj,x) \
{ TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_TNUMINT); }
#define settt_(o,t) ((o)->tt_=(t))
我們看到 setivalue
和 settt_
是宏定義苹支,我們用 v1, v2
帶入后展開:
{ TValue *io=(v1); val_(io).i=(v2); io->tt_=(LUA_TNUMINT); }
果然砾隅,除了最后將原始值的類型標(biāo)記 tt_
賦值為 LUA_TNUMINT
,其他和我們所料不差债蜜。我們也需要謹(jǐn)記晴埂,當(dāng)需要改變 Lua
值類型時(shí)究反,必須同時(shí)改變它的 tt_
類型標(biāo)記。
luaT_trybinTM
如果 switch
中的運(yùn)算失敗儒洛,繼而跳出 switch
精耐,那么就需要執(zhí)行 luaT_trybinTM
:
/* could not perform raw operation; try metamethod */
luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD));
我們看注釋說(shuō)明:如果無(wú)法執(zhí)行原始的運(yùn)算,則進(jìn)行元方法運(yùn)算琅锻。
由于元方法涉及運(yùn)算符的重載卦停,這部分屬于 Lua
元方法的內(nèi)容,我們先標(biāo)記一下恼蓬,暫且跳過(guò)惊完,等接觸到元方法之后,回過(guò)頭再來(lái)看這部分內(nèi)容滚秩。
回到最初
// ...
luaO_arith(L, op, L->top - 2, L->top - 1, L->top - 2);
L->top--; /* remove second operand */
lua_unlock(L);
當(dāng)運(yùn)算完成后专执,我們看到棧進(jìn)行了一次自減操作,目的是移除位于棧中 L->top - 1
處的第二操作數(shù)郁油,這個(gè)時(shí)候棧中 L->top - 2
索引處存儲(chǔ)的則是運(yùn)算結(jié)果了本股。
小結(jié)
-
TValue *
定義的結(jié)構(gòu)體是Lua
的原始值; - 原始值中攜帶兩份數(shù)據(jù)桐腌,一個(gè)是
Value
聯(lián)合體定義的value_
拄显,它是攜帶真實(shí)值的正體;一個(gè)是類型標(biāo)記tt_
案站; -
value_
包含了所有Lua
值類型的數(shù)據(jù)躬审,分別是:可回收的對(duì)象 : gc
、輕量級(jí) userdata : p
蟆盐、布爾值 : b
承边、輕量級(jí) C 函數(shù) : f
、整型數(shù) : i
和浮點(diǎn)數(shù) : n
石挂; - 當(dāng)原始值數(shù)據(jù)變化時(shí)博助,類型標(biāo)記
tt_
也將對(duì)應(yīng)改變,如:(o)->i=n_x
痹愚,則對(duì)應(yīng)地(o)->tt_=LUA_TNUMINT
富岳。
終了
- 看一次源碼,復(fù)習(xí)好多 C 知識(shí)拯腮,雖然很累窖式,但很充實(shí)。
- 棧的數(shù)據(jù)結(jié)構(gòu)可以去復(fù)習(xí)一下动壤。
- 閱讀中發(fā)現(xiàn)了很多 Lua 內(nèi)置實(shí)現(xiàn)的彩蛋萝喘,有一種探險(xiǎn)的驚喜。