Skip to content

Commit

Permalink
Implemented FILTER
Browse files Browse the repository at this point in the history
  • Loading branch information
ziflex committed Apr 1, 2024
1 parent 70268ba commit 3a0d82b
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 38 deletions.
55 changes: 55 additions & 0 deletions pkg/compiler/compiler_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package compiler_test

import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)

func TestForFilter(t *testing.T) {
RunUseCases(t, []UseCase{
{
`
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
FILTER i > 2
RETURN i
`,
[]any{3, 4, 3},
ShouldEqualJSON,
},
{
`
FOR i IN [ 1, 2, 3, 4, 1, 3 ]
FILTER i > 1 AND i < 4
RETURN i
`,
[]any{2, 3, 3},
ShouldEqualJSON,
},
{
`
LET users = [
{
age: 31,
gender: "m",
name: "Josh"
},
{
age: 29,
gender: "f",
name: "Mary"
},
{
age: 36,
gender: "m",
name: "Peter"
}
]
FOR u IN users
FILTER u.name =~ "r"
RETURN u
`,
[]any{map[string]any{"age": 29, "gender": "f", "name": "Mary"}, map[string]any{"age": 36, "gender": "m", "name": "Peter"}},
ShouldEqualJSON,
},
})
}
98 changes: 78 additions & 20 deletions pkg/compiler/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ type (
}

loopScope struct {
lookBack int
passThrough bool
resultOffset int
passThrough bool
position int
}

visitor struct {
Expand Down Expand Up @@ -136,7 +137,7 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{}
passThrough = true
}

v.beginLoop(passThrough, distinct)
v.beginLoopScope(passThrough, distinct)

if isForInLoop {
// Loop data source to iterate over
Expand Down Expand Up @@ -218,6 +219,8 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{}
v.defineVariable(index)
}

v.patchLoopScope(loopJump)

// body
if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 {
for _, b := range body {
Expand All @@ -242,7 +245,7 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{}
v.emitPop()
}

v.endLoop()
v.endLoopScope()

return nil
}
Expand All @@ -268,13 +271,56 @@ func (v *visitor) VisitForExpressionSource(ctx *fql.ForExpressionSourceContext)
}

func (v *visitor) VisitForExpressionBody(ctx *fql.ForExpressionBodyContext) interface{} {
if c := ctx.ForExpressionClause(); c != nil {
c.Accept(v)
}

if c := ctx.ForExpressionStatement(); c != nil {
c.Accept(v)
}

return nil
}

func (v *visitor) VisitForExpressionClause(ctx *fql.ForExpressionClauseContext) interface{} {
if c := ctx.LimitClause(); c != nil {
// TODO: Implement
c.Accept(v)
}

if c := ctx.FilterClause(); c != nil {
c.Accept(v)
}

if c := ctx.SortClause(); c != nil {
// TODO: Implement
c.Accept(v)
}

if c := ctx.CollectClause(); c != nil {
// TODO: Implement
c.Accept(v)
}

return nil
}

func (v *visitor) VisitFilterClause(ctx *fql.FilterClauseContext) interface{} {
ctx.Expression().Accept(v)
fwd := v.emitJump(runtime.OpJumpIfTrue)
// Pop on false
v.emitPop()
// And jump back to the beginning of the loop
bwd := v.emitJump(runtime.OpJumpBackward)
v.patchJumpWith(bwd, len(v.bytecode)-v.resolveLoopPosition())

// Otherwise, pop on true
v.patchJump(fwd)
v.emitPop()

return nil
}

func (v *visitor) VisitForExpressionStatement(ctx *fql.ForExpressionStatementContext) interface{} {
if c := ctx.VariableDeclaration(); c != nil {
c.Accept(v)
Expand Down Expand Up @@ -316,37 +362,37 @@ func (v *visitor) VisitFunctionCallExpression(ctx *fql.FunctionCallExpressionCon
if isNonOptional {
v.emit(runtime.OpCall, 0)
} else {
v.emit(runtime.OpCallOptional, 0)
v.emit(runtime.OpCallSafe, 0)
}
case 1:
if isNonOptional {
v.emit(runtime.OpCall1, 1)
} else {
v.emit(runtime.OpCall1Optional, 1)
v.emit(runtime.OpCall1Safe, 1)
}
case 2:
if isNonOptional {
v.emit(runtime.OpCall2, 2)
} else {
v.emit(runtime.OpCall2Optional, 2)
v.emit(runtime.OpCall2Safe, 2)
}
case 3:
if isNonOptional {
v.emit(runtime.OpCall3, 3)
} else {
v.emit(runtime.OpCall3Optional, 3)
v.emit(runtime.OpCall3Safe, 3)
}
case 4:
if isNonOptional {
v.emit(runtime.OpCall4, 4)
} else {
v.emit(runtime.OpCall4Optional, 4)
v.emit(runtime.OpCall4Safe, 4)
}
default:
if isNonOptional {
v.emit(runtime.OpCallN, size)
} else {
v.emit(runtime.OpCallNOptional, size)
v.emit(runtime.OpCallNSafe, size)
}
}

Expand Down Expand Up @@ -628,7 +674,7 @@ func (v *visitor) VisitReturnExpression(ctx *fql.ReturnExpressionContext) interf
if len(v.loops) == 0 {
v.emit(runtime.OpReturn)
} else {
v.emit(runtime.OpLoopReturn, v.resolveLoopResult())
v.emit(runtime.OpLoopReturn, v.resolveLoopResultOffset())
}

return nil
Expand Down Expand Up @@ -808,7 +854,7 @@ func (v *visitor) endScope() {
}
}

func (v *visitor) beginLoop(passThrough, distinct bool) {
func (v *visitor) beginLoopScope(passThrough, distinct bool) {
var allocate bool

// top loop
Expand All @@ -824,7 +870,7 @@ func (v *visitor) beginLoop(passThrough, distinct bool) {

// we know that during execution of RETURN expression, the top item in the stack is Iterator
// and the allocated array is below it
// thus, the default lookBack is 2 (len - 1 - 1)
// thus, the default resultOffset is 2 (len - 1 - 1)
offset := 2

if allocate {
Expand All @@ -840,16 +886,24 @@ func (v *visitor) beginLoop(passThrough, distinct bool) {
}

v.loops = append(v.loops, &loopScope{
passThrough: passThrough,
lookBack: offset,
passThrough: passThrough,
resultOffset: offset,
})
}

func (v *visitor) resolveLoopResult() int {
return v.loops[len(v.loops)-1].lookBack
func (v *visitor) patchLoopScope(jump int) {
v.loops[len(v.loops)-1].position = jump
}

func (v *visitor) resolveLoopResultOffset() int {
return v.loops[len(v.loops)-1].resultOffset
}

func (v *visitor) resolveLoopPosition() int {
return v.loops[len(v.loops)-1].position
}

func (v *visitor) endLoop() {
func (v *visitor) endLoopScope() {
v.loops = v.loops[:len(v.loops)-1]

var unwrap bool
Expand Down Expand Up @@ -959,19 +1013,23 @@ func (v *visitor) emitLoop(loopStart int) {
v.arguments[pos-1] = jump
}

// emitJump emits an opcode with a jump lookBack argument.
// emitJump emits an opcode with a jump resultOffset argument.
func (v *visitor) emitJump(op runtime.Opcode) int {
v.emit(op, jumpPlaceholder)

return len(v.bytecode)
}

// patchJump patches a jump lookBack argument.
// patchJump patches a jump resultOffset argument.
func (v *visitor) patchJump(offset int) {
jump := len(v.bytecode) - offset
v.arguments[offset-1] = jump
}

func (v *visitor) patchJumpWith(offset, jump int) {
v.arguments[offset-1] = jump
}

func (v *visitor) emitPop() {
v.emit(runtime.OpPop)
}
Expand Down
12 changes: 6 additions & 6 deletions pkg/runtime/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ const (
OpRegexpPositive
OpRegexpNegative
OpCall
OpCallOptional
OpCallSafe
OpCall1
OpCall1Optional
OpCall1Safe
OpCall2
OpCall2Optional
OpCall2Safe
OpCall3
OpCall3Optional
OpCall3Safe
OpCall4
OpCall4Optional
OpCall4Safe
OpCallN
OpCallNOptional
OpCallNSafe
OpPush
OpPop
OpPopClose
Expand Down
24 changes: 12 additions & 12 deletions pkg/runtime/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,46 +277,46 @@ loop:
reg := program.Constants[arg].(*values.Regexp)
stack.Push(!reg.Match(stack.Pop()))

case OpCall, OpCallOptional:
case OpCall, OpCallSafe:
fnName := stack.Pop().String()
res, err := vm.env.GetFunction(fnName)(ctx)

if err == nil {
stack.Push(res)
} else if op == OpCallOptional || tryCatch(vm.ip) {
} else if op == OpCallSafe || tryCatch(vm.ip) {
stack.Push(values.None)
} else {
return nil, err
}

case OpCall1, OpCall1Optional:
case OpCall1, OpCall1Safe:
arg := stack.Pop()
fnName := stack.Pop().String()
res, err := vm.env.GetFunction(fnName)(ctx, arg)

if err == nil {
stack.Push(res)
} else if op == OpCall1Optional || tryCatch(vm.ip) {
} else if op == OpCall1Safe || tryCatch(vm.ip) {
stack.Push(values.None)
} else {
return nil, err
}

case OpCall2, OpCall2Optional:
case OpCall2, OpCall2Safe:
arg2 := stack.Pop()
arg1 := stack.Pop()
fnName := stack.Pop().String()
res, err := vm.env.GetFunction(fnName)(ctx, arg1, arg2)

if err == nil {
stack.Push(res)
} else if op == OpCall2Optional || tryCatch(vm.ip) {
} else if op == OpCall2Safe || tryCatch(vm.ip) {
stack.Push(values.None)
} else {
return nil, err
}

case OpCall3, OpCall3Optional:
case OpCall3, OpCall3Safe:
arg3 := stack.Pop()
arg2 := stack.Pop()
arg1 := stack.Pop()
Expand All @@ -325,13 +325,13 @@ loop:

if err == nil {
stack.Push(res)
} else if op == OpCall3Optional || tryCatch(vm.ip) {
} else if op == OpCall3Safe || tryCatch(vm.ip) {
stack.Push(values.None)
} else {
return nil, err
}

case OpCall4, OpCall4Optional:
case OpCall4, OpCall4Safe:
arg4 := stack.Pop()
arg3 := stack.Pop()
arg2 := stack.Pop()
Expand All @@ -341,12 +341,12 @@ loop:

if err == nil {
stack.Push(res)
} else if op == OpCall4Optional || tryCatch(vm.ip) {
} else if op == OpCall4Safe || tryCatch(vm.ip) {
stack.Push(values.None)
} else {
return nil, err
}
case OpCallN, OpCallNOptional:
case OpCallN, OpCallNSafe:
// pop arguments from the stack
// and push them to the arguments array
// in reverse order because stack is LIFO and arguments array is FIFO
Expand All @@ -365,7 +365,7 @@ loop:

if err == nil {
stack.Push(res)
} else if op == OpCallNOptional || tryCatch(vm.ip) {
} else if op == OpCallNSafe || tryCatch(vm.ip) {
stack.Push(values.None)
} else {
return nil, err
Expand Down

0 comments on commit 3a0d82b

Please sign in to comment.