cuelang.org/go@v0.10.1/internal/attrs.go (about) 1 // Copyright 2020 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 internal 16 17 import ( 18 "fmt" 19 "strconv" 20 "strings" 21 22 "cuelang.org/go/cue/errors" 23 "cuelang.org/go/cue/literal" 24 "cuelang.org/go/cue/scanner" 25 "cuelang.org/go/cue/token" 26 ) 27 28 // AttrKind indicates the location of an attribute within CUE source. 29 type AttrKind uint8 30 31 const ( 32 // FieldAttr indicates an attribute is a field attribute. 33 // foo: bar @attr() 34 FieldAttr AttrKind = 1 << iota 35 36 // DeclAttr indicates an attribute was specified at a declaration position. 37 // foo: { 38 // @attr() 39 // } 40 DeclAttr 41 42 // TODO: Possible future attr kinds 43 // ElemAttr 44 // FileAttr 45 // ValueAttr = FieldAttr|DeclAttr|ElemAttr 46 ) 47 48 // Attr holds positional information for a single Attr. 49 type Attr struct { 50 Name string // e.g. "json" or "protobuf" 51 Body string 52 Kind AttrKind 53 Fields []KeyValue 54 Err errors.Error 55 Pos token.Pos 56 } 57 58 // NewNonExisting creates a non-existing attribute. 59 func NewNonExisting(key string) Attr { 60 const msgNotExist = "attribute %q does not exist" 61 return Attr{Err: errors.Newf(token.NoPos, msgNotExist, key)} 62 } 63 64 type KeyValue struct { 65 key string 66 value string 67 text string 68 } 69 70 func (kv *KeyValue) Text() string { 71 return kv.text 72 } 73 74 func (kv *KeyValue) Key() string { 75 return kv.key 76 } 77 78 func (kv *KeyValue) Value() string { 79 return kv.value 80 } 81 82 func (a *Attr) hasPos(p int) error { 83 if a.Err != nil { 84 return a.Err 85 } 86 if p >= len(a.Fields) { 87 return fmt.Errorf("field does not exist") 88 } 89 return nil 90 } 91 92 // String reports the possibly empty string value at the given position or 93 // an error the attribute is invalid or if the position does not exist. 94 func (a *Attr) String(pos int) (string, error) { 95 if err := a.hasPos(pos); err != nil { 96 return "", err 97 } 98 f := a.Fields[pos] 99 if f.key != "" { 100 // When there's a key, we return the entire value. 101 return f.Text(), nil 102 } 103 return a.Fields[pos].Value(), nil 104 } 105 106 // Int reports the integer at the given position or an error if the attribute is 107 // invalid, the position does not exist, or the value at the given position is 108 // not an integer. 109 func (a *Attr) Int(pos int) (int64, error) { 110 if err := a.hasPos(pos); err != nil { 111 return 0, err 112 } 113 // TODO: use CUE's literal parser once it exists, allowing any of CUE's 114 // number types. 115 return strconv.ParseInt(a.Fields[pos].Text(), 10, 64) 116 } 117 118 // Flag reports whether an entry with the given name exists at position pos or 119 // onwards or an error if the attribute is invalid or if the first pos-1 entries 120 // are not defined. 121 func (a *Attr) Flag(pos int, key string) (bool, error) { 122 if err := a.hasPos(pos - 1); err != nil { 123 return false, err 124 } 125 for _, kv := range a.Fields[pos:] { 126 if kv.Key() == "" && kv.Value() == key { 127 return true, nil 128 } 129 } 130 return false, nil 131 } 132 133 // Lookup searches for an entry of the form key=value from position pos onwards 134 // and reports the value if found. It reports an error if the attribute is 135 // invalid or if the first pos-1 entries are not defined. 136 func (a *Attr) Lookup(pos int, key string) (val string, found bool, err error) { 137 if err := a.hasPos(pos - 1); err != nil { 138 return "", false, err 139 } 140 for _, kv := range a.Fields[pos:] { 141 if kv.Key() == key { 142 return kv.Value(), true, nil 143 } 144 } 145 return "", false, nil 146 } 147 148 func ParseAttrBody(pos token.Pos, s string) (a Attr) { 149 // Create temporary token.File so that scanner has something 150 // to work with. 151 // TODO it's probably possible to do this without allocations. 152 tmpFile := token.NewFile("", -1, len(s)) 153 if len(s) > 0 { 154 tmpFile.AddLine(len(s) - 1) 155 } 156 a.Body = s 157 a.Pos = pos 158 var scan scanner.Scanner 159 scan.Init(tmpFile, []byte(s), nil, scanner.DontInsertCommas) 160 for { 161 start := scan.Offset() 162 tok, err := scanAttributeTokens(&scan, pos, 1<<token.COMMA|1<<token.BIND|1<<token.EOF) 163 if err != nil { 164 // Shouldn't happen because bracket nesting should have been checked previously by 165 // the regular CUE parser. 166 a.Err = err 167 return a 168 } 169 switch tok { 170 case token.EOF: 171 // Empty field. 172 a.appendField("", s[start:], s[start:]) 173 return a 174 case token.COMMA: 175 val := s[start : scan.Offset()-1] 176 a.appendField("", val, val) // All but final comma. 177 continue 178 } 179 valStart := scan.Offset() 180 key := s[start : valStart-1] // All but =. 181 tok, err = scanAttributeTokens(&scan, pos, 1<<token.COMMA|1<<token.EOF) 182 if err != nil { 183 // Shouldn't happen because bracket nesting should have been checked previously by 184 // the regular CUE parser. 185 a.Err = err 186 return a 187 } 188 valEnd := len(s) 189 if tok != token.EOF { 190 valEnd = scan.Offset() - 1 // All but final comma 191 } 192 value := s[valStart:valEnd] 193 text := s[start:valEnd] 194 a.appendField(key, value, text) 195 if tok == token.EOF { 196 return a 197 } 198 } 199 } 200 201 func (a *Attr) appendField(k, v, text string) { 202 a.Fields = append(a.Fields, KeyValue{ 203 key: strings.TrimSpace(k), 204 value: maybeUnquote(strings.TrimSpace(v)), 205 text: text, 206 }) 207 } 208 209 func maybeUnquote(s string) string { 210 if !possiblyQuoted(s) { 211 return s 212 } 213 s1, err := literal.Unquote(s) 214 if err != nil { 215 return s 216 } 217 return s1 218 } 219 220 func possiblyQuoted(s string) bool { 221 if len(s) < 2 { 222 return false 223 } 224 if s[0] == '#' && s[len(s)-1] == '#' { 225 return true 226 } 227 if s[0] == '"' && s[len(s)-1] == '"' { 228 return true 229 } 230 if s[0] == '\'' && s[len(s)-1] == '\'' { 231 return true 232 } 233 return false 234 } 235 236 // scanAttributeTokens reads tokens from s until it encounters 237 // a close token from the given bitmask. It returns the actual close token read. 238 func scanAttributeTokens(s *scanner.Scanner, startPos token.Pos, close uint64) (token.Token, errors.Error) { 239 for { 240 pos, tok, _ := s.Scan() 241 if s.ErrorCount > 0 { 242 // Shouldn't happen because the text should have been scanned previously by 243 // the regular CUE parser. 244 return 0, errors.Newf(startPos.Add(pos.Offset()), "error scanning attribute text") 245 } 246 if tok < 64 && (close&(1<<tok)) != 0 { 247 return tok, nil 248 } 249 var err error 250 switch tok { 251 case token.EOF: 252 err = fmt.Errorf("attribute missing '%s'", tokenMaskStr(close)) 253 case token.LPAREN: 254 _, err = scanAttributeTokens(s, startPos, 1<<token.RPAREN) 255 case token.LBRACE: 256 _, err = scanAttributeTokens(s, startPos, 1<<token.RBRACE) 257 case token.LBRACK: 258 _, err = scanAttributeTokens(s, startPos, 1<<token.RBRACK) 259 case token.RPAREN, token.RBRACK, token.RBRACE: 260 err = fmt.Errorf("unexpected '%s'", tok) 261 } 262 if err != nil { 263 return 0, errors.Newf(startPos.Add(pos.Offset()), "%v", err) 264 } 265 } 266 } 267 268 func tokenMaskStr(m uint64) string { 269 var buf strings.Builder 270 for t := token.Token(0); t < 64; t++ { 271 if (m & (1 << t)) != 0 { 272 if buf.Len() > 0 { 273 buf.WriteByte('|') 274 } 275 buf.WriteString(t.String()) 276 } 277 } 278 return buf.String() 279 }