Skip to content

Commit

Permalink
Added support of optional chaining (#634)
Browse files Browse the repository at this point in the history
  • Loading branch information
ziflex committed Jul 16, 2021
1 parent 742bdae commit 1d0617e
Show file tree
Hide file tree
Showing 8 changed files with 458 additions and 287 deletions.
112 changes: 112 additions & 0 deletions pkg/compiler/compiler_member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,118 @@ RETURN o1.first["second"][o2.prop].fourth["fifth"]["bottom"]
So(string(out), ShouldEqual, "1")
})
})

Convey("Optional chaining", t, func() {
Convey("Object", func() {
Convey("When value does not exist", func() {
c := compiler.New()

p, err := c.Compile(`
LET obj = { foo: None }
RETURN obj.foo?.bar
`)

So(err, ShouldBeNil)

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

So(err, ShouldBeNil)

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

Convey("When value does exists", func() {
c := compiler.New()

p, err := c.Compile(`
LET obj = { foo: { bar: "bar" } }
RETURN obj.foo?.bar
`)

So(err, ShouldBeNil)

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

So(err, ShouldBeNil)

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

Convey("Array", func() {
Convey("When value does not exist", func() {
c := compiler.New()

p, err := c.Compile(`
LET obj = { foo: None }
RETURN obj.foo?.bar?.[0]
`)

So(err, ShouldBeNil)

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

So(err, ShouldBeNil)

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

Convey("When value does exists", func() {
c := compiler.New()

p, err := c.Compile(`
LET obj = { foo: { bar: ["bar"] } }
RETURN obj.foo?.bar?.[0]
`)

So(err, ShouldBeNil)

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

So(err, ShouldBeNil)

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

Convey("Function", func() {
Convey("When value does not exist", func() {
c := compiler.New()

p, err := c.Compile(`
RETURN FIRST([])?.foo
`)

So(err, ShouldBeNil)

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

So(err, ShouldBeNil)

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

Convey("When value does exists", func() {
c := compiler.New()

p, err := c.Compile(`
RETURN FIRST([{ foo: "bar" }])?.foo
`)

So(err, ShouldBeNil)

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

So(err, ShouldBeNil)

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

func BenchmarkMemberArray(b *testing.B) {
Expand Down
11 changes: 9 additions & 2 deletions pkg/compiler/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,13 +876,14 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
}

children := ctx.AllMemberExpressionPath()
path := make([]core.Expression, 0, len(children))
path := make([]*expressions.MemberPathSegment, 0, len(children))

for _, memberPath := range children {
var exp core.Expression
var err error

memberPath := memberPath.(*fql.MemberExpressionPathContext)
optional := memberPath.QuestionMark() != nil

if prop := memberPath.PropertyName(); prop != nil {
exp, err = v.doVisitPropertyNameContext(prop.(*fql.PropertyNameContext), scope)
Expand All @@ -896,7 +897,13 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
return nil, err
}

path = append(path, exp)
segment, err := expressions.NewMemberPathSegment(exp, optional)

if err != nil {
return nil, err
}

path = append(path, segment)
}

return expressions.NewMemberExpression(
Expand Down
4 changes: 2 additions & 2 deletions pkg/parser/antlr/FqlParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ memberExpressionSource
;

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

functionCallExpression
Expand Down
2 changes: 1 addition & 1 deletion pkg/parser/fql/FqlParser.interp

Large diffs are not rendered by default.

585 changes: 308 additions & 277 deletions pkg/parser/fql/fql_parser.go

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions pkg/runtime/expressions/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (
type MemberExpression struct {
src core.SourceMap
source core.Expression
path []core.Expression
path []*MemberPathSegment
}

func NewMemberExpression(src core.SourceMap, source core.Expression, path []core.Expression) (*MemberExpression, error) {
func NewMemberExpression(src core.SourceMap, source core.Expression, path []*MemberPathSegment) (*MemberExpression, error) {
if source == nil {
return nil, core.Error(core.ErrMissedArgument, "source")
}
Expand All @@ -38,8 +38,8 @@ func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Va
out := val
path := make([]core.Value, 1)

for _, exp := range e.path {
segment, err := exp.Exec(ctx, scope)
for _, seg := range e.path {
segment, err := seg.exp.Exec(ctx, scope)

if err != nil {
return values.None, err
Expand All @@ -49,7 +49,11 @@ func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Va
c, err := values.GetIn(ctx, out, path)

if err != nil {
return values.None, core.SourceError(e.src, err)
if !seg.optional {
return values.None, core.SourceError(e.src, err)
}

return values.None, nil
}

out = c
Expand Down
16 changes: 16 additions & 0 deletions pkg/runtime/expressions/member_path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package expressions

import "github.com/MontFerret/ferret/pkg/runtime/core"

type MemberPathSegment struct {
exp core.Expression
optional bool
}

func NewMemberPathSegment(source core.Expression, optional bool) (*MemberPathSegment, error) {
if source == nil {
return nil, core.Error(core.ErrMissedArgument, "source")
}

return &MemberPathSegment{source, optional}, nil
}
1 change: 1 addition & 0 deletions pkg/runtime/values/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
// GetIn call from.GetIn method, otherwise return error.
func GetIn(ctx context.Context, from core.Value, byPath []core.Value) (core.Value, error) {
getter, ok := from.(core.Getter)

if !ok {
return None, core.TypeError(
from.Type(),
Expand Down

0 comments on commit 1d0617e

Please sign in to comment.