internal/core/export: support byte interpolations

Change-Id: I4dff3dafd0f836725c06a2882ca363b316a70d3d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7904
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/export/adt.go b/internal/core/export/adt.go
index 9cece16..1522f58 100644
--- a/internal/core/export/adt.go
+++ b/internal/core/export/adt.go
@@ -223,19 +223,38 @@
 		return &ast.SliceExpr{X: e.expr(x.X), Low: lo, High: hi}
 
 	case *adt.Interpolation:
+		var (
+			tripple    = `"""`
+			openQuote  = `"`
+			closeQuote = `"`
+			f          = literal.String
+		)
+		if x.K&adt.BytesKind != 0 {
+			tripple = `'''`
+			openQuote = `'`
+			closeQuote = `'`
+			f = literal.Bytes
+		}
+		toString := func(v adt.Expr) string {
+			str := ""
+			switch x := v.(type) {
+			case *adt.String:
+				str = x.Str
+			case *adt.Bytes:
+				str = string(x.B)
+			}
+			return str
+		}
 		t := &ast.Interpolation{}
-		f := literal.String.WithGraphicOnly() // TODO: also support bytes
-		openQuote := `"`
-		closeQuote := `"`
+		f = f.WithGraphicOnly()
 		indent := ""
 		// TODO: mark formatting in interpolation itself.
 		for i := 0; i < len(x.Parts); i += 2 {
-			str := x.Parts[i].(*adt.String).Str
-			if strings.IndexByte(str, '\n') >= 0 {
+			if strings.IndexByte(toString(x.Parts[i]), '\n') >= 0 {
 				f = f.WithTabIndent(len(e.stack))
 				indent = strings.Repeat("\t", len(e.stack))
-				openQuote = `"""` + "\n" + indent
-				closeQuote = `"""`
+				openQuote = tripple + "\n" + indent
+				closeQuote = tripple
 				break
 			}
 		}
@@ -247,7 +266,7 @@
 			} else {
 				// b := strings.Builder{}
 				buf := []byte(prefix)
-				str := elem.(*adt.String).Str
+				str := toString(elem)
 				buf = f.AppendEscaped(buf, str)
 				if i == len(x.Parts)-1 {
 					if len(closeQuote) > 1 {
diff --git a/internal/core/export/testdata/strings.txtar b/internal/core/export/testdata/strings.txtar
index 2e5967c..01c3fe4 100644
--- a/internal/core/export/testdata/strings.txtar
+++ b/internal/core/export/testdata/strings.txtar
@@ -8,6 +8,13 @@
 
 c: d: a
 
+bin1: '\(a)'
+
+bin2: '''
+	multi
+	\(b)
+	'''
+
 -- out/definition --
 a: """
 	multi
@@ -17,12 +24,19 @@
 c: {
 	d: a
 }
+bin1: '\(a)'
+bin2: '''
+		multi
+		\(b)
+		'''
 -- out/doc --
 []
 [a]
 [b]
 [c]
 [c d]
+[bin1]
+[bin2]
 -- out/value --
 == Simplified
 {
@@ -40,6 +54,15 @@
 			line
 			"""
 	}
+	bin1: '''
+		multi
+		line
+		'''
+	bin2: '''
+		multi
+		message: multi
+		line!
+		'''
 }
 == Raw
 {
@@ -57,6 +80,15 @@
 			line
 			"""
 	}
+	bin1: '''
+		multi
+		line
+		'''
+	bin2: '''
+		multi
+		message: multi
+		line!
+		'''
 }
 == Final
 {
@@ -74,6 +106,15 @@
 			line
 			"""
 	}
+	bin1: '''
+		multi
+		line
+		'''
+	bin2: '''
+		multi
+		message: multi
+		line!
+		'''
 }
 == All
 {
@@ -91,4 +132,13 @@
 			line
 			"""
 	}
+	bin1: '''
+		multi
+		line
+		'''
+	bin2: '''
+		multi
+		message: multi
+		line!
+		'''
 }