在Lua的官方C API中,Lua與C通過一個虛擬棧來互動資料。例如有個a.lua的檔案中有求和函數:
function sum(a, b) return a + bend
要在C代碼中開啟lua檔案,並調用求和函數,大致要這樣寫:
lua_State *s = luaL_newstate();luaL_openlibs(s);luaL_loadfile(s, "a.lua");lua_getglobal(s, "sum");lua_pushinteger(s, 1234);lua_pushinteger(s, 4321);lua_pcall(s, 2, 1, 0);int sum = lua_tointeger(s, -1); /* sum = 5555 */lua_pop(s, 1);lua_close(s);
可以想見官方API的設計專註於正交性,而不考慮便利性。
自然希望有一個封裝,讓程式員在C代碼中,儘可能地像Lua一樣調用Lua。
github上沒搜到特別中意的,只好自己寫一個,取名Lucy。設計目標:
1) 隱藏虛擬棧——因此也隱藏基於虛擬棧的官方C API。
2) 在C代碼中,盡量用Lua的方式調用Lua代碼。
3) function和table在C代碼中也該是第一類型,可以作為函數參數傳遞——其實是第2) 點的補充說明。
在Lua這樣的動態類型語言中,變數本身沒有類型,被賦值後才有相應的類型。
因此在Lucy中只需定義一個名為lucy_Data的結構體用以表示Lua變數:
typedef struct { lucy_Type type_; lucy_Content cntnt_;} lucy_Data;
lucy_Type是一個枚舉類型,lucy_Content則是聯合體。
如此定義,則對lucy_Data作任何操作,編譯期都沒問題,而只會在運行期宣告失敗,算是對Lua變數的類比。
lucy_Content中有個類型lucy_Ref:
typedef struct { lua_State *state_; int index_;} lucy_Ref;
儲存function, table這樣的參考型別,在虛擬棧上的索引。於是它們就可以作為第一類型了。
function傳入參數,傳出傳回值的基礎類型,都可以是lucy_Data的數群組類型lucy_List。
不難設計與實現剩下的介面。
例如有以下Lua代碼:
function Loc(x, y) return function() return x, y endendfunction GetArea(loc) local x, y = loc() return x * yend
給定x與y,通過調用GetArea函數來取得結果,通過Lucy封裝後可以這麼寫(比如x與y都為5):
lucy_File file = lucy_CreateFile();lucy_OpenFile(&file, "a.lua");lucy_Data Loc = lucy_GetData(&file, "Loc");lucy_Data five = lucy_Num(5);lucy_Data loc = lucy_Call(&Loc, 1, 2, &five, &five).datas_[0];lucy_Data GetArea = lucy_GetData(&file, "GetArea");lucy_Data area = lucy_Call(&GetArea, 1, 1, &loc).datas_[0];lucy_PrintData(&area);
運行結果:
代碼已上傳至: https://github.com/chncwang/Lucy
已有的介面包括取變數,調用function,查詢table元素,長度等。