func (S *Scanner) switch2(tok0, tok1 token.Token) token.Token {
	if S.ch == '=' {
		S.next()
		return tok1
	}
	return tok0
}
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.
package scanner
import (
	"bytes"
	"fmt"
	"path/filepath"
	"strconv"
	"unicode"
	"unicode/utf8"
	"github.com/rogpeppe/godef/go/token"
)
type Scanner struct {
	file *token.File   
	dir  string        
	src  []byte        
	err  ErrorHandler  
	mode uint          
	ch         rune  
	offset     int   
	rdOffset   int   
	lineOffset int   
	insertSemi bool  
	ErrorCount int  
}
func (S *Scanner) next() {
	if S.rdOffset < len(S.src) {
		S.offset = S.rdOffset
		if S.ch == '\n' {
			S.lineOffset = S.offset
			S.file.AddLine(S.offset)
		}
		r, w := rune(S.src[S.rdOffset]), 1
		switch {
		case r == 0:
			S.error(S.offset, "illegal character NUL")
		case r >= 0x80:
			r, w = utf8.DecodeRune(S.src[S.rdOffset:])
			if r == utf8.RuneError && w == 1 {
				S.error(S.offset, "illegal UTF-8 encoding")
			}
		}
		S.rdOffset += w
		S.ch = r
	} else {
		S.offset = len(S.src)
		if S.ch == '\n' {
			S.lineOffset = S.offset
			S.file.AddLine(S.offset)
		}
		S.ch = -1  
	}
}
const (
	ScanComments      = 1 << iota  
	AllowIllegalChars              
	InsertSemis                    
)
func (S *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode uint) {
	if file.Size() != len(src) {
		panic("file size does not match src len")
	}
	S.file = file
	S.dir, _ = filepath.Split(file.Name())
	S.src = src
	S.err = err
	S.mode = mode
	S.ch = ' '
	S.offset = 0
	S.rdOffset = 0
	S.lineOffset = 0
	S.insertSemi = false
	S.ErrorCount = 0
	S.next()
}
func (S *Scanner) error(offs int, msg string) {
	if S.err != nil {
		S.err.Error(S.file.Position(S.file.Pos(offs)), msg)
	}
	S.ErrorCount++
}
var prefix = []byte("//line ")
func (S *Scanner) interpretLineComment(text []byte) {
	if bytes.HasPrefix(text, prefix) {
		if i := bytes.LastIndex(text, []byte{':'}); i > 0 {
			if line, err := strconv.Atoi(string(text[i+1:])); err == nil && line > 0 {
				filename := filepath.Clean(string(text[len(prefix):i]))
				if !filepath.IsAbs(filename) {
					filename = filepath.Join(S.dir, filename)
				}
				S.file.AddLineInfo(S.lineOffset, filename, line-1)  
			}
		}
	}
}
func (S *Scanner) scanComment() {
	offs := S.offset - 1  
	if S.ch == '/' {
		S.next()
		for S.ch != '\n' && S.ch >= 0 {
			S.next()
		}
		if offs == S.lineOffset {
			S.interpretLineComment(S.src[offs:S.offset])
		}
		return
	}
	S.next()
	for S.ch >= 0 {
		ch := S.ch
		S.next()
		if ch == '*' && S.ch == '/' {
			S.next()
			return
		}
	}
	S.error(offs, "comment not terminated")
}
func (S *Scanner) findLineEnd() bool {
	defer func(offs int) {
		S.ch = '/'
		S.offset = offs
		S.rdOffset = offs + 1
		S.next()  
	}(S.offset - 1)
	for S.ch == '/' || S.ch == '*' {
		if S.ch == '/' {
			return true
		}
		S.next()
		for S.ch >= 0 {
			ch := S.ch
			if ch == '\n' {
				return true
			}
			S.next()
			if ch == '*' && S.ch == '/' {
				S.next()
				break
			}
		}
		S.skipWhitespace()  
		if S.ch < 0 || S.ch == '\n' {
			return true
		}
		if S.ch != '/' {
			return false
		}
		S.next()  
	}
	return false
}
func isLetter(ch rune) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
}
func isDigit(ch rune) bool {
	return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
}
func (S *Scanner) scanIdentifier() token.Token {
	offs := S.offset
	for isLetter(S.ch) || isDigit(S.ch) {
		S.next()
	}
	return token.Lookup(S.src[offs:S.offset])
}
func digitVal(ch rune) int {
	switch {
	case '0' <= ch && ch <= '9':
		return int(ch - '0')
	case 'a' <= ch && ch <= 'f':
		return int(ch - 'a' + 10)
	case 'A' <= ch && ch <= 'F':
		return int(ch - 'A' + 10)
	}
	return 16  
}
func (S *Scanner) scanMantissa(base int) {
	for digitVal(S.ch) < base {
		S.next()
	}
}
func (S *Scanner) scanNumber(seenDecimalPoint bool) token.Token {
	tok := token.INT
	if seenDecimalPoint {
		tok = token.FLOAT
		S.scanMantissa(10)
		goto exponent
	}
	if S.ch == '0' {
		offs := S.offset
		S.next()
		if S.ch == 'x' || S.ch == 'X' {
			S.next()
			S.scanMantissa(16)
			if S.offset-offs <= 2 {
				S.error(offs, "illegal hexadecimal number")
			}
		} else {
			seenDecimalDigit := false
			S.scanMantissa(8)
			if S.ch == '8' || S.ch == '9' {
				seenDecimalDigit = true
				S.scanMantissa(10)
			}
			if S.ch == '.' || S.ch == 'e' || S.ch == 'E' || S.ch == 'i' {
				goto fraction
			}
			if seenDecimalDigit {
				S.error(offs, "illegal octal number")
			}
		}
		goto exit
	}
	S.scanMantissa(10)
fraction:
	if S.ch == '.' {
		tok = token.FLOAT
		S.next()
		S.scanMantissa(10)
	}
exponent:
	if S.ch == 'e' || S.ch == 'E' {
		tok = token.FLOAT
		S.next()
		if S.ch == '-' || S.ch == '+' {
			S.next()
		}
		S.scanMantissa(10)
	}
	if S.ch == 'i' {
		tok = token.IMAG
		S.next()
	}
exit:
	return tok
}
func (S *Scanner) scanEscape(quote rune) {
	offs := S.offset
	var i, base, max uint32
	switch S.ch {
	case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote:
		S.next()
		return
	case '0', '1', '2', '3', '4', '5', '6', '7':
		i, base, max = 3, 8, 255
	case 'x':
		S.next()
		i, base, max = 2, 16, 255
	case 'u':
		S.next()
		i, base, max = 4, 16, unicode.MaxRune
	case 'U':
		S.next()
		i, base, max = 8, 16, unicode.MaxRune
	default:
		S.next()  
		S.error(offs, "unknown escape sequence")
		return
	}
	var x uint32
	for ; i > 0 && S.ch != quote && S.ch >= 0; i-- {
		d := uint32(digitVal(S.ch))
		if d >= base {
			S.error(S.offset, "illegal character in escape sequence")
			break
		}
		x = x*base + d
		S.next()
	}
	for ; i > 0 && S.ch != quote && S.ch >= 0; i-- {
		S.next()
	}
	if x > max || 0xd800 <= x && x < 0xe000 {
		S.error(offs, "escape sequence is invalid Unicode code point")
	}
}
func (S *Scanner) scanChar() {
	offs := S.offset - 1
	n := 0
	for S.ch != '\'' {
		ch := S.ch
		n++
		S.next()
		if ch == '\n' || ch < 0 {
			S.error(offs, "character literal not terminated")
			n = 1
			break
		}
		if ch == '\\' {
			S.scanEscape('\'')
		}
	}
	S.next()
	if n != 1 {
		S.error(offs, "illegal character literal")
	}
}
func (S *Scanner) scanString() {
	offs := S.offset - 1
	for S.ch != '"' {
		ch := S.ch
		S.next()
		if ch == '\n' || ch < 0 {
			S.error(offs, "string not terminated")
			break
		}
		if ch == '\\' {
			S.scanEscape('"')
		}
	}
	S.next()
}
func (S *Scanner) scanRawString() {
	offs := S.offset - 1
	for S.ch != '`' {
		ch := S.ch
		S.next()
		if ch < 0 {
			S.error(offs, "string not terminated")
			break
		}
	}
	S.next()
}
func (S *Scanner) skipWhitespace() {
	for S.ch == ' ' || S.ch == '\t' || S.ch == '\n' && !S.insertSemi || S.ch == '\r' {
		S.next()
	}
}