Skip to content

Commit

Permalink
Refactored WHILE loop
Browse files Browse the repository at this point in the history
  • Loading branch information
ziflex committed Mar 28, 2024
1 parent 7e4de7a commit 70649e1
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 182 deletions.
5 changes: 5 additions & 0 deletions pkg/compiler/compiler_for_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
264 changes: 109 additions & 155 deletions pkg/compiler/compiler_let_test.go
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
31 changes: 20 additions & 11 deletions pkg/compiler/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()))
}
Expand All @@ -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)

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

Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 70649e1

Please sign in to comment.