Skip to content

Commit

Permalink
Add filter expression function
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Aug 11, 2023
1 parent be223a6 commit 07a5346
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 11 deletions.
2 changes: 1 addition & 1 deletion cmd/docgen/docs/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestGenerateDocs(t *testing.T) {
context := completion["context"].(map[string]interface{})
functions := completion["functions"].([]interface{})

assert.Equal(t, 87, len(functions))
assert.Equal(t, 88, len(functions))

types := context["types"].([]interface{})
assert.Equal(t, 18, len(types))
Expand Down
50 changes: 40 additions & 10 deletions excellent/functions/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func init() {
"sum": OneArrayFunction(Sum),
"unique": OneArrayFunction(Unique),
"concat": TwoArrayFunction(Concat),
"filter": TwoArgFunction(Filter),

// encoded text functions
"urn_parts": OneTextFunction(URNParts),
Expand Down Expand Up @@ -1689,6 +1690,39 @@ func Concat(env envs.Environment, array1 *types.XArray, array2 *types.XArray) ty
return types.NewXArray(both...)
}

// Filter returns a new array with the items from `array` that when passed to `func` return true.
//
// @(filter(array(1, 0, 2), boolean)) -> [1, 2]
// @(filter(array("a", "b", "c"), (x) => x != "c")) -> [a, b]
//
// @function filter(array, func)
func Filter(env envs.Environment, arg1 types.XValue, arg2 types.XValue) types.XValue {
array, xerr := types.ToXArray(env, arg1)
if xerr != nil {
return xerr
}
function, xerr := types.ToXFunction(arg2)
if xerr != nil {
return xerr
}

result := make([]types.XValue, 0, array.Count())

for i := 0; i < array.Count(); i++ {
item := array.Get(i)
keep := function.Call(env, []types.XValue{item})
asBool, xerr := types.ToXBoolean(keep)
if xerr != nil {
return xerr

Check warning on line 1716 in excellent/functions/builtin.go

View check run for this annotation

Codecov / codecov/patch

excellent/functions/builtin.go#L1716

Added line #L1716 was not covered by tests
}
if asBool.Native() {
result = append(result, item)
}
}

return types.NewXArray(result...)
}

//------------------------------------------------------------------------------------------
// Encoded Text Functions
//------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -2174,14 +2208,12 @@ func ForEach(env envs.Environment, args ...types.XValue) types.XValue {
if xerr != nil {
return xerr
}

function, isFunction := args[1].(*types.XFunction)
if !isFunction {
return types.NewXErrorf("requires an function as its second argument")
function, xerr := types.ToXFunction(args[1])
if xerr != nil {
return xerr
}

otherArgs := args[2:]

result := make([]types.XValue, array.Count())

for i := 0; i < array.Count(); i++ {
Expand Down Expand Up @@ -2211,14 +2243,12 @@ func ForEachValue(env envs.Environment, args ...types.XValue) types.XValue {
if xerr != nil {
return xerr
}

function, isFunction := args[1].(*types.XFunction)
if !isFunction {
return types.NewXErrorf("requires an function as its second argument")
function, xerr := types.ToXFunction(args[1])
if xerr != nil {
return xerr
}

otherArgs := args[2:]

props := object.Properties()
result := make(map[string]types.XValue, len(props))

Expand Down
4 changes: 4 additions & 0 deletions excellent/functions/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ func TestFunctions(t *testing.T) {
{"field", dmy, []types.XValue{xs("hello"), xs("1"), ERROR}, ERROR},
{"field", dmy, []types.XValue{}, ERROR},

{"filter", dmy, []types.XValue{xa(xi(1), xi(0), xi(2)), xf("boolean")}, xa(xi(1), xi(2))},
{"filter", dmy, []types.XValue{ERROR, xf("boolean")}, ERROR},
{"filter", dmy, []types.XValue{xa(xi(1), xi(0), xi(2)), ERROR}, ERROR},

{"foreach", dmy, []types.XValue{xa(xs("a"), xs("b"), xs("c")), xf("upper")}, xa(xs("A"), xs("B"), xs("C"))},
{"foreach", dmy, []types.XValue{xa(xs("the man"), xs("fox"), xs("jumped up")), xf("word"), xi(0)}, xa(xs("the"), xs("fox"), xs("jumped"))},
{"foreach", dmy, []types.XValue{ERROR, xf("upper")}, ERROR},
Expand Down
9 changes: 9 additions & 0 deletions excellent/types/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,12 @@ func (x *XFunction) Equals(o XValue) bool {
}

var _ XValue = (*XFunction)(nil)

// ToXFunction converts the given value to a function returns an error if that isn't possible
func ToXFunction(x XValue) (*XFunction, XError) {
function, isFunction := x.(*XFunction)
if !isFunction {
return nil, NewXErrorf("%s is not a function", Describe(x))
}
return function, nil
}
29 changes: 29 additions & 0 deletions excellent/types/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,32 @@ func TestXFunction(t *testing.T) {
assert.False(t, types.Equals(anon1, anon1))
assert.False(t, types.Equals(anon1, anon2))
}

func TestToXFunction(t *testing.T) {
foo := types.NewXFunction("foo", func(env envs.Environment, args ...types.XValue) types.XValue { return types.NewXText("foo") })
anon := types.NewXFunction("", func(env envs.Environment, args ...types.XValue) types.XValue { return types.NewXText("bar") })

types.ToXFunction(types.NewXText("hello"))

var tests = []struct {
value types.XValue
asFunc *types.XFunction
hasError bool
}{
{nil, nil, true},
{types.NewXText("hi"), nil, true},
{foo, foo, false},
{anon, anon, false},
}

for _, test := range tests {
fun, err := types.ToXFunction(test.value)

if test.hasError {
assert.Error(t, err, "expected error for input %T{%s}", test.value, test.value)
} else {
assert.NoError(t, err, "unexpected error for input %T{%s}", test.value, test.value)
assert.Equal(t, test.asFunc, fun, "array mismatch for input %T{%s}", test.value, test.value)
}
}
}

0 comments on commit 07a5346

Please sign in to comment.