package glua /* * Self-contained Go-Lua binding, which handles inter-FFI errors seamlessly unlike others. * */ // #cgo pkg-config: lua // #cgo LDFLAGS: -L./ -lcglua // #include // #include // #include // #include // extern int glua_metatable_call(lua_State*); // extern int glua_metatable_gc(lua_State*); // extern int glua_metatable_tostring(lua_State*); import "C" import "fmt" import "unsafe" type Function func(l *Lua) int type FuncStatePair struct { fn Function l *Lua } var FuncIDs map[uint64]FuncStatePair = make(map[uint64]FuncStatePair) var NextFuncID uint64 = 0 //export glua_go_call_func func glua_go_call_func(id C.uint64_t) (ret C.int) { pair := FuncIDs[uint64(id)] defer func() { if r := recover(); r != nil { pair.l.PushString(fmt.Sprint(r)) ret = -1 } }() return C.int(pair.fn(pair.l)) } //export glua_go_remove_func func glua_go_remove_func(id C.uint64_t) { delete(FuncIDs, uint64(id)) } type Lua struct { state *C.lua_State } func NewState() Lua { l := Lua{C.luaL_newstate()} C.luaL_newmetatable(l.state, C.CString("GLUA_FUNCTION")) C.lua_pushcclosure(l.state, C.lua_CFunction(C.glua_metatable_call), 0) l.SetField(-2, "__call") C.lua_pushcclosure(l.state, C.lua_CFunction(C.glua_metatable_gc), 0) l.SetField(-2, "__gc") C.lua_pushcclosure(l.state, C.lua_CFunction(C.glua_metatable_tostring), 0) l.SetField(-2, "__tostring") l.Pop(1) return l } func (l Lua) Close() { C.lua_close(l.state) } func (l Lua) OpenLibs() { C.luaL_openlibs(l.state) } func (l Lua) NewTable() { C.lua_createtable(l.state, 0, 0) } func (l Lua) SetField(offset int, name string) { cstr := C.CString(name) C.lua_setfield(l.state, C.int(offset), cstr) C.free(unsafe.Pointer(cstr)) } func (l Lua) SetGlobal(name string) { cstr := C.CString(name) C.lua_setglobal(l.state, cstr) C.free(unsafe.Pointer(cstr)) } func (l Lua) GetGlobal(name string) { cstr := C.CString(name) C.lua_getglobal(l.state, cstr) C.free(unsafe.Pointer(cstr)) } func (l Lua) DoFile(filepath string) int { cstr := C.CString(filepath) defer C.free(unsafe.Pointer(cstr)) err := int(C.luaL_loadfilex(l.state, cstr, nil)) if err != 0 { return err } err = int(C.lua_pcallk(l.state, 0, -1, 0, 0, nil)) if err != 0 { return err } return 0 } func (l Lua) ToInteger(offset int) int64 { return int64(C.lua_tointegerx(l.state, C.int(offset), nil)) } func (l Lua) ToNumber(offset int) float64 { return float64(C.lua_tonumberx(l.state, C.int(offset), nil)) } func (l Lua) ToBoolean(offset int) bool { return C.lua_toboolean(l.state, C.int(offset)) != 0 } func (l Lua) ToString(offset int) string { var len C.size_t cstr := C.lua_tolstring(l.state, C.int(offset), &len) return C.GoStringN(cstr, C.int(len)) } func (l Lua) PushInteger(i int64) { C.lua_pushinteger(l.state, C.lua_Integer(i)) } func (l Lua) PushNumber(f float64) { C.lua_pushnumber(l.state, C.lua_Number(f)) } func (l Lua) PushString(s string) { cstr := C.CString(s) C.lua_pushstring(l.state, cstr) C.free(unsafe.Pointer(cstr)) } func (l Lua) PushFunction(fn Function) { i := NextFuncID for true { if _, exists := FuncIDs[i]; !exists { break } i += 1 } ud := C.lua_newuserdatauv(l.state, 8, 1) *(*C.uint64_t)(ud) = C.uint64_t(i) cstr := C.CString("GLUA_FUNCTION") C.luaL_setmetatable(l.state, cstr) C.free(unsafe.Pointer(cstr)) FuncIDs[i] = FuncStatePair{fn, &l}; } func (l Lua) PushValue(offset int) { C.lua_pushvalue(l.state, C.int(offset)); } func (l Lua) Pop(count int) { C.lua_settop(l.state, C.int(-count - 1)); } func (l Lua) Call(nargs int, nresults int, msgh int) int { err := int(C.lua_pcallk(l.state, C.int(nargs), C.int(nresults), C.int(msgh), 0, nil)) if err == 2 { l.PushValue(-1) why := l.ToString(-1) l.Pop(1) panic(why) } return err } func (l Lua) ProtectedCall(nargs int, nresults int, msgh int) int { return int(C.lua_pcallk(l.state, C.int(nargs), C.int(nresults), C.int(msgh), 0, nil)) } func (l Lua) UnsafeCall(nargs int, nresults int) { C.lua_callk(l.state, C.int(nargs), C.int(nresults), 0, nil) }