cue: implement parsing and formatting of definitions

- allow :: in addition to : in fields
- allow embedding in structs (not just top-level)
  types are checked at compile time
- does not yet handle ExpressionLabels

Not handled yet by the interpreter itself.

Issue #40

Change-Id: I9bc8ef987fb423a31f9979972208c8924bf48399
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2871
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast/ast.go b/cue/ast/ast.go
index 4755321..deb5915 100644
--- a/cue/ast/ast.go
+++ b/cue/ast/ast.go
@@ -86,6 +86,7 @@
 func (*BadDecl) declNode()           {}
 func (*EmbedDecl) declNode()         {}
 func (*Alias) declNode()             {}
+func (*Ellipsis) declNode()          {}
 
 // Not technically declarations, but appearing at the same level.
 func (*Package) declNode()      {}
@@ -285,8 +286,10 @@
 	Label    Label // must have at least one element.
 	Optional token.Pos
 
-	// No colon: Value must be an StructLit with one field.
-	Colon token.Pos
+	// No TokenPos: Value must be an StructLit with one field.
+	TokenPos token.Pos
+	Token    token.Token // ':' or '::', ILLEGAL implies ':'
+
 	Value Expr // the value associated with this field.
 
 	Attrs []*Attribute
@@ -300,6 +303,9 @@
 	return d.Value.End()
 }
 
+// TODO: make Alias a type of Field. This is possible now we have different
+// separator types.
+
 // An Alias binds another field to the alias name in the current struct.
 type Alias struct {
 	comments
@@ -419,8 +425,9 @@
 // A ForClause node represents a for clause in a comprehension.
 type ForClause struct {
 	comments
-	For    token.Pos
-	Key    *Ident // allow pattern matching?
+	For token.Pos
+	Key *Ident // allow pattern matching?
+	// TODO: change to Comma
 	Colon  token.Pos
 	Value  *Ident // allow pattern matching?
 	In     token.Pos
diff --git a/cue/format/node.go b/cue/format/node.go
index 34b45f9..05bd943 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -50,6 +50,10 @@
 	return fmt.Errorf("cue/format: unsupported node type %T", node)
 }
 
+func isRegularField(tok token.Token) bool {
+	return tok == token.ILLEGAL || tok == token.COLON
+}
+
 // Helper functions for common node lists. They may be empty.
 
 func (f *formatter) walkDeclList(list []ast.Decl) {
@@ -131,10 +135,12 @@
 		// shortcut single-element structs.
 		lastSize := len(f.labelBuf)
 		f.labelBuf = f.labelBuf[:0]
+		regular := isRegularField(n.Token)
 		first, opt := n.Label, n.Optional != token.NoPos
-		// If the field has a valid position, we assume that an unspecified
-		// Lbrace does not signal the intend to collapse fields.
-		for n.Label.Pos().IsValid() || f.printer.cfg.simplify {
+
+		// If the label has a valid position, we assume that an unspecified
+		// Lbrace signals the intend to collapse fields.
+		for (n.Label.Pos().IsValid() || f.printer.cfg.simplify) && regular {
 			obj, ok := n.Value.(*ast.StructLit)
 			if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) || len(n.Attrs) > 0 {
 				break
@@ -156,6 +162,9 @@
 			if !ok || len(mem.Attrs) > 0 {
 				break
 			}
+			if !isRegularField(mem.Token) {
+				break
+			}
 			entry := labelEntry{mem.Label, mem.Optional != token.NoPos}
 			f.labelBuf = append(f.labelBuf, entry)
 			n = mem
@@ -164,6 +173,9 @@
 		if lastSize != len(f.labelBuf) {
 			f.print(formfeed)
 		}
+		if !regular && first.Pos().RelPos() < token.Newline {
+			f.print(newline, nooverride)
+		}
 
 		f.before(nil)
 		f.label(first, opt)
@@ -179,7 +191,11 @@
 			tab = blank
 		}
 
-		f.print(n.Colon, token.COLON, tab)
+		if isRegularField(n.Token) {
+			f.print(n.TokenPos, token.COLON, tab)
+		} else {
+			f.print(blank, nooverride, n.Token, tab)
+		}
 		if n.Value != nil {
 			switch n.Value.(type) {
 			case *ast.ListComprehension, *ast.ListLit, *ast.StructLit:
@@ -250,6 +266,9 @@
 		f.expr(n.Expr)
 		f.print(newline, newsection, nooverride) // force newline
 
+	case *ast.Ellipsis:
+		f.ellipsis(n)
+
 	case *ast.Alias:
 		f.expr(n.Ident)
 		f.print(blank, n.Equal, token.BIND, blank)
@@ -340,6 +359,13 @@
 	}
 }
 
+func (f *formatter) ellipsis(x *ast.Ellipsis) {
+	f.print(x.Ellipsis, token.ELLIPSIS)
+	if x.Type != nil && !isTop(x.Type) {
+		f.expr(x.Type)
+	}
+}
+
 func (f *formatter) expr(x ast.Expr) {
 	const depth = 1
 	f.expr1(x, token.LowestPrec, depth)
@@ -465,10 +491,7 @@
 		f.print(noblank, x.Rbrack, token.RBRACK)
 
 	case *ast.Ellipsis:
-		f.print(x.Ellipsis, token.ELLIPSIS)
-		if x.Type != nil && !isTop(x.Type) {
-			f.expr(x.Type)
-		}
+		f.ellipsis(x)
 
 	case *ast.ListComprehension:
 		f.print(x.Lbrack, token.LBRACK, blank, indent)
diff --git a/cue/format/node_test.go b/cue/format/node_test.go
new file mode 100644
index 0000000..12a4892
--- /dev/null
+++ b/cue/format/node_test.go
@@ -0,0 +1,62 @@
+// Copyright 2019 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 format
+
+import (
+	"strings"
+	"testing"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/token"
+)
+
+// TestInvalidAST verifies behavior for various invalid AST inputs. In some
+// cases it is okay to be permissive, as long as the output is correct.
+func TestInvalidAST(t *testing.T) {
+	ident := func(s string) *ast.Ident {
+		return &ast.Ident{NamePos: token.NoSpace.Pos(), Name: s}
+	}
+	testCases := []struct {
+		desc string
+		node ast.Node
+		out  string
+	}{{
+		desc: "label sequence for definition",
+		node: &ast.Field{Label: ident("foo"), Value: &ast.StructLit{
+			Elts: []ast.Decl{&ast.Field{
+				Label: ident("bar"),
+				Token: token.ISA,
+				Value: &ast.StructLit{},
+			}},
+		}},
+		// Force a new struct.
+		out: `foo: {
+	bar :: {}
+}`,
+	}}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			b, err := Node(tc.node)
+			if err != nil {
+				t.Fatal(err)
+			}
+			got := string(b)
+			want := strings.TrimSpace(tc.out)
+			if got != want {
+				t.Errorf("\ngot  %v;\nwant %v", got, want)
+			}
+		})
+	}
+}
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index 65aa717..275ccb2 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -28,6 +28,17 @@
 		aaa: 10
 	}
 
+	someDefinition :: {
+		embedding
+
+		field: 2
+	}
+
+	openDef :: {
+		a: int
+		...
+	}
+
 	attrs: {
 		a:    8 @go(A) // comment
 		aa:   8 @go(A) // comment
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index 1d656ea..e44be2c 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -28,6 +28,17 @@
         aaa: 10
     }
 
+    someDefinition :: {
+        embedding
+
+        field: 2
+    }
+
+    openDef :: {
+        a: int
+        ...
+    }
+
     attrs: {
         a: 8 @go(A) // comment
         aa: 8 @go(A) // comment
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 32e104e..43ef86e 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -638,34 +638,25 @@
 		Rparen: rparen}
 }
 
-func (p *parser) parseFieldList(allowEmit bool) (list []ast.Decl) {
+func (p *parser) parseFieldList() (list []ast.Decl) {
 	if p.trace {
 		defer un(trace(p, "FieldList"))
 	}
-	origEmit := allowEmit
 	p.openList()
 	defer p.closeList()
 
-	for p.tok != token.RBRACE && p.tok != token.EOF {
-		d := p.parseField(allowEmit)
-		if e, ok := d.(*ast.EmbedDecl); ok {
-			if origEmit && !allowEmit {
-				p.errf(p.pos, "only one emit allowed at top level")
-			}
-			if !origEmit || !allowEmit {
-				d = &ast.BadDecl{From: e.Pos(), To: e.End()}
-				for _, cg := range e.Comments() {
-					d.AddComment(cg)
-				}
-			}
-			// uncomment to only allow one emit per top-level
-			// allowEmit = false
-		}
-		list = append(list, d)
+	for p.tok != token.RBRACE && p.tok != token.ELLIPSIS && p.tok != token.EOF {
+		list = append(list, p.parseField())
+	}
+
+	if p.tok == token.ELLIPSIS {
+		list = append(list, &ast.Ellipsis{Ellipsis: p.pos})
+		p.next()
 	}
 	return
 }
-func (p *parser) parseField(allowEmit bool) (decl ast.Decl) {
+
+func (p *parser) parseField() (decl ast.Decl) {
 	if p.trace {
 		defer un(trace(p, "Field"))
 	}
@@ -678,6 +669,7 @@
 	this := &ast.Field{Label: nil}
 	m := this
 
+	multipleLabels := false
 	allowComprehension := true
 
 	for i := 0; ; i++ {
@@ -686,9 +678,6 @@
 		expr, ok := p.parseLabel(m)
 
 		if !ok {
-			if !allowEmit {
-				p.errf(pos, "expected label, found %s", tok)
-			}
 			if expr == nil {
 				expr = p.parseExpr()
 			}
@@ -718,9 +707,14 @@
 			p.next()
 		}
 
-		if p.tok == token.COLON {
+		_ = multipleLabels
+		if p.tok == token.COLON || p.tok == token.ISA {
+			if p.tok == token.ISA && multipleLabels {
+				p.errf(p.pos, "more than one label before '::' (only one allowed)")
+			}
 			break
 		}
+		multipleLabels = true
 
 		// TODO: consider disallowing comprehensions with more than one label.
 		// This can be a bit awkward in some cases, but it would naturally
@@ -730,7 +724,7 @@
 
 		switch p.tok {
 		default:
-			if !allowEmit || p.tok != token.COMMA {
+			if p.tok != token.COMMA {
 				p.errorExpected(p.pos, "label or ':'")
 			}
 			switch tok {
@@ -747,12 +741,14 @@
 			m.Value = &ast.StructLit{Elts: []ast.Decl{field}}
 			m = field
 		}
-
-		allowEmit = false
 	}
 
-	this.Colon = p.pos
-	p.expect(token.COLON)
+	m.TokenPos = p.pos
+	m.Token = p.tok
+	if p.tok != token.COLON && p.tok != token.ISA {
+		p.errorExpected(pos, "':' or '::'")
+	}
+	p.next() // : or ::
 	m.Value = p.parseRHS()
 
 	p.openList()
@@ -879,7 +875,7 @@
 	p.exprLev++
 	var elts []ast.Decl
 	if p.tok != token.RBRACE {
-		elts = p.parseFieldList(false)
+		elts = p.parseFieldList()
 	}
 	p.exprLev--
 
@@ -1354,7 +1350,7 @@
 		if p.mode&importsOnlyMode == 0 {
 			// rest of package decls
 			// TODO: loop and allow multiple expressions.
-			decls = append(decls, p.parseFieldList(true)...)
+			decls = append(decls, p.parseFieldList()...)
 			p.expect(token.EOF)
 		}
 	}
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index 557233e..c73ec5a 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -82,6 +82,24 @@
 		`,
 		`a: true, b?: "2", c?: 3, "g\("en")"?: 4`,
 	}, {
+		"definition",
+		`Def :: {
+			 b: "2"
+			 c: 3
+
+			 embedding
+		}
+		`,
+		`Def :: {b: "2", c: 3, embedding}`,
+	}, {
+		"ellipsis in structs",
+		`Def :: {
+			b: "2"
+			...
+		}
+		`,
+		`Def :: {b: "2", ...}`,
+	}, {
 		"emitted referencing non-emitted",
 		`a: 1
 		 b: "2"
diff --git a/cue/parser/print.go b/cue/parser/print.go
index 9849e11..7fd2dda 100644
--- a/cue/parser/print.go
+++ b/cue/parser/print.go
@@ -137,7 +137,12 @@
 			out += "?"
 		}
 		if v.Value != nil {
-			out += ": "
+			switch v.Token {
+			case token.ILLEGAL, token.COLON:
+				out += ": "
+			default:
+				out += fmt.Sprintf(" %s ", v.Token)
+			}
 			out += debugStr(v.Value)
 			for _, a := range v.Attrs {
 				out += " "
diff --git a/cue/parser/testdata/commas.src b/cue/parser/testdata/commas.src
index 3e93a54..3865c39 100644
--- a/cue/parser/testdata/commas.src
+++ b/cue/parser/testdata/commas.src
@@ -33,3 +33,4 @@
 	3
 ]
 
+not allowed :: /* ERROR "more than one label before '::'" */ {}
diff --git a/cue/parser/testdata/test.cue b/cue/parser/testdata/test.cue
index cc048d5..cf9ef15 100644
--- a/cue/parser/testdata/test.cue
+++ b/cue/parser/testdata/test.cue
@@ -1,4 +1,3 @@
-
 import "math"
 
 foo: 1
diff --git a/cue/scanner/scanner.go b/cue/scanner/scanner.go
index 19881fb..abf4c3e 100644
--- a/cue/scanner/scanner.go
+++ b/cue/scanner/scanner.go
@@ -864,7 +864,12 @@
 			insertEOL = true
 			tok, lit = s.scanAttribute()
 		case ':':
-			tok = token.COLON
+			if s.ch == ':' {
+				s.next()
+				tok = token.ISA
+			} else {
+				tok = token.COLON
+			}
 		case ';':
 			tok = token.SEMICOLON
 			insertEOL = true
diff --git a/cue/scanner/scanner_test.go b/cue/scanner/scanner_test.go
index b182e21..3efea8c 100644
--- a/cue/scanner/scanner_test.go
+++ b/cue/scanner/scanner_test.go
@@ -159,6 +159,7 @@
 	{token.RBRACK, "]", operator},
 	{token.RBRACE, "}", operator},
 	{token.COLON, ":", operator},
+	{token.ISA, "::", operator},
 
 	// Keywords
 	{token.TRUE, "true", keyword},
@@ -399,6 +400,7 @@
 	"}^\n",
 	"}}^\n",
 	":\n",
+	"::\n",
 	";^\n",
 
 	"true^\n",
@@ -825,7 +827,7 @@
 			A: 1 // foo
 		}
 
-		b: {
+		b :: {
 			B: 2
 			// foo
 		}
diff --git a/cue/token/token.go b/cue/token/token.go
index 3edfed1..5e15443 100644
--- a/cue/token/token.go
+++ b/cue/token/token.go
@@ -87,6 +87,7 @@
 	RBRACE    // }
 	SEMICOLON // ;
 	COLON     // :
+	ISA       // ::
 	OPTION    // ?
 	operatorEnd
 
@@ -160,6 +161,7 @@
 	RBRACE:    "}",
 	SEMICOLON: ";",
 	COLON:     ":",
+	ISA:       "::",
 	OPTION:    "?",
 
 	BOTTOM: "_|_",