1
2
3
4
5
6
7
8
9
10
11
12
13
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
33 return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
34 }
35
36
37
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
54
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
74
75
76
77 func ParseIdent(n *Ident) (string, error) {
78 return parseIdent(n.NamePos, n.Name)
79 }
80
81 func parseIdent(pos token.Pos, ident string) (string, error) {
82 if ident == "" {
83 return "", errors.Newf(pos, "empty identifier")
84 }
85 quoted := false
86 if ident[0] == '`' {
87 u, err := strconv.Unquote(ident)
88 if err != nil {
89 return "", errors.Newf(pos, "invalid quoted identifier")
90 }
91 ident = u
92 quoted = true
93 }
94
95 p := 0
96 if strings.HasPrefix(ident, "_") {
97 p++
98 if len(ident) == 1 {
99 return ident, nil
100 }
101 }
102 if strings.HasPrefix(ident[p:], "#") {
103 p++
104
105
106
107 }
108
109 if p == 0 || ident[p-1] == '#' {
110 if r, _ := utf8.DecodeRuneInString(ident[p:]); isDigit(r) {
111 return "", errors.Newf(pos, "invalid character '%s' in identifier", string(r))
112 }
113 }
114
115 for _, r := range ident[p:] {
116 if isLetter(r) || isDigit(r) || r == '_' || r == '$' {
117 continue
118 }
119 if r == '-' && quoted {
120 continue
121 }
122 return "", errors.Newf(pos, "invalid character '%s' in identifier", string(r))
123 }
124
125 return ident, nil
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 func LabelName(l Label) (name string, isIdent bool, err error) {
143 if a, ok := l.(*Alias); ok {
144 l, _ = a.Expr.(Label)
145 }
146 switch n := l.(type) {
147 case *ListLit:
148
149 return "", false, errors.Newf(l.Pos(),
150 "cannot reference fields with square brackets labels outside the field value")
151
152 case *Ident:
153
154 name, err = ParseIdent(n)
155 if err != nil {
156 return "", false, err
157 }
158 isIdent = true
159
160 return name, isIdent, err
161
162 case *BasicLit:
163 switch n.Kind {
164 case token.STRING:
165
166 name, err = strconv.Unquote(n.Value)
167 if err != nil {
168 err = errors.Newf(l.Pos(), "invalid")
169 }
170
171 case token.NULL, token.TRUE, token.FALSE:
172 name = n.Value
173 isIdent = true
174
175 default:
176
177
178 return "", false, errors.Wrapf(ErrIsExpression, l.Pos(),
179 "cannot use numbers as fields")
180 }
181
182 default:
183
184 return "", false, errors.Wrapf(ErrIsExpression, l.Pos(),
185 "label is an expression")
186 }
187 if !IsValidIdent(name) {
188 isIdent = false
189 }
190 return name, isIdent, err
191
192 }
193
194
195
196 var ErrIsExpression = errors.New("not a concrete label")
197
View as plain text