cue: add Kind.String method
Change-Id: If7b149cebf514c22b7c446ef85999adea200a4e0
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3741
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/types.go b/cue/types.go
index e22a9f1..4fe7942 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -22,6 +22,7 @@
"io"
"math"
"math/big"
+ "math/bits"
"strconv"
"strings"
"unicode"
@@ -66,10 +67,68 @@
nextKind
+ // _numberKind is used as a implementation detail inside
+ // Kind.String to indicate NumberKind.
+ _numberKind
+
// NumberKind represents any kind of number.
NumberKind = IntKind | FloatKind
)
+// String returns the representation of the Kind as
+// a CUE expression. For example:
+//
+// (IntKind|ListKind).String()
+//
+// will return:
+//
+// (int|[...])
+func (k Kind) String() string {
+ if k == BottomKind {
+ return "_|_"
+ }
+ if (k & NumberKind) == NumberKind {
+ k = (k &^ NumberKind) | _numberKind
+ }
+ var buf strings.Builder
+ multiple := bits.OnesCount(uint(k)) > 1
+ if multiple {
+ buf.WriteByte('(')
+ }
+ for count := 0; ; count++ {
+ n := bits.TrailingZeros(uint(k))
+ if n == bits.UintSize {
+ break
+ }
+ bit := Kind(1 << uint(n))
+ k &^= bit
+ s, ok := kindStrs[bit]
+ if !ok {
+ s = fmt.Sprintf("bad(%d)", n)
+ }
+ if count > 0 {
+ buf.WriteByte('|')
+ }
+ buf.WriteString(s)
+ }
+ if multiple {
+ buf.WriteByte(')')
+ }
+ return buf.String()
+}
+
+var kindStrs = map[Kind]string{
+ NullKind: "null",
+ BoolKind: "bool",
+ IntKind: "int",
+ FloatKind: "float",
+ StringKind: "string",
+ BytesKind: "bytes",
+ StructKind: "{...}",
+ ListKind: "[...]",
+ _numberKind: "number",
+}
+
// An structValue represents a JSON object.
//
// TODO: remove
diff --git a/cue/types_test.go b/cue/types_test.go
index d225375..cc991f1 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -764,7 +764,7 @@
// length: "2",
}, {
input: "3",
- length: "_|_(len not supported for type 4)", // TODO: fix kind name
+ length: "_|_(len not supported for type int)",
}}
for _, tc := range testCases {
t.Run(tc.input, func(t *testing.T) {
@@ -2187,3 +2187,54 @@
})
}
}
+
+func TestKindString(t *testing.T) {
+ testCases := []struct {
+ input Kind
+ want string
+ }{{
+ input: BottomKind,
+ want: "_|_",
+ }, {
+ input: IntKind | ListKind,
+ want: `(int|[...])`,
+ }, {
+ input: NullKind,
+ want: "null",
+ }, {
+ input: IntKind,
+ want: "int",
+ }, {
+ input: FloatKind,
+ want: "float",
+ }, {
+ input: StringKind,
+ want: "string",
+ }, {
+ input: BytesKind,
+ want: "bytes",
+ }, {
+ input: StructKind,
+ want: "{...}",
+ }, {
+ input: ListKind,
+ want: "[...]",
+ }, {
+ input: NumberKind,
+ want: "number",
+ }, {
+ input: BoolKind | NumberKind | ListKind,
+ want: "(bool|[...]|number)",
+ }, {
+ input: 1 << 20,
+ want: "bad(20)",
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.want, func(t *testing.T) {
+ got := tc.input.String()
+ if got != tc.want {
+ t.Errorf("\n got %v;\nwant %v", got, tc.want)
+ }
+ })
+ }
+}