diff --git a/packages/strings.go b/packages/strings.go index a31defd2..20215985 100644 --- a/packages/strings.go +++ b/packages/strings.go @@ -50,4 +50,5 @@ func init() { "TrimSpace": strings.TrimSpace, "TrimSuffix": strings.TrimSuffix, } + stringsGo110() } diff --git a/packages/stringsGo110.go b/packages/stringsGo110.go new file mode 100644 index 00000000..0690bd91 --- /dev/null +++ b/packages/stringsGo110.go @@ -0,0 +1,13 @@ +// +build go1.10 + +package packages + +import ( + "strings" +) + +func stringsGo110() { + PackageTypes["strings"] = map[string]interface{}{ + "Builder": strings.Builder{}, + } +} diff --git a/packages/stringsGo110_test.go b/packages/stringsGo110_test.go new file mode 100644 index 00000000..0c0bb206 --- /dev/null +++ b/packages/stringsGo110_test.go @@ -0,0 +1,16 @@ +package packages + +import ( + "os" + "testing" + + "github.com/mattn/anko/internal/testlib" +) + +func TestStringsGo110(t *testing.T) { + os.Setenv("ANKO_DEBUG", "1") + tests := []testlib.Test{ + {Script: `strings = import("strings"); a = make(strings.Builder); _, err = a.WriteString("a"); if err != nil { return err.Error() }; _, err = a.WriteString("b"); if err != nil { return err.Error() }; _, err = a.WriteString("c"); if err != nil { return err.Error() }; a.String()`, RunOutput: "abc"}, + } + testlib.Run(t, tests, &testlib.Options{EnvSetupFunc: &testPackagesEnvSetupFunc}) +} diff --git a/packages/stringsNotGo110.go b/packages/stringsNotGo110.go new file mode 100644 index 00000000..97e6896a --- /dev/null +++ b/packages/stringsNotGo110.go @@ -0,0 +1,5 @@ +// +build !go1.10 + +package packages + +func stringsGo110() {} diff --git a/vm/vm.go b/vm/vm.go index 57c421e1..b629944a 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -36,6 +36,8 @@ type ( var ( nilType = reflect.TypeOf(nil) stringType = reflect.TypeOf("a") + byteType = reflect.TypeOf(byte('a')) + runeType = reflect.TypeOf('a') interfaceType = reflect.ValueOf([]interface{}{int64(1)}).Index(0).Type() interfaceSliceType = reflect.TypeOf([]interface{}{}) reflectValueType = reflect.TypeOf(reflect.Value{}) @@ -50,6 +52,8 @@ var ( reflectValueNilValue = reflect.ValueOf(nilValue) reflectValueErrorNilValue = reflect.ValueOf(reflect.New(errorType).Elem()) + errInvalidTypeConversion = fmt.Errorf("invalid type conversion") + // ErrBreak when there is an unexpected break statement ErrBreak = errors.New("unexpected break statement") // ErrContinue when there is an unexpected continue statement @@ -402,6 +406,9 @@ func makeValue(t reflect.Type) (reflect.Value, error) { case reflect.Struct: structV := reflect.New(t).Elem() for i := 0; i < structV.NumField(); i++ { + if structV.Field(i).Kind() == reflect.Ptr { + continue + } v, err := makeValue(structV.Field(i).Type()) if err != nil { return nilValue, err diff --git a/vm/vmContainers_test.go b/vm/vmContainers_test.go index cad60b21..03af5ec8 100644 --- a/vm/vmContainers_test.go +++ b/vm/vmContainers_test.go @@ -11,6 +11,84 @@ import ( "github.com/mattn/anko/parser" ) +type ( + testStruct1 struct { + aInterface interface{} + aBool bool + aInt32 int32 + aInt64 int64 + aFloat32 float32 + aFloat64 float32 + aString string + aFunc func() + + aPtrInterface *interface{} + aPtrBool *bool + aPtrInt32 *int32 + aPtrInt64 *int64 + aPtrFloat32 *float32 + aPtrFloat64 *float32 + aPtrString *string + aPtrSliceInterface *[]interface{} + aPtrSliceBool *[]bool + aPtrSliceInt32 *[]int32 + aPtrSliceInt64 *[]int64 + aPtrSliceFloat32 *[]float32 + aPtrSliceFloat64 *[]float32 + aPtrSliceString *[]string + + aSliceInterface []interface{} + aSliceBool []bool + aSliceInt32 []int32 + aSliceInt64 []int64 + aSliceFloat32 []float32 + aSliceFloat64 []float32 + aSliceString []string + aSlicePtrInterface []*interface{} + aSlicePtrBool []*bool + aSlicePtrInt32 []*int32 + aSlicePtrInt64 []*int64 + aSlicePtrFloat32 []*float32 + aSlicePtrFloat64 []*float32 + aSlicePtrString []*string + + aMapInterface map[string]interface{} + aMapBool map[string]bool + aMapInt32 map[string]int32 + aMapInt64 map[string]int64 + aMapFloat32 map[string]float32 + aMapFloat64 map[string]float32 + aMapString map[string]string + aMapPtrInterface map[string]*interface{} + aMapPtrBool map[string]*bool + aMapPtrInt32 map[string]*int32 + aMapPtrInt64 map[string]*int64 + aMapPtrFloat32 map[string]*float32 + aMapPtrFloat64 map[string]*float32 + aMapPtrString map[string]*string + + aChanInterface chan interface{} + aChanBool chan bool + aChanInt32 chan int32 + aChanInt64 chan int64 + aChanFloat32 chan float32 + aChanFloat64 chan float32 + aChanString chan string + aChanPtrInterface chan *interface{} + aChanPtrBool chan *bool + aChanPtrInt32 chan *int32 + aChanPtrInt64 chan *int64 + aChanPtrFloat32 chan *float32 + aChanPtrFloat64 chan *float32 + aChanPtrString chan *string + + aPtrStruct *testStruct1 + } + testStruct2 struct { + aStruct testStruct1 + } +) + var ( testSliceEmpty []interface{} testSlice = []interface{}{nil, true, int64(1), float64(1.1), "a"} @@ -236,7 +314,7 @@ func TestSlicesAutoAppend(t *testing.T) { {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []float64{1.5, 2.5}}, Output: map[string]interface{}{"a": []float64{1.5, 2.5, 0}}}, {Script: `a[2] = nil`, Input: map[string]interface{}{"a": []string{"a", "b"}}, Output: map[string]interface{}{"a": []string{"a", "b", ""}}}, - {Script: `a[2] = "a"`, Input: map[string]interface{}{"a": []int32{1, 2}}, RunError: fmt.Errorf("type string cannot be assigned to type int32 for slice index"), Output: map[string]interface{}{"a": []int32{1, 2}}}, + {Script: `a[2] = "a"`, Input: map[string]interface{}{"a": []int16{1, 2}}, RunError: fmt.Errorf("type string cannot be assigned to type int16 for slice index"), Output: map[string]interface{}{"a": []int16{1, 2}}}, {Script: `a[2] = true`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunError: fmt.Errorf("type bool cannot be assigned to type int64 for slice index"), Output: map[string]interface{}{"a": []int64{1, 2}}}, {Script: `a[2] = "a"`, Input: map[string]interface{}{"a": []int64{1, 2}}, RunError: fmt.Errorf("type string cannot be assigned to type int64 for slice index"), Output: map[string]interface{}{"a": []int64{1, 2}}}, {Script: `a[2] = true`, Input: map[string]interface{}{"a": []float32{1.1, 2.2}}, RunError: fmt.Errorf("type bool cannot be assigned to type float32 for slice index"), Output: map[string]interface{}{"a": []float32{1.1, 2.2}}}, @@ -1769,6 +1847,8 @@ func TestStructs(t *testing.T) { func TestMakeStructs(t *testing.T) { os.Setenv("ANKO_DEBUG", "1") tests := []testlib.Test{ + {Script: `a = make(struct1)`, Types: map[string]interface{}{"struct1": &testStruct1{}}, RunOutput: &testStruct1{}, Output: map[string]interface{}{"a": &testStruct1{}}}, + {Script: `a = make(struct2)`, Types: map[string]interface{}{"struct2": &testStruct2{}}, RunOutput: &testStruct2{}, Output: map[string]interface{}{"a": &testStruct2{}}}, {Script: `make(struct)`, Types: map[string]interface{}{"struct": &struct { A interface{} B interface{} diff --git a/vm/vmConvertToX.go b/vm/vmConvertToX.go index 4850e93f..474ce08c 100644 --- a/vm/vmConvertToX.go +++ b/vm/vmConvertToX.go @@ -73,9 +73,32 @@ func convertReflectValueToType(rv reflect.Value, rt reflect.Type) (reflect.Value return convertReflectValueToType(rv.Elem(), rt) } + if rv.Type() == stringType { + if rt == byteType { + aString := rv.String() + if len(aString) < 1 { + return reflect.Zero(rt), nil + } + if len(aString) > 1 { + return rv, errInvalidTypeConversion + } + return reflect.ValueOf(aString[0]), nil + } + if rt == runeType { + aString := rv.String() + if len(aString) < 1 { + return reflect.Zero(rt), nil + } + if len(aString) > 1 { + return rv, errInvalidTypeConversion + } + return reflect.ValueOf(rune(aString[0])), nil + } + } + // TODO: need to handle the case where either rv or rt are a pointer but not both - return rv, fmt.Errorf("invalid type conversion") + return rv, errInvalidTypeConversion } // convertSliceOrArray trys to covert the reflect.Value slice or array to the slice or array reflect.Type @@ -145,7 +168,7 @@ func convertMap(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { func convertVMFunctionToType(rv reflect.Value, rt reflect.Type) (reflect.Value, error) { // only translates runVMFunction type if !checkIfRunVMFunction(rv.Type()) { - return rv, fmt.Errorf("invalid type conversion") + return rv, errInvalidTypeConversion } // create runVMConvertFunction to match reflect.Type diff --git a/vm/vmFunctions_test.go b/vm/vmFunctions_test.go index a349c028..8cf60547 100644 --- a/vm/vmFunctions_test.go +++ b/vm/vmFunctions_test.go @@ -533,6 +533,15 @@ func TestFunctionConversions(t *testing.T) { return b() }}, RunOutput: true, Output: map[string]interface{}{"c": true, "d": int32(1), "e": int64(2), "f": float32(3.3), "g": float64(4.4), "h": "5"}}, + // string to byte + {Script: `b = a("yz"); b`, Input: map[string]interface{}{"a": func(b byte) string { return string(b) }}, RunError: fmt.Errorf("function wants argument type uint8 but received type string")}, + {Script: `b = a("x"); b`, Input: map[string]interface{}{"a": func(b byte) string { return string(b) }}, RunOutput: "x"}, + {Script: `b = a(""); b`, Input: map[string]interface{}{"a": func(b byte) string { return string(b) }}, RunOutput: "\x00"}, + // string to rune + {Script: `b = a("yz"); b`, Input: map[string]interface{}{"a": func(b rune) string { return string(b) }}, RunError: fmt.Errorf("function wants argument type int32 but received type string")}, + {Script: `b = a("x"); b`, Input: map[string]interface{}{"a": func(b rune) string { return string(b) }}, RunOutput: "x"}, + {Script: `b = a(""); b`, Input: map[string]interface{}{"a": func(b rune) string { return string(b) }}, RunOutput: "\x00"}, + // slice inteface unable to convert to int {Script: `b = [1, 2.2, "3"]; a(b)`, Input: map[string]interface{}{"a": func(b []int) int { return len(b) }}, RunError: fmt.Errorf("function wants argument type []int but received type []interface {}"), Output: map[string]interface{}{"b": []interface{}{int64(1), float64(2.2), "3"}}}, // slice no sub convertible conversion