package lua import ( "errors" "fmt" "reflect" lua "github.com/yuin/gopher-lua" ) type Engine struct { L *lua.LState } func New() *Engine { L := lua.NewState() return &Engine{L: L} } func (e *Engine) Close() { e.L.Close() } // RegisterAPI registers all Lua API modules (http, json, log, etc.) func (e *Engine) RegisterAPI() { RegisterHTTPModule(e.L) RegisterJSONModule(e.L) RegisterLogModule(e.L) RegisterUtilModule(e.L) } func (e *Engine) LoadFile(path string) error { return e.L.DoFile(path) } func (e *Engine) CallString(fn string, args ...lua.LValue) (string, error) { L := e.L if err := L.CallByParam(lua.P{ Fn: L.GetGlobal(fn), NRet: 1, Protect: true, }, args...); err != nil { return "", err } ret := L.Get(-1) L.Pop(1) return ret.String(), nil } func (e *Engine) CallStruct(fn string, out any, args ...lua.LValue) error { L := e.L f := L.GetGlobal(fn) if f.Type() != lua.LTFunction { return fmt.Errorf("lua function %s not found", fn) } if err := L.CallByParam(lua.P{ Fn: f, NRet: 1, Protect: true, }, args...); err != nil { return err } ret := L.Get(-1) L.Pop(1) table, ok := ret.(*lua.LTable) if !ok { return fmt.Errorf("lua function %s must return table", fn) } return luaTableToStruct(table, out) } func luaTableToStruct(t *lua.LTable, out any) error { v := reflect.ValueOf(out) if v.Kind() != reflect.Ptr { return errors.New("out must be pointer") } v = v.Elem() for i := 0; i < v.NumField(); i++ { field := v.Type().Field(i) key := field.Tag.Get("lua") if key == "" { continue } lv := t.RawGetString(key) if lv == lua.LNil { continue } fv := v.Field(i) if err := setFieldFromLua(fv, lv); err != nil { return fmt.Errorf("field %s: %w", field.Name, err) } } return nil } // setFieldFromLua sets a reflect.Value field from a lua.LValue func setFieldFromLua(fv reflect.Value, lv lua.LValue) error { if !fv.CanSet() { return errors.New("field cannot be set") } switch fv.Kind() { case reflect.String: fv.SetString(lv.String()) case reflect.Bool: fv.SetBool(lua.LVAsBool(lv)) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if n, ok := lv.(lua.LNumber); ok { fv.SetInt(int64(n)) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if n, ok := lv.(lua.LNumber); ok { fv.SetUint(uint64(n)) } case reflect.Float32, reflect.Float64: if n, ok := lv.(lua.LNumber); ok { fv.SetFloat(float64(n)) } case reflect.Slice: if ltable, ok := lv.(*lua.LTable); ok { return setSliceFromLuaTable(fv, ltable) } case reflect.Map: if ltable, ok := lv.(*lua.LTable); ok { return setMapFromLuaTable(fv, ltable) } case reflect.Struct: if ltable, ok := lv.(*lua.LTable); ok { return luaTableToStruct(ltable, fv.Addr().Interface()) } case reflect.Ptr: if fv.IsNil() { fv.Set(reflect.New(fv.Type().Elem())) } return setFieldFromLua(fv.Elem(), lv) } return nil } // setSliceFromLuaTable converts a Lua table to a Go slice func setSliceFromLuaTable(fv reflect.Value, t *lua.LTable) error { length := t.Len() slice := reflect.MakeSlice(fv.Type(), length, length) for i := 1; i <= length; i++ { lv := t.RawGetInt(i) if lv == lua.LNil { continue } elem := slice.Index(i - 1) if err := setFieldFromLua(elem, lv); err != nil { return fmt.Errorf("index %d: %w", i, err) } } fv.Set(slice) return nil } // setMapFromLuaTable converts a Lua table to a Go map func setMapFromLuaTable(fv reflect.Value, t *lua.LTable) error { mapType := fv.Type() newMap := reflect.MakeMap(mapType) var convErr error t.ForEach(func(key, value lua.LValue) { if convErr != nil { return } // Convert key k := reflect.New(mapType.Key()).Elem() if err := setFieldFromLua(k, key); err != nil { convErr = fmt.Errorf("key conversion: %w", err) return } // Convert value v := reflect.New(mapType.Elem()).Elem() if err := setFieldFromLua(v, value); err != nil { convErr = fmt.Errorf("value conversion: %w", err) return } newMap.SetMapIndex(k, v) }) if convErr != nil { return convErr } fv.Set(newMap) return nil } // GoToLua converts a Go value to a Lua value func GoToLua(L *lua.LState, v any) lua.LValue { if v == nil { return lua.LNil } val := reflect.ValueOf(v) return goValueToLua(L, val) } // goValueToLua converts a reflect.Value to lua.LValue func goValueToLua(L *lua.LState, v reflect.Value) lua.LValue { if !v.IsValid() { return lua.LNil } // Dereference pointers and interfaces for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { if v.IsNil() { return lua.LNil } v = v.Elem() } switch v.Kind() { case reflect.Bool: return lua.LBool(v.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return lua.LNumber(v.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return lua.LNumber(v.Uint()) case reflect.Float32, reflect.Float64: return lua.LNumber(v.Float()) case reflect.String: return lua.LString(v.String()) case reflect.Slice, reflect.Array: table := L.NewTable() for i := 0; i < v.Len(); i++ { table.Append(goValueToLua(L, v.Index(i))) } return table case reflect.Map: table := L.NewTable() iter := v.MapRange() for iter.Next() { key := iter.Key() val := goValueToLua(L, iter.Value()) // For string keys, use RawSetString for better Lua compatibility if key.Kind() == reflect.String { table.RawSetString(key.String(), val) } else { // For non-string keys, convert and use RawSet luaKey := goValueToLua(L, key) table.RawSet(luaKey, val) } } return table case reflect.Struct: table := L.NewTable() t := v.Type() for i := 0; i < v.NumField(); i++ { field := t.Field(i) // Skip unexported fields if !field.IsExported() { continue } // Get lua tag or use field name luaKey := field.Tag.Get("lua") if luaKey == "" { luaKey = field.Name } fieldValue := goValueToLua(L, v.Field(i)) table.RawSetString(luaKey, fieldValue) } return table default: return lua.LNil } }