1
0
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:
Tim Voronov 2018-10-28 01:45:26 -04:00 committed by GitHub
parent e6fd33ac1d
commit 3472630e6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2828 additions and 2185 deletions

View File

@ -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}/...

View File

@ -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()

View File

@ -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) {

View File

@ -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) {

View 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]`)
})
}

View 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"}]`)
})
}

View 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
}

View File

@ -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)
}

View File

@ -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]

View File

@ -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

View File

@ -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) {}

View File

@ -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)
}

View File

@ -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)

View File

@ -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{}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}

View File

@ -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)

View 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
}

View 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)
})
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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)
}

View 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
}

View File

@ -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()

View File

@ -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,
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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(),

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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{

View File

@ -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()

View File

@ -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

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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)
})
})

View File

@ -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 {

View File

@ -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()
}

View File

@ -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 {

View File

@ -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

View File

@ -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")

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}