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