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)
+ }
+ })
+ }
+}