mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-26 13:29:11 +01:00
872 lines
29 KiB
Go
Vendored
872 lines
29 KiB
Go
Vendored
// Copyright 2020 Google LLC
|
|
//
|
|
// Licensed 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 cel
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/google/cel-go/common"
|
|
"github.com/google/cel-go/common/ast"
|
|
"github.com/google/cel-go/common/decls"
|
|
"github.com/google/cel-go/common/env"
|
|
"github.com/google/cel-go/common/operators"
|
|
"github.com/google/cel-go/common/overloads"
|
|
"github.com/google/cel-go/common/stdlib"
|
|
"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"
|
|
"github.com/google/cel-go/parser"
|
|
)
|
|
|
|
const (
|
|
optMapMacro = "optMap"
|
|
optFlatMapMacro = "optFlatMap"
|
|
hasValueFunc = "hasValue"
|
|
unwrapOptFunc = "unwrapOpt"
|
|
optionalNoneFunc = "optional.none"
|
|
optionalOfFunc = "optional.of"
|
|
optionalOfNonZeroValueFunc = "optional.ofNonZeroValue"
|
|
optionalUnwrapFunc = "optional.unwrap"
|
|
valueFunc = "value"
|
|
unusedIterVar = "#unused"
|
|
)
|
|
|
|
// Library provides a collection of EnvOption and ProgramOption values used to configure a CEL
|
|
// environment for a particular use case or with a related set of functionality.
|
|
//
|
|
// Note, the ProgramOption values provided by a library are expected to be static and not vary
|
|
// between calls to Env.Program(). If there is a need for such dynamic configuration, prefer to
|
|
// configure these options outside the Library and within the Env.Program() call directly.
|
|
type Library interface {
|
|
// CompileOptions returns a collection of functional options for configuring the Parse / Check
|
|
// environment.
|
|
CompileOptions() []EnvOption
|
|
|
|
// ProgramOptions returns a collection of functional options which should be included in every
|
|
// Program generated from the Env.Program() call.
|
|
ProgramOptions() []ProgramOption
|
|
}
|
|
|
|
// SingletonLibrary refines the Library interface to ensure that libraries in this format are only
|
|
// configured once within the environment.
|
|
type SingletonLibrary interface {
|
|
Library
|
|
|
|
// LibraryName provides a namespaced name which is used to check whether the library has already
|
|
// been configured in the environment.
|
|
LibraryName() string
|
|
}
|
|
|
|
// LibraryAliaser generates a simple named alias for the library, for use during environment serialization.
|
|
type LibraryAliaser interface {
|
|
LibraryAlias() string
|
|
}
|
|
|
|
// LibrarySubsetter provides the subset description associated with the library, nil if not subset.
|
|
type LibrarySubsetter interface {
|
|
LibrarySubset() *env.LibrarySubset
|
|
}
|
|
|
|
// LibraryVersioner provides a version number for the library.
|
|
//
|
|
// If not implemented, the library version will be flagged as 'latest' during environment serialization.
|
|
type LibraryVersioner interface {
|
|
LibraryVersion() uint32
|
|
}
|
|
|
|
// Lib creates an EnvOption out of a Library, allowing libraries to be provided as functional args,
|
|
// and to be linked to each other.
|
|
func Lib(l Library) EnvOption {
|
|
singleton, isSingleton := l.(SingletonLibrary)
|
|
return func(e *Env) (*Env, error) {
|
|
if isSingleton {
|
|
if e.HasLibrary(singleton.LibraryName()) {
|
|
return e, nil
|
|
}
|
|
e.libraries[singleton.LibraryName()] = singleton
|
|
}
|
|
var err error
|
|
for _, opt := range l.CompileOptions() {
|
|
e, err = opt(e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
e.progOpts = append(e.progOpts, l.ProgramOptions()...)
|
|
return e, nil
|
|
}
|
|
}
|
|
|
|
// StdLibOption specifies a functional option for configuring the standard CEL library.
|
|
type StdLibOption func(*stdLibrary) *stdLibrary
|
|
|
|
// StdLibSubset configures the standard library to use a subset of its functions and macros.
|
|
//
|
|
// Since the StdLib is a singleton library, only the first instance of the StdLib() environment options
|
|
// will be configured on the environment which means only the StdLibSubset() initially configured with
|
|
// the library will be used.
|
|
func StdLibSubset(subset *env.LibrarySubset) StdLibOption {
|
|
return func(lib *stdLibrary) *stdLibrary {
|
|
lib.subset = subset
|
|
return lib
|
|
}
|
|
}
|
|
|
|
// StdLib returns an EnvOption for the standard library of CEL functions and macros.
|
|
func StdLib(opts ...StdLibOption) EnvOption {
|
|
lib := &stdLibrary{}
|
|
for _, o := range opts {
|
|
lib = o(lib)
|
|
}
|
|
return Lib(lib)
|
|
}
|
|
|
|
// stdLibrary implements the Library interface and provides functional options for the core CEL
|
|
// features documented in the specification.
|
|
type stdLibrary struct {
|
|
subset *env.LibrarySubset
|
|
}
|
|
|
|
// LibraryName implements the SingletonLibrary interface method.
|
|
func (*stdLibrary) LibraryName() string {
|
|
return "cel.lib.std"
|
|
}
|
|
|
|
// LibraryAlias returns the simple name of the library.
|
|
func (*stdLibrary) LibraryAlias() string {
|
|
return "stdlib"
|
|
}
|
|
|
|
// LibrarySubset returns the env.LibrarySubset definition associated with the CEL Library.
|
|
func (lib *stdLibrary) LibrarySubset() *env.LibrarySubset {
|
|
return lib.subset
|
|
}
|
|
|
|
// CompileOptions returns options for the standard CEL function declarations and macros.
|
|
func (lib *stdLibrary) CompileOptions() []EnvOption {
|
|
funcs := stdlib.Functions()
|
|
macros := StandardMacros
|
|
if lib.subset != nil {
|
|
subMacros := []Macro{}
|
|
for _, m := range macros {
|
|
if lib.subset.SubsetMacro(m.Function()) {
|
|
subMacros = append(subMacros, m)
|
|
}
|
|
}
|
|
macros = subMacros
|
|
subFuncs := []*decls.FunctionDecl{}
|
|
for _, fn := range funcs {
|
|
if f, include := lib.subset.SubsetFunction(fn); include {
|
|
subFuncs = append(subFuncs, f)
|
|
}
|
|
}
|
|
funcs = subFuncs
|
|
}
|
|
return []EnvOption{
|
|
func(e *Env) (*Env, error) {
|
|
var err error
|
|
if err = lib.subset.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
e.variables = append(e.variables, stdlib.Types()...)
|
|
for _, fn := range funcs {
|
|
existing, found := e.functions[fn.Name()]
|
|
if found {
|
|
fn, err = existing.Merge(fn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
e.functions[fn.Name()] = fn
|
|
}
|
|
return e, nil
|
|
},
|
|
Macros(macros...),
|
|
}
|
|
}
|
|
|
|
// ProgramOptions returns function implementations for the standard CEL functions.
|
|
func (*stdLibrary) ProgramOptions() []ProgramOption {
|
|
return []ProgramOption{}
|
|
}
|
|
|
|
// OptionalTypes enable support for optional syntax and types in CEL.
|
|
//
|
|
// The optional value type makes it possible to express whether variables have
|
|
// been provided, whether a result has been computed, and in the future whether
|
|
// an object field path, map key value, or list index has a value.
|
|
//
|
|
// # Syntax Changes
|
|
//
|
|
// OptionalTypes are unlike other CEL extensions because they modify the CEL
|
|
// syntax itself, notably through the use of a `?` preceding a field name or
|
|
// index value.
|
|
//
|
|
// ## Field Selection
|
|
//
|
|
// The optional syntax in field selection is denoted as `obj.?field`. In other
|
|
// words, if a field is set, return `optional.of(obj.field)“, else
|
|
// `optional.none()`. The optional field selection is viral in the sense that
|
|
// after the first optional selection all subsequent selections or indices
|
|
// are treated as optional, i.e. the following expressions are equivalent:
|
|
//
|
|
// obj.?field.subfield
|
|
// obj.?field.?subfield
|
|
//
|
|
// ## Indexing
|
|
//
|
|
// Similar to field selection, the optional syntax can be used in index
|
|
// expressions on maps and lists:
|
|
//
|
|
// list[?0]
|
|
// map[?key]
|
|
//
|
|
// ## Optional Field Setting
|
|
//
|
|
// When creating map or message literals, if a field may be optionally set
|
|
// based on its presence, then placing a `?` before the field name or key
|
|
// will ensure the type on the right-hand side must be optional(T) where T
|
|
// is the type of the field or key-value.
|
|
//
|
|
// The following returns a map with the key expression set only if the
|
|
// subfield is present, otherwise an empty map is created:
|
|
//
|
|
// {?key: obj.?field.subfield}
|
|
//
|
|
// ## Optional Element Setting
|
|
//
|
|
// When creating list literals, an element in the list may be optionally added
|
|
// when the element expression is preceded by a `?`:
|
|
//
|
|
// [a, ?b, ?c] // return a list with either [a], [a, b], [a, b, c], or [a, c]
|
|
//
|
|
// # Optional.Of
|
|
//
|
|
// Create an optional(T) value of a given value with type T.
|
|
//
|
|
// optional.of(10)
|
|
//
|
|
// # Optional.OfNonZeroValue
|
|
//
|
|
// Create an optional(T) value of a given value with type T if it is not a
|
|
// zero-value. A zero-value the default empty value for any given CEL type,
|
|
// including empty protobuf message types. If the value is empty, the result
|
|
// of this call will be optional.none().
|
|
//
|
|
// optional.ofNonZeroValue([1, 2, 3]) // optional(list(int))
|
|
// optional.ofNonZeroValue([]) // optional.none()
|
|
// optional.ofNonZeroValue(0) // optional.none()
|
|
// optional.ofNonZeroValue("") // optional.none()
|
|
//
|
|
// # Optional.None
|
|
//
|
|
// Create an empty optional value.
|
|
//
|
|
// # HasValue
|
|
//
|
|
// Determine whether the optional contains a value.
|
|
//
|
|
// optional.of(b'hello').hasValue() // true
|
|
// optional.ofNonZeroValue({}).hasValue() // false
|
|
//
|
|
// # Value
|
|
//
|
|
// Get the value contained by the optional. If the optional does not have a
|
|
// value, the result will be a CEL error.
|
|
//
|
|
// optional.of(b'hello').value() // b'hello'
|
|
// optional.ofNonZeroValue({}).value() // error
|
|
//
|
|
// # Or
|
|
//
|
|
// If the value on the left-hand side is optional.none(), the optional value
|
|
// on the right hand side is returned. If the value on the left-hand set is
|
|
// valued, then it is returned. This operation is short-circuiting and will
|
|
// only evaluate as many links in the `or` chain as are needed to return a
|
|
// non-empty optional value.
|
|
//
|
|
// obj.?field.or(m[?key])
|
|
// l[?index].or(obj.?field.subfield).or(obj.?other)
|
|
//
|
|
// # OrValue
|
|
//
|
|
// Either return the value contained within the optional on the left-hand side
|
|
// or return the alternative value on the right hand side.
|
|
//
|
|
// m[?key].orValue("none")
|
|
//
|
|
// # OptMap
|
|
//
|
|
// Apply a transformation to the optional's underlying value if it is not empty
|
|
// and return an optional typed result based on the transformation. The
|
|
// transformation expression type must return a type T which is wrapped into
|
|
// an optional.
|
|
//
|
|
// msg.?elements.optMap(e, e.size()).orValue(0)
|
|
//
|
|
// # OptFlatMap
|
|
//
|
|
// Introduced in version: 1
|
|
//
|
|
// Apply a transformation to the optional's underlying value if it is not empty
|
|
// and return the result. The transform expression must return an optional(T)
|
|
// rather than type T. This can be useful when dealing with zero values and
|
|
// conditionally generating an empty or non-empty result in ways which cannot
|
|
// be expressed with `optMap`.
|
|
//
|
|
// msg.?elements.optFlatMap(e, e[?0]) // return the first element if present.
|
|
//
|
|
// # First
|
|
//
|
|
// Introduced in version: 2
|
|
//
|
|
// Returns an optional with the first value from the right hand list, or
|
|
// optional.None.
|
|
//
|
|
// [1, 2, 3].first().value() == 1
|
|
//
|
|
// # Last
|
|
//
|
|
// Introduced in version: 2
|
|
//
|
|
// Returns an optional with the last value from the right hand list, or
|
|
// optional.None.
|
|
//
|
|
// [1, 2, 3].last().value() == 3
|
|
//
|
|
// This is syntactic sugar for msg.elements[msg.elements.size()-1].
|
|
//
|
|
// # Unwrap / UnwrapOpt
|
|
//
|
|
// Introduced in version: 2
|
|
//
|
|
// Returns a list of all the values that are not none in the input list of optional values.
|
|
// Can be used as optional.unwrap(List[T]) or with postfix notation: List[T].unwrapOpt()
|
|
//
|
|
// optional.unwrap([optional.of(42), optional.none()]) == [42]
|
|
// [optional.of(42), optional.none()].unwrapOpt() == [42]
|
|
func OptionalTypes(opts ...OptionalTypesOption) EnvOption {
|
|
lib := &optionalLib{version: math.MaxUint32}
|
|
for _, opt := range opts {
|
|
lib = opt(lib)
|
|
}
|
|
return Lib(lib)
|
|
}
|
|
|
|
type optionalLib struct {
|
|
version uint32
|
|
}
|
|
|
|
// OptionalTypesOption is a functional interface for configuring the strings library.
|
|
type OptionalTypesOption func(*optionalLib) *optionalLib
|
|
|
|
// OptionalTypesVersion configures the version of the optional type library.
|
|
//
|
|
// The version limits which functions are available. Only functions introduced
|
|
// below or equal to the given version included in the library. If this option
|
|
// is not set, all functions are available.
|
|
//
|
|
// See the library documentation to determine which version a function was introduced.
|
|
// If the documentation does not state which version a function was introduced, it can
|
|
// be assumed to be introduced at version 0, when the library was first created.
|
|
func OptionalTypesVersion(version uint32) OptionalTypesOption {
|
|
return func(lib *optionalLib) *optionalLib {
|
|
lib.version = version
|
|
return lib
|
|
}
|
|
}
|
|
|
|
// LibraryName implements the SingletonLibrary interface method.
|
|
func (*optionalLib) LibraryName() string {
|
|
return "cel.lib.optional"
|
|
}
|
|
|
|
// LibraryAlias returns the simple name of the library.
|
|
func (*optionalLib) LibraryAlias() string {
|
|
return "optional"
|
|
}
|
|
|
|
// LibraryVersion returns the version of the library.
|
|
func (lib *optionalLib) LibraryVersion() uint32 {
|
|
return lib.version
|
|
}
|
|
|
|
// CompileOptions implements the Library interface method.
|
|
func (lib *optionalLib) CompileOptions() []EnvOption {
|
|
paramTypeK := TypeParamType("K")
|
|
paramTypeV := TypeParamType("V")
|
|
optionalTypeV := OptionalType(paramTypeV)
|
|
listTypeV := ListType(paramTypeV)
|
|
mapTypeKV := MapType(paramTypeK, paramTypeV)
|
|
listOptionalTypeV := ListType(optionalTypeV)
|
|
|
|
opts := []EnvOption{
|
|
// Enable the optional syntax in the parser.
|
|
enableOptionalSyntax(),
|
|
|
|
// Introduce the optional type.
|
|
Types(types.OptionalType),
|
|
|
|
// Configure the optMap and optFlatMap macros.
|
|
Macros(ReceiverMacro(optMapMacro, 2, optMap,
|
|
MacroDocs(`perform computation on the value if present and return the result as an optional`),
|
|
MacroExamples(
|
|
common.MultilineDescription(
|
|
`// sub with the prefix 'dev.cel' or optional.none()`,
|
|
`request.auth.tokens.?sub.optMap(id, 'dev.cel.' + id)`),
|
|
`optional.none().optMap(i, i * 2) // optional.none()`))),
|
|
|
|
// Global and member functions for working with optional values.
|
|
Function(optionalOfFunc,
|
|
FunctionDocs(`create a new optional_type(T) with a value where any value is considered valid`),
|
|
Overload("optional_of", []*Type{paramTypeV}, optionalTypeV,
|
|
OverloadExamples(`optional.of(1) // optional(1)`),
|
|
UnaryBinding(func(value ref.Val) ref.Val {
|
|
return types.OptionalOf(value)
|
|
}))),
|
|
Function(optionalOfNonZeroValueFunc,
|
|
FunctionDocs(`create a new optional_type(T) with a value, if the value is not a zero or empty value`),
|
|
Overload("optional_ofNonZeroValue", []*Type{paramTypeV}, optionalTypeV,
|
|
OverloadExamples(
|
|
`optional.ofNonZeroValue(null) // optional.none()`,
|
|
`optional.ofNonZeroValue("") // optional.none()`,
|
|
`optional.ofNonZeroValue("hello") // optional.of('hello')`),
|
|
UnaryBinding(func(value ref.Val) ref.Val {
|
|
v, isZeroer := value.(traits.Zeroer)
|
|
if !isZeroer || !v.IsZeroValue() {
|
|
return types.OptionalOf(value)
|
|
}
|
|
return types.OptionalNone
|
|
}))),
|
|
Function(optionalNoneFunc,
|
|
FunctionDocs(`singleton value representing an optional without a value`),
|
|
Overload("optional_none", []*Type{}, optionalTypeV,
|
|
OverloadExamples(`optional.none()`),
|
|
FunctionBinding(func(values ...ref.Val) ref.Val {
|
|
return types.OptionalNone
|
|
}))),
|
|
Function(valueFunc,
|
|
FunctionDocs(`obtain the value contained by the optional, error if optional.none()`),
|
|
MemberOverload("optional_value", []*Type{optionalTypeV}, paramTypeV,
|
|
OverloadExamples(
|
|
`optional.of(1).value() // 1`,
|
|
`optional.none().value() // error`),
|
|
UnaryBinding(func(value ref.Val) ref.Val {
|
|
opt := value.(*types.Optional)
|
|
return opt.GetValue()
|
|
}))),
|
|
Function(hasValueFunc,
|
|
FunctionDocs(`determine whether the optional contains a value`),
|
|
MemberOverload("optional_hasValue", []*Type{optionalTypeV}, BoolType,
|
|
OverloadExamples(`optional.of({1: 2}).hasValue() // true`),
|
|
UnaryBinding(func(value ref.Val) ref.Val {
|
|
opt := value.(*types.Optional)
|
|
return types.Bool(opt.HasValue())
|
|
}))),
|
|
|
|
// Implementation of 'or' and 'orValue' are special-cased to support short-circuiting in the
|
|
// evaluation chain.
|
|
Function("or",
|
|
FunctionDocs(`chain optional expressions together, picking the first valued optional expression`),
|
|
MemberOverload("optional_or_optional", []*Type{optionalTypeV, optionalTypeV}, optionalTypeV,
|
|
OverloadExamples(
|
|
`optional.none().or(optional.of(1)) // optional.of(1)`,
|
|
common.MultilineDescription(
|
|
`// either a value from the first list, a value from the second, or optional.none()`,
|
|
`[1, 2, 3][?x].or([3, 4, 5][?y])`)))),
|
|
Function("orValue",
|
|
FunctionDocs(`chain optional expressions together picking the first valued optional or the default value`),
|
|
MemberOverload("optional_orValue_value", []*Type{optionalTypeV, paramTypeV}, paramTypeV,
|
|
OverloadExamples(
|
|
common.MultilineDescription(
|
|
`// pick the value for the given key if the key exists, otherwise return 'you'`,
|
|
`{'hello': 'world', 'goodbye': 'cruel world'}[?greeting].orValue('you')`)))),
|
|
|
|
// OptSelect is handled specially by the type-checker, so the receiver's field type is used to determine the
|
|
// optput type.
|
|
Function(operators.OptSelect,
|
|
FunctionDocs(`if the field is present create an optional of the field value, otherwise return optional.none()`),
|
|
Overload("select_optional_field", []*Type{DynType, StringType}, optionalTypeV,
|
|
OverloadExamples(
|
|
`msg.?field // optional.of(field) if non-empty, otherwise optional.none()`,
|
|
`msg.?field.?nested_field // optional.of(nested_field) if both field and nested_field are non-empty.`))),
|
|
|
|
// OptIndex is handled mostly like any other indexing operation on a list or map, so the type-checker can use
|
|
// these signatures to determine type-agreement without any special handling.
|
|
Function(operators.OptIndex,
|
|
FunctionDocs(`if the index is present create an optional of the field value, otherwise return optional.none()`),
|
|
Overload("list_optindex_optional_int", []*Type{listTypeV, IntType}, optionalTypeV,
|
|
OverloadExamples(`[1, 2, 3][?x] // element value if x is in the list size, else optional.none()`)),
|
|
Overload("optional_list_optindex_optional_int", []*Type{OptionalType(listTypeV), IntType}, optionalTypeV),
|
|
Overload("map_optindex_optional_value", []*Type{mapTypeKV, paramTypeK}, optionalTypeV,
|
|
OverloadExamples(
|
|
`map_value[?key] // value at the key if present, else optional.none()`,
|
|
common.MultilineDescription(
|
|
`// map key-value if index is a valid map key, else optional.none()`,
|
|
`{0: 2, 2: 4, 6: 8}[?index]`))),
|
|
Overload("optional_map_optindex_optional_value", []*Type{OptionalType(mapTypeKV), paramTypeK}, optionalTypeV)),
|
|
|
|
// Index overloads to accommodate using an optional value as the operand.
|
|
Function(operators.Index,
|
|
Overload("optional_list_index_int", []*Type{OptionalType(listTypeV), IntType}, optionalTypeV),
|
|
Overload("optional_map_index_value", []*Type{OptionalType(mapTypeKV), paramTypeK}, optionalTypeV)),
|
|
}
|
|
if lib.version >= 1 {
|
|
opts = append(opts, Macros(ReceiverMacro(optFlatMapMacro, 2, optFlatMap,
|
|
MacroDocs(`perform computation on the value if present and produce an optional value within the computation`),
|
|
MacroExamples(
|
|
common.MultilineDescription(
|
|
`// m = {'key': {}}`,
|
|
`m.?key.optFlatMap(k, k.?subkey) // optional.none()`),
|
|
common.MultilineDescription(
|
|
`// m = {'key': {'subkey': 'value'}}`,
|
|
`m.?key.optFlatMap(k, k.?subkey) // optional.of('value')`),
|
|
))))
|
|
}
|
|
|
|
if lib.version >= 2 {
|
|
opts = append(opts, Function("last",
|
|
FunctionDocs(`return the last value in a list if present, otherwise optional.none()`),
|
|
MemberOverload("list_last", []*Type{listTypeV}, optionalTypeV,
|
|
OverloadExamples(
|
|
`[].last() // optional.none()`,
|
|
`[1, 2, 3].last() ? optional.of(3)`),
|
|
UnaryBinding(func(v ref.Val) ref.Val {
|
|
list := v.(traits.Lister)
|
|
sz := list.Size().(types.Int)
|
|
if sz == types.IntZero {
|
|
return types.OptionalNone
|
|
}
|
|
return types.OptionalOf(list.Get(types.Int(sz - 1)))
|
|
}),
|
|
),
|
|
))
|
|
|
|
opts = append(opts, Function("first",
|
|
FunctionDocs(`return the first value in a list if present, otherwise optional.none()`),
|
|
MemberOverload("list_first", []*Type{listTypeV}, optionalTypeV,
|
|
OverloadExamples(
|
|
`[].first() // optional.none()`,
|
|
`[1, 2, 3].first() ? optional.of(1)`),
|
|
UnaryBinding(func(v ref.Val) ref.Val {
|
|
list := v.(traits.Lister)
|
|
sz := list.Size().(types.Int)
|
|
if sz == types.IntZero {
|
|
return types.OptionalNone
|
|
}
|
|
return types.OptionalOf(list.Get(types.Int(0)))
|
|
}),
|
|
),
|
|
))
|
|
|
|
opts = append(opts, Function(optionalUnwrapFunc,
|
|
FunctionDocs(`convert a list of optional values to a list containing only value which are not optional.none()`),
|
|
Overload("optional_unwrap", []*Type{listOptionalTypeV}, listTypeV,
|
|
OverloadExamples(`optional.unwrap([optional.of(1), optional.none()]) // [1]`),
|
|
UnaryBinding(optUnwrap))))
|
|
opts = append(opts, Function(unwrapOptFunc,
|
|
FunctionDocs(`convert a list of optional values to a list containing only value which are not optional.none()`),
|
|
MemberOverload("optional_unwrapOpt", []*Type{listOptionalTypeV}, listTypeV,
|
|
OverloadExamples(`[optional.of(1), optional.none()].unwrapOpt() // [1]`),
|
|
UnaryBinding(optUnwrap))))
|
|
}
|
|
|
|
return opts
|
|
}
|
|
|
|
// ProgramOptions implements the Library interface method.
|
|
func (lib *optionalLib) ProgramOptions() []ProgramOption {
|
|
return []ProgramOption{
|
|
CustomDecorator(decorateOptionalOr),
|
|
}
|
|
}
|
|
|
|
// Version returns the current version of the library.
|
|
func (lib *optionalLib) Version() uint32 {
|
|
return lib.version
|
|
}
|
|
|
|
func optMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *Error) {
|
|
varIdent := args[0]
|
|
varName := ""
|
|
switch varIdent.Kind() {
|
|
case ast.IdentKind:
|
|
varName = varIdent.AsIdent()
|
|
default:
|
|
return nil, meh.NewError(varIdent.ID(), "optMap() variable name must be a simple identifier")
|
|
}
|
|
mapExpr := args[1]
|
|
return meh.NewCall(
|
|
operators.Conditional,
|
|
meh.NewMemberCall(hasValueFunc, target),
|
|
meh.NewCall(optionalOfFunc,
|
|
meh.NewComprehension(
|
|
meh.NewList(),
|
|
unusedIterVar,
|
|
varName,
|
|
meh.NewMemberCall(valueFunc, meh.Copy(target)),
|
|
meh.NewLiteral(types.False),
|
|
meh.NewIdent(varName),
|
|
mapExpr,
|
|
),
|
|
),
|
|
meh.NewCall(optionalNoneFunc),
|
|
), nil
|
|
}
|
|
|
|
func optFlatMap(meh MacroExprFactory, target ast.Expr, args []ast.Expr) (ast.Expr, *Error) {
|
|
varIdent := args[0]
|
|
varName := ""
|
|
switch varIdent.Kind() {
|
|
case ast.IdentKind:
|
|
varName = varIdent.AsIdent()
|
|
default:
|
|
return nil, meh.NewError(varIdent.ID(), "optFlatMap() variable name must be a simple identifier")
|
|
}
|
|
mapExpr := args[1]
|
|
return meh.NewCall(
|
|
operators.Conditional,
|
|
meh.NewMemberCall(hasValueFunc, target),
|
|
meh.NewComprehension(
|
|
meh.NewList(),
|
|
unusedIterVar,
|
|
varName,
|
|
meh.NewMemberCall(valueFunc, meh.Copy(target)),
|
|
meh.NewLiteral(types.False),
|
|
meh.NewIdent(varName),
|
|
mapExpr,
|
|
),
|
|
meh.NewCall(optionalNoneFunc),
|
|
), nil
|
|
}
|
|
|
|
func optUnwrap(value ref.Val) ref.Val {
|
|
list := value.(traits.Lister)
|
|
var unwrappedList []ref.Val
|
|
iter := list.Iterator()
|
|
for iter.HasNext() == types.True {
|
|
val := iter.Next()
|
|
opt, isOpt := val.(*types.Optional)
|
|
if !isOpt {
|
|
return types.WrapErr(fmt.Errorf("value %v is not optional", val))
|
|
}
|
|
if opt.HasValue() {
|
|
unwrappedList = append(unwrappedList, opt.GetValue())
|
|
}
|
|
}
|
|
return types.DefaultTypeAdapter.NativeToValue(unwrappedList)
|
|
}
|
|
|
|
func enableOptionalSyntax() EnvOption {
|
|
return func(e *Env) (*Env, error) {
|
|
e.prsrOpts = append(e.prsrOpts, parser.EnableOptionalSyntax(true))
|
|
return e, nil
|
|
}
|
|
}
|
|
|
|
// EnableErrorOnBadPresenceTest enables error generation when a presence test or optional field
|
|
// selection is performed on a primitive type.
|
|
func EnableErrorOnBadPresenceTest(value bool) EnvOption {
|
|
return features(featureEnableErrorOnBadPresenceTest, value)
|
|
}
|
|
|
|
func decorateOptionalOr(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
|
call, ok := i.(interpreter.InterpretableCall)
|
|
if !ok {
|
|
return i, nil
|
|
}
|
|
args := call.Args()
|
|
if len(args) != 2 {
|
|
return i, nil
|
|
}
|
|
switch call.Function() {
|
|
case "or":
|
|
if call.OverloadID() != "" && call.OverloadID() != "optional_or_optional" {
|
|
return i, nil
|
|
}
|
|
return &evalOptionalOr{
|
|
id: call.ID(),
|
|
lhs: args[0],
|
|
rhs: args[1],
|
|
}, nil
|
|
case "orValue":
|
|
if call.OverloadID() != "" && call.OverloadID() != "optional_orValue_value" {
|
|
return i, nil
|
|
}
|
|
return &evalOptionalOrValue{
|
|
id: call.ID(),
|
|
lhs: args[0],
|
|
rhs: args[1],
|
|
}, nil
|
|
default:
|
|
return i, nil
|
|
}
|
|
}
|
|
|
|
// evalOptionalOr selects between two optional values, either the first if it has a value, or
|
|
// the second optional expression is evaluated and returned.
|
|
type evalOptionalOr struct {
|
|
id int64
|
|
lhs interpreter.Interpretable
|
|
rhs interpreter.Interpretable
|
|
}
|
|
|
|
// ID implements the Interpretable interface method.
|
|
func (opt *evalOptionalOr) ID() int64 {
|
|
return opt.id
|
|
}
|
|
|
|
// Eval evaluates the left-hand side optional to determine whether it contains a value, else
|
|
// proceeds with the right-hand side evaluation.
|
|
func (opt *evalOptionalOr) Eval(ctx interpreter.Activation) ref.Val {
|
|
// short-circuit lhs.
|
|
optLHS := opt.lhs.Eval(ctx)
|
|
optVal, ok := optLHS.(*types.Optional)
|
|
if !ok {
|
|
return optLHS
|
|
}
|
|
if optVal.HasValue() {
|
|
return optVal
|
|
}
|
|
return opt.rhs.Eval(ctx)
|
|
}
|
|
|
|
// evalOptionalOrValue selects between an optional or a concrete value. If the optional has a value,
|
|
// its value is returned, otherwise the alternative value expression is evaluated and returned.
|
|
type evalOptionalOrValue struct {
|
|
id int64
|
|
lhs interpreter.Interpretable
|
|
rhs interpreter.Interpretable
|
|
}
|
|
|
|
// ID implements the Interpretable interface method.
|
|
func (opt *evalOptionalOrValue) ID() int64 {
|
|
return opt.id
|
|
}
|
|
|
|
// Eval evaluates the left-hand side optional to determine whether it contains a value, else
|
|
// proceeds with the right-hand side evaluation.
|
|
func (opt *evalOptionalOrValue) Eval(ctx interpreter.Activation) ref.Val {
|
|
// short-circuit lhs.
|
|
optLHS := opt.lhs.Eval(ctx)
|
|
optVal, ok := optLHS.(*types.Optional)
|
|
if !ok {
|
|
return optLHS
|
|
}
|
|
if optVal.HasValue() {
|
|
return optVal.GetValue()
|
|
}
|
|
return opt.rhs.Eval(ctx)
|
|
}
|
|
|
|
type timeLegacyLibrary struct{}
|
|
|
|
func (timeLegacyLibrary) CompileOptions() []EnvOption {
|
|
return timeOverloadDeclarations
|
|
}
|
|
|
|
func (timeLegacyLibrary) ProgramOptions() []ProgramOption {
|
|
return []ProgramOption{}
|
|
}
|
|
|
|
// Declarations and functions which enable using UTC on time.Time inputs when the timezone is unspecified
|
|
// in the CEL expression.
|
|
var (
|
|
timeOverloadDeclarations = []EnvOption{
|
|
Function(overloads.TimeGetFullYear,
|
|
MemberOverload(overloads.TimestampToYear, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetFullYear, overloads.TimestampToYear, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetMonth,
|
|
MemberOverload(overloads.TimestampToMonth, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetMonth, overloads.TimestampToMonth, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetDayOfYear,
|
|
MemberOverload(overloads.TimestampToDayOfYear, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetDayOfYear, overloads.TimestampToDayOfYear, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetDayOfMonth,
|
|
MemberOverload(overloads.TimestampToDayOfMonthZeroBased, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetDayOfMonth, overloads.TimestampToDayOfMonthZeroBased, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetDate,
|
|
MemberOverload(overloads.TimestampToDayOfMonthOneBased, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetDate, overloads.TimestampToDayOfMonthOneBased, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetDayOfWeek,
|
|
MemberOverload(overloads.TimestampToDayOfWeek, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetDayOfWeek, overloads.TimestampToDayOfWeek, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetHours,
|
|
MemberOverload(overloads.TimestampToHours, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetHours, overloads.TimestampToHours, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetMinutes,
|
|
MemberOverload(overloads.TimestampToMinutes, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetMinutes, overloads.TimestampToMinutes, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetSeconds,
|
|
MemberOverload(overloads.TimestampToSeconds, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetSeconds, overloads.TimestampToSeconds, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
Function(overloads.TimeGetMilliseconds,
|
|
MemberOverload(overloads.TimestampToMilliseconds, []*Type{TimestampType}, IntType,
|
|
UnaryBinding(func(ts ref.Val) ref.Val {
|
|
t := ts.(types.Timestamp)
|
|
return t.Receive(overloads.TimeGetMilliseconds, overloads.TimestampToMilliseconds, []ref.Val{})
|
|
}),
|
|
),
|
|
),
|
|
}
|
|
)
|