| // 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. |
| |
| // This file is forked from go/build/read.go. |
| // (cmd/dist must not import go/build because we do not want it to be |
| // sensitive to the specific version of go/build present in $GOROOT_BOOTSTRAP.) |
| |
| package main |
| |
| import ( |
| "bufio" |
| "errors" |
| "io" |
| "strconv" |
| "strings" |
| "unicode/utf8" |
| ) |
| |
| type importReader struct { |
| b *bufio.Reader |
| buf []byte |
| peek byte |
| err error |
| eof bool |
| nerr int |
| } |
| |
| func isIdent(c byte) bool { |
| return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= 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 (r *importReader) syntaxError() { |
| if r.err == nil { |
| r.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 (r *importReader) readByte() byte { |
| c, err := r.b.ReadByte() |
| if err == nil { |
| r.buf = append(r.buf, c) |
| if c == 0 { |
| err = errNUL |
| } |
| } |
| if err != nil { |
| if err == io.EOF { |
| r.eof = true |
| } else if r.err == nil { |
| r.err = err |
| } |
| c = 0 |
| } |
| return c |
| } |
| |
| // 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 (r *importReader) peekByte(skipSpace bool) byte { |
| if r.err != nil { |
| if r.nerr++; r.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). |
| c := r.peek |
| if c == 0 { |
| c = r.readByte() |
| } |
| for r.err == nil && !r.eof { |
| if skipSpace { |
| // For the purposes of this reader, semicolons are never necessary to |
| // understand the input and are treated as spaces. |
| switch c { |
| case ' ', '\f', '\t', '\r', '\n', ';': |
| c = r.readByte() |
| continue |
| |
| case '/': |
| c = r.readByte() |
| if c == '/' { |
| for c != '\n' && r.err == nil && !r.eof { |
| c = r.readByte() |
| } |
| } else if c == '*' { |
| var c1 byte |
| for (c != '*' || c1 != '/') && r.err == nil { |
| if r.eof { |
| r.syntaxError() |
| } |
| c, c1 = c1, r.readByte() |
| } |
| } else { |
| r.syntaxError() |
| } |
| c = r.readByte() |
| continue |
| } |
| } |
| break |
| } |
| r.peek = c |
| return r.peek |
| } |
| |
| // nextByte is like peekByte but advances beyond the returned byte. |
| func (r *importReader) nextByte(skipSpace bool) byte { |
| c := r.peekByte(skipSpace) |
| r.peek = 0 |
| return c |
| } |
| |
| // readKeyword reads the given keyword from the input. |
| // If the keyword is not present, readKeyword records a syntax error. |
| func (r *importReader) readKeyword(kw string) { |
| r.peekByte(true) |
| for i := 0; i < len(kw); i++ { |
| if r.nextByte(false) != kw[i] { |
| r.syntaxError() |
| return |
| } |
| } |
| if isIdent(r.peekByte(false)) { |
| r.syntaxError() |
| } |
| } |
| |
| // readIdent reads an identifier from the input. |
| // If an identifier is not present, readIdent records a syntax error. |
| func (r *importReader) readIdent() { |
| c := r.peekByte(true) |
| if !isIdent(c) { |
| r.syntaxError() |
| return |
| } |
| for isIdent(r.peekByte(false)) { |
| r.peek = 0 |
| } |
| } |
| |
| // readString reads a quoted string literal from the input. |
| // If an identifier is not present, readString records a syntax error. |
| func (r *importReader) readString(save *[]string) { |
| switch r.nextByte(true) { |
| case '`': |
| start := len(r.buf) - 1 |
| for r.err == nil { |
| if r.nextByte(false) == '`' { |
| if save != nil { |
| *save = append(*save, string(r.buf[start:])) |
| } |
| break |
| } |
| if r.eof { |
| r.syntaxError() |
| } |
| } |
| case '"': |
| start := len(r.buf) - 1 |
| for r.err == nil { |
| c := r.nextByte(false) |
| if c == '"' { |
| if save != nil { |
| *save = append(*save, string(r.buf[start:])) |
| } |
| break |
| } |
| if r.eof || c == '\n' { |
| r.syntaxError() |
| } |
| if c == '\\' { |
| r.nextByte(false) |
| } |
| } |
| default: |
| r.syntaxError() |
| } |
| } |
| |
| // readImport reads an import clause - optional identifier followed by quoted string - |
| // from the input. |
| func (r *importReader) readImport(imports *[]string) { |
| c := r.peekByte(true) |
| if c == '.' { |
| r.peek = 0 |
| } else if isIdent(c) { |
| r.readIdent() |
| } |
| r.readString(imports) |
| } |
| |
| // readComments is like ioutil.ReadAll, except that it only reads the leading |
| // block of comments in the file. |
| func readComments(f io.Reader) ([]byte, error) { |
| r := &importReader{b: bufio.NewReader(f)} |
| r.peekByte(true) |
| if r.err == nil && !r.eof { |
| // Didn't reach EOF, so must have found a non-space byte. Remove it. |
| r.buf = r.buf[:len(r.buf)-1] |
| } |
| return r.buf, r.err |
| } |
| |
| // readimports returns the imports found in the named file. |
| func readimports(file string) []string { |
| var imports []string |
| r := &importReader{b: bufio.NewReader(strings.NewReader(readfile(file)))} |
| r.readKeyword("package") |
| r.readIdent() |
| for r.peekByte(true) == 'i' { |
| r.readKeyword("import") |
| if r.peekByte(true) == '(' { |
| r.nextByte(false) |
| for r.peekByte(true) != ')' && r.err == nil { |
| r.readImport(&imports) |
| } |
| r.nextByte(false) |
| } else { |
| r.readImport(&imports) |
| } |
| } |
| |
| for i := range imports { |
| unquoted, err := strconv.Unquote(imports[i]) |
| if err != nil { |
| fatalf("reading imports from %s: %v", file, err) |
| } |
| imports[i] = unquoted |
| } |
| |
| return imports |
| } |