修改 Lua |
|
__settable
),當變更資料表的數值時,會呼叫這個方法。__settable
,將會覆寫 __newindex
。以下是最佳說明。我們想要修改 Lua,以便以下程式碼會如所示執行。
test1.lua
function configmodified(t,k,v) print("Config Modified, call reload") rawset(t,k,v) end config = { maxthreads = 10, minthreads = 2 } setmetatable(config, {__setindex = configmodified})
以及結果
D:\code\lua\mod\bin>lua -i test1.lua Lua 5.1.1 Copyright (C) 1994-2006 Lua.org, PUC-Rio table: 00940180 > =config.minthreads 2 > config.minthreads = 3 Config Modified, call reload > =config.minthreads 3 > config.sleepdelay = 100 Config Modified, call reload
內部程式碼會呼叫後設方法的「標籤方法」,基本清單位於 ltm.h
/* * WARNING: if you change the order of this enumeration, * grep "ORDER TM" */ typedef enum { TM_INDEX, TM_NEWINDEX, TM_GC, TM_MODE, TM_EQ, /* last tag method with `fast' access */ TM_ADD, TM_SUB, TM_MUL, TM_DIV, TM_MOD, TM_POW, TM_UNM, TM_LEN, TM_LT, TM_LE, TM_CONCAT, TM_CALL, TM_N /* number of elements in the enum */ } TMS;
TM_EQ 的註解非常重要。由於設定數值需要有高效能,我們需要深入瞭解這一點。
「快速」存取的方式是在每個資料表上儲存一個標記,並標示標記是否存在。
資料表定義在 lobject.h 中
typedef struct Table { CommonHeader; lu_byte flags; /* 1<<p means tagmethod(p) is not present */ lu_byte lsizenode; /* log2 of size of `node' array */ struct Table *metatable; TValue *array; /* array part */ Node *node; Node *lastfree; /* any free position is before this position */ GCObject *gclist; int sizearray; /* size of `array' array */ } Table;
Table.flags 是 1 位元組,由於每個標記佔用 1 個位元,我們可以有最多 8 個「快速存取」的後設方法。基本 Lua 設定為 5 個快速存取標記,給予我們 3 個空間。我們將其中一個用於 SETINDEX,如下所示
ltm.h - 修改
typedef enum { TM_INDEX, TM_NEWINDEX, TM_SETINDEX, // AA - new metatag that is called everytime a value is set, its use disables TM_NEWINDEX''' TM_GC, TM_MODE, TM_EQ, /* last tag method with `fast' access */ ...
我們還需要為這個新標籤命名,這在以下程式碼中
ltm.c - luaT_init
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" };
我們將其變更為
static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__setindex", // AA - Added name for setindex "__gc", "__mode", "__eq", ...
最後,我們需要讓這個程式碼運作。大部分繁重的運算發生在 lvm.c 中,設定數值的標準呼叫 (非 rawset) 會經由 luaV_settable 進行。我們來看看
void luaV_settable (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); TValue *oldval = luaH_set(L, h, key); /* do a primitive set */ if (!ttisnil(oldval) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */ setobj2t(L, oldval, val); luaC_barriert(L, h, val); return; } /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_typeerror(L, t, "index"); if (ttisfunction(tm)) { callTM(L, tm, t, key, val); return; } t = tm; /* else repeat with `tm' */ } luaG_runerror(L, "loop in settable"); }
我們可以這樣修改。
void luaV_settable (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); TValue *oldval; // AA -- Have to declare this here // AA - Our new code here tm = fasttm(L, h->metatable, TM_SETINDEX); if(tm != NULL) { if (ttisfunction(tm)) { callTM(L, tm, t, key, val); return; } } oldval = luaH_set(L, h, key); /* do a primitive set */ if (!ttisnil(oldval) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */ setobj2t(L, oldval, val); luaC_barriert(L, h, val); return; } ...
現在我們編譯並測試。最上方的程式碼現在應該會如廣告所示執行。
請勿更改 LUA_VERSION 或 LUA_RELEASE 中的數字!請附加字串,例如 '(Kirk3)'。 --lhf
執行此項作業的其中一個問題是,每個設定值需要進行其他測試。可以透過以下設計考量來緩解一點問題。
---
有些文件描述 Lua 的執行狀況[1]。另請參閱 Lua 5.1 虛擬機器指令簡介[2]。Lua 中的 Yueliang[3] (Lua 的執行狀況) 也可能有幫助。
修改 Lua 本身的替代方案包括 MetaLua[4] 和 LuaTokenParsing。或許因為 Lua 強大的元編程功能而無需修改 Lua(特別請見 CodeGeneration)。