cue: factor out conversion from Go functionality

Update #24

Change-Id: I3589937d0ddf46d25938bcc8e4093542c0668ca7
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1787
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/builtin.go b/cue/builtin.go
index 72f466d..02750f0 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -22,13 +22,9 @@
 	"io"
 	"math/big"
 	"path"
-	"reflect"
 	"sort"
-	"strings"
 
-	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/parser"
-	"github.com/cockroachdb/apd"
 )
 
 // A builtin is a builtin function or constant.
@@ -380,164 +376,3 @@
 	}
 	return a
 }
-
-func convert(ctx *context, src source, x interface{}) evaluated {
-	switch v := x.(type) {
-	case evaluated:
-		return v
-	case nil:
-		return &nullLit{src.base()}
-	case ast.Expr:
-		x := newVisitorCtx(ctx, nil, nil, nil)
-		return ctx.manifest(x.walk(v))
-	case error:
-		return ctx.mkErr(src, v.Error())
-	case bool:
-		return &boolLit{src.base(), v}
-	case string:
-		return &stringLit{src.base(), v}
-	case []byte:
-		return &bytesLit{src.base(), v}
-	case int:
-		return toInt(ctx, src, int64(v))
-	case int8:
-		return toInt(ctx, src, int64(v))
-	case int16:
-		return toInt(ctx, src, int64(v))
-	case int32:
-		return toInt(ctx, src, int64(v))
-	case int64:
-		return toInt(ctx, src, int64(v))
-	case uint:
-		return toUint(ctx, src, uint64(v))
-	case uint8:
-		return toUint(ctx, src, uint64(v))
-	case uint16:
-		return toUint(ctx, src, uint64(v))
-	case uint32:
-		return toUint(ctx, src, uint64(v))
-	case uint64:
-		return toUint(ctx, src, uint64(v))
-	case float64:
-		r := newNum(src, floatKind)
-		r.v.SetString(fmt.Sprintf("%g", v))
-		return r
-	case *big.Int:
-		n := newNum(src, intKind)
-		n.v.Coeff.Set(v)
-		if v.Sign() < 0 {
-			n.v.Coeff.Neg(&n.v.Coeff)
-			n.v.Negative = true
-		}
-		return n
-	case *big.Rat:
-		n := newNum(src, numKind)
-		ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
-		if !v.IsInt() {
-			n.k = floatKind
-		}
-		return n
-	case *big.Float:
-		n := newNum(src, floatKind)
-		n.v.SetString(v.String())
-		return n
-	case *apd.Decimal:
-		n := newNum(src, floatKind|intKind)
-		n.v.Set(v)
-		if !n.isInt(ctx) {
-			n.k = floatKind
-		}
-		return n
-	case reflect.Value:
-		if v.CanInterface() {
-			return convert(ctx, src, v.Interface())
-		}
-
-	default:
-		value := reflect.ValueOf(v)
-		switch value.Kind() {
-		case reflect.Ptr:
-			if value.IsNil() {
-				return &nullLit{src.base()}
-			}
-			return convert(ctx, src, value.Elem().Interface())
-		case reflect.Struct:
-			obj := newStruct(src)
-			t := value.Type()
-			for i := 0; i < value.NumField(); i++ {
-				t := t.Field(i)
-				if t.PkgPath != "" {
-					continue
-				}
-				sub := convert(ctx, src, value.Field(i).Interface())
-				// leave errors like we do during normal evaluation or do we
-				// want to return the error?
-				name := t.Name
-				for _, s := range []string{"cue", "json", "protobuf"} {
-					if tag, ok := t.Tag.Lookup(s); ok {
-						if p := strings.Index(tag, ","); p >= 0 {
-							tag = tag[:p]
-						}
-						if tag != "" {
-							name = tag
-							break
-						}
-					}
-				}
-				f := ctx.strLabel(name)
-				obj.arcs = append(obj.arcs, arc{feature: f, v: sub})
-			}
-			sort.Sort(obj)
-			return obj
-
-		case reflect.Map:
-			obj := newStruct(src)
-			t := value.Type()
-			if t.Key().Kind() != reflect.String {
-				return ctx.mkErr(src, "builtin map key not a string, but unsupported type %s", t.Key().String())
-			}
-			keys := []string{}
-			for _, k := range value.MapKeys() {
-				keys = append(keys, k.String())
-			}
-			sort.Strings(keys)
-			for _, k := range keys {
-				sub := convert(ctx, src, value.MapIndex(reflect.ValueOf(k)).Interface())
-				// leave errors like we do during normal evaluation or do we
-				// want to return the error?
-				f := ctx.strLabel(k)
-				obj.arcs = append(obj.arcs, arc{feature: f, v: sub})
-			}
-			sort.Sort(obj)
-			return obj
-
-		case reflect.Slice, reflect.Array:
-			list := &list{baseValue: src.base()}
-			for i := 0; i < value.Len(); i++ {
-				x := convert(ctx, src, value.Index(i).Interface())
-				if isBottom(x) {
-					return x
-				}
-				list.a = append(list.a, x)
-			}
-			list.initLit()
-			// There is no need to set the type of the list, as the list will
-			// be of fixed size and all elements will already have a defined
-			// value.
-			return list
-		}
-	}
-	return ctx.mkErr(src, "builtin returned unsupported type %T", x)
-}
-
-func toInt(ctx *context, src source, x int64) evaluated {
-	n := newNum(src, intKind)
-	n.v.SetInt64(x)
-	return n
-}
-
-func toUint(ctx *context, src source, x uint64) evaluated {
-	n := newNum(src, floatKind)
-	n.v.Coeff.SetUint64(x)
-	return n
-}
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index c35c9bb..59c3e19 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -16,118 +16,10 @@
 
 import (
 	"fmt"
-	"math/big"
-	"reflect"
 	"strings"
 	"testing"
-
-	"cuelang.org/go/cue/ast"
-	"cuelang.org/go/cue/errors"
 )
 
-func TestConvert(t *testing.T) {
-	i34 := big.NewInt(34)
-	d34 := mkBigInt(34)
-	f34 := big.NewFloat(34.0000)
-	testCases := []struct {
-		goVal interface{}
-		want  string
-	}{{
-		nil, "null",
-	}, {
-		true, "true",
-	}, {
-		false, "false",
-	}, {
-		errors.New("oh noes"), "_|_(oh noes)",
-	}, {
-		"foo", `"foo"`,
-	}, {
-		3, "3",
-	}, {
-		uint(3), "3",
-	}, {
-		uint8(3), "3",
-	}, {
-		uint16(3), "3",
-	}, {
-		uint32(3), "3",
-	}, {
-		uint64(3), "3",
-	}, {
-		int8(-3), "-3",
-	}, {
-		int16(-3), "-3",
-	}, {
-		int32(-3), "-3",
-	}, {
-		int64(-3), "-3",
-	}, {
-		float64(3.1), "3.1",
-	}, {
-		&i34, "34",
-	}, {
-		&f34, "34",
-	}, {
-		&d34, "34",
-	}, {
-		[]int{1, 2, 3, 4}, "[1,2,3,4]",
-	}, {
-		[]interface{}{}, "[]",
-	}, {
-		map[string][]int{
-			"a": []int{1},
-			"b": []int{3, 4},
-		}, "<0>{a: [1], b: [3,4]}",
-	}, {
-		map[int]int{}, "_|_(builtin map key not a string, but unsupported type int)",
-	}, {
-		map[int]int{1: 2}, "_|_(builtin map key not a string, but unsupported type int)",
-	}, {
-		struct {
-			a int
-			b int
-		}{3, 4},
-		"<0>{}",
-	}, {
-		struct {
-			A int
-			B int
-		}{3, 4},
-		"<0>{A: 3, B: 4}",
-	}, {
-		struct {
-			A int `json:"a"`
-			B int `cue:"b"`
-		}{3, 4},
-		"<0>{a: 3, b: 4}",
-	}, {
-		struct {
-			A int `json:",bb" cue:"" protobuf:"aa"`
-			B int `json:"cc" cue:"bb" protobuf:"aa"`
-		}{3, 4},
-		"<0>{aa: 3, bb: 4}",
-	}, {
-		&struct{ A int }{3}, "<0>{A: 3}",
-	}, {
-		(*struct{ A int })(nil), "null",
-	}, {
-		reflect.ValueOf(3), "3",
-	}}
-	inst := getInstance(t, "foo")
-	b := ast.NewIdent("dummy")
-	for _, tc := range testCases {
-		ctx := inst.newContext()
-		t.Run("", func(t *testing.T) {
-			v := convert(ctx, newNode(b), tc.goVal)
-			got := debugStr(ctx, v)
-			if got != tc.want {
-				t.Errorf("got %q; want %q", got, tc.want)
-			}
-		})
-	}
-}
-
 func TestBuiltins(t *testing.T) {
 	test := func(pkg, expr string) []*bimport {
 		return []*bimport{&bimport{"",
diff --git a/cue/go.go b/cue/go.go
new file mode 100644
index 0000000..a45fbc6
--- /dev/null
+++ b/cue/go.go
@@ -0,0 +1,189 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+	"fmt"
+	"math/big"
+	"reflect"
+	"sort"
+	"strings"
+
+	"cuelang.org/go/cue/ast"
+	"github.com/cockroachdb/apd"
+)
+
+// This file contains functionality for converting Go to CUE.
+
+func convert(ctx *context, src source, x interface{}) evaluated {
+	switch v := x.(type) {
+	case evaluated:
+		return v
+	case nil:
+		return &nullLit{src.base()}
+	case ast.Expr:
+		x := newVisitorCtx(ctx, nil, nil, nil)
+		return ctx.manifest(x.walk(v))
+	case error:
+		return ctx.mkErr(src, v.Error())
+	case bool:
+		return &boolLit{src.base(), v}
+	case string:
+		return &stringLit{src.base(), v}
+	case []byte:
+		return &bytesLit{src.base(), v}
+	case int:
+		return toInt(ctx, src, int64(v))
+	case int8:
+		return toInt(ctx, src, int64(v))
+	case int16:
+		return toInt(ctx, src, int64(v))
+	case int32:
+		return toInt(ctx, src, int64(v))
+	case int64:
+		return toInt(ctx, src, int64(v))
+	case uint:
+		return toUint(ctx, src, uint64(v))
+	case uint8:
+		return toUint(ctx, src, uint64(v))
+	case uint16:
+		return toUint(ctx, src, uint64(v))
+	case uint32:
+		return toUint(ctx, src, uint64(v))
+	case uint64:
+		return toUint(ctx, src, uint64(v))
+	case float64:
+		r := newNum(src, floatKind)
+		r.v.SetString(fmt.Sprintf("%g", v))
+		return r
+	case *big.Int:
+		n := newNum(src, intKind)
+		n.v.Coeff.Set(v)
+		if v.Sign() < 0 {
+			n.v.Coeff.Neg(&n.v.Coeff)
+			n.v.Negative = true
+		}
+		return n
+	case *big.Rat:
+		n := newNum(src, numKind)
+		ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
+		if !v.IsInt() {
+			n.k = floatKind
+		}
+		return n
+	case *big.Float:
+		n := newNum(src, floatKind)
+		n.v.SetString(v.String())
+		return n
+	case *apd.Decimal:
+		n := newNum(src, floatKind|intKind)
+		n.v.Set(v)
+		if !n.isInt(ctx) {
+			n.k = floatKind
+		}
+		return n
+	case reflect.Value:
+		if v.CanInterface() {
+			return convert(ctx, src, v.Interface())
+		}
+
+	default:
+		value := reflect.ValueOf(v)
+		switch value.Kind() {
+		case reflect.Ptr:
+			if value.IsNil() {
+				return &nullLit{src.base()}
+			}
+			return convert(ctx, src, value.Elem().Interface())
+		case reflect.Struct:
+			obj := newStruct(src)
+			t := value.Type()
+			for i := 0; i < value.NumField(); i++ {
+				t := t.Field(i)
+				if t.PkgPath != "" {
+					continue
+				}
+				sub := convert(ctx, src, value.Field(i).Interface())
+				// leave errors like we do during normal evaluation or do we
+				// want to return the error?
+				name := t.Name
+				for _, s := range []string{"cue", "json", "protobuf"} {
+					if tag, ok := t.Tag.Lookup(s); ok {
+						if p := strings.Index(tag, ","); p >= 0 {
+							tag = tag[:p]
+						}
+						if tag != "" {
+							name = tag
+							break
+						}
+					}
+				}
+				f := ctx.strLabel(name)
+				obj.arcs = append(obj.arcs, arc{feature: f, v: sub})
+			}
+			sort.Sort(obj)
+			return obj
+
+		case reflect.Map:
+			obj := newStruct(src)
+			t := value.Type()
+			if t.Key().Kind() != reflect.String {
+				return ctx.mkErr(src, "builtin map key not a string, but unsupported type %s", t.Key().String())
+			}
+			keys := []string{}
+			for _, k := range value.MapKeys() {
+				keys = append(keys, k.String())
+			}
+			sort.Strings(keys)
+			for _, k := range keys {
+				sub := convert(ctx, src, value.MapIndex(reflect.ValueOf(k)).Interface())
+				// leave errors like we do during normal evaluation or do we
+				// want to return the error?
+				f := ctx.strLabel(k)
+				obj.arcs = append(obj.arcs, arc{feature: f, v: sub})
+			}
+			sort.Sort(obj)
+			return obj
+
+		case reflect.Slice, reflect.Array:
+			list := &list{baseValue: src.base()}
+			for i := 0; i < value.Len(); i++ {
+				x := convert(ctx, src, value.Index(i).Interface())
+				if isBottom(x) {
+					return x
+				}
+				list.a = append(list.a, x)
+			}
+			list.initLit()
+			// There is no need to set the type of the list, as the list will
+			// be of fixed size and all elements will already have a defined
+			// value.
+			return list
+		}
+	}
+	return ctx.mkErr(src, "builtin returned unsupported type %T", x)
+}
+
+func toInt(ctx *context, src source, x int64) evaluated {
+	n := newNum(src, intKind)
+	n.v.SetInt64(x)
+	return n
+}
+
+func toUint(ctx *context, src source, x uint64) evaluated {
+	n := newNum(src, floatKind)
+	n.v.Coeff.SetUint64(x)
+	return n
+}
diff --git a/cue/go_test.go b/cue/go_test.go
new file mode 100644
index 0000000..88895c3
--- /dev/null
+++ b/cue/go_test.go
@@ -0,0 +1,130 @@
+// Copyright 2019 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cue
+
+import (
+	"go/ast"
+	"math/big"
+	"reflect"
+	"testing"
+	"time"
+
+	"cuelang.org/go/cue/errors"
+)
+
+func TestConvert(t *testing.T) {
+	i34 := big.NewInt(34)
+	d34 := mkBigInt(34)
+	f34 := big.NewFloat(34.0000)
+	testCases := []struct {
+		goVal interface{}
+		want  string
+	}{{
+		nil, "null",
+	}, {
+		true, "true",
+	}, {
+		false, "false",
+	}, {
+		errors.New("oh noes"), "_|_(oh noes)",
+	}, {
+		"foo", `"foo"`,
+	}, {
+		3, "3",
+	}, {
+		uint(3), "3",
+	}, {
+		uint8(3), "3",
+	}, {
+		uint16(3), "3",
+	}, {
+		uint32(3), "3",
+	}, {
+		uint64(3), "3",
+	}, {
+		int8(-3), "-3",
+	}, {
+		int16(-3), "-3",
+	}, {
+		int32(-3), "-3",
+	}, {
+		int64(-3), "-3",
+	}, {
+		float64(3.1), "3.1",
+	}, {
+		&i34, "34",
+	}, {
+		&f34, "34",
+	}, {
+		&d34, "34",
+	}, {
+		[]int{1, 2, 3, 4}, "[1,2,3,4]",
+	}, {
+		[]interface{}{}, "[]",
+	}, {
+		map[string][]int{
+			"a": []int{1},
+			"b": []int{3, 4},
+		}, "<0>{a: [1], b: [3,4]}",
+	}, {
+		map[int]int{}, "_|_(builtin map key not a string, but unsupported type int)",
+	}, {
+		map[int]int{1: 2}, "_|_(builtin map key not a string, but unsupported type int)",
+	}, {
+		struct {
+			a int
+			b int
+		}{3, 4},
+		"<0>{}",
+	}, {
+		struct {
+			A int
+			B int
+		}{3, 4},
+		"<0>{A: 3, B: 4}",
+	}, {
+		struct {
+			A int `json:"a"`
+			B int `yaml:"b"`
+		}{3, 4},
+		"<0>{a: 3, b: 4}",
+	}, {
+		struct {
+			A int `json:",bb" yaml:"" protobuf:"aa"`
+			B int `yaml:"cc" json:"bb" protobuf:"aa"`
+		}{3, 4},
+		"<0>{aa: 3, bb: 4}",
+	}, {
+		&struct{ A int }{3}, "<0>{A: 3}",
+	}, {
+		(*struct{ A int })(nil), "null",
+	}, {
+		reflect.ValueOf(3), "3",
+	}, {
+		time.Date(2019, 4, 1, 0, 0, 0, 0, time.UTC), `"2019-04-01T00:00:00Z"`,
+	}}
+	inst := getInstance(t, "foo")
+	b := ast.NewIdent("dummy")
+	for _, tc := range testCases {
+		ctx := inst.newContext()
+		t.Run("", func(t *testing.T) {
+			v := convert(ctx, newNode(b), tc.goVal)
+			got := debugStr(ctx, v)
+			if got != tc.want {
+				t.Errorf("got %q; want %q", got, tc.want)
+			}
+		})
+	}
+}