github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/actions/lua/util/deep_pull.go (about) 1 package util 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/Shopify/go-lua" 8 ) 9 10 var ( 11 ErrCannotPull = errors.New("cannot pull go type into lua") 12 ErrStackExhausted = errors.New("pull table, stack exhausted") 13 ) 14 15 func Open(l *lua.State) { 16 l.Register("array", luaArray) 17 } 18 19 func PullStringTable(l *lua.State, idx int) (map[string]string, error) { 20 if !l.IsTable(idx) { 21 return nil, fmt.Errorf("need a table at index %d, got %s: %w", idx, lua.TypeNameOf(l, idx), ErrCannotPull) 22 } 23 24 // Table at idx 25 l.PushNil() // Add free slot for the value, +1 26 27 table := make(map[string]string) 28 // -1:nil, idx:table 29 for l.Next(idx) { 30 // -1:val, -2:key, idx:table 31 key, ok := l.ToString(-2) 32 if !ok { 33 return nil, fmt.Errorf("key should be a string (%v): %w", l.ToValue(-2), ErrCannotPull) 34 } 35 val, ok := l.ToString(-1) 36 if !ok { 37 return nil, fmt.Errorf("value for key '%s' should be a string (%v): %w", key, l.ToValue(-1), ErrCannotPull) 38 } 39 table[key] = val 40 l.Pop(1) // remove val from top, -1 41 // -1:key, idx: table 42 } 43 44 return table, nil 45 } 46 47 func PullTable(l *lua.State, idx int) (interface{}, error) { 48 if !l.IsTable(idx) { 49 return nil, fmt.Errorf("need a table at index %d, got %s: %w", idx, lua.TypeNameOf(l, idx), ErrCannotPull) 50 } 51 52 return pullTableRec(l, idx) 53 } 54 55 func pullTableRec(l *lua.State, idx int) (interface{}, error) { 56 if !l.CheckStack(2) { 57 return nil, ErrStackExhausted 58 } 59 60 idx = l.AbsIndex(idx) 61 if isArray(l, idx) { 62 return pullArrayRec(l, idx) 63 } 64 65 table := make(map[string]interface{}) 66 67 l.PushNil() 68 for l.Next(idx) { 69 // -1: value, -2: key, ..., idx: table 70 key, ok := l.ToString(-2) 71 if !ok { 72 err := fmt.Errorf("key should be a string (%s): %w", lua.TypeNameOf(l, -2), ErrCannotPull) 73 l.Pop(2) 74 return nil, err 75 } 76 77 value, err := toGoValue(l, -1) 78 if err != nil { 79 l.Pop(2) 80 return nil, err 81 } 82 83 table[key] = value 84 85 l.Pop(1) 86 } 87 88 return table, nil 89 } 90 91 const arrayMarkerField = "_is_array" 92 93 func luaArray(l *lua.State) int { 94 l.NewTable() 95 l.PushBoolean(true) 96 l.SetField(-2, arrayMarkerField) 97 l.SetMetaTable(-2) 98 return 1 99 } 100 101 func isArray(l *lua.State, idx int) bool { 102 if !l.IsTable(idx) { 103 return false 104 } 105 106 if !lua.MetaField(l, idx, arrayMarkerField) { 107 return false 108 } 109 defer l.Pop(1) 110 111 return l.ToBoolean(-1) 112 } 113 114 func pullArrayRec(l *lua.State, idx int) (interface{}, error) { 115 table := make([]interface{}, lua.LengthEx(l, idx)) 116 117 l.PushNil() 118 for l.Next(idx) { 119 k, ok := l.ToInteger(-2) 120 if !ok { 121 l.Pop(2) 122 return nil, fmt.Errorf("pull array: expected numeric index, got '%s': %w", l.TypeOf(-2), ErrCannotPull) 123 } 124 125 v, err := toGoValue(l, -1) 126 if err != nil { 127 l.Pop(2) 128 return nil, err 129 } 130 131 table[k-1] = v 132 l.Pop(1) 133 } 134 135 return table, nil 136 } 137 138 func toGoValue(l *lua.State, idx int) (interface{}, error) { 139 t := l.TypeOf(idx) 140 switch t { 141 case lua.TypeBoolean: 142 return l.ToBoolean(idx), nil 143 case lua.TypeString: 144 return lua.CheckString(l, idx), nil 145 case lua.TypeNumber: 146 return lua.CheckNumber(l, idx), nil 147 case lua.TypeTable: 148 return pullTableRec(l, idx) 149 default: 150 err := fmt.Errorf("pull table, unsupported type %s: %w", lua.TypeNameOf(l, idx), ErrCannotPull) 151 return nil, err 152 } 153 }