cuelang.org/go@v0.13.0/pkg/list/list.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 list contains functions for manipulating and examining lists. 16 package list 17 18 import ( 19 "fmt" 20 "slices" 21 22 "cuelang.org/go/cue" 23 "cuelang.org/go/cue/errors" 24 "cuelang.org/go/cue/token" 25 "cuelang.org/go/internal/core/adt" 26 "cuelang.org/go/internal/core/eval" 27 "cuelang.org/go/internal/pkg" 28 "cuelang.org/go/internal/types" 29 "cuelang.org/go/internal/value" 30 ) 31 32 // Drop reports the suffix of list x after the first n elements, 33 // or [] if n > len(x). 34 // 35 // For instance: 36 // 37 // Drop([1, 2, 3, 4], 2) 38 // 39 // results in 40 // 41 // [3, 4] 42 func Drop(x []cue.Value, n int) ([]cue.Value, error) { 43 if n < 0 { 44 return nil, fmt.Errorf("negative index") 45 } 46 47 if n > len(x) { 48 return []cue.Value{}, nil 49 } 50 51 return x[n:], nil 52 } 53 54 // TODO: disable Flatten until we know the right default for depth. 55 // The right time to determine is at least some point after the query 56 // extensions are introduced, which may provide flatten functionality 57 // natively. 58 // 59 // // Flatten reports a flattened sequence of the list xs by expanding any elements 60 // // that are lists. 61 // // 62 // // For instance: 63 // // 64 // // Flatten([1, [[2, 3], []], [4]]) 65 // // 66 // // results in 67 // // 68 // // [1, 2, 3, 4] 69 // // 70 // func Flatten(xs cue.Value) ([]cue.Value, error) { 71 // var flatten func(cue.Value) ([]cue.Value, error) 72 // flatten = func(xs cue.Value) ([]cue.Value, error) { 73 // var res []cue.Value 74 // iter, err := xs.List() 75 // if err != nil { 76 // return nil, err 77 // } 78 // for iter.Next() { 79 // val := iter.Value() 80 // if val.Kind() == cue.ListKind { 81 // vals, err := flatten(val) 82 // if err != nil { 83 // return nil, err 84 // } 85 // res = append(res, vals...) 86 // } else { 87 // res = append(res, val) 88 // } 89 // } 90 // return res, nil 91 // } 92 // return flatten(xs) 93 // } 94 95 // FlattenN reports a flattened sequence of the list xs by expanding any elements 96 // depth levels deep. If depth is negative all elements are expanded. 97 // 98 // For instance: 99 // 100 // FlattenN([1, [[2, 3], []], [4]], 1) 101 // 102 // results in 103 // 104 // [1, [2, 3], [], 4] 105 func FlattenN(xs cue.Value, depth int) ([]cue.Value, error) { 106 var flattenN func(cue.Value, int) ([]cue.Value, error) 107 flattenN = func(xs cue.Value, depth int) ([]cue.Value, error) { 108 var res []cue.Value 109 iter, err := xs.List() 110 if err != nil { 111 return nil, err 112 } 113 for iter.Next() { 114 val, _ := iter.Value().Default() 115 if val.Kind() == cue.ListKind && depth != 0 { 116 d := depth - 1 117 values, err := flattenN(val, d) 118 if err != nil { 119 return nil, err 120 } 121 res = append(res, values...) 122 } else { 123 res = append(res, val) 124 } 125 } 126 return res, nil 127 } 128 return flattenN(xs, depth) 129 } 130 131 // Repeat returns a new list consisting of count copies of list x. 132 // 133 // For instance: 134 // 135 // Repeat([1, 2], 2) 136 // 137 // results in 138 // 139 // [1, 2, 1, 2] 140 func Repeat(x []cue.Value, count int) ([]cue.Value, error) { 141 if count < 0 { 142 return nil, fmt.Errorf("negative count") 143 } 144 return slices.Repeat(x, count), nil 145 } 146 147 // Concat takes a list of lists and concatenates them. 148 // 149 // Concat([a, b, c]) is equivalent to 150 // 151 // [for x in a {x}, for x in b {x}, for x in c {x}] 152 func Concat(a []cue.Value) ([]cue.Value, error) { 153 var res []cue.Value 154 for _, e := range a { 155 iter, err := e.List() 156 if err != nil { 157 return nil, err 158 } 159 for iter.Next() { 160 res = append(res, iter.Value()) 161 } 162 } 163 return res, nil 164 } 165 166 // Take reports the prefix of length n of list x, or x itself if n > len(x). 167 // 168 // For instance: 169 // 170 // Take([1, 2, 3, 4], 2) 171 // 172 // results in 173 // 174 // [1, 2] 175 func Take(x []cue.Value, n int) ([]cue.Value, error) { 176 if n < 0 { 177 return nil, fmt.Errorf("negative index") 178 } 179 180 if n > len(x) { 181 return x, nil 182 } 183 184 return x[:n], nil 185 } 186 187 // Slice extracts the consecutive elements from list x starting from position i 188 // up till, but not including, position j, where 0 <= i < j <= len(x). 189 // 190 // For instance: 191 // 192 // Slice([1, 2, 3, 4], 1, 3) 193 // 194 // results in 195 // 196 // [2, 3] 197 func Slice(x []cue.Value, i, j int) ([]cue.Value, error) { 198 if i < 0 { 199 return nil, fmt.Errorf("negative index") 200 } 201 202 if i > j { 203 return nil, fmt.Errorf("invalid index: %v > %v", i, j) 204 } 205 206 if i > len(x) { 207 return nil, fmt.Errorf("slice bounds out of range") 208 } 209 210 if j > len(x) { 211 return nil, fmt.Errorf("slice bounds out of range") 212 } 213 214 return x[i:j], nil 215 } 216 217 // Reverse reverses a list. 218 // 219 // For instance: 220 // 221 // Reverse([1, 2, 3, 4]) 222 // 223 // results in 224 // 225 // [4, 3, 2, 1] 226 func Reverse(x []cue.Value) []cue.Value { 227 slices.Reverse(x) 228 return x 229 } 230 231 // MinItems reports whether a has at least n items. 232 func MinItems(list pkg.List, n int) (bool, error) { 233 count := len(list.Elems()) 234 if count >= n { 235 return true, nil 236 } 237 code := adt.EvalError 238 if list.IsOpen() { 239 code = adt.IncompleteError 240 } 241 return false, pkg.ValidationError{B: &adt.Bottom{ 242 Code: code, 243 Err: errors.Newf(token.NoPos, "len(list) < MinItems(%[2]d) (%[1]d < %[2]d)", count, n), 244 }} 245 } 246 247 // MaxItems reports whether a has at most n items. 248 func MaxItems(list pkg.List, n int) (bool, error) { 249 count := len(list.Elems()) 250 if count > n { 251 return false, pkg.ValidationError{B: &adt.Bottom{ 252 Code: adt.EvalError, 253 Err: errors.Newf(token.NoPos, "len(list) > MaxItems(%[2]d) (%[1]d > %[2]d)", count, n), 254 }} 255 } 256 257 return true, nil 258 } 259 260 // UniqueItems reports whether all elements in the list are unique. 261 func UniqueItems(a []cue.Value) (bool, error) { 262 if len(a) <= 1 { 263 return true, nil 264 } 265 266 // TODO(perf): this is an O(n^2) algorithm. We should make it O(n log n). 267 // This could be done as follows: 268 // - Create a list with some hash value for each element x in a as well 269 // alongside the value of x itself. 270 // - Sort the elements based on the hash value. 271 // - Compare subsequent elements to see if they are equal. 272 273 var tv types.Value 274 a[0].Core(&tv) 275 ctx := eval.NewContext(tv.R, tv.V) 276 277 posX, posY := 0, 0 278 code := adt.IncompleteError 279 280 outer: 281 for i, x := range a { 282 _, vx := value.ToInternal(x) 283 284 for j := i + 1; j < len(a); j++ { 285 _, vy := value.ToInternal(a[j]) 286 287 if adt.Equal(ctx, vx, vy, adt.RegularOnly) { 288 posX, posY = i, j 289 if adt.IsFinal(vy) { 290 code = adt.EvalError 291 break outer 292 } 293 } 294 } 295 } 296 297 if posX == posY { 298 return true, nil 299 } 300 301 var err errors.Error 302 switch x := a[posX].Value(); x.Kind() { 303 case cue.BoolKind, cue.NullKind, cue.IntKind, cue.FloatKind, cue.StringKind, cue.BytesKind: 304 err = errors.Newf(token.NoPos, "equal value (%v) at position %d and %d", x, posX, posY) 305 default: 306 err = errors.Newf(token.NoPos, "equal values at position %d and %d", posX, posY) 307 } 308 309 return false, pkg.ValidationError{B: &adt.Bottom{ 310 Code: code, 311 Err: err, 312 }} 313 } 314 315 // Contains reports whether v is contained in a. The value must be a 316 // comparable value. 317 func Contains(a []cue.Value, v cue.Value) bool { 318 return slices.ContainsFunc(a, v.Equals) 319 } 320 321 // MatchN is a validator that checks that the number of elements in the given 322 // list that unifies with the schema "matchValue" matches "n". 323 // "n" may be a number constraint and does not have to be a concrete number. 324 // Likewise, "matchValue" will usually be a non-concrete value. 325 func MatchN(list []cue.Value, n pkg.Schema, matchValue pkg.Schema) (bool, error) { 326 c := value.OpContext(n) 327 return matchN(c, list, n, matchValue) 328 } 329 330 // matchN is the actual implementation of MatchN. 331 func matchN(c *adt.OpContext, list []cue.Value, n pkg.Schema, matchValue pkg.Schema) (bool, error) { 332 var nmatch int64 333 for _, w := range list { 334 vx := adt.Unify(c, value.Vertex(matchValue), value.Vertex(w)) 335 x := value.Make(c, vx) 336 if x.Validate(cue.Final()) == nil { 337 nmatch++ 338 } 339 } 340 341 ctx := value.Context(c) 342 343 if err := n.Unify(ctx.Encode(nmatch)).Err(); err != nil { 344 return false, pkg.ValidationError{B: &adt.Bottom{ 345 Code: adt.EvalError, 346 Err: errors.Newf( 347 token.NoPos, 348 "number of matched elements is %d: does not satisfy %v", 349 nmatch, 350 n, 351 ), 352 }} 353 } 354 355 return true, nil 356 }