1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// This file is forked from go/build/read.go.
6// (cmd/dist must not import go/build because we do not want it to be
7// sensitive to the specific version of go/build present in $GOROOT_BOOTSTRAP.)
8
9package main
10
11import (
12	"bufio"
13	"errors"
14	"fmt"
15	"io"
16	"path"
17	"path/filepath"
18	"strconv"
19	"strings"
20	"unicode/utf8"
21)
22
23type importReader struct {
24	b    *bufio.Reader
25	buf  []byte
26	peek byte
27	err  error
28	eof  bool
29	nerr int
30}
31
32func isIdent(c byte) bool {
33	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
34}
35
36var (
37	errSyntax = errors.New("syntax error")
38	errNUL    = errors.New("unexpected NUL in input")
39)
40
41// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
42func (r *importReader) syntaxError() {
43	if r.err == nil {
44		r.err = errSyntax
45	}
46}
47
48// readByte reads the next byte from the input, saves it in buf, and returns it.
49// If an error occurs, readByte records the error in r.err and returns 0.
50func (r *importReader) readByte() byte {
51	c, err := r.b.ReadByte()
52	if err == nil {
53		r.buf = append(r.buf, c)
54		if c == 0 {
55			err = errNUL
56		}
57	}
58	if err != nil {
59		if err == io.EOF {
60			r.eof = true
61		} else if r.err == nil {
62			r.err = err
63		}
64		c = 0
65	}
66	return c
67}
68
69// peekByte returns the next byte from the input reader but does not advance beyond it.
70// If skipSpace is set, peekByte skips leading spaces and comments.
71func (r *importReader) peekByte(skipSpace bool) byte {
72	if r.err != nil {
73		if r.nerr++; r.nerr > 10000 {
74			panic("go/build: import reader looping")
75		}
76		return 0
77	}
78
79	// Use r.peek as first input byte.
80	// Don't just return r.peek here: it might have been left by peekByte(false)
81	// and this might be peekByte(true).
82	c := r.peek
83	if c == 0 {
84		c = r.readByte()
85	}
86	for r.err == nil && !r.eof {
87		if skipSpace {
88			// For the purposes of this reader, semicolons are never necessary to
89			// understand the input and are treated as spaces.
90			switch c {
91			case ' ', '\f', '\t', '\r', '\n', ';':
92				c = r.readByte()
93				continue
94
95			case '/':
96				c = r.readByte()
97				if c == '/' {
98					for c != '\n' && r.err == nil && !r.eof {
99						c = r.readByte()
100					}
101				} else if c == '*' {
102					var c1 byte
103					for (c != '*' || c1 != '/') && r.err == nil {
104						if r.eof {
105							r.syntaxError()
106						}
107						c, c1 = c1, r.readByte()
108					}
109				} else {
110					r.syntaxError()
111				}
112				c = r.readByte()
113				continue
114			}
115		}
116		break
117	}
118	r.peek = c
119	return r.peek
120}
121
122// nextByte is like peekByte but advances beyond the returned byte.
123func (r *importReader) nextByte(skipSpace bool) byte {
124	c := r.peekByte(skipSpace)
125	r.peek = 0
126	return c
127}
128
129// readKeyword reads the given keyword from the input.
130// If the keyword is not present, readKeyword records a syntax error.
131func (r *importReader) readKeyword(kw string) {
132	r.peekByte(true)
133	for i := 0; i < len(kw); i++ {
134		if r.nextByte(false) != kw[i] {
135			r.syntaxError()
136			return
137		}
138	}
139	if isIdent(r.peekByte(false)) {
140		r.syntaxError()
141	}
142}
143
144// readIdent reads an identifier from the input.
145// If an identifier is not present, readIdent records a syntax error.
146func (r *importReader) readIdent() {
147	c := r.peekByte(true)
148	if !isIdent(c) {
149		r.syntaxError()
150		return
151	}
152	for isIdent(r.peekByte(false)) {
153		r.peek = 0
154	}
155}
156
157// readString reads a quoted string literal from the input.
158// If an identifier is not present, readString records a syntax error.
159func (r *importReader) readString(save *[]string) {
160	switch r.nextByte(true) {
161	case '`':
162		start := len(r.buf) - 1
163		for r.err == nil {
164			if r.nextByte(false) == '`' {
165				if save != nil {
166					*save = append(*save, string(r.buf[start:]))
167				}
168				break
169			}
170			if r.eof {
171				r.syntaxError()
172			}
173		}
174	case '"':
175		start := len(r.buf) - 1
176		for r.err == nil {
177			c := r.nextByte(false)
178			if c == '"' {
179				if save != nil {
180					*save = append(*save, string(r.buf[start:]))
181				}
182				break
183			}
184			if r.eof || c == '\n' {
185				r.syntaxError()
186			}
187			if c == '\\' {
188				r.nextByte(false)
189			}
190		}
191	default:
192		r.syntaxError()
193	}
194}
195
196// readImport reads an import clause - optional identifier followed by quoted string -
197// from the input.
198func (r *importReader) readImport(imports *[]string) {
199	c := r.peekByte(true)
200	if c == '.' {
201		r.peek = 0
202	} else if isIdent(c) {
203		r.readIdent()
204	}
205	r.readString(imports)
206}
207
208// readComments is like ioutil.ReadAll, except that it only reads the leading
209// block of comments in the file.
210func readComments(f io.Reader) ([]byte, error) {
211	r := &importReader{b: bufio.NewReader(f)}
212	r.peekByte(true)
213	if r.err == nil && !r.eof {
214		// Didn't reach EOF, so must have found a non-space byte. Remove it.
215		r.buf = r.buf[:len(r.buf)-1]
216	}
217	return r.buf, r.err
218}
219
220// readimports returns the imports found in the named file.
221func readimports(file string) []string {
222	var imports []string
223	r := &importReader{b: bufio.NewReader(strings.NewReader(readfile(file)))}
224	r.readKeyword("package")
225	r.readIdent()
226	for r.peekByte(true) == 'i' {
227		r.readKeyword("import")
228		if r.peekByte(true) == '(' {
229			r.nextByte(false)
230			for r.peekByte(true) != ')' && r.err == nil {
231				r.readImport(&imports)
232			}
233			r.nextByte(false)
234		} else {
235			r.readImport(&imports)
236		}
237	}
238
239	for i := range imports {
240		unquoted, err := strconv.Unquote(imports[i])
241		if err != nil {
242			fatalf("reading imports from %s: %v", file, err)
243		}
244		imports[i] = unquoted
245	}
246
247	return imports
248}
249
250// resolveVendor returns a unique package path imported with the given import
251// path from srcDir.
252//
253// resolveVendor assumes that a package is vendored if and only if its first
254// path component contains a dot. If a package is vendored, its import path
255// is returned with a "vendor" or "cmd/vendor" prefix, depending on srcDir.
256// Otherwise, the import path is returned verbatim.
257func resolveVendor(imp, srcDir string) string {
258	var first string
259	if i := strings.Index(imp, "/"); i < 0 {
260		first = imp
261	} else {
262		first = imp[:i]
263	}
264	isStandard := !strings.Contains(first, ".")
265	if isStandard {
266		return imp
267	}
268
269	if strings.HasPrefix(srcDir, filepath.Join(goroot, "src", "cmd")) {
270		return path.Join("cmd", "vendor", imp)
271	} else if strings.HasPrefix(srcDir, filepath.Join(goroot, "src")) {
272		return path.Join("vendor", imp)
273	} else {
274		panic(fmt.Sprintf("srcDir %q not in GOOROT/src", srcDir))
275	}
276}
277