1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-15 20:02:56 +02:00

Fixed sorting

This commit is contained in:
Tim Voronov
2025-05-25 21:22:00 -04:00
parent 1a9e447f29
commit 294f04d814
36 changed files with 1039 additions and 532 deletions

View File

@@ -1334,7 +1334,7 @@ func (v *visitor) emitLoopEnd(loop *Loop) vm.Operand {
if loop.Allocate {
// TODO: Reuse the dsReg register
v.emitter.EmitA(vm.OpClose, loop.Iterator)
v.emitter.EmitAB(vm.OpDataSetToList, dst, loop.Result)
v.emitter.EmitAB(vm.OpMove, dst, loop.Result)
if loop.Kind == ForLoop {
v.emitter.PatchJump(loop.Jump)

View File

@@ -12,27 +12,27 @@ type Array struct {
data []Value
}
func EmptyArray() List {
func EmptyArray() *Array {
return &Array{data: make([]Value, 0)}
}
func NewArray(cap int) List {
func NewArray(cap int) *Array {
return &Array{data: make([]Value, 0, cap)}
}
func NewArray64(cap Int) List {
func NewArray64(cap Int) *Array {
return &Array{data: make([]Value, 0, cap)}
}
func NewSizedArray(size int) List {
func NewSizedArray(size int) *Array {
return &Array{data: make([]Value, size)}
}
func NewArrayWith(values ...Value) List {
func NewArrayWith(values ...Value) *Array {
return &Array{data: values}
}
func NewArrayOf(values []Value) List {
func NewArrayOf(values []Value) *Array {
return &Array{data: values}
}
@@ -128,8 +128,8 @@ func (t *Array) Copy() Value {
return &Array{data: t.copyInternal(0)}
}
func (t *Array) CopyWithCap(_ context.Context, cap Int) (List, error) {
return &Array{data: t.copyInternal(cap)}, nil
func (t *Array) CopyWithGrowth(cap Int) *Array {
return &Array{data: t.copyInternal(cap)}
}
func (t *Array) copyInternal(cap Int) []Value {

View File

@@ -24,7 +24,7 @@ func TestArrayIterator(t *testing.T) {
Convey("One value", t, func() {
ctx := context.Background()
arr := runtime.NewArray(1)
arr.Add(ctx, NewInt(1))
arr.Add(ctx, runtime.NewInt(1))
iter := runtime.NewArrayIterator(arr)
hasNext, err := iter.HasNext(ctx)
@@ -35,8 +35,8 @@ func TestArrayIterator(t *testing.T) {
val, key, err := iter.Next(ctx)
So(err, ShouldBeNil)
So(val, ShouldEqual, NewInt(1))
So(key, ShouldEqual, NewInt(0))
So(val, ShouldEqual, runtime.NewInt(1))
So(key, ShouldEqual, runtime.NewInt(0))
hasNext, err = iter.HasNext(ctx)
@@ -47,14 +47,14 @@ func TestArrayIterator(t *testing.T) {
Convey("Multiple values", t, func() {
ctx := context.Background()
arr := runtime.NewArray(5)
arr.Push(NewInt(1))
arr.Push(NewInt(2))
arr.Push(NewInt(3))
arr.Push(NewInt(4))
arr.Push(NewInt(5))
arr.Add(ctx, runtime.NewInt(1))
arr.Add(ctx, runtime.NewInt(2))
arr.Add(ctx, runtime.NewInt(3))
arr.Add(ctx, runtime.NewInt(4))
arr.Add(ctx, runtime.NewInt(5))
iter := runtime.NewArrayIterator(arr)
actual := make([]Int, 0, 5)
actual := make([]runtime.Int, 0, 5)
for {
hasNext, err := iter.HasNext(ctx)
@@ -62,23 +62,22 @@ func TestArrayIterator(t *testing.T) {
break
}
val, _, err := iter.Next(ctx)
actual = append(actual, val.(Int))
actual = append(actual, val.(runtime.Int))
}
So(actual, ShouldResemble, []Int{1, 2, 3, 4, 5})
So(actual, ShouldResemble, []runtime.Int{1, 2, 3, 4, 5})
})
}
func BenchmarkArrayIterator(b *testing.B) {
size := 100
arr := runtime.NewArray(size)
ctx := context.Background()
for i := 0; i < size; i++ {
arr.Push(NewInt(i))
arr.Add(ctx, runtime.NewInt(i))
}
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {

View File

@@ -4,27 +4,32 @@ import (
"encoding/json"
"testing"
c "context"
"github.com/MontFerret/ferret/pkg/runtime"
. "github.com/smartystreets/goconvey/convey"
)
func TestArray(t *testing.T) {
ctx := c.Background()
Convey("#constructor", t, func() {
Convey("Should create an empty array", func() {
arr := runtime.NewArray(10)
size, _ := arr.Length(ctx)
So(arr.Length(), ShouldEqual, 0)
So(size, ShouldEqual, 0)
})
Convey("Should create an array, from passed values", func() {
arr := runtime.NewArrayWith(
NewInt(1),
NewInt(2),
NewInt(3),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
)
size, _ := arr.Length(ctx)
So(arr.Length(), ShouldEqual, 3)
So(size, ShouldEqual, 3)
})
})
@@ -40,9 +45,9 @@ func TestArray(t *testing.T) {
Convey("Should serialize full array", func() {
arr := runtime.NewArrayWith(
NewInt(1),
NewInt(2),
NewInt(3),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
)
marshaled, err := json.Marshal(arr)
@@ -55,8 +60,8 @@ func TestArray(t *testing.T) {
Convey(".Unwrap", t, func() {
Convey("Should return a an array of unwrapped values", func() {
arr := runtime.NewArrayWith(
ZeroInt,
ZeroInt,
runtime.ZeroInt,
runtime.ZeroInt,
)
for _, val := range arr.Unwrap().([]interface{}) {
@@ -67,24 +72,24 @@ func TestArray(t *testing.T) {
Convey(".String", t, func() {
Convey("Should return a string representation ", func() {
arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
arr := runtime.NewArrayWith(runtime.ZeroInt, runtime.ZeroInt)
So(arr.String(), ShouldEqual, "[0,0]")
})
})
Convey(".CompareValues", t, func() {
Convey(".Compareruntime.Values", t, func() {
Convey("It should return 1 for all non-array and non-object values", func() {
arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
arr := runtime.NewArrayWith(runtime.ZeroInt, runtime.ZeroInt)
So(arr.Compare(None), ShouldEqual, 1)
So(arr.Compare(ZeroInt), ShouldEqual, 1)
So(arr.Compare(ZeroFloat), ShouldEqual, 1)
So(arr.Compare(EmptyString), ShouldEqual, 1)
So(arr.Compare(runtime.None), ShouldEqual, 1)
So(arr.Compare(runtime.ZeroInt), ShouldEqual, 1)
So(arr.Compare(runtime.ZeroFloat), ShouldEqual, 1)
So(arr.Compare(runtime.EmptyString), ShouldEqual, 1)
})
Convey("It should return -1 for all object values", func() {
arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
arr := runtime.NewArrayWith(runtime.ZeroInt, runtime.ZeroInt)
obj := runtime.NewObject()
So(arr.Compare(obj), ShouldEqual, -1)
@@ -98,15 +103,15 @@ func TestArray(t *testing.T) {
})
Convey("It should return 1 when other array is empty", func() {
arr1 := runtime.NewArrayWith(ZeroFloat)
arr1 := runtime.NewArrayWith(runtime.ZeroFloat)
arr2 := runtime.NewArray(1)
So(arr1.Compare(arr2), ShouldEqual, 1)
})
Convey("It should return 1 when values are bigger", func() {
arr1 := runtime.NewArrayWith(NewInt(1))
arr2 := runtime.NewArrayWith(ZeroInt)
arr1 := runtime.NewArrayWith(runtime.NewInt(1))
arr2 := runtime.NewArrayWith(runtime.ZeroInt)
So(arr1.Compare(arr2), ShouldEqual, 1)
})
@@ -114,10 +119,10 @@ func TestArray(t *testing.T) {
Convey("It should return 0 when arrays are equal", func() {
Convey("When only simple types are nested", func() {
arr1 := runtime.NewArrayWith(
NewInt(0), NewString("str"),
runtime.NewInt(0), runtime.NewString("str"),
)
arr2 := runtime.NewArrayWith(
NewInt(0), NewString("str"),
runtime.NewInt(0), runtime.NewString("str"),
)
So(arr1.Compare(arr2), ShouldEqual, 0)
@@ -126,18 +131,18 @@ func TestArray(t *testing.T) {
Convey("When object and array are nested at the same time", func() {
arr1 := runtime.NewArrayWith(
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
arr2 := runtime.NewArrayWith(
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
@@ -147,12 +152,12 @@ func TestArray(t *testing.T) {
Convey("When only objects are nested", func() {
arr1 := runtime.NewArrayWith(
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
)
arr2 := runtime.NewArrayWith(
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
)
@@ -162,12 +167,12 @@ func TestArray(t *testing.T) {
Convey("When only arrays are nested", func() {
arr1 := runtime.NewArrayWith(
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
arr2 := runtime.NewArrayWith(
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
@@ -176,21 +181,21 @@ func TestArray(t *testing.T) {
Convey("When simple and complex types at the same time", func() {
arr1 := runtime.NewArrayWith(
NewInt(0),
runtime.NewInt(0),
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
arr2 := runtime.NewArrayWith(
NewInt(0),
runtime.NewInt(0),
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
@@ -221,9 +226,9 @@ func TestArray(t *testing.T) {
Convey(".Hash", t, func() {
Convey("It should calculate hash of non-empty array", func() {
arr := runtime.NewArrayWith(
NewInt(1),
NewInt(2),
NewInt(3),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
)
h := arr.Hash()
@@ -241,13 +246,13 @@ func TestArray(t *testing.T) {
Convey("Hash sum should be consistent", func() {
arr := runtime.NewArrayWith(
True,
NewInt(1),
NewFloat(1.1),
NewString("foobar"),
NewCurrentDateTime(),
runtime.NewArrayWith(NewInt(1), True),
runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewString("bar"))),
runtime.True,
runtime.NewInt(1),
runtime.NewFloat(1.1),
runtime.NewString("foobar"),
runtime.NewCurrentDateTime(),
runtime.NewArrayWith(runtime.NewInt(1), runtime.True),
runtime.NewObjectWith(runtime.NewObjectProperty("foo", runtime.NewString("bar"))),
)
h1 := arr.Hash()
@@ -261,62 +266,70 @@ func TestArray(t *testing.T) {
Convey("Should return 0 when empty", func() {
arr := runtime.NewArray(1)
So(arr.Length(), ShouldEqual, 0)
size, _ := arr.Length(ctx)
So(size, ShouldEqual, 0)
})
Convey("Should return greater than 0 when not empty", func() {
arr := runtime.NewArrayWith(ZeroInt, ZeroInt)
arr := runtime.NewArrayWith(runtime.ZeroInt, runtime.ZeroInt)
So(arr.Length(), ShouldEqual, 2)
size, _ := arr.Length(ctx)
So(size, ShouldEqual, 2)
})
})
Convey(".ForEach", t, func() {
Convey(".ForEachIter", t, func() {
Convey("Should iterate over elements", func() {
arr := runtime.NewArrayWith(
NewInt(1),
NewInt(2),
NewInt(3),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
)
counter := 0
arr.ForEach(func(value Value, idx int) bool {
_ = arr.ForEach(ctx, func(_ c.Context, val runtime.Value, idx runtime.Int) (runtime.Boolean, error) {
counter++
return true
return true, nil
})
So(counter, ShouldEqual, arr.Length())
size, _ := arr.Length(ctx)
So(counter, ShouldEqual, size)
})
Convey("Should not iterate when empty", func() {
arr := runtime.NewArrayWith()
counter := 0
arr.ForEach(func(value Value, idx int) bool {
_ = arr.ForEach(ctx, func(_ c.Context, val runtime.Value, idx runtime.Int) (runtime.Boolean, error) {
counter++
return true
return true, nil
})
So(counter, ShouldEqual, arr.Length())
size, _ := arr.Length(ctx)
So(counter, ShouldEqual, size)
})
//Convey("Should break iteration when false returned", func() {
// arr := values.NewArrayWith(
// values.NewInt(1),
// values.NewInt(2),
// values.NewInt(3),
// values.NewInt(4),
// values.NewInt(5),
// values.runtime.NewInt(1),
// values.runtime.NewInt(2),
// values.runtime.NewInt(3),
// values.runtime.NewInt(4),
// values.runtime.NewInt(5),
// )
// threshold := 3
// counter := 0
//
// arr.ForEach(func(value core.Value, idx int) bool {
// arr.ForEachIter(func(value core.runtime.Value, idx int) bool {
// counter++
//
// return value.CompareValues(values.NewInt(threshold)) == -1
// return value.Compareruntime.Values(values.runtime.NewInt(threshold)) == -1
// })
//
// So(counter, ShouldEqual, threshold)
@@ -326,32 +339,32 @@ func TestArray(t *testing.T) {
//Convey(".Get", t, func() {
// Convey("Should return item by index", func() {
// arr := values.NewArrayWith(
// values.NewInt(1),
// values.NewInt(2),
// values.NewInt(3),
// values.NewInt(4),
// values.NewInt(5),
// values.runtime.NewInt(1),
// values.runtime.NewInt(2),
// values.runtime.NewInt(3),
// values.runtime.NewInt(4),
// values.runtime.NewInt(5),
// )
//
// el := arr.Get(1)
//
// So(el.CompareValues(values.NewInt(2)), ShouldEqual, 0)
// So(el.Compareruntime.Values(values.runtime.NewInt(2)), ShouldEqual, 0)
// })
//
// Convey("Should return None when no items", func() {
// Convey("Should return runtime.None when no items", func() {
// arr := values.NewArrayWith()
//
// el := arr.Get(1)
//
// So(el.CompareValues(values.None), ShouldEqual, 0)
// So(el.Compareruntime.Values(values.runtime.None), ShouldEqual, 0)
// })
//})
Convey(".Set", t, func() {
//Convey("Should set item by index", func() {
// arr := values.NewArrayWith(values.ZeroInt)
// arr := values.NewArrayWith(values.runtime.ZeroInt)
//
// err := arr.Set(0, values.NewInt(1))
// err := arr.Set(0, values.runtime.NewInt(1))
//
// So(err, ShouldBeNil)
// So(arr.Length(), ShouldEqual, 1)
@@ -361,10 +374,12 @@ func TestArray(t *testing.T) {
Convey("Should return an error when index is out of bounds", func() {
arr := runtime.NewArray(10)
err := arr.Set(0, NewInt(1))
err := arr.Set(ctx, 0, runtime.NewInt(1))
So(err, ShouldNotBeNil)
So(arr.Length(), ShouldEqual, 0)
size, _ := arr.Length(ctx)
So(size, ShouldEqual, 0)
})
})
@@ -372,184 +387,202 @@ func TestArray(t *testing.T) {
Convey("Should add an item", func() {
arr := runtime.NewArray(10)
src := []Value{
ZeroInt,
ZeroInt,
ZeroInt,
ZeroInt,
ZeroInt,
src := []runtime.Value{
runtime.ZeroInt,
runtime.ZeroInt,
runtime.ZeroInt,
runtime.ZeroInt,
runtime.ZeroInt,
}
for _, val := range src {
arr.Push(val)
arr.Add(ctx, val)
}
So(arr.Length(), ShouldEqual, len(src))
size, _ := arr.Length(ctx)
So(size, ShouldEqual, len(src))
})
})
//Convey(".Slice", t, func() {
// Convey("Should return a slice", func() {
// arr := values.NewArrayWith(
// values.NewInt(0),
// values.NewInt(1),
// values.NewInt(2),
// values.NewInt(3),
// values.NewInt(4),
// values.NewInt(5),
// values.runtime.NewInt(0),
// values.runtime.NewInt(1),
// values.runtime.NewInt(2),
// values.runtime.NewInt(3),
// values.runtime.NewInt(4),
// values.runtime.NewInt(5),
// )
//
// s := arr.Slice(0, 1)
//
// So(s.Length(), ShouldEqual, 1)
// So(s.Get(0).CompareValues(values.ZeroInt), ShouldEqual, 0)
// So(s.Length(ctx), ShouldEqual, 1)
// So(s.Get(0).Compareruntime.Values(values.runtime.ZeroInt), ShouldEqual, 0)
//
// s2 := arr.Slice(2, arr.Length())
// s2 := arr.Slice(2, arr.Length(ctx))
//
// So(s2.Length(), ShouldEqual, arr.Length()-2)
// So(s2.Length(ctx), ShouldEqual, arr.Length(ctx)-2)
// })
//})
Convey(".Insert", t, func() {
Convey("Should insert an item in the middle of an array", func() {
arr := runtime.NewArrayWith(
NewInt(0),
NewInt(1),
NewInt(2),
NewInt(3),
NewInt(4),
NewInt(5),
runtime.NewInt(0),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
runtime.NewInt(4),
runtime.NewInt(5),
)
lenBefore := arr.Length()
lenBefore, _ := arr.Length(ctx)
arr.Insert(3, NewInt(100))
arr.Insert(ctx, 3, runtime.NewInt(100))
lenAfter := arr.Length()
lenAfter, _ := arr.Length(ctx)
act, _ := arr.Get(ctx, 3)
So(lenAfter, ShouldBeGreaterThan, lenBefore)
So(arr.Get(3), ShouldEqual, 100)
So(act, ShouldEqual, 100)
})
})
Convey(".RemoveAt", t, func() {
Convey("Should remove an item from the middle", func() {
arr := runtime.NewArrayWith(
NewInt(0),
NewInt(1),
NewInt(2),
NewInt(3),
NewInt(4),
NewInt(5),
runtime.NewInt(0),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
runtime.NewInt(4),
runtime.NewInt(5),
)
lenBefore := arr.Length()
lenBefore, _ := arr.Length(ctx)
arr.RemoveAt(3)
arr.RemoveAt(ctx, 3)
lenAfter := arr.Length()
lenAfter, _ := arr.Length(ctx)
val, _ := arr.Get(ctx, 3)
So(lenAfter, ShouldBeLessThan, lenBefore)
So(arr.Get(3), ShouldEqual, 4)
So(val, ShouldEqual, 4)
})
Convey("Should remove an item from the end", func() {
arr := runtime.NewArrayWith(
NewInt(0),
NewInt(1),
NewInt(2),
NewInt(3),
NewInt(4),
NewInt(5),
runtime.NewInt(0),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
runtime.NewInt(4),
runtime.NewInt(5),
)
lenBefore := arr.Length()
lenBefore, _ := arr.Length(ctx)
arr.RemoveAt(5)
arr.RemoveAt(ctx, 5)
lenAfter := arr.Length()
lenAfter, _ := arr.Length(ctx)
val, _ := arr.Get(ctx, 4)
So(lenAfter, ShouldBeLessThan, lenBefore)
So(lenAfter, ShouldEqual, 5)
So(arr.Get(4), ShouldEqual, 4)
So(val, ShouldEqual, 4)
})
Convey("Should remove an item from the beginning", func() {
arr := runtime.NewArrayWith(
NewInt(0),
NewInt(1),
NewInt(2),
NewInt(3),
NewInt(4),
NewInt(5),
runtime.NewInt(0),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
runtime.NewInt(4),
runtime.NewInt(5),
)
lenBefore := arr.Length()
lenBefore, _ := arr.Length(ctx)
arr.RemoveAt(0)
arr.RemoveAt(ctx, 0)
lenAfter := arr.Length()
lenAfter, _ := arr.Length(ctx)
val, _ := arr.Get(ctx, 0)
So(lenAfter, ShouldBeLessThan, lenBefore)
So(arr.Get(0), ShouldEqual, 1)
So(val, ShouldEqual, 1)
})
})
Convey(".Clone", t, func() {
Convey("Cloned array should be equal to source array", func() {
arr := runtime.NewArrayWith(
NewInt(0),
runtime.NewInt(0),
runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("one", runtime.NewInt(1)),
),
runtime.NewArrayWith(
NewInt(2),
runtime.NewInt(2),
),
)
clone := arr.Clone().(*runtime.Array)
cloned, _ := arr.Clone(ctx)
clone := cloned.(*runtime.Array)
So(arr.Length(), ShouldEqual, clone.Length())
size, _ := arr.Length(ctx)
cloneSize, _ := clone.Length(ctx)
So(size, ShouldEqual, cloneSize)
So(arr.Compare(clone), ShouldEqual, 0)
})
Convey("Cloned array should be independent of the source array", func() {
arr := runtime.NewArrayWith(
NewInt(0),
NewInt(1),
NewInt(2),
NewInt(3),
NewInt(4),
NewInt(5),
runtime.NewInt(0),
runtime.NewInt(1),
runtime.NewInt(2),
runtime.NewInt(3),
runtime.NewInt(4),
runtime.NewInt(5),
)
clone := arr.Clone().(*runtime.Array)
cloned, _ := arr.Clone(ctx)
clone := cloned.(*runtime.Array)
arr.Push(NewInt(6))
arr.Add(ctx, runtime.NewInt(6))
So(arr.Length(), ShouldNotEqual, clone.Length())
size, _ := arr.Length(ctx)
cloneSize, _ := clone.Length(ctx)
So(size, ShouldEqual, cloneSize)
So(arr.Compare(clone), ShouldNotEqual, 0)
})
//Convey("Cloned array must contain copies of the nested objects", func() {
// arr := values.NewArrayWith(
// values.NewArrayWith(
// values.NewInt(0),
// values.NewInt(1),
// values.NewInt(2),
// values.NewInt(3),
// values.NewInt(4),
// values.runtime.NewInt(0),
// values.runtime.NewInt(1),
// values.runtime.NewInt(2),
// values.runtime.NewInt(3),
// values.runtime.NewInt(4),
// ),
// )
//
// clone := arr.Clone().(*values.Array)
//
// nestedInArr := arr.Get(values.NewInt(0)).(*values.Array)
// nestedInArr.Push(values.NewInt(5))
// nestedInArr := arr.Get(values.runtime.NewInt(0)).(*values.Array)
// nestedInArr.Add(ctx, values.runtime.NewInt(5))
//
// nestedInClone := clone.Get(values.NewInt(0)).(*values.Array)
// nestedInClone := clone.Get(values.runtime.NewInt(0)).(*values.Array)
//
// So(nestedInArr.CompareValues(nestedInClone), ShouldNotEqual, 0)
// So(nestedInArr.Compareruntime.Values(nestedInClone), ShouldNotEqual, 0)
//})
})
}

View File

@@ -79,7 +79,7 @@ func AssertList(input Value) error {
}
func AssertItemsOf(ctx context.Context, input Iterable, assertion TypeAssertion) error {
return ForEachOf(ctx, input, func(ctx context.Context, value, _ Value) (Boolean, error) {
return ForEach(ctx, input, func(ctx context.Context, value, _ Value) (Boolean, error) {
if err := assertion(value); err != nil {
return false, err
}

View File

@@ -3,7 +3,7 @@ package runtime_test
import (
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
. "github.com/MontFerret/ferret/pkg/runtime"
. "github.com/smartystreets/goconvey/convey"
)
@@ -11,7 +11,7 @@ import (
func TestBoolean(t *testing.T) {
Convey(".MarshalJSON", t, func() {
Convey("Should serialize a boolean items", func() {
b := runtime.True
b := True
marshaled, err := b.MarshalJSON()
So(err, ShouldBeNil)
@@ -22,19 +22,19 @@ func TestBoolean(t *testing.T) {
Convey(".Unwrap", t, func() {
Convey("Should return an unwrapped items", func() {
So(runtime.True.Unwrap(), ShouldHaveSameTypeAs, true)
So(True.Unwrap(), ShouldHaveSameTypeAs, true)
})
})
Convey(".String", t, func() {
Convey("Should return a string representation ", func() {
So(runtime.True.String(), ShouldEqual, "true")
So(True.String(), ShouldEqual, "true")
})
})
Convey(".CompareValues", t, func() {
Convey("It should return 1 when compared to None", func() {
So(runtime.True.Compare(runtime.None), ShouldEqual, 1)
So(True.Compare(None), ShouldEqual, 1)
})
Convey("It should return -1 for all non-boolean values", func() {
@@ -42,8 +42,8 @@ func TestBoolean(t *testing.T) {
NewString("foo"),
NewInt(1),
NewFloat(1.1),
runtime.NewArray(10),
runtime.NewObject(),
NewArray(10),
NewObject(),
}
for _, v := range vals {

View File

@@ -26,7 +26,6 @@ type (
Cloneable
Iterable
IsEmpty(ctx context.Context) (Boolean, error)
Clear(ctx context.Context) error
}
@@ -35,22 +34,20 @@ type (
List interface {
Collection
Indexed
Sortable
Add(ctx context.Context, value Value) error
Set(ctx context.Context, idx Int, value Value) error
Insert(ctx context.Context, idx Int, value Value) error
Remove(ctx context.Context, value Value) error
RemoveAt(ctx context.Context, idx Int) (Value, error)
Insert(ctx context.Context, idx Int, value Value) error
Swap(ctx context.Context, a, b Int) error
Find(ctx context.Context, predicate IndexedPredicate) (List, error)
FindOne(ctx context.Context, predicate IndexedPredicate) (Value, Boolean, error)
Contains(ctx context.Context, value Value) (Boolean, error)
IndexOf(ctx context.Context, value Value) (Int, error)
First(context.Context) (Value, error)
Last(context.Context) (Value, error)
Slice(ctx context.Context, start, end Int) (List, error)
CopyWithCap(ctx context.Context, cap Int) (List, error)
ForEach(ctx context.Context, predicate IndexedPredicate) error
}

View File

@@ -38,7 +38,7 @@ var (
NewBinary = runtime.NewBinary
NewBoolean = runtime.NewBoolean
ForEach = runtime.ForEach
ForEach = runtime.ForEachIter
NewFunctions = runtime.NewFunctions
NewFunctionsFromMap = runtime.NewFunctionsFromMap

View File

@@ -4,36 +4,21 @@ import (
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/pkg/errors"
. "github.com/smartystreets/goconvey/convey"
)
func TestSourceError(t *testing.T) {
Convey("Should match", t, func() {
sm := NewSourceMap("test", 1, 1)
msg := "test at 1:1"
cause := errors.New("cause")
e := errors.Errorf("%s: %s", cause.Error(), msg)
cse := core.SourceError(sm, cause)
So(cse, ShouldNotBeNil)
So(cse.Error(), ShouldEqual, e.Error())
})
}
func TestTypeError(t *testing.T) {
Convey("Should match", t, func() {
e := runtime.TypeError(TypeA{})
e := runtime.TypeError(runtime.True, runtime.TypeList)
So(e, ShouldNotBeNil)
e = runtime.TypeError(TypeA{}, TypeB{})
e = runtime.TypeError(runtime.True, runtime.TypeList, runtime.TypeString)
So(e, ShouldNotBeNil)
cause := errors.New("invalid type: expected type_b or type_c, but got type_a")
e = runtime.TypeError(TypeA{}, TypeB{}, TypeC{})
cause := errors.New("invalid type: expected foo or bar, but got boolean")
e = runtime.TypeError(runtime.True, "foo", "bar")
So(e.Error(), ShouldEqual, cause.Error())
})
}

View File

@@ -72,7 +72,7 @@ func TestIsNil(t *testing.T) {
}
func (t *CustomValue) MarshalJSON() ([]byte, error) {
return nil, ErrNotImplemented
return nil, runtime.ErrNotImplemented
}
func (t *CustomValue) String() string {
@@ -87,8 +87,8 @@ func (t *CustomValue) Hash() uint64 {
return 0
}
func (t *CustomValue) Copy() Value {
return None
func (t *CustomValue) Copy() runtime.Value {
return runtime.None
}
func TestHelpers(t *testing.T) {
@@ -96,14 +96,14 @@ func TestHelpers(t *testing.T) {
Convey("Parse", func() {
Convey("It should parse values", func() {
inputs := []struct {
Parsed Value
Parsed runtime.Value
Raw interface{}
}{
{Parsed: NewInt(1), Raw: int(1)},
{Parsed: NewInt(1), Raw: int8(1)},
{Parsed: NewInt(1), Raw: int16(1)},
{Parsed: NewInt(1), Raw: int32(1)},
{Parsed: NewInt(1), Raw: int64(1)},
{Parsed: runtime.NewInt(1), Raw: int(1)},
{Parsed: runtime.NewInt(1), Raw: int8(1)},
{Parsed: runtime.NewInt(1), Raw: int16(1)},
{Parsed: runtime.NewInt(1), Raw: int32(1)},
{Parsed: runtime.NewInt(1), Raw: int64(1)},
}
for _, input := range inputs {
@@ -120,58 +120,58 @@ func TestHelpers(t *testing.T) {
Convey("ToBoolean", func() {
Convey("Should convert values", func() {
inputs := [][]Value{
inputs := [][]runtime.Value{
{
None,
False,
runtime.None,
runtime.False,
},
{
True,
True,
runtime.True,
runtime.True,
},
{
False,
False,
runtime.False,
runtime.False,
},
{
NewInt(1),
True,
runtime.NewInt(1),
runtime.True,
},
{
NewInt(0),
False,
runtime.NewInt(0),
runtime.False,
},
{
NewFloat(1),
True,
runtime.NewFloat(1),
runtime.True,
},
{
NewFloat(0),
False,
runtime.NewFloat(0),
runtime.False,
},
{
NewString("Foo"),
True,
runtime.NewString("Foo"),
runtime.True,
},
{
EmptyString,
False,
runtime.EmptyString,
runtime.False,
},
{
NewCurrentDateTime(),
True,
runtime.NewCurrentDateTime(),
runtime.True,
},
{
NewArray(1),
True,
runtime.NewArray(1),
runtime.True,
},
{
NewObject(),
True,
runtime.NewObject(),
runtime.True,
},
{
NewBinary([]byte("")),
True,
runtime.NewBinary([]byte("")),
runtime.True,
},
}
@@ -184,152 +184,187 @@ func TestHelpers(t *testing.T) {
})
})
ctx := context.Background()
Convey("ToFloat", func() {
Convey("Should convert Int", func() {
input := NewInt(100)
output := runtime.ToFloat(input)
input := runtime.NewInt(100)
output, err := runtime.ToFloat(ctx, input)
So(output, ShouldEqual, NewFloat(100))
So(err, ShouldBeNil)
So(output, ShouldEqual, runtime.NewFloat(100))
})
Convey("Should convert Float", func() {
input := NewFloat(100)
output := runtime.ToFloat(input)
input := runtime.NewFloat(100)
output, err := runtime.ToFloat(ctx, input)
So(output, ShouldEqual, NewFloat(100))
So(err, ShouldBeNil)
So(output, ShouldEqual, runtime.NewFloat(100))
})
Convey("Should convert String", func() {
input := NewString("100.1")
output := runtime.ToFloat(input)
input := runtime.NewString("100.1")
output, err := runtime.ToFloat(ctx, input)
So(output, ShouldEqual, NewFloat(100.1))
So(err, ShouldBeNil)
So(output, ShouldEqual, runtime.NewFloat(100.1))
output2 := runtime.ToFloat(NewString("foobar"))
So(output2, ShouldEqual, ZeroFloat)
output2, err := runtime.ToFloat(ctx, runtime.NewString("foobar"))
So(output2, ShouldEqual, runtime.ZeroFloat)
})
Convey("Should convert Boolean", func() {
So(runtime.ToFloat(True), ShouldEqual, NewFloat(1))
So(runtime.ToFloat(False), ShouldEqual, NewFloat(0))
out, err := runtime.ToFloat(ctx, runtime.True)
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewFloat(1))
out, err = runtime.ToFloat(ctx, runtime.False)
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewFloat(0))
})
Convey("Should convert Array with single item", func() {
So(runtime.ToFloat(NewArrayWith(NewFloat(1))), ShouldEqual, NewFloat(1))
out, err := runtime.ToFloat(ctx, runtime.NewArrayWith(runtime.NewFloat(1)))
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewFloat(1))
})
Convey("Should convert Array with multiple items", func() {
arg := NewArrayWith(NewFloat(1), NewFloat(1))
arg := runtime.NewArrayWith(runtime.NewFloat(1), runtime.NewFloat(1))
So(runtime.ToFloat(arg), ShouldEqual, NewFloat(2))
out, err := runtime.ToFloat(ctx, arg)
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewFloat(2))
})
Convey("Should convert DateTime", func() {
dt := NewCurrentDateTime()
dt := runtime.NewCurrentDateTime()
ts := dt.Time.Unix()
So(runtime.ToFloat(dt), ShouldEqual, NewFloat(float64(ts)))
out, err := runtime.ToFloat(ctx, dt)
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewFloat(float64(ts)))
})
Convey("Should NOT convert other types", func() {
inputs := []Value{
NewObject(),
NewBinary([]byte("")),
inputs := []runtime.Value{
runtime.NewObject(),
runtime.NewBinary([]byte("")),
}
for _, input := range inputs {
So(runtime.ToFloat(input), ShouldEqual, ZeroFloat)
out, err := runtime.ToFloat(ctx, input)
So(err, ShouldNotBeNil)
So(out, ShouldEqual, runtime.ZeroFloat)
}
})
})
Convey("ToInt", func() {
Convey("Should convert Int", func() {
input := NewInt(100)
output := runtime.ToInt(input)
input := runtime.NewInt(100)
output, err := runtime.ToInt(ctx, input)
So(output, ShouldEqual, NewInt(100))
So(err, ShouldBeNil)
So(output, ShouldEqual, runtime.NewInt(100))
})
Convey("Should convert Float", func() {
input := NewFloat(100.1)
output := runtime.ToInt(input)
So(output, ShouldEqual, NewInt(100))
input := runtime.NewFloat(100.1)
output, err := runtime.ToInt(ctx, input)
So(err, ShouldBeNil)
So(output, ShouldEqual, runtime.NewInt(100))
})
Convey("Should convert String", func() {
input := NewString("100")
output := runtime.ToInt(input)
input := runtime.NewString("100")
output, err := runtime.ToInt(ctx, input)
So(output, ShouldEqual, NewInt(100))
So(err, ShouldBeNil)
So(output, ShouldEqual, runtime.NewInt(100))
output2 := runtime.ToInt(NewString("foobar"))
So(output2, ShouldEqual, ZeroInt)
output2, err := runtime.ToInt(ctx, runtime.NewString("foobar"))
So(err, ShouldNotBeNil)
So(output2, ShouldEqual, runtime.ZeroInt)
})
Convey("Should convert Boolean", func() {
So(runtime.ToInt(True), ShouldEqual, NewInt(1))
So(runtime.ToInt(False), ShouldEqual, NewInt(0))
out, err := runtime.ToInt(ctx, runtime.True)
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewInt(1))
out, err = runtime.ToInt(ctx, runtime.False)
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewInt(0))
})
Convey("Should convert Array with single item", func() {
So(runtime.ToInt(NewArrayWith(NewFloat(1))), ShouldEqual, NewInt(1))
out, err := runtime.ToInt(ctx, runtime.NewArrayWith(runtime.NewInt(1)))
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewInt(1))
})
Convey("Should convert Array with multiple items", func() {
arg := NewArrayWith(NewFloat(1), NewFloat(1))
arg := runtime.NewArrayWith(runtime.NewFloat(1), runtime.NewFloat(1))
out, err := runtime.ToInt(ctx, arg)
So(runtime.ToInt(arg), ShouldEqual, NewFloat(2))
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewFloat(2))
})
Convey("Should convert DateTime", func() {
dt := NewCurrentDateTime()
dt := runtime.NewCurrentDateTime()
ts := dt.Time.Unix()
out, err := runtime.ToInt(ctx, dt)
So(runtime.ToInt(dt), ShouldEqual, NewInt(int(ts)))
So(err, ShouldBeNil)
So(out, ShouldEqual, runtime.NewInt(int(ts)))
})
Convey("Should NOT convert other types", func() {
inputs := []Value{
NewObject(),
NewBinary([]byte("")),
inputs := []runtime.Value{
runtime.NewObject(),
runtime.NewBinary([]byte("")),
}
for _, input := range inputs {
So(runtime.ToInt(input), ShouldEqual, ZeroInt)
out, err := runtime.ToInt(ctx, input)
So(err, ShouldNotBeNil)
So(out, ShouldEqual, runtime.ZeroInt)
}
})
})
Convey("ToList", func() {
Convey("Should convert primitives", func() {
dt := NewCurrentDateTime()
dt := runtime.NewCurrentDateTime()
inputs := [][]Value{
inputs := [][]runtime.Value{
{
None,
NewArray(0),
runtime.None,
runtime.NewArray(0),
},
{
True,
NewArrayWith(True),
runtime.True,
runtime.NewArrayWith(runtime.True),
},
{
NewInt(1),
NewArrayWith(NewInt(1)),
runtime.NewInt(1),
runtime.NewArrayWith(runtime.NewInt(1)),
},
{
NewFloat(1),
NewArrayWith(NewFloat(1)),
runtime.NewFloat(1),
runtime.NewArrayWith(runtime.NewFloat(1)),
},
{
NewString("foo"),
NewArrayWith(NewString("foo")),
runtime.NewString("foo"),
runtime.NewArrayWith(runtime.NewString("foo")),
},
{
dt,
NewArrayWith(dt),
runtime.NewArrayWith(dt),
},
}
@@ -342,24 +377,24 @@ func TestHelpers(t *testing.T) {
})
//Convey("Should create a copy of a given array", func() {
// vals := []core.Value{
// values.NewInt(1),
// values.NewInt(2),
// values.NewInt(3),
// values.NewInt(4),
// values.NewArray(10),
// values.NewObject(),
// vals := []core.runtime.Value{
// values.runtime.NewInt(1),
// values.runtime.NewInt(2),
// values.runtime.NewInt(3),
// values.runtime.NewInt(4),
// values.runtime.NewArray(10),
// values.runtime.NewObject(),
// }
//
// input := values.NewArrayWith(vals...)
// input := values.runtime.NewArrayWith(vals...)
// arr := values.ToList(context.Background(), input)
//
// So(input == arr, ShouldBeFalse)
// So(arr.Length() == input.Length(), ShouldBeTrue)
//
// for idx := range vals {
// expected := input.Get(values.NewInt(idx))
// actual := arr.Get(values.NewInt(idx))
// expected := input.Get(values.runtime.NewInt(idx))
// actual := arr.Get(values.runtime.NewInt(idx))
//
// // same ref
// So(actual == expected, ShouldBeTrue)
@@ -369,15 +404,15 @@ func TestHelpers(t *testing.T) {
//Convey("Should convert object to an array", func() {
// input := values.NewObjectWith(
// values.NewObjectProperty("foo", values.NewString("bar")),
// values.NewObjectProperty("baz", values.NewInt(1)),
// values.NewObjectProperty("qaz", values.NewObject()),
// values.NewObjectProperty("foo", values.runtime.NewString("bar")),
// values.NewObjectProperty("baz", values.runtime.NewInt(1)),
// values.NewObjectProperty("qaz", values.runtime.NewObject()),
// )
//
// arr := values.ToList(context.Background(), input).Sort()
//
// So(arr.String(), ShouldEqual, "[1,\"bar\",{}]")
// So(arr.Get(values.NewInt(2)) == input.MustGet("qaz"), ShouldBeTrue)
// So(arr.Get(values.runtime.NewInt(2)) == input.MustGet("qaz"), ShouldBeTrue)
//})
})

View File

@@ -18,14 +18,14 @@ type (
}
)
func ForEachOf(ctx context.Context, input Iterable, predicate Predicate) error {
func ForEach(ctx context.Context, input Iterable, predicate Predicate) error {
iter, err := input.Iterate(ctx)
if err != nil {
return err
}
err = ForEach(ctx, iter, predicate)
err = ForEachIter(ctx, iter, predicate)
closable, ok := iter.(io.Closer)
if ok {
@@ -37,7 +37,7 @@ func ForEachOf(ctx context.Context, input Iterable, predicate Predicate) error {
return err
}
func ForEach(ctx context.Context, iter Iterator, predicate Predicate) error {
func ForEachIter(ctx context.Context, iter Iterator, predicate Predicate) error {
for {
hasNext, err := iter.HasNext(ctx)

View File

@@ -24,11 +24,11 @@ func NewObjectProperty(name string, value Value) *ObjectProperty {
return &ObjectProperty{name, value}
}
func NewObject() Map {
func NewObject() *Object {
return &Object{make(map[string]Value)}
}
func NewObjectWith(props ...*ObjectProperty) Map {
func NewObjectWith(props ...*ObjectProperty) *Object {
obj := &Object{make(map[string]Value)}
for _, prop := range props {
@@ -38,7 +38,7 @@ func NewObjectWith(props ...*ObjectProperty) Map {
return obj
}
func NewObjectOf(props map[string]Value) Map {
func NewObjectOf(props map[string]Value) *Object {
return &Object{props}
}

View File

@@ -5,7 +5,7 @@ import (
"slices"
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
. "github.com/MontFerret/ferret/pkg/runtime"
. "github.com/smartystreets/goconvey/convey"
)
@@ -13,8 +13,8 @@ import (
func TestObjectIterator(t *testing.T) {
Convey("No values", t, func() {
ctx := context.Background()
obj := runtime.NewObject()
iter := runtime.NewObjectIterator(obj)
obj := NewObject()
iter := NewObjectIterator(obj)
hasNext, err := iter.HasNext(ctx)
@@ -24,9 +24,9 @@ func TestObjectIterator(t *testing.T) {
Convey("One value", t, func() {
ctx := context.Background()
obj := runtime.NewObject()
obj.Set(NewString("key"), NewInt(1))
iter := runtime.NewObjectIterator(obj)
obj := NewObject()
obj.Set(ctx, NewString("key"), NewInt(1))
iter := NewObjectIterator(obj)
hasNext, err := iter.HasNext(ctx)
@@ -47,13 +47,13 @@ func TestObjectIterator(t *testing.T) {
Convey("Multiple values", t, func() {
ctx := context.Background()
obj := runtime.NewObject()
obj.Set(NewString("key1"), NewInt(1))
obj.Set(NewString("key2"), NewInt(2))
obj.Set(NewString("key3"), NewInt(3))
obj.Set(NewString("key4"), NewInt(4))
obj.Set(NewString("key5"), NewInt(5))
iter := runtime.NewObjectIterator(obj)
obj := NewObject()
obj.Set(ctx, NewString("key1"), NewInt(1))
obj.Set(ctx, NewString("key2"), NewInt(2))
obj.Set(ctx, NewString("key3"), NewInt(3))
obj.Set(ctx, NewString("key4"), NewInt(4))
obj.Set(ctx, NewString("key5"), NewInt(5))
iter := NewObjectIterator(obj)
actual := make([][2]Value, 0, 5)
@@ -84,14 +84,14 @@ func TestObjectIterator(t *testing.T) {
func BenchmarkObjectIterator(b *testing.B) {
size := 100
obj := runtime.NewObject()
obj := NewObject()
ctx := context.Background()
for i := 0; i < size; i++ {
obj.Set(NewString("key"+ToString(NewInt(i)).String()), NewInt(i))
obj.Set(ctx, NewString("key"+ToString(NewInt(i)).String()), NewInt(i))
}
ctx := context.Background()
iter := runtime.NewObjectIterator(obj)
iter := NewObjectIterator(obj)
b.ResetTimer()

View File

@@ -1,9 +1,10 @@
package runtime_test
import (
c "context"
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
. "github.com/MontFerret/ferret/pkg/runtime"
. "github.com/smartystreets/goconvey/convey"
)
@@ -11,29 +12,34 @@ import (
func TestObject(t *testing.T) {
Convey("#constructor", t, func() {
Convey("Should create an empty object", func() {
obj := runtime.NewObject()
obj := NewObject()
size, err := obj.Length(c.Background())
So(obj.Length(), ShouldEqual, 0)
So(err, ShouldBeNil)
So(size, ShouldEqual, 0)
})
Convey("Should create an object, from passed values", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("none", None),
runtime.NewObjectProperty("boolean", False),
runtime.NewObjectProperty("int", NewInt(1)),
runtime.NewObjectProperty("float", Float(1)),
runtime.NewObjectProperty("string", NewString("1")),
runtime.NewObjectProperty("array", NewArray(10)),
runtime.NewObjectProperty("object", runtime.NewObject()),
obj := NewObjectWith(
NewObjectProperty("none", None),
NewObjectProperty("boolean", False),
NewObjectProperty("int", NewInt(1)),
NewObjectProperty("float", Float(1)),
NewObjectProperty("string", NewString("1")),
NewObjectProperty("array", NewArray(10)),
NewObjectProperty("object", NewObject()),
)
So(obj.Length(), ShouldEqual, 7)
size, err := obj.Length(c.Background())
So(err, ShouldBeNil)
So(size, ShouldEqual, 7)
})
})
Convey(".MarshalJSON", t, func() {
Convey("Should serialize an empty object", func() {
obj := runtime.NewObject()
obj := NewObject()
marshaled, err := obj.MarshalJSON()
So(err, ShouldBeNil)
@@ -42,14 +48,14 @@ func TestObject(t *testing.T) {
})
Convey("Should serialize full object", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("none", None),
runtime.NewObjectProperty("boolean", False),
runtime.NewObjectProperty("int", NewInt(1)),
runtime.NewObjectProperty("float", Float(1)),
runtime.NewObjectProperty("string", NewString("1")),
runtime.NewObjectProperty("array", NewArray(10)),
runtime.NewObjectProperty("object", runtime.NewObject()),
obj := NewObjectWith(
NewObjectProperty("none", None),
NewObjectProperty("boolean", False),
NewObjectProperty("int", NewInt(1)),
NewObjectProperty("float", Float(1)),
NewObjectProperty("string", NewString("1")),
NewObjectProperty("array", NewArray(10)),
NewObjectProperty("object", NewObject()),
)
marshaled, err := obj.MarshalJSON()
@@ -61,9 +67,9 @@ func TestObject(t *testing.T) {
Convey(".Unwrap", t, func() {
Convey("Should return an unwrapped items", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", NewString("foo")),
runtime.NewObjectProperty("bar", NewString("bar")),
obj := NewObjectWith(
NewObjectProperty("foo", NewString("foo")),
NewObjectProperty("bar", NewString("bar")),
)
for _, val := range obj.Unwrap().(map[string]interface{}) {
@@ -74,8 +80,8 @@ func TestObject(t *testing.T) {
Convey(".String", t, func() {
Convey("Should return a string representation ", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", NewString("bar")),
obj := NewObjectWith(
NewObjectProperty("foo", NewString("bar")),
)
So(obj.String(), ShouldEqual, "{\"foo\":\"bar\"}")
@@ -92,7 +98,7 @@ func TestObject(t *testing.T) {
NewString("1"),
NewArray(10),
}
obj := runtime.NewObject()
obj := NewObject()
for _, val := range arr {
So(obj.Compare(val), ShouldEqual, 1)
@@ -101,26 +107,26 @@ func TestObject(t *testing.T) {
Convey("It should return -1 for all object values", func() {
arr := NewArrayWith(ZeroInt, ZeroInt)
obj := runtime.NewObject()
obj := NewObject()
So(arr.Compare(obj), ShouldEqual, -1)
})
Convey("It should return 0 when both objects are empty", func() {
obj1 := runtime.NewObject()
obj2 := runtime.NewObject()
obj1 := NewObject()
obj2 := NewObject()
So(obj1.Compare(obj2), ShouldEqual, 0)
})
Convey("It should return 0 when both objects are equal (independent of key order)", func() {
obj1 := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", NewString("foo")),
runtime.NewObjectProperty("bar", NewString("bar")),
obj1 := NewObjectWith(
NewObjectProperty("foo", NewString("foo")),
NewObjectProperty("bar", NewString("bar")),
)
obj2 := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", NewString("foo")),
runtime.NewObjectProperty("bar", NewString("bar")),
obj2 := NewObjectWith(
NewObjectProperty("foo", NewString("foo")),
NewObjectProperty("bar", NewString("bar")),
)
So(obj1.Compare(obj1), ShouldEqual, 0)
@@ -130,82 +136,82 @@ func TestObject(t *testing.T) {
})
Convey("It should return 1 when other array is empty", func() {
obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewString("bar")))
obj2 := runtime.NewObject()
obj1 := NewObjectWith(NewObjectProperty("foo", NewString("bar")))
obj2 := NewObject()
So(obj1.Compare(obj2), ShouldEqual, 1)
})
Convey("It should return 1 when values are bigger", func() {
obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(3)))
obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(2)))
obj1 := NewObjectWith(NewObjectProperty("foo", NewFloat(3)))
obj2 := NewObjectWith(NewObjectProperty("foo", NewFloat(2)))
So(obj1.Compare(obj2), ShouldEqual, 1)
})
Convey("It should return 1 when values are less", func() {
obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(1)))
obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewFloat(2)))
obj1 := NewObjectWith(NewObjectProperty("foo", NewFloat(1)))
obj2 := NewObjectWith(NewObjectProperty("foo", NewFloat(2)))
So(obj1.Compare(obj2), ShouldEqual, -1)
})
Convey("ArangoDB compatibility", func() {
Convey("It should return 1 when {a:1} and {b:2}", func() {
obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("b", NewInt(2)))
obj1 := NewObjectWith(NewObjectProperty("a", NewInt(1)))
obj2 := NewObjectWith(NewObjectProperty("b", NewInt(2)))
So(obj1.Compare(obj2), ShouldEqual, 1)
})
Convey("It should return 0 when {a:1} and {a:1}", func() {
obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj1 := NewObjectWith(NewObjectProperty("a", NewInt(1)))
obj2 := NewObjectWith(NewObjectProperty("a", NewInt(1)))
So(obj1.Compare(obj2), ShouldEqual, 0)
})
Convey("It should return 0 {a:1, c:2} and {c:2, a:1}", func() {
obj1 := runtime.NewObjectWith(
runtime.NewObjectProperty("a", NewInt(1)),
runtime.NewObjectProperty("c", NewInt(2)),
obj1 := NewObjectWith(
NewObjectProperty("a", NewInt(1)),
NewObjectProperty("c", NewInt(2)),
)
obj2 := runtime.NewObjectWith(
runtime.NewObjectProperty("c", NewInt(2)),
runtime.NewObjectProperty("a", NewInt(1)),
obj2 := NewObjectWith(
NewObjectProperty("c", NewInt(2)),
NewObjectProperty("a", NewInt(1)),
)
So(obj1.Compare(obj2), ShouldEqual, 0)
})
Convey("It should return -1 when {a:1} and {a:2}", func() {
obj1 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(1)))
obj2 := runtime.NewObjectWith(runtime.NewObjectProperty("a", NewInt(2)))
obj1 := NewObjectWith(NewObjectProperty("a", NewInt(1)))
obj2 := NewObjectWith(NewObjectProperty("a", NewInt(2)))
So(obj1.Compare(obj2), ShouldEqual, -1)
})
Convey("It should return 1 when {a:1, c:2} and {c:2, b:2}", func() {
obj1 := runtime.NewObjectWith(
runtime.NewObjectProperty("a", NewInt(1)),
runtime.NewObjectProperty("c", NewInt(2)),
obj1 := NewObjectWith(
NewObjectProperty("a", NewInt(1)),
NewObjectProperty("c", NewInt(2)),
)
obj2 := runtime.NewObjectWith(
runtime.NewObjectProperty("c", NewInt(2)),
runtime.NewObjectProperty("b", NewInt(2)),
obj2 := NewObjectWith(
NewObjectProperty("c", NewInt(2)),
NewObjectProperty("b", NewInt(2)),
)
So(obj1.Compare(obj2), ShouldEqual, 1)
})
Convey("It should return 1 {a:1, c:3} and {c:2, a:1}", func() {
obj1 := runtime.NewObjectWith(
runtime.NewObjectProperty("a", NewInt(1)),
runtime.NewObjectProperty("c", NewInt(3)),
obj1 := NewObjectWith(
NewObjectProperty("a", NewInt(1)),
NewObjectProperty("c", NewInt(3)),
)
obj2 := runtime.NewObjectWith(
runtime.NewObjectProperty("c", NewInt(2)),
runtime.NewObjectProperty("a", NewInt(1)),
obj2 := NewObjectWith(
NewObjectProperty("c", NewInt(2)),
NewObjectProperty("a", NewInt(1)),
)
So(obj1.Compare(obj2), ShouldEqual, 1)
@@ -215,10 +221,10 @@ func TestObject(t *testing.T) {
Convey(".Hash", t, func() {
Convey("It should calculate hash of non-empty object", func() {
v := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", NewString("bar")),
runtime.NewObjectProperty("faz", NewInt(1)),
runtime.NewObjectProperty("qaz", True),
v := NewObjectWith(
NewObjectProperty("foo", NewString("bar")),
NewObjectProperty("faz", NewInt(1)),
NewObjectProperty("qaz", True),
)
h := v.Hash()
@@ -227,7 +233,7 @@ func TestObject(t *testing.T) {
})
Convey("It should calculate hash of empty object", func() {
v := runtime.NewObject()
v := NewObject()
h := v.Hash()
@@ -235,14 +241,14 @@ func TestObject(t *testing.T) {
})
Convey("Hash sum should be consistent", func() {
v := runtime.NewObjectWith(
runtime.NewObjectProperty("boolean", True),
runtime.NewObjectProperty("int", NewInt(1)),
runtime.NewObjectProperty("float", NewFloat(1.1)),
runtime.NewObjectProperty("string", NewString("foobar")),
runtime.NewObjectProperty("datetime", NewCurrentDateTime()),
runtime.NewObjectProperty("array", NewArrayWith(NewInt(1), True)),
runtime.NewObjectProperty("object", runtime.NewObjectWith(runtime.NewObjectProperty("foo", NewString("bar")))),
v := NewObjectWith(
NewObjectProperty("boolean", True),
NewObjectProperty("int", NewInt(1)),
NewObjectProperty("float", NewFloat(1.1)),
NewObjectProperty("string", NewString("foobar")),
NewObjectProperty("datetime", NewCurrentDateTime()),
NewObjectProperty("array", NewArrayWith(NewInt(1), True)),
NewObjectProperty("object", NewObjectWith(NewObjectProperty("foo", NewString("bar")))),
)
h1 := v.Hash()
@@ -254,66 +260,80 @@ func TestObject(t *testing.T) {
Convey(".Length", t, func() {
Convey("Should return 0 when empty", func() {
obj := runtime.NewObject()
obj := NewObject()
size, err := obj.Length(c.Background())
So(obj.Length(), ShouldEqual, 0)
So(err, ShouldBeNil)
So(size, ShouldEqual, 0)
})
Convey("Should return greater than 0 when not empty", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", ZeroInt),
runtime.NewObjectProperty("bar", ZeroInt),
obj := NewObjectWith(
NewObjectProperty("foo", ZeroInt),
NewObjectProperty("bar", ZeroInt),
)
So(obj.Length(), ShouldEqual, 2)
size, err := obj.Length(c.Background())
So(err, ShouldBeNil)
So(size, ShouldEqual, 2)
})
})
Convey(".ForEach", t, func() {
Convey(".ForEachIter", t, func() {
Convey("Should iterate over elements", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("foo", ZeroInt),
runtime.NewObjectProperty("bar", ZeroInt),
obj := NewObjectWith(
NewObjectProperty("bar", ZeroInt),
NewObjectProperty("foo", ZeroInt),
)
counter := 0
ctx := c.Background()
obj.ForEach(func(value Value, key string) bool {
obj.ForEach(ctx, func(_ c.Context, value, key Value) (Boolean, error) {
counter++
return true
return true, nil
})
So(counter, ShouldEqual, obj.Length())
size, err := obj.Length(ctx)
So(err, ShouldBeNil)
So(counter, ShouldEqual, size)
})
Convey("Should not iterate when empty", func() {
obj := runtime.NewObject()
obj := NewObject()
counter := 0
obj.ForEach(func(value Value, key string) bool {
ctx := c.Background()
obj.ForEach(ctx, func(_ c.Context, value, key Value) (Boolean, error) {
counter++
return true
return true, nil
})
So(counter, ShouldEqual, obj.Length())
size, err := obj.Length(ctx)
So(err, ShouldBeNil)
So(counter, ShouldEqual, size)
})
Convey("Should break iteration when false returned", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("1", NewInt(1)),
runtime.NewObjectProperty("2", NewInt(2)),
runtime.NewObjectProperty("3", NewInt(3)),
runtime.NewObjectProperty("4", NewInt(4)),
runtime.NewObjectProperty("5", NewInt(5)),
obj := NewObjectWith(
NewObjectProperty("1", NewInt(1)),
NewObjectProperty("2", NewInt(2)),
NewObjectProperty("3", NewInt(3)),
NewObjectProperty("4", NewInt(4)),
NewObjectProperty("5", NewInt(5)),
)
threshold := 3
counter := 0
ctx := c.Background()
obj.ForEach(func(value Value, key string) bool {
obj.ForEach(ctx, func(_ c.Context, value, key Value) (Boolean, error) {
counter++
return counter < threshold
return counter < threshold, nil
})
So(counter, ShouldEqual, threshold)
@@ -357,43 +377,49 @@ func TestObject(t *testing.T) {
Convey(".Clone", t, func() {
Convey("Cloned object should be equal to source object", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("two", NewInt(2)),
obj := NewObjectWith(
NewObjectProperty("one", NewInt(1)),
NewObjectProperty("two", NewInt(2)),
)
clone := obj.Clone().(*runtime.Object)
ctx := c.Background()
cloned, _ := obj.Clone(ctx)
clone := cloned.(*Object)
So(obj.Compare(clone), ShouldEqual, 0)
})
Convey("Cloned object should be independent of the source object", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty("one", NewInt(1)),
runtime.NewObjectProperty("two", NewInt(2)),
obj := NewObjectWith(
NewObjectProperty("one", NewInt(1)),
NewObjectProperty("two", NewInt(2)),
)
clone := obj.Clone().(*runtime.Object)
ctx := c.Background()
cloned, _ := obj.Clone(ctx)
clone := cloned.(*Object)
obj.Remove(NewString("one"))
obj.Remove(ctx, NewString("one"))
So(obj.Compare(clone), ShouldNotEqual, 0)
})
Convey("Cloned object must contain copies of the nested objects", func() {
obj := runtime.NewObjectWith(
runtime.NewObjectProperty(
obj := NewObjectWith(
NewObjectProperty(
"arr", NewArrayWith(NewInt(1)),
),
)
clone := obj.Clone().(*runtime.Object)
ctx := c.Background()
cloned, _ := obj.Clone(ctx)
clone := cloned.(*Object)
nestedInObj, _ := obj.Get(NewString("arr"))
nestedInObj, _ := obj.Get(ctx, NewString("arr"))
nestedInObjArr := nestedInObj.(*Array)
nestedInObjArr.Push(NewInt(2))
nestedInObjArr.Add(ctx, NewInt(2))
nestedInClone, _ := clone.Get(NewString("arr"))
nestedInClone, _ := clone.Get(ctx, NewString("arr"))
nestedInCloneArr := nestedInClone.(*Array)
So(nestedInObjArr.Compare(nestedInCloneArr), ShouldNotEqual, 0)

224
pkg/runtime/sort.go Normal file
View File

@@ -0,0 +1,224 @@
package runtime
import (
"context"
)
type (
SortLess = func(ctx context.Context, a, b Int) (Boolean, error)
SortSwap = func(ctx context.Context, a, b Int) error
)
// sortInsertionSort sorts data[a:b] using insertion Sort.
func sortInsertionSort(ctx context.Context, less SortLess, swap SortSwap, a, b Int) error {
for i := a + 1; i < b; i++ {
for j := i; j > a; j-- {
isLess, err := less(ctx, j, j-1)
if err != nil {
return err
}
if !isLess {
break
}
if err := swap(ctx, j, j-1); err != nil {
return err
}
}
}
return nil
}
func sortSwapRange(ctx context.Context, swap SortSwap, a, b, n Int) error {
for i := ZeroInt; i < n; i++ {
if err := swap(ctx, a+i, b+i); err != nil {
return err
}
}
return nil
}
func stableSort(ctx context.Context, less SortLess, swap SortSwap, n Int) error {
blockSize := Int(20) // must be > 0
a, b := ZeroInt, blockSize
for b <= n {
if err := sortInsertionSort(ctx, less, swap, a, b); err != nil {
return err
}
a = b
b += blockSize
}
if err := sortInsertionSort(ctx, less, swap, a, n); err != nil {
return err
}
for blockSize < n {
a, b = 0, 2*blockSize
for b <= n {
if err := sortSymMerge(ctx, less, swap, a, a+blockSize, b); err != nil {
return err
}
a = b
b += 2 * blockSize
}
if m := a + blockSize; m < n {
if err := sortSymMerge(ctx, less, swap, a, m, n); err != nil {
return err
}
}
blockSize *= 2
}
return nil
}
func sortSymMerge(ctx context.Context, less SortLess, swap SortSwap, a, m, b Int) error {
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[a] into data[m:b]
// if data[a:m] only contains one element.
if m-a == 1 {
// Use binary search to find the lowest index i
// such that data[i] >= data[a] for m <= i < b.
// Exit the search loop with i == b in case no such index exists.
i := m
j := b
for i < j {
h := Int(uint(i+j) >> 1)
isLess, err := less(ctx, h, a)
if err != nil {
return err
}
if isLess {
i = h + 1
} else {
j = h
}
}
// Swap values until data[a] reaches the position before i.
for k := a; k < i-1; k++ {
if err := swap(ctx, k, k+1); err != nil {
return err
}
}
return nil
}
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[m] into data[a:m]
// if data[m:b] only contains one element.
if b-m == 1 {
// Use binary search to find the lowest index i
// such that data[i] > data[m] for a <= i < m.
// Exit the search loop with i == m in case no such index exists.
i := a
j := m
for i < j {
h := Int(uint(i+j) >> 1)
isLess, err := less(ctx, m, h)
if err != nil {
return err
}
if !isLess {
i = h + 1
} else {
j = h
}
}
// Swap values until data[m] reaches the position i.
for k := m; k > i; k-- {
if err := swap(ctx, k, k-1); err != nil {
return err
}
}
return nil
}
mid := Int(uint(a+b) >> 1)
n := mid + m
var start, r Int
if m > mid {
start = n - b
r = mid
} else {
start = a
r = m
}
p := n - 1
for start < r {
c := Int(uint(start+r) >> 1)
isLess, err := less(ctx, p-c, c)
if err != nil {
return err
}
if !isLess {
start = c + 1
} else {
r = c
}
}
end := n - start
if start < m && m < end {
if err := sortRotate(ctx, swap, start, m, end); err != nil {
return err
}
}
if a < start && start < mid {
if err := sortSymMerge(ctx, less, swap, a, start, mid); err != nil {
return err
}
}
if mid < end && end < b {
if err := sortSymMerge(ctx, less, swap, mid, end, b); err != nil {
return err
}
}
return nil
}
func sortRotate(ctx context.Context, swap SortSwap, a, m, b Int) error {
i := m - a
j := b - m
for i != j {
if i > j {
if err := sortSwapRange(ctx, swap, m-i, m, j); err != nil {
return err
}
i -= j
} else {
if err := sortSwapRange(ctx, swap, m-i, m+j-i, i); err != nil {
return err
}
j -= i
}
}
// i == j
return sortSwapRange(ctx, swap, m-i, m, i)
}

View File

@@ -12,9 +12,96 @@ type Sortable interface {
// SortDesc sorts the collection in descending order.
SortDesc(context.Context) error
}
// SortWith sorts the collection using a custom comparator.
SortWith(context.Context, Comparator) error
func SortAsc(ctx context.Context, values Value) error {
return Sort(ctx, values, true)
}
func SortDesc(ctx context.Context, values Value) error {
return Sort(ctx, values, false)
}
// Sort is a generic sorting function that accepts either a List or value that implements Sortable interface.
func Sort(ctx context.Context, values Value, ascending Boolean) error {
switch value := values.(type) {
case Sortable:
if ascending {
return value.SortAsc(ctx)
}
return value.SortDesc(ctx)
case List:
return SortList(ctx, value, ascending)
default:
return TypeError(values, TypeList, TypeSortable)
}
}
func SortListAsc(ctx context.Context, values List) error {
return SortList(ctx, values, true)
}
func SortListDesc(ctx context.Context, values List) error {
return SortList(ctx, values, false)
}
// SortList sorts the given List using the stable Sort algorithm
func SortList(ctx context.Context, values List, ascending Boolean) error {
var pivot int64 = -1
if ascending {
pivot = 1
}
size, err := values.Length(ctx)
if err != nil {
return err
}
return stableSort(ctx, func(ctx context.Context, a, b Int) (Boolean, error) {
aVal, err := values.Get(ctx, a)
if err != nil {
return false, err
}
bVal, err := values.Get(ctx, b)
if err != nil {
return false, err
}
comp := CompareValues(aVal, bVal) * pivot
return comp == -1, nil
}, values.Swap, size)
}
// SortListWith sorts the given List using the stable Sort algorithm using a custom comparator
func SortListWith(ctx context.Context, values List, comparator Comparator) error {
size, err := values.Length(ctx)
if err != nil {
return err
}
return stableSort(ctx, func(ctx context.Context, a, b Int) (Boolean, error) {
aVal, err := values.Get(ctx, a)
if err != nil {
return false, err
}
bVal, err := values.Get(ctx, b)
if err != nil {
return false, err
}
return comparator(aVal, bVal) == -1, nil
}, values.Swap, size)
}
func SortSlice(values []Value, ascending Boolean) {

View File

@@ -0,0 +1,36 @@
package runtime_test
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/MontFerret/ferret/pkg/runtime"
)
func TestSortList(t *testing.T) {
Convey("Should sort Array DESC", t, func() {
arr1 := runtime.NewArrayWith(runtime.Int(3), runtime.Int(1), runtime.Int(2), runtime.Int(6), runtime.Int(4), runtime.Int(5))
err := runtime.SortList(context.Background(), arr1, false)
So(err, ShouldBeNil)
j, err := arr1.MarshalJSON()
So(err, ShouldBeNil)
So(string(j), ShouldEqual, `[6,5,4,3,2,1]`)
})
Convey("Should sort Array ASC", t, func() {
arr1 := runtime.NewArrayWith(runtime.Int(3), runtime.Int(1), runtime.Int(2), runtime.Int(6), runtime.Int(4), runtime.Int(5))
err := runtime.SortList(context.Background(), arr1, true)
So(err, ShouldBeNil)
j, err := arr1.MarshalJSON()
So(err, ShouldBeNil)
So(string(j), ShouldEqual, `[1,2,3,4,5,6]`)
})
}

View File

@@ -23,8 +23,10 @@ type (
Iterable
Measurable
Indexed
Sortable
Add(ctx context.Context, value Value) error
AddKV(ctx context.Context, key, value Value) error
}
StorageManager interface {

View File

@@ -1,6 +1,7 @@
package runtime_test
import (
c "context"
"encoding/json"
"fmt"
"testing"
@@ -34,8 +35,10 @@ func TestString(t *testing.T) {
Convey(".Length", t, func() {
Convey("Should return unicode length", func() {
str := runtime.NewString("Спутник")
size, err := str.Length(c.Background())
So(str.Length(), ShouldEqual, 7)
So(err, ShouldBeNil)
So(size, ShouldEqual, 7)
})
})

View File

@@ -13,11 +13,13 @@ const (
TypeMap = "map"
TypeBinary = "binary"
// Interfaces
TypeIterable = "iterable"
TypeIterator = "iterator"
TypeMeasurable = "measurable"
TypeComparable = "comparable"
TypeCloneable = "cloneable"
TypeSortable = "sortable"
TypeObservable = "observable"
)

View File

@@ -12,5 +12,6 @@ type Value interface {
String() string
Unwrap() interface{}
Hash() uint64
// TODO: Add context and return error. It needs to support values that rely on external storages.
Copy() Value
}

View File

@@ -36,22 +36,25 @@ func Append(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
unique = arg3
}
var next runtime.List
// We do not know for sure if the list is an array or custom List implementation.
// Hence, we must solely rely on the List interface.
next, err := list.CopyWithCap(ctx, 1)
if err != nil {
return runtime.None, err
switch v := arg.(type) {
case *runtime.Array:
next = v.CopyWithGrowth(1)
case runtime.List:
next = v.Copy().(runtime.List)
}
if unique {
contains, err := list.Contains(ctx, arg)
idx, err := list.IndexOf(ctx, arg)
if err != nil {
return runtime.None, err
}
if contains {
if idx > -1 {
return next, nil
}
}

View File

@@ -27,11 +27,11 @@ func RemoveNth(ctx context.Context, args ...runtime.Value) (runtime.Value, error
return runtime.None, err
}
next, err := list.CopyWithCap(ctx, 0)
next := list.Copy().(runtime.List)
if err != nil {
return runtime.None, err
}
//if err != nil {
// return runtime.None, err
//}
_, err = next.RemoveAt(ctx, index)

View File

@@ -6,7 +6,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime"
)
// SORTED sorts all elements in anyArray.
// SORTED sorts all elements in the given list.
// The function will use the default comparison order for FQL value types.
// @param {Any[]} array - Target array.
// @return {Any[]} - Sorted array.
@@ -31,13 +31,13 @@ func Sorted(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.EmptyArray(), nil
}
copied, err := list.CopyWithCap(ctx, 0)
copied := list.Copy()
if err != nil {
return runtime.None, err
}
//if err != nil {
// return runtime.None, err
//}
if err := copied.SortAsc(ctx); err != nil {
if err := runtime.SortAsc(ctx, copied); err != nil {
return runtime.None, err
}

View File

@@ -28,7 +28,7 @@ func SortedUnique(ctx context.Context, args ...runtime.Value) (runtime.Value, er
return runtime.None, err
}
if err := uniq.SortAsc(ctx); err != nil {
if err := runtime.SortAsc(ctx, uniq); err != nil {
return runtime.None, err
}

View File

@@ -45,7 +45,7 @@ func Includes(ctx context.Context, args ...runtime.Value) (runtime.Value, error)
return runtime.False, err
}
err = runtime.ForEach(ctx, iter, func(c context.Context, value runtime.Value, key runtime.Value) (runtime.Boolean, error) {
err = runtime.ForEachIter(ctx, iter, func(c context.Context, value runtime.Value, key runtime.Value) (runtime.Boolean, error) {
if runtime.CompareValues(needle, value) == 0 {
result = runtime.True

View File

@@ -21,13 +21,16 @@ func Median(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.None, err
}
sorted, err := arr.CopyWithCap(ctx, 0)
sorted := arr.Copy().(runtime.List)
if err != nil {
return runtime.None, err
}
//
//sorted, err := arr.CopyWithGrowth(ctx, 0)
//
//if err != nil {
// return runtime.None, err
//}
if err := sorted.SortDesc(ctx); err != nil {
if err := runtime.SortDesc(ctx, sorted); err != nil {
return runtime.None, err
}

View File

@@ -62,13 +62,13 @@ func Percentile(ctx context.Context, args ...runtime.Value) (runtime.Value, erro
return runtime.NaN(), errors.New("input is outside of range")
}
sorted, err := arr.CopyWithCap(ctx, 0)
sorted := arr.Copy().(runtime.List)
if err != nil {
return runtime.NaN(), err
}
//if err != nil {
// return runtime.NaN(), err
//}
if err := sorted.SortDesc(ctx); err != nil {
if err := runtime.SortAsc(ctx, sorted); err != nil {
return runtime.NaN(), err
}

View File

@@ -1,8 +1,9 @@
package internal
import (
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/wI2L/jettison"
"github.com/MontFerret/ferret/pkg/runtime"
)
type Box[T any] struct {

View File

@@ -2,23 +2,22 @@ package internal
import (
"context"
"errors"
"github.com/MontFerret/ferret/pkg/runtime"
)
type DataSet struct {
uniqueness runtime.MapStorage
groups runtime.MapStorage
values runtime.ListStorage
uniqueness map[uint64]bool
groups map[string]runtime.List
values runtime.List
keyed bool
}
func NewDataSet(distinct bool) *DataSet {
var hashmap runtime.MapStorage
func NewDataSet(distinct bool) runtime.List {
var hashmap map[uint64]bool
if distinct {
hashmap = runtime.NewObject()
hashmap = make(map[uint64]bool)
}
return &DataSet{
@@ -28,7 +27,7 @@ func NewDataSet(distinct bool) *DataSet {
}
func (ds *DataSet) Sort(ctx context.Context, direction runtime.Int) error {
return ds.values.SortWith(ctx, func(first, second runtime.Value) int64 {
return runtime.SortListWith(ctx, ds.values, func(first, second runtime.Value) int64 {
firstKV, firstOk := first.(*KV)
secondKV, secondOk := second.(*KV)
@@ -49,7 +48,7 @@ func (ds *DataSet) Sort(ctx context.Context, direction runtime.Int) error {
}
func (ds *DataSet) SortMany(ctx context.Context, directions []runtime.Int) error {
return ds.values.SortWith(ctx, func(first, second runtime.Value) int64 {
return runtime.SortListWith(ctx, ds.values, func(first, second runtime.Value) int64 {
firstKV, firstOk := first.(*KV)
secondKV, secondOk := second.(*KV)
@@ -120,7 +119,19 @@ func (ds *DataSet) AddKV(ctx context.Context, key, value runtime.Value) error {
}
func (ds *DataSet) Collect(ctx context.Context, key, value runtime.Value) error {
return nil
if ds.groups == nil {
ds.groups = make(map[string]runtime.List)
}
keyStr := key.String()
group, ok := ds.groups[keyStr]
if !ok {
group = runtime.NewArray(8)
ds.groups[keyStr] = group
}
return group.Add(ctx, value)
}
func (ds *DataSet) Iterate(ctx context.Context) (runtime.Iterator, error) {
@@ -158,30 +169,83 @@ func (ds *DataSet) Copy() runtime.Value {
}
func (ds *DataSet) MarshalJSON() ([]byte, error) {
return nil, nil
return ds.values.MarshalJSON()
}
func (ds *DataSet) ToList() runtime.List {
return ds.values
func (ds *DataSet) Compare(other runtime.Value) int64 {
return ds.values.Compare(other)
}
func (ds *DataSet) canAdd(ctx context.Context, value runtime.Value) (bool, error) {
func (ds *DataSet) Clone(ctx context.Context) (runtime.Cloneable, error) {
return ds.values.Clone(ctx)
}
func (ds *DataSet) Clear(ctx context.Context) error {
return ds.values.Clear(ctx)
}
func (ds *DataSet) Set(ctx context.Context, idx runtime.Int, value runtime.Value) error {
return ds.values.Set(ctx, idx, value)
}
func (ds *DataSet) Remove(ctx context.Context, value runtime.Value) error {
return ds.values.Remove(ctx, value)
}
func (ds *DataSet) RemoveAt(ctx context.Context, idx runtime.Int) (runtime.Value, error) {
return ds.values.RemoveAt(ctx, idx)
}
func (ds *DataSet) Insert(ctx context.Context, idx runtime.Int, value runtime.Value) error {
return ds.values.Insert(ctx, idx, value)
}
func (ds *DataSet) Swap(ctx context.Context, a, b runtime.Int) error {
return ds.values.Swap(ctx, a, b)
}
func (ds *DataSet) Find(ctx context.Context, predicate runtime.IndexedPredicate) (runtime.List, error) {
return ds.values.Find(ctx, predicate)
}
func (ds *DataSet) FindOne(ctx context.Context, predicate runtime.IndexedPredicate) (runtime.Value, runtime.Boolean, error) {
return ds.values.FindOne(ctx, predicate)
}
func (ds *DataSet) IndexOf(ctx context.Context, value runtime.Value) (runtime.Int, error) {
return ds.values.IndexOf(ctx, value)
}
func (ds *DataSet) First(ctx context.Context) (runtime.Value, error) {
return ds.values.First(ctx)
}
func (ds *DataSet) Last(ctx context.Context) (runtime.Value, error) {
return ds.values.Last(ctx)
}
func (ds *DataSet) Slice(ctx context.Context, start, end runtime.Int) (runtime.List, error) {
return ds.values.Slice(ctx, start, end)
}
func (ds *DataSet) ForEach(ctx context.Context, predicate runtime.IndexedPredicate) error {
return ds.values.ForEach(ctx, predicate)
}
func (ds *DataSet) canAdd(_ context.Context, value runtime.Value) (bool, error) {
if ds.uniqueness == nil {
return true, nil
}
hash := value.Hash()
rnHash := runtime.Int(int64(hash))
_, err := ds.uniqueness.Get(ctx, rnHash)
_, exists := ds.uniqueness[hash]
if err != nil {
if errors.Is(err, runtime.ErrNotFound) {
return true, ds.uniqueness.Set(ctx, rnHash, value)
}
return false, err
if exists {
return false, nil
}
ds.uniqueness[hash] = true
return true, nil
}

View File

@@ -4,6 +4,8 @@ import (
"encoding/binary"
"hash/fnv"
"github.com/wI2L/jettison"
"github.com/MontFerret/ferret/pkg/runtime"
)
@@ -20,7 +22,12 @@ func NewKV(key, value runtime.Value) *KV {
}
func (p *KV) MarshalJSON() ([]byte, error) {
return p.Value.MarshalJSON()
pair := [2]runtime.Value{
p.Key,
p.Value,
}
return jettison.MarshalOpts(pair, jettison.NoHTMLEscaping())
}
func (p *KV) String() string {

View File

@@ -2,6 +2,7 @@ package internal
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime"
)

View File

@@ -13,12 +13,13 @@ import (
func Contains(ctx context.Context, input runtime.Value, value runtime.Value) runtime.Boolean {
switch val := input.(type) {
case runtime.List:
contains, err := val.Contains(ctx, value)
idx, err := val.IndexOf(ctx, value)
if err != nil {
return runtime.False
}
return contains
return idx > -1
case runtime.Map:
containsValue, err := val.ContainsValue(ctx, value)
@@ -34,7 +35,7 @@ func Contains(ctx context.Context, input runtime.Value, value runtime.Value) run
}
}
func Add(ctx context.Context, inputL, inputR runtime.Value) runtime.Value {
func Add(_ context.Context, inputL, inputR runtime.Value) runtime.Value {
left := runtime.ToNumberOrString(inputL)
switch leftVal := left.(type) {

View File

@@ -1,8 +1,9 @@
package internal
import (
"github.com/MontFerret/ferret/pkg/runtime"
"time"
"github.com/MontFerret/ferret/pkg/runtime"
)
type StreamValue struct {

View File

@@ -70,10 +70,9 @@ const (
OpCollect // Groups a collection by a key (COLLECT R1, R2, R3 - groups a collection in R1 with a key from R2 and a value from R3)
OpCollectMany
OpDataSet // Creates a new dataset
OpDataSetAdd // Adds a value to a dataset
OpDataSetAddKV // Adds a key-value pair to a dataset
OpDataSetToList // TODO: Temporary. Remove when the final API is ready.
OpDataSet // Creates a new dataset
OpDataSetAdd // Adds a value to a dataset
OpDataSetAddKV // Adds a key-value pair to a dataset
OpIter // Creates an iterator (ITER R2, R3 [, R4] - creates an iterator in R2 with a collection from R3 and optional params from R4)
OpIterNext // Moves to the next element in the iterator (ITER R2, R3 - moves to the next element in the iterator in R2 with a collection from R3)

View File

@@ -366,9 +366,6 @@ loop:
ds := reg[dst].(*internal.DataSet)
ds.AddKV(ctx, key, value)
case OpDataSetToList:
ds := reg[src1].(*internal.DataSet)
reg[dst] = ds.ToList()
case OpIter:
input := reg[src1]