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 }