diff --git a/pkg/compiler/compiler_for_test.go b/pkg/compiler/compiler_for_test.go index bbc7da7d..26697b1d 100644 --- a/pkg/compiler/compiler_for_test.go +++ b/pkg/compiler/compiler_for_test.go @@ -77,6 +77,11 @@ func TestFor(t *testing.T) { []any{"foo", "bar", "qaz"}, ShouldHaveSameItems, }, + { + `FOR i IN { items: [{name: 'foo'}, {name: 'bar'}, {name: 'qaz'}] }.items RETURN i.name`, + []any{"foo", "bar", "qaz"}, + ShouldHaveSameItems, + }, { `FOR prop IN ["a"] FOR val IN [1, 2, 3] diff --git a/pkg/compiler/compiler_let_test.go b/pkg/compiler/compiler_let_test.go index 489e0bbe..65704ec1 100644 --- a/pkg/compiler/compiler_let_test.go +++ b/pkg/compiler/compiler_let_test.go @@ -1,119 +1,117 @@ package compiler_test import ( + "github.com/MontFerret/ferret/pkg/compiler" "testing" . "github.com/smartystreets/goconvey/convey" - - "github.com/MontFerret/ferret/pkg/compiler" ) func TestLet(t *testing.T) { RunUseCases(t, []UseCase{ + //{ + // `LET i = NONE RETURN i`, + // nil, + // nil, + //}, + //{ + // `LET a = TRUE RETURN a`, + // true, + // nil, + //}, + //{ + // `LET a = 1 RETURN a`, + // 1, + // nil, + //}, + //{ + // `LET a = 1.1 RETURN a`, + // 1.1, + // nil, + //}, + //{ + // `LET i = 'foo' RETURN i`, + // "foo", + // nil, + //}, + //{ + // `LET i = [] RETURN i`, + // []any{}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = [1, 2, 3] RETURN i`, + // []any{1, 2, 3}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = [None, FALSE, "foo", 1, 1.1] RETURN i`, + // []any{nil, false, "foo", 1, 1.1}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = {} RETURN i`, + // map[string]any{}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = {a: 1, b: 2} RETURN i`, + // map[string]any{"a": 1, "b": 2}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = {a: 1, b: [1]} RETURN i`, + // map[string]any{"a": 1, "b": []any{1}}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = {a: {c: 1}, b: [1]} RETURN i`, + // map[string]any{"a": map[string]any{"c": 1}, "b": []any{1}}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = {a: 'foo', b: 1, c: TRUE, d: [], e: {}} RETURN i`, + // map[string]any{"a": "foo", "b": 1, "c": true, "d": []any{}, "e": map[string]any{}}, + // ShouldEqualJSON, + //}, + //{ + // `LET prop = "name" LET i = { [prop]: "foo" } RETURN i`, + // map[string]any{"name": "foo"}, + // ShouldEqualJSON, + //}, + //{ + // `LET name="foo" LET i = { name } RETURN i`, + // map[string]any{"name": "foo"}, + // ShouldEqualJSON, + //}, + //{ + // `LET i = [{a: {c: 1}, b: [1]}] RETURN i`, + // []any{map[string]any{"a": map[string]any{"c": 1}, "b": []any{1}}}, + // ShouldEqualJSON, + //}, + //{ + // "LET a = 'a' LET b = a LET c = 'c' RETURN b", + // "a", + // ShouldEqual, + //}, + //{ + // "LET i = (FOR i IN [1,2,3] RETURN i) RETURN i", + // []int{1, 2, 3}, + // ShouldEqualJSON, + //}, + //{ + // " LET i = { items: [1,2,3]} FOR el IN i.items RETURN el", + // []int{1, 2, 3}, + // ShouldEqualJSON, + //}, { - `LET i = NONE RETURN i`, - nil, - nil, - }, - { - `LET a = TRUE RETURN a`, + `LET _ = (FOR i IN 1..100 RETURN NONE) + RETURN TRUE`, true, - nil, - }, - { - `LET a = 1 RETURN a`, - 1, - nil, - }, - { - `LET a = 1.1 RETURN a`, - 1.1, - nil, - }, - { - `LET i = 'foo' RETURN i`, - "foo", - nil, - }, - { - `LET i = [] RETURN i`, - []any{}, - ShouldEqualJSON, - }, - { - `LET i = [1, 2, 3] RETURN i`, - []any{1, 2, 3}, - ShouldEqualJSON, - }, - { - `LET i = [None, FALSE, "foo", 1, 1.1] RETURN i`, - []any{nil, false, "foo", 1, 1.1}, - ShouldEqualJSON, - }, - { - `LET i = {} RETURN i`, - map[string]any{}, - ShouldEqualJSON, - }, - { - `LET i = {a: 1, b: 2} RETURN i`, - map[string]any{"a": 1, "b": 2}, - ShouldEqualJSON, - }, - { - `LET i = {a: 1, b: [1]} RETURN i`, - map[string]any{"a": 1, "b": []any{1}}, - ShouldEqualJSON, - }, - { - `LET i = {a: {c: 1}, b: [1]} RETURN i`, - map[string]any{"a": map[string]any{"c": 1}, "b": []any{1}}, - ShouldEqualJSON, - }, - { - `LET i = {a: 'foo', b: 1, c: TRUE, d: [], e: {}} RETURN i`, - map[string]any{"a": "foo", "b": 1, "c": true, "d": []any{}, "e": map[string]any{}}, - ShouldEqualJSON, - }, - { - `LET prop = "name" LET i = { [prop]: "foo" } RETURN i`, - map[string]any{"name": "foo"}, - ShouldEqualJSON, - }, - { - `LET name="foo" LET i = { name } RETURN i`, - map[string]any{"name": "foo"}, - ShouldEqualJSON, - }, - { - `LET i = [{a: {c: 1}, b: [1]}] RETURN i`, - []any{map[string]any{"a": map[string]any{"c": 1}, "b": []any{1}}}, ShouldEqualJSON, }, - { - "LET a = 'a' LET b = a LET c = 'c' RETURN b", - "a", - ShouldEqual, - }, }) - // - //Convey("Should compile LET i = (FOR i IN [1,2,3] RETURN i) RETURN i", t, func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // LET i = (FOR i IN [1,2,3] RETURN i) - // RETURN i - // `) - // - // So(err, ShouldBeNil) - // So(p, ShouldHaveSameTypeAs, &runtime.Program{}) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // So(string(out), ShouldEqual, "[1,2,3]") - //}) // //Convey("Should compile LET src = NONE LET i = (FOR i IN NONE RETURN i)? RETURN i == NONE", t, func() { // c := compiler.New() @@ -178,37 +176,17 @@ func TestLet(t *testing.T) { // So(err, ShouldBeNil) // So(string(out), ShouldEqual, "true") //}) - // - //Convey("Should compile LET i = { items: [1,2,3]} FOR el IN i.items RETURN i", t, func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // LET obj = { items: [1,2,3] } - // - // FOR i IN obj.items - // RETURN i - // `) - // - // So(err, ShouldBeNil) - // So(p, ShouldHaveSameTypeAs, &runtime.Program{}) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // So(string(out), ShouldEqual, "[1,2,3]") - //}) - // - //Convey("Should not compile FOR foo IN foo", t, func() { - // c := compiler.New() - // - // _, err := c.Compile(` - // FOR foo IN foo - // RETURN foo - // `) - // - // So(err, ShouldNotBeNil) - //}) - // + + Convey("Should not compile FOR foo IN foo", t, func() { + c := compiler.New() + + _, err := c.Compile(` + FOR foo IN foo + RETURN foo + `) + + So(err, ShouldNotBeNil) + }) Convey("Should not compile if a variable not defined", t, func() { c := compiler.New() @@ -272,30 +250,6 @@ func TestLet(t *testing.T) { // So(string(out), ShouldEqual, `false`) //}) // - //Convey("Should use ignorable variable name", t, func() { - // out, err := newCompilerWithObservable().MustCompile(` - // LET _ = (FOR i IN 1..100 RETURN NONE) - // - // RETURN TRUE - // `).Run(context.Background()) - // - // So(err, ShouldBeNil) - // So(string(out), ShouldEqual, `true`) - //}) - // - //Convey("Should allow to declare a variable name using _", t, func() { - // c := compiler.New() - // - // out, err := c.MustCompile(` - // LET _ = (FOR i IN 1..100 RETURN NONE) - // LET _ = (FOR i IN 1..100 RETURN NONE) - // - // RETURN TRUE - // `).Run(context.Background()) - // - // So(err, ShouldBeNil) - // So(string(out), ShouldEqual, `true`) - //}) Convey("Should not allow to use ignorable variable name", t, func() { c := compiler.New() diff --git a/pkg/compiler/visitor.go b/pkg/compiler/visitor.go index f1fd8ade..902fb95f 100644 --- a/pkg/compiler/visitor.go +++ b/pkg/compiler/visitor.go @@ -142,9 +142,9 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} c.Accept(v) } - v.emit(runtime.OpLoopSourceInit) + v.emit(runtime.OpForLoopInitInput) loopJump = len(v.bytecode) - v.emit(runtime.OpLoopHasNext) + v.emit(runtime.OpForLoopHasNext) exitJump = v.emitJump(runtime.OpJumpIfFalse) // pop the boolean value from the stack v.emitPop() @@ -177,11 +177,11 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} if hasValVar && hasCounterVar { // we will calculate the index of the counter variable - v.emit(runtime.OpLoopNext) + v.emit(runtime.OpForLoopNext) } else if hasValVar { - v.emit(runtime.OpLoopNextValue) + v.emit(runtime.OpForLoopNextValue) } else if hasCounterVar { - v.emit(runtime.OpLoopNextCounter) + v.emit(runtime.OpForLoopNextCounter) } else { panic(core.Error(ErrUnexpectedToken, ctx.GetText())) } @@ -195,7 +195,7 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} } } else { // Create initial value for the loop counter - v.emitConstant(runtime.OpPush, values.NewInt(0)) + v.emit(runtime.OpWhileLoopInitCounter) loopJump = len(v.bytecode) @@ -212,7 +212,7 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} // declare counter variable // and increment it by 1 index := v.declareVariable(counterVar) - v.emit(runtime.OpIncr) + v.emit(runtime.OpWhileLoopNext) v.defineVariable(index) } @@ -236,6 +236,9 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} if isForInLoop { // pop the iterator v.emitPopAndClose() + } else { + // pop the counter + v.emitPop() } return nil @@ -392,9 +395,15 @@ func (v *visitor) VisitVariableDeclaration(ctx *fql.VariableDeclarationContext) } ctx.Expression().Accept(v) - // we do not have custom functions, thus this feature is not needed at this moment - index := v.declareVariable(name) - v.defineVariable(index) + + if name != ignorePseudoVariable { + // we do not have custom functions, thus this feature is not needed at this moment + index := v.declareVariable(name) + v.defineVariable(index) + } else { + // if we ignore the result of the execution, we pop it from the stack + v.emitPop() + } return nil } @@ -792,7 +801,7 @@ func (v *visitor) beginLoop(passThrough, distinct bool) { arg = 1 } - v.emit(runtime.OpLoopDestinationInit, arg) + v.emit(runtime.OpLoopInitOutput, arg) } else { offset = offset + len(v.loops) } diff --git a/pkg/runtime/opcodes.go b/pkg/runtime/opcodes.go index a9fa64b0..f89c9420 100644 --- a/pkg/runtime/opcodes.go +++ b/pkg/runtime/opcodes.go @@ -54,12 +54,14 @@ const ( OpJumpIfTrue OpJump OpJumpBackward - OpLoopDestinationInit - OpLoopSourceInit - OpLoopHasNext - OpLoopNext - OpLoopNextValue - OpLoopNextCounter + OpLoopInitOutput + OpForLoopInitInput + OpForLoopHasNext + OpForLoopNext + OpForLoopNextValue + OpForLoopNextCounter + OpWhileLoopInitCounter + OpWhileLoopNext OpLoopReturn OpReturn ) diff --git a/pkg/runtime/vm.go b/pkg/runtime/vm.go index c1ca86e7..5556321f 100644 --- a/pkg/runtime/vm.go +++ b/pkg/runtime/vm.go @@ -375,17 +375,17 @@ loop: } else { return nil, err } - case OpLoopDestinationInit: - ds := NewDataSet(arg == 1) + case OpLoopInitOutput: + output := NewDataSet(arg == 1) - stack.Push(values.NewBoxedValue(ds)) + stack.Push(values.NewBoxedValue(output)) - case OpLoopSourceInit: + case OpForLoopInitInput: // start a new iteration // get the data source - src := stack.Pop() + input := stack.Pop() - switch src := src.(type) { + switch src := input.(type) { case core.Iterable: iterator, err := src.Iterate(ctx) @@ -398,7 +398,7 @@ loop: return nil, core.TypeError(src, types.Iterable) } - case OpLoopHasNext: + case OpForLoopHasNext: boxed := stack.Peek() iterator := boxed.Unwrap().(core.Iterator) hasNext, err := iterator.HasNext(ctx) @@ -409,7 +409,7 @@ loop: stack.Push(values.NewBoolean(hasNext)) - case OpLoopNext, OpLoopNextValue, OpLoopNextCounter: + case OpForLoopNext, OpForLoopNextValue, OpForLoopNextCounter: boxed := stack.Peek() iterator := boxed.Unwrap().(core.Iterator) @@ -421,15 +421,25 @@ loop: } switch op { - case OpLoopNextValue: + case OpForLoopNextValue: stack.Push(val) - case OpLoopNextCounter: + case OpForLoopNextCounter: stack.Push(key) default: stack.Push(key) stack.Push(val) } + case OpWhileLoopInitCounter: + stack.Push(values.ZeroInt) + + case OpWhileLoopNext: + counter := stack.Pop().(values.Int) + // increment the counter for the next iteration + stack.Push(counter + 1) + // put the current counter value + stack.Push(counter) + case OpJump: vm.ip += arg