cue/parser: use a more robust exit mechanism

The panic raised when there are too many errors could be
overridden by unmatched closed lists, and the like, causing
the sentinel used of bailing out to be overridden.

The exit state is now explicitly marked in the parser, resulting
in a more robust exit mechanism.

Change-Id: I9eadd1076330539d3b228bc3016fa7abb5dfb3e8
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/1641
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/parser/interface.go b/cue/parser/interface.go
index a66fcbf..e2bfb82 100644
--- a/cue/parser/interface.go
+++ b/cue/parser/interface.go
@@ -147,11 +147,8 @@
 
 	var pp parser
 	defer func() {
-		if e := recover(); e != nil {
-			// resume same panic if it's not a bailout
-			if _, ok := e.(bailout); !ok {
-				panic(e)
-			}
+		if pp.panicking {
+			recover()
 		}
 
 		// set result values
@@ -198,11 +195,8 @@
 
 	var p parser
 	defer func() {
-		if e := recover(); e != nil {
-			// resume same panic if it's not a bailout
-			if _, ok := e.(bailout); !ok {
-				panic(e)
-			}
+		if p.panicking {
+			recover()
 		}
 		p.errors.Sort()
 		err = p.errors.Err()
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 30b6114..2874049 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -33,9 +33,10 @@
 	scanner scanner.Scanner
 
 	// Tracing/debugging
-	mode   mode // parsing mode
-	trace  bool // == (mode & Trace != 0)
-	indent int  // indentation used for tracing output
+	mode      mode // parsing mode
+	trace     bool // == (mode & Trace != 0)
+	panicking bool // set if we are bailing out due to too many errors.
+	indent    int  // indentation used for tracing output
 
 	// Comments
 	leadComment *ast.CommentGroup
@@ -150,7 +151,9 @@
 	}
 	switch c.isList--; {
 	case c.isList < 0:
-		panic("unmatched close list")
+		if !p.panicking {
+			panic("unmatched close list")
+		}
 	case c.isList == 0:
 		parent := c.parent
 		parent.groups = append(parent.groups, c.groups...)
@@ -161,7 +164,10 @@
 
 func (c *commentState) closeNode(p *parser, n ast.Node) ast.Node {
 	if p.comments != c {
-		panic("unmatched comments")
+		if !p.panicking {
+			panic("unmatched comments")
+		}
+		return n
 	}
 	p.comments = c.parent
 	if c.parent != nil {
@@ -336,9 +342,6 @@
 	}
 }
 
-// A bailout panic is raised to indicate early termination.
-type bailout struct{}
-
 func (p *parser) error(pos token.Pos, msg string) {
 	ePos := p.file.Position(pos)
 
@@ -351,7 +354,8 @@
 			return // discard - likely a spurious error
 		}
 		if n > 10 {
-			panic(bailout{})
+			p.panicking = true
+			panic("too many errors")
 		}
 	}