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  }