func copyTableToMap(L *lua.State, t reflect.Type, idx int, visited map[uintptr]interface{}) interface{} { if t == nil { t = tmap } te, tk := t.Elem(), t.Key() m := reflect.MakeMap(t) // See copyTableToSlice. ptr := L.ToPointer(idx) if !luaIsEmpty(L, idx) { visited[ptr] = m.Interface() } L.PushNil() if idx < 0 { idx-- } for L.Next(idx) != 0 { // key at -2, value at -1 key := reflect.ValueOf(luaToGo(L, tk, -2, visited)) val := reflect.ValueOf(luaToGo(L, te, -1, visited)) if val.Interface() == nullv.Interface() { val = reflect.Zero(te) } m.SetMapIndex(key, val) L.Pop(1) } return m.Interface() }
func copyTableToStruct(L *lua.State, t reflect.Type, idx int, visited map[uintptr]interface{}) interface{} { if t == nil { RaiseError(L, "type argument must be non-nill") } wasPtr := t.Kind() == reflect.Ptr if wasPtr { t = t.Elem() } s := reflect.New(t) // T -> *T ref := s.Elem() // See copyTableToSlice. ptr := L.ToPointer(idx) if !luaIsEmpty(L, idx) { if wasPtr { visited[ptr] = s.Interface() } else { visited[ptr] = s.Elem().Interface() } } // Associate Lua keys with Go fields: tags have priority over matching field // name. fields := map[string]string{} st := ref.Type() for i := 0; i < ref.NumField(); i++ { field := st.Field(i) tag := field.Tag.Get("lua") if tag != "" { fields[tag] = field.Name continue } fields[field.Name] = field.Name } L.PushNil() if idx < 0 { idx-- } for L.Next(idx) != 0 { key := L.ToString(-2) f := ref.FieldByName(fields[key]) if f.CanSet() && f.IsValid() { val := reflect.ValueOf(luaToGo(L, f.Type(), -1, visited)) f.Set(val) } L.Pop(1) } if wasPtr { return s.Interface() } return s.Elem().Interface() }
// Also for arrays. func copyTableToSlice(L *lua.State, t reflect.Type, idx int, visited map[uintptr]interface{}) interface{} { if t == nil { t = tslice } ref := t // There is probably no point at accepting more than one level of dreference. if t.Kind() == reflect.Ptr { t = t.Elem() } n := int(L.ObjLen(idx)) var slice reflect.Value if t.Kind() == reflect.Array { slice = reflect.New(t) slice = slice.Elem() } else { slice = reflect.MakeSlice(t, n, n) } // Do not add empty slices to the list of visited elements. // The empty Lua table is a single instance object and gets re-used across maps, slices and others. if n > 0 { ptr := L.ToPointer(idx) if ref.Kind() == reflect.Ptr { visited[ptr] = slice.Addr().Interface() } else { visited[ptr] = slice.Interface() } } te := t.Elem() for i := 1; i <= n; i++ { L.RawGeti(idx, i) val := reflect.ValueOf(luaToGo(L, te, -1, visited)) if val.Interface() == nullv.Interface() { val = reflect.Zero(te) } slice.Index(i - 1).Set(val) L.Pop(1) } if ref.Kind() == reflect.Ptr { return slice.Addr().Interface() } return slice.Interface() }
func luaToGo(L *lua.State, t reflect.Type, idx int, visited map[uintptr]interface{}) interface{} { var value interface{} var kind reflect.Kind if t != nil { if t.Kind() == reflect.Ptr { kind = t.Elem().Kind() } else if t.Kind() == reflect.Interface { // Let the Lua type drive the conversion. t = nil } else { kind = t.Kind() } } switch L.Type(idx) { case lua.LUA_TNIL: if t == nil { return nil } switch kind { default: value = reflect.Zero(t).Interface() } case lua.LUA_TBOOLEAN: if t == nil { kind = reflect.Bool } switch kind { case reflect.Bool: ptr := new(bool) *ptr = L.ToBoolean(idx) value = *ptr default: value = reflect.Zero(t).Interface() } case lua.LUA_TSTRING: if t == nil { kind = reflect.String } switch kind { case reflect.String: tos := L.ToString(idx) ptr := new(string) *ptr = tos value = *ptr default: value = reflect.Zero(t).Interface() } case lua.LUA_TNUMBER: if t == nil { // Infering the type here (e.g. int if round value) would break backward // compatibility and drift away from Lua's design: the numeric type is // specified at compile time. kind = reflect.Float64 } switch kind { case reflect.Float64: ptr := new(float64) *ptr = L.ToNumber(idx) value = *ptr case reflect.Float32: ptr := new(float32) *ptr = float32(L.ToNumber(idx)) value = *ptr case reflect.Int: ptr := new(int) *ptr = int(L.ToNumber(idx)) value = *ptr case reflect.Int8: ptr := new(int8) *ptr = int8(L.ToNumber(idx)) value = *ptr case reflect.Int16: ptr := new(int16) *ptr = int16(L.ToNumber(idx)) value = *ptr case reflect.Int32: ptr := new(int32) *ptr = int32(L.ToNumber(idx)) value = *ptr case reflect.Int64: ptr := new(int64) *ptr = int64(L.ToNumber(idx)) value = *ptr case reflect.Uint: ptr := new(uint) *ptr = uint(L.ToNumber(idx)) value = *ptr case reflect.Uint8: ptr := new(uint8) *ptr = uint8(L.ToNumber(idx)) value = *ptr case reflect.Uint16: ptr := new(uint16) *ptr = uint16(L.ToNumber(idx)) value = *ptr case reflect.Uint32: ptr := new(uint32) *ptr = uint32(L.ToNumber(idx)) value = *ptr case reflect.Uint64: ptr := new(uint64) *ptr = uint64(L.ToNumber(idx)) value = *ptr default: value = reflect.Zero(t).Interface() } case lua.LUA_TTABLE: if t == nil { kind = reflect.Interface } fallthrough default: istable := L.IsTable(idx) // If we don't know the type and the Lua object is userdata, // then it might be a proxy for a Go object. Otherwise wrap // it up as a LuaObject. if t == nil && !istable { if isValueProxy(L, idx) { v, _ := valueOfProxy(L, idx) return v.Interface() } return NewLuaObject(L, idx) } switch kind { case reflect.Array: if istable { ptr := L.ToPointer(idx) if val, ok := visited[ptr]; ok { return val } value = copyTableToSlice(L, t, idx, visited) } else { value = mustUnwrapProxy(L, idx) } case reflect.Slice: // if we get a table, then copy its values to a new slice if istable { ptr := L.ToPointer(idx) if val, ok := visited[ptr]; ok { return val } value = copyTableToSlice(L, t, idx, visited) } else { value = mustUnwrapProxy(L, idx) } case reflect.Map: if istable { ptr := L.ToPointer(idx) if val, ok := visited[ptr]; ok { return val } value = copyTableToMap(L, t, idx, visited) } else { value = mustUnwrapProxy(L, idx) } case reflect.Struct: if istable { ptr := L.ToPointer(idx) if val, ok := visited[ptr]; ok { return val } value = copyTableToStruct(L, t, idx, visited) } else { value = mustUnwrapProxy(L, idx) } case reflect.Interface: if istable { ptr := L.ToPointer(idx) if val, ok := visited[ptr]; ok { return val } // We have to make an executive decision here: tables with non-zero // length are assumed to be slices! if L.ObjLen(idx) > 0 { value = copyTableToSlice(L, nil, idx, visited) } else { value = copyTableToMap(L, nil, idx, visited) } } else if L.IsNumber(idx) { value = L.ToNumber(idx) } else if L.IsString(idx) { value = L.ToString(idx) } else if L.IsBoolean(idx) { value = L.ToBoolean(idx) } else if L.IsNil(idx) { return nil } else { value = mustUnwrapProxy(L, idx) } default: value = mustUnwrapProxy(L, idx) } } return value }