diff --git a/lib/collections.go b/lib/collections.go index a2ebcbf..d1c02a8 100644 --- a/lib/collections.go +++ b/lib/collections.go @@ -201,6 +201,25 @@ import ( // [1,2,3,4,5,6,7].min() // return 1 // min([1,2,3,4,5,6,7]) // return 1 // +// # Tail +// +// Returns the elements of a list after the first element: +// +// tail(>) -> > +// +// Examples: +// +// tail([1, 2, 3, 4, 5, 6]) // return [2, 3, 4, 5, 6] +// tail([6]) // return [] +// tail([]) // return [] +// +// The conjugate of tail, 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 +// // # Values // // Returns a list of values from a map: @@ -391,6 +410,15 @@ func (collectionsLib) CompileOptions() []cel.EnvOption { ), ), + cel.Function("tail", + cel.Overload( + "tail_list", + []*cel.Type{listV}, + listV, + cel.UnaryBinding(tail), + ), + ), + cel.Function("values", cel.MemberOverload( "map_values", @@ -452,6 +480,30 @@ func (collectionsLib) CompileOptions() []cel.EnvOption { func (collectionsLib) ProgramOptions() []cel.ProgramOption { return nil } +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 flatten(arg ref.Val) ref.Val { obj := arg l, ok := obj.(traits.Lister) diff --git a/testdata/want_more_lispy.txt b/testdata/want_more_lispy.txt new file mode 100644 index 0000000..2b099cc --- /dev/null +++ b/testdata/want_more_lispy.txt @@ -0,0 +1,83 @@ +mito -data state.json src.cel +! stderr . +cmp stdout want.txt + +-- state.json -- +{"list": [1, 2, 3, 4, 5]} +-- src.cel -- +{ + ?"head": state.list[?0], // Pure CEL spelling of (car state.list). + "list": tail(state.list), + "old": state.list, + "want_more": state.list[?0].hasValue(), +} +-- want.txt -- +{ + "head": 1, + "list": [ + 2, + 3, + 4, + 5 + ], + "old": [ + 1, + 2, + 3, + 4, + 5 + ], + "want_more": true +} +{ + "head": 2, + "list": [ + 3, + 4, + 5 + ], + "old": [ + 2, + 3, + 4, + 5 + ], + "want_more": true +} +{ + "head": 3, + "list": [ + 4, + 5 + ], + "old": [ + 3, + 4, + 5 + ], + "want_more": true +} +{ + "head": 4, + "list": [ + 5 + ], + "old": [ + 4, + 5 + ], + "want_more": true +} +{ + "head": 5, + "list": [], + "old": [ + 5 + ], + "want_more": true +} +{ + "list": [], + "old": [], + "want_more": false +}