github.com/sc0rp1us/gb@v0.4.1-0.20160319180011-4ba8cf1baa5a/importer/read.go (about)

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