Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: Extract keys and values from map #46

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions lib/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package lib

import (
"reflect"
"sort"
"strings"

"github.com/google/cel-go/cel"
Expand Down Expand Up @@ -236,6 +237,30 @@ import (
//
// zip(["a", "b"], [1, 2]) // return {"a":1, "b":2}
// ["a", "b"].zip([1, 2]) // return {"a":1, "b":2}
//
// # 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]
//
// # 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"]
func Collections() cel.EnvOption {
return cel.Lib(collectionsLib{})
}
Expand Down Expand Up @@ -379,6 +404,34 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
[]string{"K", "V"},
),
),
decls.NewFunction("keys",
decls.NewParameterizedInstanceOverload(
"map_keys",
[]*expr.Type{mapKV},
listK,
[]string{"K"},
),
decls.NewParameterizedOverload(
"keys_map",
[]*expr.Type{mapKV},
listK,
[]string{"K"},
),
),
decls.NewFunction("values",
decls.NewParameterizedInstanceOverload(
"map_values",
[]*expr.Type{mapKV},
listV,
[]string{"V"},
),
decls.NewParameterizedOverload(
"values_map",
[]*expr.Type{mapKV},
listV,
[]string{"V"},
),
),
),
}
}
Expand Down Expand Up @@ -481,6 +534,26 @@ func (collectionsLib) ProgramOptions() []cel.ProgramOption {
Binary: zipLists,
},
),
cel.Functions(
&functions.Overload{
Operator: "map_keys",
Unary: mapKeys,
},
&functions.Overload{
Operator: "keys_map",
Unary: mapKeys,
},
),
cel.Functions(
&functions.Overload{
Operator: "map_values",
Unary: mapValues,
},
&functions.Overload{
Operator: "values_map",
Unary: mapValues,
},
),
}
}

Expand Down Expand Up @@ -949,6 +1022,79 @@ func zipLists(arg0, arg1 ref.Val) ref.Val {
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 {
kcreddy marked this conversation as resolved.
Show resolved Hide resolved
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(eh parser.ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
ident := args[0]
if ident.Kind() != ast.IdentKind {
Expand Down
20 changes: 20 additions & 0 deletions testdata/keys.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
mito -use collections,try src.cel
! stderr .
cmp stdout want.txt

-- src.cel --
{
"good_instance": {"b": [2, 3], "a": {1: "aa"}}.keys(),
"good_function": keys({1: "a", 2: "b"}),
}
-- want.txt --
{
"good_function": [
1,
2
],
"good_instance": [
"a",
"b"
]
}
30 changes: 30 additions & 0 deletions testdata/values.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
mito -use collections,try src.cel
! stderr .
cmp stdout want.txt

-- src.cel --
{
"good_instance": {"c": {"cc": 4, "cd": 5}, "a": ["aa", "ab"], "b": [2, 3]}.values(),
"good_function": values({2: "b", 1: "a"}),
}
-- want.txt --
{
"good_function": [
"a",
"b"
],
"good_instance": [
[
"aa",
"ab"
],
[
2,
3
],
{
"cc": 4,
"cd": 5
}
]
Comment on lines +16 to +29
Copy link
Contributor Author

@kcreddy kcreddy Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is failing because map doesn't maintain order when iterating using range. Any workarounds to make the tests pass?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a nice property to have would be to make zip(map_keys(m), map_values(m)) return the original map (well, a copy of it). This currently does not happen, but if it did, this test would pass. It adds an allocation cost to the map_values call and a sort CPU cost to both, but I think it is worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added new test into zip to test zip(map_keys(m), map_values(m))

}
5 changes: 5 additions & 0 deletions testdata/zip.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cmp stdout want.txt
{
"good_instance": ["a", "b"].zip([1, 2]),
"good_function": zip(["a", "b"], [1, 2]),
"good_function_2": zip(keys({"a": 1, "b": 2}), values({"a": 1, "b": 2})),
"bad_instance": try(["a", "b"].zip([1, 2, 3])),
"bad_function": try(zip(["a", "b"], [1, 2, 3])),
}
Expand All @@ -17,6 +18,10 @@ cmp stdout want.txt
"a": 1,
"b": 2
},
"good_function_2": {
"a": 1,
"b": 2
},
"good_instance": {
"a": 1,
"b": 2
Expand Down
Loading