| // Copyright 2018 The CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package load |
| |
| import ( |
| "bufio" |
| "io" |
| "unicode/utf8" |
| |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/cue/token" |
| ) |
| |
| type importReader struct { |
| b *bufio.Reader |
| buf []byte |
| peek byte |
| err errors.Error |
| eof bool |
| nerr int |
| } |
| |
| func isIdent(c byte) bool { |
| return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf |
| } |
| |
| var ( |
| errSyntax = errors.Newf(token.NoPos, "syntax error") // TODO: remove |
| errNUL = errors.Newf(token.NoPos, "unexpected NUL in input") |
| ) |
| |
| // syntaxError records a syntax error, but only if an I/O error has not already been recorded. |
| func (r *importReader) syntaxError() { |
| if r.err == nil { |
| r.err = errSyntax |
| } |
| } |
| |
| // readByte reads the next byte from the input, saves it in buf, and returns it. |
| // If an error occurs, readByte records the error in r.err and returns 0. |
| func (r *importReader) readByte() byte { |
| c, err := r.b.ReadByte() |
| if err == nil { |
| r.buf = append(r.buf, c) |
| if c == 0 { |
| err = errNUL |
| } |
| } |
| if err != nil { |
| if err == io.EOF { |
| r.eof = true |
| } else if r.err == nil { |
| r.err = errors.Wrapf(err, token.NoPos, "readByte") |
| } |
| c = 0 |
| } |
| return c |
| } |
| |
| // peekByte returns the next byte from the input reader but does not advance beyond it. |
| // If skipSpace is set, peekByte skips leading spaces and comments. |
| func (r *importReader) peekByte(skipSpace bool) byte { |
| if r.err != nil { |
| if r.nerr++; r.nerr > 10000 { |
| panic("go/build: import reader looping") |
| } |
| return 0 |
| } |
| |
| // Use r.peek as first input byte. |
| // Don't just return r.peek here: it might have been left by peekByte(false) |
| // and this might be peekByte(true). |
| c := r.peek |
| if c == 0 { |
| c = r.readByte() |
| } |
| for r.err == nil && !r.eof { |
| if skipSpace { |
| // For the purposes of this reader, semicolons are never necessary to |
| // understand the input and are treated as spaces. |
| switch c { |
| case ' ', '\f', '\t', '\r', '\n', ';': |
| c = r.readByte() |
| continue |
| |
| case '/': |
| c = r.readByte() |
| if c == '/' { |
| for c != '\n' && r.err == nil && !r.eof { |
| c = r.readByte() |
| } |
| } else if c == '*' { |
| var c1 byte |
| for (c != '*' || c1 != '/') && r.err == nil { |
| if r.eof { |
| r.syntaxError() |
| } |
| c, c1 = c1, r.readByte() |
| } |
| } else { |
| r.syntaxError() |
| } |
| c = r.readByte() |
| continue |
| } |
| } |
| break |
| } |
| r.peek = c |
| return r.peek |
| } |
| |
| // nextByte is like peekByte but advances beyond the returned byte. |
| func (r *importReader) nextByte(skipSpace bool) byte { |
| c := r.peekByte(skipSpace) |
| r.peek = 0 |
| return c |
| } |
| |
| // readKeyword reads the given keyword from the input. |
| // If the keyword is not present, readKeyword records a syntax error. |
| func (r *importReader) readKeyword(kw string) { |
| r.peekByte(true) |
| for i := 0; i < len(kw); i++ { |
| if r.nextByte(false) != kw[i] { |
| r.syntaxError() |
| return |
| } |
| } |
| if isIdent(r.peekByte(false)) { |
| r.syntaxError() |
| } |
| } |
| |
| // readIdent reads an identifier from the input. |
| // If an identifier is not present, readIdent records a syntax error. |
| func (r *importReader) readIdent() { |
| c := r.peekByte(true) |
| if !isIdent(c) { |
| r.syntaxError() |
| return |
| } |
| for isIdent(r.peekByte(false)) { |
| r.peek = 0 |
| } |
| } |
| |
| // readString reads a quoted string literal from the input. |
| // If an identifier is not present, readString records a syntax error. |
| func (r *importReader) readString(save *[]string) { |
| switch r.nextByte(true) { |
| case '`': |
| start := len(r.buf) - 1 |
| for r.err == nil { |
| if r.nextByte(false) == '`' { |
| if save != nil { |
| *save = append(*save, string(r.buf[start:])) |
| } |
| break |
| } |
| if r.eof { |
| r.syntaxError() |
| } |
| } |
| case '"': |
| start := len(r.buf) - 1 |
| for r.err == nil { |
| c := r.nextByte(false) |
| if c == '"' { |
| if save != nil { |
| *save = append(*save, string(r.buf[start:])) |
| } |
| break |
| } |
| if r.eof || c == '\n' { |
| r.syntaxError() |
| } |
| if c == '\\' { |
| r.nextByte(false) |
| } |
| } |
| default: |
| r.syntaxError() |
| } |
| } |
| |
| // readImport reads an import clause - optional identifier followed by quoted string - |
| // from the input. |
| func (r *importReader) readImport(imports *[]string) { |
| c := r.peekByte(true) |
| if c == '.' { |
| r.peek = 0 |
| } else if isIdent(c) { |
| r.readIdent() |
| } |
| r.readString(imports) |
| } |
| |
| // readComments is like ioutil.ReadAll, except that it only reads the leading |
| // block of comments in the file. |
| func readComments(f io.Reader) ([]byte, errors.Error) { |
| r := &importReader{b: bufio.NewReader(f)} |
| r.peekByte(true) |
| if r.err == nil && !r.eof { |
| // Didn't reach EOF, so must have found a non-space byte. Remove it. |
| r.buf = r.buf[:len(r.buf)-1] |
| } |
| return r.buf, r.err |
| } |
| |
| // readImports is like ioutil.ReadAll, except that it expects a CUE file as |
| // input and stops reading the input once the imports have completed. |
| func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, errors.Error) { |
| r := &importReader{b: bufio.NewReader(f)} |
| |
| r.readKeyword("package") |
| r.readIdent() |
| for r.peekByte(true) == 'i' { |
| r.readKeyword("import") |
| if r.peekByte(true) == '(' { |
| r.nextByte(false) |
| for r.peekByte(true) != ')' && r.err == nil { |
| r.readImport(imports) |
| } |
| r.nextByte(false) |
| } else { |
| r.readImport(imports) |
| } |
| } |
| |
| // If we stopped successfully before EOF, we read a byte that told us we were done. |
| // Return all but that last byte, which would cause a syntax error if we let it through. |
| if r.err == nil && !r.eof { |
| return r.buf[:len(r.buf)-1], nil |
| } |
| |
| // If we stopped for a syntax error, consume the whole file so that |
| // we are sure we don't change the errors that go/parser returns. |
| if r.err == errSyntax && !reportSyntaxError { |
| r.err = nil |
| for r.err == nil && !r.eof { |
| r.readByte() |
| } |
| } |
| |
| return r.buf, r.err |
| } |