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