package main import ( "reflect" "slices" "fmt" "runtime/cgo" "time" "errors" ) const LUA_MULTRET = -1 // Require loads and executes the file pushing results onto the Lua stack. func (l *Lua) Require(file string) error { err := l.LoadFile(file) if err != nil { return errors.New("could not open the file:\n" + err.Error()) } err = l.PCall(0, LUA_MULTRET, 1) if err != nil { return errors.New("could not execute the file:\n" + err.Error()) } return nil } // PushObject recursively pushes string->any Go table onto the stack. func (l *Lua) PushObject(table map[string]any) error { l.CreateTable(len(table)) for k, v := range table { err := l.PushAny(v) if err != nil { return err } l.SetTableItem(k) } return nil } // PushAny pushes value v onto the stack. func (l *Lua) PushAny(v any) error { switch v.(type) { case nil: l.PushNil() case string: v, _ := v.(string) l.PushString(v) case func (l *Lua) int: v, _ := v.(func (l *Lua) int) l.PushGoFunction(v) case int: v, _ := v.(int) l.PushNumber(v) case int64: v, _ := v.(int64) l.PushNumber(int(v)) case float64: v, _ := v.(float64) l.PushFloatNumber(v) case bool: v, _ := v.(bool) l.PushBoolean(v) case map[string]any: v, _ := v.(map[string]any) err := l.PushObject(v) if err != nil { return fmt.Errorf("object push error: ", err) } case []any: v, _ := v.([]any) err := l.PushArray(v) if err != nil { return fmt.Errorf("array push error: ", err) } case time.Time: v, _ := v.(time.Time) l.PushString(v.Format(time.DateTime)) default: return fmt.Errorf("unsupported value type: %T", v) } 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) // 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 } // 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 () { before := l.StackLen() return func () { after := l.StackLen() diff := after - before if diff == 0 { return } else if diff < 0 { msg := fmt.Sprintf( "too many stack pops: len before: %d, after: %d\n", before, after, ) panic(msg) } l.SetTop(before) } }