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