package ext
import (
"math"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker"
"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/interpreter"
)
func Sets() cel.EnvOption {
return cel.Lib(setsLib{})
}
type setsLib struct{}
func (setsLib) LibraryName() string {
return "cel.lib.ext.sets"
}
func (setsLib) CompileOptions() []cel.EnvOption {
listType := cel.ListType(cel.TypeParamType("T"))
return []cel.EnvOption{
cel.Function("sets.contains",
cel.Overload("list_sets_contains_list", []*cel.Type{listType, listType}, cel.BoolType,
cel.BinaryBinding(setsContains))),
cel.Function("sets.equivalent",
cel.Overload("list_sets_equivalent_list", []*cel.Type{listType, listType}, cel.BoolType,
cel.BinaryBinding(setsEquivalent))),
cel.Function("sets.intersects",
cel.Overload("list_sets_intersects_list", []*cel.Type{listType, listType}, cel.BoolType,
cel.BinaryBinding(setsIntersects))),
cel.CostEstimatorOptions(
checker.OverloadCostEstimate("list_sets_contains_list", estimateSetsCost(1)),
checker.OverloadCostEstimate("list_sets_intersects_list", estimateSetsCost(1)),
checker.OverloadCostEstimate("list_sets_equivalent_list", estimateSetsCost(2)),
),
}
}
func (setsLib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{
cel.CostTrackerOptions(
interpreter.OverloadCostTracker("list_sets_contains_list", trackSetsCost(1)),
interpreter.OverloadCostTracker("list_sets_intersects_list", trackSetsCost(1)),
interpreter.OverloadCostTracker("list_sets_equivalent_list", trackSetsCost(2)),
),
}
}
func setsIntersects(listA, listB ref.Val) ref.Val {
lA := listA.(traits.Lister)
lB := listB.(traits.Lister)
it := lA.Iterator()
for it.HasNext() == types.True {
exists := lB.Contains(it.Next())
if exists == types.True {
return types.True
}
}
return types.False
}
func setsContains(list, sublist ref.Val) ref.Val {
l := list.(traits.Lister)
sub := sublist.(traits.Lister)
it := sub.Iterator()
for it.HasNext() == types.True {
exists := l.Contains(it.Next())
if exists != types.True {
return exists
}
}
return types.True
}
func setsEquivalent(listA, listB ref.Val) ref.Val {
aContainsB := setsContains(listA, listB)
if aContainsB != types.True {
return aContainsB
}
return setsContains(listB, listA)
}
func estimateSetsCost(costFactor float64) checker.FunctionEstimator {
return func(estimator checker.CostEstimator, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
if len(args) == 2 {
arg0Size := estimateSize(estimator, args[0])
arg1Size := estimateSize(estimator, args[1])
costEstimate := arg0Size.Multiply(arg1Size).MultiplyByCostFactor(costFactor).Add(callCostEstimate)
return &checker.CallEstimate{CostEstimate: costEstimate}
}
return nil
}
}
func estimateSize(estimator checker.CostEstimator, node checker.AstNode) checker.SizeEstimate {
if l := node.ComputedSize(); l != nil {
return *l
}
if l := estimator.EstimateSize(node); l != nil {
return *l
}
return checker.SizeEstimate{Min: 0, Max: math.MaxUint64}
}
func trackSetsCost(costFactor float64) interpreter.FunctionTracker {
return func(args []ref.Val, _ ref.Val) *uint64 {
lhsSize := actualSize(args[0])
rhsSize := actualSize(args[1])
cost := callCost + uint64(float64(lhsSize*rhsSize)*costFactor)
return &cost
}
}
func actualSize(value ref.Val) uint64 {
if sz, ok := value.(traits.Sizer); ok {
return uint64(sz.Size().(types.Int))
}
return 1
}
var (
callCostEstimate = checker.CostEstimate{Min: 1, Max: 1}
callCost = uint64(1)
)