pkg/strings: add ByteAt, ByteSlice, and Runes

To replace indexing and slicing of
string types.

Change-Id: I040966df5cd7a0367dac73bb1920ec81a42ff29a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2879
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index a2c9c8a..a9c467f 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"math/big"
+	"strconv"
 	"strings"
 	"testing"
 )
@@ -141,6 +142,15 @@
 		test("strings", `strings.Join([1, 2], " ")`),
 		`_|_(invalid list element 0 in argument 0 to strings.Join: cannot use value 1 (type int) as string)`,
 	}, {
+		test("strings", `strings.ByteAt("a", 0)`),
+		strconv.Itoa('a'),
+	}, {
+		test("strings", `strings.ByteSlice("Hello", 2, 5)`),
+		`'llo'`,
+	}, {
+		test("strings", `strings.Runes("Café")`),
+		strings.Replace(fmt.Sprint([]rune{'C', 'a', 'f', 'é'}), " ", ",", -1),
+	}, {
 		test("math/bits", `bits.Or(0x8, 0x1)`),
 		`9`,
 	}, {
diff --git a/cue/builtins.go b/cue/builtins.go
index 40d446f..1fc722d 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -1977,6 +1977,42 @@
 	},
 	"strings": &builtinPkg{
 		native: []*builtin{{
+			Name:   "ByteAt",
+			Params: []kind{stringKind, intKind},
+			Result: intKind,
+			Func: func(c *callCtxt) {
+				b, i := c.bytes(0), c.int(1)
+				c.ret, c.err = func() (interface{}, error) {
+					if i < 0 || i >= len(b) {
+						return 0, fmt.Errorf("index out of range")
+					}
+					return b[i], nil
+				}()
+			},
+		}, {
+			Name:   "ByteSlice",
+			Params: []kind{stringKind, intKind, intKind},
+			Result: stringKind,
+			Func: func(c *callCtxt) {
+				b, start, end := c.bytes(0), c.int(1), c.int(2)
+				c.ret, c.err = func() (interface{}, error) {
+					if start < 0 || start > end || end > len(b) {
+						return nil, fmt.Errorf("index out of range")
+					}
+					return b[start:end], nil
+				}()
+			},
+		}, {
+			Name:   "Runes",
+			Params: []kind{stringKind},
+			Result: listKind,
+			Func: func(c *callCtxt) {
+				s := c.string(0)
+				c.ret = func() interface{} {
+					return []rune(s)
+				}()
+			},
+		}, {
 			Name:   "MinRunes",
 			Params: []kind{stringKind, intKind},
 			Result: boolKind,
diff --git a/pkg/strings/manual.go b/pkg/strings/manual.go
index 849a0e3..de3436d 100644
--- a/pkg/strings/manual.go
+++ b/pkg/strings/manual.go
@@ -26,10 +26,33 @@
 package strings
 
 import (
+	"fmt"
 	"strings"
 	"unicode"
 )
 
+// ByteAt reports the ith byte of the underlying strings or byte.
+func ByteAt(b []byte, i int) (byte, error) {
+	if i < 0 || i >= len(b) {
+		return 0, fmt.Errorf("index out of range")
+	}
+	return b[i], nil
+}
+
+// ByteSlice reports the bytes of the underlying string data from the start
+// index up to but not including the end index.
+func ByteSlice(b []byte, start, end int) ([]byte, error) {
+	if start < 0 || start > end || end > len(b) {
+		return nil, fmt.Errorf("index out of range")
+	}
+	return b[start:end], nil
+}
+
+// Runes returns the Unicode code points of the given string.
+func Runes(s string) []rune {
+	return []rune(s)
+}
+
 // MinRunes reports whether the number of runes (Unicode codepoints) in a string
 // is at least a certain minimum. MinRunes can be used a a field constraint to
 // except all strings for which this property holds.