github.com/k14s/starlark-go@v0.0.0-20200720175618-3a5c849cc368/starlark/unpack.go (about) 1 package starlark 2 3 // This file defines the Unpack helper functions used by 4 // built-in functions to interpret their call arguments. 5 6 import ( 7 "fmt" 8 "log" 9 "reflect" 10 "strings" 11 ) 12 13 // UnpackArgs unpacks the positional and keyword arguments into the 14 // supplied parameter variables. pairs is an alternating list of names 15 // and pointers to variables. 16 // 17 // If the variable is a bool, int, string, *List, *Dict, Callable, 18 // Iterable, or user-defined implementation of Value, 19 // UnpackArgs performs the appropriate type check. 20 // An int uses the AsInt32 check. 21 // If the parameter name ends with "?", 22 // it and all following parameters are optional. 23 // 24 // If the variable implements Value, UnpackArgs may call 25 // its Type() method while constructing the error message. 26 // 27 // Beware: an optional *List, *Dict, Callable, Iterable, or Value variable that is 28 // not assigned is not a valid Starlark Value, so the caller must 29 // explicitly handle such cases by interpreting nil as None or some 30 // computed default. 31 func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{}) error { 32 nparams := len(pairs) / 2 33 var defined intset 34 defined.init(nparams) 35 36 paramName := func(x interface{}) string { // (no free variables) 37 name := x.(string) 38 if name[len(name)-1] == '?' { 39 name = name[:len(name)-1] 40 } 41 return name 42 } 43 44 // positional arguments 45 if len(args) > nparams { 46 return fmt.Errorf("%s: got %d arguments, want at most %d", 47 fnname, len(args), nparams) 48 } 49 for i, arg := range args { 50 defined.set(i) 51 if err := unpackOneArg(arg, pairs[2*i+1]); err != nil { 52 name := paramName(pairs[2*i]) 53 return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) 54 } 55 } 56 57 // keyword arguments 58 kwloop: 59 for _, item := range kwargs { 60 name, arg := item[0].(String), item[1] 61 for i := 0; i < nparams; i++ { 62 if paramName(pairs[2*i]) == string(name) { 63 // found it 64 if defined.set(i) { 65 return fmt.Errorf("%s: got multiple values for keyword argument %s", 66 fnname, name) 67 } 68 ptr := pairs[2*i+1] 69 if err := unpackOneArg(arg, ptr); err != nil { 70 return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) 71 } 72 continue kwloop 73 } 74 } 75 return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name) 76 } 77 78 // Check that all non-optional parameters are defined. 79 // (We needn't check the first len(args).) 80 for i := len(args); i < nparams; i++ { 81 name := pairs[2*i].(string) 82 if strings.HasSuffix(name, "?") { 83 break // optional 84 } 85 if !defined.get(i) { 86 return fmt.Errorf("%s: missing argument for %s", fnname, name) 87 } 88 } 89 90 return nil 91 } 92 93 // UnpackPositionalArgs unpacks the positional arguments into 94 // corresponding variables. Each element of vars is a pointer; see 95 // UnpackArgs for allowed types and conversions. 96 // 97 // UnpackPositionalArgs reports an error if the number of arguments is 98 // less than min or greater than len(vars), if kwargs is nonempty, or if 99 // any conversion fails. 100 func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, vars ...interface{}) error { 101 if len(kwargs) > 0 { 102 return fmt.Errorf("%s: unexpected keyword arguments", fnname) 103 } 104 max := len(vars) 105 if len(args) < min { 106 var atleast string 107 if min < max { 108 atleast = "at least " 109 } 110 return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atleast, min) 111 } 112 if len(args) > max { 113 var atmost string 114 if max > min { 115 atmost = "at most " 116 } 117 return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atmost, max) 118 } 119 for i, arg := range args { 120 if err := unpackOneArg(arg, vars[i]); err != nil { 121 return fmt.Errorf("%s: for parameter %d: %s", fnname, i+1, err) 122 } 123 } 124 return nil 125 } 126 127 func unpackOneArg(v Value, ptr interface{}) error { 128 // On failure, don't clobber *ptr. 129 switch ptr := ptr.(type) { 130 case *Value: 131 *ptr = v 132 case *string: 133 s, ok := AsString(v) 134 if !ok { 135 return fmt.Errorf("got %s, want string", v.Type()) 136 } 137 *ptr = s 138 case *bool: 139 b, ok := v.(Bool) 140 if !ok { 141 return fmt.Errorf("got %s, want bool", v.Type()) 142 } 143 *ptr = bool(b) 144 case *int: 145 i, err := AsInt32(v) 146 if err != nil { 147 return err 148 } 149 *ptr = i 150 case **List: 151 list, ok := v.(*List) 152 if !ok { 153 return fmt.Errorf("got %s, want list", v.Type()) 154 } 155 *ptr = list 156 case **Dict: 157 dict, ok := v.(*Dict) 158 if !ok { 159 return fmt.Errorf("got %s, want dict", v.Type()) 160 } 161 *ptr = dict 162 case *Callable: 163 f, ok := v.(Callable) 164 if !ok { 165 return fmt.Errorf("got %s, want callable", v.Type()) 166 } 167 *ptr = f 168 case *Iterable: 169 it, ok := v.(Iterable) 170 if !ok { 171 return fmt.Errorf("got %s, want iterable", v.Type()) 172 } 173 *ptr = it 174 default: 175 // v must have type *V, where V is some subtype of starlark.Value. 176 ptrv := reflect.ValueOf(ptr) 177 if ptrv.Kind() != reflect.Ptr { 178 log.Panicf("internal error: not a pointer: %T", ptr) 179 } 180 paramVar := ptrv.Elem() 181 if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) { 182 // The value is not assignable to the variable. 183 184 // Detect a possible bug in the Go program that called Unpack: 185 // If the variable *ptr is not a subtype of Value, 186 // no value of v can possibly work. 187 if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) { 188 log.Panicf("pointer element type does not implement Value: %T", ptr) 189 } 190 191 // Report Starlark dynamic type error. 192 // 193 // We prefer the Starlark Value.Type name over 194 // its Go reflect.Type name, but calling the 195 // Value.Type method on the variable is not safe 196 // in general. If the variable is an interface, 197 // the call will fail. Even if the variable has 198 // a concrete type, it might not be safe to call 199 // Type() on a zero instance. Thus we must use 200 // recover. 201 202 // Default to Go reflect.Type name 203 paramType := paramVar.Type().String() 204 205 // Attempt to call Value.Type method. 206 func() { 207 defer func() { recover() }() 208 paramType = paramVar.MethodByName("Type").Call(nil)[0].String() 209 }() 210 return fmt.Errorf("got %s, want %s", v.Type(), paramType) 211 } 212 paramVar.Set(reflect.ValueOf(v)) 213 } 214 return nil 215 } 216 217 type intset struct { 218 small uint64 // bitset, used if n < 64 219 large map[int]bool // set, used if n >= 64 220 } 221 222 func (is *intset) init(n int) { 223 if n >= 64 { 224 is.large = make(map[int]bool) 225 } 226 } 227 228 func (is *intset) set(i int) (prev bool) { 229 if is.large == nil { 230 prev = is.small&(1<<uint(i)) != 0 231 is.small |= 1 << uint(i) 232 } else { 233 prev = is.large[i] 234 is.large[i] = true 235 } 236 return 237 } 238 239 func (is *intset) get(i int) bool { 240 if is.large == nil { 241 return is.small&(1<<uint(i)) != 0 242 } 243 return is.large[i] 244 } 245 246 func (is *intset) len() int { 247 if is.large == nil { 248 // Suboptimal, but used only for error reporting. 249 len := 0 250 for i := 0; i < 64; i++ { 251 if is.small&(1<<uint(i)) != 0 { 252 len++ 253 } 254 } 255 return len 256 } 257 return len(is.large) 258 }