1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package scanner provides a scanner and tokenizer for UTF-8-encoded text.
6// It takes an io.Reader providing the source, which then can be tokenized
7// through repeated calls to the Scan function. For compatibility with
8// existing tools, the NUL character is not allowed. If the first character
9// in the source is a UTF-8 encoded byte order mark (BOM), it is discarded.
10//
11// By default, a [Scanner] skips white space and Go comments and recognizes all
12// literals as defined by the Go language specification. It may be
13// customized to recognize only a subset of those literals and to recognize
14// different identifier and white space characters.
15package scanner
16
17import (
18	"bytes"
19	"fmt"
20	"io"
21	"os"
22	"unicode"
23	"unicode/utf8"
24)
25
26// Position is a value that represents a source position.
27// A position is valid if Line > 0.
28type Position struct {
29	Filename string // filename, if any
30	Offset   int    // byte offset, starting at 0
31	Line     int    // line number, starting at 1
32	Column   int    // column number, starting at 1 (character count per line)
33}
34
35// IsValid reports whether the position is valid.
36func (pos *Position) IsValid() bool { return pos.Line > 0 }
37
38func (pos Position) String() string {
39	s := pos.Filename
40	if s == "" {
41		s = "<input>"
42	}
43	if pos.IsValid() {
44		s += fmt.Sprintf(":%d:%d", pos.Line, pos.Column)
45	}
46	return s
47}
48
49// Predefined mode bits to control recognition of tokens. For instance,
50// to configure a [Scanner] such that it only recognizes (Go) identifiers,
51// integers, and skips comments, set the Scanner's Mode field to:
52//
53//	ScanIdents | ScanInts | SkipComments
54//
55// With the exceptions of comments, which are skipped if SkipComments is
56// set, unrecognized tokens are not ignored. Instead, the scanner simply
57// returns the respective individual characters (or possibly sub-tokens).
58// For instance, if the mode is ScanIdents (not ScanStrings), the string
59// "foo" is scanned as the token sequence '"' [Ident] '"'.
60//
61// Use GoTokens to configure the Scanner such that it accepts all Go
62// literal tokens including Go identifiers. Comments will be skipped.
63const (
64	ScanIdents     = 1 << -Ident
65	ScanInts       = 1 << -Int
66	ScanFloats     = 1 << -Float // includes Ints and hexadecimal floats
67	ScanChars      = 1 << -Char
68	ScanStrings    = 1 << -String
69	ScanRawStrings = 1 << -RawString
70	ScanComments   = 1 << -Comment
71	SkipComments   = 1 << -skipComment // if set with ScanComments, comments become white space
72	GoTokens       = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments
73)
74
75// The result of Scan is one of these tokens or a Unicode character.
76const (
77	EOF = -(iota + 1)
78	Ident
79	Int
80	Float
81	Char
82	String
83	RawString
84	Comment
85
86	// internal use only
87	skipComment
88)
89
90var tokenString = map[rune]string{
91	EOF:       "EOF",
92	Ident:     "Ident",
93	Int:       "Int",
94	Float:     "Float",
95	Char:      "Char",
96	String:    "String",
97	RawString: "RawString",
98	Comment:   "Comment",
99}
100
101// TokenString returns a printable string for a token or Unicode character.
102func TokenString(tok rune) string {
103	if s, found := tokenString[tok]; found {
104		return s
105	}
106	return fmt.Sprintf("%q", string(tok))
107}
108
109// GoWhitespace is the default value for the [Scanner]'s Whitespace field.
110// Its value selects Go's white space characters.
111const GoWhitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' '
112
113const bufLen = 1024 // at least utf8.UTFMax
114
115// A Scanner implements reading of Unicode characters and tokens from an [io.Reader].
116type Scanner struct {
117	// Input
118	src io.Reader
119
120	// Source buffer
121	srcBuf [bufLen + 1]byte // +1 for sentinel for common case of s.next()
122	srcPos int              // reading position (srcBuf index)
123	srcEnd int              // source end (srcBuf index)
124
125	// Source position
126	srcBufOffset int // byte offset of srcBuf[0] in source
127	line         int // line count
128	column       int // character count
129	lastLineLen  int // length of last line in characters (for correct column reporting)
130	lastCharLen  int // length of last character in bytes
131
132	// Token text buffer
133	// Typically, token text is stored completely in srcBuf, but in general
134	// the token text's head may be buffered in tokBuf while the token text's
135	// tail is stored in srcBuf.
136	tokBuf bytes.Buffer // token text head that is not in srcBuf anymore
137	tokPos int          // token text tail position (srcBuf index); valid if >= 0
138	tokEnd int          // token text tail end (srcBuf index)
139
140	// One character look-ahead
141	ch rune // character before current srcPos
142
143	// Error is called for each error encountered. If no Error
144	// function is set, the error is reported to os.Stderr.
145	Error func(s *Scanner, msg string)
146
147	// ErrorCount is incremented by one for each error encountered.
148	ErrorCount int
149
150	// The Mode field controls which tokens are recognized. For instance,
151	// to recognize Ints, set the ScanInts bit in Mode. The field may be
152	// changed at any time.
153	Mode uint
154
155	// The Whitespace field controls which characters are recognized
156	// as white space. To recognize a character ch <= ' ' as white space,
157	// set the ch'th bit in Whitespace (the Scanner's behavior is undefined
158	// for values ch > ' '). The field may be changed at any time.
159	Whitespace uint64
160
161	// IsIdentRune is a predicate controlling the characters accepted
162	// as the ith rune in an identifier. The set of valid characters
163	// must not intersect with the set of white space characters.
164	// If no IsIdentRune function is set, regular Go identifiers are
165	// accepted instead. The field may be changed at any time.
166	IsIdentRune func(ch rune, i int) bool
167
168	// Start position of most recently scanned token; set by Scan.
169	// Calling Init or Next invalidates the position (Line == 0).
170	// The Filename field is always left untouched by the Scanner.
171	// If an error is reported (via Error) and Position is invalid,
172	// the scanner is not inside a token. Call Pos to obtain an error
173	// position in that case, or to obtain the position immediately
174	// after the most recently scanned token.
175	Position
176}
177
178// Init initializes a [Scanner] with a new source and returns s.
179// [Scanner.Error] is set to nil, [Scanner.ErrorCount] is set to 0, [Scanner.Mode] is set to [GoTokens],
180// and [Scanner.Whitespace] is set to [GoWhitespace].
181func (s *Scanner) Init(src io.Reader) *Scanner {
182	s.src = src
183
184	// initialize source buffer
185	// (the first call to next() will fill it by calling src.Read)
186	s.srcBuf[0] = utf8.RuneSelf // sentinel
187	s.srcPos = 0
188	s.srcEnd = 0
189
190	// initialize source position
191	s.srcBufOffset = 0
192	s.line = 1
193	s.column = 0
194	s.lastLineLen = 0
195	s.lastCharLen = 0
196
197	// initialize token text buffer
198	// (required for first call to next()).
199	s.tokPos = -1
200
201	// initialize one character look-ahead
202	s.ch = -2 // no char read yet, not EOF
203
204	// initialize public fields
205	s.Error = nil
206	s.ErrorCount = 0
207	s.Mode = GoTokens
208	s.Whitespace = GoWhitespace
209	s.Line = 0 // invalidate token position
210
211	return s
212}
213
214// next reads and returns the next Unicode character. It is designed such
215// that only a minimal amount of work needs to be done in the common ASCII
216// case (one test to check for both ASCII and end-of-buffer, and one test
217// to check for newlines).
218func (s *Scanner) next() rune {
219	ch, width := rune(s.srcBuf[s.srcPos]), 1
220
221	if ch >= utf8.RuneSelf {
222		// uncommon case: not ASCII or not enough bytes
223		for s.srcPos+utf8.UTFMax > s.srcEnd && !utf8.FullRune(s.srcBuf[s.srcPos:s.srcEnd]) {
224			// not enough bytes: read some more, but first
225			// save away token text if any
226			if s.tokPos >= 0 {
227				s.tokBuf.Write(s.srcBuf[s.tokPos:s.srcPos])
228				s.tokPos = 0
229				// s.tokEnd is set by Scan()
230			}
231			// move unread bytes to beginning of buffer
232			copy(s.srcBuf[0:], s.srcBuf[s.srcPos:s.srcEnd])
233			s.srcBufOffset += s.srcPos
234			// read more bytes
235			// (an io.Reader must return io.EOF when it reaches
236			// the end of what it is reading - simply returning
237			// n == 0 will make this loop retry forever; but the
238			// error is in the reader implementation in that case)
239			i := s.srcEnd - s.srcPos
240			n, err := s.src.Read(s.srcBuf[i:bufLen])
241			s.srcPos = 0
242			s.srcEnd = i + n
243			s.srcBuf[s.srcEnd] = utf8.RuneSelf // sentinel
244			if err != nil {
245				if err != io.EOF {
246					s.error(err.Error())
247				}
248				if s.srcEnd == 0 {
249					if s.lastCharLen > 0 {
250						// previous character was not EOF
251						s.column++
252					}
253					s.lastCharLen = 0
254					return EOF
255				}
256				// If err == EOF, we won't be getting more
257				// bytes; break to avoid infinite loop. If
258				// err is something else, we don't know if
259				// we can get more bytes; thus also break.
260				break
261			}
262		}
263		// at least one byte
264		ch = rune(s.srcBuf[s.srcPos])
265		if ch >= utf8.RuneSelf {
266			// uncommon case: not ASCII
267			ch, width = utf8.DecodeRune(s.srcBuf[s.srcPos:s.srcEnd])
268			if ch == utf8.RuneError && width == 1 {
269				// advance for correct error position
270				s.srcPos += width
271				s.lastCharLen = width
272				s.column++
273				s.error("invalid UTF-8 encoding")
274				return ch
275			}
276		}
277	}
278
279	// advance
280	s.srcPos += width
281	s.lastCharLen = width
282	s.column++
283
284	// special situations
285	switch ch {
286	case 0:
287		// for compatibility with other tools
288		s.error("invalid character NUL")
289	case '\n':
290		s.line++
291		s.lastLineLen = s.column
292		s.column = 0
293	}
294
295	return ch
296}
297
298// Next reads and returns the next Unicode character.
299// It returns [EOF] at the end of the source. It reports
300// a read error by calling s.Error, if not nil; otherwise
301// it prints an error message to [os.Stderr]. Next does not
302// update the [Scanner.Position] field; use [Scanner.Pos]() to
303// get the current position.
304func (s *Scanner) Next() rune {
305	s.tokPos = -1 // don't collect token text
306	s.Line = 0    // invalidate token position
307	ch := s.Peek()
308	if ch != EOF {
309		s.ch = s.next()
310	}
311	return ch
312}
313
314// Peek returns the next Unicode character in the source without advancing
315// the scanner. It returns [EOF] if the scanner's position is at the last
316// character of the source.
317func (s *Scanner) Peek() rune {
318	if s.ch == -2 {
319		// this code is only run for the very first character
320		s.ch = s.next()
321		if s.ch == '\uFEFF' {
322			s.ch = s.next() // ignore BOM
323		}
324	}
325	return s.ch
326}
327
328func (s *Scanner) error(msg string) {
329	s.tokEnd = s.srcPos - s.lastCharLen // make sure token text is terminated
330	s.ErrorCount++
331	if s.Error != nil {
332		s.Error(s, msg)
333		return
334	}
335	pos := s.Position
336	if !pos.IsValid() {
337		pos = s.Pos()
338	}
339	fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg)
340}
341
342func (s *Scanner) errorf(format string, args ...any) {
343	s.error(fmt.Sprintf(format, args...))
344}
345
346func (s *Scanner) isIdentRune(ch rune, i int) bool {
347	if s.IsIdentRune != nil {
348		return ch != EOF && s.IsIdentRune(ch, i)
349	}
350	return ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) && i > 0
351}
352
353func (s *Scanner) scanIdentifier() rune {
354	// we know the zero'th rune is OK; start scanning at the next one
355	ch := s.next()
356	for i := 1; s.isIdentRune(ch, i); i++ {
357		ch = s.next()
358	}
359	return ch
360}
361
362func lower(ch rune) rune     { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter
363func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' }
364func isHex(ch rune) bool     { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' }
365
366// digits accepts the sequence { digit | '_' } starting with ch0.
367// If base <= 10, digits accepts any decimal digit but records
368// the first invalid digit >= base in *invalid if *invalid == 0.
369// digits returns the first rune that is not part of the sequence
370// anymore, and a bitset describing whether the sequence contained
371// digits (bit 0 is set), or separators '_' (bit 1 is set).
372func (s *Scanner) digits(ch0 rune, base int, invalid *rune) (ch rune, digsep int) {
373	ch = ch0
374	if base <= 10 {
375		max := rune('0' + base)
376		for isDecimal(ch) || ch == '_' {
377			ds := 1
378			if ch == '_' {
379				ds = 2
380			} else if ch >= max && *invalid == 0 {
381				*invalid = ch
382			}
383			digsep |= ds
384			ch = s.next()
385		}
386	} else {
387		for isHex(ch) || ch == '_' {
388			ds := 1
389			if ch == '_' {
390				ds = 2
391			}
392			digsep |= ds
393			ch = s.next()
394		}
395	}
396	return
397}
398
399func (s *Scanner) scanNumber(ch rune, seenDot bool) (rune, rune) {
400	base := 10         // number base
401	prefix := rune(0)  // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b'
402	digsep := 0        // bit 0: digit present, bit 1: '_' present
403	invalid := rune(0) // invalid digit in literal, or 0
404
405	// integer part
406	var tok rune
407	var ds int
408	if !seenDot {
409		tok = Int
410		if ch == '0' {
411			ch = s.next()
412			switch lower(ch) {
413			case 'x':
414				ch = s.next()
415				base, prefix = 16, 'x'
416			case 'o':
417				ch = s.next()
418				base, prefix = 8, 'o'
419			case 'b':
420				ch = s.next()
421				base, prefix = 2, 'b'
422			default:
423				base, prefix = 8, '0'
424				digsep = 1 // leading 0
425			}
426		}
427		ch, ds = s.digits(ch, base, &invalid)
428		digsep |= ds
429		if ch == '.' && s.Mode&ScanFloats != 0 {
430			ch = s.next()
431			seenDot = true
432		}
433	}
434
435	// fractional part
436	if seenDot {
437		tok = Float
438		if prefix == 'o' || prefix == 'b' {
439			s.error("invalid radix point in " + litname(prefix))
440		}
441		ch, ds = s.digits(ch, base, &invalid)
442		digsep |= ds
443	}
444
445	if digsep&1 == 0 {
446		s.error(litname(prefix) + " has no digits")
447	}
448
449	// exponent
450	if e := lower(ch); (e == 'e' || e == 'p') && s.Mode&ScanFloats != 0 {
451		switch {
452		case e == 'e' && prefix != 0 && prefix != '0':
453			s.errorf("%q exponent requires decimal mantissa", ch)
454		case e == 'p' && prefix != 'x':
455			s.errorf("%q exponent requires hexadecimal mantissa", ch)
456		}
457		ch = s.next()
458		tok = Float
459		if ch == '+' || ch == '-' {
460			ch = s.next()
461		}
462		ch, ds = s.digits(ch, 10, nil)
463		digsep |= ds
464		if ds&1 == 0 {
465			s.error("exponent has no digits")
466		}
467	} else if prefix == 'x' && tok == Float {
468		s.error("hexadecimal mantissa requires a 'p' exponent")
469	}
470
471	if tok == Int && invalid != 0 {
472		s.errorf("invalid digit %q in %s", invalid, litname(prefix))
473	}
474
475	if digsep&2 != 0 {
476		s.tokEnd = s.srcPos - s.lastCharLen // make sure token text is terminated
477		if i := invalidSep(s.TokenText()); i >= 0 {
478			s.error("'_' must separate successive digits")
479		}
480	}
481
482	return tok, ch
483}
484
485func litname(prefix rune) string {
486	switch prefix {
487	default:
488		return "decimal literal"
489	case 'x':
490		return "hexadecimal literal"
491	case 'o', '0':
492		return "octal literal"
493	case 'b':
494		return "binary literal"
495	}
496}
497
498// invalidSep returns the index of the first invalid separator in x, or -1.
499func invalidSep(x string) int {
500	x1 := ' ' // prefix char, we only care if it's 'x'
501	d := '.'  // digit, one of '_', '0' (a digit), or '.' (anything else)
502	i := 0
503
504	// a prefix counts as a digit
505	if len(x) >= 2 && x[0] == '0' {
506		x1 = lower(rune(x[1]))
507		if x1 == 'x' || x1 == 'o' || x1 == 'b' {
508			d = '0'
509			i = 2
510		}
511	}
512
513	// mantissa and exponent
514	for ; i < len(x); i++ {
515		p := d // previous digit
516		d = rune(x[i])
517		switch {
518		case d == '_':
519			if p != '0' {
520				return i
521			}
522		case isDecimal(d) || x1 == 'x' && isHex(d):
523			d = '0'
524		default:
525			if p == '_' {
526				return i - 1
527			}
528			d = '.'
529		}
530	}
531	if d == '_' {
532		return len(x) - 1
533	}
534
535	return -1
536}
537
538func digitVal(ch rune) int {
539	switch {
540	case '0' <= ch && ch <= '9':
541		return int(ch - '0')
542	case 'a' <= lower(ch) && lower(ch) <= 'f':
543		return int(lower(ch) - 'a' + 10)
544	}
545	return 16 // larger than any legal digit val
546}
547
548func (s *Scanner) scanDigits(ch rune, base, n int) rune {
549	for n > 0 && digitVal(ch) < base {
550		ch = s.next()
551		n--
552	}
553	if n > 0 {
554		s.error("invalid char escape")
555	}
556	return ch
557}
558
559func (s *Scanner) scanEscape(quote rune) rune {
560	ch := s.next() // read character after '/'
561	switch ch {
562	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote:
563		// nothing to do
564		ch = s.next()
565	case '0', '1', '2', '3', '4', '5', '6', '7':
566		ch = s.scanDigits(ch, 8, 3)
567	case 'x':
568		ch = s.scanDigits(s.next(), 16, 2)
569	case 'u':
570		ch = s.scanDigits(s.next(), 16, 4)
571	case 'U':
572		ch = s.scanDigits(s.next(), 16, 8)
573	default:
574		s.error("invalid char escape")
575	}
576	return ch
577}
578
579func (s *Scanner) scanString(quote rune) (n int) {
580	ch := s.next() // read character after quote
581	for ch != quote {
582		if ch == '\n' || ch < 0 {
583			s.error("literal not terminated")
584			return
585		}
586		if ch == '\\' {
587			ch = s.scanEscape(quote)
588		} else {
589			ch = s.next()
590		}
591		n++
592	}
593	return
594}
595
596func (s *Scanner) scanRawString() {
597	ch := s.next() // read character after '`'
598	for ch != '`' {
599		if ch < 0 {
600			s.error("literal not terminated")
601			return
602		}
603		ch = s.next()
604	}
605}
606
607func (s *Scanner) scanChar() {
608	if s.scanString('\'') != 1 {
609		s.error("invalid char literal")
610	}
611}
612
613func (s *Scanner) scanComment(ch rune) rune {
614	// ch == '/' || ch == '*'
615	if ch == '/' {
616		// line comment
617		ch = s.next() // read character after "//"
618		for ch != '\n' && ch >= 0 {
619			ch = s.next()
620		}
621		return ch
622	}
623
624	// general comment
625	ch = s.next() // read character after "/*"
626	for {
627		if ch < 0 {
628			s.error("comment not terminated")
629			break
630		}
631		ch0 := ch
632		ch = s.next()
633		if ch0 == '*' && ch == '/' {
634			ch = s.next()
635			break
636		}
637	}
638	return ch
639}
640
641// Scan reads the next token or Unicode character from source and returns it.
642// It only recognizes tokens t for which the respective [Scanner.Mode] bit (1<<-t) is set.
643// It returns [EOF] at the end of the source. It reports scanner errors (read and
644// token errors) by calling s.Error, if not nil; otherwise it prints an error
645// message to [os.Stderr].
646func (s *Scanner) Scan() rune {
647	ch := s.Peek()
648
649	// reset token text position
650	s.tokPos = -1
651	s.Line = 0
652
653redo:
654	// skip white space
655	for s.Whitespace&(1<<uint(ch)) != 0 {
656		ch = s.next()
657	}
658
659	// start collecting token text
660	s.tokBuf.Reset()
661	s.tokPos = s.srcPos - s.lastCharLen
662
663	// set token position
664	// (this is a slightly optimized version of the code in Pos())
665	s.Offset = s.srcBufOffset + s.tokPos
666	if s.column > 0 {
667		// common case: last character was not a '\n'
668		s.Line = s.line
669		s.Column = s.column
670	} else {
671		// last character was a '\n'
672		// (we cannot be at the beginning of the source
673		// since we have called next() at least once)
674		s.Line = s.line - 1
675		s.Column = s.lastLineLen
676	}
677
678	// determine token value
679	tok := ch
680	switch {
681	case s.isIdentRune(ch, 0):
682		if s.Mode&ScanIdents != 0 {
683			tok = Ident
684			ch = s.scanIdentifier()
685		} else {
686			ch = s.next()
687		}
688	case isDecimal(ch):
689		if s.Mode&(ScanInts|ScanFloats) != 0 {
690			tok, ch = s.scanNumber(ch, false)
691		} else {
692			ch = s.next()
693		}
694	default:
695		switch ch {
696		case EOF:
697			break
698		case '"':
699			if s.Mode&ScanStrings != 0 {
700				s.scanString('"')
701				tok = String
702			}
703			ch = s.next()
704		case '\'':
705			if s.Mode&ScanChars != 0 {
706				s.scanChar()
707				tok = Char
708			}
709			ch = s.next()
710		case '.':
711			ch = s.next()
712			if isDecimal(ch) && s.Mode&ScanFloats != 0 {
713				tok, ch = s.scanNumber(ch, true)
714			}
715		case '/':
716			ch = s.next()
717			if (ch == '/' || ch == '*') && s.Mode&ScanComments != 0 {
718				if s.Mode&SkipComments != 0 {
719					s.tokPos = -1 // don't collect token text
720					ch = s.scanComment(ch)
721					goto redo
722				}
723				ch = s.scanComment(ch)
724				tok = Comment
725			}
726		case '`':
727			if s.Mode&ScanRawStrings != 0 {
728				s.scanRawString()
729				tok = RawString
730			}
731			ch = s.next()
732		default:
733			ch = s.next()
734		}
735	}
736
737	// end of token text
738	s.tokEnd = s.srcPos - s.lastCharLen
739
740	s.ch = ch
741	return tok
742}
743
744// Pos returns the position of the character immediately after
745// the character or token returned by the last call to [Scanner.Next] or [Scanner.Scan].
746// Use the [Scanner.Position] field for the start position of the most
747// recently scanned token.
748func (s *Scanner) Pos() (pos Position) {
749	pos.Filename = s.Filename
750	pos.Offset = s.srcBufOffset + s.srcPos - s.lastCharLen
751	switch {
752	case s.column > 0:
753		// common case: last character was not a '\n'
754		pos.Line = s.line
755		pos.Column = s.column
756	case s.lastLineLen > 0:
757		// last character was a '\n'
758		pos.Line = s.line - 1
759		pos.Column = s.lastLineLen
760	default:
761		// at the beginning of the source
762		pos.Line = 1
763		pos.Column = 1
764	}
765	return
766}
767
768// TokenText returns the string corresponding to the most recently scanned token.
769// Valid after calling [Scanner.Scan] and in calls of [Scanner.Error].
770func (s *Scanner) TokenText() string {
771	if s.tokPos < 0 {
772		// no token text
773		return ""
774	}
775
776	if s.tokEnd < s.tokPos {
777		// if EOF was reached, s.tokEnd is set to -1 (s.srcPos == 0)
778		s.tokEnd = s.tokPos
779	}
780	// s.tokEnd >= s.tokPos
781
782	if s.tokBuf.Len() == 0 {
783		// common case: the entire token text is still in srcBuf
784		return string(s.srcBuf[s.tokPos:s.tokEnd])
785	}
786
787	// part of the token text was saved in tokBuf: save the rest in
788	// tokBuf as well and return its content
789	s.tokBuf.Write(s.srcBuf[s.tokPos:s.tokEnd])
790	s.tokPos = s.tokEnd // ensure idempotency of TokenText() call
791	return s.tokBuf.String()
792}
793