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