cuelang.org/go@v0.13.0/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 "strings" 20 21 "cuelang.org/go/cue/errors" 22 "cuelang.org/go/cue/literal" 23 "cuelang.org/go/cue/scanner" 24 "cuelang.org/go/cue/token" 25 "github.com/cockroachdb/apd/v3" 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 var ni literal.NumInfo 114 if err := literal.ParseNum(a.Fields[pos].Text(), &ni); err != nil { 115 return 0, err 116 } 117 var d apd.Decimal 118 if err := ni.Decimal(&d); err != nil { 119 return 0, err 120 } 121 return d.Int64() 122 } 123 124 // Flag reports whether an entry with the given name exists at position pos or 125 // onwards or an error if the attribute is invalid or if the first pos-1 entries 126 // are not defined. 127 func (a *Attr) Flag(pos int, key string) (bool, error) { 128 if err := a.hasPos(pos - 1); err != nil { 129 return false, err 130 } 131 for _, kv := range a.Fields[pos:] { 132 if kv.Key() == "" && kv.Value() == key { 133 return true, nil 134 } 135 } 136 return false, nil 137 } 138 139 // Lookup searches for an entry of the form key=value from position pos onwards 140 // and reports the value if found. It reports an error if the attribute is 141 // invalid or if the first pos-1 entries are not defined. 142 func (a *Attr) Lookup(pos int, key string) (val string, found bool, err error) { 143 if err := a.hasPos(pos - 1); err != nil { 144 return "", false, err 145 } 146 for _, kv := range a.Fields[pos:] { 147 if kv.Key() == key { 148 return kv.Value(), true, nil 149 } 150 } 151 return "", false, nil 152 } 153 154 func ParseAttrBody(pos token.Pos, s string) (a Attr) { 155 // Create temporary token.File so that scanner has something 156 // to work with. 157 // TODO it's probably possible to do this without allocations. 158 tmpFile := token.NewFile("", -1, len(s)) 159 if len(s) > 0 { 160 tmpFile.AddLine(len(s) - 1) 161 } 162 a.Body = s 163 a.Pos = pos 164 var scan scanner.Scanner 165 scan.Init(tmpFile, []byte(s), nil, scanner.DontInsertCommas) 166 for { 167 start := scan.Offset() 168 tok, err := scanAttributeTokens(&scan, pos, 1<<token.COMMA|1<<token.BIND|1<<token.EOF) 169 if err != nil { 170 // Shouldn't happen because bracket nesting should have been checked previously by 171 // the regular CUE parser. 172 a.Err = err 173 return a 174 } 175 switch tok { 176 case token.EOF: 177 // Empty field. 178 a.appendField("", s[start:], s[start:]) 179 return a 180 case token.COMMA: 181 val := s[start : scan.Offset()-1] 182 a.appendField("", val, val) // All but final comma. 183 continue 184 } 185 valStart := scan.Offset() 186 key := s[start : valStart-1] // All but =. 187 tok, err = scanAttributeTokens(&scan, pos, 1<<token.COMMA|1<<token.EOF) 188 if err != nil { 189 // Shouldn't happen because bracket nesting should have been checked previously by 190 // the regular CUE parser. 191 a.Err = err 192 return a 193 } 194 valEnd := len(s) 195 if tok != token.EOF { 196 valEnd = scan.Offset() - 1 // All but final comma 197 } 198 value := s[valStart:valEnd] 199 text := s[start:valEnd] 200 a.appendField(key, value, text) 201 if tok == token.EOF { 202 return a 203 } 204 } 205 } 206 207 func (a *Attr) appendField(k, v, text string) { 208 a.Fields = append(a.Fields, KeyValue{ 209 key: strings.TrimSpace(k), 210 value: maybeUnquote(strings.TrimSpace(v)), 211 text: text, 212 }) 213 } 214 215 func maybeUnquote(s string) string { 216 if !possiblyQuoted(s) { 217 return s 218 } 219 s1, err := literal.Unquote(s) 220 if err != nil { 221 return s 222 } 223 return s1 224 } 225 226 func possiblyQuoted(s string) bool { 227 if len(s) < 2 { 228 return false 229 } 230 if s[0] == '#' && s[len(s)-1] == '#' { 231 return true 232 } 233 if s[0] == '"' && s[len(s)-1] == '"' { 234 return true 235 } 236 if s[0] == '\'' && s[len(s)-1] == '\'' { 237 return true 238 } 239 return false 240 } 241 242 // scanAttributeTokens reads tokens from s until it encounters 243 // a close token from the given bitmask. It returns the actual close token read. 244 func scanAttributeTokens(s *scanner.Scanner, startPos token.Pos, close uint64) (token.Token, errors.Error) { 245 for { 246 pos, tok, _ := s.Scan() 247 if s.ErrorCount > 0 { 248 // Shouldn't happen because the text should have been scanned previously by 249 // the regular CUE parser. 250 return 0, errors.Newf(startPos.Add(pos.Offset()), "error scanning attribute text") 251 } 252 if tok < 64 && (close&(1<<tok)) != 0 { 253 return tok, nil 254 } 255 var err error 256 switch tok { 257 case token.EOF: 258 err = fmt.Errorf("attribute missing '%s'", tokenMaskStr(close)) 259 case token.LPAREN: 260 _, err = scanAttributeTokens(s, startPos, 1<<token.RPAREN) 261 case token.LBRACE: 262 _, err = scanAttributeTokens(s, startPos, 1<<token.RBRACE) 263 case token.LBRACK: 264 _, err = scanAttributeTokens(s, startPos, 1<<token.RBRACK) 265 case token.RPAREN, token.RBRACK, token.RBRACE: 266 err = fmt.Errorf("unexpected '%s'", tok) 267 } 268 if err != nil { 269 return 0, errors.Newf(startPos.Add(pos.Offset()), "%v", err) 270 } 271 } 272 } 273 274 func tokenMaskStr(m uint64) string { 275 var buf strings.Builder 276 for t := token.Token(0); t < 64; t++ { 277 if (m & (1 << t)) != 0 { 278 if buf.Len() > 0 { 279 buf.WriteByte('|') 280 } 281 buf.WriteString(t.String()) 282 } 283 } 284 return buf.String() 285 }