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