修改 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 - mods
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 VM 指令的简明介绍[2]。Yueliang[3](Lua 中的 Lua 实现)也可能有所帮助。
除了直接修改 Lua 代码,还可以考虑使用 MetaLua[4] 和 LuaTokenParsing 等工具。鉴于 Lua 强大的元编程能力(特别是 代码生成),你甚至可能不需要修改 Lua 本身。