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