Copyright 2009 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
Package scanner implements a scanner for Go source text. It takes a []byte as source which can then be tokenized through repeated calls to the Scan method.
package scanner

import (
	
	
	
	
	
	
	
)
An ErrorHandler may be provided to Scanner.Init. If a syntax error is encountered and a handler was installed, the handler is called with a position and an error message. The position points to the beginning of the offending token.
type ErrorHandler func(pos token.Position, msg string)
A Scanner holds the scanner's internal state while processing a given text. It can be allocated as part of another data structure but must be initialized via Init before use.
immutable state
	file *token.File  // source file handle
	dir  string       // directory portion of file.Name()
	src  []byte       // source
	err  ErrorHandler // error reporting; or nil
	mode Mode         // scanning mode
scanning state
	ch         rune // current character
	offset     int  // character offset
	rdOffset   int  // reading offset (position after current character)
	lineOffset int  // current line offset
	insertSemi bool // insert a semicolon before next newline
public state - ok to modify
	ErrorCount int // number of errors encountered
}

const bom = 0xFEFF // byte order mark, only permitted as very first character
Read the next Unicode char into s.ch. s.ch < 0 means end-of-file.
func ( *Scanner) () {
	if .rdOffset < len(.src) {
		.offset = .rdOffset
		if .ch == '\n' {
			.lineOffset = .offset
			.file.AddLine(.offset)
		}
		,  := rune(.src[.rdOffset]), 1
		switch {
		case  == 0:
			.error(.offset, "illegal character NUL")
not ASCII
			,  = utf8.DecodeRune(.src[.rdOffset:])
			if  == utf8.RuneError &&  == 1 {
				.error(.offset, "illegal UTF-8 encoding")
			} else if  == bom && .offset > 0 {
				.error(.offset, "illegal byte order mark")
			}
		}
		.rdOffset += 
		.ch = 
	} else {
		.offset = len(.src)
		if .ch == '\n' {
			.lineOffset = .offset
			.file.AddLine(.offset)
		}
		.ch = -1 // eof
	}
}
peek returns the byte following the most recently read character without advancing the scanner. If the scanner is at EOF, peek returns 0.
func ( *Scanner) () byte {
	if .rdOffset < len(.src) {
		return .src[.rdOffset]
	}
	return 0
}
A mode value is a set of flags (or 0). They control scanner behavior.
type Mode uint

const (
	ScanComments    Mode = 1 << iota // return comments as COMMENT tokens
	dontInsertSemis                  // do not automatically insert semicolons - for testing only
)
Init prepares the scanner s to tokenize the text src by setting the scanner at the beginning of src. The scanner uses the file set file for position information and it adds line information for each line. It is ok to re-use the same file when re-scanning the same file as line information which is already present is ignored. Init causes a panic if the file size does not match the src size. Calls to Scan will invoke the error handler err if they encounter a syntax error and err is not nil. Also, for each error encountered, the Scanner field ErrorCount is incremented by one. The mode parameter determines how comments are handled. Note that Init may call err if there is an error in the first character of the file.
Explicitly initialize all fields since a scanner may be reused.
	if .Size() != len() {
		panic(fmt.Sprintf("file size (%d) does not match src len (%d)", .Size(), len()))
	}
	.file = 
	.dir, _ = filepath.Split(.Name())
	.src = 
	.err = 
	.mode = 

	.ch = ' '
	.offset = 0
	.rdOffset = 0
	.lineOffset = 0
	.insertSemi = false
	.ErrorCount = 0

	.next()
	if .ch == bom {
		.next() // ignore BOM at file beginning
	}
}

func ( *Scanner) ( int,  string) {
	if .err != nil {
		.err(.file.Position(.file.Pos()), )
	}
	.ErrorCount++
}

func ( *Scanner) ( int,  string,  ...interface{}) {
	.error(, fmt.Sprintf(, ...))
}

initial '/' already consumed; s.ch == '/' || s.ch == '*'
	 := .offset - 1 // position of initial '/'
	 := -1           // position immediately following the comment; < 0 means invalid comment
	 := 0

-style comment (the final '\n' is not considered part of the comment)
		.next()
		for .ch != '\n' && .ch >= 0 {
			if .ch == '\r' {
				++
			}
			.next()
if we are at '\n', the position following the comment is afterwards
		 = .offset
		if .ch == '\n' {
			++
		}
		goto 
	}
-style comment
	.next()
	for .ch >= 0 {
		 := .ch
		if  == '\r' {
			++
		}
		.next()
		if  == '*' && .ch == '/' {
			.next()
			 = .offset
			goto 
		}
	}

	.error(, "comment not terminated")

:
	 := .src[:.offset]
On Windows, a (//-comment) line may end in "\r\n". Remove the final '\r' before analyzing the text for line directives (matching the compiler). Remove any other '\r' afterwards (matching the pre-existing be- havior of the scanner).
	if  > 0 && len() >= 2 && [1] == '/' && [len()-1] == '\r' {
		 = [:len()-1]
		--
	}
interpret line directives (//line directives must start at the beginning of the current line)
	if  >= 0 /* implies valid comment */ && ([1] == '*' ||  == .lineOffset) && bytes.HasPrefix([2:], prefix) {
		.updateLineInfo(, , )
	}

	if  > 0 {
		 = stripCR(, [1] == '*')
	}

	return string()
}

var prefix = []byte("line ")
updateLineInfo parses the incoming comment text at offset offs as a line directive. If successful, it updates the line info table for the position next per the line directive.
extract comment text
	if [1] == '*' {
		 = [:len()-2] // lop off trailing "*/"
	}
	 = [7:] // lop off leading "//line " or "/*line "
	 += 7

	, ,  := trailingDigits()
	if  == 0 {
		return // ignore (not a line directive)
i > 0

text has a suffix :xxx but xxx is not a number
		.error(+, "invalid line number: "+string([:]))
		return
	}

	var ,  int
	, ,  := trailingDigits([:-1])
line filename:line:col
		,  = , 
		,  = , 
		if  == 0 {
			.error(+, "invalid column number: "+string([:]))
			return
		}
		 = [:-1] // lop off ":col"
line filename:line
		 = 
	}

	if  == 0 {
		.error(+, "invalid line number: "+string([:]))
		return
	}
If we have a column (//line filename:line:col form), an empty filename means to use the previous filename.
	 := string([:-1]) // lop off ":line", and trim white space
	if  == "" &&  {
		 = .file.Position(.file.Pos()).Filename
Put a relative filename in the current directory. This is for compatibility with earlier releases. See issue 26671.
		 = filepath.Clean()
		if !filepath.IsAbs() {
			 = filepath.Join(.dir, )
		}
	}

	.file.AddLineColumnInfo(, , , )
}

func ( []byte) (int, int, bool) {
	 := bytes.LastIndexByte(, ':') // look from right (Windows filenames may contain ':')
	if  < 0 {
		return 0, 0, false // no ":"
i >= 0
	,  := strconv.ParseUint(string([+1:]), 10, 0)
	return  + 1, int(),  == nil
}

initial '/' already consumed

reset scanner state to where it was upon calling findLineEnd
		.ch = '/'
		.offset = 
		.rdOffset =  + 1
		.next() // consume initial '/' again
	}(.offset - 1)
read ahead until a newline, EOF, or non-comment token is found
	for .ch == '/' || .ch == '*' {
-style comment always contains a newline
			return true
-style comment: look for newline
		.next()
		for .ch >= 0 {
			 := .ch
			if  == '\n' {
				return true
			}
			.next()
			if  == '*' && .ch == '/' {
				.next()
				break
			}
		}
		.skipWhitespace() // s.insertSemi is set
		if .ch < 0 || .ch == '\n' {
			return true
		}
non-comment token
			return false
		}
		.next() // consume '/'
	}

	return false
}

func ( rune) bool {
	return 'a' <= lower() && lower() <= 'z' ||  == '_' ||  >= utf8.RuneSelf && unicode.IsLetter()
}

func ( rune) bool {
	return isDecimal() ||  >= utf8.RuneSelf && unicode.IsDigit()
}

func ( *Scanner) () string {
	 := .offset
	for isLetter(.ch) || isDigit(.ch) {
		.next()
	}
	return string(.src[:.offset])
}

func ( rune) int {
	switch {
	case '0' <=  &&  <= '9':
		return int( - '0')
	case 'a' <= lower() && lower() <= 'f':
		return int(lower() - 'a' + 10)
	}
	return 16 // larger than any legal digit val
}

func ( rune) rune     { return ('a' - 'A') |  } // returns lower-case ch iff ch is ASCII letter
func ( rune) bool { return '0' <=  &&  <= '9' }
func ( rune) bool     { return '0' <=  &&  <= '9' || 'a' <= lower() && lower() <= 'f' }
digits accepts the sequence { digit | '_' }. If base <= 10, digits accepts any decimal digit but records the offset (relative to the source start) of a digit >= base in *invalid, if *invalid < 0. digits returns a bitset describing whether the sequence contained digits (bit 0 is set), or separators '_' (bit 1 is set).
func ( *Scanner) ( int,  *int) ( int) {
	if  <= 10 {
		 := rune('0' + )
		for isDecimal(.ch) || .ch == '_' {
			 := 1
			if .ch == '_' {
				 = 2
			} else if .ch >=  && * < 0 {
				* = int(.offset) // record invalid rune offset
			}
			 |= 
			.next()
		}
	} else {
		for isHex(.ch) || .ch == '_' {
			 := 1
			if .ch == '_' {
				 = 2
			}
			 |= 
			.next()
		}
	}
	return
}

func ( *Scanner) () (token.Token, string) {
	 := .offset
	 := token.ILLEGAL

	 := 10        // number base
	 := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b'
	 := 0       // bit 0: digit present, bit 1: '_' present
	 := -1     // index of invalid digit in literal, or < 0
integer part
	if .ch != '.' {
		 = token.INT
		if .ch == '0' {
			.next()
			switch lower(.ch) {
			case 'x':
				.next()
				,  = 16, 'x'
			case 'o':
				.next()
				,  = 8, 'o'
			case 'b':
				.next()
				,  = 2, 'b'
			default:
				,  = 8, '0'
				 = 1 // leading 0
			}
		}
		 |= .digits(, &)
	}
fractional part
	if .ch == '.' {
		 = token.FLOAT
		if  == 'o' ||  == 'b' {
			.error(.offset, "invalid radix point in "+litname())
		}
		.next()
		 |= .digits(, &)
	}

	if &1 == 0 {
		.error(.offset, litname()+" has no digits")
	}
exponent
	if  := lower(.ch);  == 'e' ||  == 'p' {
		switch {
		case  == 'e' &&  != 0 &&  != '0':
			.errorf(.offset, "%q exponent requires decimal mantissa", .ch)
		case  == 'p' &&  != 'x':
			.errorf(.offset, "%q exponent requires hexadecimal mantissa", .ch)
		}
		.next()
		 = token.FLOAT
		if .ch == '+' || .ch == '-' {
			.next()
		}
		 := .digits(10, nil)
		 |= 
		if &1 == 0 {
			.error(.offset, "exponent has no digits")
		}
	} else if  == 'x' &&  == token.FLOAT {
		.error(.offset, "hexadecimal mantissa requires a 'p' exponent")
	}
suffix 'i'
	if .ch == 'i' {
		 = token.IMAG
		.next()
	}

	 := string(.src[:.offset])
	if  == token.INT &&  >= 0 {
		.errorf(, "invalid digit %q in %s", [-], litname())
	}
	if &2 != 0 {
		if  := invalidSep();  >= 0 {
			.error(+, "'_' must separate successive digits")
		}
	}

	return , 
}

func ( rune) string {
	switch  {
	case 'x':
		return "hexadecimal literal"
	case 'o', '0':
		return "octal literal"
	case 'b':
		return "binary literal"
	}
	return "decimal literal"
}
invalidSep returns the index of the first invalid separator in x, or -1.
func ( string) int {
	 := ' ' // prefix char, we only care if it's 'x'
	 := '.'  // digit, one of '_', '0' (a digit), or '.' (anything else)
	 := 0
a prefix counts as a digit
	if len() >= 2 && [0] == '0' {
		 = lower(rune([1]))
		if  == 'x' ||  == 'o' ||  == 'b' {
			 = '0'
			 = 2
		}
	}
mantissa and exponent
	for ;  < len(); ++ {
		 :=  // previous digit
		 = rune([])
		switch {
		case  == '_':
			if  != '0' {
				return 
			}
		case isDecimal() ||  == 'x' && isHex():
			 = '0'
		default:
			if  == '_' {
				return  - 1
			}
			 = '.'
		}
	}
	if  == '_' {
		return len() - 1
	}

	return -1
}
scanEscape parses an escape sequence where rune is the accepted escaped quote. In case of a syntax error, it stops at the offending character (without consuming it) and returns false. Otherwise it returns true.
func ( *Scanner) ( rune) bool {
	 := .offset

	var  int
	var ,  uint32
	switch .ch {
	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', :
		.next()
		return true
	case '0', '1', '2', '3', '4', '5', '6', '7':
		, ,  = 3, 8, 255
	case 'x':
		.next()
		, ,  = 2, 16, 255
	case 'u':
		.next()
		, ,  = 4, 16, unicode.MaxRune
	case 'U':
		.next()
		, ,  = 8, 16, unicode.MaxRune
	default:
		 := "unknown escape sequence"
		if .ch < 0 {
			 = "escape sequence not terminated"
		}
		.error(, )
		return false
	}

	var  uint32
	for  > 0 {
		 := uint32(digitVal(.ch))
		if  >=  {
			 := fmt.Sprintf("illegal character %#U in escape sequence", .ch)
			if .ch < 0 {
				 = "escape sequence not terminated"
			}
			.error(.offset, )
			return false
		}
		 = * + 
		.next()
		--
	}

	if  >  || 0xD800 <=  &&  < 0xE000 {
		.error(, "escape sequence is invalid Unicode code point")
		return false
	}

	return true
}

'\'' opening already consumed
	 := .offset - 1

	 := true
	 := 0
	for {
		 := .ch
only report error if we don't have one already
			if  {
				.error(, "rune literal not terminated")
				 = false
			}
			break
		}
		.next()
		if  == '\'' {
			break
		}
		++
		if  == '\\' {
			if !.scanEscape('\'') {
				 = false
continue to read to closing quote
		}
	}

	if  &&  != 1 {
		.error(, "illegal rune literal")
	}

	return string(.src[:.offset])
}

'"' opening already consumed
	 := .offset - 1

	for {
		 := .ch
		if  == '\n' ||  < 0 {
			.error(, "string literal not terminated")
			break
		}
		.next()
		if  == '"' {
			break
		}
		if  == '\\' {
			.scanEscape('"')
		}
	}

	return string(.src[:.offset])
}

func ( []byte,  bool) []byte {
	 := make([]byte, len())
	 := 0
In a -style comment, don't strip \r from *\r/ (incl. sequences of \r from *\r\r...\r/) since the resulting would terminate the comment too early unless the \r is immediately following the opening in which case it's ok because / is not closed yet (issue #11151).
		if  != '\r' ||  &&  > len("/*") && [-1] == '*' && +1 < len() && [+1] == '/' {
			[] = 
			++
		}
	}
	return [:]
}

'`' opening already consumed
	 := .offset - 1

	 := false
	for {
		 := .ch
		if  < 0 {
			.error(, "raw string literal not terminated")
			break
		}
		.next()
		if  == '`' {
			break
		}
		if  == '\r' {
			 = true
		}
	}

	 := .src[:.offset]
	if  {
		 = stripCR(, false)
	}

	return string()
}

func ( *Scanner) () {
	for .ch == ' ' || .ch == '\t' || .ch == '\n' && !.insertSemi || .ch == '\r' {
		.next()
	}
}
Helper functions for scanning multi-byte tokens such as >> += >>= . Different routines recognize different length tok_i based on matches of ch_i. If a token ends in '=', the result is tok1 or tok3 respectively. Otherwise, the result is tok0 if there was no other matching character, or tok2 if the matching character was ch2.

func ( *Scanner) (,  token.Token) token.Token {
	if .ch == '=' {
		.next()
		return 
	}
	return 
}

func ( *Scanner) (,  token.Token,  rune,  token.Token) token.Token {
	if .ch == '=' {
		.next()
		return 
	}
	if .ch ==  {
		.next()
		return 
	}
	return 
}

func ( *Scanner) (,  token.Token,  rune, ,  token.Token) token.Token {
	if .ch == '=' {
		.next()
		return 
	}
	if .ch ==  {
		.next()
		if .ch == '=' {
			.next()
			return 
		}
		return 
	}
	return 
}
Scan scans the next token and returns the token position, the token, and its literal string if applicable. The source end is indicated by token.EOF. If the returned token is a literal (token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING) or token.COMMENT, the literal string has the corresponding value. If the returned token is a keyword, the literal string is the keyword. If the returned token is token.SEMICOLON, the corresponding literal string is ";" if the semicolon was present in the source, and "\n" if the semicolon was inserted because of a newline or at EOF. If the returned token is token.ILLEGAL, the literal string is the offending character. In all other cases, Scan returns an empty literal string. For more tolerant parsing, Scan will return a valid token if possible even if a syntax error was encountered. Thus, even if the resulting token sequence contains no illegal tokens, a client may not assume that no error occurred. Instead it must check the scanner's ErrorCount or the number of calls of the error handler, if there was one installed. Scan adds line information to the file added to the file set with Init. Token positions are relative to that file and thus relative to the file set.
func ( *Scanner) () ( token.Pos,  token.Token,  string) {
:
	.skipWhitespace()
current token start
	 = .file.Pos(.offset)
determine token value
	 := false
	switch  := .ch; {
	case isLetter():
		 = .scanIdentifier()
keywords are longer than one letter - avoid lookup otherwise
			 = token.Lookup()
			switch  {
			case token.IDENT, token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN:
				 = true
			}
		} else {
			 = true
			 = token.IDENT
		}
	case isDecimal() ||  == '.' && isDecimal(rune(.peek())):
		 = true
		,  = .scanNumber()
	default:
		.next() // always make progress
		switch  {
		case -1:
			if .insertSemi {
				.insertSemi = false // EOF consumed
				return , token.SEMICOLON, "\n"
			}
			 = token.EOF
we only reach here if s.insertSemi was set in the first place and exited early from s.skipWhitespace()
			.insertSemi = false // newline consumed
			return , token.SEMICOLON, "\n"
		case '"':
			 = true
			 = token.STRING
			 = .scanString()
		case '\'':
			 = true
			 = token.CHAR
			 = .scanRune()
		case '`':
			 = true
			 = token.STRING
			 = .scanRawString()
		case ':':
			 = .switch2(token.COLON, token.DEFINE)
fractions starting with a '.' are handled by outer switch
			 = token.PERIOD
			if .ch == '.' && .peek() == '.' {
				.next()
				.next() // consume last '.'
				 = token.ELLIPSIS
			}
		case ',':
			 = token.COMMA
		case ';':
			 = token.SEMICOLON
			 = ";"
		case '(':
			 = token.LPAREN
		case ')':
			 = true
			 = token.RPAREN
		case '[':
			 = token.LBRACK
		case ']':
			 = true
			 = token.RBRACK
		case '{':
			 = token.LBRACE
		case '}':
			 = true
			 = token.RBRACE
		case '+':
			 = .switch3(token.ADD, token.ADD_ASSIGN, '+', token.INC)
			if  == token.INC {
				 = true
			}
		case '-':
			 = .switch3(token.SUB, token.SUB_ASSIGN, '-', token.DEC)
			if  == token.DEC {
				 = true
			}
		case '*':
			 = .switch2(token.MUL, token.MUL_ASSIGN)
		case '/':
comment
reset position to the beginning of the comment
					.ch = '/'
					.offset = .file.Offset()
					.rdOffset = .offset + 1
					.insertSemi = false // newline consumed
					return , token.SEMICOLON, "\n"
				}
				 := .scanComment()
skip comment
					.insertSemi = false // newline consumed
					goto 
				}
				 = token.COMMENT
				 = 
			} else {
				 = .switch2(token.QUO, token.QUO_ASSIGN)
			}
		case '%':
			 = .switch2(token.REM, token.REM_ASSIGN)
		case '^':
			 = .switch2(token.XOR, token.XOR_ASSIGN)
		case '<':
			if .ch == '-' {
				.next()
				 = token.ARROW
			} else {
				 = .switch4(token.LSS, token.LEQ, '<', token.SHL, token.SHL_ASSIGN)
			}
		case '>':
			 = .switch4(token.GTR, token.GEQ, '>', token.SHR, token.SHR_ASSIGN)
		case '=':
			 = .switch2(token.ASSIGN, token.EQL)
		case '!':
			 = .switch2(token.NOT, token.NEQ)
		case '&':
			if .ch == '^' {
				.next()
				 = .switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
			} else {
				 = .switch3(token.AND, token.AND_ASSIGN, '&', token.LAND)
			}
		case '|':
			 = .switch3(token.OR, token.OR_ASSIGN, '|', token.LOR)
next reports unexpected BOMs - don't repeat
			if  != bom {
				.errorf(.file.Offset(), "illegal character %#U", )
			}
			 = .insertSemi // preserve insertSemi info
			 = token.ILLEGAL
			 = string()
		}
	}
	if .mode&dontInsertSemis == 0 {
		.insertSemi = 
	}

	return