在Lua的官方C API中,Lua与C通过一个虚拟栈来交互数据。例如有个a.lua的文件中有求和函数:
function sum(a, b) return a + b end
要在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 end end function GetArea(loc) local x, y = loc() return x * y end
给定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元素,长度等。