mirror of
https://github.com/MontFerret/ferret.git
synced 2025-03-03 15:02:32 +02:00
Bug/#142 clauses and statements (#148)
This commit is contained in:
parent
e6fd33ac1d
commit
3472630e6f
2
Makefile
2
Makefile
@ -22,7 +22,7 @@ install:
|
||||
dep ensure
|
||||
|
||||
test:
|
||||
go test -v -race ${DIR_PKG}/...
|
||||
go test -race ${DIR_PKG}/...
|
||||
|
||||
cover:
|
||||
go test -race -coverprofile=coverage.txt -covermode=atomic ${DIR_PKG}/...
|
||||
|
@ -56,6 +56,51 @@ func TestCollect(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should not have access to variables defined before COLLECT", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
_, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
married: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
married: false,
|
||||
age: 25,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
married: false,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: false,
|
||||
married: true,
|
||||
age: 69,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
married: true,
|
||||
age: 45,
|
||||
gender: "f"
|
||||
}
|
||||
]
|
||||
FOR i IN users
|
||||
LET x = "foo"
|
||||
COLLECT gender = i.gender
|
||||
RETURN {x, gender}
|
||||
`)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should group result by a single key", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
|
@ -3,6 +3,8 @@ package compiler_test
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
@ -311,6 +313,60 @@ func TestForFilter(t *testing.T) {
|
||||
|
||||
So(string(out), ShouldEqual, `[]`)
|
||||
})
|
||||
|
||||
Convey("Should define variables", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
|
||||
LET x = 2
|
||||
FILTER i > x
|
||||
RETURN i + x
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[5,6,5]`)
|
||||
})
|
||||
|
||||
Convey("Should call funcions", t, func() {
|
||||
c := compiler.New()
|
||||
counterA := 0
|
||||
counterB := 0
|
||||
c.RegisterFunction("COUNT_A", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
counterA++
|
||||
|
||||
return values.None, nil
|
||||
})
|
||||
|
||||
c.RegisterFunction("COUNT_B", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
counterB++
|
||||
|
||||
return values.None, nil
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
|
||||
LET x = 2
|
||||
COUNT_A()
|
||||
FILTER i > x
|
||||
COUNT_B()
|
||||
RETURN i + x
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(counterA, ShouldEqual, 6)
|
||||
So(counterB, ShouldEqual, 3)
|
||||
So(string(out), ShouldEqual, `[5,6,5]`)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkFilter(b *testing.B) {
|
||||
|
@ -248,154 +248,6 @@ func TestFor(t *testing.T) {
|
||||
|
||||
So(string(out), ShouldEqual, `[1,2,3,4]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with LIMIT 2", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
|
||||
LIMIT 2
|
||||
RETURN i
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[1,2]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with LIMIT 2, 2", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
// 4 is offset
|
||||
// 2 is count
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1,2,3,4,5,6,7,8 ]
|
||||
LIMIT 4, 2
|
||||
RETURN i
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[5,6]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with SORT statement", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
SORT u.age
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":29,"gender":"f"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":36,"gender":"m"}]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with SORT DESC statement", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
SORT u.age DESC
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":36,"gender":"m"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":29,"gender":"f"}]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with SORT statement with multiple expressions", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
SORT u.age, u.gender
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":29,"gender":"f"},{"active":true,"age":31,"gender":"f"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":36,"gender":"m"}]`)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkForEmpty(b *testing.B) {
|
||||
|
96
pkg/compiler/compiler_limit_test.go
Normal file
96
pkg/compiler/compiler_limit_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package compiler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestForLimit(t *testing.T) {
|
||||
Convey("Should compile query with LIMIT 2", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
|
||||
LIMIT 2
|
||||
RETURN i
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[1,2]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with LIMIT 2, 2", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
// 4 is offset
|
||||
// 2 is count
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1,2,3,4,5,6,7,8 ]
|
||||
LIMIT 4, 2
|
||||
RETURN i
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[5,6]`)
|
||||
})
|
||||
|
||||
Convey("Should define variables and call functions", t, func() {
|
||||
c := compiler.New()
|
||||
counter := 0
|
||||
c.RegisterFunction("TEST", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
counter++
|
||||
|
||||
So(args[0], ShouldEqual, "foo")
|
||||
|
||||
return values.None, nil
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1,2,3,4,5,6,7,8 ]
|
||||
LET x = "foo"
|
||||
TEST(x)
|
||||
LIMIT 2
|
||||
RETURN i
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(counter, ShouldEqual, 2)
|
||||
So(string(out), ShouldEqual, `[1,2]`)
|
||||
})
|
||||
|
||||
Convey("Should be able to reuse values from a source", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
FOR i IN [ 1,2,3,4,5,6,7,8 ]
|
||||
LET x = i
|
||||
LIMIT 2
|
||||
RETURN i*x
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(string(out), ShouldEqual, `[1,4]`)
|
||||
})
|
||||
}
|
212
pkg/compiler/compiler_sort_test.go
Normal file
212
pkg/compiler/compiler_sort_test.go
Normal file
@ -0,0 +1,212 @@
|
||||
package compiler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/compiler"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestForSort(t *testing.T) {
|
||||
Convey("Should compile query with SORT statement", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
SORT u.age
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":29,"gender":"f"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":36,"gender":"m"}]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with SORT DESC statement", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
SORT u.age DESC
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":36,"gender":"m"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":29,"gender":"f"}]`)
|
||||
})
|
||||
|
||||
Convey("Should compile query with SORT statement with multiple expressions", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
SORT u.age, u.gender
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":29,"gender":"f"},{"active":true,"age":31,"gender":"f"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":36,"gender":"m"}]`)
|
||||
})
|
||||
|
||||
Convey("Should define variables and call functions", t, func() {
|
||||
c := compiler.New()
|
||||
counter := 0
|
||||
c.RegisterFunction("TEST", func(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
counter++
|
||||
|
||||
So(args[0], ShouldEqual, "foo")
|
||||
|
||||
return values.None, nil
|
||||
})
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
LET x = "foo"
|
||||
TEST(x)
|
||||
SORT u.age, u.gender
|
||||
RETURN u
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(counter, ShouldEqual, 4)
|
||||
So(string(out), ShouldEqual, `[{"active":true,"age":29,"gender":"f"},{"active":true,"age":31,"gender":"f"},{"active":true,"age":31,"gender":"m"},{"active":true,"age":36,"gender":"m"}]`)
|
||||
})
|
||||
|
||||
Convey("Should be able to reuse values from a source", t, func() {
|
||||
c := compiler.New()
|
||||
|
||||
p, err := c.Compile(`
|
||||
LET users = [
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "m"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 29,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 31,
|
||||
gender: "f"
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
age: 36,
|
||||
gender: "m"
|
||||
}
|
||||
]
|
||||
FOR u IN users
|
||||
LET x = u.gender
|
||||
SORT u.age, u.gender
|
||||
RETURN {u,x}
|
||||
`)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
out, err := p.Run(context.Background())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(string(out), ShouldEqual, `[{"u":{"active":true,"age":29,"gender":"f"},"x":"f"},{"u":{"active":true,"age":31,"gender":"f"},"x":"f"},{"u":{"active":true,"age":31,"gender":"m"},"x":"m"},{"u":{"active":true,"age":36,"gender":"m"},"x":"m"}]`)
|
||||
})
|
||||
}
|
11
pkg/compiler/compiler_test.go
Normal file
11
pkg/compiler/compiler_test.go
Normal file
@ -0,0 +1,11 @@
|
||||
package compiler_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
func NOOP(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.None, nil
|
||||
}
|
@ -2,7 +2,6 @@ package compiler
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -49,7 +48,7 @@ func (s *scope) SetVariable(name string) error {
|
||||
_, exists := s.vars[name]
|
||||
|
||||
if exists {
|
||||
return errors.Wrap(ErrVariableNotUnique, name)
|
||||
return core.Error(ErrVariableNotUnique, name)
|
||||
}
|
||||
|
||||
// TODO: add type detection
|
||||
@ -62,7 +61,7 @@ func (s *scope) RemoveVariable(name string) error {
|
||||
_, exists := s.vars[name]
|
||||
|
||||
if !exists {
|
||||
return errors.Wrap(ErrVariableNotFound, name)
|
||||
return core.Error(ErrVariableNotFound, name)
|
||||
}
|
||||
|
||||
delete(s.vars, name)
|
||||
@ -70,6 +69,10 @@ func (s *scope) RemoveVariable(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scope) ClearVariables() {
|
||||
s.vars = make(map[string]core.Type)
|
||||
}
|
||||
|
||||
func (s *scope) Fork() *scope {
|
||||
return newScope(s)
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func (v *visitor) VisitProgram(ctx *fql.ProgramContext) interface{} {
|
||||
|
||||
func (v *visitor) doVisitBody(ctx *fql.BodyContext, scope *scope) (core.Expression, error) {
|
||||
statements := ctx.AllBodyStatement()
|
||||
body := expressions.NewBlockExpression(len(statements) + 1)
|
||||
body := expressions.NewBodyExpression(len(statements) + 1)
|
||||
|
||||
for _, stmt := range statements {
|
||||
e, err := v.doVisitBodyStatement(stmt.(*fql.BodyStatementContext), scope)
|
||||
@ -91,7 +91,7 @@ func (v *visitor) doVisitBodyStatement(ctx *fql.BodyStatementContext, scope *sco
|
||||
return v.doVisitFunctionCallExpression(funcCall.(*fql.FunctionCallExpressionContext), scope)
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(ErrInvalidToken, ctx.GetText())
|
||||
return nil, core.Error(ErrInvalidToken, ctx.GetText())
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitBodyExpression(ctx *fql.BodyExpressionContext, scope *scope) (core.Expression, error) {
|
||||
@ -188,98 +188,41 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
|
||||
// Clauses.
|
||||
// We put clauses parsing before parsing the query body because COLLECT clause overrides scope variables
|
||||
for _, clause := range ctx.AllForExpressionClause() {
|
||||
clause := clause.(*fql.ForExpressionClauseContext)
|
||||
for _, e := range ctx.AllForExpressionBody() {
|
||||
e := e.(*fql.ForExpressionBodyContext)
|
||||
clauseCtx := e.ForExpressionClause()
|
||||
statementCtx := e.ForExpressionStatement()
|
||||
|
||||
limitCtx := clause.LimitClause()
|
||||
|
||||
if limitCtx != nil {
|
||||
limit, offset, err := v.createLimit(limitCtx.(*fql.LimitClauseContext))
|
||||
if clauseCtx != nil {
|
||||
setter, err := v.doVisitForExpressionClause(
|
||||
clauseCtx.(*fql.ForExpressionClauseContext),
|
||||
forInScope,
|
||||
valVarName,
|
||||
keyVarName,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||
return f.AddLimit(v.getSourceMap(limitCtx), limit, offset)
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
filterCtx := clause.FilterClause()
|
||||
|
||||
if filterCtx != nil {
|
||||
filterExp, err := v.createFilter(filterCtx.(*fql.FilterClauseContext), forInScope)
|
||||
parsedClauses = append(parsedClauses, setter)
|
||||
} else if statementCtx != nil {
|
||||
exp, err := v.doVisitForExpressionStatement(
|
||||
statementCtx.(*fql.ForExpressionStatementContext),
|
||||
forInScope,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||
return f.AddFilter(v.getSourceMap(filterCtx), filterExp)
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
sortCtx := clause.SortClause()
|
||||
|
||||
if sortCtx != nil {
|
||||
sortCtx := sortCtx.(*fql.SortClauseContext)
|
||||
sortExps, err := v.createSort(sortCtx, forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||
return f.AddSort(v.getSourceMap(sortCtx), sortExps...)
|
||||
})
|
||||
}
|
||||
|
||||
collectCtx := clause.CollectClause()
|
||||
|
||||
if collectCtx != nil {
|
||||
collectCtx := collectCtx.(*fql.CollectClauseContext)
|
||||
|
||||
params, err := v.createCollect(collectCtx, forInScope, valVarName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forInScope.RemoveVariable(valVarName)
|
||||
|
||||
if keyVarName != "" {
|
||||
forInScope.RemoveVariable(keyVarName)
|
||||
}
|
||||
|
||||
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||
return f.AddCollect(v.getSourceMap(collectCtx), params)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
body := ctx.AllForExpressionBody()
|
||||
predicate := expressions.NewBlockExpression(len(body) + 1)
|
||||
|
||||
for _, el := range body {
|
||||
el, err := v.doVisitForExpressionBody(el.(*fql.ForExpressionBodyContext), forInScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = predicate.Add(el)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
parsedClauses = append(parsedClauses, exp)
|
||||
}
|
||||
}
|
||||
|
||||
var spread bool
|
||||
var distinct bool
|
||||
var predicate core.Expression
|
||||
forRetCtx := ctx.ForExpressionReturn().(*fql.ForExpressionReturnContext)
|
||||
returnCtx := forRetCtx.ReturnExpression()
|
||||
|
||||
@ -297,7 +240,7 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
distinct = true
|
||||
}
|
||||
|
||||
predicate.Add(returnExp)
|
||||
predicate = returnExp
|
||||
} else {
|
||||
forInCtx := forRetCtx.ForExpression().(*fql.ForExpressionContext)
|
||||
forInExp, err := v.doVisitForExpression(forInCtx, forInScope)
|
||||
@ -308,7 +251,7 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
|
||||
spread = true
|
||||
|
||||
predicate.Add(forInExp)
|
||||
predicate = forInExp
|
||||
}
|
||||
|
||||
forExp, err := expressions.NewForExpression(
|
||||
@ -440,6 +383,7 @@ func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, val
|
||||
var projection *clauses.CollectProjection
|
||||
var count *clauses.CollectCount
|
||||
var aggregate *clauses.CollectAggregate
|
||||
variables := make([]string, 0, 10)
|
||||
|
||||
groupingCtx := ctx.CollectGrouping()
|
||||
|
||||
@ -459,10 +403,7 @@ func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, val
|
||||
}
|
||||
|
||||
selectors = append(selectors, selector)
|
||||
|
||||
if err := scope.SetVariable(selector.Variable()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
variables = append(variables, selector.Variable())
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,10 +462,7 @@ func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, val
|
||||
}
|
||||
|
||||
if projectionSelector != nil {
|
||||
if err := scope.SetVariable(projectionSelector.Variable()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
variables = append(variables, projectionSelector.Variable())
|
||||
projection, err = clauses.NewCollectProjection(projectionSelector)
|
||||
|
||||
if err != nil {
|
||||
@ -539,10 +477,7 @@ func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, val
|
||||
if countCtx != nil {
|
||||
countCtx := countCtx.(*fql.CollectCounterContext)
|
||||
variable := countCtx.Identifier().GetText()
|
||||
|
||||
if err := scope.SetVariable(variable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
variables = append(variables, variable)
|
||||
|
||||
count, err = clauses.NewCollectCount(variable)
|
||||
|
||||
@ -567,6 +502,7 @@ func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, val
|
||||
}
|
||||
|
||||
selectors = append(selectors, selector)
|
||||
variables = append(variables, selector.Variable())
|
||||
}
|
||||
|
||||
aggregate, err = clauses.NewCollectAggregate(selectors)
|
||||
@ -576,6 +512,15 @@ func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, val
|
||||
}
|
||||
}
|
||||
|
||||
// clear all variables defined before
|
||||
scope.ClearVariables()
|
||||
|
||||
for _, variable := range variables {
|
||||
if err := scope.SetVariable(variable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return clauses.NewCollect(selectors, projection, count, aggregate)
|
||||
}
|
||||
|
||||
@ -607,10 +552,6 @@ func (v *visitor) createCollectAggregateSelector(ctx *fql.CollectAggregateSelect
|
||||
return nil, core.Error(core.ErrInvalidType, "expected function expression")
|
||||
}
|
||||
|
||||
if err := scope.SetVariable(variable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clauses.NewCollectAggregateSelector(variable, fnExp.Arguments(), fnExp.Function())
|
||||
}
|
||||
|
||||
@ -663,17 +604,101 @@ func (v *visitor) doVisitForExpressionSource(ctx *fql.ForExpressionSourceContext
|
||||
return nil, core.Error(ErrInvalidDataSource, ctx.GetText())
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitForExpressionBody(ctx *fql.ForExpressionBodyContext, scope *scope) (core.Expression, error) {
|
||||
varDecCtx := ctx.VariableDeclaration()
|
||||
func (v *visitor) doVisitForExpressionClause(ctx *fql.ForExpressionClauseContext, scope *scope, valVarName, _ string) (func(f *expressions.ForExpression) error, error) {
|
||||
limitCtx := ctx.LimitClause()
|
||||
|
||||
if varDecCtx != nil {
|
||||
return v.doVisitVariableDeclaration(varDecCtx.(*fql.VariableDeclarationContext), scope)
|
||||
if limitCtx != nil {
|
||||
limit, offset, err := v.createLimit(limitCtx.(*fql.LimitClauseContext))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(f *expressions.ForExpression) error {
|
||||
return f.AddLimit(v.getSourceMap(limitCtx), limit, offset)
|
||||
}, nil
|
||||
}
|
||||
|
||||
funcCallCtx := ctx.FunctionCallExpression()
|
||||
filterCtx := ctx.FilterClause()
|
||||
|
||||
if funcCallCtx != nil {
|
||||
return v.doVisitFunctionCallExpression(funcCallCtx.(*fql.FunctionCallExpressionContext), scope)
|
||||
if filterCtx != nil {
|
||||
filterExp, err := v.createFilter(filterCtx.(*fql.FilterClauseContext), scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(f *expressions.ForExpression) error {
|
||||
return f.AddFilter(v.getSourceMap(filterCtx), filterExp)
|
||||
}, nil
|
||||
}
|
||||
|
||||
sortCtx := ctx.SortClause()
|
||||
|
||||
if sortCtx != nil {
|
||||
sortCtx := sortCtx.(*fql.SortClauseContext)
|
||||
sortExps, err := v.createSort(sortCtx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(f *expressions.ForExpression) error {
|
||||
return f.AddSort(v.getSourceMap(sortCtx), sortExps...)
|
||||
}, nil
|
||||
}
|
||||
|
||||
collectCtx := ctx.CollectClause()
|
||||
|
||||
if collectCtx != nil {
|
||||
collectCtx := collectCtx.(*fql.CollectClauseContext)
|
||||
params, err := v.createCollect(collectCtx, scope, valVarName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(f *expressions.ForExpression) error {
|
||||
return f.AddCollect(v.getSourceMap(collectCtx), params)
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, v.unexpectedToken(ctx)
|
||||
}
|
||||
|
||||
func (v *visitor) doVisitForExpressionStatement(ctx *fql.ForExpressionStatementContext, scope *scope) (func(f *expressions.ForExpression) error, error) {
|
||||
variableCtx := ctx.VariableDeclaration()
|
||||
|
||||
if variableCtx != nil {
|
||||
variableExp, err := v.doVisitVariableDeclaration(
|
||||
variableCtx.(*fql.VariableDeclarationContext),
|
||||
scope,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(f *expressions.ForExpression) error {
|
||||
return f.AddStatement(variableExp)
|
||||
}, nil
|
||||
}
|
||||
|
||||
fnCallCtx := ctx.FunctionCallExpression()
|
||||
|
||||
if fnCallCtx != nil {
|
||||
fnCallExp, err := v.doVisitFunctionCallExpression(
|
||||
fnCallCtx.(*fql.FunctionCallExpressionContext),
|
||||
scope,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(f *expressions.ForExpression) error {
|
||||
return f.AddStatement(fnCallExp)
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, v.unexpectedToken(ctx)
|
||||
@ -950,7 +975,7 @@ func (v *visitor) doVisitRangeOperator(ctx *fql.RangeOperatorContext, scope *sco
|
||||
}
|
||||
|
||||
if len(exp) < 2 {
|
||||
return nil, errors.Wrap(ErrInvalidToken, ctx.GetText())
|
||||
return nil, core.Error(ErrInvalidToken, ctx.GetText())
|
||||
}
|
||||
|
||||
left := exp[0]
|
||||
|
@ -28,7 +28,6 @@ returnExpression
|
||||
|
||||
forExpression
|
||||
: For forExpressionValueVariable (Comma forExpressionKeyVariable)? In forExpressionSource
|
||||
(forExpressionClause)*
|
||||
(forExpressionBody)*
|
||||
forExpressionReturn
|
||||
;
|
||||
@ -58,6 +57,21 @@ forExpressionClause
|
||||
| collectClause
|
||||
;
|
||||
|
||||
forExpressionStatement
|
||||
: variableDeclaration
|
||||
| functionCallExpression
|
||||
;
|
||||
|
||||
forExpressionBody
|
||||
: forExpressionStatement
|
||||
| forExpressionClause
|
||||
;
|
||||
|
||||
forExpressionReturn
|
||||
: returnExpression
|
||||
| forExpression
|
||||
;
|
||||
|
||||
filterClause
|
||||
: Filter expression
|
||||
;
|
||||
@ -108,16 +122,6 @@ collectCounter
|
||||
: With Count Into Identifier
|
||||
;
|
||||
|
||||
forExpressionBody
|
||||
: variableDeclaration
|
||||
| functionCallExpression
|
||||
;
|
||||
|
||||
forExpressionReturn
|
||||
: returnExpression
|
||||
| forExpression
|
||||
;
|
||||
|
||||
variableDeclaration
|
||||
: Let Identifier Assign expression
|
||||
| Let Identifier Assign OpenParen forExpression CloseParen
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -82,6 +82,24 @@ func (s *BaseFqlParserListener) EnterForExpressionClause(ctx *ForExpressionClaus
|
||||
// ExitForExpressionClause is called when production forExpressionClause is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionClause(ctx *ForExpressionClauseContext) {}
|
||||
|
||||
// EnterForExpressionStatement is called when production forExpressionStatement is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionStatement(ctx *ForExpressionStatementContext) {}
|
||||
|
||||
// ExitForExpressionStatement is called when production forExpressionStatement is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionStatement(ctx *ForExpressionStatementContext) {}
|
||||
|
||||
// EnterForExpressionBody is called when production forExpressionBody is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
||||
// ExitForExpressionBody is called when production forExpressionBody is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
||||
// EnterForExpressionReturn is called when production forExpressionReturn is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionReturn(ctx *ForExpressionReturnContext) {}
|
||||
|
||||
// ExitForExpressionReturn is called when production forExpressionReturn is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionReturn(ctx *ForExpressionReturnContext) {}
|
||||
|
||||
// EnterFilterClause is called when production filterClause is entered.
|
||||
func (s *BaseFqlParserListener) EnterFilterClause(ctx *FilterClauseContext) {}
|
||||
|
||||
@ -148,18 +166,6 @@ func (s *BaseFqlParserListener) EnterCollectCounter(ctx *CollectCounterContext)
|
||||
// ExitCollectCounter is called when production collectCounter is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectCounter(ctx *CollectCounterContext) {}
|
||||
|
||||
// EnterForExpressionBody is called when production forExpressionBody is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
||||
// ExitForExpressionBody is called when production forExpressionBody is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
||||
// EnterForExpressionReturn is called when production forExpressionReturn is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionReturn(ctx *ForExpressionReturnContext) {}
|
||||
|
||||
// ExitForExpressionReturn is called when production forExpressionReturn is exited.
|
||||
func (s *BaseFqlParserListener) ExitForExpressionReturn(ctx *ForExpressionReturnContext) {}
|
||||
|
||||
// EnterVariableDeclaration is called when production variableDeclaration is entered.
|
||||
func (s *BaseFqlParserListener) EnterVariableDeclaration(ctx *VariableDeclarationContext) {}
|
||||
|
||||
|
@ -47,6 +47,18 @@ func (v *BaseFqlParserVisitor) VisitForExpressionClause(ctx *ForExpressionClause
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionStatement(ctx *ForExpressionStatementContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionReturn(ctx *ForExpressionReturnContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitFilterClause(ctx *FilterClauseContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
@ -91,14 +103,6 @@ func (v *BaseFqlParserVisitor) VisitCollectCounter(ctx *CollectCounterContext) i
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitForExpressionReturn(ctx *ForExpressionReturnContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitVariableDeclaration(ctx *VariableDeclarationContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
@ -37,6 +37,15 @@ type FqlParserListener interface {
|
||||
// EnterForExpressionClause is called when entering the forExpressionClause production.
|
||||
EnterForExpressionClause(c *ForExpressionClauseContext)
|
||||
|
||||
// EnterForExpressionStatement is called when entering the forExpressionStatement production.
|
||||
EnterForExpressionStatement(c *ForExpressionStatementContext)
|
||||
|
||||
// EnterForExpressionBody is called when entering the forExpressionBody production.
|
||||
EnterForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
||||
// EnterForExpressionReturn is called when entering the forExpressionReturn production.
|
||||
EnterForExpressionReturn(c *ForExpressionReturnContext)
|
||||
|
||||
// EnterFilterClause is called when entering the filterClause production.
|
||||
EnterFilterClause(c *FilterClauseContext)
|
||||
|
||||
@ -70,12 +79,6 @@ type FqlParserListener interface {
|
||||
// EnterCollectCounter is called when entering the collectCounter production.
|
||||
EnterCollectCounter(c *CollectCounterContext)
|
||||
|
||||
// EnterForExpressionBody is called when entering the forExpressionBody production.
|
||||
EnterForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
||||
// EnterForExpressionReturn is called when entering the forExpressionReturn production.
|
||||
EnterForExpressionReturn(c *ForExpressionReturnContext)
|
||||
|
||||
// EnterVariableDeclaration is called when entering the variableDeclaration production.
|
||||
EnterVariableDeclaration(c *VariableDeclarationContext)
|
||||
|
||||
@ -190,6 +193,15 @@ type FqlParserListener interface {
|
||||
// ExitForExpressionClause is called when exiting the forExpressionClause production.
|
||||
ExitForExpressionClause(c *ForExpressionClauseContext)
|
||||
|
||||
// ExitForExpressionStatement is called when exiting the forExpressionStatement production.
|
||||
ExitForExpressionStatement(c *ForExpressionStatementContext)
|
||||
|
||||
// ExitForExpressionBody is called when exiting the forExpressionBody production.
|
||||
ExitForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
||||
// ExitForExpressionReturn is called when exiting the forExpressionReturn production.
|
||||
ExitForExpressionReturn(c *ForExpressionReturnContext)
|
||||
|
||||
// ExitFilterClause is called when exiting the filterClause production.
|
||||
ExitFilterClause(c *FilterClauseContext)
|
||||
|
||||
@ -223,12 +235,6 @@ type FqlParserListener interface {
|
||||
// ExitCollectCounter is called when exiting the collectCounter production.
|
||||
ExitCollectCounter(c *CollectCounterContext)
|
||||
|
||||
// ExitForExpressionBody is called when exiting the forExpressionBody production.
|
||||
ExitForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
||||
// ExitForExpressionReturn is called when exiting the forExpressionReturn production.
|
||||
ExitForExpressionReturn(c *ForExpressionReturnContext)
|
||||
|
||||
// ExitVariableDeclaration is called when exiting the variableDeclaration production.
|
||||
ExitVariableDeclaration(c *VariableDeclarationContext)
|
||||
|
||||
|
@ -37,6 +37,15 @@ type FqlParserVisitor interface {
|
||||
// Visit a parse tree produced by FqlParser#forExpressionClause.
|
||||
VisitForExpressionClause(ctx *ForExpressionClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionStatement.
|
||||
VisitForExpressionStatement(ctx *ForExpressionStatementContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionBody.
|
||||
VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionReturn.
|
||||
VisitForExpressionReturn(ctx *ForExpressionReturnContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#filterClause.
|
||||
VisitFilterClause(ctx *FilterClauseContext) interface{}
|
||||
|
||||
@ -70,12 +79,6 @@ type FqlParserVisitor interface {
|
||||
// Visit a parse tree produced by FqlParser#collectCounter.
|
||||
VisitCollectCounter(ctx *CollectCounterContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionBody.
|
||||
VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionReturn.
|
||||
VisitForExpressionReturn(ctx *ForExpressionReturnContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#variableDeclaration.
|
||||
VisitVariableDeclaration(ctx *VariableDeclarationContext) interface{}
|
||||
|
||||
|
@ -1,108 +0,0 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type DataSet map[string]core.Value
|
||||
|
||||
func NewDataSet() DataSet {
|
||||
return make(DataSet)
|
||||
}
|
||||
|
||||
func (ds DataSet) Apply(scope *core.Scope, variables Variables) error {
|
||||
if err := ValidateDataSet(ds, variables); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, variable := range variables {
|
||||
if variable != "" {
|
||||
value, found := ds[variable]
|
||||
|
||||
if !found {
|
||||
return core.Errorf(core.ErrNotFound, "variable not found in a given data set: %s", variable)
|
||||
}
|
||||
|
||||
scope.SetVariable(variable, value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds DataSet) Set(key string, value core.Value) {
|
||||
ds[key] = value
|
||||
}
|
||||
|
||||
func (ds DataSet) Get(key string) core.Value {
|
||||
val, found := ds[key]
|
||||
|
||||
if found {
|
||||
return val
|
||||
}
|
||||
|
||||
return values.None
|
||||
}
|
||||
|
||||
func (ds DataSet) Hash() uint64 {
|
||||
h := fnv.New64a()
|
||||
|
||||
keys := make([]string, 0, len(ds))
|
||||
|
||||
for key := range ds {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// order does not really matter
|
||||
// but it will give us a consistent hash sum
|
||||
sort.Strings(keys)
|
||||
endIndex := len(keys) - 1
|
||||
|
||||
h.Write([]byte("{"))
|
||||
|
||||
for idx, key := range keys {
|
||||
h.Write([]byte(key))
|
||||
h.Write([]byte(":"))
|
||||
|
||||
el := ds[key]
|
||||
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, el.Hash())
|
||||
|
||||
h.Write(bytes)
|
||||
|
||||
if idx != endIndex {
|
||||
h.Write([]byte(","))
|
||||
}
|
||||
}
|
||||
|
||||
h.Write([]byte("}"))
|
||||
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func (ds DataSet) Compare(other DataSet) int {
|
||||
if len(ds) > len(ds) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(ds) < len(ds) {
|
||||
return -1
|
||||
}
|
||||
|
||||
var res = 0
|
||||
|
||||
for key, otherVal := range other {
|
||||
res = -1
|
||||
|
||||
if val, exists := ds[key]; exists {
|
||||
res = val.Compare(otherVal)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrExhausted = core.Error(core.ErrInvalidOperation, "iterator has been exhausted")
|
||||
ErrResultSetMismatch = core.Error(core.ErrInvalidArgument, "count of values in result set is less that count of variables")
|
||||
)
|
||||
|
||||
func ValidateDataSet(set DataSet, variables Variables) error {
|
||||
if len(variables) > len(set) {
|
||||
return ErrResultSetMismatch
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,80 +1,47 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
FilterPredicate func(set DataSet) (bool, error)
|
||||
FilterPredicate func(ctx context.Context, scope *core.Scope) (bool, error)
|
||||
|
||||
FilterIterator struct {
|
||||
src Iterator
|
||||
values Iterator
|
||||
predicate FilterPredicate
|
||||
dataSet DataSet
|
||||
ready bool
|
||||
}
|
||||
)
|
||||
|
||||
func NewFilterIterator(src Iterator, predicate FilterPredicate) (*FilterIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "source")
|
||||
func NewFilterIterator(values Iterator, predicate FilterPredicate) (*FilterIterator, error) {
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
if core.IsNil(predicate) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "predicate")
|
||||
if predicate == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "predicate")
|
||||
}
|
||||
|
||||
return &FilterIterator{src: src, predicate: predicate}, nil
|
||||
return &FilterIterator{values: values, predicate: predicate}, nil
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) HasNext() bool {
|
||||
if !iterator.ready {
|
||||
iterator.filter()
|
||||
iterator.ready = true
|
||||
}
|
||||
|
||||
return iterator.dataSet != nil
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) Next() (DataSet, error) {
|
||||
if iterator.HasNext() == true {
|
||||
ds := iterator.dataSet
|
||||
|
||||
iterator.filter()
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) filter() {
|
||||
var doNext bool
|
||||
|
||||
for iterator.src.HasNext() {
|
||||
set, err := iterator.src.Next()
|
||||
func (iterator *FilterIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
for {
|
||||
nextScope, err := iterator.values.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
doNext = false
|
||||
break
|
||||
return nil, err
|
||||
}
|
||||
|
||||
take, err := iterator.predicate(set)
|
||||
|
||||
if err != nil {
|
||||
doNext = false
|
||||
break
|
||||
if nextScope == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
take, err := iterator.predicate(ctx, nextScope)
|
||||
|
||||
if take == true {
|
||||
doNext = true
|
||||
iterator.dataSet = set
|
||||
break
|
||||
return nextScope, nil
|
||||
}
|
||||
}
|
||||
|
||||
if doNext == false {
|
||||
iterator.dataSet = nil
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
@ -11,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
Convey("Should filter out non-even values", t, func() {
|
||||
Convey("Should filter out non-even result", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
@ -20,8 +21,8 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(ds collections.DataSet) (bool, error) {
|
||||
i := float64(ds.Get(collections.DefaultValueVar).Unwrap().(int))
|
||||
predicate := func(_ context.Context, scope *core.Scope) (bool, error) {
|
||||
i := float64(scope.MustGetVariable(collections.DefaultValueVar).Unwrap().(int))
|
||||
calc := float64(i / 2)
|
||||
|
||||
return calc == math.Floor(calc), nil
|
||||
@ -34,16 +35,10 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
scope, _ := core.NewRootScope()
|
||||
res, err := collections.ToSlice(context.Background(), scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(res, ShouldHaveLength, 2)
|
||||
})
|
||||
|
||||
@ -56,8 +51,8 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(ds collections.DataSet) (bool, error) {
|
||||
i := float64(ds.Get(collections.DefaultKeyVar).Unwrap().(int))
|
||||
predicate := func(_ context.Context, scope *core.Scope) (bool, error) {
|
||||
i := float64(scope.MustGetVariable(collections.DefaultKeyVar).Unwrap().(int))
|
||||
|
||||
if i == 0 {
|
||||
return false, nil
|
||||
@ -75,20 +70,15 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
scope, _ := core.NewRootScope()
|
||||
res, err := collections.ToSlice(context.Background(), scope, iter)
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(res, ShouldHaveLength, 2)
|
||||
})
|
||||
|
||||
Convey("Should filter out values all values", t, func() {
|
||||
Convey("Should filter out result all result", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
@ -97,7 +87,7 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(_ collections.DataSet) (bool, error) {
|
||||
predicate := func(_ context.Context, _ *core.Scope) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -108,20 +98,14 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
scope, _ := core.NewRootScope()
|
||||
res, err := collections.ToSlice(context.Background(), scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(res, ShouldHaveLength, 0)
|
||||
})
|
||||
|
||||
Convey("Should pass through all values", t, func() {
|
||||
Convey("Should pass through all result", t, func() {
|
||||
arr := []core.Value{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
@ -130,7 +114,7 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(_ collections.DataSet) (bool, error) {
|
||||
predicate := func(_ context.Context, _ *core.Scope) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@ -141,16 +125,10 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
scope, _ := core.NewRootScope()
|
||||
res, err := collections.ToSlice(context.Background(), scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(res, ShouldHaveLength, len(arr))
|
||||
})
|
||||
|
||||
@ -163,7 +141,7 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(_ collections.DataSet) (bool, error) {
|
||||
predicate := func(_ context.Context, _ *core.Scope) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@ -174,20 +152,15 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
scope, _ := core.NewRootScope()
|
||||
_, err = collections.ToSlice(context.Background(), scope, iter)
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := next(iter)
|
||||
item, err := iter.Next(context.Background(), scope)
|
||||
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeError)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should iterate over nested filter", t, func() {
|
||||
@ -200,13 +173,13 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
// i < 5
|
||||
predicate1 := func(ds collections.DataSet) (bool, error) {
|
||||
return ds.Get(collections.DefaultValueVar).Compare(values.NewInt(5)) == -1, nil
|
||||
predicate1 := func(_ context.Context, scope *core.Scope) (bool, error) {
|
||||
return scope.MustGetVariable(collections.DefaultValueVar).Compare(values.NewInt(5)) == -1, nil
|
||||
}
|
||||
|
||||
// i > 2
|
||||
predicate2 := func(ds collections.DataSet) (bool, error) {
|
||||
return ds.Get(collections.DefaultValueVar).Compare(values.NewInt(2)) == 1, nil
|
||||
predicate2 := func(_ context.Context, scope *core.Scope) (bool, error) {
|
||||
return scope.MustGetVariable(collections.DefaultValueVar).Compare(values.NewInt(2)) == 1, nil
|
||||
}
|
||||
|
||||
it, _ := collections.NewFilterIterator(
|
||||
@ -221,7 +194,8 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(iter)
|
||||
scope, _ := core.NewRootScope()
|
||||
sets, err := collections.ToSlice(context.Background(), scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
@ -14,27 +16,40 @@ type HTMLNodeIterator struct {
|
||||
func NewHTMLNodeIterator(
|
||||
valVar,
|
||||
keyVar string,
|
||||
input values.HTMLNode,
|
||||
) Iterator {
|
||||
return &HTMLNodeIterator{valVar, keyVar, input, 0}
|
||||
values values.HTMLNode,
|
||||
) (Iterator, error) {
|
||||
if valVar == "" {
|
||||
return nil, core.Error(core.ErrMissedArgument, "value variable")
|
||||
}
|
||||
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
return &HTMLNodeIterator{valVar, keyVar, values, 0}, nil
|
||||
}
|
||||
|
||||
func (iterator *HTMLNodeIterator) HasNext() bool {
|
||||
return iterator.values.Length() > values.NewInt(iterator.pos)
|
||||
}
|
||||
|
||||
func (iterator *HTMLNodeIterator) Next() (DataSet, error) {
|
||||
func (iterator *HTMLNodeIterator) Next(_ context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
if iterator.values.Length() > values.NewInt(iterator.pos) {
|
||||
idx := values.NewInt(iterator.pos)
|
||||
val := iterator.values.GetChildNode(idx)
|
||||
|
||||
iterator.pos++
|
||||
|
||||
return DataSet{
|
||||
iterator.valVar: val,
|
||||
iterator.keyVar: idx,
|
||||
}, nil
|
||||
nextScope := scope.Fork()
|
||||
|
||||
if err := nextScope.SetVariable(iterator.valVar, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if iterator.keyVar != "" {
|
||||
if err := nextScope.SetVariable(iterator.keyVar, idx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
@ -19,32 +21,46 @@ type IndexedIterator struct {
|
||||
func NewIndexedIterator(
|
||||
valVar,
|
||||
keyVar string,
|
||||
input IndexedCollection,
|
||||
) Iterator {
|
||||
return &IndexedIterator{valVar, keyVar, input, 0}
|
||||
values IndexedCollection,
|
||||
) (Iterator, error) {
|
||||
if valVar == "" {
|
||||
return nil, core.Error(core.ErrMissedArgument, "value variable")
|
||||
}
|
||||
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
return &IndexedIterator{valVar, keyVar, values, 0}, nil
|
||||
}
|
||||
|
||||
func NewDefaultIndexedIterator(
|
||||
input IndexedCollection,
|
||||
) Iterator {
|
||||
return &IndexedIterator{DefaultValueVar, DefaultKeyVar, input, 0}
|
||||
values IndexedCollection,
|
||||
) (Iterator, error) {
|
||||
return NewIndexedIterator(DefaultValueVar, DefaultKeyVar, values)
|
||||
}
|
||||
|
||||
func (iterator *IndexedIterator) HasNext() bool {
|
||||
return int(iterator.values.Length()) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *IndexedIterator) Next() (DataSet, error) {
|
||||
func (iterator *IndexedIterator) Next(_ context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
if int(iterator.values.Length()) > iterator.pos {
|
||||
idx := values.NewInt(iterator.pos)
|
||||
val := iterator.values.Get(idx)
|
||||
|
||||
iterator.pos++
|
||||
|
||||
return DataSet{
|
||||
iterator.valVar: val,
|
||||
iterator.keyVar: idx,
|
||||
}, nil
|
||||
nextScope := scope.Fork()
|
||||
|
||||
if err := nextScope.SetVariable(iterator.valVar, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if iterator.keyVar != "" {
|
||||
if err := nextScope.SetVariable(iterator.keyVar, idx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -8,25 +9,13 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func next(iterator collections.Iterator) (core.Value, core.Value, error) {
|
||||
ds, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
val := ds[collections.DefaultValueVar]
|
||||
key := ds[collections.DefaultKeyVar]
|
||||
|
||||
return val, key, nil
|
||||
}
|
||||
|
||||
func arrayIterator(arr *values.Array) collections.Iterator {
|
||||
return collections.NewDefaultIndexedIterator(arr)
|
||||
iterator, _ := collections.NewDefaultIndexedIterator(arr)
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
func TestArrayIterator(t *testing.T) {
|
||||
|
||||
Convey("Should iterate over an array", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
@ -42,13 +31,19 @@ func TestArrayIterator(t *testing.T) {
|
||||
|
||||
pos := 0
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := next(iter)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for {
|
||||
nextScope, err := iter.Next(ctx, scope.Fork())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(key.Unwrap(), ShouldEqual, pos)
|
||||
|
||||
res = append(res, item)
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
res = append(res, nextScope.MustGetVariable(collections.DefaultValueVar))
|
||||
|
||||
pos += 1
|
||||
}
|
||||
@ -69,12 +64,19 @@ func TestArrayIterator(t *testing.T) {
|
||||
|
||||
res := make([]core.Value, 0, arr.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for {
|
||||
nextScope, err := iter.Next(ctx, scope.Fork())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
res = append(res, nextScope.MustGetVariable(collections.DefaultValueVar))
|
||||
}
|
||||
|
||||
arr.ForEach(func(expected core.Value, idx int) bool {
|
||||
@ -99,18 +101,25 @@ func TestArrayIterator(t *testing.T) {
|
||||
|
||||
res := make([]core.Value, 0, arr.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for {
|
||||
nextScope, err := iter.Next(ctx, scope.Fork())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
res = append(res, nextScope.MustGetVariable(collections.DefaultValueVar))
|
||||
}
|
||||
|
||||
item, _, err := next(iter)
|
||||
item, err := iter.Next(ctx, scope)
|
||||
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeError)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over an empty array", t, func() {
|
||||
@ -118,12 +127,12 @@ func TestArrayIterator(t *testing.T) {
|
||||
|
||||
iter := arrayIterator(arr)
|
||||
|
||||
var iterated bool
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
nextScope, err := iter.Next(ctx, scope)
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
So(err, ShouldBeNil)
|
||||
So(nextScope, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
@ -6,31 +6,29 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
Variables []string
|
||||
|
||||
Iterator interface {
|
||||
HasNext() bool
|
||||
Next() (DataSet, error)
|
||||
Next(ctx context.Context, scope *core.Scope) (*core.Scope, error)
|
||||
}
|
||||
|
||||
Iterable interface {
|
||||
Variables() Variables
|
||||
Iterate(ctx context.Context, scope *core.Scope) (Iterator, error)
|
||||
}
|
||||
)
|
||||
|
||||
func ToSlice(iterator Iterator) ([]DataSet, error) {
|
||||
res := make([]DataSet, 0, 10)
|
||||
func ToSlice(ctx context.Context, scope *core.Scope, iterator Iterator) ([]*core.Scope, error) {
|
||||
res := make([]*core.Scope, 0, 10)
|
||||
|
||||
for iterator.HasNext() {
|
||||
ds, err := iterator.Next()
|
||||
for {
|
||||
nextScope, err := iterator.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, ds)
|
||||
}
|
||||
if nextScope == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
res = append(res, nextScope)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
@ -15,35 +17,48 @@ type KeyedIterator struct {
|
||||
func NewKeyedIterator(
|
||||
valVar,
|
||||
keyVar string,
|
||||
input KeyedCollection,
|
||||
) Iterator {
|
||||
return &KeyedIterator{valVar, keyVar, input, nil, 0}
|
||||
values KeyedCollection,
|
||||
) (Iterator, error) {
|
||||
if valVar == "" {
|
||||
return nil, core.Error(core.ErrMissedArgument, "value variable")
|
||||
}
|
||||
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
return &KeyedIterator{valVar, keyVar, values, nil, 0}, nil
|
||||
}
|
||||
|
||||
func NewDefaultKeyedIterator(input KeyedCollection) Iterator {
|
||||
func NewDefaultKeyedIterator(input KeyedCollection) (Iterator, error) {
|
||||
return NewKeyedIterator(DefaultValueVar, DefaultKeyVar, input)
|
||||
}
|
||||
|
||||
func (iterator *KeyedIterator) HasNext() bool {
|
||||
// lazy initialization
|
||||
func (iterator *KeyedIterator) Next(_ context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
if iterator.keys == nil {
|
||||
iterator.keys = iterator.values.Keys()
|
||||
}
|
||||
|
||||
return len(iterator.keys) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *KeyedIterator) Next() (DataSet, error) {
|
||||
if len(iterator.keys) > iterator.pos {
|
||||
key := values.NewString(iterator.keys[iterator.pos])
|
||||
val, _ := iterator.values.Get(key)
|
||||
|
||||
iterator.pos++
|
||||
|
||||
return DataSet{
|
||||
iterator.valVar: val,
|
||||
iterator.keyVar: key,
|
||||
}, nil
|
||||
nextScope := scope.Fork()
|
||||
|
||||
if err := nextScope.SetVariable(iterator.valVar, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if iterator.keyVar != "" {
|
||||
if err := nextScope.SetVariable(iterator.keyVar, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -9,7 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func objectIterator(obj *values.Object) collections.Iterator {
|
||||
return collections.NewDefaultKeyedIterator(obj)
|
||||
iter, _ := collections.NewDefaultKeyedIterator(obj)
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func TestObjectIterator(t *testing.T) {
|
||||
@ -26,11 +29,21 @@ func TestObjectIterator(t *testing.T) {
|
||||
|
||||
res := make([]core.Value, 0, m.Length())
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := next(iter)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for {
|
||||
nextScope, err := iter.Next(ctx, scope)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
key := nextScope.MustGetVariable(collections.DefaultKeyVar)
|
||||
item := nextScope.MustGetVariable(collections.DefaultValueVar)
|
||||
|
||||
expected, exists := m.Get(values.NewString(key.String()))
|
||||
|
||||
So(bool(exists), ShouldBeTrue)
|
||||
@ -53,20 +66,18 @@ func TestObjectIterator(t *testing.T) {
|
||||
|
||||
iter := objectIterator(m)
|
||||
|
||||
res := make([]core.Value, 0, m.Length())
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
nextScope, err := iter.Next(ctx, scope)
|
||||
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeError)
|
||||
So(nextScope, ShouldBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over a empty map", t, func() {
|
||||
@ -74,12 +85,12 @@ func TestObjectIterator(t *testing.T) {
|
||||
|
||||
iter := objectIterator(m)
|
||||
|
||||
var iterated bool
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
nextScope, err := iter.Next(ctx, scope)
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
So(nextScope, ShouldBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
@ -1,60 +1,62 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LimitIterator struct {
|
||||
src Iterator
|
||||
values Iterator
|
||||
count int
|
||||
offset int
|
||||
currCount int
|
||||
}
|
||||
|
||||
func NewLimitIterator(src Iterator, count, offset int) (*LimitIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "source")
|
||||
func NewLimitIterator(values Iterator, count, offset int) (*LimitIterator, error) {
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
return &LimitIterator{src, count, offset, 0}, nil
|
||||
return &LimitIterator{values, count, offset, 0}, nil
|
||||
}
|
||||
|
||||
func (i *LimitIterator) HasNext() bool {
|
||||
i.verifyOffset()
|
||||
|
||||
if i.src.HasNext() == false {
|
||||
return false
|
||||
func (iterator *LimitIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
if err := iterator.verifyOffset(ctx, scope); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i.counter() < i.count
|
||||
iterator.currCount++
|
||||
|
||||
if iterator.counter() <= iterator.count {
|
||||
return iterator.values.Next(ctx, scope)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (i *LimitIterator) Next() (DataSet, error) {
|
||||
if i.counter() <= i.count {
|
||||
i.currCount++
|
||||
|
||||
return i.src.Next()
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
func (iterator *LimitIterator) counter() int {
|
||||
return iterator.currCount - iterator.offset
|
||||
}
|
||||
|
||||
func (i *LimitIterator) counter() int {
|
||||
return i.currCount - i.offset
|
||||
}
|
||||
|
||||
func (i *LimitIterator) verifyOffset() {
|
||||
if i.offset == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if (i.offset < i.currCount) || i.src.HasNext() == false {
|
||||
return
|
||||
}
|
||||
|
||||
for (i.offset > i.currCount) && i.src.HasNext() {
|
||||
i.currCount++
|
||||
i.src.Next()
|
||||
}
|
||||
func (iterator *LimitIterator) verifyOffset(ctx context.Context, scope *core.Scope) error {
|
||||
if iterator.offset == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for iterator.offset > iterator.currCount {
|
||||
nextScope, err := iterator.values.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nextScope == nil {
|
||||
iterator.currCount = iterator.offset
|
||||
return nil
|
||||
}
|
||||
|
||||
iterator.currCount++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -18,7 +19,7 @@ func TestLimit(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
iter, err := collections.NewLimitIterator(
|
||||
sliceIterator(arr),
|
||||
1,
|
||||
0,
|
||||
@ -26,16 +27,12 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(res), ShouldEqual, 1)
|
||||
})
|
||||
|
||||
@ -48,7 +45,7 @@ func TestLimit(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
iter, err := collections.NewLimitIterator(
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
0,
|
||||
@ -56,16 +53,12 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(res), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
@ -79,7 +72,7 @@ func TestLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
offset := 2
|
||||
src, err := collections.NewLimitIterator(
|
||||
iter, err := collections.NewLimitIterator(
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
@ -87,20 +80,17 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(res), ShouldEqual, 2)
|
||||
|
||||
for idx, current := range res {
|
||||
for idx, nextScope := range res {
|
||||
expected := arr[idx+offset]
|
||||
current := nextScope.MustGetVariable(collections.DefaultValueVar)
|
||||
|
||||
So(expected, ShouldEqual, current)
|
||||
}
|
||||
@ -117,7 +107,7 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
offset := 3
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
iter, err := collections.NewLimitIterator(
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
@ -125,20 +115,17 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(res), ShouldEqual, 2)
|
||||
|
||||
for idx, current := range res {
|
||||
for idx, nextScope := range res {
|
||||
expected := arr[idx+offset]
|
||||
current := nextScope.MustGetVariable(collections.DefaultValueVar)
|
||||
|
||||
So(expected, ShouldEqual, current)
|
||||
}
|
||||
@ -155,7 +142,7 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
offset := 4
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
iter, err := collections.NewLimitIterator(
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
@ -163,16 +150,12 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(res), ShouldEqual, 1)
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
@ -16,18 +17,24 @@ type MapIterator struct {
|
||||
func NewMapIterator(
|
||||
valVar,
|
||||
keyVar string,
|
||||
input map[string]core.Value,
|
||||
) Iterator {
|
||||
return &MapIterator{valVar, keyVar, input, nil, 0}
|
||||
values map[string]core.Value,
|
||||
) (Iterator, error) {
|
||||
if valVar == "" {
|
||||
return nil, core.Error(core.ErrMissedArgument, "value variable")
|
||||
}
|
||||
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
return &MapIterator{valVar, keyVar, values, nil, 0}, nil
|
||||
}
|
||||
|
||||
func NewDefaultMapIterator(
|
||||
input map[string]core.Value,
|
||||
) Iterator {
|
||||
return &MapIterator{DefaultValueVar, DefaultKeyVar, input, nil, 0}
|
||||
func NewDefaultMapIterator(values map[string]core.Value) (Iterator, error) {
|
||||
return NewMapIterator(DefaultValueVar, DefaultKeyVar, values)
|
||||
}
|
||||
|
||||
func (iterator *MapIterator) HasNext() bool {
|
||||
func (iterator *MapIterator) Next(_ context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
// lazy initialization
|
||||
if iterator.keys == nil {
|
||||
keys := make([]string, len(iterator.values))
|
||||
@ -41,20 +48,26 @@ func (iterator *MapIterator) HasNext() bool {
|
||||
iterator.keys = keys
|
||||
}
|
||||
|
||||
return len(iterator.keys) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *MapIterator) Next() (DataSet, error) {
|
||||
if len(iterator.keys) > iterator.pos {
|
||||
key := iterator.keys[iterator.pos]
|
||||
val := iterator.values[key]
|
||||
|
||||
iterator.pos++
|
||||
|
||||
return DataSet{
|
||||
iterator.valVar: val,
|
||||
iterator.keyVar: values.NewString(key),
|
||||
}, nil
|
||||
nextScope := scope.Fork()
|
||||
|
||||
if err := nextScope.SetVariable(iterator.valVar, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if iterator.keyVar != "" {
|
||||
if err := nextScope.SetVariable(iterator.keyVar, values.NewString(key)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -9,7 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func mapIterator(m map[string]core.Value) collections.Iterator {
|
||||
return collections.NewDefaultMapIterator(m)
|
||||
iter, _ := collections.NewDefaultMapIterator(m)
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func TestMapIterator(t *testing.T) {
|
||||
@ -25,12 +28,21 @@ func TestMapIterator(t *testing.T) {
|
||||
iter := mapIterator(m)
|
||||
|
||||
res := make([]core.Value, 0, len(m))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := next(iter)
|
||||
for {
|
||||
nextScope, err := iter.Next(ctx, scope)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
key := nextScope.MustGetVariable(collections.DefaultKeyVar)
|
||||
item := nextScope.MustGetVariable(collections.DefaultValueVar)
|
||||
|
||||
expected, exists := m[key.String()]
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
@ -52,21 +64,16 @@ func TestMapIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
iter := mapIterator(m)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
res := make([]core.Value, 0, len(m))
|
||||
_, err := collections.ToSlice(ctx, scope, iter)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := next(iter)
|
||||
item, err := iter.Next(ctx, scope)
|
||||
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeError)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over a empty map", t, func() {
|
||||
@ -74,12 +81,12 @@ func TestMapIterator(t *testing.T) {
|
||||
|
||||
iter := mapIterator(m)
|
||||
|
||||
var iterated bool
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
item, err := iter.Next(ctx, scope)
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
package collections
|
||||
|
||||
type noopIterator struct{}
|
||||
|
||||
var NoopIterator = &noopIterator{}
|
||||
|
||||
func (iterator *noopIterator) HasNext() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (iterator *noopIterator) Next() (DataSet, error) {
|
||||
return nil, ErrExhausted
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
@ -15,30 +16,40 @@ type SliceIterator struct {
|
||||
func NewSliceIterator(
|
||||
valVar,
|
||||
keyVar string,
|
||||
input []core.Value,
|
||||
) Iterator {
|
||||
return &SliceIterator{valVar, keyVar, input, 0}
|
||||
values []core.Value,
|
||||
) (Iterator, error) {
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "result")
|
||||
}
|
||||
|
||||
return &SliceIterator{valVar, keyVar, values, 0}, nil
|
||||
}
|
||||
|
||||
func NewDefaultSliceIterator(input []core.Value) Iterator {
|
||||
func NewDefaultSliceIterator(input []core.Value) (Iterator, error) {
|
||||
return NewSliceIterator(DefaultValueVar, DefaultKeyVar, input)
|
||||
}
|
||||
|
||||
func (iterator *SliceIterator) HasNext() bool {
|
||||
return len(iterator.values) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *SliceIterator) Next() (DataSet, error) {
|
||||
func (iterator *SliceIterator) Next(_ context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
if len(iterator.values) > iterator.pos {
|
||||
idx := iterator.pos
|
||||
val := iterator.values[idx]
|
||||
|
||||
iterator.pos++
|
||||
|
||||
return DataSet{
|
||||
iterator.valVar: val,
|
||||
iterator.keyVar: values.NewInt(idx),
|
||||
}, nil
|
||||
nextScope := scope.Fork()
|
||||
|
||||
if err := nextScope.SetVariable(iterator.valVar, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if iterator.keyVar != "" {
|
||||
if err := nextScope.SetVariable(iterator.keyVar, values.NewInt(idx)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -9,7 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func sliceIterator(value []core.Value) collections.Iterator {
|
||||
return collections.NewDefaultSliceIterator(value)
|
||||
iter, _ := collections.NewDefaultSliceIterator(value)
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
func TestSliceIterator(t *testing.T) {
|
||||
@ -25,13 +28,23 @@ func TestSliceIterator(t *testing.T) {
|
||||
iter := sliceIterator(arr)
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
pos := 0
|
||||
|
||||
for iter.HasNext() {
|
||||
item, key, err := next(iter)
|
||||
for {
|
||||
nextScope, err := iter.Next(ctx, scope)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
key := nextScope.MustGetVariable(collections.DefaultKeyVar)
|
||||
item := nextScope.MustGetVariable(collections.DefaultValueVar)
|
||||
|
||||
So(key.Unwrap(), ShouldEqual, pos)
|
||||
|
||||
res = append(res, item)
|
||||
@ -52,20 +65,17 @@ func TestSliceIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
iter := sliceIterator(arr)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
for idx := range arr {
|
||||
expected := arr[idx]
|
||||
actual := res[idx]
|
||||
nextScope := res[idx]
|
||||
actual := nextScope.MustGetVariable(collections.DefaultValueVar)
|
||||
|
||||
So(actual, ShouldEqual, expected)
|
||||
}
|
||||
@ -81,34 +91,27 @@ func TestSliceIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
iter := sliceIterator(arr)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
_, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
item, _, err := next(iter)
|
||||
item, err := iter.Next(ctx, scope)
|
||||
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeError)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Should NOT iterate over an empty slice", t, func() {
|
||||
arr := []core.Value{}
|
||||
|
||||
iter := sliceIterator(arr)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
var iterated bool
|
||||
item, err := iter.Next(ctx, scope)
|
||||
|
||||
for iter.HasNext() {
|
||||
iterated = true
|
||||
}
|
||||
|
||||
So(iterated, ShouldBeFalse)
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
@ -10,7 +10,7 @@ import (
|
||||
type (
|
||||
SortDirection int
|
||||
|
||||
Comparator func(first DataSet, second DataSet) (int, error)
|
||||
Comparator func(ctx context.Context, first, second *core.Scope) (int, error)
|
||||
|
||||
Sorter struct {
|
||||
fn Comparator
|
||||
@ -18,11 +18,10 @@ type (
|
||||
}
|
||||
|
||||
SortIterator struct {
|
||||
src Iterator
|
||||
values Iterator
|
||||
sorters []*Sorter
|
||||
ready bool
|
||||
values []DataSet
|
||||
err error
|
||||
result []*core.Scope
|
||||
pos int
|
||||
}
|
||||
)
|
||||
@ -62,65 +61,53 @@ func NewSorter(fn Comparator, direction SortDirection) (*Sorter, error) {
|
||||
}
|
||||
|
||||
func NewSortIterator(
|
||||
src Iterator,
|
||||
values Iterator,
|
||||
comparators ...*Sorter,
|
||||
) (*SortIterator, error) {
|
||||
if core.IsNil(src) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "source")
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "values")
|
||||
}
|
||||
|
||||
if comparators == nil || len(comparators) == 0 {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "comparator")
|
||||
return nil, core.Error(core.ErrMissedArgument, "comparator")
|
||||
}
|
||||
|
||||
return &SortIterator{
|
||||
src,
|
||||
values,
|
||||
comparators,
|
||||
false,
|
||||
nil, nil,
|
||||
nil,
|
||||
0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (iterator *SortIterator) HasNext() bool {
|
||||
func (iterator *SortIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
// we need to initialize the iterator
|
||||
if iterator.ready == false {
|
||||
iterator.ready = true
|
||||
sorted, err := iterator.sort()
|
||||
sorted, err := iterator.sort(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
// dataSet to true because we do not want to initialize next time anymore
|
||||
iterator.values = nil
|
||||
iterator.err = err
|
||||
|
||||
// if there is an error, we need to show it during Next()
|
||||
return true
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iterator.values = sorted
|
||||
iterator.result = sorted
|
||||
}
|
||||
|
||||
return iterator.values != nil && len(iterator.values) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *SortIterator) Next() (DataSet, error) {
|
||||
if iterator.err != nil {
|
||||
return nil, iterator.err
|
||||
}
|
||||
|
||||
if len(iterator.values) > iterator.pos {
|
||||
if len(iterator.result) > iterator.pos {
|
||||
idx := iterator.pos
|
||||
val := iterator.values[idx]
|
||||
val := iterator.result[idx]
|
||||
|
||||
iterator.pos++
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
return nil, ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (iterator *SortIterator) sort() ([]DataSet, error) {
|
||||
res, err := ToSlice(iterator.src)
|
||||
func (iterator *SortIterator) sort(ctx context.Context, scope *core.Scope) ([]*core.Scope, error) {
|
||||
scopes, err := ToSlice(ctx, scope, iterator.values)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -128,7 +115,7 @@ func (iterator *SortIterator) sort() ([]DataSet, error) {
|
||||
|
||||
var failure error
|
||||
|
||||
sort.SliceStable(res, func(i, j int) bool {
|
||||
sort.SliceStable(scopes, func(i, j int) bool {
|
||||
// ignore next execution
|
||||
if failure != nil {
|
||||
return false
|
||||
@ -137,10 +124,10 @@ func (iterator *SortIterator) sort() ([]DataSet, error) {
|
||||
var out bool
|
||||
|
||||
for _, comp := range iterator.sorters {
|
||||
left := res[i]
|
||||
right := res[j]
|
||||
left := scopes[i]
|
||||
right := scopes[j]
|
||||
|
||||
eq, err := comp.fn(left, right)
|
||||
eq, err := comp.fn(ctx, left, right)
|
||||
|
||||
if err != nil {
|
||||
failure = err
|
||||
@ -169,5 +156,5 @@ func (iterator *SortIterator) sort() ([]DataSet, error) {
|
||||
return nil, failure
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return scopes, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
@ -9,18 +10,18 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func toValues(sets []collections.DataSet) []core.Value {
|
||||
res := make([]core.Value, 0, len(sets))
|
||||
func toValues(scopes []*core.Scope) []core.Value {
|
||||
res := make([]core.Value, 0, len(scopes))
|
||||
|
||||
for _, ds := range sets {
|
||||
res = append(res, ds.Get(collections.DefaultValueVar))
|
||||
for _, scope := range scopes {
|
||||
res = append(res, scope.MustGetVariable(collections.DefaultValueVar))
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func toArrayOfValues(sets []collections.DataSet) *values.Array {
|
||||
return values.NewArrayWith(toValues(sets)...)
|
||||
func toArrayOfValues(scopes []*core.Scope) *values.Array {
|
||||
return values.NewArrayWith(toValues(scopes)...)
|
||||
}
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
@ -34,27 +35,30 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
return first.MustGetVariable(collections.DefaultValueVar).Compare(second.MustGetVariable(collections.DefaultValueVar)), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
iter, err := collections.NewSortIterator(
|
||||
sliceIterator(arr),
|
||||
s,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
|
||||
for idx, num := range numbers {
|
||||
So(res[idx].Get(collections.DefaultValueVar).Unwrap(), ShouldEqual, num)
|
||||
So(res[idx].MustGetVariable(collections.DefaultValueVar).Unwrap(), ShouldEqual, num)
|
||||
}
|
||||
})
|
||||
|
||||
@ -68,27 +72,29 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
return first.MustGetVariable(collections.DefaultValueVar).Compare(second.MustGetVariable(collections.DefaultValueVar)), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
iter, err := collections.NewSortIterator(
|
||||
sliceIterator(arr),
|
||||
s,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
numbers := []int{5, 4, 3, 2, 1}
|
||||
|
||||
for idx, num := range numbers {
|
||||
So(res[idx].Get(collections.DefaultValueVar).Unwrap(), ShouldEqual, num)
|
||||
So(res[idx].MustGetVariable(collections.DefaultValueVar).Unwrap(), ShouldEqual, num)
|
||||
}
|
||||
})
|
||||
|
||||
@ -114,9 +120,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -124,16 +130,16 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
iter, err := collections.NewSortIterator(
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
@ -141,7 +147,10 @@ func TestSort(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(src)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -174,9 +183,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -184,16 +193,16 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
iter, err := collections.NewSortIterator(
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
@ -201,7 +210,10 @@ func TestSort(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(src)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -234,9 +246,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -244,16 +256,16 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
iter, err := collections.NewSortIterator(
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
@ -261,7 +273,10 @@ func TestSort(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(src)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -294,9 +309,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -304,9 +319,9 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
o1, _ := first.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.MustGetVariable(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -321,7 +336,10 @@ func TestSort(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(src)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(ctx, scope, src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
43
pkg/runtime/collections/tap.go
Normal file
43
pkg/runtime/collections/tap.go
Normal file
@ -0,0 +1,43 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
type TapIterator struct {
|
||||
values Iterator
|
||||
predicate core.Expression
|
||||
}
|
||||
|
||||
func NewTapIterator(values Iterator, predicate core.Expression) (Iterator, error) {
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "values")
|
||||
}
|
||||
|
||||
if predicate == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "predicate")
|
||||
}
|
||||
|
||||
return &TapIterator{values, predicate}, nil
|
||||
}
|
||||
|
||||
func (iterator *TapIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
nextScope, err := iterator.values.Next(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nextScope == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err = iterator.predicate.Exec(ctx, nextScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
128
pkg/runtime/collections/tap_test.go
Normal file
128
pkg/runtime/collections/tap_test.go
Normal file
@ -0,0 +1,128 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func tapIterator(values collections.Iterator, predicate core.Expression) collections.Iterator {
|
||||
iter, _ := collections.NewTapIterator(values, predicate)
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
type TestExpression struct {
|
||||
fn func(ctx context.Context, scope *core.Scope) (core.Value, error)
|
||||
}
|
||||
|
||||
func (exp *TestExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return exp.fn(ctx, scope)
|
||||
}
|
||||
|
||||
type ErrorIterator struct{}
|
||||
|
||||
func (iterator *ErrorIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
return nil, core.ErrInvalidOperation
|
||||
}
|
||||
|
||||
func TestTapIterator(t *testing.T) {
|
||||
Convey("Should iterate over a given iterator and execute a predicate", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
counter := 0
|
||||
|
||||
iter := tapIterator(arrayIterator(arr), &TestExpression{
|
||||
fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
counter++
|
||||
|
||||
return values.None, nil
|
||||
},
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(res, ShouldHaveLength, int(arr.Length()))
|
||||
So(counter, ShouldEqual, int(arr.Length()))
|
||||
})
|
||||
|
||||
Convey("Should stop when a predicate return an error", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
)
|
||||
|
||||
counter := 0
|
||||
|
||||
iter := tapIterator(arrayIterator(arr), &TestExpression{
|
||||
fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
counter++
|
||||
|
||||
return values.None, core.ErrInvalidOperation
|
||||
},
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
_, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(counter, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Should not invoke a predicate when underlying iterator returns error", t, func() {
|
||||
counter := 0
|
||||
|
||||
iter := tapIterator(&ErrorIterator{}, &TestExpression{
|
||||
fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
counter++
|
||||
|
||||
return values.None, core.ErrInvalidOperation
|
||||
},
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
_, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
So(counter, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should not invoke a predicate when underlying iterator is empty", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
counter := 0
|
||||
|
||||
iter := tapIterator(arrayIterator(arr), &TestExpression{
|
||||
fn: func(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
counter++
|
||||
|
||||
return values.None, core.ErrInvalidOperation
|
||||
},
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
res, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(res, ShouldHaveLength, 0)
|
||||
So(counter, ShouldEqual, 0)
|
||||
})
|
||||
}
|
@ -1,69 +1,49 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
)
|
||||
|
||||
type (
|
||||
UniqueIterator struct {
|
||||
src Iterator
|
||||
values Iterator
|
||||
hashes map[uint64]bool
|
||||
hashKey string
|
||||
dataSet DataSet
|
||||
err error
|
||||
}
|
||||
)
|
||||
|
||||
func NewUniqueIterator(src Iterator, hashKey string) (*UniqueIterator, error) {
|
||||
if src == nil {
|
||||
func NewUniqueIterator(values Iterator, hashKey string) (*UniqueIterator, error) {
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
return &UniqueIterator{
|
||||
src: src,
|
||||
values: values,
|
||||
hashes: make(map[uint64]bool),
|
||||
hashKey: hashKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (iterator *UniqueIterator) HasNext() bool {
|
||||
if !iterator.src.HasNext() {
|
||||
return false
|
||||
}
|
||||
|
||||
iterator.doNext()
|
||||
|
||||
if iterator.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if iterator.dataSet != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (iterator *UniqueIterator) Next() (DataSet, error) {
|
||||
return iterator.dataSet, iterator.err
|
||||
}
|
||||
|
||||
func (iterator *UniqueIterator) doNext() {
|
||||
// reset state
|
||||
iterator.err = nil
|
||||
iterator.dataSet = nil
|
||||
|
||||
// iterate over source until we find a non-unique item
|
||||
for iterator.src.HasNext() {
|
||||
ds, err := iterator.src.Next()
|
||||
func (iterator *UniqueIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
for {
|
||||
nextScope, err := iterator.values.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
iterator.err = err
|
||||
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := ds.Get(iterator.hashKey).Hash()
|
||||
if nextScope == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
v, err := nextScope.GetVariable(iterator.hashKey)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := v.Hash()
|
||||
|
||||
_, exists := iterator.hashes[h]
|
||||
|
||||
@ -72,8 +52,7 @@ func (iterator *UniqueIterator) doNext() {
|
||||
}
|
||||
|
||||
iterator.hashes[h] = true
|
||||
iterator.dataSet = ds
|
||||
|
||||
return
|
||||
return nextScope, nil
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
@ -29,8 +30,10 @@ func TestUniqueIterator(t *testing.T) {
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(iter)
|
||||
sets, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -56,7 +59,10 @@ func TestUniqueIterator(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(iter)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -87,7 +93,10 @@ func TestUniqueIterator(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sets, err := collections.ToSlice(iter)
|
||||
ctx := context.Background()
|
||||
scope, _ := core.NewRootScope()
|
||||
|
||||
sets, err := collections.ToSlice(ctx, scope, iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
|
@ -1,35 +1,65 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
CloseFunc func()
|
||||
CloseFunc func() error
|
||||
|
||||
RootScope struct {
|
||||
closed bool
|
||||
disposables []io.Closer
|
||||
}
|
||||
|
||||
Scope struct {
|
||||
closed bool
|
||||
vars map[string]Value
|
||||
parent *Scope
|
||||
children []*Scope
|
||||
root *RootScope
|
||||
parent *Scope
|
||||
vars map[string]Value
|
||||
}
|
||||
)
|
||||
|
||||
func NewRootScope() (*Scope, CloseFunc) {
|
||||
scope := NewScope(nil)
|
||||
root := &RootScope{
|
||||
closed: false,
|
||||
disposables: make([]io.Closer, 0, 10),
|
||||
}
|
||||
|
||||
return scope, func() {
|
||||
scope.close()
|
||||
return newScope(root, nil), func() error {
|
||||
return root.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func NewScope(parent *Scope) *Scope {
|
||||
func (s *RootScope) AddDisposable(disposable io.Closer) {
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
|
||||
if disposable != nil {
|
||||
s.disposables = append(s.disposables, disposable)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RootScope) Close() error {
|
||||
if s.closed {
|
||||
return Error(ErrInvalidOperation, "scope is already closed")
|
||||
}
|
||||
|
||||
s.closed = true
|
||||
|
||||
// close all values implemented io.Close
|
||||
for _, c := range s.disposables {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newScope(root *RootScope, parent *Scope) *Scope {
|
||||
return &Scope{
|
||||
closed: false,
|
||||
vars: make(map[string]Value),
|
||||
parent: parent,
|
||||
children: make([]*Scope, 0, 5),
|
||||
root: root,
|
||||
parent: parent,
|
||||
vars: make(map[string]Value),
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +68,13 @@ func (s *Scope) SetVariable(name string, val Value) error {
|
||||
|
||||
// it already has been declared in the current scope
|
||||
if exists {
|
||||
return errors.Wrapf(ErrNotUnique, "variable is already declared '%s'", name)
|
||||
return Errorf(ErrNotUnique, "variable is already declared: '%s'", name)
|
||||
}
|
||||
|
||||
disposable, ok := val.(io.Closer)
|
||||
|
||||
if ok {
|
||||
s.root.AddDisposable(disposable)
|
||||
}
|
||||
|
||||
s.vars[name] = val
|
||||
@ -70,45 +106,36 @@ func (s *Scope) GetVariable(name string) (Value, error) {
|
||||
return s.parent.GetVariable(name)
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(ErrNotFound, "variable '%s'", name)
|
||||
return nil, Errorf(ErrNotFound, "variable: '%s'", name)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Scope) Fork() *Scope {
|
||||
child := NewScope(s)
|
||||
func (s *Scope) MustGetVariable(name string) Value {
|
||||
out, err := s.GetVariable(name)
|
||||
|
||||
s.children = append(s.children, child)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Scope) UpdateVariable(name string, val Value) error {
|
||||
_, exists := s.vars[name]
|
||||
|
||||
if !exists {
|
||||
return Errorf(ErrNotFound, "variable: '%s'", name)
|
||||
}
|
||||
|
||||
delete(s.vars, name)
|
||||
|
||||
return s.SetVariable(name, val)
|
||||
}
|
||||
|
||||
func (s *Scope) Fork() *Scope {
|
||||
child := newScope(s.root, s)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (s *Scope) close() error {
|
||||
if s.closed {
|
||||
return errors.Wrap(ErrInvalidOperation, "scope is already closed")
|
||||
}
|
||||
|
||||
s.closed = true
|
||||
|
||||
// close all active child scopes
|
||||
for _, c := range s.children {
|
||||
c.close()
|
||||
}
|
||||
|
||||
// do clean up
|
||||
// if some of the variables implements io.Closer interface
|
||||
// we need to close them
|
||||
for _, v := range s.vars {
|
||||
closer, ok := v.(io.Closer)
|
||||
|
||||
if ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
s.children = nil
|
||||
s.vars = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -9,61 +9,228 @@ import (
|
||||
)
|
||||
|
||||
func TestScope(t *testing.T) {
|
||||
Convey("Should match", t, func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
Convey(".SetVariable", t, func() {
|
||||
Convey("Should set a new variable", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
So(cf, ShouldNotBeNil)
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
s := core.NewScope(rs)
|
||||
err := rs.SetVariable("foo", values.NewString("bar"))
|
||||
|
||||
So(s.HasVariable("a"), ShouldBeFalse)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
s.SetVariable("a", values.NewString("test"))
|
||||
Convey("Should return an error when a variable is already defined", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
So(s.HasVariable("a"), ShouldBeTrue)
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
v, err := s.GetVariable("a")
|
||||
err := rs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(v, ShouldEqual, "test")
|
||||
err = rs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldHaveSameTypeAs, core.ErrNotUnique)
|
||||
})
|
||||
})
|
||||
|
||||
c := s.Fork()
|
||||
Convey(".GetVariable", t, func() {
|
||||
Convey("Should set and get a variable", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
So(c.HasVariable("a"), ShouldBeTrue)
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
cv, err := c.GetVariable("a")
|
||||
err := rs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(cv, ShouldEqual, "test")
|
||||
v, err := rs.GetVariable("foo")
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(v, ShouldEqual, "bar")
|
||||
})
|
||||
|
||||
Convey("Should return an error when variable is not defined", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
_, err := rs.GetVariable("foo")
|
||||
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".HasVariable", t, func() {
|
||||
Convey("Should return TRUE when a variable exists", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
err := rs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
exists := rs.HasVariable("foo")
|
||||
|
||||
So(exists, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Should return FALSE when a variable exists", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
exists := rs.HasVariable("foo")
|
||||
|
||||
So(exists, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Fork", t, func() {
|
||||
Convey("Should create a nested scope", func() {
|
||||
Convey("Should set a variable only in a child scope", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
cs := rs.Fork()
|
||||
cs.SetVariable("foo", values.NewString("bar"))
|
||||
|
||||
exists := rs.HasVariable("foo")
|
||||
|
||||
So(exists, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Should return a variable defined only in a child scope", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
cs := rs.Fork()
|
||||
err := cs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
v, err := cs.GetVariable("foo")
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(v, ShouldEqual, "bar")
|
||||
})
|
||||
|
||||
Convey("Should return a variable defined only in a parent scope", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
cs := rs.Fork()
|
||||
err := cs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = rs.SetVariable("faz", values.NewString("qaz"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
v, err := cs.GetVariable("faz")
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(v, ShouldEqual, "qaz")
|
||||
})
|
||||
|
||||
Convey("Should set a new variable with a same name defined in a parent scope", func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
err := rs.SetVariable("foo", values.NewString("bar"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cs := rs.Fork()
|
||||
err = cs.SetVariable("foo", values.NewString("faz"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rsV, err := rs.GetVariable("foo")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
csV, err := cs.GetVariable("foo")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(csV, ShouldNotEqual, rsV)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestScopeTraversing(t *testing.T) {
|
||||
Convey("Should match", t, func() {
|
||||
func BenchmarkScope(b *testing.B) {
|
||||
root, _ := core.NewRootScope()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
root.Fork()
|
||||
}
|
||||
}
|
||||
|
||||
type TestCloser struct {
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (tc *TestCloser) MarshalJSON() ([]byte, error) {
|
||||
return nil, core.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (tc *TestCloser) Type() core.Type {
|
||||
return core.NoneType
|
||||
}
|
||||
|
||||
func (tc *TestCloser) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (tc *TestCloser) Compare(other core.Value) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (tc *TestCloser) Unwrap() interface{} {
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc *TestCloser) Hash() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (tc *TestCloser) Copy() core.Value {
|
||||
return &TestCloser{}
|
||||
}
|
||||
|
||||
func (tc *TestCloser) Close() error {
|
||||
if tc.closed {
|
||||
return core.Error(core.ErrInvalidOperation, "already closed")
|
||||
}
|
||||
|
||||
tc.closed = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCloseFunc(t *testing.T) {
|
||||
Convey("Should close root scope and close all io.Closer values", t, func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
So(cf, ShouldNotBeNil)
|
||||
|
||||
s := core.NewScope(rs)
|
||||
tc := &TestCloser{}
|
||||
|
||||
rs.SetVariable("a", values.NewString("test"))
|
||||
v, err := s.GetVariable("a")
|
||||
rs.SetVariable("disposable", tc)
|
||||
So(tc.closed, ShouldBeFalse)
|
||||
|
||||
// root traversal should work
|
||||
err := cf()
|
||||
So(err, ShouldBeNil)
|
||||
So(v, ShouldEqual, "test")
|
||||
|
||||
s.SetVariable("b", values.NewString("test2"))
|
||||
s2 := core.NewScope(rs)
|
||||
_, err = s2.GetVariable("b")
|
||||
So(tc.closed, ShouldBeTrue)
|
||||
})
|
||||
|
||||
// child traversal should fail
|
||||
So(err, ShouldNotBeNil)
|
||||
Convey("Should return error if it's already closed", t, func() {
|
||||
rs, cf := core.NewRootScope()
|
||||
|
||||
v2, err := s2.GetVariable("a")
|
||||
tc := &TestCloser{}
|
||||
|
||||
// root traversal should work
|
||||
rs.SetVariable("disposable", tc)
|
||||
So(tc.closed, ShouldBeFalse)
|
||||
|
||||
err := cf()
|
||||
So(err, ShouldBeNil)
|
||||
So(v2, ShouldEqual, "test")
|
||||
|
||||
So(tc.closed, ShouldBeTrue)
|
||||
|
||||
err = cf()
|
||||
So(err, ShouldHaveSameTypeAs, core.ErrInvalidOperation)
|
||||
})
|
||||
}
|
||||
|
@ -2,58 +2,49 @@ package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type BlockExpression struct {
|
||||
values collections.Iterable
|
||||
statements []core.Expression
|
||||
expression core.Expression
|
||||
}
|
||||
|
||||
func NewBlockExpression(size int) *BlockExpression {
|
||||
return &BlockExpression{make([]core.Expression, 0, size), nil}
|
||||
}
|
||||
|
||||
func NewBlockExpressionWith(elements ...core.Expression) *BlockExpression {
|
||||
block := NewBlockExpression(len(elements))
|
||||
|
||||
for _, el := range elements {
|
||||
block.Add(el)
|
||||
func NewBlockExpression(values collections.Iterable) (*BlockExpression, error) {
|
||||
if values == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "values")
|
||||
}
|
||||
|
||||
return block
|
||||
return &BlockExpression{
|
||||
values: values,
|
||||
statements: make([]core.Expression, 0, 5),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BlockExpression) Add(exp core.Expression) error {
|
||||
switch exp.(type) {
|
||||
case *ForExpression, *ReturnExpression:
|
||||
// return an error?
|
||||
if !core.IsNil(b.expression) {
|
||||
return errors.Wrap(core.ErrInvalidOperation, "return expression is already defined")
|
||||
}
|
||||
|
||||
b.expression = exp
|
||||
|
||||
break
|
||||
default:
|
||||
b.statements = append(b.statements, exp)
|
||||
}
|
||||
|
||||
return nil
|
||||
func (exp *BlockExpression) Add(stmt core.Expression) {
|
||||
exp.statements = append(exp.statements, stmt)
|
||||
}
|
||||
|
||||
func (b *BlockExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
for _, exp := range b.statements {
|
||||
if _, err := exp.Exec(ctx, scope); err != nil {
|
||||
func (exp *BlockExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
for _, stmt := range exp.statements {
|
||||
_, err := stmt.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
}
|
||||
|
||||
if !core.IsNil(b.expression) {
|
||||
return b.expression.Exec(ctx, scope)
|
||||
}
|
||||
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
func (exp *BlockExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
iter, err := exp.values.Iterate(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections.NewTapIterator(iter, exp)
|
||||
}
|
||||
|
47
pkg/runtime/expressions/body.go
Normal file
47
pkg/runtime/expressions/body.go
Normal file
@ -0,0 +1,47 @@
|
||||
package expressions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type BodyExpression struct {
|
||||
statements []core.Expression
|
||||
expression core.Expression
|
||||
}
|
||||
|
||||
func NewBodyExpression(size int) *BodyExpression {
|
||||
return &BodyExpression{make([]core.Expression, 0, size), nil}
|
||||
}
|
||||
|
||||
func (b *BodyExpression) Add(exp core.Expression) error {
|
||||
switch exp.(type) {
|
||||
case *ForExpression, *ReturnExpression:
|
||||
if b.expression != nil {
|
||||
return core.Error(core.ErrInvalidOperation, "return expression is already defined")
|
||||
}
|
||||
|
||||
b.expression = exp
|
||||
|
||||
break
|
||||
default:
|
||||
b.statements = append(b.statements, exp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BodyExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
for _, exp := range b.statements {
|
||||
if _, err := exp.Exec(ctx, scope); err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
}
|
||||
|
||||
if b.expression != nil {
|
||||
return b.expression.Exec(ctx, scope)
|
||||
}
|
||||
|
||||
return values.None, nil
|
||||
}
|
@ -10,25 +10,17 @@ import (
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestNewBlockExpression(t *testing.T) {
|
||||
func TestNewBodyExpression(t *testing.T) {
|
||||
Convey("Should create a block expression", t, func() {
|
||||
s := expressions.NewBlockExpression(1)
|
||||
s := expressions.NewBodyExpression(1)
|
||||
|
||||
So(s, ShouldHaveSameTypeAs, &expressions.BlockExpression{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewBlockExpressionWith(t *testing.T) {
|
||||
Convey("Should create a block expression from passed values", t, func() {
|
||||
s := expressions.NewBlockExpressionWith(&expressions.BlockExpression{})
|
||||
|
||||
So(s, ShouldHaveSameTypeAs, &expressions.BlockExpression{})
|
||||
So(s, ShouldHaveSameTypeAs, &expressions.BodyExpression{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockExpressionAddVariableExpression(t *testing.T) {
|
||||
Convey("Should add a new expression of a default type", t, func() {
|
||||
s := expressions.NewBlockExpression(0)
|
||||
s := expressions.NewBodyExpression(0)
|
||||
|
||||
sourceMap := core.NewSourceMap("test", 1, 1)
|
||||
exp, err := expressions.NewVariableExpression(sourceMap, "testExp")
|
||||
@ -41,7 +33,7 @@ func TestBlockExpressionAddVariableExpression(t *testing.T) {
|
||||
|
||||
func TestBlockExpressionAddReturnExpression(t *testing.T) {
|
||||
Convey("Should add a new Return expression", t, func() {
|
||||
s := expressions.NewBlockExpression(0)
|
||||
s := expressions.NewBodyExpression(0)
|
||||
|
||||
sourceMap := core.NewSourceMap("test", 1, 1)
|
||||
predicate, err := expressions.NewVariableExpression(sourceMap, "testExp")
|
||||
@ -57,7 +49,7 @@ func TestBlockExpressionAddReturnExpression(t *testing.T) {
|
||||
|
||||
func TestBlockExpressionAddReturnExpressionFailed(t *testing.T) {
|
||||
Convey("Should not add an already defined Return expression", t, func() {
|
||||
s := expressions.NewBlockExpression(0)
|
||||
s := expressions.NewBodyExpression(0)
|
||||
|
||||
sourceMap := core.NewSourceMap("test", 1, 1)
|
||||
predicate, err := expressions.NewVariableExpression(sourceMap, "testExp")
|
||||
@ -71,13 +63,13 @@ func TestBlockExpressionAddReturnExpressionFailed(t *testing.T) {
|
||||
|
||||
err = s.Add(exp)
|
||||
So(err, ShouldBeError)
|
||||
So(err.Error(), ShouldEqual, "return expression is already defined: invalid operation")
|
||||
So(err.Error(), ShouldEqual, "invalid operation: return expression is already defined")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockExpressionExec(t *testing.T) {
|
||||
Convey("Should exec a block expression", t, func() {
|
||||
s := expressions.NewBlockExpression(1)
|
||||
s := expressions.NewBodyExpression(1)
|
||||
|
||||
sourceMap := core.NewSourceMap("test", 1, 1)
|
||||
predicate, err := expressions.NewVariableExpression(sourceMap, "test")
|
||||
@ -90,7 +82,7 @@ func TestBlockExpressionExec(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootScope, fn := core.NewRootScope()
|
||||
scope := core.NewScope(rootScope)
|
||||
scope := rootScope.Fork()
|
||||
scope.SetVariable("test", values.NewString("value"))
|
||||
fn()
|
||||
|
||||
@ -103,7 +95,7 @@ func TestBlockExpressionExec(t *testing.T) {
|
||||
|
||||
func TestBlockExpressionExecNonFound(t *testing.T) {
|
||||
Convey("Should not found a missing statement", t, func() {
|
||||
s := expressions.NewBlockExpression(1)
|
||||
s := expressions.NewBodyExpression(1)
|
||||
|
||||
sourceMap := core.NewSourceMap("test", 1, 1)
|
||||
predicate, err := expressions.NewVariableExpression(sourceMap, "testExp")
|
||||
@ -116,7 +108,7 @@ func TestBlockExpressionExecNonFound(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootScope, fn := core.NewRootScope()
|
||||
scope := core.NewScope(rootScope)
|
||||
scope := rootScope.Fork()
|
||||
scope.SetVariable("test", values.NewString("value"))
|
||||
fn()
|
||||
|
||||
@ -129,7 +121,7 @@ func TestBlockExpressionExecNonFound(t *testing.T) {
|
||||
|
||||
func TestBlockExpressionExecNilExpression(t *testing.T) {
|
||||
Convey("Should not exec a nil block expression", t, func() {
|
||||
s := expressions.NewBlockExpression(1)
|
||||
s := expressions.NewBodyExpression(1)
|
||||
|
||||
sourceMap := core.NewSourceMap("test", 1, 1)
|
||||
exp, err := expressions.NewVariableExpression(sourceMap, "test")
|
||||
@ -139,7 +131,7 @@ func TestBlockExpressionExecNilExpression(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootScope, fn := core.NewRootScope()
|
||||
scope := core.NewScope(rootScope)
|
||||
scope := rootScope.Fork()
|
||||
scope.SetVariable("test", values.NewString("value"))
|
||||
fn()
|
||||
|
@ -116,40 +116,6 @@ func NewCollectClause(
|
||||
return &CollectClause{src, dataSource, params}, nil
|
||||
}
|
||||
|
||||
func (clause *CollectClause) Variables() collections.Variables {
|
||||
vars := make(collections.Variables, 0, 10)
|
||||
|
||||
if clause.params.group != nil {
|
||||
grouping := clause.params.group
|
||||
|
||||
for _, selector := range grouping.selectors {
|
||||
vars = append(vars, selector.variable)
|
||||
}
|
||||
|
||||
if grouping.projection != nil {
|
||||
vars = append(vars, clause.params.group.projection.selector.variable)
|
||||
}
|
||||
|
||||
if grouping.count != nil {
|
||||
vars = append(vars, clause.params.group.count.variable)
|
||||
}
|
||||
|
||||
if grouping.aggregate != nil {
|
||||
for _, selector := range grouping.aggregate.selectors {
|
||||
vars = append(vars, selector.variable)
|
||||
}
|
||||
}
|
||||
} else if clause.params.count != nil {
|
||||
vars = append(vars, clause.params.count.variable)
|
||||
} else if clause.params.aggregate != nil {
|
||||
for _, selector := range clause.params.aggregate.selectors {
|
||||
vars = append(vars, selector.variable)
|
||||
}
|
||||
}
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
func (clause *CollectClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
srcIterator, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
@ -157,14 +123,9 @@ func (clause *CollectClause) Iterate(ctx context.Context, scope *core.Scope) (co
|
||||
return nil, core.SourceError(clause.src, err)
|
||||
}
|
||||
|
||||
srcVariables := clause.dataSource.Variables()
|
||||
|
||||
return NewCollectIterator(
|
||||
clause.src,
|
||||
clause.params,
|
||||
srcIterator,
|
||||
srcVariables,
|
||||
ctx,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
|
@ -9,24 +9,17 @@ import (
|
||||
|
||||
type CollectIterator struct {
|
||||
ready bool
|
||||
values []collections.DataSet
|
||||
values []*core.Scope
|
||||
pos int
|
||||
src core.SourceMap
|
||||
params *Collect
|
||||
dataSource collections.Iterator
|
||||
variables collections.Variables
|
||||
ctx context.Context
|
||||
scope *core.Scope
|
||||
}
|
||||
|
||||
//revive:disable context-as-argument
|
||||
func NewCollectIterator(
|
||||
src core.SourceMap,
|
||||
params *Collect,
|
||||
dataSource collections.Iterator,
|
||||
variables collections.Variables,
|
||||
ctx context.Context,
|
||||
scope *core.Scope,
|
||||
) (*CollectIterator, error) {
|
||||
if params.group != nil {
|
||||
if params.group.selectors != nil {
|
||||
@ -34,7 +27,7 @@ func NewCollectIterator(
|
||||
sorters := make([]*collections.Sorter, len(params.group.selectors))
|
||||
|
||||
for i, selector := range params.group.selectors {
|
||||
sorter, err := newGroupSorter(ctx, scope, variables, selector)
|
||||
sorter, err := newGroupSorter(selector)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -62,27 +55,18 @@ func NewCollectIterator(
|
||||
src,
|
||||
params,
|
||||
dataSource,
|
||||
variables,
|
||||
ctx,
|
||||
scope,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newGroupSorter(ctx context.Context, scope *core.Scope, variables collections.Variables, selector *CollectSelector) (*collections.Sorter, error) {
|
||||
return collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
scope1 := scope.Fork()
|
||||
first.Apply(scope1, variables)
|
||||
|
||||
f, err := selector.expression.Exec(ctx, scope1)
|
||||
func newGroupSorter(selector *CollectSelector) (*collections.Sorter, error) {
|
||||
return collections.NewSorter(func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
f, err := selector.expression.Exec(ctx, first)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
scope2 := scope.Fork()
|
||||
second.Apply(scope2, variables)
|
||||
|
||||
s, err := selector.expression.Exec(ctx, scope2)
|
||||
s, err := selector.expression.Exec(ctx, second)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
@ -92,24 +76,18 @@ func newGroupSorter(ctx context.Context, scope *core.Scope, variables collection
|
||||
}, collections.SortDirectionAsc)
|
||||
}
|
||||
|
||||
func (iterator *CollectIterator) HasNext() bool {
|
||||
func (iterator *CollectIterator) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
if !iterator.ready {
|
||||
iterator.ready = true
|
||||
groups, err := iterator.init()
|
||||
groups, err := iterator.init(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
iterator.values = nil
|
||||
|
||||
return false
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iterator.values = groups
|
||||
}
|
||||
|
||||
return len(iterator.values) > iterator.pos
|
||||
}
|
||||
|
||||
func (iterator *CollectIterator) Next() (collections.DataSet, error) {
|
||||
if len(iterator.values) > iterator.pos {
|
||||
val := iterator.values[iterator.pos]
|
||||
iterator.pos++
|
||||
@ -117,34 +95,33 @@ func (iterator *CollectIterator) Next() (collections.DataSet, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
return nil, collections.ErrExhausted
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (iterator *CollectIterator) init() ([]collections.DataSet, error) {
|
||||
func (iterator *CollectIterator) init(ctx context.Context, scope *core.Scope) ([]*core.Scope, error) {
|
||||
if iterator.params.group != nil {
|
||||
return iterator.group()
|
||||
return iterator.group(ctx, scope)
|
||||
}
|
||||
|
||||
if iterator.params.count != nil {
|
||||
return iterator.count()
|
||||
return iterator.count(ctx, scope)
|
||||
}
|
||||
|
||||
if iterator.params.aggregate != nil {
|
||||
return iterator.aggregate()
|
||||
return iterator.aggregate(ctx, scope)
|
||||
}
|
||||
|
||||
return nil, core.ErrInvalidOperation
|
||||
}
|
||||
|
||||
func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
func (iterator *CollectIterator) group(ctx context.Context, scope *core.Scope) ([]*core.Scope, error) {
|
||||
// TODO: honestly, this code is ugly. it needs to be refactored in more chained way with much less if statements
|
||||
// slice of groups
|
||||
collected := make([]collections.DataSet, 0, 10)
|
||||
collected := make([]*core.Scope, 0, 10)
|
||||
// hash table of unique values
|
||||
// key is a DataSet hash
|
||||
// value is its index in result slice (collected)
|
||||
hashTable := make(map[uint64]int)
|
||||
ctx := iterator.ctx
|
||||
|
||||
groupSelectors := iterator.params.group.selectors
|
||||
proj := iterator.params.group.projection
|
||||
@ -152,57 +129,62 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
aggr := iterator.params.group.aggregate
|
||||
|
||||
// iterating over underlying data source
|
||||
for iterator.dataSource.HasNext() {
|
||||
set, err := iterator.dataSource.Next()
|
||||
for {
|
||||
// keep all defined variables in forked scopes
|
||||
// all those variables should not be available for further executions
|
||||
dataSourceScope, err := iterator.dataSource.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(set) == 0 {
|
||||
continue
|
||||
if dataSourceScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// creating a new scope for all further operations
|
||||
childScope := iterator.scope.Fork()
|
||||
// this data dataSourceScope represents a data of a given iteration with values retrieved by selectors
|
||||
collectScope := scope.Fork()
|
||||
|
||||
// populate the new scope with results from an underlying source and its exposed variables
|
||||
if err := set.Apply(childScope, iterator.variables); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// map for calculating a hash value
|
||||
vals := make(map[string]core.Value)
|
||||
|
||||
// this data set represents a data of a given iteration with values retrieved by selectors
|
||||
ds := collections.NewDataSet()
|
||||
|
||||
// iterate over each selector for a current data set
|
||||
// iterate over each selector for a current data
|
||||
for _, selector := range groupSelectors {
|
||||
// execute a selector and get a value
|
||||
// e.g. COLLECT age = u.age
|
||||
value, err := selector.expression.Exec(ctx, childScope)
|
||||
value, err := selector.expression.Exec(ctx, dataSourceScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds.Set(selector.variable, value)
|
||||
if err := collectScope.SetVariable(selector.variable, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vals[selector.variable] = value
|
||||
}
|
||||
|
||||
// it important to get hash value before projection and counting
|
||||
// otherwise hash value will be inaccurate
|
||||
h := ds.Hash()
|
||||
h := values.MapHash(vals)
|
||||
|
||||
_, exists := hashTable[h]
|
||||
|
||||
if !exists {
|
||||
collected = append(collected, ds)
|
||||
collected = append(collected, collectScope)
|
||||
hashTable[h] = len(collected) - 1
|
||||
|
||||
if proj != nil {
|
||||
// create a new variable for keeping projection
|
||||
ds.Set(proj.selector.variable, values.NewArray(10))
|
||||
if err := collectScope.SetVariable(proj.selector.variable, values.NewArray(10)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if count != nil {
|
||||
// create a new variable for keeping counter
|
||||
ds.Set(count.variable, values.ZeroInt)
|
||||
if err := collectScope.SetVariable(count.variable, values.ZeroInt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if aggr != nil {
|
||||
// create a new variable for keeping aggregated values
|
||||
for _, selector := range aggr.selectors {
|
||||
@ -212,15 +194,21 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
arr.Push(values.None)
|
||||
}
|
||||
|
||||
ds.Set(selector.variable, arr)
|
||||
if err := collectScope.SetVariable(selector.variable, arr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if proj != nil {
|
||||
idx := hashTable[h]
|
||||
ds := collected[idx]
|
||||
groupValue := ds.Get(proj.selector.variable)
|
||||
collectedScope := collected[idx]
|
||||
groupValue, err := collectedScope.GetVariable(proj.selector.variable)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr, ok := groupValue.(*values.Array)
|
||||
|
||||
@ -228,7 +216,7 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
return nil, core.TypeError(groupValue.Type(), core.IntType)
|
||||
}
|
||||
|
||||
value, err := proj.selector.expression.Exec(ctx, childScope)
|
||||
value, err := proj.selector.expression.Exec(ctx, dataSourceScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -238,7 +226,11 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
} else if count != nil {
|
||||
idx := hashTable[h]
|
||||
ds := collected[idx]
|
||||
groupValue := ds.Get(count.variable)
|
||||
groupValue, err := ds.GetVariable(count.variable)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
counter, ok := groupValue.(values.Int)
|
||||
|
||||
@ -247,21 +239,29 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
}
|
||||
|
||||
groupValue = counter + 1
|
||||
// set a new value
|
||||
ds.Set(count.variable, groupValue)
|
||||
// dataSourceScope a new value
|
||||
if err := ds.UpdateVariable(count.variable, groupValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if aggr != nil {
|
||||
idx := hashTable[h]
|
||||
ds := collected[idx]
|
||||
|
||||
// iterate over each selector for a current data set
|
||||
// iterate over each selector for a current data dataSourceScope
|
||||
for _, selector := range aggr.selectors {
|
||||
vv := ds.Get(selector.variable).(*values.Array)
|
||||
sv, err := ds.GetVariable(selector.variable)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vv := sv.(*values.Array)
|
||||
|
||||
// execute a selector and get a value
|
||||
// e.g. AGGREGATE age = CONCAT(u.age, u.dob)
|
||||
// u.age and u.dob get executed
|
||||
for idx, exp := range selector.aggregators {
|
||||
arg, err := exp.Exec(ctx, childScope)
|
||||
arg, err := exp.Exec(ctx, dataSourceScope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -284,9 +284,15 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
}
|
||||
|
||||
if aggr != nil {
|
||||
for _, ds := range collected {
|
||||
for _, iterScope := range collected {
|
||||
for _, selector := range aggr.selectors {
|
||||
arr := ds[selector.variable].(*values.Array)
|
||||
sv, err := iterScope.GetVariable(selector.variable)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr := sv.(*values.Array)
|
||||
|
||||
matrix := make([]core.Value, arr.Length())
|
||||
|
||||
@ -303,7 +309,9 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
}
|
||||
|
||||
// replace value with calculated one
|
||||
ds.Set(selector.variable, reduced)
|
||||
if err := iterScope.UpdateVariable(selector.variable, reduced); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,56 +319,58 @@ func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||
return collected, nil
|
||||
}
|
||||
|
||||
func (iterator *CollectIterator) count() ([]collections.DataSet, error) {
|
||||
func (iterator *CollectIterator) count(ctx context.Context, scope *core.Scope) ([]*core.Scope, error) {
|
||||
var counter int
|
||||
|
||||
// iterating over underlying data source
|
||||
for iterator.dataSource.HasNext() {
|
||||
_, err := iterator.dataSource.Next()
|
||||
for {
|
||||
// keep all defined variables in forked scopes
|
||||
// all those variables should not be available for further executions
|
||||
os, err := iterator.dataSource.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if os == nil {
|
||||
break
|
||||
}
|
||||
|
||||
counter++
|
||||
}
|
||||
|
||||
return []collections.DataSet{
|
||||
{
|
||||
iterator.params.count.variable: values.NewInt(counter),
|
||||
},
|
||||
}, nil
|
||||
cs := scope.Fork()
|
||||
|
||||
if err := cs.SetVariable(iterator.params.count.variable, values.NewInt(counter)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*core.Scope{cs}, nil
|
||||
}
|
||||
|
||||
func (iterator *CollectIterator) aggregate() ([]collections.DataSet, error) {
|
||||
ds := collections.NewDataSet()
|
||||
func (iterator *CollectIterator) aggregate(ctx context.Context, scope *core.Scope) ([]*core.Scope, error) {
|
||||
cs := scope.Fork()
|
||||
|
||||
// matrix of aggregated expressions
|
||||
// string key of the map is a selector variable
|
||||
// value of the map is a matrix of arguments
|
||||
// e.g. x = CONCAT(arg1, arg2, argN...)
|
||||
// x is a string key where a nested array is an array of all values of argN expressions
|
||||
aggregated := make(map[string][]core.Value)
|
||||
ctx := iterator.ctx
|
||||
selectors := iterator.params.aggregate.selectors
|
||||
|
||||
// iterating over underlying data source
|
||||
for iterator.dataSource.HasNext() {
|
||||
set, err := iterator.dataSource.Next()
|
||||
for {
|
||||
// keep all defined variables in forked scopes
|
||||
// all those variables should not be available for further executions
|
||||
os, err := iterator.dataSource.Next(ctx, scope.Fork())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(set) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// creating a new scope for all further operations
|
||||
childScope := iterator.scope.Fork()
|
||||
|
||||
// populate the new scope with results from an underlying source and its exposed variables
|
||||
if err := set.Apply(childScope, iterator.variables); err != nil {
|
||||
return nil, err
|
||||
if os == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// iterate over each selector for a current data set
|
||||
@ -376,7 +386,7 @@ func (iterator *CollectIterator) aggregate() ([]collections.DataSet, error) {
|
||||
// e.g. AGGREGATE age = CONCAT(u.age, u.dob)
|
||||
// u.age and u.dob get executed
|
||||
for idx, exp := range selector.aggregators {
|
||||
arg, err := exp.Exec(ctx, childScope)
|
||||
arg, err := exp.Exec(ctx, os)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -405,8 +415,10 @@ func (iterator *CollectIterator) aggregate() ([]collections.DataSet, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds.Set(selector.variable, reduced)
|
||||
if err := cs.SetVariable(selector.variable, reduced); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return []collections.DataSet{ds}, nil
|
||||
return []*core.Scope{cs}, nil
|
||||
}
|
||||
|
@ -32,10 +32,6 @@ func NewFilterClause(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (clause *FilterClause) Variables() collections.Variables {
|
||||
return clause.dataSource.Variables()
|
||||
}
|
||||
|
||||
func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
@ -43,27 +39,19 @@ func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (col
|
||||
return nil, err
|
||||
}
|
||||
|
||||
variables := clause.dataSource.Variables()
|
||||
|
||||
return collections.NewFilterIterator(src, func(set collections.DataSet) (bool, error) {
|
||||
innerScope := scope.Fork()
|
||||
|
||||
err := set.Apply(innerScope, variables)
|
||||
|
||||
if err != nil {
|
||||
return false, core.SourceError(clause.src, err)
|
||||
}
|
||||
|
||||
ret, err := clause.predicate.Exec(ctx, innerScope)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ret == values.True {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
return collections.NewFilterIterator(src, clause.filter)
|
||||
}
|
||||
|
||||
func (clause *FilterClause) filter(ctx context.Context, scope *core.Scope) (bool, error) {
|
||||
ret, err := clause.predicate.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ret == values.True {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
@ -26,10 +26,6 @@ func NewLimitClause(
|
||||
return &LimitClause{src, dataSource, count, offset}, nil
|
||||
}
|
||||
|
||||
func (clause *LimitClause) Variables() collections.Variables {
|
||||
return clause.dataSource.Variables()
|
||||
}
|
||||
|
||||
func (clause *LimitClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
|
@ -11,6 +11,7 @@ type (
|
||||
expression core.Expression
|
||||
direction collections.SortDirection
|
||||
}
|
||||
|
||||
SortClause struct {
|
||||
src core.SourceMap
|
||||
dataSource collections.Iterable
|
||||
@ -46,10 +47,6 @@ func NewSortClause(
|
||||
return &SortClause{src, dataSource, sorters}, nil
|
||||
}
|
||||
|
||||
func (clause *SortClause) Variables() collections.Variables {
|
||||
return clause.dataSource.Variables()
|
||||
}
|
||||
|
||||
func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
@ -58,31 +55,10 @@ func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (colle
|
||||
}
|
||||
|
||||
sorters := make([]*collections.Sorter, len(clause.sorters))
|
||||
variables := clause.dataSource.Variables()
|
||||
|
||||
// converting sorter reducer into collections.Sorter
|
||||
for idx, srt := range clause.sorters {
|
||||
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
scope1 := scope.Fork()
|
||||
first.Apply(scope1, variables)
|
||||
|
||||
f, err := srt.expression.Exec(ctx, scope1)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
scope2 := scope.Fork()
|
||||
second.Apply(scope2, variables)
|
||||
|
||||
s, err := srt.expression.Exec(ctx, scope2)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return f.Compare(s), nil
|
||||
}, srt.direction)
|
||||
sorter, err := newSorter(srt)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -93,3 +69,21 @@ func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (colle
|
||||
|
||||
return collections.NewSortIterator(src, sorters...)
|
||||
}
|
||||
|
||||
func newSorter(srt *SorterExpression) (*collections.Sorter, error) {
|
||||
return collections.NewSorter(func(ctx context.Context, first, second *core.Scope) (int, error) {
|
||||
f, err := srt.expression.Exec(ctx, first)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
s, err := srt.expression.Exec(ctx, second)
|
||||
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return f.Compare(s), nil
|
||||
}, srt.direction)
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ func NewConditionExpression(
|
||||
consequent core.Expression,
|
||||
alternate core.Expression,
|
||||
) (*ConditionExpression, error) {
|
||||
if core.IsNil(test) {
|
||||
if test == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "test expression")
|
||||
}
|
||||
|
||||
if core.IsNil(alternate) {
|
||||
if alternate == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "alternate expression")
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type DataSource struct {
|
||||
src core.SourceMap
|
||||
variables collections.Variables
|
||||
exp core.Expression
|
||||
src core.SourceMap
|
||||
valVariable string
|
||||
keyVariable string
|
||||
exp core.Expression
|
||||
}
|
||||
|
||||
func NewDataSource(
|
||||
@ -25,15 +26,12 @@ func NewDataSource(
|
||||
|
||||
return &DataSource{
|
||||
src,
|
||||
collections.Variables{valVariable, keyVariable},
|
||||
valVariable,
|
||||
keyVariable,
|
||||
exp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ds *DataSource) Variables() collections.Variables {
|
||||
return ds.variables
|
||||
}
|
||||
|
||||
func (ds *DataSource) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
data, err := ds.exp.Exec(ctx, scope)
|
||||
|
||||
@ -41,23 +39,20 @@ func (ds *DataSource) Iterate(ctx context.Context, scope *core.Scope) (collectio
|
||||
return nil, core.SourceError(ds.src, err)
|
||||
}
|
||||
|
||||
valVar := ds.variables[0]
|
||||
keyVar := ds.variables[1]
|
||||
|
||||
switch data.Type() {
|
||||
case core.ArrayType:
|
||||
return collections.NewIndexedIterator(valVar, keyVar, data.(collections.IndexedCollection)), nil
|
||||
return collections.NewIndexedIterator(ds.valVariable, ds.keyVariable, data.(collections.IndexedCollection))
|
||||
case core.ObjectType:
|
||||
return collections.NewKeyedIterator(valVar, keyVar, data.(collections.KeyedCollection)), nil
|
||||
return collections.NewKeyedIterator(ds.valVariable, ds.keyVariable, data.(collections.KeyedCollection))
|
||||
case core.HTMLElementType, core.HTMLDocumentType:
|
||||
return collections.NewHTMLNodeIterator(valVar, keyVar, data.(values.HTMLNode)), nil
|
||||
return collections.NewHTMLNodeIterator(ds.valVariable, ds.keyVariable, data.(values.HTMLNode))
|
||||
default:
|
||||
// fallback to user defined types
|
||||
switch data.(type) {
|
||||
case collections.KeyedCollection:
|
||||
return collections.NewIndexedIterator(valVar, keyVar, data.(collections.IndexedCollection)), nil
|
||||
return collections.NewIndexedIterator(ds.valVariable, ds.keyVariable, data.(collections.IndexedCollection))
|
||||
case collections.IndexedCollection:
|
||||
return collections.NewKeyedIterator(valVar, keyVar, data.(collections.KeyedCollection)), nil
|
||||
return collections.NewKeyedIterator(ds.valVariable, ds.keyVariable, data.(collections.KeyedCollection))
|
||||
default:
|
||||
return nil, core.TypeError(
|
||||
data.Type(),
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions/clauses"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ForExpression struct {
|
||||
@ -24,12 +23,12 @@ func NewForExpression(
|
||||
distinct,
|
||||
spread bool,
|
||||
) (*ForExpression, error) {
|
||||
if core.IsNil(dataSource) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed source expression")
|
||||
if dataSource == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "missed source expression")
|
||||
}
|
||||
|
||||
if core.IsNil(predicate) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed return expression")
|
||||
if predicate == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "missed return expression")
|
||||
}
|
||||
|
||||
return &ForExpression{
|
||||
@ -89,6 +88,25 @@ func (e *ForExpression) AddCollect(src core.SourceMap, params *clauses.Collect)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ForExpression) AddStatement(stmt core.Expression) error {
|
||||
tap, ok := e.dataSource.(*BlockExpression)
|
||||
|
||||
if !ok {
|
||||
t, err := NewBlockExpression(e.dataSource)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tap = t
|
||||
e.dataSource = tap
|
||||
}
|
||||
|
||||
tap.Add(stmt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
iterator, err := e.dataSource.Iterate(ctx, scope)
|
||||
|
||||
@ -104,22 +122,19 @@ func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value
|
||||
}
|
||||
|
||||
res := values.NewArray(10)
|
||||
variables := e.dataSource.Variables()
|
||||
|
||||
for iterator.HasNext() {
|
||||
ds, err := iterator.Next()
|
||||
for {
|
||||
nextScope, err := iterator.Next(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
innerScope := scope.Fork()
|
||||
|
||||
if err := ds.Apply(innerScope, variables); err != nil {
|
||||
return values.None, err
|
||||
// no data anymore
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
out, err := e.predicate.Exec(ctx, innerScope)
|
||||
out, err := e.predicate.Exec(ctx, nextScope)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type MemberExpression struct {
|
||||
@ -15,11 +14,11 @@ type MemberExpression struct {
|
||||
|
||||
func NewMemberExpression(src core.SourceMap, variableName string, path []core.Expression) (*MemberExpression, error) {
|
||||
if variableName == "" {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "variable name")
|
||||
return nil, core.Error(core.ErrMissedArgument, "variable name")
|
||||
}
|
||||
|
||||
if path == nil || len(path) == 0 {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "path expressions")
|
||||
return nil, core.Error(core.ErrMissedArgument, "path expressions")
|
||||
}
|
||||
|
||||
return &MemberExpression{src, variableName, path}, nil
|
||||
|
@ -15,11 +15,11 @@ func NewRangeOperator(
|
||||
left core.Expression,
|
||||
right core.Expression,
|
||||
) (*RangeOperator, error) {
|
||||
if core.IsNil(left) {
|
||||
if left == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "left expression")
|
||||
}
|
||||
|
||||
if core.IsNil(right) {
|
||||
if right == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "right expression")
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -19,7 +18,7 @@ func NewReturnExpression(
|
||||
predicate core.Expression,
|
||||
) (*ReturnExpression, error) {
|
||||
if predicate == nil {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "expression")
|
||||
return nil, core.Error(core.ErrMissedArgument, "expression")
|
||||
}
|
||||
|
||||
return &ReturnExpression{
|
||||
|
@ -40,7 +40,7 @@ func TestReturnExpressionExec(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootScope, fn := core.NewRootScope()
|
||||
scope := core.NewScope(rootScope)
|
||||
scope := rootScope.Fork()
|
||||
scope.SetVariable("test", values.NewString("value"))
|
||||
fn()
|
||||
|
||||
@ -59,7 +59,7 @@ func TestReturnExpressionExec(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootScope, fn := core.NewRootScope()
|
||||
scope := core.NewScope(rootScope)
|
||||
scope := rootScope.Fork()
|
||||
scope.SetVariable("test", values.NewString("value"))
|
||||
fn()
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -21,7 +20,7 @@ type (
|
||||
|
||||
func NewVariableExpression(src core.SourceMap, name string) (*VariableExpression, error) {
|
||||
if name == "" {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed variable name")
|
||||
return nil, core.Error(core.ErrMissedArgument, "missed variable name")
|
||||
}
|
||||
|
||||
return &VariableExpression{src, name}, nil
|
||||
@ -34,8 +33,8 @@ func NewVariableDeclarationExpression(src core.SourceMap, name string, init core
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if core.IsNil(init) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed variable initializer")
|
||||
if init == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "missed variable initializer")
|
||||
}
|
||||
|
||||
return &VariableDeclarationExpression{v, init}, nil
|
||||
|
@ -17,7 +17,7 @@ func NewProgram(src string, body core.Expression) (*Program, error) {
|
||||
return nil, core.Error(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
if core.IsNil(body) {
|
||||
if body == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "body")
|
||||
}
|
||||
|
||||
|
@ -7,26 +7,28 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
ArrayPredicate = func(value core.Value, idx int) bool
|
||||
Array struct {
|
||||
value []core.Value
|
||||
|
||||
ArraySorter = func(first, second core.Value) bool
|
||||
|
||||
Array struct {
|
||||
items []core.Value
|
||||
}
|
||||
)
|
||||
|
||||
func NewArray(size int) *Array {
|
||||
return &Array{value: make([]core.Value, 0, size)}
|
||||
return &Array{items: make([]core.Value, 0, size)}
|
||||
}
|
||||
|
||||
func NewArrayWith(values ...core.Value) *Array {
|
||||
return &Array{value: values}
|
||||
return &Array{items: values}
|
||||
}
|
||||
|
||||
func (t *Array) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.value)
|
||||
return json.Marshal(t.items)
|
||||
}
|
||||
|
||||
func (t *Array) Type() core.Type {
|
||||
@ -79,7 +81,7 @@ func (t *Array) Compare(other core.Value) int {
|
||||
func (t *Array) Unwrap() interface{} {
|
||||
arr := make([]interface{}, t.Length())
|
||||
|
||||
for idx, val := range t.value {
|
||||
for idx, val := range t.items {
|
||||
arr[idx] = val.Unwrap()
|
||||
}
|
||||
|
||||
@ -93,9 +95,9 @@ func (t *Array) Hash() uint64 {
|
||||
h.Write([]byte(":"))
|
||||
h.Write([]byte("["))
|
||||
|
||||
endIndex := len(t.value) - 1
|
||||
endIndex := len(t.items) - 1
|
||||
|
||||
for i, el := range t.value {
|
||||
for i, el := range t.items {
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, el.Hash())
|
||||
|
||||
@ -112,9 +114,9 @@ func (t *Array) Hash() uint64 {
|
||||
}
|
||||
|
||||
func (t *Array) Copy() core.Value {
|
||||
c := NewArray(len(t.value))
|
||||
c := NewArray(len(t.items))
|
||||
|
||||
for _, el := range t.value {
|
||||
for _, el := range t.items {
|
||||
c.Push(el)
|
||||
}
|
||||
|
||||
@ -122,11 +124,11 @@ func (t *Array) Copy() core.Value {
|
||||
}
|
||||
|
||||
func (t *Array) Length() Int {
|
||||
return Int(len(t.value))
|
||||
return Int(len(t.items))
|
||||
}
|
||||
|
||||
func (t *Array) ForEach(predicate ArrayPredicate) {
|
||||
for idx, val := range t.value {
|
||||
for idx, val := range t.items {
|
||||
if predicate(val, idx) == false {
|
||||
break
|
||||
}
|
||||
@ -134,7 +136,7 @@ func (t *Array) ForEach(predicate ArrayPredicate) {
|
||||
}
|
||||
|
||||
func (t *Array) Get(idx Int) core.Value {
|
||||
l := len(t.value) - 1
|
||||
l := len(t.items) - 1
|
||||
|
||||
if l < 0 {
|
||||
return None
|
||||
@ -144,23 +146,23 @@ func (t *Array) Get(idx Int) core.Value {
|
||||
return None
|
||||
}
|
||||
|
||||
return t.value[idx]
|
||||
return t.items[idx]
|
||||
}
|
||||
|
||||
func (t *Array) Set(idx Int, value core.Value) error {
|
||||
last := len(t.value) - 1
|
||||
last := len(t.items) - 1
|
||||
|
||||
if last >= int(idx) {
|
||||
t.value[idx] = value
|
||||
t.items[idx] = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(core.ErrInvalidOperation, "out of bounds")
|
||||
return core.Error(core.ErrInvalidOperation, "out of bounds")
|
||||
}
|
||||
|
||||
func (t *Array) Push(item core.Value) {
|
||||
t.value = append(t.value, item)
|
||||
t.items = append(t.items, item)
|
||||
}
|
||||
|
||||
func (t *Array) Slice(from, to Int) *Array {
|
||||
@ -175,7 +177,7 @@ func (t *Array) Slice(from, to Int) *Array {
|
||||
}
|
||||
|
||||
result := new(Array)
|
||||
result.value = t.value[from:to]
|
||||
result.items = t.items[from:to]
|
||||
|
||||
return result
|
||||
}
|
||||
@ -183,7 +185,7 @@ func (t *Array) Slice(from, to Int) *Array {
|
||||
func (t *Array) IndexOf(item core.Value) Int {
|
||||
res := Int(-1)
|
||||
|
||||
for idx, el := range t.value {
|
||||
for idx, el := range t.items {
|
||||
if el.Compare(item) == 0 {
|
||||
res = Int(idx)
|
||||
break
|
||||
@ -194,18 +196,18 @@ func (t *Array) IndexOf(item core.Value) Int {
|
||||
}
|
||||
|
||||
func (t *Array) Insert(idx Int, value core.Value) {
|
||||
t.value = append(t.value[:idx], append([]core.Value{value}, t.value[idx:]...)...)
|
||||
t.items = append(t.items[:idx], append([]core.Value{value}, t.items[idx:]...)...)
|
||||
}
|
||||
|
||||
func (t *Array) RemoveAt(idx Int) {
|
||||
i := int(idx)
|
||||
max := len(t.value) - 1
|
||||
max := len(t.items) - 1
|
||||
|
||||
if i > max {
|
||||
return
|
||||
}
|
||||
|
||||
t.value = append(t.value[:i], t.value[i+1:]...)
|
||||
t.items = append(t.items[:i], t.items[i+1:]...)
|
||||
}
|
||||
|
||||
func (t *Array) Clone() core.Cloneable {
|
||||
@ -224,15 +226,21 @@ func (t *Array) Clone() core.Cloneable {
|
||||
}
|
||||
|
||||
func (t *Array) Sort() *Array {
|
||||
c := make([]core.Value, len(t.value))
|
||||
copy(c, t.value)
|
||||
return t.SortWith(func(first, second core.Value) bool {
|
||||
return first.Compare(second) == -1
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Array) SortWith(sorter ArraySorter) *Array {
|
||||
c := make([]core.Value, len(t.items))
|
||||
copy(c, t.items)
|
||||
|
||||
sort.SliceStable(c, func(i, j int) bool {
|
||||
return c[i].Compare(c[j]) == -1
|
||||
return sorter(c[i], c[j])
|
||||
})
|
||||
|
||||
res := new(Array)
|
||||
res.value = c
|
||||
res.items = c
|
||||
|
||||
return res
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ func TestArray(t *testing.T) {
|
||||
So(el.Compare(values.NewInt(2)), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return None when no value", func() {
|
||||
Convey("Should return None when no items", func() {
|
||||
arr := values.NewArrayWith()
|
||||
|
||||
el := arr.Get(1)
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Boolean bool
|
||||
@ -43,7 +42,7 @@ func ParseBoolean(input interface{}) (Boolean, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return False, errors.Wrap(core.ErrInvalidType, "expected 'bool'")
|
||||
return False, core.Error(core.ErrInvalidType, "expected 'bool'")
|
||||
}
|
||||
|
||||
func ParseBooleanP(input interface{}) Boolean {
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
func TestBoolean(t *testing.T) {
|
||||
Convey(".MarshalJSON", t, func() {
|
||||
Convey("Should serialize a boolean value", func() {
|
||||
Convey("Should serialize a boolean items", func() {
|
||||
b := values.True
|
||||
marshaled, err := b.MarshalJSON()
|
||||
|
||||
@ -26,7 +26,7 @@ func TestBoolean(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey(".Unwrap", t, func() {
|
||||
Convey("Should return an unwrapped value", func() {
|
||||
Convey("Should return an unwrapped items", func() {
|
||||
So(values.True.Unwrap(), ShouldHaveSameTypeAs, true)
|
||||
})
|
||||
})
|
||||
|
@ -4,12 +4,10 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Float float64
|
||||
@ -50,7 +48,7 @@ func ParseFloat(input interface{}) (Float, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return ZeroFloat, errors.Wrap(core.ErrInvalidType, "expected 'float'")
|
||||
return ZeroFloat, core.Error(core.ErrInvalidType, "expected 'float'")
|
||||
}
|
||||
|
||||
func ParseFloatP(input interface{}) Float {
|
||||
|
@ -1,8 +1,11 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"hash/fnv"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
@ -61,7 +64,7 @@ func GetIn(from core.Value, byPath []core.Value) (core.Value, error) {
|
||||
result = el.InnerText()
|
||||
case "innerHTML":
|
||||
result = el.InnerHTML()
|
||||
case "value":
|
||||
case "items":
|
||||
result = el.Value()
|
||||
case "attributes":
|
||||
result = el.GetAttributes()
|
||||
@ -358,3 +361,40 @@ func ToArray(input core.Value) core.Value {
|
||||
return NewArray(0)
|
||||
}
|
||||
}
|
||||
|
||||
func MapHash(input map[string]core.Value) uint64 {
|
||||
h := fnv.New64a()
|
||||
|
||||
keys := make([]string, 0, len(input))
|
||||
|
||||
for key := range input {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// order does not really matter
|
||||
// but it will give us a consistent hash sum
|
||||
sort.Strings(keys)
|
||||
endIndex := len(keys) - 1
|
||||
|
||||
h.Write([]byte("{"))
|
||||
|
||||
for idx, key := range keys {
|
||||
h.Write([]byte(key))
|
||||
h.Write([]byte(":"))
|
||||
|
||||
el := input[key]
|
||||
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, el.Hash())
|
||||
|
||||
h.Write(bytes)
|
||||
|
||||
if idx != endIndex {
|
||||
h.Write([]byte(","))
|
||||
}
|
||||
}
|
||||
|
||||
h.Write([]byte("}"))
|
||||
|
||||
return h.Sum64()
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Int int
|
||||
@ -48,7 +47,7 @@ func ParseInt(input interface{}) (Int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return ZeroInt, errors.Wrap(core.ErrInvalidType, "expected 'int'")
|
||||
return ZeroInt, core.Error(core.ErrInvalidType, "expected 'int'")
|
||||
}
|
||||
|
||||
func ParseIntP(input interface{}) Int {
|
||||
|
@ -206,7 +206,7 @@ func (t *Object) GetIn(path []core.Value) (core.Value, error) {
|
||||
}
|
||||
|
||||
func (t *Object) Set(key String, value core.Value) {
|
||||
if core.IsNil(value) == false {
|
||||
if value != nil {
|
||||
t.value[string(key)] = value
|
||||
} else {
|
||||
t.value[string(key)] = None
|
||||
|
@ -68,7 +68,7 @@ func TestObject(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey(".Unwrap", t, func() {
|
||||
Convey("Should return an unwrapped value", func() {
|
||||
Convey("Should return an unwrapped items", func() {
|
||||
obj := values.NewObjectWith(
|
||||
values.NewObjectProperty("foo", values.NewString("foo")),
|
||||
values.NewObjectProperty("bar", values.NewString("bar")),
|
||||
@ -341,7 +341,7 @@ func TestObject(t *testing.T) {
|
||||
So(el.Compare(values.NewInt(1)), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return None when no value", func() {
|
||||
Convey("Should return None when no items", func() {
|
||||
obj := values.NewObject()
|
||||
|
||||
el, _ := obj.Get("foo")
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type String string
|
||||
@ -52,7 +51,7 @@ func ParseString(input interface{}) (String, error) {
|
||||
return String(stringer.String()), nil
|
||||
}
|
||||
|
||||
return EmptyString, errors.Wrap(core.ErrInvalidType, "expected 'string'")
|
||||
return EmptyString, core.Error(core.ErrInvalidType, "expected 'string'")
|
||||
}
|
||||
|
||||
func ParseStringP(input interface{}) String {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
@ -34,18 +33,22 @@ func NewLib() map[string]core.Function {
|
||||
}
|
||||
}
|
||||
|
||||
func toArray(iterator collections.Iterator) (core.Value, error) {
|
||||
arr := values.NewArray(10)
|
||||
func ToUniqueArray(arr *values.Array) *values.Array {
|
||||
hashTable := make(map[uint64]bool)
|
||||
result := values.NewArray(int(arr.Length()))
|
||||
|
||||
for iterator.HasNext() {
|
||||
ds, err := iterator.Next()
|
||||
arr.ForEach(func(item core.Value, _ int) bool {
|
||||
h := item.Hash()
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
_, exists := hashTable[h]
|
||||
|
||||
if !exists {
|
||||
hashTable[h] = true
|
||||
result.Push(item)
|
||||
}
|
||||
|
||||
arr.Push(ds.Get(collections.DefaultValueVar))
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return arr, nil
|
||||
return result
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ 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"
|
||||
)
|
||||
@ -31,22 +29,5 @@ func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
}, collections.SortDirectionAsc)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
iterator, err := collections.NewSortIterator(
|
||||
collections.NewDefaultIndexedIterator(arr),
|
||||
sorter,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return toArray(iterator)
|
||||
return arr.Sort(), nil
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ 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"
|
||||
)
|
||||
@ -32,31 +31,5 @@ func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
}, collections.SortDirectionAsc)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
uniqIterator, err := collections.NewUniqueIterator(
|
||||
collections.NewDefaultIndexedIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
iterator, err := collections.NewSortIterator(
|
||||
uniqIterator,
|
||||
sorter,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return toArray(iterator)
|
||||
return ToUniqueArray(arr.Sort()), nil
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ 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"
|
||||
)
|
||||
@ -30,14 +29,5 @@ func Unique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
return values.NewArray(0), nil
|
||||
}
|
||||
|
||||
iterator, err := collections.NewUniqueIterator(
|
||||
collections.NewDefaultIndexedIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return toArray(iterator)
|
||||
return ToUniqueArray(arr), nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user