lib/collections.go (897 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package lib
import (
"reflect"
"sort"
"strings"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/ast"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
"github.com/google/cel-go/parser"
)
// Collections returns a cel.EnvOption to configure extended functions for
// handling collections.
//
// As (Macro)
//
// The as macro is syntactic sugar for [val].map(var, function)[0].
//
// Examples:
//
// {"a":1, "b":2}.as(v, v.a == 1) // return true
// {"a":1, "b":2}.as(v, v) // return {"a":1, "b":2}
// {"a":1, "b":2}.as(v, v.with({"c":3})) // return {"a":1, "b":2, "c":3}
// {"a":1, "b":2}.as(v, [v, v]) // return [{"a":1, "b":2}, {"a":1, "b":2}]
//
// # Collate
//
// Returns a list of values obtained by traversing fields in the receiver with
// the path or paths given as the string parameter. When a list is traversed all
// children are include in the resulting list:
//
// <list<dyn>>.collate(<string>) -> <list<dyn>>
// <list<dyn>>.collate(<list<string>>) -> <list<dyn>>
// <map<string,dyn>>.collate(<string>) -> <list<dyn>>
// <map<string,dyn>>.collate(<list<string>>) -> <list<dyn>>
//
// Examples:
//
// Given v:
// {
// "a": [
// {"b": 1},
// {"b": 2},
// {"b": 3}
// ],
// "b": [
// {"b": -1, "c": 10},
// {"b": -2, "c": 20},
// {"b": -3, "c": 30}
// ]
// }
//
// v.collate("a") // return [{"b": 1}, {"b": 2}, {"b": 3}]
// v.collate("a.b") // return [1, 2, 3]
// v.collate(["a.b", "b.b"]) // return [1, 2, 3, -1, -2, -3]
// v.collate(["a", "b.b"]) // return [{"b": 1 }, {"b": 2 }, {"b": 3 }, -1, -2, -3 ]
//
// If the the path to be dropped includes a dot, it can be escaped with a literal
// backslash. See drop below.
//
// # Drop
//
// Returns the value of the receiver with the object at the given paths remove:
//
// <list<dyn>>.drop(<string>) -> <list<dyn>>
// <list<dyn>>.drop(<list<string>>) -> <list<dyn>>
// <map<string,dyn>>.drop(<string>) -> <map<string,dyn>>
// <map<string,dyn>>.drop(<list<string>>) -> <map<string,dyn>>
//
// Examples:
//
// Given v:
// {
// "a": [
// {"b": 1},
// {"b": 2},
// {"b": 3}
// ],
// "b": [
// {"b": -1, "c": 10},
// {"b": -2, "c": 20},
// {"b": -3, "c": 30}
// ]
// }
//
// v.drop("a") // return {"b": [{"b": -1, "c": 10}, {"b": -2, "c": 20}, {"b": -3, "c": 30}]}
// v.drop("a.b") // return {"a": [{}, {}, {}], "b": [{"b": -1, "c": 10}, {"b": -2, "c": 20}, {"b": -3, "c": 30}]}
// v.drop(["a.b", "b.b"]) // return {"a": [{}, {}, {}], "b": [{"c": 10}, {"c": 20}, {"c": 30}]}
// v.drop(["a", "b.b"]) // return {"b": [{"c": 10}, {"c": 20}, {"c": 30}]}
//
// If the the path to be dropped includes a dot, it can be escaped with a literal
// backslash.
//
// Examples:
//
// Given v:
// {
// "dotted.path": [
// {"b": -1, "c": 10},
// {"b": -2, "c": 20},
// {"b": -3, "c": 30}
// ]
// }
//
// v.drop("dotted\\.path.b") // return {"dotted.path": [{"c": 10}, {"c": 20}, {"c": 30}]}
//
// # Drop Empty
//
// Returns the value of the receiver with all empty lists and maps removed,
// recursively
//
// <list<dyn>>.drop_empty() -> <list<dyn>>
// <map<string,dyn>>.drop_empty() -> <map<string,dyn>>
//
// Examples:
//
// Given v:
// {
// "a": [
// {},
// {},
// {}
// ],
// "b": [
// {"b": -1, "c": 10},
// {"b": -2, "c": 20},
// {"b": -3, "c": 30}
// ]
// }
//
// v.drop_empty() // return {"b":[{"b":-1, "c":10}, {"b":-2, "c":20}, {"b":-3, "c":30}]}
//
// # Flatten
//
// Returns a list of non-list objects resulting from the depth-first
// traversal of a nested list:
//
// <list<dyn>...>.flatten() -> <list<dyn>>
//
// Examples:
//
// [[1],[2,3],[[[4]],[5,6]]].flatten() // return [1, 2, 3, 4, 5, 6]
// [[{"a":1,"b":[10, 11]}],[2,3],[[[4]],[5,6]]].flatten() // return [{"a":1, "b":[10, 11]}, 2, 3, 4, 5, 6]
//
// # Front
//
// Returns the first n elements of a list:
//
// front(<list<dyn>>, <int>) -> <optional<dyn>>
//
// Examples:
//
// front([1, 2, 3, 4, 5, 6], 2) // return [1, 2]
// front([6], 2) // return [6]
// front([], 2) // return []
//
// # Keys
//
// Returns a list of keys from a map:
//
// keys(<map<dyn,dyn>>) -> <list<dyn>>
// <map<dyn,dyn>>.keys() -> <list<dyn>>
//
// Examples:
//
// keys({"a":1, "b":2}) // return ["a", "b"]
// {1:"a", 2:"b"}.keys() // return [1, 2]
//
// # Max
//
// Returns the maximum value of a list or pair of comparable objects:
//
// <list<dyn>>.max() -> <dyn>
// max(<list<dyn>>) -> <dyn>
// max(<dyn>, <dyn>) -> <dyn>
//
// Examples:
//
// [1,2,3,4,5,6,7].max() // return 7
// max([1,2,3,4,5,6,7]) // return 7
// max(1,7) // return 7
//
// # Min
//
// Returns the minimum value of a list or pair of comparable objects:
//
// <list<dyn>>.min() -> <dyn>
// min(<list<dyn>>) -> <dyn>
// min(<dyn>, <dyn>) -> <dyn>
//
// Examples:
//
// [1,2,3,4,5,6,7].min() // return 1
// min([1,2,3,4,5,6,7]) // return 1
// min(1,7) // return 1
//
// # Sum
//
// Returns the sum of a list of int or list of double:
//
// <list<int>>.sum() -> <int>
// sum(<list<int>>) -> <int>
// <list<double>>.sum() -> <double>
// sum(<list<double>>) -> <double>
//
// sum([]) or sum with a list of mixed int and double will return an error.
//
// Examples:
//
// [1,2,3,4,5,6,7].sum() // return 28
// sum([1,2,3,4,5,6,7]) // return 28
//
// # Tail
//
// Returns the elements of a list after the first element, or if an integer
// second parameter is provided, after that index:
//
// tail(<list<dyn>>) -> <optional<dyn>>
// tail(<list<dyn>>, <int>) -> <optional<dyn>>
//
// Examples:
//
// tail([1, 2, 3, 4, 5, 6]) // return [2, 3, 4, 5, 6]
// tail([6]) // return []
// tail([]) // return []
// tail([1, 2, 3, 4, 5, 6], 2) // return [3, 4, 5, 6]
// tail([6], 2) // return []
// tail([], 2) // return []
//
// The conjugate of the single parameter tail call, getting the first element,
// can be achieved directly using list indexing, for example if a is
// [1, 2, 3, 4, 5, 6] and b is []:
//
// a[?0] // return 1
// b[?0] // return optional.none
//
// The conjugate of the two parameter tail call, getting the first n elements,
// can be achieved by using the front function.
//
// # Values
//
// Returns a list of values from a map:
//
// values(<map<dyn,dyn>>) -> <list<dyn>>
// <map<dyn,dyn>>.values() -> <list<dyn>>
//
// Examples:
//
// values({"a":1, "b":2}) // return [1, 2]
// {1:"a", 2:"b"}.values() // return ["a", "b"]
//
// # With
//
// Returns the receiver's value with the value of the parameter updating
// or adding fields:
//
// <map<K,V>>.with(<map<K,V>>) -> <map<K,V>>
//
// Examples:
//
// {"a":1, "b":2}.with({"a":10, "c":3}) // return {"a":10, "b":2, "c":3}
//
// # With Replace
//
// Returns the receiver's value with the value of the parameter replacing
// existing fields:
//
// <map<K,V>>.with_replace(<map<K,V>>) -> <map<K,V>>
//
// Examples:
//
// {"a":1, "b":2}.with_replace({"a":10, "c":3}) // return {"a":10, "b":2}
//
// # With Update
//
// Returns the receiver's value with the value of the parameter updating
// the map without replacing any existing fields:
//
// <map<K,V>>.with_update(<map<K,V>>) -> <map<K,V>>
//
// Examples:
//
// {"a":1, "b":2}.with_update({"a":10, "c":3}) // return {"a":1, "b":2, "c":3}
//
// # Zip
//
// Returns a map keyed on elements of a list with values from another equally
// sized list:
//
// zip(<list<K>>, <list<V>>) -> <map<K,V>>
// <list<K>>.zip(<list<V>>) -> <map<K,V>>
//
// Examples:
//
// zip(["a", "b"], [1, 2]) // return {"a":1, "b":2}
// ["a", "b"].zip([1, 2]) // return {"a":1, "b":2}
func Collections() cel.EnvOption {
return cel.Lib(collectionsLib{})
}
type collectionsLib struct{}
func (collectionsLib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
cel.Macros(parser.NewReceiverMacro("as", 2, makeAs)),
cel.Function("collate",
cel.MemberOverload(
"list_collate_string",
[]*cel.Type{listDyn, cel.StringType},
listDyn,
cel.BinaryBinding(catch(collateFields)),
),
cel.MemberOverload(
"list_collate_list_string",
[]*cel.Type{listDyn, listString},
listDyn,
cel.BinaryBinding(catch(collateFields)),
),
cel.MemberOverload(
"map_collate_string",
[]*cel.Type{mapStringDyn, cel.StringType},
listDyn,
cel.BinaryBinding(catch(collateFields)),
),
cel.MemberOverload(
"map_collate_list_string",
[]*cel.Type{mapStringDyn, listString},
listDyn,
cel.BinaryBinding(catch(collateFields)),
),
),
cel.Function("drop",
cel.MemberOverload(
"list_drop_string",
[]*cel.Type{listV, cel.StringType},
listV,
cel.BinaryBinding(catch(dropFields)),
),
cel.MemberOverload(
"list_drop_list_string",
[]*cel.Type{listV, listString},
listV,
cel.BinaryBinding(catch(dropFields)),
),
cel.MemberOverload(
"map_drop_string",
[]*cel.Type{mapKV, cel.StringType},
mapKV,
cel.BinaryBinding(catch(dropFields)),
),
cel.MemberOverload(
"map_drop_list_string",
[]*cel.Type{mapKV, listString},
mapKV,
cel.BinaryBinding(catch(dropFields)),
),
),
cel.Function("drop_empty",
cel.MemberOverload(
"list_drop_empty",
[]*cel.Type{listV},
listV,
cel.UnaryBinding(catch(dropEmpty)),
),
cel.MemberOverload(
"map_drop_empty",
[]*cel.Type{mapKV},
mapKV,
cel.UnaryBinding(catch(dropEmpty)),
),
),
cel.Function("flatten",
cel.MemberOverload(
"list_flatten",
[]*cel.Type{listDyn},
listDyn,
cel.UnaryBinding(catch(flatten)),
),
),
cel.Function("front",
cel.Overload(
"front_list_int",
[]*cel.Type{listV, cel.IntType},
listV,
cel.BinaryBinding(catch(frontN)),
),
),
cel.Function("keys",
cel.MemberOverload(
"map_keys",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(catch(mapKeys)),
),
cel.Overload(
"keys_map",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(catch(mapKeys)),
),
),
cel.Function("max",
cel.MemberOverload(
"list_max",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(catch(maxList)),
),
cel.Overload(
"max_list",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(catch(maxList)),
),
cel.Overload(
"max_pair",
[]*cel.Type{typeV, typeV},
typeV,
cel.BinaryBinding(maxDiadic),
),
),
cel.Function("min",
cel.MemberOverload(
"list_min",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(catch(minList)),
),
cel.Overload(
"min_list",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(catch(minList)),
),
cel.Overload(
"min_pair",
[]*cel.Type{typeV, typeV},
typeV,
cel.BinaryBinding(minDiadic),
),
),
cel.Function("sum",
cel.MemberOverload(
"list_sum",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(catch(sumList)),
),
cel.Overload(
"sum_list",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(catch(sumList)),
),
),
cel.Function("tail",
cel.Overload(
"tail_list",
[]*cel.Type{listV},
listV,
cel.UnaryBinding(catch(tail)),
),
cel.Overload(
"tail_list_int",
[]*cel.Type{listV, cel.IntType},
listV,
cel.BinaryBinding(catch(tailN)),
),
),
cel.Function("values",
cel.MemberOverload(
"map_values",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(catch(mapValues)),
),
cel.Overload(
"values_map",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(catch(mapValues)),
),
),
cel.Function("with",
cel.MemberOverload(
"map_with_map",
[]*cel.Type{mapKV, mapKV},
mapKV,
cel.BinaryBinding(catch(withAll)),
),
),
cel.Function("with_update",
cel.MemberOverload(
"map_with_update_map",
[]*cel.Type{mapKV, mapKV},
mapKV,
cel.BinaryBinding(catch(withUpdate)),
),
),
cel.Function("with_replace",
cel.MemberOverload(
"map_with_replace_map",
[]*cel.Type{mapKV, mapKV},
mapKV,
cel.BinaryBinding(catch(withReplace)),
),
),
cel.Function("zip",
cel.MemberOverload(
"list_zip",
[]*cel.Type{listK, listV},
mapKV,
cel.BinaryBinding(catch(zipLists)),
),
cel.Overload(
"zip_list",
[]*cel.Type{listK, listV},
mapKV,
cel.BinaryBinding(catch(zipLists)),
),
),
}
}
func (collectionsLib) ProgramOptions() []cel.ProgramOption { return nil }
func frontN(arg0, arg1 ref.Val) ref.Val {
obj := arg0
l, ok := obj.(traits.Lister)
if !ok {
return types.ValOrErr(obj, "no such overload")
}
until, ok := arg1.(types.Int)
if !ok {
return types.ValOrErr(until, "no such overload")
}
if l.Size() == types.IntZero {
return arg0
}
t := make([]ref.Val, 0, until)
it := l.Iterator()
for it.HasNext() == types.True && until > 0 {
t = append(t, it.Next())
until--
}
return types.NewRefValList(types.DefaultTypeAdapter, t)
}
func tail(arg ref.Val) ref.Val {
obj := arg
l, ok := obj.(traits.Lister)
if !ok {
return types.ValOrErr(obj, "no such overload")
}
if l.Size() == types.IntZero {
return arg
}
n := l.Size().(types.Int)
t := make([]ref.Val, 0, n-1)
it := l.Iterator()
head := true
for it.HasNext() == types.True {
if head {
it.Next()
head = false
continue
}
t = append(t, it.Next())
}
return types.NewRefValList(types.DefaultTypeAdapter, t)
}
func tailN(arg0, arg1 ref.Val) ref.Val {
obj := arg0
l, ok := obj.(traits.Lister)
if !ok {
return types.ValOrErr(obj, "no such overload")
}
after, ok := arg1.(types.Int)
if !ok {
return types.ValOrErr(after, "no such overload")
}
n := l.Size().(types.Int)
if n == 0 {
return arg0
}
if n <= after {
return types.NewRefValList(types.DefaultTypeAdapter, []ref.Val{})
}
t := make([]ref.Val, 0, n-after)
it := l.Iterator()
for it.HasNext() == types.True {
if after > 0 {
it.Next()
after--
continue
}
t = append(t, it.Next())
}
return types.NewRefValList(types.DefaultTypeAdapter, t)
}
func flatten(arg ref.Val) ref.Val {
obj := arg
l, ok := obj.(traits.Lister)
if !ok {
return types.ValOrErr(obj, "no such overload")
}
dst := types.NewMutableList(types.DefaultTypeAdapter)
flattenParts(dst, l)
return dst.ToImmutableList()
}
func flattenParts(dst traits.MutableLister, val traits.Lister) {
it := val.Iterator()
for it.HasNext().Value().(bool) {
if _, ok := it.Next().(traits.Lister); !ok {
dst.Add(val)
return
}
}
it = val.Iterator()
for it.HasNext() == types.True {
flattenParts(dst, it.Next().(traits.Lister))
}
}
func withAll(dst, src ref.Val) ref.Val {
new, other, err := with(dst, src)
if err != nil {
return err
}
for k, v := range other {
new[k] = v
}
return types.NewRefValMap(types.DefaultTypeAdapter, new)
}
func withUpdate(dst, src ref.Val) ref.Val {
new, other, err := with(dst, src)
if err != nil {
return err
}
for k, v := range other {
if _, ok := new[k]; ok {
continue
}
new[k] = v
}
return types.NewRefValMap(types.DefaultTypeAdapter, new)
}
func withReplace(dst, src ref.Val) ref.Val {
new, other, err := with(dst, src)
if err != nil {
return err
}
for k, v := range other {
if _, ok := new[k]; !ok {
continue
}
new[k] = v
}
return types.NewRefValMap(types.DefaultTypeAdapter, new)
}
var refValMap = reflect.TypeOf(map[ref.Val]ref.Val(nil))
func with(dst, src ref.Val) (res, other map[ref.Val]ref.Val, maybe ref.Val) {
obj, ok := dst.(traits.Mapper)
if !ok {
return nil, nil, types.ValOrErr(obj, "no such overload")
}
val, ok := src.(traits.Mapper)
if !ok {
return nil, nil, types.ValOrErr(src, "unsupported src type")
}
new := make(map[ref.Val]ref.Val)
m, err := obj.ConvertToNative(refValMap)
if err != nil {
return nil, nil, types.NewErr("unable to convert dst to native: %v", err)
}
for k, v := range m.(map[ref.Val]ref.Val) {
new[k] = v
}
m, err = val.ConvertToNative(refValMap)
if err != nil {
return nil, nil, types.NewErr("unable to convert src to native: %v", err)
}
return new, m.(map[ref.Val]ref.Val), nil
}
// TODO: Make this configurable to allow map, list and string emptiness and null.
func dropEmpty(val ref.Val) ref.Val {
obj, ok := val.(iterator)
if !ok || !hasEmpty(obj) {
return val
}
switch obj := val.(type) {
case traits.Lister:
new := make([]ref.Val, 0, obj.Size().Value().(int64))
it := obj.Iterator()
for it.HasNext() == types.True {
elem := it.Next()
switch val := elem.(type) {
case iterator:
if val.Size() != types.IntZero {
res := dropEmpty(val)
if v, ok := res.(traits.Sizer); ok {
if v.Size() != types.IntZero {
new = append(new, res)
}
} else {
new = append(new, res)
}
}
default:
new = append(new, val)
}
}
return types.NewRefValList(types.DefaultTypeAdapter, new)
case traits.Mapper:
new := make(map[ref.Val]ref.Val)
m, err := obj.ConvertToNative(refValMap)
if err != nil {
return types.NewErr("unable to convert map to native: %v", err)
}
for k, v := range m.(map[ref.Val]ref.Val) {
switch val := v.(type) {
case iterator:
if val.Size() != types.IntZero {
res := dropEmpty(v)
if v, ok := res.(traits.Sizer); ok {
if v.Size() != types.IntZero {
new[k] = res
}
} else {
new[k] = res
}
}
default:
new[k] = v
}
}
return types.NewRefValMap(types.DefaultTypeAdapter, new)
default:
// This should never happen since non-iterator
// types will have been returned in the preamble.
return val
}
}
// hasEmpty returns whether val is a map or a list that has any zero-sized
// map or list elements recursively. Zero sized strings are not considered
// to be empty.
func hasEmpty(val iterator) bool {
it := val.Iterator()
switch val := val.(type) {
case traits.Lister:
for it.HasNext() == types.True {
elem := it.Next()
iter, ok := elem.(iterator)
if !ok {
continue
}
if iter.Size() == types.IntZero || hasEmpty(iter) {
return true
}
}
case traits.Mapper:
for it.HasNext() == types.True {
elem := val.Get(it.Next())
iter, ok := elem.(iterator)
if !ok {
continue
}
if iter.Size() == types.IntZero || hasEmpty(iter) {
return true
}
}
}
return false
}
// iterator is the common interface for lists and maps required for dropEmpty.
type iterator interface {
ref.Val
traits.Iterable
traits.Sizer
}
func dropFields(obj, fields ref.Val) ref.Val {
switch fields := fields.(type) {
case types.String:
return dropFieldPath(obj, fields)
case traits.Lister:
it := fields.Iterator()
for it.HasNext() == types.True {
obj = dropFieldPath(obj, it.Next().ConvertToType(types.StringType).(types.String))
}
return obj
}
return types.NewErr("invalid parameter type for drop: %v", fields.Type())
}
func dropFieldPath(arg ref.Val, path types.String) (val ref.Val) {
defer func() {
switch err := recover().(type) {
case *types.Err:
val = err
}
}()
if !hasFieldPath(arg, path) {
return arg
}
switch obj := arg.(type) {
case traits.Lister:
new := make([]ref.Val, 0, obj.Size().Value().(int64))
it := obj.Iterator()
for it.HasNext() == types.True {
elem := it.Next()
new = append(new, dropFieldPath(elem, path))
}
return types.NewRefValList(types.DefaultTypeAdapter, new)
case traits.Mapper:
dotIdx, escaped := pathSepIndex(string(path))
switch {
case dotIdx == 0, dotIdx == len(path)-1:
return types.NewErr("invalid parameter path for drop: %s", path)
case dotIdx < 0:
new := make(map[ref.Val]ref.Val)
m, err := obj.ConvertToNative(refValMap)
if err != nil {
return types.NewErr("unable to convert map to native: %v", err)
}
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(path) == types.False {
new[k] = v
}
}
return types.NewRefValMap(types.DefaultTypeAdapter, new)
default:
new := make(map[ref.Val]ref.Val)
m, err := obj.ConvertToNative(refValMap)
if err != nil {
return types.NewErr("unable to convert map to native: %v", err)
}
head := path[:dotIdx]
if escaped {
head = types.String(strings.ReplaceAll(string(head), `\.`, "."))
}
tail := path[dotIdx+1:]
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(head) == types.True {
new[head] = dropFieldPath(v, tail)
} else {
new[k] = v
}
}
return types.NewRefValMap(types.DefaultTypeAdapter, new)
}
default:
return obj
}
}
func hasFieldPath(arg ref.Val, path types.String) bool {
switch obj := arg.(type) {
case traits.Lister:
it := obj.Iterator()
for it.HasNext() == types.True {
if hasFieldPath(it.Next(), path) {
return true
}
}
return false
case traits.Mapper:
dotIdx, escaped := pathSepIndex(string(path))
switch {
case dotIdx == 0, dotIdx == len(path)-1:
panic(types.NewErr("invalid parameter path for drop: %s", path))
case dotIdx < 0:
m, err := obj.ConvertToNative(refValMap)
if err != nil {
panic(types.NewErr("unable to convert map to native: %v", err))
}
for k := range m.(map[ref.Val]ref.Val) {
if k.Equal(path) == types.True {
return true
}
}
return false
default:
m, err := obj.ConvertToNative(refValMap)
if err != nil {
panic(types.NewErr("unable to convert map to native: %v", err))
}
head := path[:dotIdx]
if escaped {
head = types.String(strings.ReplaceAll(string(head), `\.`, "."))
}
tail := path[dotIdx+1:]
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(head) == types.True {
return hasFieldPath(v, tail)
}
}
return false
}
default:
return false
}
}
func collateFields(arg, fields ref.Val) (vals ref.Val) {
defer func() {
switch err := recover().(type) {
case *types.Err:
vals = err
}
}()
switch fields := fields.(type) {
case types.String:
return types.NewRefValList(types.DefaultTypeAdapter, collateFieldPath(arg, fields))
case traits.Lister:
var elems []ref.Val
it := fields.Iterator()
for it.HasNext() == types.True {
switch field := it.Next().(type) {
case types.String:
elems = append(elems, collateFieldPath(arg, field.ConvertToType(types.StringType).(types.String))...)
default:
return types.NewErr("invalid parameter type for collate fields: %v", field.Type())
}
}
return types.NewRefValList(types.DefaultTypeAdapter, elems)
}
return types.NewErr("invalid parameter type for collate: %v", fields.Type())
}
func collateFieldPath(arg ref.Val, path types.String) []ref.Val {
var collation []ref.Val
switch obj := arg.(type) {
case traits.Lister:
it := obj.Iterator()
for it.HasNext() == types.True {
elem := it.Next()
collation = append(collation, collateFieldPath(elem, path)...)
}
return collation
case traits.Mapper:
dotIdx, escaped := pathSepIndex(string(path))
switch {
case dotIdx == 0, dotIdx == len(path)-1:
panic(types.NewErr("invalid parameter path for drop: %s", path))
case dotIdx < 0:
m, err := obj.ConvertToNative(refValMap)
if err != nil {
panic(types.NewErr("unable to convert map to native: %v", err))
}
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(path) == types.True {
switch v := v.(type) {
case traits.Lister:
it := v.Iterator()
for it.HasNext() == types.True {
collation = append(collation, it.Next())
}
default:
collation = append(collation, v)
}
}
}
default:
m, err := obj.ConvertToNative(refValMap)
if err != nil {
panic(types.NewErr("unable to convert map to native: %v", err))
}
head := path[:dotIdx]
if escaped {
head = types.String(strings.ReplaceAll(string(head), `\.`, "."))
}
tail := path[dotIdx+1:]
for k, v := range m.(map[ref.Val]ref.Val) {
if k.Equal(head) == types.True {
collation = append(collation, collateFieldPath(v, tail)...)
}
}
}
default:
if path == "" {
collation = []ref.Val{obj}
}
}
return collation
}
func sumList(arg ref.Val) ref.Val {
list, ok := arg.(traits.Lister)
if !ok {
return types.NoSuchOverloadErr()
}
if list.Size() == types.IntZero {
return types.NewErr("no sum of empty list")
}
var (
iSum int
fSum float64
hasInt, hasFloat bool
)
it := list.Iterator()
for i := 0; it.HasNext() == types.True; i++ {
elem := it.Next()
switch elem := elem.(type) {
case types.Double:
if hasInt {
return types.NewErr("no sum of mixed int and double: first mismatch at index %d", i)
}
hasFloat = true
fSum += float64(elem)
case types.Int:
if hasFloat {
return types.NewErr("no sum of mixed int and double: first mismatch at index %d", i)
}
hasInt = true
iSum += int(elem)
default:
return types.NewErr("no sum of list containing %T", elem)
}
}
if hasInt {
return types.Int(iSum)
}
return types.Double(fSum)
}
type comparer interface {
ref.Val
traits.Comparer
}
func minDiadic(arg0, arg1 ref.Val) ref.Val {
return compareDiadic(arg0, arg1, -1)
}
func maxDiadic(arg0, arg1 ref.Val) ref.Val {
return compareDiadic(arg0, arg1, 1)
}
func compareDiadic(arg0, arg1 ref.Val, cmp types.Int) ref.Val {
a, ok := arg0.(comparer)
if !ok {
return types.NoSuchOverloadErr()
}
b, ok := arg1.(comparer)
if !ok {
return types.NoSuchOverloadErr()
}
if a.Compare(b) == cmp {
return a
}
return b
}
func minList(arg ref.Val) ref.Val {
return compare(arg, -1)
}
func maxList(arg ref.Val) ref.Val {
return compare(arg, 1)
}
func compare(arg ref.Val, cmp types.Int) ref.Val {
list, ok := arg.(traits.Lister)
if !ok {
return types.NoSuchOverloadErr()
}
if list.Size() == types.IntZero {
return types.NewErr("no extremum of empty list")
}
var min comparer
it := list.Iterator()
for it.HasNext() == types.True {
elem, ok := it.Next().(comparer)
if !ok {
return types.NoSuchOverloadErr()
}
if min == nil || elem.Compare(min) == cmp {
min = elem
}
}
return min
}
func zipLists(arg0, arg1 ref.Val) ref.Val {
keys, ok := arg0.(traits.Lister)
if !ok {
return types.NoSuchOverloadErr()
}
vals, ok := arg1.(traits.Lister)
if !ok {
return types.NoSuchOverloadErr()
}
if keys.Size() != vals.Size() {
return types.NewErr("zip: size(keys) != size(vals): %d != %d", keys.Size(), vals.Size())
}
n, _ := keys.Size().(types.Int)
m := make(map[ref.Val]ref.Val, n)
for i := types.Int(0); i < n; i++ {
m[keys.Get(i)] = vals.Get(i)
}
return types.NewRefValMap(types.DefaultTypeAdapter, m)
}
func mapKeys(val ref.Val) ref.Val {
mapK, ok := val.(traits.Mapper)
if !ok {
return types.ValOrErr(mapK, "no such overload")
}
n, ok := mapK.Size().(types.Int)
if !ok {
return types.NewErr("unable to get size of map")
}
keys := make([]ref.Val, 0, n)
if mapK.Size() != types.IntZero {
canSort := true
it := mapK.Iterator()
for it.HasNext() == types.True {
k := it.Next()
keys = append(keys, k)
_, ok := k.(traits.Comparer)
if !ok {
canSort = false
}
}
if canSort {
sort.Slice(keys, func(i, j int) bool {
return keys[i].(traits.Comparer).Compare(keys[j]) == types.Int(-1)
})
}
}
return types.NewRefValList(types.DefaultTypeAdapter, keys)
}
func mapValues(val ref.Val) ref.Val {
mapK, ok := val.(traits.Mapper)
if !ok {
return types.ValOrErr(mapK, "no such overload")
}
n, ok := mapK.Size().(types.Int)
if !ok {
return types.NewErr("unable to get size of map")
}
values := make([]ref.Val, 0, n)
type valComparer interface {
ref.Val
traits.Comparer
}
type kv struct {
Key valComparer
Value ref.Val
}
if mapK.Size() != types.IntZero {
canSort := true
it := mapK.Iterator()
ss := make([]kv, 0, n)
for it.HasNext() == types.True {
k := it.Next()
v := mapK.Get(k)
ck, ok := k.(valComparer)
if !ok {
canSort = false
}
ss = append(ss, kv{ck, v})
}
if canSort {
sort.Slice(ss, func(i, j int) bool {
return ss[i].Key.Compare(ss[j].Key) == types.Int(-1)
})
}
for _, kv := range ss {
values = append(values, kv.Value)
}
}
return types.NewRefValList(types.DefaultTypeAdapter, values)
}
func makeAs(mef cel.MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *cel.Error) {
init := target
ident := args[0]
expr := args[1]
if ident.Kind() != ast.IdentKind {
return nil, &common.Error{Message: "argument is not an identifier"}
}
label := ident.AsIdent()
const unused = "_"
return mef.NewComprehension(
mef.NewList(),
unused,
label,
init,
mef.NewLiteral(types.False),
mef.NewIdent(label),
expr,
), nil
}
// pathSepIndex returns the offset to a non-escaped dot path separator and
// whether the path element before the separator contains a backslash-escaped
// path separator.
func pathSepIndex(s string) (off int, escaped bool) {
for {
idx := strings.IndexByte(s[off:], '.')
if idx == -1 {
return -1, escaped
}
off += idx
if idx == 0 || s[off-1] != '\\' {
return off, escaped
}
off++
escaped = true
}
}