cue/parser: support string selector labels

Allowed by the spec, but previously unimplemented.

Change-Id: I2fb7aad303cdfac8862a0744b80d524cb749f5b3
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7342
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 97ea172..a7acb12 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -660,8 +660,8 @@
 
 // A SelectorExpr node represents an expression followed by a selector.
 type SelectorExpr struct {
-	X   Expr   // expression
-	Sel *Ident // field selector
+	X   Expr  // expression
+	Sel Label // field selector
 
 	comments
 	expr
diff --git a/cue/format/node.go b/cue/format/node.go
index 367003c..ebbb64f 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -909,11 +909,13 @@
 	f.expr1(x.X, token.HighestPrec, depth)
 	f.print(token.PERIOD)
 	if x.Sel.Pos().IsNewline() {
-		f.print(indent, formfeed, x.Sel.Pos(), x.Sel)
+		f.print(indent, formfeed)
+		f.expr(x.Sel.(ast.Expr))
 		f.print(unindent)
 		return true
 	}
-	f.print(x.Sel.Pos(), x.Sel)
+	f.print(noblank)
+	f.expr(x.Sel.(ast.Expr))
 	return false
 }
 
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index be50d3d..b7e4457 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -213,4 +213,14 @@
 	]
 
 	foo: bar
+
+	a: "foo-bar": 3
+	b: a."foo-bar"
+	c: a."foo-bar".b
+	d: a.
+		"foo-bar"
+	e: a.
+		"foo-bar".
+		b
+	f: 2
 }
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index a1be441..c0898ee 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -209,4 +209,14 @@
     ]
 
     foo : bar
+
+    a: "foo-bar": 3
+    b: a."foo-bar"
+    c: a. "foo-bar" . b
+    d: a.
+        "foo-bar"
+    e: a.
+        "foo-bar".
+                b
+    f: 2
 }
\ No newline at end of file
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index b0b1431..dbdf0f3 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -1361,6 +1361,21 @@
 					X:   p.checkExpr(x),
 					Sel: p.parseIdent(),
 				}
+			case token.STRING:
+				if strings.HasPrefix(p.lit, `"`) && !strings.HasPrefix(p.lit, `""`) {
+					str := &ast.BasicLit{
+						ValuePos: p.pos,
+						Kind:     token.STRING,
+						Value:    p.lit,
+					}
+					p.next()
+					x = &ast.SelectorExpr{
+						X:   p.checkExpr(x),
+						Sel: str,
+					}
+					break
+				}
+				fallthrough
 			default:
 				pos := p.pos
 				p.errorExpected(pos, "selector")
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index a829192..42c9c8e 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -117,6 +117,23 @@
 		`{ V1, V2 }`,
 		`{V1, V2}`,
 	}, {
+		"selectors",
+		`a.b. "str"`,
+		`a.b."str"`,
+	}, {
+		"selectors",
+		`a.b. "str"`,
+		`a.b."str"`,
+	}, {
+		"faulty bytes selector",
+		`a.b.'str'`,
+		"a.b._\nexpected selector, found 'STRING' 'str'",
+	}, {
+		"faulty multiline string selector",
+		`a.b."""
+			"""`,
+		"a.b._\nexpected selector, found 'STRING' \"\"\"\n\t\t\t\"\"\"",
+	}, {
 		"expression embedding",
 		`#Def: {
 			a.b.c
@@ -697,8 +714,8 @@
 // *BadExpr.
 func TestIncompleteSelection(t *testing.T) {
 	for _, src := range []string{
-		"{ a: fmt. }",           // at end of object
-		"{ a: fmt.\n\"a\": x }", // not at end of struct
+		"{ a: fmt. }",         // at end of object
+		"{ a: fmt.\n0.0: x }", // not at end of struct
 	} {
 		t.Run("", func(t *testing.T) {
 			f, err := ParseFile("", src)
diff --git a/cue/testdata/eval/selectors.txtar b/cue/testdata/eval/selectors.txtar
index 08be88e..4fdc64e 100644
--- a/cue/testdata/eval/selectors.txtar
+++ b/cue/testdata/eval/selectors.txtar
@@ -1,14 +1,21 @@
 -- in.cue --
-	a: 1
-	b: a + 1
-	d: {
-		x: _
-		y: b + x
-	}
-	e: d & {
-		x: 5
-	}
-
+a: 1
+b: a + 1
+d: {
+  x: _
+  y: b + x
+}
+e: d & {
+  x: 5
+}
+f: {
+  a: "foo-bar": 3
+  b: a."foo-bar"
+}
+g: {
+  a: "foo-bar": c: 3
+  b: a."foo-bar".c
+}
 -- out/eval --
 (struct){
   a: (int){ 1 }
@@ -24,6 +31,20 @@
     x: (int){ 5 }
     y: (int){ 7 }
   }
+  f: (struct){
+    a: (struct){
+      "foo-bar": (int){ 3 }
+    }
+    b: (int){ 3 }
+  }
+  g: (struct){
+    a: (struct){
+      "foo-bar": (struct){
+        c: (int){ 3 }
+      }
+    }
+    b: (int){ 3 }
+  }
 }
 -- out/compile --
 --- in.cue
@@ -37,4 +58,18 @@
   e: (〈0;d〉 & {
     x: 5
   })
+  f: {
+    a: {
+      "foo-bar": 3
+    }
+    b: 〈0;a〉."foo-bar"
+  }
+  g: {
+    a: {
+      "foo-bar": {
+        c: 3
+      }
+    }
+    b: 〈0;a〉."foo-bar".c
+  }
 }
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 5ad3380..131c3b3 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -583,7 +583,7 @@
 
 func (x *SelectorExpr) resolve(c *OpContext) *Vertex {
 	n := c.node(x.X, Partial)
-	return c.lookup(n, x.Src.Sel.NamePos, x.Sel)
+	return c.lookup(n, x.Src.Sel.Pos(), x.Sel)
 }
 
 // IndexExpr is like a selector, but selects an index.
diff --git a/internal/core/export/adt.go b/internal/core/export/adt.go
index bff96e7..8013a05 100644
--- a/internal/core/export/adt.go
+++ b/internal/core/export/adt.go
@@ -155,7 +155,7 @@
 	case *adt.SelectorExpr:
 		return &ast.SelectorExpr{
 			X:   e.expr(x.X),
-			Sel: e.ident(x.Sel),
+			Sel: e.stringLabel(x.Sel),
 		}
 
 	case *adt.IndexExpr: