From c26739ce130bc40ded6ff9dfea1be1eba1a2e4eb Mon Sep 17 00:00:00 2001 From: unwox Date: Tue, 9 Sep 2025 16:37:32 +0600 Subject: support ntables in Lua.Scan --- lua_common.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 lua_common.go (limited to 'lua_common.go') diff --git a/lua_common.go b/lua_common.go new file mode 100644 index 0000000..4e58d69 --- /dev/null +++ b/lua_common.go @@ -0,0 +1,139 @@ +package main + +import ( + "reflect" + "slices" + "fmt" + "runtime/cgo" +) + +// Scan scans values from the Lua stack into vars according to their types. +func (l *Lua) Scan(vars ...any) error { + slices.Reverse(vars) + for i, v := range vars { + t := reflect.TypeOf(v) + // unwrap pointers + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + tk := t.Kind() + if t.Name() == "LuaRef" { + if l.IsFunction(-1) == false { + return fmt.Errorf( + "passed arg #%d must be function", + len(vars)-i, + ) + } + *v.(*LuaRef) = l.PopToRef() + } else if t.String() == "cgo.Handle" { + if l.IsNumber(-1) == false { + return fmt.Errorf( + "passed arg #%d must be Go handler", + len(vars)-i, + ) + } + *v.(*cgo.Handle) = cgo.Handle(uintptr(l.ToInt(-1))) + l.Pop(1) + } else if tk == reflect.String { + if l.IsString(-1) == false { + return fmt.Errorf( + "passed arg #%d must be string", + len(vars)-i, + ) + } + *v.(*string) = l.ToString(-1) + l.Pop(1) + } else if tk == reflect.Int { + if l.IsNumber(-1) == false { + return fmt.Errorf( + "passed arg #%d must be number", + len(vars)-i, + ) + } + *v.(*int) = l.ToInt(-1) + l.Pop(1) + } else if tk == reflect.Map && t.Key().Kind() == reflect.String { + // TODO: should be possible to use maps with any types + // of value, not only strings. + vm, _ := v.(*map[string]string) + l.PushNil() + for l.Next() { + if !l.IsString(-1) { + return fmt.Errorf( + "map arg #%d must only have string values", + len(vars)-i, + ) + } + if !l.IsString(-2) { + return fmt.Errorf( + "map arg #%d must only have string keys", + len(vars)-i, + ) + } + v := l.ToString(-1) + l.Pop(1) + // We must not pop the item key from the stack + // because otherwise C.lua_next won't work + // properly. + k := l.ToString(-1) + (*vm)[k] = v + } + l.Pop(1) + } else if tk == reflect.Slice && t.Elem().Kind() == reflect.Interface { + if !l.IsTable(-1) { + return fmt.Errorf( + "passed arg #%d must be a table", + len(vars)-i, + ) + } + va, _ := v.(*[]any) + + // Check if table contains element "n" with + // a count of elements. Useful when an array may + // contain nils. + l.GetTableItem("n") + n := 0 + if l.IsNumber(-1) { + n = l.ToInt(-1) + } + l.Pop(1) + + scanAndAppend := func () error { + var v any = nil + if l.IsString(-1) { + v = l.ToString(-1) + } else if l.IsNumber(-1) { + v = l.ToInt(-1) + } else if l.IsBoolean(-1) { + v = l.ToBoolean(-1) + } else if l.IsNil(-1) { + v = nil + } else { + return fmt.Errorf("unknown value in array") + } + l.Pop(1) + (*va) = append((*va), v) + return nil + } + if n > 0 { + for i := 0; i < n; i++ { + l.RawGetI(-1, i + 1) + if err := scanAndAppend(); err != nil { + return err + } + } + } else { + l.PushNil() + for l.Next() { + if err := scanAndAppend(); err != nil { + return err + } + } + } + l.Pop(1) + } else { + return fmt.Errorf("unknown var type: %s", t) + } + } + return nil +} -- cgit v1.2.3