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 gcfg configuration text. It takes a []byte as source which can then be tokenized through repeated calls to the Scan method. Note that the API for the scanner package may change to accommodate new features or implementation changes in gcfg.
package scanner

import (
	
	
	
	
)

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
	nextVal    bool // next token is expected to be a value
public state - ok to modify
	ErrorCount int // number of errors encountered
}
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")
			}
		}
		.rdOffset += 
		.ch = 
	} else {
		.offset = len(.src)
		if .ch == '\n' {
			.lineOffset = .offset
			.file.AddLine(.offset)
		}
		.ch = -1 // eof
	}
}
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
)
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
	.ErrorCount = 0
	.nextVal = false

	.next()
}

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

initial [;#] already consumed
	 := .offset - 1 // position of initial [;#]

	for .ch != '\n' && .ch >= 0 {
		.next()
	}
	return string(.src[:.offset])
}

func ( rune) bool {
	return 'a' <=  &&  <= 'z' || 'A' <=  &&  <= 'Z' ||  >= 0x80 && unicode.IsLetter()
}

func ( rune) bool {
	return '0' <=  &&  <= '9' ||  >= 0x80 && unicode.IsDigit()
}

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

func ( *Scanner) ( bool) {
	 := .offset
	 := .ch
	.next() // always make progress
	switch  {
ok
	case 'n', 't', 'b':
		if  {
			break // ok
		}
		fallthrough
	default:
		.error(, "unknown escape sequence")
	}
}

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

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

	.next()

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

func ( []byte) []byte {
	 := make([]byte, len())
	 := 0
	for ,  := range  {
		if  != '\r' {
			[] = 
			++
		}
	}
	return [:]
}

func ( *Scanner) () string {
	 := .offset

	 := false
	 := 
	 := false
:
	for  || .ch >= 0 && .ch != '\n' && .ch != ';' && .ch != '#' {
		 := .ch
		.next()
		switch {
		case  &&  == '\\':
			.scanEscape(true)
		case ! &&  == '\\':
			if .ch == '\r' {
				 = true
				.next()
			}
			if .ch != '\n' {
				.scanEscape(true)
			} else {
				.next()
			}
		case  == '"':
			 = !
		case  == '\r':
			 = true
		case  < 0 ||  &&  == '\n':
			.error(, "string not terminated")
			break 
		}
		if  || !isWhiteSpace() {
			 = .offset
		}
	}

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

	return string()
}

func ( rune) bool {
	return  == ' ' ||  == '\t' ||  == '\r'
}

func ( *Scanner) () {
	for isWhiteSpace(.ch) {
		.next()
	}
}
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.STRING) or token.COMMENT, the literal string has the corresponding value. 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
	switch  := .ch; {
	case .nextVal:
		 = .scanValString()
		 = token.STRING
		.nextVal = false
	case isLetter():
		 = .scanIdentifier()
		 = token.IDENT
	default:
		.next() // always make progress
		switch  {
		case -1:
			 = token.EOF
		case '\n':
			 = token.EOL
		case '"':
			 = token.STRING
			 = .scanString()
		case '[':
			 = token.LBRACK
		case ']':
			 = token.RBRACK
comment
			 = .scanComment()
skip comment
				goto 
			}
			 = token.COMMENT
		case '=':
			 = token.ASSIGN
			.nextVal = true
		default:
			.error(.file.Offset(), fmt.Sprintf("illegal character %#U", ))
			 = token.ILLEGAL
			 = string()
		}
	}

	return