github.com/elves/elvish@v0.15.0/pkg/eval/vals/conversion.go (about) 1 package vals 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "strconv" 8 "unicode/utf8" 9 ) 10 11 // Conversion between native and Elvish values. 12 // 13 // Elvish uses native Go types most of the time - string, bool, hashmap.Map, 14 // vector.Vector, etc., and there is no need for any conversions. There are some 15 // exceptions, for instance int and rune, since Elvish currently lacks integer 16 // types. 17 // 18 // There is a many-to-one relationship between Go types and Elvish types. A 19 // Go value can always be converted to an Elvish value unambiguously, but to 20 // convert an Elvish value into a Go value one must know the destination type 21 // first. For example, all of the Go values int(1), rune('1') and string("1") 22 // convert to Elvish "1"; conversely, Elvish "1" may be converted to any of the 23 // aforementioned three possible values, depending on the destination type. 24 // 25 // In future, Elvish may gain distinct types for integers and characters, making 26 // the examples above unnecessary; however, the conversion logic may not 27 // entirely go away, as there might always be some mismatch between Elvish's 28 // type system and Go's. 29 30 type wrongType struct { 31 wantKind string 32 gotKind string 33 } 34 35 func (err wrongType) Error() string { 36 return fmt.Sprintf("wrong type: need %s, got %s", err.wantKind, err.gotKind) 37 } 38 39 type cannotParseAs struct { 40 want string 41 repr string 42 } 43 44 func (err cannotParseAs) Error() string { 45 return fmt.Sprintf("cannot parse as %s: %s", err.want, err.repr) 46 } 47 48 var ( 49 errMustBeString = errors.New("must be string") 50 errMustBeValidUTF8 = errors.New("must be valid UTF-8") 51 errMustHaveSingleRune = errors.New("must have a single rune") 52 errMustBeNumber = errors.New("must be number") 53 errMustBeInteger = errors.New("must be integer") 54 ) 55 56 // ScanToGo converts an Elvish value to a Go value. the pointer points to. It 57 // uses the type of the pointer to determine the destination type, and puts the 58 // converted value in the location the pointer points to. Conversion only 59 // happens when the destination type is int, float64 or rune; in other cases, 60 // this function just checks that the source value is already assignable to the 61 // destination. 62 func ScanToGo(src interface{}, ptr interface{}) error { 63 switch ptr := ptr.(type) { 64 case *int: 65 i, err := elvToInt(src) 66 if err == nil { 67 *ptr = i 68 } 69 return err 70 case *float64: 71 f, err := elvToFloat(src) 72 if err == nil { 73 *ptr = f 74 } 75 return err 76 case *rune: 77 r, err := elvToRune(src) 78 if err == nil { 79 *ptr = r 80 } 81 return err 82 case Scanner: 83 return ptr.ScanElvish(src) 84 default: 85 // Do a generic `*ptr = src` via reflection 86 ptrType := TypeOf(ptr) 87 if ptrType.Kind() != reflect.Ptr { 88 return fmt.Errorf("internal bug: need pointer to scan to, got %T", ptr) 89 } 90 dstType := ptrType.Elem() 91 if !TypeOf(src).AssignableTo(dstType) { 92 return wrongType{Kind(reflect.Zero(dstType).Interface()), Kind(src)} 93 } 94 ValueOf(ptr).Elem().Set(ValueOf(src)) 95 return nil 96 } 97 } 98 99 // Scanner is implemented by types that can scan an Elvish value into itself. 100 type Scanner interface { 101 ScanElvish(interface{}) error 102 } 103 104 // FromGo converts a Go value to an Elvish value. Conversion happens when the 105 // argument is int, float64 or rune (this is consistent with ScanToGo). In other 106 // cases, this function just returns the argument. 107 func FromGo(a interface{}) interface{} { 108 switch a := a.(type) { 109 case int: 110 return strconv.Itoa(a) 111 case rune: 112 return string(a) 113 default: 114 return a 115 } 116 } 117 118 func elvToFloat(arg interface{}) (float64, error) { 119 switch arg := arg.(type) { 120 case float64: 121 return arg, nil 122 case string: 123 f, err := strconv.ParseFloat(arg, 64) 124 if err == nil { 125 return f, nil 126 } 127 i, err := strconv.ParseInt(arg, 0, 64) 128 if err == nil { 129 return float64(i), err 130 } 131 return 0, cannotParseAs{"number", Repr(arg, -1)} 132 default: 133 return 0, errMustBeNumber 134 } 135 } 136 137 func elvToInt(arg interface{}) (int, error) { 138 switch arg := arg.(type) { 139 case float64: 140 i := int(arg) 141 if float64(i) != arg { 142 return 0, errMustBeInteger 143 } 144 return i, nil 145 case string: 146 num, err := strconv.ParseInt(arg, 0, 0) 147 if err == nil { 148 return int(num), nil 149 } 150 return 0, cannotParseAs{"integer", Repr(arg, -1)} 151 default: 152 return 0, errMustBeInteger 153 } 154 } 155 156 func elvToRune(arg interface{}) (rune, error) { 157 ss, ok := arg.(string) 158 if !ok { 159 return -1, errMustBeString 160 } 161 s := ss 162 r, size := utf8.DecodeRuneInString(s) 163 if r == utf8.RuneError { 164 return -1, errMustBeValidUTF8 165 } 166 if size != len(s) { 167 return -1, errMustHaveSingleRune 168 } 169 return r, nil 170 }