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  }