internal: hoist attribute parsing logic

Change-Id: I76b2163dc717aba19a51b9daa0544792c7a8d599
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4900
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/attrs_test.go b/internal/attrs_test.go
new file mode 100644
index 0000000..01cba60
--- /dev/null
+++ b/internal/attrs_test.go
@@ -0,0 +1,104 @@
+// Copyright 2020 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 internal
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"cuelang.org/go/cue/token"
+)
+
+func TestAttributeBody(t *testing.T) {
+	testdata := []struct {
+		in, out string
+		err     string
+	}{{
+		in:  "",
+		out: "[{ 0}]",
+	}, {
+		in:  "bb",
+		out: "[{bb 0}]",
+	}, {
+		in:  "a,",
+		out: "[{a 0} { 0}]",
+	}, {
+		in:  `"a",`,
+		out: "[{a 0} { 0}]",
+	}, {
+		in:  "a,b",
+		out: "[{a 0} {b 0}]",
+	}, {
+		in:  `foo,"bar",#"baz"#`,
+		out: "[{foo 0} {bar 0} {baz 0}]",
+	}, {
+		in:  `bar=str`,
+		out: "[{bar=str 3}]",
+	}, {
+		in:  `bar="str"`,
+		out: "[{bar=str 3}]",
+	}, {
+		in:  `foo.bar="str"`,
+		out: "[{foo.bar=str 7}]",
+	}, {
+		in:  `bar=,baz=`,
+		out: "[{bar= 3} {baz= 3}]",
+	}, {
+		in:  `foo=1,bar="str",baz=free form`,
+		out: "[{foo=1 3} {bar=str 3} {baz=free form 3}]",
+	}, {
+		in: `"""
+		"""`,
+		out: "[{ 0}]",
+	}, {
+		in: `#'''
+			\#x20
+			'''#`,
+		out: "[{  0}]",
+	}, {
+		in:  "'' ,b",
+		err: "invalid attribute",
+	}, {
+		in:  "' ,b",
+		err: "not terminated",
+	}, {
+		in:  `"\ "`,
+		err: "invalid attribute",
+	}, {
+		in:  `# `,
+		err: "invalid attribute",
+	}}
+	for _, tc := range testdata {
+		t.Run(tc.in, func(t *testing.T) {
+			pa := ParseAttrBody(token.NoPos, tc.in)
+			err := pa.Err
+
+			if tc.err != "" {
+				if !strings.Contains(err.Error(), tc.err) {
+					t.Errorf("error was %v; want %v", err, tc.err)
+				}
+				return
+			}
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if got := fmt.Sprint(pa.Fields); got != tc.out {
+				t.Errorf("got %v; want %v", got, tc.out)
+			}
+		})
+	}
+}