github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/internal/core/adt/feature.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 adt 16 17 import ( 18 "fmt" 19 "strconv" 20 "strings" 21 22 "github.com/joomcode/cue/cue/ast" 23 "github.com/joomcode/cue/cue/errors" 24 "github.com/joomcode/cue/cue/literal" 25 "github.com/joomcode/cue/cue/token" 26 "github.com/joomcode/cue/internal" 27 ) 28 29 // A Feature is an encoded form of a label which comprises a compact 30 // representation of an integer or string label as well as a label type. 31 type Feature uint32 32 33 // TODO: create labels such that list are sorted first (or last with index.) 34 35 // InvalidLabel is an encoding of an erroneous label. 36 const ( 37 InvalidLabel Feature = 0 38 39 // MaxIndex indicates the maximum number of unique strings that are used for 40 // labeles within this CUE implementation. 41 MaxIndex = 1<<(32-indexShift) - 1 42 ) 43 44 // These labels can be used for wildcard queries. 45 var ( 46 AnyDefinition Feature = makeLabel(MaxIndex, DefinitionLabel) 47 AnyHidden Feature = makeLabel(MaxIndex, HiddenLabel) 48 AnyString Feature = makeLabel(MaxIndex, StringLabel) 49 AnyIndex Feature = makeLabel(MaxIndex, IntLabel) 50 ) 51 52 // A StringIndexer coverts strings to and from an index that is unique for a 53 // given string. 54 type StringIndexer interface { 55 // ToIndex returns a unique positive index for s (0 < index < 2^28-1). 56 // 57 // For each pair of strings s and t it must return the same index if and 58 // only if s == t. 59 StringToIndex(s string) (index int64) 60 61 // ToString returns a string s for index such that ToIndex(s) == index. 62 IndexToString(index int64) string 63 } 64 65 // SelectorString reports the shortest string representation of f when used as a 66 // selector. 67 func (f Feature) SelectorString(index StringIndexer) string { 68 x := f.safeIndex() 69 switch f.Typ() { 70 case IntLabel: 71 return strconv.Itoa(int(x)) 72 case StringLabel: 73 s := index.IndexToString(x) 74 if ast.IsValidIdent(s) && !internal.IsDefOrHidden(s) { 75 return s 76 } 77 return literal.String.Quote(s) 78 default: 79 return f.IdentString(index) 80 } 81 } 82 83 // IdentString reports the identifier of f. The result is undefined if f 84 // is not an identifier label. 85 func (f Feature) IdentString(index StringIndexer) string { 86 s := index.IndexToString(f.safeIndex()) 87 if f.IsHidden() { 88 if p := strings.IndexByte(s, '\x00'); p >= 0 { 89 s = s[:p] 90 } 91 } 92 return s 93 } 94 95 // PkgID returns the package identifier, composed of the module and package 96 // name, associated with this identifier. It will return "" if this is not 97 // a hidden label. 98 func (f Feature) PkgID(index StringIndexer) string { 99 if !f.IsHidden() { 100 return "" 101 } 102 s := index.IndexToString(f.safeIndex()) 103 if p := strings.IndexByte(s, '\x00'); p >= 0 { 104 s = s[p+1:] 105 } 106 return s 107 } 108 109 // StringValue reports the string value of f, which must be a string label. 110 func (f Feature) StringValue(index StringIndexer) string { 111 if !f.IsString() { 112 panic("not a string label") 113 } 114 x := f.safeIndex() 115 return index.IndexToString(x) 116 } 117 118 // ToValue converts a label to a value, which will be a Num for integer labels 119 // and a String for string labels. It panics when f is not a regular label. 120 func (f Feature) ToValue(ctx *OpContext) Value { 121 if !f.IsRegular() { 122 panic("not a regular label") 123 } 124 // TODO: Handle special regular values: invalid and AnyRegular. 125 if f.IsInt() { 126 return ctx.NewInt64(int64(f.Index())) 127 } 128 x := f.safeIndex() 129 str := ctx.IndexToString(x) 130 return ctx.NewString(str) 131 } 132 133 // StringLabel converts s to a string label. 134 func (c *OpContext) StringLabel(s string) Feature { 135 return labelFromValue(c, nil, &String{Str: s}) 136 } 137 138 // MakeStringLabel creates a label for the given string. 139 func MakeStringLabel(r StringIndexer, s string) Feature { 140 i := r.StringToIndex(s) 141 142 // TODO: set position if it exists. 143 f, err := MakeLabel(nil, i, StringLabel) 144 if err != nil { 145 panic("out of free string slots") 146 } 147 return f 148 } 149 150 // MakeIdentLabel creates a label for the given identifier. 151 func MakeIdentLabel(r StringIndexer, s, pkgpath string) Feature { 152 t := StringLabel 153 switch { 154 case strings.HasPrefix(s, "_#"): 155 t = HiddenDefinitionLabel 156 s = fmt.Sprintf("%s\x00%s", s, pkgpath) 157 case strings.HasPrefix(s, "#"): 158 t = DefinitionLabel 159 case strings.HasPrefix(s, "_"): 160 s = fmt.Sprintf("%s\x00%s", s, pkgpath) 161 t = HiddenLabel 162 } 163 i := r.StringToIndex(s) 164 f, err := MakeLabel(nil, i, t) 165 if err != nil { 166 panic("out of free string slots") 167 } 168 return f 169 } 170 171 const msgGround = "invalid non-ground value %s (must be concrete %s)" 172 173 func labelFromValue(c *OpContext, src Expr, v Value) Feature { 174 var i int64 175 var t FeatureType 176 if isError(v) { 177 return InvalidLabel 178 } 179 switch v.Kind() { 180 case IntKind, NumKind: 181 x, _ := Unwrap(v).(*Num) 182 if x == nil { 183 c.addErrf(IncompleteError, pos(v), msgGround, v, "int") 184 return InvalidLabel 185 } 186 t = IntLabel 187 var err error 188 i, err = x.X.Int64() 189 if err != nil || x.K != IntKind { 190 if src == nil { 191 src = v 192 } 193 c.AddErrf("invalid index %v: %v", src, err) 194 return InvalidLabel 195 } 196 if i < 0 { 197 switch src.(type) { 198 case nil, *Num, *UnaryExpr: 199 // If the value is a constant, we know it is always an error. 200 // UnaryExpr is an approximation for a constant value here. 201 c.AddErrf("invalid index %s (index must be non-negative)", x) 202 default: 203 // Use a different message is it is the result of evaluation. 204 c.AddErrf("index %s out of range [%s]", src, x) 205 } 206 return InvalidLabel 207 } 208 209 case StringKind: 210 x, _ := Unwrap(v).(*String) 211 if x == nil { 212 c.addErrf(IncompleteError, pos(v), msgGround, v, "string") 213 return InvalidLabel 214 } 215 t = StringLabel 216 i = c.StringToIndex(x.Str) 217 218 default: 219 if src != nil { 220 c.AddErrf("invalid index %s (invalid type %v)", src, v.Kind()) 221 } else { 222 c.AddErrf("invalid index type %v", v.Kind()) 223 } 224 return InvalidLabel 225 } 226 227 // TODO: set position if it exists. 228 f, err := MakeLabel(nil, i, t) 229 if err != nil { 230 c.AddErr(err) 231 } 232 return f 233 } 234 235 // MakeLabel creates a label. It reports an error if the index is out of range. 236 func MakeLabel(src ast.Node, index int64, f FeatureType) (Feature, errors.Error) { 237 if 0 > index || index > MaxIndex-1 { 238 p := token.NoPos 239 if src != nil { 240 p = src.Pos() 241 } 242 return InvalidLabel, 243 errors.Newf(p, "int label out of range (%d not >=0 and <= %d)", 244 index, MaxIndex-1) 245 } 246 return Feature(index)<<indexShift | Feature(f), nil 247 } 248 249 func makeLabel(index int64, f FeatureType) Feature { 250 return Feature(index)<<indexShift | Feature(f) 251 } 252 253 // A FeatureType indicates the type of label. 254 type FeatureType int8 255 256 const ( 257 InvalidLabelType FeatureType = iota 258 StringLabel 259 IntLabel 260 DefinitionLabel 261 HiddenLabel 262 HiddenDefinitionLabel 263 ) 264 265 const ( 266 fTypeMask Feature = 0b1111 267 268 indexShift = 4 269 ) 270 271 func (f FeatureType) IsDef() bool { 272 return f == DefinitionLabel || f == HiddenDefinitionLabel 273 } 274 275 func (f FeatureType) IsHidden() bool { 276 return f == HiddenLabel || f == HiddenDefinitionLabel 277 } 278 279 // IsValid reports whether f is a valid label. 280 func (f Feature) IsValid() bool { return f != InvalidLabel } 281 282 // Typ reports the type of label. 283 func (f Feature) Typ() FeatureType { return FeatureType(f & fTypeMask) } 284 285 // IsRegular reports whether a label represents a data field. 286 func (f Feature) IsRegular() bool { 287 t := f.Typ() 288 return t == IntLabel || t == StringLabel 289 } 290 291 // IsString reports whether a label represents a regular field. 292 func (f Feature) IsString() bool { return f.Typ() == StringLabel } 293 294 // IsDef reports whether the label is a definition (an identifier starting with 295 // # or #_. 296 func (f Feature) IsDef() bool { 297 return f.Typ().IsDef() 298 } 299 300 // IsInt reports whether this is an integer index. 301 func (f Feature) IsInt() bool { return f.Typ() == IntLabel } 302 303 // IsHidden reports whether this label is hidden (an identifier starting with 304 // _ or #_). 305 func (f Feature) IsHidden() bool { 306 return f.Typ().IsHidden() 307 } 308 309 // Index reports the abstract index associated with f. 310 func (f Feature) Index() int { 311 return int(f >> indexShift) 312 } 313 314 // SafeIndex reports the abstract index associated with f, setting MaxIndex to 0. 315 func (f Feature) safeIndex() int64 { 316 x := int(f >> indexShift) 317 if x == MaxIndex { 318 x = 0 // Safety, MaxIndex means any 319 } 320 return int64(x) 321 } 322 323 // TODO: should let declarations be implemented as fields? 324 // func (f Feature) isLet() bool { return f.typ() == letLabel }