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  }