Source file
src/go/build/read.go
1
2
3
4
5 package build
6
7 import (
8 "bufio"
9 "bytes"
10 "errors"
11 "fmt"
12 "go/ast"
13 "go/parser"
14 "go/token"
15 "io"
16 "strconv"
17 "strings"
18 "unicode"
19 "unicode/utf8"
20 )
21
22 type importReader struct {
23 b *bufio.Reader
24 buf []byte
25 peek byte
26 err error
27 eof bool
28 nerr int
29 pos token.Position
30 }
31
32 var bom = []byte{0xef, 0xbb, 0xbf}
33
34 func newImportReader(name string, r io.Reader) *importReader {
35 b := bufio.NewReader(r)
36
37
38
39
40 if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) {
41 b.Discard(3)
42 }
43 return &importReader{
44 b: b,
45 pos: token.Position{
46 Filename: name,
47 Line: 1,
48 Column: 1,
49 },
50 }
51 }
52
53 func isIdent(c byte) bool {
54 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
55 }
56
57 var (
58 errSyntax = errors.New("syntax error")
59 errNUL = errors.New("unexpected NUL in input")
60 )
61
62
63 func (r *importReader) syntaxError() {
64 if r.err == nil {
65 r.err = errSyntax
66 }
67 }
68
69
70
71 func (r *importReader) readByte() byte {
72 c, err := r.b.ReadByte()
73 if err == nil {
74 r.buf = append(r.buf, c)
75 if c == 0 {
76 err = errNUL
77 }
78 }
79 if err != nil {
80 if err == io.EOF {
81 r.eof = true
82 } else if r.err == nil {
83 r.err = err
84 }
85 c = 0
86 }
87 return c
88 }
89
90
91
92 func (r *importReader) readByteNoBuf() byte {
93 var c byte
94 var err error
95 if len(r.buf) > 0 {
96 c = r.buf[0]
97 r.buf = r.buf[1:]
98 } else {
99 c, err = r.b.ReadByte()
100 if err == nil && c == 0 {
101 err = errNUL
102 }
103 }
104
105 if err != nil {
106 if err == io.EOF {
107 r.eof = true
108 } else if r.err == nil {
109 r.err = err
110 }
111 return 0
112 }
113 r.pos.Offset++
114 if c == '\n' {
115 r.pos.Line++
116 r.pos.Column = 1
117 } else {
118 r.pos.Column++
119 }
120 return c
121 }
122
123
124
125 func (r *importReader) peekByte(skipSpace bool) byte {
126 if r.err != nil {
127 if r.nerr++; r.nerr > 10000 {
128 panic("go/build: import reader looping")
129 }
130 return 0
131 }
132
133
134
135
136 c := r.peek
137 if c == 0 {
138 c = r.readByte()
139 }
140 for r.err == nil && !r.eof {
141 if skipSpace {
142
143
144 switch c {
145 case ' ', '\f', '\t', '\r', '\n', ';':
146 c = r.readByte()
147 continue
148
149 case '/':
150 c = r.readByte()
151 if c == '/' {
152 for c != '\n' && r.err == nil && !r.eof {
153 c = r.readByte()
154 }
155 } else if c == '*' {
156 var c1 byte
157 for (c != '*' || c1 != '/') && r.err == nil {
158 if r.eof {
159 r.syntaxError()
160 }
161 c, c1 = c1, r.readByte()
162 }
163 } else {
164 r.syntaxError()
165 }
166 c = r.readByte()
167 continue
168 }
169 }
170 break
171 }
172 r.peek = c
173 return r.peek
174 }
175
176
177 func (r *importReader) nextByte(skipSpace bool) byte {
178 c := r.peekByte(skipSpace)
179 r.peek = 0
180 return c
181 }
182
183 var goEmbed = []byte("go:embed")
184
185
186
187
188 func (r *importReader) findEmbed(first bool) bool {
189
190
191
192
193 startLine := !first
194 var c byte
195 for r.err == nil && !r.eof {
196 c = r.readByteNoBuf()
197 Reswitch:
198 switch c {
199 default:
200 startLine = false
201
202 case '\n':
203 startLine = true
204
205 case ' ', '\t':
206
207
208 case '"':
209 startLine = false
210 for r.err == nil {
211 if r.eof {
212 r.syntaxError()
213 }
214 c = r.readByteNoBuf()
215 if c == '\\' {
216 r.readByteNoBuf()
217 if r.err != nil {
218 r.syntaxError()
219 return false
220 }
221 continue
222 }
223 if c == '"' {
224 c = r.readByteNoBuf()
225 goto Reswitch
226 }
227 }
228 goto Reswitch
229
230 case '`':
231 startLine = false
232 for r.err == nil {
233 if r.eof {
234 r.syntaxError()
235 }
236 c = r.readByteNoBuf()
237 if c == '`' {
238 c = r.readByteNoBuf()
239 goto Reswitch
240 }
241 }
242
243 case '/':
244 c = r.readByteNoBuf()
245 switch c {
246 default:
247 startLine = false
248 goto Reswitch
249
250 case '*':
251 var c1 byte
252 for (c != '*' || c1 != '/') && r.err == nil {
253 if r.eof {
254 r.syntaxError()
255 }
256 c, c1 = c1, r.readByteNoBuf()
257 }
258 startLine = false
259
260 case '/':
261 if startLine {
262
263 for i := range goEmbed {
264 c = r.readByteNoBuf()
265 if c != goEmbed[i] {
266 goto SkipSlashSlash
267 }
268 }
269 c = r.readByteNoBuf()
270 if c == ' ' || c == '\t' {
271
272 return true
273 }
274 }
275 SkipSlashSlash:
276 for c != '\n' && r.err == nil && !r.eof {
277 c = r.readByteNoBuf()
278 }
279 startLine = true
280 }
281 }
282 }
283 return false
284 }
285
286
287
288 func (r *importReader) readKeyword(kw string) {
289 r.peekByte(true)
290 for i := 0; i < len(kw); i++ {
291 if r.nextByte(false) != kw[i] {
292 r.syntaxError()
293 return
294 }
295 }
296 if isIdent(r.peekByte(false)) {
297 r.syntaxError()
298 }
299 }
300
301
302
303 func (r *importReader) readIdent() {
304 c := r.peekByte(true)
305 if !isIdent(c) {
306 r.syntaxError()
307 return
308 }
309 for isIdent(r.peekByte(false)) {
310 r.peek = 0
311 }
312 }
313
314
315
316 func (r *importReader) readString() {
317 switch r.nextByte(true) {
318 case '`':
319 for r.err == nil {
320 if r.nextByte(false) == '`' {
321 break
322 }
323 if r.eof {
324 r.syntaxError()
325 }
326 }
327 case '"':
328 for r.err == nil {
329 c := r.nextByte(false)
330 if c == '"' {
331 break
332 }
333 if r.eof || c == '\n' {
334 r.syntaxError()
335 }
336 if c == '\\' {
337 r.nextByte(false)
338 }
339 }
340 default:
341 r.syntaxError()
342 }
343 }
344
345
346
347 func (r *importReader) readImport() {
348 c := r.peekByte(true)
349 if c == '.' {
350 r.peek = 0
351 } else if isIdent(c) {
352 r.readIdent()
353 }
354 r.readString()
355 }
356
357
358
359 func readComments(f io.Reader) ([]byte, error) {
360 r := newImportReader("", f)
361 r.peekByte(true)
362 if r.err == nil && !r.eof {
363
364 r.buf = r.buf[:len(r.buf)-1]
365 }
366 return r.buf, r.err
367 }
368
369
370
371
372
373
374
375
376 func readGoInfo(f io.Reader, info *fileInfo) error {
377 r := newImportReader(info.name, f)
378
379 r.readKeyword("package")
380 r.readIdent()
381 for r.peekByte(true) == 'i' {
382 r.readKeyword("import")
383 if r.peekByte(true) == '(' {
384 r.nextByte(false)
385 for r.peekByte(true) != ')' && r.err == nil {
386 r.readImport()
387 }
388 r.nextByte(false)
389 } else {
390 r.readImport()
391 }
392 }
393
394 info.header = r.buf
395
396
397
398 if r.err == nil && !r.eof {
399 info.header = r.buf[:len(r.buf)-1]
400 }
401
402
403
404 if r.err == errSyntax {
405 r.err = nil
406 for r.err == nil && !r.eof {
407 r.readByte()
408 }
409 info.header = r.buf
410 }
411 if r.err != nil {
412 return r.err
413 }
414
415 if info.fset == nil {
416 return nil
417 }
418
419
420 info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments)
421 if info.parseErr != nil {
422 return nil
423 }
424
425 hasEmbed := false
426 for _, decl := range info.parsed.Decls {
427 d, ok := decl.(*ast.GenDecl)
428 if !ok {
429 continue
430 }
431 for _, dspec := range d.Specs {
432 spec, ok := dspec.(*ast.ImportSpec)
433 if !ok {
434 continue
435 }
436 quoted := spec.Path.Value
437 path, err := strconv.Unquote(quoted)
438 if err != nil {
439 return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted)
440 }
441 if path == "embed" {
442 hasEmbed = true
443 }
444
445 doc := spec.Doc
446 if doc == nil && len(d.Specs) == 1 {
447 doc = d.Doc
448 }
449 info.imports = append(info.imports, fileImport{path, spec.Pos(), doc})
450 }
451 }
452
453
454
455
456
457
458
459
460
461 if hasEmbed {
462 var line []byte
463 for first := true; r.findEmbed(first); first = false {
464 line = line[:0]
465 pos := r.pos
466 for {
467 c := r.readByteNoBuf()
468 if c == '\n' || r.err != nil || r.eof {
469 break
470 }
471 line = append(line, c)
472 }
473
474
475
476 embs, err := parseGoEmbed(string(line), pos)
477 if err == nil {
478 info.embeds = append(info.embeds, embs...)
479 }
480 }
481 }
482
483 return nil
484 }
485
486
487
488
489
490 func parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) {
491 trimBytes := func(n int) {
492 pos.Offset += n
493 pos.Column += utf8.RuneCountInString(args[:n])
494 args = args[n:]
495 }
496 trimSpace := func() {
497 trim := strings.TrimLeftFunc(args, unicode.IsSpace)
498 trimBytes(len(args) - len(trim))
499 }
500
501 var list []fileEmbed
502 for trimSpace(); args != ""; trimSpace() {
503 var path string
504 pathPos := pos
505 Switch:
506 switch args[0] {
507 default:
508 i := len(args)
509 for j, c := range args {
510 if unicode.IsSpace(c) {
511 i = j
512 break
513 }
514 }
515 path = args[:i]
516 trimBytes(i)
517
518 case '`':
519 i := strings.Index(args[1:], "`")
520 if i < 0 {
521 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
522 }
523 path = args[1 : 1+i]
524 trimBytes(1 + i + 1)
525
526 case '"':
527 i := 1
528 for ; i < len(args); i++ {
529 if args[i] == '\\' {
530 i++
531 continue
532 }
533 if args[i] == '"' {
534 q, err := strconv.Unquote(args[:i+1])
535 if err != nil {
536 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
537 }
538 path = q
539 trimBytes(i + 1)
540 break Switch
541 }
542 }
543 if i >= len(args) {
544 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
545 }
546 }
547
548 if args != "" {
549 r, _ := utf8.DecodeRuneInString(args)
550 if !unicode.IsSpace(r) {
551 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
552 }
553 }
554 list = append(list, fileEmbed{path, pathPos})
555 }
556 return list, nil
557 }
558
View as plain text