Skip to content

Commit

Permalink
Added support of optional ignoring of errors to function calls (#652)
Browse files Browse the repository at this point in the history
* Added support of optional ignoring of errors to function calls

* Added support of handling of source failure to optional chaining

* Updated unit tests
  • Loading branch information
ziflex committed Sep 7, 2021
1 parent 0cb7623 commit 5f361e9
Show file tree
Hide file tree
Showing 16 changed files with 1,150 additions and 832 deletions.
15 changes: 15 additions & 0 deletions pkg/compiler/compiler_func_ns_test.go
Expand Up @@ -80,6 +80,21 @@ func TestFunctionNSCall(t *testing.T) {
So(err, ShouldNotBeNil)
})

Convey("T::FAIL()? should return NONE", t, func() {
c := compiler.New()

p, err := c.Compile(`
RETURN T::FAIL()?
`)

So(err, ShouldBeNil)

out, err := p.Run(context.Background())

So(err, ShouldBeNil)
So(string(out), ShouldEqual, `null`)
})

Convey("Should use keywords", t, func() {
c := compiler.New()

Expand Down
39 changes: 39 additions & 0 deletions pkg/compiler/compiler_func_test.go
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/pkg/errors"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
Expand Down Expand Up @@ -83,6 +84,44 @@ func TestFunctionCall(t *testing.T) {

So(string(out), ShouldEqual, `[2,4,6,8]`)
})

Convey("Should handle errors when ? is used", t, func() {
c := compiler.New()
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, errors.New("test error")
})

p, err := c.Compile(`
RETURN ERROR()?
`)

So(err, ShouldBeNil)

out, err := p.Run(context.Background())

So(err, ShouldBeNil)

So(string(out), ShouldEqual, `null`)
})

Convey("Should return NONE when error is handled", t, func() {
c := compiler.New()
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.NewString("booo"), errors.New("test error")
})

p, err := c.Compile(`
RETURN ERROR()?
`)

So(err, ShouldBeNil)

out, err := p.Run(context.Background())

So(err, ShouldBeNil)

So(string(out), ShouldEqual, `null`)
})
}

func BenchmarkFunctionCallArg1(b *testing.B) {
Expand Down
40 changes: 39 additions & 1 deletion pkg/compiler/compiler_logical_test.go
Expand Up @@ -2,8 +2,10 @@ package compiler_test

import (
"context"
"errors"
"github.com/MontFerret/ferret/pkg/compiler"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
Expand Down Expand Up @@ -71,7 +73,43 @@ func TestLogicalOperators(t *testing.T) {
So(string(out), ShouldEqual, `"foo"`)
})

Convey("NONE && true should return null", t, func() {
Convey("ERROR()? || 'boo' should return 'boo'", t, func() {
c := compiler.New()
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
return nil, errors.New("test")
})

p, err := c.Compile(`
RETURN ERROR()? || 'boo'
`)

So(err, ShouldBeNil)

out, err := p.Run(context.Background())

So(err, ShouldBeNil)
So(string(out), ShouldEqual, `"boo"`)
})

Convey("!ERROR()? && TRUE should return false", t, func() {
c := compiler.New()
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
return nil, errors.New("test")
})

p, err := c.Compile(`
RETURN !ERROR()? && TRUE
`)

So(err, ShouldBeNil)

out, err := p.Run(context.Background())

So(err, ShouldBeNil)
So(string(out), ShouldEqual, `true`)
})

Convey("NONE && true should return null", t, func() {
c := compiler.New()

p, err := c.Compile(`
Expand Down
20 changes: 20 additions & 0 deletions pkg/compiler/compiler_member_test.go
Expand Up @@ -3,6 +3,7 @@ package compiler_test
import (
"context"
"fmt"
"github.com/MontFerret/ferret/pkg/runtime/core"
"testing"

"github.com/MontFerret/ferret/pkg/compiler"
Expand Down Expand Up @@ -495,6 +496,25 @@ RETURN o1.first["second"][o2.prop].fourth["fifth"]["bottom"]

So(string(out), ShouldEqual, `"bar"`)
})

Convey("When function returns error", func() {
c := compiler.New()
c.RegisterFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) {
return nil, core.ErrNotImplemented
})

p, err := c.Compile(`
RETURN ERROR()?.foo
`)

So(err, ShouldBeNil)

out, err := p.Run(context.Background())

So(err, ShouldBeNil)

So(string(out), ShouldEqual, `null`)
})
})
})
}
Expand Down
15 changes: 12 additions & 3 deletions pkg/compiler/visitor.go
Expand Up @@ -928,8 +928,8 @@ func (v *visitor) doVisitMemberExpressionSource(ctx *fql.MemberExpressionSourceC
return v.doVisitParamContext(param.(*fql.ParamContext), scope)
}

if fnCall := ctx.FunctionCallExpression(); fnCall != nil {
return v.doVisitFunctionCallExpression(fnCall.(*fql.FunctionCallExpressionContext), scope)
if fnCall := ctx.FunctionCall(); fnCall != nil {
return v.doVisitFunctionCall(fnCall.(*fql.FunctionCallContext), false, scope)
}

if objectLiteral := ctx.ObjectLiteral(); objectLiteral != nil {
Expand Down Expand Up @@ -1134,7 +1134,7 @@ func (v *visitor) doVisitRangeOperator(ctx *fql.RangeOperatorContext, scope *sco
)
}

func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (core.Expression, error) {
func (v *visitor) doVisitFunctionCall(context *fql.FunctionCallContext, ignoreErrors bool, scope *scope) (core.Expression, error) {
args := make([]core.Expression, 0, 5)
argsCtx := context.Arguments()

Expand Down Expand Up @@ -1170,10 +1170,19 @@ func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpress
return expressions.NewFunctionCallExpression(
v.getSourceMap(context),
fun,
ignoreErrors,
args...,
)
}

func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (core.Expression, error) {
return v.doVisitFunctionCall(
context.FunctionCall().(*fql.FunctionCallContext),
context.QuestionMark() != nil,
scope,
)
}

func (v *visitor) doVisitParamContext(context *fql.ParamContext, s *scope) (core.Expression, error) {
name := context.Identifier().GetText()

Expand Down
5 changes: 4 additions & 1 deletion pkg/drivers/cdp/events/loop_test.go
Expand Up @@ -57,6 +57,7 @@ func NewBufferedTestEventStream(buffer int) *TestEventStream {
es := new(TestEventStream)
es.ready = make(chan struct{}, buffer)
es.message = make(chan interface{}, buffer)

return es
}

Expand Down Expand Up @@ -394,7 +395,7 @@ func TestLoop(t *testing.T) {
// Stop the loop
cancel()

time.Sleep(time.Duration(100) * time.Millisecond)
time.Sleep(time.Duration(500) * time.Millisecond)

onLoad.Emit(&page.LoadEventFiredReply{})

Expand All @@ -404,6 +405,8 @@ func TestLoop(t *testing.T) {
onLoad.Emit(&page.LoadEventFiredReply{})
}

time.Sleep(time.Duration(500) * time.Millisecond)

So(counter.Value(), ShouldEqual, eventsToFire)
})
}
Expand Down
14 changes: 9 additions & 5 deletions pkg/parser/antlr/FqlParser.g4
Expand Up @@ -264,18 +264,22 @@ memberExpression
memberExpressionSource
: variable
| param
| functionCallExpression
| arrayLiteral
| objectLiteral
| functionCall
;

memberExpressionPath
: QuestionMark? Dot propertyName
| (QuestionMark Dot)? computedPropertyName
functionCall
: namespace functionIdentifier arguments
;

functionCallExpression
: namespace functionIdentifier arguments
: functionCall QuestionMark?
;

memberExpressionPath
: QuestionMark? Dot propertyName
| (QuestionMark Dot)? computedPropertyName
;

functionIdentifier
Expand Down
5 changes: 3 additions & 2 deletions pkg/parser/fql/FqlParser.interp

Large diffs are not rendered by default.

0 comments on commit 5f361e9

Please sign in to comment.