修改 Lua

lua-users home
wiki

以下是修改 Lua 源碼以更改其功能的一些第一步驟。以下範例不一定是很好的修改選項,也不一定是修改這些選項的最佳方式。不過,我們選擇這些範例是因為它們的影響力和要更改的程式碼性質。本教學課程使用 Lua 5.1.1 編寫。

任務

以下是最佳說明。我們想要修改 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 執行的第一個相當基本但有效的修改。另一個我建議執行的動作,是更改版本和釋出常數,以便輕鬆找出這是修改版本的 lua。它們位於 lua.h 中

請勿更改 LUA_VERSION 或 LUA_RELEASE 中的數字!請附加字串,例如 '(Kirk3)'。 --lhf

執行此項作業的其中一個問題是,每個設定值需要進行其他測試。可以透過以下設計考量來緩解一點問題。

---

有些文件描述 Lua 的執行狀況[1]。另請參閱 Lua 5.1 虛擬機器指令簡介[2]。Lua 中的 Yueliang[3] (Lua 的執行狀況) 也可能有幫助。

修改 Lua 本身的替代方案包括 MetaLua[4]LuaTokenParsing。或許因為 Lua 強大的元編程功能而無需修改 Lua(特別請見 CodeGeneration)。


最新異動 · 偏好設定
編輯 · 歷史
上次編輯於 2017 年 10 月 21 日下午 12:15 GMT (差異)