From 285a141825f1902705d3260c8259cc9f9bd20c6c Mon Sep 17 00:00:00 2001 From: mid <> Date: Mon, 23 Feb 2026 19:18:09 +0200 Subject: [PATCH] Initial commit --- .gitignore | 4 ++ glua/cglua.c | 32 +++++++++ glua/glua.go | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 8 +++ go.sum | 4 ++ main.go | 41 +++++++++++ main.lua | 1 + 7 files changed, 286 insertions(+) create mode 100644 .gitignore create mode 100644 glua/cglua.c create mode 100644 glua/glua.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 main.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f27b991 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.a +/goci +/goci.exe diff --git a/glua/cglua.c b/glua/cglua.c new file mode 100644 index 0000000..cf4217b --- /dev/null +++ b/glua/cglua.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +extern int glua_go_call_func(uint64_t); +extern int glua_go_remove_func(uint64_t); + +int glua_metatable_call(lua_State *L) { + uint64_t id = *(uint64_t*) lua_touserdata(L, 1); + int result = glua_go_call_func(id); + if(result < 0) { + lua_error(L); + } + return result; +} +int glua_metatable_gc(lua_State *L) { + uint64_t id = *(uint64_t*) lua_touserdata(L, 1); + glua_go_remove_func(id); + return 0; +} +int glua_metatable_tostring(lua_State *L) { + uint64_t id = *(uint64_t*) lua_touserdata(L, 1); + + char buf[64]; + snprintf(buf, sizeof(buf), "gofunc: 0x%X", id); + + lua_pushstring(L, buf); + + return 1; +} + diff --git a/glua/glua.go b/glua/glua.go new file mode 100644 index 0000000..90e8d9d --- /dev/null +++ b/glua/glua.go @@ -0,0 +1,196 @@ +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) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bd5de3c --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module mid.net.ua/git/mid/goci + +go 1.25 + +require ( + github.com/Shopify/go-lua v0.0.0-20250718183320-1e37f32ad7d0 // indirect + github.com/mappu/miqt v0.13.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..36f33f3 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/Shopify/go-lua v0.0.0-20250718183320-1e37f32ad7d0 h1:oGlw/+ndlFMn8KWLjEX5nULcDwOC4tJy3Kk1Pm84Cys= +github.com/Shopify/go-lua v0.0.0-20250718183320-1e37f32ad7d0/go.mod h1:M4CxjVc/1Nwka5atBv7G/sb7Ac2BDe3+FxbiT9iVNIQ= +github.com/mappu/miqt v0.13.0 h1:Dzvclso1BwAUVNem7giSYCmh4Rx08j2gqt6j4SCy098= +github.com/mappu/miqt v0.13.0/go.mod h1:xFg7ADaO1QSkmXPsPODoKe/bydJpRG9fgCYyIDl/h1U= diff --git a/main.go b/main.go new file mode 100644 index 0000000..cef37c3 --- /dev/null +++ b/main.go @@ -0,0 +1,41 @@ +package main + +import "os" +import "fmt" +import "github.com/mappu/miqt/qt6" + +import "mid.net.ua/git/mid/goci/glua" + +func InitUI() { + qt6.NewQApplication(os.Args) + + mw := qt6.NewQMainWindow(nil) + + { + dock := qt6.NewQDockWidget(mw.QWidget) + mw.AddDockWidget(qt6.DockWidgetArea(1), dock) + } + + mw.Show() +} + +var L glua.Lua +func InitLua() { + L := glua.NewState() + L.OpenLibs() + + L.NewTable() + // Our interface functions go here + L.SetGlobal("UI") + + L.DoFile("main.lua") +} + +func main() { + fmt.Println("I'm printing dis to prevent unused package warning") + + InitUI() + InitLua() + + qt6.QApplication_Exec() +} diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/main.lua @@ -0,0 +1 @@ +