mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-14 11:17:52 +08:00
ab88de7c15
When the destination to scan into is an interface, its zero value is simply a nil interface, losing the information of the original type and resulting in error messages like "need nil, got $actual-type". ScanToGo now handles this case specifically, and uses the string representation of the interface type in the error message. Before: ~> ns [] Exception: wrong type of argument 0: wrong type: need nil, got list ~> var x~ = x Exception: wrong type: need nil, got string After: ~> ns [] Exception: wrong type of argument 0: wrong type: need !!hashmap.Map, got list ~> var x~ = x Exception: wrong type: need !!eval.Callable, got string This is still not ideal, since the "need" type is not given as an Elvish "kind", but it's much less confusing than than old "need nil" messages. This fixes #715.
172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
package vals
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"reflect"
|
|
"strconv"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Conversion between native and Elvish values.
|
|
//
|
|
// Elvish uses native Go types most of the time - string, bool, hashmap.Map,
|
|
// vector.Vector, etc., and there is no need for any conversions. There are some
|
|
// exceptions, for instance int and rune, since Elvish currently lacks integer
|
|
// types.
|
|
//
|
|
// There is a many-to-one relationship between Go types and Elvish types. A
|
|
// Go value can always be converted to an Elvish value unambiguously, but to
|
|
// convert an Elvish value into a Go value one must know the destination type
|
|
// first. For example, all of the Go values int(1), rune('1') and string("1")
|
|
// convert to Elvish "1"; conversely, Elvish "1" may be converted to any of the
|
|
// aforementioned three possible values, depending on the destination type.
|
|
//
|
|
// In future, Elvish may gain distinct types for integers and characters, making
|
|
// the examples above unnecessary; however, the conversion logic may not
|
|
// entirely go away, as there might always be some mismatch between Elvish's
|
|
// type system and Go's.
|
|
|
|
type wrongType struct {
|
|
wantKind string
|
|
gotKind string
|
|
}
|
|
|
|
func (err wrongType) Error() string {
|
|
return fmt.Sprintf("wrong type: need %s, got %s", err.wantKind, err.gotKind)
|
|
}
|
|
|
|
type cannotParseAs struct {
|
|
want string
|
|
repr string
|
|
}
|
|
|
|
func (err cannotParseAs) Error() string {
|
|
return fmt.Sprintf("cannot parse as %s: %s", err.want, err.repr)
|
|
}
|
|
|
|
var (
|
|
errMustBeString = errors.New("must be string")
|
|
errMustBeValidUTF8 = errors.New("must be valid UTF-8")
|
|
errMustHaveSingleRune = errors.New("must have a single rune")
|
|
errMustBeNumber = errors.New("must be number")
|
|
errMustBeInteger = errors.New("must be integer")
|
|
)
|
|
|
|
// ScanToGo converts an Elvish value to a Go value that the pointer refers to. It
|
|
// uses the type of the pointer to determine the destination type, and puts the
|
|
// converted value in the location the pointer points to. Conversion only
|
|
// happens when the destination type is int, float64 or rune; in other cases,
|
|
// this function just checks that the source value is already assignable to the
|
|
// destination.
|
|
func ScanToGo(src interface{}, ptr interface{}) error {
|
|
switch ptr := ptr.(type) {
|
|
case *int:
|
|
i, err := elvToInt(src)
|
|
if err == nil {
|
|
*ptr = i
|
|
}
|
|
return err
|
|
case *float64:
|
|
n, err := elvToNum(src)
|
|
if err == nil {
|
|
*ptr = ConvertToFloat64(n)
|
|
}
|
|
return err
|
|
case *Num:
|
|
n, err := elvToNum(src)
|
|
if err == nil {
|
|
*ptr = n
|
|
}
|
|
return err
|
|
case *rune:
|
|
r, err := elvToRune(src)
|
|
if err == nil {
|
|
*ptr = r
|
|
}
|
|
return err
|
|
default:
|
|
// Do a generic `*ptr = src` via reflection
|
|
ptrType := TypeOf(ptr)
|
|
if ptrType.Kind() != reflect.Ptr {
|
|
return fmt.Errorf("internal bug: need pointer to scan to, got %T", ptr)
|
|
}
|
|
dstType := ptrType.Elem()
|
|
if !TypeOf(src).AssignableTo(dstType) {
|
|
var dstKind string
|
|
if dstType.Kind() == reflect.Interface {
|
|
dstKind = "!!" + dstType.String()
|
|
} else {
|
|
dstKind = Kind(reflect.Zero(dstType).Interface())
|
|
}
|
|
return wrongType{dstKind, Kind(src)}
|
|
}
|
|
ValueOf(ptr).Elem().Set(ValueOf(src))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// FromGo converts a Go value to an Elvish value. Most types are returned as
|
|
// is, but exact numerical types are normalized to one of int, *big.Int and
|
|
// *big.Rat, using the small representation that can hold the value, and runes
|
|
// are converted to strings.
|
|
func FromGo(a interface{}) interface{} {
|
|
switch a := a.(type) {
|
|
case *big.Int:
|
|
return NormalizeBigInt(a)
|
|
case *big.Rat:
|
|
return NormalizeBigRat(a)
|
|
case rune:
|
|
return string(a)
|
|
default:
|
|
return a
|
|
}
|
|
}
|
|
|
|
func elvToInt(arg interface{}) (int, error) {
|
|
switch arg := arg.(type) {
|
|
case int:
|
|
return arg, nil
|
|
case string:
|
|
num, err := strconv.ParseInt(arg, 0, 0)
|
|
if err == nil {
|
|
return int(num), nil
|
|
}
|
|
return 0, cannotParseAs{"integer", Repr(arg, -1)}
|
|
default:
|
|
return 0, errMustBeInteger
|
|
}
|
|
}
|
|
|
|
func elvToNum(arg interface{}) (Num, error) {
|
|
switch arg := arg.(type) {
|
|
case int, *big.Int, *big.Rat, float64:
|
|
return arg, nil
|
|
case string:
|
|
n := ParseNum(arg)
|
|
if n == nil {
|
|
return 0, cannotParseAs{"number", Repr(arg, -1)}
|
|
}
|
|
return n, nil
|
|
default:
|
|
return 0, errMustBeNumber
|
|
}
|
|
}
|
|
|
|
func elvToRune(arg interface{}) (rune, error) {
|
|
ss, ok := arg.(string)
|
|
if !ok {
|
|
return -1, errMustBeString
|
|
}
|
|
s := ss
|
|
r, size := utf8.DecodeRuneInString(s)
|
|
if r == utf8.RuneError {
|
|
return -1, errMustBeValidUTF8
|
|
}
|
|
if size != len(s) {
|
|
return -1, errMustHaveSingleRune
|
|
}
|
|
return r, nil
|
|
}
|