src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/vals/conversion.go (about) 1 package vals 2 3 import ( 4 "errors" 5 "fmt" 6 "math/big" 7 "reflect" 8 "strconv" 9 "sync" 10 "unicode/utf8" 11 12 "src.elv.sh/pkg/eval/errs" 13 "src.elv.sh/pkg/strutil" 14 ) 15 16 // Conversion between "Go values" (those expected by native Go functions) and 17 // "Elvish values" (those participating in the Elvish runtime). 18 // 19 // Among the conversion functions, [ScanToGo] and [FromGo] implement the 20 // implicit conversion used when calling native Go functions from Elvish. The 21 // API is asymmetric; this has to do with two characteristics of Elvish's type 22 // system: 23 // 24 // - Elvish doesn't have a dedicated rune type and uses strings to represent 25 // them. 26 // 27 // - Elvish permits using strings that look like numbers in place of numbers. 28 // 29 // As a result, while FromGo can always convert a "Go value" to an "Elvish 30 // value" unambiguously, ScanToGo can't do that in the opposite direction. 31 // For example, "1" may be converted into "1", '1' or 1, depending on what 32 // the destination type is, and the process may fail. Thus ScanToGo takes the 33 // pointer to the destination as an argument, and returns an error. 34 // 35 // The rest of the conversion functions are exported for use in more 36 // sophisticated binding code, and need to explicitly invoked. 37 38 // WrongType is returned by ScanToGo if the source value doesn't have a 39 // compatible type. 40 type WrongType struct { 41 wantKind string 42 gotKind string 43 } 44 45 // Error implements the error interface. 46 func (err WrongType) Error() string { 47 return fmt.Sprintf("wrong type: need %s, got %s", err.wantKind, err.gotKind) 48 } 49 50 type cannotParseAs struct { 51 want string 52 repr string 53 } 54 55 func (err cannotParseAs) Error() string { 56 return fmt.Sprintf("cannot parse as %s: %s", err.want, err.repr) 57 } 58 59 var ( 60 errMustBeString = errors.New("must be string") 61 errMustBeValidUTF8 = errors.New("must be valid UTF-8") 62 errMustHaveSingleRune = errors.New("must have a single rune") 63 errMustBeNumber = errors.New("must be number") 64 errMustBeInteger = errors.New("must be integer") 65 ) 66 67 // CallableType should be set from the eval package to the type of 68 // eval.Callable. It is not initialized here to avoid creating an import cycle. 69 var CallableType reflect.Type 70 71 // ScanToGo converts an Elvish value, and stores it in the destination of ptr, 72 // which must be a pointer. 73 // 74 // If ptr has type *int, *float64, *Num or *rune, it performs a suitable 75 // conversion, and returns an error if the conversion fails. In other cases, 76 // this function just tries to perform "*ptr = src" via reflection and returns 77 // an error if the assignment can't be done. 78 func ScanToGo(src any, ptr any) error { 79 switch ptr := ptr.(type) { 80 case *int: 81 i, err := elvToInt(src) 82 if err == nil { 83 *ptr = i 84 } 85 return err 86 case *float64: 87 n, err := elvToNum(src) 88 if err == nil { 89 *ptr = ConvertToFloat64(n) 90 } 91 return err 92 case *Num: 93 n, err := elvToNum(src) 94 if err == nil { 95 *ptr = n 96 } 97 return err 98 case *rune: 99 r, err := elvToRune(src) 100 if err == nil { 101 *ptr = r 102 } 103 return err 104 default: 105 // Do a generic `*ptr = src` via reflection 106 ptrType := TypeOf(ptr) 107 if ptrType.Kind() != reflect.Ptr { 108 return fmt.Errorf("internal bug: need pointer to scan to, got %T", ptr) 109 } 110 dstType := ptrType.Elem() 111 if dstType == CallableType && ValueOf(src) == ValueOf(nil) { 112 // Allow using the nil value of an empty interface (which is the 113 // value of Elvish's $nil) as a Callable. 114 ValueOf(ptr).Elem().Set(reflect.Zero(dstType)) 115 return nil 116 } 117 if !TypeOf(src).AssignableTo(dstType) { 118 var dstKind string 119 if dstType.Kind() == reflect.Interface { 120 dstKind = "!!" + dstType.String() 121 } else { 122 dstKind = Kind(reflect.Zero(dstType).Interface()) 123 } 124 return WrongType{dstKind, Kind(src)} 125 } 126 ValueOf(ptr).Elem().Set(ValueOf(src)) 127 return nil 128 } 129 } 130 131 func elvToInt(arg any) (int, error) { 132 switch arg := arg.(type) { 133 case int: 134 return arg, nil 135 case string: 136 num, err := strconv.ParseInt(arg, 0, 0) 137 if err == nil { 138 return int(num), nil 139 } 140 return 0, cannotParseAs{"integer", ReprPlain(arg)} 141 default: 142 return 0, errMustBeInteger 143 } 144 } 145 146 func elvToNum(arg any) (Num, error) { 147 switch arg := arg.(type) { 148 case int, *big.Int, *big.Rat, float64: 149 return arg, nil 150 case string: 151 n := ParseNum(arg) 152 if n == nil { 153 return 0, cannotParseAs{"number", ReprPlain(arg)} 154 } 155 return n, nil 156 default: 157 return 0, errMustBeNumber 158 } 159 } 160 161 func elvToRune(arg any) (rune, error) { 162 ss, ok := arg.(string) 163 if !ok { 164 return -1, errMustBeString 165 } 166 s := ss 167 r, size := utf8.DecodeRuneInString(s) 168 if r == utf8.RuneError { 169 return -1, errMustBeValidUTF8 170 } 171 if size != len(s) { 172 return -1, errMustHaveSingleRune 173 } 174 return r, nil 175 } 176 177 // ScanListToGo converts a List to a slice, using ScanToGo to convert each 178 // element. 179 func ScanListToGo(src List, ptr any) error { 180 n := src.Len() 181 values := reflect.MakeSlice(reflect.TypeOf(ptr).Elem(), n, n) 182 i := 0 183 for it := src.Iterator(); it.HasElem(); it.Next() { 184 err := ScanToGo(it.Elem(), values.Index(i).Addr().Interface()) 185 if err != nil { 186 return err 187 } 188 i++ 189 } 190 reflect.ValueOf(ptr).Elem().Set(values) 191 return nil 192 } 193 194 // Optional wraps the last pointer passed to ScanListElementsToGo, to indicate 195 // that it is optional. 196 func Optional(ptr any) any { return optional{ptr} } 197 198 type optional struct{ ptr any } 199 200 // ScanListElementsToGo unpacks elements from a list, storing the each element 201 // in the given pointers with ScanToGo. 202 // 203 // The last pointer may be wrapped with Optional to indicate that it is 204 // optional. 205 func ScanListElementsToGo(src List, ptrs ...any) error { 206 if o, ok := ptrs[len(ptrs)-1].(optional); ok { 207 switch src.Len() { 208 case len(ptrs) - 1: 209 ptrs = ptrs[:len(ptrs)-1] 210 case len(ptrs): 211 ptrs[len(ptrs)-1] = o.ptr 212 default: 213 return errs.ArityMismatch{What: "list elements", 214 ValidLow: len(ptrs) - 1, ValidHigh: len(ptrs), Actual: src.Len()} 215 } 216 } else if src.Len() != len(ptrs) { 217 return errs.ArityMismatch{What: "list elements", 218 ValidLow: len(ptrs), ValidHigh: len(ptrs), Actual: src.Len()} 219 } 220 221 i := 0 222 for it := src.Iterator(); it.HasElem(); it.Next() { 223 err := ScanToGo(it.Elem(), ptrs[i]) 224 if err != nil { 225 return err 226 } 227 i++ 228 } 229 return nil 230 } 231 232 // ScanMapToGo scans map elements into ptr, which must be a pointer to a struct. 233 // Struct field names are converted to map keys with CamelToDashed. 234 // 235 // The map may contains keys that don't correspond to struct fields, and it 236 // doesn't have to contain all keys that correspond to struct fields. 237 func ScanMapToGo(src Map, ptr any) error { 238 // Iterate over the struct keys instead of the map: since extra keys are 239 // allowed, the map may be very big, while the size of the struct is bound. 240 keys, _ := StructFieldsInfo(reflect.TypeOf(ptr).Elem()) 241 structValue := reflect.ValueOf(ptr).Elem() 242 for i, key := range keys { 243 if key == "" { 244 continue 245 } 246 val, ok := src.Index(key) 247 if !ok { 248 continue 249 } 250 err := ScanToGo(val, structValue.Field(i).Addr().Interface()) 251 if err != nil { 252 return err 253 } 254 } 255 return nil 256 } 257 258 // StructFieldsInfo takes a type for a struct, and returns a slice for each 259 // field name, converted with CamelToDashed, and a reverse index. Unexported 260 // fields result in an empty string in the slice, and is omitted from the 261 // reverse index. 262 func StructFieldsInfo(t reflect.Type) ([]string, map[string]int) { 263 if info, ok := structFieldsInfoCache.Load(t); ok { 264 info := info.(structFieldsInfo) 265 return info.keys, info.keyIdx 266 } 267 info := makeStructFieldsInfo(t) 268 structFieldsInfoCache.Store(t, info) 269 return info.keys, info.keyIdx 270 } 271 272 var structFieldsInfoCache sync.Map 273 274 type structFieldsInfo struct { 275 keys []string 276 keyIdx map[string]int 277 } 278 279 func makeStructFieldsInfo(t reflect.Type) structFieldsInfo { 280 keys := make([]string, t.NumField()) 281 keyIdx := make(map[string]int) 282 for i := 0; i < t.NumField(); i++ { 283 field := t.Field(i) 284 if field.PkgPath != "" { 285 continue 286 } 287 key := strutil.CamelToDashed(field.Name) 288 keyIdx[key] = i 289 keys[i] = key 290 } 291 return structFieldsInfo{keys, keyIdx} 292 } 293 294 // FromGo converts a Go value to an Elvish value. 295 // 296 // Exact numbers are normalized to the smallest types that can hold them, and 297 // runes are converted to strings. Values of other types are returned unchanged. 298 func FromGo(a any) any { 299 switch a := a.(type) { 300 case *big.Int: 301 return NormalizeBigInt(a) 302 case *big.Rat: 303 return NormalizeBigRat(a) 304 case rune: 305 return string(a) 306 default: 307 return a 308 } 309 }