cmd/compile/internal/syntax: allow more than one rune "unread"

Make it possible to "unread" more than one byte before the most
recently read rune. Use a better name than ungetr2 and make it
slightly more efficient.

R=Go1.13

Change-Id: I45d5dfa11e508259a972ca6560d1f78d7a51fe15
Reviewed-on: https://go-review.googlesource.com/c/158957
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/src/cmd/compile/internal/syntax/scanner.go b/src/cmd/compile/internal/syntax/scanner.go
index 7db33fb..112afa5 100644
--- a/src/cmd/compile/internal/syntax/scanner.go
+++ b/src/cmd/compile/internal/syntax/scanner.go
@@ -150,7 +150,7 @@
 	case '.':
 		c = s.getr()
 		if isDigit(c) {
-			s.ungetr2()
+			s.unread(1)
 			s.number('.')
 			break
 		}
@@ -160,7 +160,7 @@
 				s.tok = _DotDotDot
 				break
 			}
-			s.ungetr2()
+			s.unread(1)
 		}
 		s.ungetr()
 		s.tok = _Dot
diff --git a/src/cmd/compile/internal/syntax/source.go b/src/cmd/compile/internal/syntax/source.go
index c6168b8..c671e3c 100644
--- a/src/cmd/compile/internal/syntax/source.go
+++ b/src/cmd/compile/internal/syntax/source.go
@@ -22,6 +22,9 @@
 const linebase = 1
 const colbase = 1
 
+// max. number of bytes to unread
+const maxunread = 10
+
 // buf [...read...|...|...unread...|s|...free...]
 //         ^      ^   ^            ^
 //         |      |   |            |
@@ -59,20 +62,21 @@
 	s.suf = -1
 }
 
-// ungetr ungets the most recently read rune.
+// ungetr sets the reading position to a previous reading
+// position, usually the one of the most recently read
+// rune, but possibly earlier (see unread below).
 func (s *source) ungetr() {
 	s.r, s.line, s.col = s.r0, s.line0, s.col0
 }
 
-// ungetr2 is like ungetr but enables a 2nd ungetr.
-// It must not be called if one of the runes seen
-// was a newline or had a UTF-8 encoding longer than
-// 1 byte.
-func (s *source) ungetr2() {
-	s.ungetr()
-	// line must not have changed
-	s.r0--
-	s.col0--
+// unread moves the previous reading position to a position
+// that is n bytes earlier in the source. The next ungetr
+// call will set the reading position to that moved position.
+// The "unread" runes must be single byte and not contain any
+// newlines; and 0 <= n <= maxunread must hold.
+func (s *source) unread(n int) {
+	s.r0 -= n
+	s.col0 -= uint(n)
 }
 
 func (s *source) error(msg string) {
@@ -142,7 +146,7 @@
 	// BOM's are only allowed as the first character in a file
 	const BOM = 0xfeff
 	if r == BOM {
-		if s.r0 > 0 { // s.r0 is always > 0 after 1st character (fill will set it to 1)
+		if s.r0 > 0 { // s.r0 is always > 0 after 1st character (fill will set it to maxunread)
 			s.error("invalid BOM in the middle of the file")
 		}
 		goto redo
@@ -153,20 +157,25 @@
 
 func (s *source) fill() {
 	// Slide unread bytes to beginning but preserve last read char
-	// (for one ungetr call) plus one extra byte (for a 2nd ungetr
-	// call, only for ".." character sequence and float literals
-	// starting with ".").
-	if s.r0 > 1 {
+	// (for one ungetr call) plus maxunread extra bytes (for one
+	// unread call).
+	if s.r0 > maxunread {
+		n := s.r0 - maxunread // number of bytes to slide down
 		// save literal prefix, if any
-		// (We see at most one ungetr call while reading
-		// a literal, so make sure s.r0 remains in buf.)
+		// (make sure we keep maxunread bytes and the last
+		// read char in the buffer)
 		if s.suf >= 0 {
-			s.lit = append(s.lit, s.buf[s.suf:s.r0]...)
-			s.suf = 1 // == s.r0 after slide below
+			// we have a literal
+			if s.suf < n {
+				// save literal prefix
+				s.lit = append(s.lit, s.buf[s.suf:n]...)
+				s.suf = 0
+			} else {
+				s.suf -= n
+			}
 		}
-		n := s.r0 - 1
 		copy(s.buf[:], s.buf[n:s.w])
-		s.r0 = 1 // eqv: s.r0 -= n
+		s.r0 = maxunread // eqv: s.r0 -= n
 		s.r -= n
 		s.w -= n
 	}