• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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// Copied from Go distribution src/go/build/read.go.
6
7package imports
8
9import (
10	"bufio"
11	"bytes"
12	"errors"
13	"io"
14	"unicode/utf8"
15)
16
17type importReader struct {
18	b    *bufio.Reader
19	buf  []byte
20	peek byte
21	err  error
22	eof  bool
23	nerr int
24}
25
26var bom = []byte{0xef, 0xbb, 0xbf}
27
28func newImportReader(b *bufio.Reader) *importReader {
29	// Remove leading UTF-8 BOM.
30	// Per https://golang.org/ref/spec#Source_code_representation:
31	// a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF)
32	// if it is the first Unicode code point in the source text.
33	if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) {
34		b.Discard(3)
35	}
36	return &importReader{b: b}
37}
38
39func isIdent(c byte) bool {
40	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
41}
42
43var (
44	errSyntax = errors.New("syntax error")
45	errNUL    = errors.New("unexpected NUL in input")
46)
47
48// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
49func (r *importReader) syntaxError() {
50	if r.err == nil {
51		r.err = errSyntax
52	}
53}
54
55// readByte reads the next byte from the input, saves it in buf, and returns it.
56// If an error occurs, readByte records the error in r.err and returns 0.
57func (r *importReader) readByte() byte {
58	c, err := r.b.ReadByte()
59	if err == nil {
60		r.buf = append(r.buf, c)
61		if c == 0 {
62			err = errNUL
63		}
64	}
65	if err != nil {
66		if err == io.EOF {
67			r.eof = true
68		} else if r.err == nil {
69			r.err = err
70		}
71		c = 0
72	}
73	return c
74}
75
76// peekByte returns the next byte from the input reader but does not advance beyond it.
77// If skipSpace is set, peekByte skips leading spaces and comments.
78func (r *importReader) peekByte(skipSpace bool) byte {
79	if r.err != nil {
80		if r.nerr++; r.nerr > 10000 {
81			panic("go/build: import reader looping")
82		}
83		return 0
84	}
85
86	// Use r.peek as first input byte.
87	// Don't just return r.peek here: it might have been left by peekByte(false)
88	// and this might be peekByte(true).
89	c := r.peek
90	if c == 0 {
91		c = r.readByte()
92	}
93	for r.err == nil && !r.eof {
94		if skipSpace {
95			// For the purposes of this reader, semicolons are never necessary to
96			// understand the input and are treated as spaces.
97			switch c {
98			case ' ', '\f', '\t', '\r', '\n', ';':
99				c = r.readByte()
100				continue
101
102			case '/':
103				c = r.readByte()
104				if c == '/' {
105					for c != '\n' && r.err == nil && !r.eof {
106						c = r.readByte()
107					}
108				} else if c == '*' {
109					var c1 byte
110					for (c != '*' || c1 != '/') && r.err == nil {
111						if r.eof {
112							r.syntaxError()
113						}
114						c, c1 = c1, r.readByte()
115					}
116				} else {
117					r.syntaxError()
118				}
119				c = r.readByte()
120				continue
121			}
122		}
123		break
124	}
125	r.peek = c
126	return r.peek
127}
128
129// nextByte is like peekByte but advances beyond the returned byte.
130func (r *importReader) nextByte(skipSpace bool) byte {
131	c := r.peekByte(skipSpace)
132	r.peek = 0
133	return c
134}
135
136// readKeyword reads the given keyword from the input.
137// If the keyword is not present, readKeyword records a syntax error.
138func (r *importReader) readKeyword(kw string) {
139	r.peekByte(true)
140	for i := 0; i < len(kw); i++ {
141		if r.nextByte(false) != kw[i] {
142			r.syntaxError()
143			return
144		}
145	}
146	if isIdent(r.peekByte(false)) {
147		r.syntaxError()
148	}
149}
150
151// readIdent reads an identifier from the input.
152// If an identifier is not present, readIdent records a syntax error.
153func (r *importReader) readIdent() {
154	c := r.peekByte(true)
155	if !isIdent(c) {
156		r.syntaxError()
157		return
158	}
159	for isIdent(r.peekByte(false)) {
160		r.peek = 0
161	}
162}
163
164// readString reads a quoted string literal from the input.
165// If an identifier is not present, readString records a syntax error.
166func (r *importReader) readString(save *[]string) {
167	switch r.nextByte(true) {
168	case '`':
169		start := len(r.buf) - 1
170		for r.err == nil {
171			if r.nextByte(false) == '`' {
172				if save != nil {
173					*save = append(*save, string(r.buf[start:]))
174				}
175				break
176			}
177			if r.eof {
178				r.syntaxError()
179			}
180		}
181	case '"':
182		start := len(r.buf) - 1
183		for r.err == nil {
184			c := r.nextByte(false)
185			if c == '"' {
186				if save != nil {
187					*save = append(*save, string(r.buf[start:]))
188				}
189				break
190			}
191			if r.eof || c == '\n' {
192				r.syntaxError()
193			}
194			if c == '\\' {
195				r.nextByte(false)
196			}
197		}
198	default:
199		r.syntaxError()
200	}
201}
202
203// readImport reads an import clause - optional identifier followed by quoted string -
204// from the input.
205func (r *importReader) readImport(imports *[]string) {
206	c := r.peekByte(true)
207	if c == '.' {
208		r.peek = 0
209	} else if isIdent(c) {
210		r.readIdent()
211	}
212	r.readString(imports)
213}
214
215// ReadComments is like io.ReadAll, except that it only reads the leading
216// block of comments in the file.
217func ReadComments(f io.Reader) ([]byte, error) {
218	r := newImportReader(bufio.NewReader(f))
219	r.peekByte(true)
220	if r.err == nil && !r.eof {
221		// Didn't reach EOF, so must have found a non-space byte. Remove it.
222		r.buf = r.buf[:len(r.buf)-1]
223	}
224	return r.buf, r.err
225}
226
227// ReadImports is like io.ReadAll, except that it expects a Go file as input
228// and stops reading the input once the imports have completed.
229func ReadImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) {
230	r := newImportReader(bufio.NewReader(f))
231
232	r.readKeyword("package")
233	r.readIdent()
234	for r.peekByte(true) == 'i' {
235		r.readKeyword("import")
236		if r.peekByte(true) == '(' {
237			r.nextByte(false)
238			for r.peekByte(true) != ')' && r.err == nil {
239				r.readImport(imports)
240			}
241			r.nextByte(false)
242		} else {
243			r.readImport(imports)
244		}
245	}
246
247	// If we stopped successfully before EOF, we read a byte that told us we were done.
248	// Return all but that last byte, which would cause a syntax error if we let it through.
249	if r.err == nil && !r.eof {
250		return r.buf[:len(r.buf)-1], nil
251	}
252
253	// If we stopped for a syntax error, consume the whole file so that
254	// we are sure we don't change the errors that go/parser returns.
255	if r.err == errSyntax && !reportSyntaxError {
256		r.err = nil
257		for r.err == nil && !r.eof {
258			r.readByte()
259		}
260	}
261
262	return r.buf, r.err
263}
264