cuelang.org/go@v0.10.1/cue/ast/ident.go (about) 1 // Copyright 2019 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 ast 16 17 import ( 18 "strconv" 19 "strings" 20 "unicode" 21 "unicode/utf8" 22 23 "cuelang.org/go/cue/errors" 24 "cuelang.org/go/cue/token" 25 ) 26 27 func isLetter(ch rune) bool { 28 return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) 29 } 30 31 func isDigit(ch rune) bool { 32 // TODO(mpvl): Is this correct? 33 return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch) 34 } 35 36 // IsValidIdent reports whether str is a valid identifier. 37 // Note that the underscore "_" string is considered valid, for top. 38 func IsValidIdent(ident string) bool { 39 if ident == "" { 40 return false 41 } 42 43 consumed := false 44 if strings.HasPrefix(ident, "_") { 45 ident = ident[1:] 46 consumed = true 47 if len(ident) == 0 { 48 return true 49 } 50 } 51 if strings.HasPrefix(ident, "#") { 52 ident = ident[1:] 53 // Note: _#0 is not allowed by the spec, although _0 is. 54 // TODO: set consumed to true here to allow #0. 55 consumed = false 56 } 57 58 if !consumed { 59 if r, _ := utf8.DecodeRuneInString(ident); isDigit(r) { 60 return false 61 } 62 } 63 64 for _, r := range ident { 65 if isLetter(r) || isDigit(r) || r == '_' || r == '$' { 66 continue 67 } 68 return false 69 } 70 return true 71 } 72 73 // LabelName reports the name of a label, whether it is an identifier 74 // (it binds a value to a scope), and whether it is valid. 75 // Keywords that are allowed in label positions are interpreted accordingly. 76 // 77 // Examples: 78 // 79 // Label Result 80 // foo "foo" true nil 81 // true "true" true nil 82 // "foo" "foo" false nil 83 // "x-y" "x-y" false nil 84 // "foo "" false invalid string 85 // "\(x)" "" false errors.Is(err, ErrIsExpression) 86 // X=foo "foo" true nil 87 func LabelName(l Label) (name string, isIdent bool, err error) { 88 if a, ok := l.(*Alias); ok { 89 l, _ = a.Expr.(Label) 90 } 91 switch n := l.(type) { 92 case *ListLit: 93 // An expression, but not one that can evaluated. 94 return "", false, errors.Newf(l.Pos(), 95 "cannot reference fields with square brackets labels outside the field value") 96 97 case *Ident: 98 name = n.Name 99 if !IsValidIdent(name) { 100 return "", false, errors.Newf(l.Pos(), "invalid identifier") 101 } 102 return name, true, err 103 104 case *BasicLit: 105 switch n.Kind { 106 case token.STRING: 107 // Use strconv to only allow double-quoted, single-line strings. 108 name, err = strconv.Unquote(n.Value) 109 if err != nil { 110 err = errors.Newf(l.Pos(), "invalid") 111 } 112 113 case token.NULL, token.TRUE, token.FALSE: 114 name = n.Value 115 isIdent = true 116 117 default: 118 // TODO: allow numbers to be fields 119 // This includes interpolation and template labels. 120 return "", false, errors.Wrapf(ErrIsExpression, l.Pos(), 121 "cannot use numbers as fields") 122 } 123 return name, isIdent, err 124 125 default: 126 // This includes interpolation and template labels. 127 return "", false, errors.Wrapf(ErrIsExpression, l.Pos(), 128 "label is an expression") 129 } 130 } 131 132 // ErrIsExpression reports whether a label is an expression. 133 // This error is never returned directly. Use errors.Is. 134 var ErrIsExpression = errors.New("not a concrete label")