diff --git a/pkg/compiler/allocator.go b/pkg/compiler/allocator.go index fbf43023..8ea1eaf8 100644 --- a/pkg/compiler/allocator.go +++ b/pkg/compiler/allocator.go @@ -45,7 +45,7 @@ const ( func NewRegisterAllocator() *RegisterAllocator { return &RegisterAllocator{ registers: make(map[runtime.Operand]*RegisterStatus), - nextRegister: runtime.ResultOperand + 1, // we start at 1 to avoid ResultOperand + nextRegister: runtime.NoopOperand + 1, // we start at 1 to avoid NoopOperand lifetimes: make(map[string]*RegisterLifetime), usageGraph: make(map[runtime.Operand]map[runtime.Operand]bool), } @@ -196,7 +196,7 @@ func (ra *RegisterAllocator) findContiguousRegisters(count int) (runtime.Operand // First, try to find a contiguous block in existing registers maxReg := ra.nextRegister - for start := runtime.ResultOperand + 1; start < maxReg; start++ { + for start := runtime.NoopOperand + 1; start < maxReg; start++ { if ra.isContiguousBlockFree(start, count) { return start, true } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index 8e3672e9..3cdd3253 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -6,6 +6,7 @@ import ( "github.com/MontFerret/ferret/pkg/parser" "github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/stdlib" + goruntime "runtime" ) type Compiler struct { @@ -38,14 +39,19 @@ func (c *Compiler) Compile(query string) (program *runtime.Program, err error) { defer func() { if r := recover(); r != nil { + buf := make([]byte, 1024) + n := goruntime.Stack(buf, false) + stackTrace := string(buf[:n]) + // find out exactly what the error was and set err + // Find out exactly what the error was and set err switch x := r.(type) { case string: - err = errors.New(x) + err = errors.New(x + "\n" + stackTrace) case error: - err = x + err = errors.New(x.Error() + "\n" + stackTrace) default: - err = errors.New("unknown panic") + err = errors.New("unknown panic\n" + stackTrace) } program = nil diff --git a/pkg/compiler/compiler_exec_test.go b/pkg/compiler/compiler_exec_test.go index d546c5a8..b8df44b5 100644 --- a/pkg/compiler/compiler_exec_test.go +++ b/pkg/compiler/compiler_exec_test.go @@ -3,6 +3,10 @@ package compiler_test import ( "context" "fmt" + "github.com/MontFerret/ferret/pkg/parser" + "regexp" + "strconv" + "strings" "testing" "github.com/MontFerret/ferret/pkg/compiler" @@ -779,17 +783,17 @@ func TestMember(t *testing.T) { }, { `LET o1 = { - first: { - second: { - ["third"]: { - fourth: { - fifth: { - bottom: true - } - } - } - } - } + first: { + second: { + ["third"]: { + fourth: { + fifth: { + bottom: true + } + } + } + } + } } LET o2 = { prop: "third" } @@ -802,15 +806,15 @@ func TestMember(t *testing.T) { { `LET o1 = { first: { - second: { - third: { - fourth: { - fifth: { - bottom: true - } - } - } - } + second: { + third: { + fourth: { + fifth: { + bottom: true + } + } + } + } } } @@ -842,274 +846,174 @@ func TestMember(t *testing.T) { nil, nil, }, + { + `RETURN {first: {second: "third"}}.first`, + map[string]any{ + "second": "third", + }, + nil, + }, + { + `RETURN KEEP_KEYS({first: {second: "third"}}.first, "second")`, + map[string]any{ + "second": "third", + }, + nil, + }, + { + ` + FOR v, k IN {f: {foo: "bar"}}.f + RETURN [k, v] + `, + []any{ + []any{"foo", "bar"}, + }, + nil, + }, + { + `RETURN FIRST([[1, 2]][0])`, + 1, + nil, + }, + { + `RETURN [[1, 2]][0]`, + []any{1, 2}, + ShouldEqualJSON, + }, + { + ` + FOR i IN [[1, 2]][0] + RETURN i + `, + []any{1, 2}, + ShouldEqualJSON, + }, + { + ` + LET arr = [{ name: "Bob" }] + + RETURN FIRST(arr).name + `, + "Bob", + nil, + }, + { + ` + LET arr = [{ name: { first: "Bob" } }] + + RETURN FIRST(arr)['name'].first + `, + "Bob", + nil, + }, + { + ` + LET obj = { foo: None } + + RETURN obj.foo?.bar + `, + nil, + nil, + }, }) +} - // Convey("ObjectDecl by literal passed to func call", func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // RETURN KEEP_KEYS({first: {second: "third"}}.first, "second") - // `) - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `{"second":"third"}`) - // }) - // - // Convey("ObjectDecl by literal as forSource", func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // FOR v, k IN {f: {foo: "bar"}}.f - // RETURN [k, v] - // `) - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `[["foo","bar"]]`) - // }) - // +func TestMemberReservedWords(t *testing.T) { + RunUseCases(t, []UseCase{}) - // - // Convey("ArrayDecl by literal passed to func call", func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // RETURN FIRST([[1, 2]][0]) - // `) - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `1`) - // }) - // - // Convey("ArrayDecl by literal as forSource", func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // FOR i IN [[1, 2]][0] - // RETURN i - // `) - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `[1,2]`) - // }) - // + Convey("Reserved words as property name", t, func() { + p := parser.New("RETURN TRUE") - // - // Convey("Prop after a func call", func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // LET arr = [{ name: "Bob" }] - // - // RETURN FIRST(arr).name - // `) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `"Bob"`) - // }) - // - // Convey("Computed prop after a func call", func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // LET arr = [{ name: { first: "Bob" } }] - // - // RETURN FIRST(arr)['name'].first - // `) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `"Bob"`) - // }) - // + r := regexp.MustCompile(`\w+`) - // - // 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"`) - // }) - // - // Convey("When function returns error", func() { - // c := compiler.New() - // c.RegisterFunction("ERROR", func(ctx context.visitor, args ...core.Second) (core.Second, 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`) - // }) - // }) - // }) - // - //Convey("Reserved words as property name", t, func() { - // p := parser.New("RETURN TRUE") - // - // r := regexp.MustCompile(`\w+`) - // - // for idx, l := range p.GetLiteralNames() { - // if r.MatchString(l) { - // query := strings.Builder{} - // query.WriteString("LET o = {\n") - // query.WriteString(l[1 : len(l)-1]) - // query.WriteString(":") - // query.WriteString(strconv.Itoa(idx)) - // query.WriteString(",\n") - // query.WriteString("}\n") - // query.WriteString("RETURN o") - // - // expected := strings.Builder{} - // expected.WriteString("{") - // expected.WriteString(strings.ReplaceAll(l, "'", "\"")) - // expected.WriteString(":") - // expected.WriteString(strconv.Itoa(idx)) - // expected.WriteString("}") - // - // c := compiler.New() - // prog, err := c.Compile(query.String()) - // - // So(err, ShouldBeNil) - // - // out, err := Exec(prog, true, runtime.WithFunctions(c.Functions().Unwrap())) - // - // So(err, ShouldBeNil) - // So(out, ShouldEqual, expected.String()) - // } - // } - //}) + for idx, l := range p.GetLiteralNames() { + if r.MatchString(l) { + query := strings.Builder{} + query.WriteString("LET o = {\n") + query.WriteString(l[1 : len(l)-1]) + query.WriteString(":") + query.WriteString(strconv.Itoa(idx)) + query.WriteString(",\n") + query.WriteString("}\n") + query.WriteString("RETURN o") + + expected := strings.Builder{} + expected.WriteString("{") + expected.WriteString(strings.ReplaceAll(l, "'", "\"")) + expected.WriteString(":") + expected.WriteString(strconv.Itoa(idx)) + expected.WriteString("}") + + c := compiler.New() + prog, err := c.Compile(query.String()) + + So(err, ShouldBeNil) + + out, err := Exec(prog, true, runtime.WithFunctions(c.Functions().Unwrap())) + + So(err, ShouldBeNil) + So(out, ShouldEqual, expected.String()) + } + } + }) +} + +func TestOptionalChaining(t *testing.T) { + RunUseCases(t, []UseCase{ + { + ` + LET obj = { foo: { bar: "bar" } } + + RETURN obj.foo?.bar + `, + "bar", + nil, + }, + { + ` + LET obj = { foo: None } + + RETURN obj.foo?.bar?.[0] + `, + nil, + nil, + }, + { + ` + LET obj = { foo: { bar: ["bar"] } } + + RETURN obj.foo?.bar?.[0] + `, + "bar", + nil, + }, + { + ` + RETURN FIRST([])?.foo + `, + nil, + nil, + }, + { + ` + RETURN FIRST([{ foo: "bar" }])?.foo + `, + "bar", + nil, + }, + }) + + RunUseCases(t, []UseCase{ + { + ` + RETURN ERROR()?.foo + `, + nil, + nil, + }, + }, runtime.WithFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) { + return nil, core.ErrNotImplemented + })) } func TestFor(t *testing.T) { @@ -1121,80 +1025,82 @@ func TestFor(t *testing.T) { // ShouldEqualJSON, //}, RunUseCases(t, []UseCase{ - //{ - // "FOR i IN 1..5 RETURN i", - // []any{1, 2, 3, 4, 5}, - // ShouldEqualJSON, - //}, + // { + // "FOR i IN 1..5 RETURN i", + // []any{1, 2, 3, 4, 5}, + // ShouldEqualJSON, + // }, + // { + // ` + //FOR i IN 1..5 + // LET x = i * 2 + // RETURN x + //`, + // []any{2, 4, 6, 8, 10}, + // ShouldEqualJSON, + // }, + // { + // ` + //FOR val, counter IN 1..5 + // LET x = val + // PRINT(counter) + // LET y = counter + // RETURN [x, y] + // `, + // []any{[]any{1, 0}, []any{2, 1}, []any{3, 2}, []any{4, 3}, []any{5, 4}}, + // ShouldEqualJSON, + // }, + // { + // `FOR i IN [] RETURN i + // `, + // []any{}, + // ShouldEqualJSON, + // }, + // { + // `FOR i IN [1, 2, 3] RETURN i + // `, + // []any{1, 2, 3}, + // ShouldEqualJSON, + // }, + // + // { + // `FOR i, k IN [1, 2, 3] RETURN k`, + // []any{0, 1, 2}, + // ShouldEqualJSON, + // }, + // { + // `FOR i IN ['foo', 'bar', 'qaz'] RETURN i`, + // []any{"foo", "bar", "qaz"}, + // ShouldEqualJSON, + // }, + // { + // `FOR i IN {a: 'bar', b: 'foo', c: 'qaz'} RETURN i`, + // []any{"foo", "bar", "qaz"}, + // ShouldHaveSameItems, + // }, + // { + // `FOR i, k IN {a: 'foo', b: 'bar', c: 'qaz'} RETURN k`, + // []any{"a", "b", "c"}, + // ShouldHaveSameItems, + // }, + // { + // `FOR i IN [{name: 'foo'}, {name: 'bar'}, {name: 'qaz'}] RETURN i.name`, + // []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 i IN 1..5 - LET x = i * 2 - RETURN x - `, - []any{2, 4, 6, 8, 10}, + `FOR prop IN ["a"] + FOR val IN [1, 2, 3] + RETURN {[prop]: val}`, + []any{map[string]any{"a": 1}, map[string]any{"a": 2}, map[string]any{"a": 3}}, ShouldEqualJSON, }, //{ - // `FOR val, counter IN 1..5 - // LET x = val - // PRINT(counter) - // LET y = counter - // RETURN [x, y] - //`, - // []any{[]any{1, 0}, []any{2, 1}, []any{3, 2}, []any{4, 3}, []any{5, 4}}, - // ShouldEqualJSON, - //}, - //{ - // `FOR i IN [] RETURN i - //`, - // []any{}, - // ShouldEqualJSON, - //}, - //{ - // `FOR i IN [1, 2, 3] RETURN i - //`, - // []any{1, 2, 3}, - // ShouldEqualJSON, - //}, - // - //{ - // `FOR i, k IN [1, 2, 3] RETURN k`, - // []any{0, 1, 2}, - // ShouldEqualJSON, - //}, - //{ - // `FOR i IN ['foo', 'bar', 'qaz'] RETURN i`, - // []any{"foo", "bar", "qaz"}, - // ShouldEqualJSON, - //}, - //{ - // `FOR i IN {a: 'bar', b: 'foo', c: 'qaz'} RETURN i`, - // []any{"foo", "bar", "qaz"}, - // ShouldHaveSameItems, - //}, - //{ - // `FOR i, k IN {a: 'foo', b: 'bar', c: 'qaz'} RETURN k`, - // []any{"a", "b", "c"}, - // ShouldHaveSameItems, - //}, - //{ - // `FOR i IN [{name: 'foo'}, {name: 'bar'}, {name: 'qaz'}] RETURN i.name`, - // []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] - // RETURN {[prop]: val}`, - // []any{map[string]any{"a": 1}, map[string]any{"a": 2}, map[string]any{"a": 3}}, - // ShouldEqualJSON, - //}, - //{ // `FOR val IN 1..3 // FOR prop IN ["a"] // RETURN {[prop]: val}`, diff --git a/pkg/compiler/emitter.go b/pkg/compiler/emitter.go index 20abcf22..708ce31e 100644 --- a/pkg/compiler/emitter.go +++ b/pkg/compiler/emitter.go @@ -35,6 +35,11 @@ func (e *Emitter) PatchJump(dest int) { e.instructions[dest].Operands[0] = runtime.Operand(len(e.instructions) - 1) } +// PatchJumpx patches a jump opcode with a new destination and position offset. +func (e *Emitter) PatchJumpx(dest int, offset int) { + e.instructions[dest].Operands[0] = runtime.Operand(len(e.instructions) - 1 + offset) +} + // Emit emits an opcode with no arguments. func (e *Emitter) Emit(op runtime.Opcode) { e.EmitABC(op, 0, 0, 0) diff --git a/pkg/compiler/loops.go b/pkg/compiler/loops.go index 8e21c5a7..0960faea 100644 --- a/pkg/compiler/loops.go +++ b/pkg/compiler/loops.go @@ -8,8 +8,8 @@ type ( Distinct bool Result runtime.Operand Iterator runtime.Operand - Allocated bool - Position int + Allocate bool + Jump int } LoopTable struct { @@ -25,14 +25,13 @@ func NewLoopTable(registers *RegisterAllocator) *LoopTable { } } -func (lt *LoopTable) EnterLoop(position int, passThrough, distinct bool) { +func (lt *LoopTable) EnterLoop(passThrough, distinct bool) *Loop { var allocate bool var state runtime.Operand // top loop if len(lt.loops) == 0 { allocate = true - state = lt.registers.Allocate(Result) } else if !passThrough { // nested with explicit RETURN expression prev := lt.loops[len(lt.loops)-1] @@ -42,13 +41,18 @@ func (lt *LoopTable) EnterLoop(position int, passThrough, distinct bool) { state = prev.Result } + if allocate { + state = lt.registers.Allocate(Result) + } + lt.loops = append(lt.loops, &Loop{ PassThrough: passThrough, Distinct: distinct, Result: state, - Allocated: allocate, - Position: position, + Allocate: allocate, }) + + return lt.loops[len(lt.loops)-1] } func (lt *LoopTable) Loop() *Loop { diff --git a/pkg/compiler/visitor.go b/pkg/compiler/visitor.go index 635925f5..9dcc176c 100644 --- a/pkg/compiler/visitor.go +++ b/pkg/compiler/visitor.go @@ -81,10 +81,12 @@ func (v *visitor) VisitBodyExpression(ctx *fql.BodyExpressionContext) interface{ if c := ctx.ForExpression(); c != nil { out, ok := c.Accept(v).(runtime.Operand) - if ok && out != runtime.ResultOperand { - v.emitter.EmitAB(runtime.OpMove, runtime.ResultOperand, out) + if ok && out != runtime.NoopOperand { + v.emitter.EmitAB(runtime.OpMove, runtime.NoopOperand, out) } + v.emitter.Emit(runtime.OpReturn) + return out } else if c := ctx.ReturnExpression(); c != nil { return c.Accept(v) @@ -103,7 +105,6 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} var passThrough bool var distinct bool var returnRuleCtx antlr.RuleContext - var loopJump int // identify whether it's WHILE or FOR loop isForInLoop := ctx.While() == nil returnCtx := ctx.ForExpressionReturn() @@ -116,11 +117,10 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} passThrough = true } - v.loops.EnterLoop(v.emitter.Size(), passThrough, distinct) - - dsReg := v.loops.Loop().Result + loop := v.loops.EnterLoop(passThrough, distinct) + dsReg := loop.Result - if !passThrough { + if loop.Allocate { v.emitter.EmitAb(runtime.OpLoopInit, dsReg, distinct) } @@ -131,7 +131,8 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} iterReg := v.registers.Allocate(Iter) v.emitter.EmitAB(runtime.OpForLoopCall, iterReg, src1) - loopJump = v.emitter.EmitJumpc(runtime.OpForLoopNext, jumpPlaceholder, iterReg) + // jumpPlaceholder is a placeholder for the exit jump position + loop.Jump = v.emitter.EmitJumpc(runtime.OpForLoopNext, jumpPlaceholder, iterReg) valVar := ctx.GetValueVariable().GetText() counterVarCtx := ctx.GetCounterVariable() @@ -190,40 +191,31 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} } } - // return - if returnRuleCtx != nil { + // RETURN + if !passThrough { c := returnRuleCtx.(*fql.ReturnExpressionContext) expReg := c.Expression().Accept(v).(runtime.Operand) - v.emitter.EmitAB(runtime.OpLoopReturn, dsReg, expReg) + v.emitter.EmitAB(runtime.OpLoopPush, dsReg, expReg) + } else if returnRuleCtx != nil { + returnRuleCtx.Accept(v) } - v.emitter.EmitJump(runtime.OpJump, loopJump) + v.emitter.EmitJump(runtime.OpJump, loop.Jump) // TODO: Do not allocate for pass-through loops dst := v.registers.Allocate(Temp) - if !passThrough { + if loop.Allocate { v.emitter.EmitAB(runtime.OpLoopFinalize, dst, dsReg) - } - - v.emitter.PatchJump(loopJump) - - if isForInLoop { - // pop the iterator - //v.emitPopAndClose() + v.emitter.PatchJump(loop.Jump) } else { - // pop the counter - //v.emitPop() + v.emitter.PatchJumpx(loop.Jump, 1) } v.loops.ExitLoop() v.symbols.ExitScope() - //if !passThrough { - // return dsReg - //} - return dst } @@ -323,58 +315,16 @@ func (v *visitor) VisitForExpressionStatement(ctx *fql.ForExpressionStatementCon } func (v *visitor) VisitFunctionCallExpression(ctx *fql.FunctionCallExpressionContext) interface{} { - call := ctx.FunctionCall().(*fql.FunctionCallContext) - - var name string - funcNS := call.Namespace() - - if funcNS != nil { - name += funcNS.GetText() - } - - name += call.FunctionName().GetText() - - var size int - var seq *RegisterSequence - - if list := call.ArgumentList(); list != nil { - // Get all array element expressions - exps := list.(*fql.ArgumentListContext).AllExpression() - size = len(exps) - - if size > 0 { - // Allocate seq for function arguments - seq = v.registers.AllocateSequence(size) - - // Evaluate each element into seq registers - for i, exp := range exps { - // Compile expression and move to seq register - srcReg := exp.Accept(v).(runtime.Operand) - - // TODO: Figure out how to remove OpMove and use registers returned from each expression - v.emitter.EmitAB(runtime.OpMove, seq.Registers[i], srcReg) - - // Free source register if temporary - if srcReg.IsRegister() { - //v.registers.Free(srcReg) - } - } - } - } - - nameAndDest := v.loadConstant(values.NewString(name)) - - if ctx.ErrorOperator() == nil { - v.emitter.EmitAs(runtime.OpCall, nameAndDest, seq) - } else { - v.emitter.EmitAs(runtime.OpCallSafe, nameAndDest, seq) - } + return v.visitFunctionCall(ctx.FunctionCall().(*fql.FunctionCallContext), ctx.ErrorOperator() != nil) +} - return nameAndDest +func (v *visitor) VisitFunctionCall(ctx *fql.FunctionCallContext) interface{} { + return v.visitFunctionCall(ctx, false) } func (v *visitor) VisitMemberExpression(ctx *fql.MemberExpressionContext) interface{} { mes := ctx.MemberExpressionSource().(*fql.MemberExpressionSourceContext) + segments := ctx.AllMemberExpressionPath() var mesOut interface{} @@ -387,12 +337,13 @@ func (v *visitor) VisitMemberExpression(ctx *fql.MemberExpressionContext) interf } else if c := mes.ArrayLiteral(); c != nil { mesOut = c.Accept(v) } else if c := mes.FunctionCall(); c != nil { - mesOut = c.Accept(v) + // FOO()?.bar + segment := segments[0].(*fql.MemberExpressionPathContext) + mesOut = v.visitFunctionCall(c.(*fql.FunctionCallContext), segment.ErrorOperator() != nil) } var dst runtime.Operand src1 := mesOut.(runtime.Operand) - segments := ctx.AllMemberExpressionPath() for _, segment := range segments { var out2 interface{} @@ -472,7 +423,7 @@ func (v *visitor) VisitVariableDeclaration(ctx *fql.VariableDeclarationContext) return dest } - return nil + return runtime.NoopOperand } func (v *visitor) VisitVariable(ctx *fql.VariableContext) interface{} { @@ -722,20 +673,14 @@ func (v *visitor) VisitReturnExpression(ctx *fql.ReturnExpressionContext) interf valReg := ctx.Expression().Accept(v).(runtime.Operand) if valReg.IsConstant() { - v.emitter.EmitAB(runtime.OpLoadGlobal, runtime.ResultOperand, valReg) + v.emitter.EmitAB(runtime.OpLoadGlobal, runtime.NoopOperand, valReg) } else { - v.emitter.EmitAB(runtime.OpMove, runtime.ResultOperand, valReg) + v.emitter.EmitAB(runtime.OpMove, runtime.NoopOperand, valReg) } v.emitter.Emit(runtime.OpReturn) - //if len(v.loops) == 0 { - // v.emitter.EmitABC(runtime.OpReturn) - //} else { - // v.emitter.EmitABC(runtime.OpLoopReturn, v.resolveLoopResultPosition()) - //} - - return runtime.ResultOperand + return runtime.NoopOperand } func (v *visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { @@ -976,6 +921,55 @@ func (v *visitor) VisitExpressionAtom(ctx *fql.ExpressionAtomContext) interface{ panic(core.Error(ErrUnexpectedToken, ctx.GetText())) } +func (v *visitor) visitFunctionCall(ctx *fql.FunctionCallContext, safeCall bool) interface{} { + var name string + funcNS := ctx.Namespace() + + if funcNS != nil { + name += funcNS.GetText() + } + + name += ctx.FunctionName().GetText() + + var size int + var seq *RegisterSequence + + if list := ctx.ArgumentList(); list != nil { + // Get all array element expressions + exps := list.(*fql.ArgumentListContext).AllExpression() + size = len(exps) + + if size > 0 { + // Allocate seq for function arguments + seq = v.registers.AllocateSequence(size) + + // Evaluate each element into seq registers + for i, exp := range exps { + // Compile expression and move to seq register + srcReg := exp.Accept(v).(runtime.Operand) + + // TODO: Figure out how to remove OpMove and use registers returned from each expression + v.emitter.EmitAB(runtime.OpMove, seq.Registers[i], srcReg) + + // Free source register if temporary + if srcReg.IsRegister() { + //v.registers.Free(srcReg) + } + } + } + } + + nameAndDest := v.loadConstant(values.NewString(name)) + + if !safeCall { + v.emitter.EmitAs(runtime.OpCall, nameAndDest, seq) + } else { + v.emitter.EmitAs(runtime.OpCallSafe, nameAndDest, seq) + } + + return nameAndDest +} + func (v *visitor) loadConstant(constant core.Value) runtime.Operand { reg := v.registers.Allocate(Temp) v.emitter.EmitAB(runtime.OpLoadConst, reg, v.symbols.AddConstant(constant)) diff --git a/pkg/runtime/env_options.go b/pkg/runtime/env_options.go index 6f8bc208..bf280dac 100644 --- a/pkg/runtime/env_options.go +++ b/pkg/runtime/env_options.go @@ -13,3 +13,13 @@ func WithFunctions(functions map[string]core.Function) EnvironmentOption { env.functions = functions } } + +func WithFunction(name string, function core.Function) EnvironmentOption { + return func(env *Environment) { + if env.functions == nil { + env.functions = make(map[string]core.Function) + } + + env.functions[name] = function + } +} diff --git a/pkg/runtime/frame.go b/pkg/runtime/frame.go index 4a096b8c..f69b8e96 100644 --- a/pkg/runtime/frame.go +++ b/pkg/runtime/frame.go @@ -13,7 +13,7 @@ type Frame struct { func newFrame(size, pc int, parent *Frame) *Frame { registers := make([]core.Value, size) - registers[ResultOperand] = values.None + registers[NoopOperand] = values.None return &Frame{ registers: registers, diff --git a/pkg/runtime/opcode.go b/pkg/runtime/opcode.go index 9fc769af..3e990ab6 100644 --- a/pkg/runtime/opcode.go +++ b/pkg/runtime/opcode.go @@ -52,7 +52,7 @@ const ( OpCallSafe OpLoopInit // Creates a loop result dataset - OpLoopReturn + OpLoopPush OpLoopFinalize OpForLoopCall // Creates an iterator for a loop diff --git a/pkg/runtime/operand.go b/pkg/runtime/operand.go index 8a11e291..4f63de60 100644 --- a/pkg/runtime/operand.go +++ b/pkg/runtime/operand.go @@ -4,7 +4,8 @@ import "fmt" type Operand int -const ResultOperand = Operand(0) +// NoopOperand is a reserved operand for no operation and final results. +const NoopOperand = Operand(0) func NewConstantOperand(idx int) Operand { return Operand(-idx - 1) diff --git a/pkg/runtime/vm.go b/pkg/runtime/vm.go index 1fb72286..6d7639d5 100644 --- a/pkg/runtime/vm.go +++ b/pkg/runtime/vm.go @@ -317,7 +317,7 @@ loop: // TODO: Remove boxed value!!! iter := reg[src1].(*values.Boxed).Unwrap().(core.Iterator) reg[dst] = iter.Key() - case OpLoopReturn: + case OpLoopPush: ds := reg[dst].(*DataSet) ds.Push(reg[src1]) case OpReturn: @@ -325,5 +325,5 @@ loop: } } - return vm.currentFrame.registers[ResultOperand], nil + return vm.currentFrame.registers[NoopOperand], nil }