cuelang.org/go@v0.10.1/internal/cueimports/read.go (about)

     1  // Copyright 2023 The CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package cueimports provides support for reading the import
    16  // section of a CUE file without needing to read the rest of it.
    17  package cueimports
    18  
    19  import (
    20  	"bufio"
    21  	"io"
    22  	"unicode/utf8"
    23  
    24  	"cuelang.org/go/cue/errors"
    25  	"cuelang.org/go/cue/token"
    26  )
    27  
    28  type importReader struct {
    29  	b    *bufio.Reader
    30  	buf  []byte
    31  	peek byte
    32  	err  errors.Error
    33  	eof  bool
    34  	nerr int
    35  }
    36  
    37  func isIdent(c byte) bool {
    38  	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
    39  }
    40  
    41  var (
    42  	errSyntax = errors.Newf(token.NoPos, "syntax error") // TODO: remove
    43  	errNUL    = errors.Newf(token.NoPos, "unexpected NUL in input")
    44  )
    45  
    46  // syntaxError records a syntax error, but only if an I/O error has not already been recorded.
    47  func (r *importReader) syntaxError() {
    48  	if r.err == nil {
    49  		r.err = errSyntax
    50  	}
    51  }
    52  
    53  // readByte reads the next byte from the input, saves it in buf, and returns it.
    54  // If an error occurs, readByte records the error in r.err and returns 0.
    55  func (r *importReader) readByte() byte {
    56  	c, err := r.b.ReadByte()
    57  	if err == nil {
    58  		r.buf = append(r.buf, c)
    59  		if c == 0 {
    60  			err = errNUL
    61  		}
    62  	}
    63  	if err != nil {
    64  		if err == io.EOF {
    65  			r.eof = true
    66  		} else if r.err == nil {
    67  			r.err = errors.Wrapf(err, token.NoPos, "readByte")
    68  		}
    69  		c = 0
    70  	}
    71  	return c
    72  }
    73  
    74  // peekByte returns the next byte from the input reader but does not advance beyond it.
    75  // If skipSpace is set, peekByte skips leading spaces and comments.
    76  func (r *importReader) peekByte(skipSpace bool) byte {
    77  	if r.err != nil {
    78  		if r.nerr++; r.nerr > 10000 {
    79  			panic("import reader looping")
    80  		}
    81  		return 0
    82  	}
    83  
    84  	// Use r.peek as first input byte.
    85  	// Don't just return r.peek here: it might have been left by peekByte(false)
    86  	// and this might be peekByte(true).
    87  	c := r.peek
    88  	if c == 0 {
    89  		c = r.readByte()
    90  	}
    91  	for r.err == nil && !r.eof {
    92  		if skipSpace {
    93  			// For the purposes of this reader, commas are never necessary to
    94  			// understand the input and are treated as spaces.
    95  			switch c {
    96  			case ' ', '\f', '\t', '\r', '\n', ',':
    97  				c = r.readByte()
    98  				continue
    99  
   100  			case '/':
   101  				c = r.readByte()
   102  				if c == '/' {
   103  					for c != '\n' && r.err == nil && !r.eof {
   104  						c = r.readByte()
   105  					}
   106  				} else {
   107  					r.syntaxError()
   108  				}
   109  				c = r.readByte()
   110  				continue
   111  			}
   112  		}
   113  		break
   114  	}
   115  	r.peek = c
   116  	return r.peek
   117  }
   118  
   119  // nextByte is like peekByte but advances beyond the returned byte.
   120  func (r *importReader) nextByte(skipSpace bool) byte {
   121  	c := r.peekByte(skipSpace)
   122  	r.peek = 0
   123  	return c
   124  }
   125  
   126  // readKeyword reads the given keyword from the input.
   127  // If the keyword is not present, readKeyword records a syntax error.
   128  func (r *importReader) readKeyword(kw string) {
   129  	r.peekByte(true)
   130  	for i := 0; i < len(kw); i++ {
   131  		if r.nextByte(false) != kw[i] {
   132  			r.syntaxError()
   133  			return
   134  		}
   135  	}
   136  	if isIdent(r.peekByte(false)) {
   137  		r.syntaxError()
   138  	}
   139  }
   140  
   141  // readIdent reads an identifier from the input.
   142  // If an identifier is not present, readIdent records a syntax error.
   143  func (r *importReader) readIdent() {
   144  	c := r.peekByte(true)
   145  	if !isIdent(c) {
   146  		r.syntaxError()
   147  		return
   148  	}
   149  	for isIdent(r.peekByte(false)) {
   150  		r.peek = 0
   151  	}
   152  }
   153  
   154  // readString reads a quoted string literal from the input.
   155  // If an identifier is not present, readString records a syntax error.
   156  func (r *importReader) readString() {
   157  	switch r.nextByte(true) {
   158  	case '"':
   159  		// Note: although the syntax in the specification only allows
   160  		// a simple string literal here, the cue/parser package also
   161  		// allows #"..."# and """ literals, so there's some impedance-mismatch here.
   162  		for r.err == nil {
   163  			c := r.nextByte(false)
   164  			if c == '"' {
   165  				break
   166  			}
   167  			if r.eof || c == '\n' {
   168  				r.syntaxError()
   169  			}
   170  			if c == '\\' {
   171  				r.nextByte(false)
   172  			}
   173  		}
   174  	default:
   175  		r.syntaxError()
   176  	}
   177  }
   178  
   179  // readImport reads an import clause - optional identifier followed by quoted string -
   180  // from the input.
   181  func (r *importReader) readImport() {
   182  	c := r.peekByte(true)
   183  	if isIdent(c) {
   184  		r.readIdent()
   185  	}
   186  	r.readString()
   187  }
   188  
   189  // Read is like io.ReadAll, except that it expects a CUE file as
   190  // input and stops reading the input once the imports have completed.
   191  func Read(f io.Reader) ([]byte, errors.Error) {
   192  	r := &importReader{b: bufio.NewReader(f)}
   193  
   194  	r.readKeyword("package")
   195  	r.readIdent()
   196  	for r.peekByte(true) == 'i' {
   197  		r.readKeyword("import")
   198  		if r.peekByte(true) == '(' {
   199  			r.nextByte(false)
   200  			for r.peekByte(true) != ')' && r.err == nil {
   201  				r.readImport()
   202  			}
   203  			r.nextByte(false)
   204  		} else {
   205  			r.readImport()
   206  		}
   207  	}
   208  
   209  	// If we stopped successfully before EOF, we read a byte that told us we were done.
   210  	// Return all but that last byte, which would cause a syntax error if we let it through.
   211  	if r.err == nil && !r.eof {
   212  		return r.buf[:len(r.buf)-1], nil
   213  	}
   214  
   215  	// If we stopped for a syntax error, consume the whole file so that
   216  	// we are sure we don't change the errors that the cue/parser returns.
   217  	if r.err == errSyntax {
   218  		r.err = nil
   219  		for r.err == nil && !r.eof {
   220  			r.readByte()
   221  		}
   222  	}
   223  
   224  	return r.buf, r.err
   225  }