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.go | 113 +++-------------------------------------------- lua_common.go | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ luajit.go | 113 +++-------------------------------------------- 3 files changed, 149 insertions(+), 216 deletions(-) create mode 100644 lua_common.go diff --git a/lua.go b/lua.go index f90ba38..fb0b72a 100644 --- a/lua.go +++ b/lua.go @@ -25,9 +25,7 @@ import "C" import ( "errors" "fmt" - "reflect" "runtime/cgo" - "slices" "time" "unsafe" ) @@ -205,6 +203,11 @@ func (l *Lua) GetTableItem(key string) { C.lua_getfield(l.l, -1, C.CString(key)) } +// RawGetI pushes onto the stack value from table from tableOffset at index i. +func (l *Lua) RawGetI(tableOffset, i int) { + C.lua_rawgeti(l.l, C.int(tableOffset), C.longlong(i)) +} + // PushAny pushes value v onto the stack. func (l *Lua) PushAny(v any) error { switch v.(type) { @@ -348,112 +351,6 @@ func (l *Lua) LoadAndCall(code string) error { return nil } -// 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) - l.PushNil() - for l.Next() { - 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) - } - l.Pop(1) - } else { - return fmt.Errorf("unknown var type: %s", t) - } - } - return nil -} - // RestoreStackFunc remembers the Lua stack size and then restores it when a // returned function is called. It's a helper function to avoid stack leakage. func (l *Lua) RestoreStackFunc() func () { 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 +} diff --git a/luajit.go b/luajit.go index 40b91ba..0c51f13 100644 --- a/luajit.go +++ b/luajit.go @@ -25,9 +25,7 @@ import "C" import ( "errors" "fmt" - "reflect" "runtime/cgo" - "slices" "time" "unsafe" ) @@ -199,6 +197,11 @@ func (l *Lua) GetTableItem(key string) { C.lua_getfield(l.l, -1, C.CString(key)) } +// RawGetI pushes onto the stack value from table from tableOffset at index i. +func (l *Lua) RawGetI(tableOffset, i int) { + C.lua_rawgeti(l.l, C.int(tableOffset), C.int(i)) +} + // PushAny pushes value v onto the stack. func (l *Lua) PushAny(v any) error { switch v.(type) { @@ -341,112 +344,6 @@ func (l *Lua) LoadAndCall(code string) error { return nil } -// 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) - l.PushNil() - for l.Next() { - 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) - } - l.Pop(1) - } else { - return fmt.Errorf("unknown var type: %s", t) - } - } - return nil -} - // RestoreStackFunc remembers the Lua stack size and then restores it when a // returned function is called. It's a helper function to avoid stack leakage. func (l *Lua) RestoreStackFunc() func () { -- cgit v1.2.3