mirror of
https://github.com/MontFerret/ferret.git
synced 2025-11-23 21:54:45 +02:00
Feature/#9 array functions (#57)
* #9 Added 'APPEND' function * #9 Added 'FIRST' function * #9 Added 'FLATTEN' function * #9 Added 'INTERSECTION' function * #9 Added 'LAST' function * #9 Added 'MINUS' function * #9 Added 'NTH' function * #9 Added 'OUTERSECTION' function * #9 Added 'POP' function * #9 Added 'POSITION' function * #9 Added 'PUSH' function * Fixed nil pointer exception in value parser * #9 Added 'REMOVE_NTH' function * #9 Added 'REMOVE_VALUE' function * #9 Added 'REMOVE_VALUES' function * #9 Added 'REVERSE' function * #9 Added 'SHIFT' function * #9 Added 'SLICE' function * Removed meme * #9 Added 'SORTED' function * #9 Added SORTED_UNIQUE function * #9 Added 'UNION' function * #9 Added 'UNION_DISTINCT' function * #9 Added 'UNIQUE' function * #9 Added 'UNSHIFT' function * #9 Made more strict optional arg validation * #9 Fixed linting errors
This commit is contained in:
74
pkg/stdlib/arrays/append.go
Normal file
74
pkg/stdlib/arrays/append.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Appends a new item to an array and returns a new array with a given element.
|
||||
* If ``uniqueOnly`` is set to true, then will add the item only if it's unique.
|
||||
* @param arr (Array) - Target array.
|
||||
* @param item (Value) - Target value to add.
|
||||
* @returns arr (Array) - New array.
|
||||
*/
|
||||
func Append(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
arg := args[1]
|
||||
unique := values.False
|
||||
|
||||
if len(args) > 2 {
|
||||
err = core.ValidateType(args[2], core.BooleanType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
unique = args[2].(values.Boolean)
|
||||
}
|
||||
|
||||
next := values.NewArray(int(arr.Length()) + 1)
|
||||
|
||||
if !unique {
|
||||
arr.ForEach(func(item core.Value, idx int) bool {
|
||||
next.Push(item)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
next.Push(arg)
|
||||
|
||||
return next, nil
|
||||
}
|
||||
|
||||
hasDuplicate := false
|
||||
|
||||
arr.ForEach(func(item core.Value, idx int) bool {
|
||||
next.Push(item)
|
||||
|
||||
if !hasDuplicate {
|
||||
hasDuplicate = item.Compare(arg) == 0
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if !hasDuplicate {
|
||||
next.Push(arg)
|
||||
}
|
||||
|
||||
return next, nil
|
||||
}
|
||||
50
pkg/stdlib/arrays/append_test.go
Normal file
50
pkg/stdlib/arrays/append_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
Convey("Should return a copy of an array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Append(context.Background(), arr, values.NewInt(6))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldNotEqual, arr)
|
||||
So(out.(collections.Collection).Length(), ShouldBeGreaterThan, arr.Length())
|
||||
})
|
||||
|
||||
Convey("Should ignore non-unique items", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Append(context.Background(), arr, values.NewInt(5), values.True)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldNotEqual, arr)
|
||||
So(out.(collections.Collection).Length(), ShouldEqual, arr.Length())
|
||||
|
||||
out2, err := arrays.Append(context.Background(), arr, values.NewInt(6), values.True)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out2, ShouldNotEqual, arr)
|
||||
So(out2.(collections.Collection).Length(), ShouldBeGreaterThan, arr.Length())
|
||||
})
|
||||
}
|
||||
30
pkg/stdlib/arrays/first.go
Normal file
30
pkg/stdlib/arrays/first.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a first element from a given array.
|
||||
* @param arr (Array) - Target array.
|
||||
* @returns element (Value) - First element in a given array.
|
||||
*/
|
||||
func First(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
return arr.Get(0), nil
|
||||
}
|
||||
35
pkg/stdlib/arrays/first_test.go
Normal file
35
pkg/stdlib/arrays/first_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFirst(t *testing.T) {
|
||||
Convey("Should return a first element form a given array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.First(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Should return NONE if a given array is empty", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
out, err := arrays.First(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldEqual, values.None)
|
||||
})
|
||||
}
|
||||
67
pkg/stdlib/arrays/flatten.go
Normal file
67
pkg/stdlib/arrays/flatten.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Turn an array of arrays into a flat array.
|
||||
* All array elements in array will be expanded in the result array.
|
||||
* Non-array elements are added as they are.
|
||||
* The function will recurse into sub-arrays up to the specified depth.
|
||||
* Duplicates will not be removed.
|
||||
* @param arr (Array) - Target array.
|
||||
* @param depth (Int, optional) - Depth level.
|
||||
* @returns (Array) - Flat array.
|
||||
*/
|
||||
func Flatten(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 2)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
level := 1
|
||||
|
||||
if len(args) > 1 {
|
||||
err = core.ValidateType(args[1], core.IntType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
level = int(args[1].(values.Int))
|
||||
}
|
||||
|
||||
currentLevel := 0
|
||||
result := values.NewArray(int(arr.Length()) * 2)
|
||||
var unwrap func(input *values.Array)
|
||||
|
||||
unwrap = func(input *values.Array) {
|
||||
currentLevel++
|
||||
|
||||
input.ForEach(func(value core.Value, idx int) bool {
|
||||
if value.Type() != core.ArrayType || currentLevel > level {
|
||||
result.Push(value)
|
||||
} else {
|
||||
unwrap(value.(*values.Array))
|
||||
currentLevel--
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
unwrap(arr)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
71
pkg/stdlib/arrays/flatten_test.go
Normal file
71
pkg/stdlib/arrays/flatten_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
Convey("Should flatten an array with depth 1", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
),
|
||||
),
|
||||
values.NewInt(7),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(8),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(9),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
out, err := arrays.Flatten(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,[5,6],7,8,[9,[10]]]")
|
||||
})
|
||||
|
||||
Convey("Should flatten an array with depth more than 1", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
),
|
||||
),
|
||||
values.NewInt(7),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(8),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(9),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
out, err := arrays.Flatten(context.Background(), arr, values.NewInt(2))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,5,6,7,8,9,[10]]")
|
||||
})
|
||||
}
|
||||
70
pkg/stdlib/arrays/intersection.go
Normal file
70
pkg/stdlib/arrays/intersection.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Return the intersection of all arrays specified.
|
||||
* The result is an array of values that occur in all arguments.
|
||||
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
|
||||
* @returns (Array) - A single array with only the elements, which exist in all provided arrays.
|
||||
* The element order is random. Duplicates are removed.
|
||||
*/
|
||||
func Intersection(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return sections(args, len(args))
|
||||
}
|
||||
|
||||
func sections(args []core.Value, count int) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
intersections := make(map[uint64][]core.Value)
|
||||
capacity := len(args)
|
||||
|
||||
for _, i := range args {
|
||||
err := core.ValidateType(i, core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := i.(*values.Array)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
h := value.Hash()
|
||||
|
||||
bucket, exists := intersections[h]
|
||||
|
||||
if !exists {
|
||||
bucket = make([]core.Value, 0, 5)
|
||||
}
|
||||
|
||||
bucket = append(bucket, value)
|
||||
intersections[h] = bucket
|
||||
bucketLen := len(bucket)
|
||||
|
||||
if bucketLen > capacity {
|
||||
capacity = bucketLen
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
result := values.NewArray(capacity)
|
||||
required := count
|
||||
|
||||
for _, bucket := range intersections {
|
||||
if len(bucket) == required {
|
||||
result.Push(bucket[0])
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
102
pkg/stdlib/arrays/intersection_test.go
Normal file
102
pkg/stdlib/arrays/intersection_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
Convey("Should find intersections between 2 arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
values.NewInt(7),
|
||||
values.NewInt(8),
|
||||
values.NewInt(9),
|
||||
)
|
||||
|
||||
out, err := arrays.Intersection(context.Background(), arr1, arr2)
|
||||
|
||||
check := map[int]bool{
|
||||
4: true,
|
||||
5: true,
|
||||
6: true,
|
||||
}
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
arr := out.(*values.Array)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 3)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
_, exists := check[int(value.(values.Int))]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should find intersections between more than 2 arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
arr3 := values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
values.NewInt(7),
|
||||
)
|
||||
|
||||
out, err := arrays.Intersection(context.Background(), arr1, arr2, arr3)
|
||||
|
||||
check := map[int]bool{
|
||||
3: true,
|
||||
4: true,
|
||||
5: true,
|
||||
}
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
arr := out.(*values.Array)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 3)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
_, exists := check[int(value.(values.Int))]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
30
pkg/stdlib/arrays/last.go
Normal file
30
pkg/stdlib/arrays/last.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns the last element of an array.
|
||||
* @param array (Array) - The target array.
|
||||
* @returns (Value) - Last element of an array.
|
||||
*/
|
||||
func Last(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
return arr.Get(arr.Length() - 1), nil
|
||||
}
|
||||
35
pkg/stdlib/arrays/last_test.go
Normal file
35
pkg/stdlib/arrays/last_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLast(t *testing.T) {
|
||||
Convey("Should return a last element form a given array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Last(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldEqual, 5)
|
||||
})
|
||||
|
||||
Convey("Should return NONE if a given array is empty", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
out, err := arrays.Last(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldEqual, values.None)
|
||||
})
|
||||
}
|
||||
31
pkg/stdlib/arrays/lib.go
Normal file
31
pkg/stdlib/arrays/lib.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package arrays
|
||||
|
||||
import "github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
|
||||
func NewLib() map[string]core.Function {
|
||||
return map[string]core.Function{
|
||||
"APPEND": Append,
|
||||
"FIRST": First,
|
||||
"FLATTEN": Flatten,
|
||||
"INTERSECTION": Intersection,
|
||||
"LAST": Last,
|
||||
"MINUS": Minus,
|
||||
"NTH": Nth,
|
||||
"OUTERSECTION": Outersection,
|
||||
"POP": Pop,
|
||||
"POSITION": Position,
|
||||
"PUSH": Push,
|
||||
"REMOVE_NTH": RemoveNth,
|
||||
"REMOVE_VALUE": RemoveValue,
|
||||
"REMOVE_VALUES": RemoveValues,
|
||||
"REVERSE": Reverse,
|
||||
"SHIFT": Shift,
|
||||
"SLICE": Slice,
|
||||
"SORTED": Sorted,
|
||||
"SORTED_UNIQUE": SortedUnique,
|
||||
"UNION": Union,
|
||||
"UNION_DISTINCT": UnionDistinct,
|
||||
"UNIQUE": Unique,
|
||||
"UNSHIFT": Unshift,
|
||||
}
|
||||
}
|
||||
63
pkg/stdlib/arrays/minus.go
Normal file
63
pkg/stdlib/arrays/minus.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Return the difference of all arrays specified.
|
||||
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
|
||||
* @returns array (Array) - An array of values that occur in the first array, but not in any of the subsequent arrays.
|
||||
* The order of the result array is undefined and should not be relied on. Duplicates will be removed.
|
||||
*/
|
||||
func Minus(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
intersections := make(map[uint64]core.Value)
|
||||
capacity := values.NewInt(0)
|
||||
|
||||
for idx, i := range args {
|
||||
err := core.ValidateType(i, core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := i.(*values.Array)
|
||||
|
||||
arr.ForEach(func(value core.Value, _ int) bool {
|
||||
h := value.Hash()
|
||||
|
||||
// first array, fill out the map
|
||||
if idx == 0 {
|
||||
capacity = arr.Length()
|
||||
intersections[h] = value
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
_, exists := intersections[h]
|
||||
|
||||
// if it exists in the first array, remove it
|
||||
if exists {
|
||||
delete(intersections, h)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
result := values.NewArray(int(capacity))
|
||||
|
||||
for _, item := range intersections {
|
||||
result.Push(item)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
94
pkg/stdlib/arrays/minus_test.go
Normal file
94
pkg/stdlib/arrays/minus_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMinus(t *testing.T) {
|
||||
Convey("Should find differences between 2 arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Minus(context.Background(), arr1, arr2)
|
||||
|
||||
check := map[int]bool{
|
||||
1: true,
|
||||
2: true,
|
||||
}
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
arr := out.(*values.Array)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 2)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
_, exists := check[int(value.(values.Int))]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should find differences between more than 2 arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(9),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
arr3 := values.NewArrayWith(
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
values.NewInt(7),
|
||||
values.NewInt(8),
|
||||
)
|
||||
|
||||
out, err := arrays.Minus(context.Background(), arr1, arr2, arr3)
|
||||
|
||||
check := map[int]bool{
|
||||
1: true,
|
||||
2: true,
|
||||
}
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
arr := out.(*values.Array)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 2)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
_, exists := check[int(value.(values.Int))]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
40
pkg/stdlib/arrays/nth.go
Normal file
40
pkg/stdlib/arrays/nth.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns the element of an array at a given position.
|
||||
* It is the same as anyArray[position] for positive positions, but does not support negative positions.
|
||||
* @param array (Array) - An array with elements of arbitrary type.
|
||||
* @param index (Int) - Position of desired element in array, positions start at 0.
|
||||
* @returns (Value) - The array element at the given position.
|
||||
* If position is negative or beyond the upper bound of the array, then NONE will be returned.
|
||||
*/
|
||||
func Nth(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 2)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[1], core.IntType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
idx := args[1].(values.Int)
|
||||
|
||||
return arr.Get(idx), nil
|
||||
}
|
||||
44
pkg/stdlib/arrays/nth_test.go
Normal file
44
pkg/stdlib/arrays/nth_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNth(t *testing.T) {
|
||||
Convey("Should return item by index", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Nth(context.Background(), arr, values.NewInt(1))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.Compare(values.NewInt(2)), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return None when no value", t, func() {
|
||||
arr := values.NewArrayWith()
|
||||
|
||||
out, err := arrays.Nth(context.Background(), arr, values.NewInt(1))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.Compare(values.None), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return None when passed negative value", t, func() {
|
||||
arr := values.NewArrayWith()
|
||||
|
||||
out, err := arrays.Nth(context.Background(), arr, values.NewInt(-1))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.Compare(values.None), ShouldEqual, 0)
|
||||
})
|
||||
}
|
||||
16
pkg/stdlib/arrays/outersection.go
Normal file
16
pkg/stdlib/arrays/outersection.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
/*
|
||||
* Return the values that occur only once across all arrays specified.
|
||||
* @param arrays (Array, repeated) - An arbitrary number of arrays as multiple arguments (at least 2).
|
||||
* @returns (Array) - A single array with only the elements that exist only once across all provided arrays.
|
||||
* The element order is random.
|
||||
*/
|
||||
func Outersection(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return sections(args, 1)
|
||||
}
|
||||
88
pkg/stdlib/arrays/outersection_test.go
Normal file
88
pkg/stdlib/arrays/outersection_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutersection(t *testing.T) {
|
||||
Convey("Should find intersections between 2 arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
out, err := arrays.Outersection(context.Background(), arr1, arr2)
|
||||
|
||||
check := map[int]bool{
|
||||
1: true,
|
||||
4: true,
|
||||
}
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
arr := out.(*values.Array)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 2)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
_, exists := check[int(value.(values.Int))]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should find intersections between more than 2 arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
arr3 := values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Outersection(context.Background(), arr1, arr2, arr3)
|
||||
|
||||
check := map[int]bool{
|
||||
1: true,
|
||||
5: true,
|
||||
}
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
arr := out.(*values.Array)
|
||||
|
||||
So(arr.Length(), ShouldEqual, 2)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
_, exists := check[int(value.(values.Int))]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
44
pkg/stdlib/arrays/pop.go
Normal file
44
pkg/stdlib/arrays/pop.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a new array without last element.
|
||||
* @param array (Array) - Target array.
|
||||
* @returns (Array) - Copy of an array without last element.
|
||||
*/
|
||||
func Pop(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
length := int(arr.Length())
|
||||
result := values.NewArray(length)
|
||||
lastIdx := length - 1
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
if idx == lastIdx {
|
||||
return false
|
||||
}
|
||||
|
||||
result.Push(value)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
35
pkg/stdlib/arrays/pop_test.go
Normal file
35
pkg/stdlib/arrays/pop_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPop(t *testing.T) {
|
||||
Convey("Should return a copy of an array without last element", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Pop(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4]")
|
||||
})
|
||||
|
||||
Convey("Should return empty array if a given one is empty", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
out, err := arrays.Pop(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[]")
|
||||
})
|
||||
}
|
||||
49
pkg/stdlib/arrays/position.go
Normal file
49
pkg/stdlib/arrays/position.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a value indicating whether an element is contained in array. Optionally returns its position.
|
||||
* @param array (Array) - The source array.
|
||||
* @param value (Value) - The target value.
|
||||
* @param returnIndex (Boolean, optional) - Value which indicates whether to return item's position.
|
||||
*/
|
||||
func Position(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
el := args[1]
|
||||
retIdx := false
|
||||
|
||||
if len(args) > 2 {
|
||||
err = core.ValidateType(args[2], core.BooleanType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
retIdx = args[2].Compare(values.True) == 0
|
||||
}
|
||||
|
||||
position := arr.IndexOf(el)
|
||||
|
||||
if !retIdx {
|
||||
return values.NewBoolean(position > -1), nil
|
||||
}
|
||||
|
||||
return position, nil
|
||||
}
|
||||
81
pkg/stdlib/arrays/position_test.go
Normal file
81
pkg/stdlib/arrays/position_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPosition(t *testing.T) {
|
||||
Convey("Should return TRUE when a value exists in a given array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Position(context.Background(), arr, values.NewInt(3))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "true")
|
||||
})
|
||||
|
||||
Convey("Should return FALSE when a value does not exist in a given array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Position(context.Background(), arr, values.NewInt(6))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "false")
|
||||
})
|
||||
|
||||
Convey("Should return index when a value exists in a given array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Position(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(3),
|
||||
values.NewBoolean(true),
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "2")
|
||||
})
|
||||
|
||||
Convey("Should return -1 when a value does not exist in a given array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Position(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(6),
|
||||
values.NewBoolean(true),
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "-1")
|
||||
})
|
||||
}
|
||||
61
pkg/stdlib/arrays/push.go
Normal file
61
pkg/stdlib/arrays/push.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Create a new array with appended value.
|
||||
* @param array (Array) - Source array.
|
||||
* @param value (Value) - Target value.
|
||||
* @param unique (Boolean, optional) - Value indicating whether to do uniqueness check.
|
||||
* @returns (Array) - A new array with appended value.
|
||||
*/
|
||||
func Push(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
value := args[1]
|
||||
uniq := false
|
||||
|
||||
if len(args) > 2 {
|
||||
err = core.ValidateType(args[2], core.BooleanType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
uniq = args[2].Compare(values.True) == 0
|
||||
}
|
||||
|
||||
result := values.NewArray(int(arr.Length() + 1))
|
||||
push := true
|
||||
|
||||
arr.ForEach(func(item core.Value, idx int) bool {
|
||||
if uniq && push {
|
||||
push = !(item.Compare(value) == 0)
|
||||
}
|
||||
|
||||
result.Push(item)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if push {
|
||||
result.Push(value)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
55
pkg/stdlib/arrays/push_test.go
Normal file
55
pkg/stdlib/arrays/push_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPush(t *testing.T) {
|
||||
Convey("Should create a new array with a new element in the end", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Push(context.Background(), arr, values.NewInt(6))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||
})
|
||||
|
||||
Convey("Should not add a new element if not unique when uniqueness check is enabled", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Push(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(6),
|
||||
values.True,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||
|
||||
out2, err := arrays.Push(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(6),
|
||||
values.True,
|
||||
)
|
||||
|
||||
So(out2.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||
})
|
||||
}
|
||||
47
pkg/stdlib/arrays/remove_nth.go
Normal file
47
pkg/stdlib/arrays/remove_nth.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a new array without an element by a given position.
|
||||
* @param array (Array) - Source array.
|
||||
* @param position (Int) - Target element position.
|
||||
* @return (Array) - A new array without an element by a given position.
|
||||
*/
|
||||
func RemoveNth(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 2)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[1], core.IntType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
index := int(args[1].(values.Int))
|
||||
result := values.NewArray(int(arr.Length() - 1))
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
if idx != index {
|
||||
result.Push(value)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
41
pkg/stdlib/arrays/remove_nth_test.go
Normal file
41
pkg/stdlib/arrays/remove_nth_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveNth(t *testing.T) {
|
||||
Convey("Should return a copy of an array without an element by its position", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.RemoveNth(context.Background(), arr, values.NewInt(2))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,4,5]")
|
||||
})
|
||||
|
||||
Convey("Should return a copy of an array with all elements when a position is invalid", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.RemoveNth(context.Background(), arr, values.NewInt(6))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,5]")
|
||||
})
|
||||
}
|
||||
64
pkg/stdlib/arrays/remove_value.go
Normal file
64
pkg/stdlib/arrays/remove_value.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a new array with removed all occurrences of value in a given array.
|
||||
* Optionally with a limit to the number of removals.
|
||||
* @param array (Array) - Source array.
|
||||
* @param value (Value) - Target value.
|
||||
* @param limit (Int, optional) - A limit to the number of removals.
|
||||
* @returns (Array) - A new array with removed all occurrences of value in a given array.
|
||||
*/
|
||||
func RemoveValue(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
value := args[1]
|
||||
limit := -1
|
||||
|
||||
if len(args) > 2 {
|
||||
err = core.ValidateType(args[2], core.IntType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
limit = int(args[2].(values.Int))
|
||||
}
|
||||
|
||||
result := values.NewArray(int(arr.Length()))
|
||||
|
||||
counter := 0
|
||||
arr.ForEach(func(item core.Value, idx int) bool {
|
||||
remove := item.Compare(value) == 0
|
||||
|
||||
if remove {
|
||||
if counter == limit {
|
||||
result.Push(item)
|
||||
}
|
||||
|
||||
counter++
|
||||
} else {
|
||||
result.Push(item)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
48
pkg/stdlib/arrays/remove_value_test.go
Normal file
48
pkg/stdlib/arrays/remove_value_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveValue(t *testing.T) {
|
||||
Convey("Should return a copy of an array without given element(s)", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(3),
|
||||
)
|
||||
|
||||
out, err := arrays.RemoveValue(context.Background(), arr, values.NewInt(3))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,4]")
|
||||
})
|
||||
|
||||
Convey("Should return a copy of an array without given element(s) with limit", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(3),
|
||||
values.NewInt(5),
|
||||
values.NewInt(3),
|
||||
)
|
||||
|
||||
out, err := arrays.RemoveValue(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(3),
|
||||
values.Int(2),
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,4,5,3]")
|
||||
})
|
||||
}
|
||||
59
pkg/stdlib/arrays/remove_values.go
Normal file
59
pkg/stdlib/arrays/remove_values.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a new array with removed all occurrences of values in a given array.
|
||||
* @param array (Array) - Source array.
|
||||
* @param values (Array) - Target values.
|
||||
* @returns (Array) - A new array with removed all occurrences of values in a given array.
|
||||
*/
|
||||
func RemoveValues(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 2)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[1], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
vals := args[1].(*values.Array)
|
||||
|
||||
result := values.NewArray(int(arr.Length()))
|
||||
lookupTable := make(map[uint64]bool)
|
||||
|
||||
vals.ForEach(func(value core.Value, idx int) bool {
|
||||
lookupTable[value.Hash()] = true
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
h := value.Hash()
|
||||
|
||||
_, exists := lookupTable[h]
|
||||
|
||||
if !exists {
|
||||
result.Push(value)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
35
pkg/stdlib/arrays/remove_values_test.go
Normal file
35
pkg/stdlib/arrays/remove_values_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveValues(t *testing.T) {
|
||||
Convey("Should return a copy of an array without given elements", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.RemoveValues(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
),
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,4]")
|
||||
})
|
||||
}
|
||||
36
pkg/stdlib/arrays/reverse.go
Normal file
36
pkg/stdlib/arrays/reverse.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Return a new array with its elements reversed.
|
||||
* @param array (Array) - Target array.
|
||||
* @returns (Array) - A new array with its elements reversed.
|
||||
*/
|
||||
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
size := int(arr.Length())
|
||||
result := values.NewArray(size)
|
||||
|
||||
for i := size - 1; i >= 0; i-- {
|
||||
result.Push(arr.Get(values.NewInt(i)))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
42
pkg/stdlib/arrays/reverse_test.go
Normal file
42
pkg/stdlib/arrays/reverse_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
Convey("Should return a copy of an array with reversed elements", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Reverse(
|
||||
context.Background(),
|
||||
arr,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[6,5,4,3,2,1]")
|
||||
})
|
||||
|
||||
Convey("Should return an empty array when there no elements in a source one", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
out, err := arrays.Reverse(
|
||||
context.Background(),
|
||||
arr,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[]")
|
||||
})
|
||||
}
|
||||
41
pkg/stdlib/arrays/shift.go
Normal file
41
pkg/stdlib/arrays/shift.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a new array without the first element.
|
||||
* @param array (Array) - Target array.
|
||||
* @returns (Array) - Copy of an array without the first element.
|
||||
*/
|
||||
func Shift(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
length := int(arr.Length())
|
||||
result := values.NewArray(length)
|
||||
|
||||
arr.ForEach(func(value core.Value, idx int) bool {
|
||||
if idx != 0 {
|
||||
result.Push(value)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
35
pkg/stdlib/arrays/shift_test.go
Normal file
35
pkg/stdlib/arrays/shift_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShift(t *testing.T) {
|
||||
Convey("Should return a copy of an array without the first element", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Shift(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[2,3,4,5]")
|
||||
})
|
||||
|
||||
Convey("Should return empty array if a given one is empty", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
out, err := arrays.Shift(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[]")
|
||||
})
|
||||
}
|
||||
50
pkg/stdlib/arrays/slice.go
Normal file
50
pkg/stdlib/arrays/slice.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns a new sliced array.
|
||||
* @param array (Array) - Source array.
|
||||
* @param start (Int) - Start position of extraction.
|
||||
* @param length (Int, optional) - Value indicating how many elements to extract.
|
||||
* @returns (Array) - Sliced array.
|
||||
*/
|
||||
func Slice(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[1], core.IntType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
start := args[1].(values.Int)
|
||||
length := values.NewInt(int(arr.Length()))
|
||||
|
||||
if len(args) > 2 {
|
||||
if args[2].Type() == core.IntType {
|
||||
arg2 := args[2].(values.Int)
|
||||
|
||||
if arg2 > 0 {
|
||||
length = start + args[2].(values.Int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arr.Slice(start, length), nil
|
||||
}
|
||||
80
pkg/stdlib/arrays/slice_test.go
Normal file
80
pkg/stdlib/arrays/slice_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
Convey("Should return a sliced array with a given start position ", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Slice(context.Background(), arr, values.NewInt(3))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[4,5,6]")
|
||||
})
|
||||
|
||||
Convey("Should return an empty array when start position is out of bounds", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Slice(context.Background(), arr, values.NewInt(6))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[]")
|
||||
})
|
||||
|
||||
Convey("Should return a sliced array with a given start position and length", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Slice(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(2),
|
||||
values.NewInt(2),
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[3,4]")
|
||||
})
|
||||
|
||||
Convey("Should return an empty array when length is out of bounds", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Slice(context.Background(), arr, values.NewInt(2), values.NewInt(10))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[3,4,5,6]")
|
||||
})
|
||||
}
|
||||
53
pkg/stdlib/arrays/sorted.go
Normal file
53
pkg/stdlib/arrays/sorted.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Sorts all elements in anyArray.
|
||||
* The function will use the default comparison order for FQL value types.
|
||||
* @param array (Array) - Target array.
|
||||
* @returns (Array) - Sorted array.
|
||||
*/
|
||||
func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
if arr.Length() == 0 {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
}, collections.SortDirectionAsc)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
iterator, err := collections.NewSortIterator(
|
||||
collections.NewArrayIterator(arr),
|
||||
sorter,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return collections.ToArray(iterator)
|
||||
}
|
||||
52
pkg/stdlib/arrays/sorted_test.go
Normal file
52
pkg/stdlib/arrays/sorted_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSorted(t *testing.T) {
|
||||
Convey("Should sort numbers", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(1),
|
||||
values.NewInt(6),
|
||||
values.NewInt(2),
|
||||
values.NewInt(5),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
out, err := arrays.Sorted(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||
})
|
||||
|
||||
Convey("Should sort strings", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewString("b"),
|
||||
values.NewString("c"),
|
||||
values.NewString("a"),
|
||||
values.NewString("d"),
|
||||
values.NewString("e"),
|
||||
values.NewString("f"),
|
||||
)
|
||||
|
||||
out, err := arrays.Sorted(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
||||
})
|
||||
|
||||
Convey("Should return empty array", t, func() {
|
||||
arr := values.NewArrayWith()
|
||||
|
||||
out, err := arrays.Sorted(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, `[]`)
|
||||
})
|
||||
}
|
||||
60
pkg/stdlib/arrays/sorted_unique.go
Normal file
60
pkg/stdlib/arrays/sorted_unique.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Sorts all elements in anyArray.
|
||||
* The function will use the default comparison order for FQL value types.
|
||||
* Additionally, the values in the result array will be made unique
|
||||
* @param array (Array) - Target array.
|
||||
* @returns (Array) - Sorted array.
|
||||
*/
|
||||
func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
if arr.Length() == 0 {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
}, collections.SortDirectionAsc)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
uniqIterator, err := collections.NewUniqueIterator(collections.NewArrayIterator(arr))
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
iterator, err := collections.NewSortIterator(
|
||||
uniqIterator,
|
||||
sorter,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return collections.ToArray(iterator)
|
||||
}
|
||||
60
pkg/stdlib/arrays/sorted_unique_test.go
Normal file
60
pkg/stdlib/arrays/sorted_unique_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSortedUnique(t *testing.T) {
|
||||
Convey("Should sort numbers", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(1),
|
||||
values.NewInt(6),
|
||||
values.NewInt(2),
|
||||
values.NewInt(6),
|
||||
values.NewInt(5),
|
||||
values.NewInt(1),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
out, err := arrays.SortedUnique(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, "[1,2,3,4,5,6]")
|
||||
})
|
||||
|
||||
Convey("Should sort strings", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewString("e"),
|
||||
values.NewString("b"),
|
||||
values.NewString("a"),
|
||||
values.NewString("c"),
|
||||
values.NewString("a"),
|
||||
values.NewString("d"),
|
||||
values.NewString("f"),
|
||||
values.NewString("d"),
|
||||
values.NewString("e"),
|
||||
values.NewString("f"),
|
||||
)
|
||||
|
||||
out, err := arrays.SortedUnique(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
||||
})
|
||||
|
||||
Convey("Should return empty array", t, func() {
|
||||
arr := values.NewArrayWith()
|
||||
|
||||
out, err := arrays.SortedUnique(context.Background(), arr)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, `[]`)
|
||||
})
|
||||
}
|
||||
46
pkg/stdlib/arrays/union.go
Normal file
46
pkg/stdlib/arrays/union.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns the union of all passed arrays.
|
||||
* @param arrays (Array, repeated) - List of arrays to combine.
|
||||
* @returns (Array) - All array elements combined in a single array, in any order.
|
||||
*/
|
||||
func Union(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
firstArrLen := args[0].(*values.Array).Length()
|
||||
result := values.NewArray(len(args) * int(firstArrLen))
|
||||
|
||||
for _, arg := range args {
|
||||
err := core.ValidateType(arg, core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := arg.(*values.Array)
|
||||
|
||||
arr.ForEach(func(value core.Value, _ int) bool {
|
||||
result.Push(value)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
50
pkg/stdlib/arrays/union_distinct.go
Normal file
50
pkg/stdlib/arrays/union_distinct.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
func UnionDistinct(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
firstArrLen := args[0].(*values.Array).Length()
|
||||
result := values.NewArray(len(args) * int(firstArrLen))
|
||||
hashes := make(map[uint64]bool)
|
||||
|
||||
for _, arg := range args {
|
||||
err := core.ValidateType(arg, core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := arg.(*values.Array)
|
||||
|
||||
arr.ForEach(func(value core.Value, _ int) bool {
|
||||
h := value.Hash()
|
||||
|
||||
_, exists := hashes[h]
|
||||
|
||||
if !exists {
|
||||
hashes[h] = true
|
||||
result.Push(value)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
51
pkg/stdlib/arrays/union_distinct_test.go
Normal file
51
pkg/stdlib/arrays/union_distinct_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnionDistinct(t *testing.T) {
|
||||
Convey("Should union all arrays with unique values", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
)
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewInt(5),
|
||||
values.NewInt(2),
|
||||
values.NewInt(6),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
arr3 := values.NewArrayWith(
|
||||
values.NewString("a"),
|
||||
values.NewString("b"),
|
||||
values.NewString("c"),
|
||||
values.NewString("d"),
|
||||
)
|
||||
|
||||
arr4 := values.NewArrayWith(
|
||||
values.NewString("e"),
|
||||
values.NewString("b"),
|
||||
values.NewString("f"),
|
||||
values.NewString("d"),
|
||||
)
|
||||
|
||||
out, err := arrays.UnionDistinct(
|
||||
context.Background(),
|
||||
arr1,
|
||||
arr2,
|
||||
arr3,
|
||||
arr4,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, `[1,2,3,4,5,6,"a","b","c","d","e","f"]`)
|
||||
})
|
||||
}
|
||||
53
pkg/stdlib/arrays/union_test.go
Normal file
53
pkg/stdlib/arrays/union_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns the union of distinct values of all passed arrays.
|
||||
* @param arrays (Array, repeated) - List of arrays to combine.
|
||||
* @returns (Array) - All array elements combined in a single array, without duplicates, in any order.
|
||||
*/
|
||||
func TestUnion(t *testing.T) {
|
||||
Convey("Should union all arrays", t, func() {
|
||||
arr1 := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
)
|
||||
|
||||
arr2 := values.NewArrayWith(
|
||||
values.NewString("a"),
|
||||
values.NewString("b"),
|
||||
values.NewString("c"),
|
||||
values.NewString("d"),
|
||||
)
|
||||
|
||||
arr3 := values.NewArrayWith(
|
||||
values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
),
|
||||
values.NewArrayWith(
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
),
|
||||
)
|
||||
|
||||
out, err := arrays.Union(
|
||||
context.Background(),
|
||||
arr1,
|
||||
arr2,
|
||||
arr3,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out.String(), ShouldEqual, `[1,2,3,4,"a","b","c","d",[1,2],[3,4]]`)
|
||||
})
|
||||
}
|
||||
43
pkg/stdlib/arrays/unique.go
Normal file
43
pkg/stdlib/arrays/unique.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Returns all unique elements from a given array.
|
||||
* @param array (Array) - Target array.
|
||||
* @returns (Array) - New array without duplicates.
|
||||
*/
|
||||
func Unique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
|
||||
if arr.Length() == 0 {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
iterator, err := collections.NewUniqueIterator(
|
||||
collections.NewArrayIterator(arr),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return collections.ToArray(iterator)
|
||||
}
|
||||
35
pkg/stdlib/arrays/unique_test.go
Normal file
35
pkg/stdlib/arrays/unique_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnique(t *testing.T) {
|
||||
Convey("Should return only unique items", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(3),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
res, err := arrays.Unique(
|
||||
context.Background(),
|
||||
arr,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
|
||||
})
|
||||
}
|
||||
80
pkg/stdlib/arrays/unshift.go
Normal file
80
pkg/stdlib/arrays/unshift.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
/*
|
||||
* Prepends value to a given array.
|
||||
* @param array (Array) - Target array.
|
||||
* @param value (Value) - Target value to prepend.
|
||||
* @param unique (Boolean, optional) - Optional value indicating whether a value must be unique to be prepended.
|
||||
* Default is false.
|
||||
* @returns (Array) - New array with prepended value.
|
||||
*/
|
||||
func Unshift(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], core.ArrayType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
value := args[1]
|
||||
uniq := values.False
|
||||
|
||||
if len(args) > 2 {
|
||||
err = core.ValidateType(args[2], core.BooleanType)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
uniq = args[2].(values.Boolean)
|
||||
}
|
||||
|
||||
result := values.NewArray(int(arr.Length() + 1))
|
||||
|
||||
if !uniq {
|
||||
result.Push(value)
|
||||
|
||||
arr.ForEach(func(el core.Value, _ int) bool {
|
||||
result.Push(el)
|
||||
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
ok := true
|
||||
|
||||
// let's just hope it's unique
|
||||
// if not, we will terminate the loop and return a copy of an array
|
||||
result.Push(value)
|
||||
|
||||
arr.ForEach(func(el core.Value, idx int) bool {
|
||||
if el.Compare(value) != 0 {
|
||||
result.Push(el)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// not unique
|
||||
ok = false
|
||||
return false
|
||||
})
|
||||
|
||||
if !ok {
|
||||
// value is not unique, just return a new copy with same elements
|
||||
return arr.Clone(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
59
pkg/stdlib/arrays/unshift_test.go
Normal file
59
pkg/stdlib/arrays/unshift_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package arrays_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnshift(t *testing.T) {
|
||||
Convey("Should return a copy of an array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Unshift(context.Background(), arr, values.NewInt(0))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldNotEqual, arr)
|
||||
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
|
||||
})
|
||||
|
||||
Convey("Should ignore non-unique items", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
out, err := arrays.Unshift(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(0),
|
||||
values.True,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldNotEqual, arr)
|
||||
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
|
||||
|
||||
out2, err := arrays.Unshift(
|
||||
context.Background(),
|
||||
arr,
|
||||
values.NewInt(0),
|
||||
values.True,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out2, ShouldNotEqual, arr)
|
||||
So(out.String(), ShouldEqual, "[0,1,2,3,4,5]")
|
||||
})
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package stdlib
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/collections"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/html"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/strings"
|
||||
@@ -21,6 +22,7 @@ func NewLib() map[string]core.Function {
|
||||
add(types.NewLib())
|
||||
add(strings.NewLib())
|
||||
add(collections.NewLib())
|
||||
add(arrays.NewLib())
|
||||
add(html.NewLib())
|
||||
add(utils.NewLib())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user