1. 背景

在早期的gshellos的实现中, 我们用了tengo解释器来在gshell框架下解释运行.tengo代码.

tengo使用tengo object来表示对象, 比如int在tengo中是tengo.Int.

2. ToObject

ToObject函数的作用是把一个原生的go对象转换为tengo对象, 支持简单的int byte bool等基础value, 以及map slice等复合value的组合. ToObject函数是递归的, 并且设计了fast path和slow path来做value的转换.

  • fast path用类型断言
  • slow path用reflect

下面我把代码贴出来, 用作后面参考.

// ToObject traverses the value v recursively and converts the value to tengo object.
//  Pointer values encode as the value pointed to.
//  A nil pointer/interface/slice/map encodes as the tengo.UndefinedValue value.
//  Struct values encode as tengo map. Only exported field can be encoded with filed names as map keys but with its first letter turned into lower case.
//    e.g. struct{Field1: 123, AnotherField: 456} will be converted to tengo map{field: 123, anotherField: 456}
//  int, string, float, bool, Time.time, error encodes as their corresponding tengo object.
//  slices encode as tengo Array, maps with key as string encode as tengo Map, returns ErrInvalidType if key type in map is not string.
// Returns ErrInvalidType on unsupported value type.
// Note as ToObject follows pointers, be careful with cyclic pointer references which results in infinite loop.
func ToObject(v interface{}) (tengo.Object, error) {
    // fast path
    switch v := v.(type) {
    case nil:
        return tengo.UndefinedValue, nil
    case string:
        if len(v) > tengo.MaxStringLen {
            return nil, tengo.ErrStringLimit
        }
        return &tengo.String{Value: v}, nil
    case int64:
        return &tengo.Int{Value: v}, nil
    case int:
        return &tengo.Int{Value: int64(v)}, nil
    case bool:
        if v {
            return tengo.TrueValue, nil
        }
        return tengo.FalseValue, nil
    case rune:
        return &tengo.Char{Value: v}, nil
    case byte:
        return &tengo.Char{Value: rune(v)}, nil
    case float64:
        return &tengo.Float{Value: v}, nil
    case *UserFunction:
        return v, nil
    case *tengo.UserFunction:
        return v, nil
    case tengo.Object:
        return v, nil
    case tengo.CallableFunc:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        return &tengo.UserFunction{Value: v}, nil
    case []byte:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        if len(v) > tengo.MaxBytesLen {
            return nil, tengo.ErrBytesLimit
        }
        return &tengo.Bytes{Value: v}, nil
    case error:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        return &tengo.Error{Value: &tengo.String{Value: v.Error()}}, nil
    case map[string]tengo.Object:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        return &tengo.Map{Value: v}, nil
    case map[string]int:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        kv := make(map[string]tengo.Object, len(v))
        for vk, vv := range v {
            vo, err := ToObject(vv)
            if err != nil {
                return nil, err
            }
            kv[vk] = vo
        }
        return &tengo.Map{Value: kv}, nil
    case map[string]int64:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        kv := make(map[string]tengo.Object, len(v))
        for vk, vv := range v {
            vo, err := ToObject(vv)
            if err != nil {
                return nil, err
            }
            kv[vk] = vo
        }
        return &tengo.Map{Value: kv}, nil
    case map[string]float64:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        kv := make(map[string]tengo.Object, len(v))
        for vk, vv := range v {
            vo, err := ToObject(vv)
            if err != nil {
                return nil, err
            }
            kv[vk] = vo
        }
        return &tengo.Map{Value: kv}, nil
    case map[string]string:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        kv := make(map[string]tengo.Object, len(v))
        for vk, vv := range v {
            vo, err := ToObject(vv)
            if err != nil {
                return nil, err
            }
            kv[vk] = vo
        }
        return &tengo.Map{Value: kv}, nil
    case map[string]interface{}:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        kv := make(map[string]tengo.Object, len(v))
        for vk, vv := range v {
            vo, err := ToObject(vv)
            if err != nil {
                return nil, err
            }
            kv[vk] = vo
        }
        return &tengo.Map{Value: kv}, nil
    case []tengo.Object:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        return &tengo.Array{Value: v}, nil
    case []int:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        arr := make([]tengo.Object, len(v))
        for i, e := range v {
            vo, err := ToObject(e)
            if err != nil {
                return nil, err
            }
            arr[i] = vo
        }
        return &tengo.Array{Value: arr}, nil
    case []int64:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        arr := make([]tengo.Object, len(v))
        for i, e := range v {
            vo, err := ToObject(e)
            if err != nil {
                return nil, err
            }
            arr[i] = vo
        }
        return &tengo.Array{Value: arr}, nil
    case []float64:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        arr := make([]tengo.Object, len(v))
        for i, e := range v {
            vo, err := ToObject(e)
            if err != nil {
                return nil, err
            }
            arr[i] = vo
        }
        return &tengo.Array{Value: arr}, nil
    case []string:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        arr := make([]tengo.Object, len(v))
        for i, e := range v {
            vo, err := ToObject(e)
            if err != nil {
                return nil, err
            }
            arr[i] = vo
        }
        return &tengo.Array{Value: arr}, nil
    case []interface{}:
        if v == nil {
            return tengo.UndefinedValue, nil
        }
        arr := make([]tengo.Object, len(v))
        for i, e := range v {
            vo, err := ToObject(e)
            if err != nil {
                return nil, err
            }
            arr[i] = vo
        }
        return &tengo.Array{Value: arr}, nil
    case time.Time:
        return &tengo.Time{Value: v}, nil
    }

    // slow path
    rv := reflect.ValueOf(v)
    switch rv.Kind() {
    case reflect.Ptr, reflect.Interface, reflect.Map, reflect.Slice:
        if rv.IsNil() {
            return tengo.UndefinedValue, nil
        }
    }

    rv = reflect.Indirect(rv)
    switch rv.Kind() {
    case reflect.Bool:
        if rv.Bool() {
            return tengo.TrueValue, nil
        }
        return tengo.FalseValue, nil
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return &tengo.Int{Value: rv.Int()}, nil
    case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        return &tengo.Int{Value: int64(rv.Uint())}, nil
    case reflect.Float32, reflect.Float64:
        return &tengo.Float{Value: rv.Float()}, nil
    case reflect.Array, reflect.Slice:
        arr := make([]tengo.Object, rv.Len())
        for i := 0; i < rv.Len(); i++ {
            obj, err := ToObject(rv.Index(i).Interface())
            if err != nil {
                return nil, err
            }
            arr[i] = obj
        }
        return &tengo.Array{Value: arr}, nil
    case reflect.Interface:
        obj, err := ToObject(rv.Elem().Interface())
        if err != nil {
            return nil, err
        }
        return obj, nil
    case reflect.Map:
        kv := make(map[string]tengo.Object, rv.Len())
        iter := rv.MapRange()
        for iter.Next() {
            k := iter.Key()
            if k.Kind() != reflect.String {
                return nil, ErrInvalidType
            }
            v := iter.Value()
            obj, err := ToObject(v.Interface())
            if err != nil {
                return nil, err
            }
            kv[k.String()] = obj
        }
        return &tengo.Map{Value: kv}, nil
    case reflect.Struct:
        kv := make(map[string]tengo.Object, rv.NumField())
        typ := rv.Type()

        for i := 0; i < rv.NumField(); i++ {
            obj, err := ToObject(rv.Field(i).Interface())
            if err != nil {
                return nil, err
            }
            kv[firstLetterLower(typ.Field(i).Name)] = obj
        }
        return &tengo.Map{Value: kv}, nil
    }

    return nil, ErrInvalidType
}

3. FromObject

FromObject是ToObject的反过程.

// FromObject parses the tengo object and stores the result in the value pointed to by v.
// FromObject uses the inverse of the encodings that ToObject uses, allocating maps, slices, and pointers as necessary.
//
// FromObject converts tengo Map object into a struct by map look up with field names as keys.
// Filed name and tengo map key are matched in a way that the first letter is insensitive.
//   e.g. both tengo map{name: "san"} and map{Name: "san"} can be converted to struct{Name: "san"}
//  If v is nil or not a pointer, ObjectToValue returns an ErrInvalidPtr error.
//  If o is already a tengo object, it is copied to the value that v points to.
//  If v represents a *tengo.CallableFunc, and o is a tengo UserFunction object, the CallableFunc f will be copied to where v points.
// Returns ErrNotConvertibleType if o can not be converted to v, e.g. you are trying to get a map vale from tengo Array object.
// Not supported value types:
//  interface, chan, complex, func
//  In particular, interface error is not convertible.
func FromObject(v interface{}, o tengo.Object) error {
    if o == tengo.UndefinedValue {
        return nil // ignore undefined value
    }

    // fast path
    switch ptr := v.(type) {
    case *int:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToInt(o); ok {
            *ptr = v
            return nil
        }
    case *int64:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToInt64(o); ok {
            *ptr = v
            return nil
        }
    case *string:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToString(o); ok {
            *ptr = v
            return nil
        }
    case *float64:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToFloat64(o); ok {
            *ptr = v
            return nil
        }
    case *bool:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToBool(o); ok {
            *ptr = v
            return nil
        }
    case *rune:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToRune(o); ok {
            *ptr = v
            return nil
        }
    case *[]byte:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToByteSlice(o); ok {
            *ptr = v
            return nil
        }
    case *time.Time:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if v, ok := tengo.ToTime(o); ok {
            *ptr = v
            return nil
        }
    case *[]int:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toA := func(objArray []tengo.Object) bool {
            array := make([]int, len(objArray))
            for i, o := range objArray {
                v, ok := tengo.ToInt(o)
                if !ok {
                    return false
                }
                array[i] = v
            }
            *ptr = array
            return true
        }
        switch o := o.(type) {
        case *tengo.Array:
            if toA(o.Value) {
                return nil
            }
        case *tengo.ImmutableArray:
            if toA(o.Value) {
                return nil
            }
        }
    case *[]int64:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toA := func(objArray []tengo.Object) bool {
            array := make([]int64, len(objArray))
            for i, o := range objArray {
                v, ok := tengo.ToInt64(o)
                if !ok {
                    return false
                }
                array[i] = v
            }
            *ptr = array
            return true
        }
        switch o := o.(type) {
        case *tengo.Array:
            if toA(o.Value) {
                return nil
            }
        case *tengo.ImmutableArray:
            if toA(o.Value) {
                return nil
            }
        }
    case *[]float64:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toA := func(objArray []tengo.Object) bool {
            array := make([]float64, len(objArray))
            for i, o := range objArray {
                v, ok := tengo.ToFloat64(o)
                if !ok {
                    return false
                }
                array[i] = v
            }
            *ptr = array
            return true
        }
        switch o := o.(type) {
        case *tengo.Array:
            if toA(o.Value) {
                return nil
            }
        case *tengo.ImmutableArray:
            if toA(o.Value) {
                return nil
            }
        }
    case *[]string:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toA := func(objArray []tengo.Object) bool {
            array := make([]string, len(objArray))
            for i, o := range objArray {
                v, ok := tengo.ToString(o)
                if !ok {
                    return false
                }
                array[i] = v
            }
            *ptr = array
            return true
        }
        switch o := o.(type) {
        case *tengo.Array:
            if toA(o.Value) {
                return nil
            }
        case *tengo.ImmutableArray:
            if toA(o.Value) {
                return nil
            }
        }
    case *map[string]int:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toM := func(objMap map[string]tengo.Object) bool {
            mp := make(map[string]int, len(objMap))
            for k, o := range objMap {
                v, ok := tengo.ToInt(o)
                if !ok {
                    return false
                }
                mp[k] = v
            }
            *ptr = mp
            return true
        }
        switch o := o.(type) {
        case *tengo.Map:
            if toM(o.Value) {
                return nil
            }
        case *tengo.ImmutableMap:
            if toM(o.Value) {
                return nil
            }
        }
    case *map[string]int64:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toM := func(objMap map[string]tengo.Object) bool {
            mp := make(map[string]int64, len(objMap))
            for k, o := range objMap {
                v, ok := tengo.ToInt64(o)
                if !ok {
                    return false
                }
                mp[k] = v
            }
            *ptr = mp
            return true
        }
        switch o := o.(type) {
        case *tengo.Map:
            if toM(o.Value) {
                return nil
            }
        case *tengo.ImmutableMap:
            if toM(o.Value) {
                return nil
            }
        }
    case *map[string]float64:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toM := func(objMap map[string]tengo.Object) bool {
            mp := make(map[string]float64, len(objMap))
            for k, o := range objMap {
                v, ok := tengo.ToFloat64(o)
                if !ok {
                    return false
                }
                mp[k] = v
            }
            *ptr = mp
            return true
        }
        switch o := o.(type) {
        case *tengo.Map:
            if toM(o.Value) {
                return nil
            }
        case *tengo.ImmutableMap:
            if toM(o.Value) {
                return nil
            }
        }
    case *map[string]string:
        if ptr == nil {
            return ErrInvalidPtr
        }
        toM := func(objMap map[string]tengo.Object) bool {
            mp := make(map[string]string, len(objMap))
            for k, o := range objMap {
                v, ok := tengo.ToString(o)
                if !ok {
                    return false
                }
                mp[k] = v
            }
            *ptr = mp
            return true
        }
        switch o := o.(type) {
        case *tengo.Map:
            if toM(o.Value) {
                return nil
            }
        case *tengo.ImmutableMap:
            if toM(o.Value) {
                return nil
            }
        }
    case *tengo.Object:
        if ptr == nil {
            return ErrInvalidPtr
        }
        *ptr = o
        return nil
    case *tengo.CallableFunc:
        if ptr == nil {
            return ErrInvalidPtr
        }
        if f, ok := o.(*tengo.UserFunction); ok {
            *ptr = f.Value
            return nil
        }
    default:
        // slow path
        rptr := reflect.ValueOf(v)
        if rptr.Kind() != reflect.Ptr || rptr.IsNil() {
            return ErrInvalidPtr
        }
        rv := rptr.Elem()
        switch rv.Kind() {
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
            if v, ok := tengo.ToInt64(o); ok {
                rv.SetInt(v)
                return nil
            }
        case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
            if v, ok := tengo.ToInt64(o); ok {
                rv.SetUint(uint64(v))
                return nil
            }
        case reflect.Float32, reflect.Float64:
            if v, ok := tengo.ToFloat64(o); ok {
                rv.SetFloat(v)
                return nil
            }
        case reflect.Ptr:
            if rv.IsNil() {
                rv.Set(reflect.New(rv.Type().Elem()))
            }
            if err := FromObject(rv.Interface(), o); err == nil {
                return nil
            }
        case reflect.Array, reflect.Slice:
            toA := func(objArray []tengo.Object) bool {
                array := reflect.MakeSlice(rv.Type(), len(objArray), len(objArray))
                for i, o := range objArray {
                    if o == tengo.UndefinedValue {
                        continue
                    }
                    elem := array.Index(i)
                    if err := FromObject(elem.Addr().Interface(), o); err != nil {
                        return false
                    }
                }
                rv.Set(array)
                return true
            }

            switch o := o.(type) {
            case *tengo.Array:
                if toA(o.Value) {
                    return nil
                }
            case *tengo.ImmutableArray:
                if toA(o.Value) {
                    return nil
                }
            }
        case reflect.Map:
            toM := func(objMap map[string]tengo.Object) bool {
                typ := rv.Type()
                if typ.Key().Kind() != reflect.String {
                    return false
                }
                mp := reflect.MakeMapWithSize(typ, len(objMap))
                elemPtr := reflect.New(typ.Elem())
                for k, o := range objMap {
                    if o == tengo.UndefinedValue {
                        continue
                    }
                    if err := FromObject(elemPtr.Interface(), o); err != nil {
                        return false
                    }
                    mp.SetMapIndex(reflect.ValueOf(k), elemPtr.Elem())
                }
                rv.Set(mp)
                return true
            }

            switch o := o.(type) {
            case *tengo.Map:
                if toM(o.Value) {
                    return nil
                }
            case *tengo.ImmutableMap:
                if toM(o.Value) {
                    return nil
                }
            }
        case reflect.Struct:
            toStruct := func(objMap map[string]tengo.Object) bool {
                typ := rv.Type()
                for i := 0; i < rv.NumField(); i++ {
                    fieldName := typ.Field(i).Name
                    obj, ok := objMap[firstLetterLower(fieldName)]
                    if !ok {
                        obj, ok = objMap[fieldName]
                    }
                    if ok {
                        if obj == tengo.UndefinedValue {
                            continue
                        }
                        field := rv.Field(i)
                        if err := FromObject(field.Addr().Interface(), obj); err != nil {
                            return false
                        }
                    }
                }
                return true
            }
            switch o := o.(type) {
            case *tengo.Map:
                if toStruct(o.Value) {
                    return nil
                }
            case *tengo.ImmutableMap:
                if toStruct(o.Value) {
                    return nil
                }
            }
        }
    }
    return errNotConvertible(reflect.ValueOf(v).Elem().String(), o)
}

4. 测试程序

package gshellos

import (
    "errors"
    "fmt"
    "reflect"
    "regexp"
    "strings"
    "testing"
    "time"

    "github.com/d5/tengo/v2"
)

func TestToObject(t *testing.T) {
    testb := true
    var testi uint32 = 88
    testf := 33.33
    var itf interface{}
    itf = testf

    type student struct {
        Name    string
        Age     int
        Scores  map[string]float32
        Friends []student
    }

    empty := struct {
        A string
        B int
        C float64
        D []int
        E []int64
        F []float64
        G []tengo.Object
        H []string
        I []byte
        J []interface{}
        K tengo.CallableFunc
        L error
        M map[string]int
        N map[string]int64
        O map[string]float64
        P map[string]string
        Q map[string]interface{}
        R map[string]tengo.Object
        S *int
    }{}

    cases := []struct {
        v    interface{}
        want string
    }{
        {&tengo.Int{Value: 123}, `&tengo.Int{ObjectImpl:tengo.ObjectImpl{}, Value:123}`},
        {nil, `&tengo.Undefined{ObjectImpl:tengo.ObjectImpl{}}`},
        {1, `&tengo.Int{ObjectImpl:tengo.ObjectImpl{}, Value:1}`},
        {"hello world", `&tengo.String{ObjectImpl:tengo.ObjectImpl{}, Value:"hello world", runeStr:[]int32(nil)}`},
        {99.99, `&tengo.Float{ObjectImpl:tengo.ObjectImpl{}, Value:99.99}`},
        {false, `&tengo.Bool{ObjectImpl:tengo.ObjectImpl{}, value:false}`},
        {'@', `&tengo.Char{ObjectImpl:tengo.ObjectImpl{}, Value:64}`},
        {byte(56), `&tengo.Char{ObjectImpl:tengo.ObjectImpl{}, Value:56}`},
        {[]byte("567"), `&tengo.Bytes{ObjectImpl:tengo.ObjectImpl{}, Value:[]uint8{0x35, 0x36, 0x37}}`},
        {errors.New("err"), `&tengo.Error{ObjectImpl:tengo.ObjectImpl{}, Value:\(\*tengo.String\)\(0x[0-9a-f]+\)}`},
        {map[string]int{"zhangsan": 30, "lisi": 35},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((zhangsan)|(lisi))":\(\*tengo.Int\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {map[string]int64{"zhangsan": 30, "lisi": 35},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((zhangsan)|(lisi))":\(\*tengo.Int\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {map[string]string{"zhangsan": "teacher", "lisi": "student"},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((zhangsan)|(lisi))":\(\*tengo.String\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {map[string]interface{}{"zhangsan": 30, "lisi": "student"},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((zhangsan)|(lisi))":\(\*tengo.((String)|(Int))\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {map[string]float64{"zhangsan": 30.1, "lisi": 35.2},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((zhangsan)|(lisi))":\(\*tengo.Float\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {[2]int{11, 13},
            `^&tengo.Array{ObjectImpl:tengo.ObjectImpl{}, Value:\[\]tengo.Object{(\(\*tengo.Int\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {[]int{101, 103, 105},
            `^&tengo.Array{ObjectImpl:tengo.ObjectImpl{}, Value:\[\]tengo.Object{(\(\*tengo.Int\)\(0x[0-9a-f]+\),? ?){3}}}$`},
        {[]int64{101, 103, 105},
            `^&tengo.Array{ObjectImpl:tengo.ObjectImpl{}, Value:\[\]tengo.Object{(\(\*tengo.Int\)\(0x[0-9a-f]+\),? ?){3}}}$`},
        {[]float64{101.1, 103.1, 105.1},
            `^&tengo.Array{ObjectImpl:tengo.ObjectImpl{}, Value:\[\]tengo.Object{(\(\*tengo.Float\)\(0x[0-9a-f]+\),? ?){3}}}$`},
        {[]string{"ni", "hao", "ma"},
            `^&tengo.Array{ObjectImpl:tengo.ObjectImpl{}, Value:\[\]tengo.Object{(\(\*tengo.String\)\(0x[0-9a-f]+\),? ?){3}}}$`},
        {[]interface{}{"ni", "hao", 123},
            `^&tengo.Array{ObjectImpl:tengo.ObjectImpl{}, Value:\[\]tengo.Object{(\(\*tengo.((String)|(Int))\)\(0x[0-9a-f]+\),? ?){3}}}$`},
        {time.Now(), `^&tengo.Time{ObjectImpl:tengo.ObjectImpl{}, Value:time.Time{.*}}$`},
        {&testb, `&tengo.Bool{ObjectImpl:tengo.ObjectImpl{}, value:true}`},
        {int16(55), `&tengo.Int{ObjectImpl:tengo.ObjectImpl{}, Value:55}`},
        {&testi, `&tengo.Int{ObjectImpl:tengo.ObjectImpl{}, Value:88}`},
        {&testf, `&tengo.Float{ObjectImpl:tengo.ObjectImpl{}, Value:33.33}`},
        {itf, `&tengo.Float{ObjectImpl:tengo.ObjectImpl{}, Value:33.33}`},
        {student{"lisi", 20, map[string]float32{"yuwen": 86.5, "shuxue": 83.1}, []student{{Name: "zhangsan"}, {Name: "wangwu"}}},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((age)|(friends)|(name)|(scores))":\(\*tengo.((Int)|Array|String|Map)\)\(0x[0-9a-f]+\),? ?){4}}}$`},
        {map[string]student{"zhangsan": {Name: "zhangsan"}, "lisi": {Name: "lisi"}},
            `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{("((lisi)|(zhangsan))":\(\*tengo.Map\)\(0x[0-9a-f]+\),? ?){2}}}$`},
        {empty, `^&tengo.Map{ObjectImpl:tengo.ObjectImpl{}, Value:map\[string\]tengo.Object{"a":\(\*tengo.String\)\(0x[0-9a-f]+\), "b":\(\*tengo.Int\)\(0x[0-9a-f]+\), "c":\(\*tengo.Float\)\(0x[0-9a-f]+\), ("[d-s]{1}":\(\*tengo.Undefined\)\(0x[0-9a-f]+\),? ?){16}}}$`},
    }

    for _, c := range cases {
        if len(c.want) == 0 {
            t.Error("empty want")
        }
        obj, err := ToObject(c.v)
        if err != nil {
            t.Error(err)
            continue
        }
        got := fmt.Sprintf("%#v", obj)
        //t.Logf("%v\n", obj)
        if got == c.want {
            continue
        }
        re := regexp.MustCompile(c.want)
        if !re.MatchString(got) {
            t.Errorf("want: %s, got: %s", c.want, got)
        }
    }

    _, err := ToObject([]complex64{complex(1, -2), complex(1.0, -1.4)})
    if err != ErrInvalidType {
        t.Error("complex supported?")
    }

    _, err = ToObject(map[string]interface{}{"a": complex(1, -2), "b": complex(1.0, -1.4)})
    if err != ErrInvalidType {
        t.Error("complex supported?")
    }
}

func TestFromObject(t *testing.T) {
    obj, _ := ToObject(55)
    emptyCases := []interface{}{
        (*int)(nil),
        (*int64)(nil),
        (*string)(nil),
        (*float64)(nil),
        (*bool)(nil),
        (*rune)(nil),
        (*[]byte)(nil),
        (*time.Time)(nil),
        (*[]int)(nil),
        (*[]int64)(nil),
        (*[]float64)(nil),
        (*[]string)(nil),
        (*map[string]int)(nil),
        (*map[string]int64)(nil),
        (*map[string]float64)(nil),
        (*map[string]string)(nil),
        (*tengo.Object)(nil),
        (*tengo.CallableFunc)(nil),
        (*int32)(nil),
        nil,
    }

    for _, c := range emptyCases {
        err := FromObject(c, obj)
        if err != ErrInvalidPtr {
            t.Fatal("empty ptr error expected")
        }
    }

    var got tengo.Object
    err := FromObject(&got, obj)
    if err != nil {
        t.Error(err)
    }
    if !reflect.DeepEqual(got, obj) {
        t.Errorf("want: %#v, got: %#v", obj, got)
    }

    testf := func(args ...tengo.Object) (tengo.Object, error) {
        return nil, nil
    }
    var gotf func(args ...tengo.Object) (tengo.Object, error)
    fobj, err := ToObject(testf)
    if err != nil {
        t.Error(err)
    }
    err = FromObject(&gotf, fobj)
    if err != nil {
        t.Error(err)
    }
    var itf interface{} = gotf
    gotstring := fmt.Sprintf("%#v", itf)
    wantstring := `(func(...tengo.Object) (tengo.Object, error))`
    if !strings.Contains(gotstring, wantstring) {
        t.Errorf("want: %s, got: %s", wantstring, gotstring)
    }
    err = FromObject(&gotf, obj)
    if !errors.As(err, &ErrNotConvertibleType{}) {
        t.Error(err)
    }

    type student struct {
        Name       string
        Age        int
        Scores     map[string]float32
        Classmates []student
        Deskmate   *student
        Friends    map[string]*student
    }

    studentA := student{
        "lisi",
        20,
        map[string]float32{"yuwen": 86.5, "shuxue": 83.1},
        []student{{Name: "zhangsan"}, {Name: "wangwu"}},
        nil,
        nil,
    }

    studentB := student{
        "zhangsan",
        21,
        map[string]float32{"yuwen": 78.5, "shuxue": 96.1},
        []student{{Name: "lisi"}, {Name: "wangwu"}},
        &studentA,
        map[string]*student{"si": &studentA},
    }

    cases := []interface{}{
        "hello world",
        55,
        int64(33),
        55.77,
        true,
        'U',
        []byte{1, 2, 3, 4, 5},
        time.Now(),
        []int{22, 33, 44},
        []int64{22, 33, 44},
        []float64{22.1, 33.2, 44.9},
        []string{"ni", "hao", "ma"},
        map[string]int{"A": 1, "b": 15},
        map[string]int64{"A": 1, "b": 15},
        map[string]float64{"a": 1.54, "U": 3.14},
        map[string]string{"a": "12345", "U": "hello world"},
        int16(12),
        uint16(12),
        float32(1.2345),
        studentB,
        studentA,
    }

    for _, c := range cases {
        t.Logf("c: %#v", c)
        obj, err := ToObject(c)
        if err != nil {
            t.Fatal(err)
        }
        t.Logf("obj: %#v", obj)

        ptr := reflect.New(reflect.TypeOf(c))
        err = FromObject(ptr.Interface(), obj)
        if err != nil {
            t.Error(err)
            continue
        }
        v := ptr.Elem().Interface()
        t.Logf("v: %#v", v)
        if !reflect.DeepEqual(c, v) {
            t.Errorf("want: %#v, got: %#v", c, v)
        }
    }
    //t.Error("err")
}

results matching ""

    No results matching ""