github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/vals/conversion.go (about)

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