Copyright 2012 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 build

import (
	
	
	
	
	
	
	
	
	
	
	
)

type importReader struct {
	b    *bufio.Reader
	buf  []byte
	peek byte
	err  error
	eof  bool
	nerr int
	pos  token.Position
}

func ( string,  io.Reader) *importReader {
	return &importReader{
		b: bufio.NewReader(),
		pos: token.Position{
			Filename: ,
			Line:     1,
			Column:   1,
		},
	}
}

func ( byte) bool {
	return 'A' <=  &&  <= 'Z' || 'a' <=  &&  <= 'z' || '0' <=  &&  <= '9' ||  == '_' ||  >= utf8.RuneSelf
}

var (
	errSyntax = errors.New("syntax error")
	errNUL    = errors.New("unexpected NUL in input")
)
syntaxError records a syntax error, but only if an I/O error has not already been recorded.
func ( *importReader) () {
	if .err == nil {
		.err = errSyntax
	}
}
readByte reads the next byte from the input, saves it in buf, and returns it. If an error occurs, readByte records the error in r.err and returns 0.
func ( *importReader) () byte {
	,  := .b.ReadByte()
	if  == nil {
		.buf = append(.buf, )
		if  == 0 {
			 = errNUL
		}
	}
	if  != nil {
		if  == io.EOF {
			.eof = true
		} else if .err == nil {
			.err = 
		}
		 = 0
	}
	return 
}
readByteNoBuf is like readByte but doesn't buffer the byte. It exhausts r.buf before reading from r.b.
func ( *importReader) () byte {
	var  byte
	var  error
	if len(.buf) > 0 {
		 = .buf[0]
		.buf = .buf[1:]
	} else {
		,  = .b.ReadByte()
		if  == nil &&  == 0 {
			 = errNUL
		}
	}

	if  != nil {
		if  == io.EOF {
			.eof = true
		} else if .err == nil {
			.err = 
		}
		return 0
	}
	.pos.Offset++
	if  == '\n' {
		.pos.Line++
		.pos.Column = 1
	} else {
		.pos.Column++
	}
	return 
}
peekByte returns the next byte from the input reader but does not advance beyond it. If skipSpace is set, peekByte skips leading spaces and comments.
func ( *importReader) ( bool) byte {
	if .err != nil {
		if .nerr++; .nerr > 10000 {
			panic("go/build: import reader looping")
		}
		return 0
	}
Use r.peek as first input byte. Don't just return r.peek here: it might have been left by peekByte(false) and this might be peekByte(true).
	 := .peek
	if  == 0 {
		 = .readByte()
	}
	for .err == nil && !.eof {
For the purposes of this reader, semicolons are never necessary to understand the input and are treated as spaces.
			switch  {
			case ' ', '\f', '\t', '\r', '\n', ';':
				 = .readByte()
				continue

			case '/':
				 = .readByte()
				if  == '/' {
					for  != '\n' && .err == nil && !.eof {
						 = .readByte()
					}
				} else if  == '*' {
					var  byte
					for ( != '*' ||  != '/') && .err == nil {
						if .eof {
							.syntaxError()
						}
						,  = , .readByte()
					}
				} else {
					.syntaxError()
				}
				 = .readByte()
				continue
			}
		}
		break
	}
	.peek = 
	return .peek
}
nextByte is like peekByte but advances beyond the returned byte.
func ( *importReader) ( bool) byte {
	 := .peekByte()
	.peek = 0
	return 
}

var goEmbed = []byte("go:embed")
findEmbed advances the input reader to the next //go:embed comment. It reports whether it found a comment. (Otherwise it found an error or EOF.)
The import block scan stopped after a non-space character, so the reader is not at the start of a line on the first call. After that, each //go:embed extraction leaves the reader at the end of a line.
	 := !
	var  byte
	for .err == nil && !.eof {
		 = .readByteNoBuf()
	:
		switch  {
		default:
			 = false

		case '\n':
			 = true

leave startLine alone

		case '"':
			 = false
			for .err == nil {
				if .eof {
					.syntaxError()
				}
				 = .readByteNoBuf()
				if  == '\\' {
					.readByteNoBuf()
					if .err != nil {
						.syntaxError()
						return false
					}
					continue
				}
				if  == '"' {
					 = .readByteNoBuf()
					goto 
				}
			}
			goto 

		case '`':
			 = false
			for .err == nil {
				if .eof {
					.syntaxError()
				}
				 = .readByteNoBuf()
				if  == '`' {
					 = .readByteNoBuf()
					goto 
				}
			}

		case '/':
			 = .readByteNoBuf()
			switch  {
			default:
				 = false
				goto 

			case '*':
				var  byte
				for ( != '*' ||  != '/') && .err == nil {
					if .eof {
						.syntaxError()
					}
					,  = , .readByteNoBuf()
				}
				 = false

			case '/':
Try to read this as a //go:embed comment.
					for  := range goEmbed {
						 = .readByteNoBuf()
						if  != goEmbed[] {
							goto 
						}
					}
					 = .readByteNoBuf()
Found one!
						return true
					}
				}
			:
				for  != '\n' && .err == nil && !.eof {
					 = .readByteNoBuf()
				}
				 = true
			}
		}
	}
	return false
}
readKeyword reads the given keyword from the input. If the keyword is not present, readKeyword records a syntax error.
func ( *importReader) ( string) {
	.peekByte(true)
	for  := 0;  < len(); ++ {
		if .nextByte(false) != [] {
			.syntaxError()
			return
		}
	}
	if isIdent(.peekByte(false)) {
		.syntaxError()
	}
}
readIdent reads an identifier from the input. If an identifier is not present, readIdent records a syntax error.
func ( *importReader) () {
	 := .peekByte(true)
	if !isIdent() {
		.syntaxError()
		return
	}
	for isIdent(.peekByte(false)) {
		.peek = 0
	}
}
readString reads a quoted string literal from the input. If an identifier is not present, readString records a syntax error.
func ( *importReader) () {
	switch .nextByte(true) {
	case '`':
		for .err == nil {
			if .nextByte(false) == '`' {
				break
			}
			if .eof {
				.syntaxError()
			}
		}
	case '"':
		for .err == nil {
			 := .nextByte(false)
			if  == '"' {
				break
			}
			if .eof ||  == '\n' {
				.syntaxError()
			}
			if  == '\\' {
				.nextByte(false)
			}
		}
	default:
		.syntaxError()
	}
}
readImport reads an import clause - optional identifier followed by quoted string - from the input.
func ( *importReader) () {
	 := .peekByte(true)
	if  == '.' {
		.peek = 0
	} else if isIdent() {
		.readIdent()
	}
	.readString()
}
readComments is like io.ReadAll, except that it only reads the leading block of comments in the file.
func ( io.Reader) ([]byte, error) {
	 := newImportReader("", )
	.peekByte(true)
Didn't reach EOF, so must have found a non-space byte. Remove it.
		.buf = .buf[:len(.buf)-1]
	}
	return .buf, .err
}
readGoInfo expects a Go file as input and reads the file up to and including the import section. It records what it learned in *info. If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr, info.imports, info.embeds, and info.embedErr. It only returns an error if there are problems reading the file, not for syntax errors in the file itself.
func ( io.Reader,  *fileInfo) error {
	 := newImportReader(.name, )

	.readKeyword("package")
	.readIdent()
	for .peekByte(true) == 'i' {
		.readKeyword("import")
		if .peekByte(true) == '(' {
			.nextByte(false)
			for .peekByte(true) != ')' && .err == nil {
				.readImport()
			}
			.nextByte(false)
		} else {
			.readImport()
		}
	}

	.header = .buf
If we stopped successfully before EOF, we read a byte that told us we were done. Return all but that last byte, which would cause a syntax error if we let it through.
	if .err == nil && !.eof {
		.header = .buf[:len(.buf)-1]
	}
If we stopped for a syntax error, consume the whole file so that we are sure we don't change the errors that go/parser returns.
	if .err == errSyntax {
		.err = nil
		for .err == nil && !.eof {
			.readByte()
		}
		.header = .buf
	}
	if .err != nil {
		return .err
	}

	if .fset == nil {
		return nil
	}
Parse file header & record imports.
	.parsed, .parseErr = parser.ParseFile(.fset, .name, .header, parser.ImportsOnly|parser.ParseComments)
	if .parseErr != nil {
		return nil
	}

	 := false
	for ,  := range .parsed.Decls {
		,  := .(*ast.GenDecl)
		if ! {
			continue
		}
		for ,  := range .Specs {
			,  := .(*ast.ImportSpec)
			if ! {
				continue
			}
			 := .Path.Value
			,  := strconv.Unquote()
			if  != nil {
				return fmt.Errorf("parser returned invalid quoted string: <%s>", )
			}
			if  == "embed" {
				 = true
			}

			 := .Doc
			if  == nil && len(.Specs) == 1 {
				 = .Doc
			}
			.imports = append(.imports, fileImport{, .Pos(), })
		}
	}
If the file imports "embed", we have to look for //go:embed comments in the remainder of the file. The compiler will enforce the mapping of comments to declared variables. We just need to know the patterns. If there were //go:embed comments earlier in the file (near the package statement or imports), the compiler will reject them. They can be (and have already been) ignored.
	if  {
		var  []byte
		for  := true; .findEmbed();  = false {
			 = [:0]
			 := .pos
			for {
				 := .readByteNoBuf()
				if  == '\n' || .err != nil || .eof {
					break
				}
				 = append(, )
Add args if line is well-formed. Ignore badly-formed lines - the compiler will report them when it finds them, and we can pretend they are not there to help go list succeed with what it knows.
			,  := parseGoEmbed(string(), )
			if  == nil {
				.embeds = append(.embeds, ...)
			}
		}
	}

	return nil
}
parseGoEmbed parses the text following "//go:embed" to extract the glob patterns. It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings. This is based on a similar function in cmd/compile/internal/gc/noder.go; this version calculates position information as well.
func ( string,  token.Position) ([]fileEmbed, error) {
	 := func( int) {
		.Offset += 
		.Column += utf8.RuneCountInString([:])
		 = [:]
	}
	 := func() {
		 := strings.TrimLeftFunc(, unicode.IsSpace)
		(len() - len())
	}

	var  []fileEmbed
	for ();  != ""; () {
		var  string
		 := 
	:
		switch [0] {
		default:
			 := len()
			for ,  := range  {
				if unicode.IsSpace() {
					 = 
					break
				}
			}
			 = [:]
			()

		case '`':
			 := strings.Index([1:], "`")
			if  < 0 {
				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", )
			}
			 = [1 : 1+]
			(1 +  + 1)

		case '"':
			 := 1
			for ;  < len(); ++ {
				if [] == '\\' {
					++
					continue
				}
				if [] == '"' {
					,  := strconv.Unquote([:+1])
					if  != nil {
						return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", [:+1])
					}
					 = 
					( + 1)
					break 
				}
			}
			if  >= len() {
				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", )
			}
		}

		if  != "" {
			,  := utf8.DecodeRuneInString()
			if !unicode.IsSpace() {
				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", )
			}
		}
		 = append(, fileEmbed{, })
	}
	return , nil