From 9ceb5961bd6790e991fa4351c8ae07dbf62391ee Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Tue, 5 Nov 2024 15:45:17 -0500 Subject: [PATCH] Fixes fixes fixes --- pkg/compiler/allocator.go | 154 +--------- pkg/compiler/compiler_exec_test.go | 443 ++++++++++++----------------- pkg/compiler/emitter.go | 29 +- pkg/compiler/visitor.go | 79 ++--- pkg/runtime/opcode.go | 10 +- pkg/runtime/values/noop_iter.go | 26 ++ pkg/runtime/vm.go | 25 +- 7 files changed, 322 insertions(+), 444 deletions(-) create mode 100644 pkg/runtime/values/noop_iter.go diff --git a/pkg/compiler/allocator.go b/pkg/compiler/allocator.go index 8ea1eaf8..17fd4f3a 100644 --- a/pkg/compiler/allocator.go +++ b/pkg/compiler/allocator.go @@ -9,10 +9,7 @@ type ( // RegisterStatus tracks register usage RegisterStatus struct { IsAllocated bool - LastUse int // Instruction number of last use - NextUse int // Instruction number of next use - Type RegisterType // Type of variable stored - Lifetime *RegisterLifetime // Lifetime information + Type RegisterType // Type of variable stored } RegisterLifetime struct { @@ -22,7 +19,6 @@ type ( RegisterSequence struct { Registers []runtime.Operand - Lifetime *RegisterLifetime } // RegisterAllocator manages register allocation @@ -30,24 +26,20 @@ type ( registers map[runtime.Operand]*RegisterStatus nextRegister runtime.Operand currentInstr int - lifetimes map[string]*RegisterLifetime - usageGraph map[runtime.Operand]map[runtime.Operand]bool } ) const ( Temp RegisterType = iota // Short-lived intermediate results Var // Local variables - Iter // FOR loop iterators - Result // Final result variables + State // FOR loop state + Result // FOR loop result ) func NewRegisterAllocator() *RegisterAllocator { return &RegisterAllocator{ registers: make(map[runtime.Operand]*RegisterStatus), 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), } } @@ -67,10 +59,7 @@ func (ra *RegisterAllocator) Allocate(regType RegisterType) runtime.Operand { // Initialize register status ra.registers[newReg] = &RegisterStatus{ IsAllocated: true, - LastUse: ra.currentInstr, - NextUse: -1, Type: regType, - Lifetime: &RegisterLifetime{Start: ra.currentInstr}, } return newReg @@ -78,17 +67,17 @@ func (ra *RegisterAllocator) Allocate(regType RegisterType) runtime.Operand { // Free marks a register as available func (ra *RegisterAllocator) Free(reg runtime.Operand) { - if status, exists := ra.registers[reg]; exists { - status.IsAllocated = false - status.Lifetime.End = ra.currentInstr + //if status, exists := ra.registers[reg]; exists { + //status.IsAllocated = false + //status.Lifetime.End = ra.currentInstr - // Clean up interference graph - delete(ra.usageGraph, reg) - - for _, edges := range ra.usageGraph { - delete(edges, reg) - } - } + //// Clean up interference graph + //delete(ra.usageGraph, reg) + // + //for _, edges := range ra.usageGraph { + // delete(edges, reg) + //} + //} } // findFreeRegister looks for an unused register @@ -100,58 +89,13 @@ func (ra *RegisterAllocator) findFreeRegister() (runtime.Operand, bool) { } } - // TODO: Implement register reuse - // If no free registers, try to find one that's no longer needed - //var candidate runtime.Operand - //var found bool - //maxLastUse := -1 - // - //for reg, status := range ra.registers { - // if status.NextUse == -1 && status.LastUse > maxLastUse { - // maxLastUse = status.LastUse - // candidate = reg - // found = true - // } - //} - // - //if found { - // // Free the candidate register - // ra.Free(candidate) - // - // return candidate, true - //} - return 0, false } -// UpdateUse updates the usage information for a register -func (ra *RegisterAllocator) UpdateUse(reg runtime.Operand) { - status := ra.registers[reg] - - if status == nil { - return - } - - status.LastUse = ra.currentInstr - - // Update interference graph for simultaneously live registers - for otherReg, otherStatus := range ra.registers { - if otherReg != reg && otherStatus.IsAllocated && - ra.registersInterfere(reg, otherReg) { - ra.addInterference(reg, otherReg) - } - } - - ra.currentInstr++ -} - // AllocateSequence allocates a sequence of registers for function arguments or similar uses func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence { sequence := &RegisterSequence{ Registers: make([]runtime.Operand, count), - Lifetime: &RegisterLifetime{ - Start: ra.currentInstr, - }, } // First pass: try to find contiguous free registers @@ -166,12 +110,7 @@ func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence { // Initialize or update register status ra.registers[reg] = &RegisterStatus{ IsAllocated: true, - LastUse: ra.currentInstr, - NextUse: -1, Type: Temp, - Lifetime: &RegisterLifetime{ - Start: ra.currentInstr, - }, } } } else { @@ -182,9 +121,6 @@ func (ra *RegisterAllocator) AllocateSequence(count int) *RegisterSequence { } } - // Update interference graph for the sequence - ra.updateSequenceInterference(sequence) - return sequence } @@ -221,73 +157,9 @@ func (ra *RegisterAllocator) isContiguousBlockFree(start runtime.Operand, count return true } -// updateSequenceInterference updates interference information for sequence registers -func (ra *RegisterAllocator) updateSequenceInterference(seq *RegisterSequence) { - // Add interference between sequence registers - for i := 0; i < len(seq.Registers); i++ { - for j := i + 1; j < len(seq.Registers); j++ { - ra.addInterference(seq.Registers[i], seq.Registers[j]) - } - } - - // Add interference with other live registers - for _, seqReg := range seq.Registers { - for otherReg, otherStatus := range ra.registers { - if otherStatus.IsAllocated { - found := false - for _, r := range seq.Registers { - if r == otherReg { - found = true - break - } - } - if !found { - ra.addInterference(seqReg, otherReg) - } - } - } - } -} - // FreeSequence frees all registers in a sequence func (ra *RegisterAllocator) FreeSequence(seq *RegisterSequence) { - seq.Lifetime.End = ra.currentInstr - for _, reg := range seq.Registers { ra.Free(reg) } } - -// UpdateSequenceUse updates usage information for all registers in a sequence -func (ra *RegisterAllocator) UpdateSequenceUse(seq *RegisterSequence) { - for _, reg := range seq.Registers { - ra.UpdateUse(reg) - } -} - -// registersInterfere checks if two registers have overlapping lifetimes -func (ra *RegisterAllocator) registersInterfere(reg1, reg2 runtime.Operand) bool { - status1 := ra.registers[reg1] - status2 := ra.registers[reg2] - - if status1 == nil || status2 == nil { - return false - } - - // Registers interfere if their lifetimes overlap - return status1.Lifetime.Start <= status2.Lifetime.End && - status2.Lifetime.Start <= status1.Lifetime.End -} - -// addInterference records that two registers interfere -func (ra *RegisterAllocator) addInterference(reg1, reg2 runtime.Operand) { - if ra.usageGraph[reg1] == nil { - ra.usageGraph[reg1] = make(map[runtime.Operand]bool) - } - if ra.usageGraph[reg2] == nil { - ra.usageGraph[reg2] = make(map[runtime.Operand]bool) - } - - ra.usageGraph[reg1][reg2] = true - ra.usageGraph[reg2][reg1] = true -} diff --git a/pkg/compiler/compiler_exec_test.go b/pkg/compiler/compiler_exec_test.go index 65ba3f8c..6d655818 100644 --- a/pkg/compiler/compiler_exec_test.go +++ b/pkg/compiler/compiler_exec_test.go @@ -124,43 +124,33 @@ func TestVariables(t *testing.T) { "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 _ = (FOR i IN 1..100 RETURN NONE) - // RETURN TRUE`, - // true, - // ShouldEqualJSON, - //}, + { + "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 _ = (FOR i IN 1..100 RETURN NONE) + RETURN TRUE`, + true, + ShouldEqualJSON, + }, + { + ` + LET src = NONE + LET i = (FOR i IN src RETURN i)? + RETURN i + `, + []any{}, + nil, + }, }) - // - //Convey("Should compile LET src = NONE LET i = (FOR i IN NONE RETURN i)? RETURN i == NONE", t, func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // LET src = NONE - // LET i = (FOR i IN src RETURN i)? - // RETURN i == NONE - // `) - // - // So(err, ShouldBeNil) - // So(p, ShouldHaveSameTypeAs, &runtime.Program{}) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // So(string(out), ShouldEqual, "true") - //}) - // //Convey("Should compile LET i = (FOR i WHILE COUNTER() < 5 RETURN i) RETURN i", t, func() { // c := compiler.New() // counter := -1 @@ -207,16 +197,16 @@ func TestVariables(t *testing.T) { // So(string(out), ShouldEqual, "true") //}) - //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() @@ -228,18 +218,18 @@ func TestVariables(t *testing.T) { So(err, ShouldNotBeNil) }) - //Convey("Should not compile if a variable is not unique", t, func() { - // c := compiler.New() - // - // _, err := c.Compile(` - // LET foo = "bar" - // LET foo = "baz" - // - // RETURN foo - // `) - // - // So(err, ShouldNotBeNil) - //}) + Convey("Should not compile if a variable is not unique", t, func() { + c := compiler.New() + + _, err := c.Compile(` + LET foo = "bar" + LET foo = "baz" + + RETURN foo + `) + + So(err, ShouldNotBeNil) + }) //SkipConvey("Should use value returned from WAITFOR EVENT", t, func() { // out, err := newCompilerWithObservable().MustCompile(` @@ -281,17 +271,17 @@ func TestVariables(t *testing.T) { //}) // - //Convey("Should not allow to use ignorable variable name", t, func() { - // c := compiler.New() - // - // _, err := c.Compile(` - // LET _ = (FOR i IN 1..100 RETURN NONE) - // - // RETURN _ - // `) - // - // So(err, ShouldNotBeNil) - //}) + Convey("Should not allow to use ignorable variable name", t, func() { + c := compiler.New() + + _, err := c.Compile(` + LET _ = (FOR i IN 1..100 RETURN NONE) + + RETURN _ + `) + + So(err, ShouldNotBeNil) + }) } func TestMathOperators(t *testing.T) { @@ -382,64 +372,30 @@ func TestLogicalOperators(t *testing.T) { {"RETURN 1 || 7", 1, nil}, {"RETURN 0 || 7", 7, nil}, {"RETURN NONE || 'foo'", "foo", nil}, - }) - - // - //Convey("ERROR()? || 'boo' should return 'boo'", t, func() { - // c := compiler.New() - // c.RegisterFunction("ERROR", func(ctx context.visitor, args ...core.Second) (core.Second, 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.visitor, args ...core.Second) (core.Second, 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("NOT u.valid should return true", t, func() { - // c := compiler.New() - // - // p, err := c.Compile(` - // LET u = { valid: false } - // - // RETURN NOT u.valid - // `) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // So(string(out), ShouldEqual, `true`) - //}) + { + ` + RETURN ERROR()? || 'boo' + `, + "boo", + nil, + }, + { + ` + RETURN !ERROR()? && TRUE + `, + true, + nil, + }, + { + ` LET u = { valid: false } + + RETURN NOT u.valid`, + true, + nil, + }, + }, runtime.WithFunction("ERROR", func(ctx context.Context, args ...core.Value) (core.Value, error) { + return values.None, fmt.Errorf("test") + })) } func TestTernaryOperator(t *testing.T) { @@ -448,103 +404,75 @@ func TestTernaryOperator(t *testing.T) { {"RETURN 1 > 2 ? 3 : 4", 4, nil}, {"RETURN 2 ? : 4", 2, nil}, {` - LET foo = TRUE - RETURN foo ? TRUE : FALSE - `, true, nil}, + LET foo = TRUE + RETURN foo ? TRUE : FALSE + `, true, nil}, {` - LET foo = FALSE - RETURN foo ? TRUE : FALSE - `, false, nil}, + LET foo = FALSE + RETURN foo ? TRUE : FALSE + `, false, nil}, + { + ` + FOR i IN [1, 2, 3, 4, 5, 6] + RETURN i < 3 ? i * 3 : i * 2 + `, + []int{3, 6, 6, 8, 10, 12}, + ShouldEqualJSON, + }, + { + ` + FOR i IN [1, 2, 3, 4, 5, 6] + RETURN i < 3 ? : i * 2 + `, + []any{true, true, 6, 8, 10, 12}, + ShouldEqualJSON, + }, + { + ` + FOR i IN [NONE, 2, 3, 4, 5, 6] + RETURN i ? : i + `, + []any{nil, 2, 3, 4, 5, 6}, + ShouldEqualJSON, + }, + { + `RETURN 0 && true ? "1" : "some"`, + "some", + nil, + }, + { + `RETURN length([]) > 0 && true ? "1" : "some"`, + "some", + nil, + }, }) - //Convey("Should compile ternary operator", t, func() { - // c := compiler.New() - // p, err := c.Compile(` - // FOR i IN [1, 2, 3, 4, 5, 6] - // RETURN i < 3 ? i * 3 : i * 2 - // `) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `[3,6,6,8,10,12]`) - //}) - // - //Convey("Should compile ternary operator with shortcut", t, func() { - // c := compiler.New() - // p, err := c.Compile(` - // FOR i IN [1, 2, 3, 4, 5, 6] - // RETURN i < 3 ? : i * 2 - // `) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `[true,true,6,8,10,12]`) - //}) - // - //Convey("Should compile ternary operator with shortcut with nones", t, func() { - // c := compiler.New() - // p, err := c.Compile(` - // FOR i IN [NONE, 2, 3, 4, 5, 6] - // RETURN i ? : i - // `) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `[null,2,3,4,5,6]`) - //}) - // - //Convey("Should compile ternary operator with default values", t, func() { - // vals := []string{ - // "0", - // "0.0", - // "''", - // "NONE", - // "FALSE", - // } - // - // c := compiler.New() - // - // for _, val := range vals { - // p, err := c.Compile(fmt.Sprintf(` - // FOR i IN [%s, 1, 2, 3] - // RETURN i ? i * 2 : 'no value' - // `, val)) - // - // So(err, ShouldBeNil) - // - // out, err := p.Run(context.Background()) - // - // So(err, ShouldBeNil) - // - // So(string(out), ShouldEqual, `["no value",2,4,6]`) - // } - //}) - // - //Convey("Multi expression", t, func() { - // out := compiler.New().MustCompile(` - // RETURN 0 && true ? "1" : "some" - // `).MustRun(context.Background()) - // - // So(string(out), ShouldEqual, `"some"`) - // - // out = compiler.New().MustCompile(` - // RETURN length([]) > 0 && true ? "1" : "some" - // `).MustRun(context.Background()) - // - // So(string(out), ShouldEqual, `"some"`) - //}) + Convey("Should compile ternary operator with default values", t, func() { + vals := []string{ + "0", + "0.0", + "''", + "NONE", + "FALSE", + } + + c := compiler.New() + + for _, val := range vals { + p, err := c.Compile(fmt.Sprintf(` + FOR i IN [%s, 1, 2, 3] + RETURN i ? i * 2 : 'no value' + `, val)) + + So(err, ShouldBeNil) + + out, err := Run(p) + + So(err, ShouldBeNil) + + So(string(out), ShouldEqual, `["no value",2,4,6]`) + } + }) } func TestLikeOperator(t *testing.T) { @@ -645,13 +573,18 @@ func TestFunctionCall(t *testing.T) { "abc", nil, }, + { + "RETURN CONCAT(CONCAT('a', 'b'), 'c', CONCAT('d', 'e'))", + "abcde", + nil, + }, { ` -LET arr = [] -LET a = 1 -LET res = APPEND(arr, a) -RETURN res -`, + LET arr = [] + LET a = 1 + LET res = APPEND(arr, a) + RETURN res + `, []any{1}, ShouldEqualJSON, }, @@ -660,36 +593,36 @@ RETURN res 1, nil, }, - //{ - // "RETURN (FALSE OR T::FAIL())?", - // nil, - // nil, - //}, - //{ - // "RETURN T::FAIL()?", - // nil, - // nil, - //}, - //{ - // `FOR i IN [1, 2, 3, 4] - // LET duration = 10 - // - // WAIT(duration) - // - // RETURN i * 2`, - // []int{2, 4, 6, 8}, - // ShouldEqualJSON, - //}, - //{ - // `RETURN FIRST((FOR i IN 1..10 RETURN i * 2))`, - // 2, - // nil, - //}, - //{ - // `RETURN UNION((FOR i IN 0..5 RETURN i), (FOR i IN 6..10 RETURN i))`, - // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - // ShouldEqualJSON, - //}, + { + "RETURN (FALSE OR T::FAIL())?", + nil, + nil, + }, + { + "RETURN T::FAIL()?", + nil, + nil, + }, + { + `FOR i IN [1, 2, 3, 4] + LET duration = 10 + + WAIT(duration) + + RETURN i * 2`, + []int{2, 4, 6, 8}, + ShouldEqualJSON, + }, + { + `RETURN FIRST((FOR i IN 1..10 RETURN i * 2))`, + 2, + nil, + }, + { + `RETURN UNION((FOR i IN 0..5 RETURN i), (FOR i IN 6..10 RETURN i))`, + []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + ShouldEqualJSON, + }, }) } diff --git a/pkg/compiler/emitter.go b/pkg/compiler/emitter.go index 708ce31e..a4472041 100644 --- a/pkg/compiler/emitter.go +++ b/pkg/compiler/emitter.go @@ -23,6 +23,13 @@ func (e *Emitter) EmitJump(op runtime.Opcode, pos int) int { return len(e.instructions) - 1 } +// EmitJumpAB emits a jump opcode with a state and an argument. +func (e *Emitter) EmitJumpAB(op runtime.Opcode, state, cond runtime.Operand, pos int) int { + e.EmitABC(op, state, cond, runtime.Operand(pos)) + + return len(e.instructions) - 1 +} + // EmitJumpc emits a conditional jump opcode. func (e *Emitter) EmitJumpc(op runtime.Opcode, pos int, reg runtime.Operand) int { e.EmitAB(op, runtime.Operand(pos), reg) @@ -30,14 +37,24 @@ func (e *Emitter) EmitJumpc(op runtime.Opcode, pos int, reg runtime.Operand) int return len(e.instructions) - 1 } -// PatchJump patches a jump opcode with a new destination. -func (e *Emitter) PatchJump(dest int) { - e.instructions[dest].Operands[0] = runtime.Operand(len(e.instructions) - 1) +// PatchJump patches a jump opcode. +func (e *Emitter) PatchJump(instr int) { + e.instructions[instr].Operands[0] = runtime.Operand(len(e.instructions) - 1) +} + +// PatchJumpAB patches a jump opcode with a new destination. +func (e *Emitter) PatchJumpAB(inst int) { + e.instructions[inst].Operands[2] = runtime.Operand(len(e.instructions) - 1) +} + +// PatchJumpNextAB patches a jump instruction to jump over a current position. +func (e *Emitter) PatchJumpNextAB(instr int) { + e.instructions[instr].Operands[2] = runtime.Operand(len(e.instructions)) } -// 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) +// PatchJumpNext patches a jump instruction to jump over a current position. +func (e *Emitter) PatchJumpNext(instr int) { + e.instructions[instr].Operands[0] = runtime.Operand(len(e.instructions)) } // Emit emits an opcode with no arguments. diff --git a/pkg/compiler/visitor.go b/pkg/compiler/visitor.go index 9dcc176c..724d4887 100644 --- a/pkg/compiler/visitor.go +++ b/pkg/compiler/visitor.go @@ -106,7 +106,7 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} var distinct bool var returnRuleCtx antlr.RuleContext // identify whether it's WHILE or FOR loop - isForInLoop := ctx.While() == nil + isForLoop := ctx.While() == nil returnCtx := ctx.ForExpressionReturn() if c := returnCtx.ReturnExpression(); c != nil { @@ -121,16 +121,16 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} dsReg := loop.Result if loop.Allocate { - v.emitter.EmitAb(runtime.OpLoopInit, dsReg, distinct) + v.emitter.EmitAb(runtime.OpLoopBegin, dsReg, distinct) } - if isForInLoop { + if isForLoop { // Loop data source to iterate over src1 := ctx.ForExpressionSource().Accept(v).(runtime.Operand) - iterReg := v.registers.Allocate(Iter) + iterReg := v.registers.Allocate(State) - v.emitter.EmitAB(runtime.OpForLoopCall, iterReg, src1) + v.emitter.EmitAB(runtime.OpForLoopInit, iterReg, src1) // jumpPlaceholder is a placeholder for the exit jump position loop.Jump = v.emitter.EmitJumpc(runtime.OpForLoopNext, jumpPlaceholder, iterReg) @@ -162,26 +162,21 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} v.emitter.EmitAB(runtime.OpForLoopKey, keyReg, iterReg) } } else { - //// Create initial value for the loop counter - //v.emitter.EmitABC(runtime.OpWhileLoopInitCounter) - // - //loopJump = len(v.instructions) - // - //// Condition expression - //ctx.Expression().Accept(v) - // - //// Condition check - //exitJump = v.emitJump(runtime.OpJumpIfFalse) - //// pop the boolean value from the stack - //v.emitPop() - // - //counterVar := ctx.GetCounterVariable().GetText() - // - //// declare counter variable - //// and increment it by 1 - //index := v.declareVariable(counterVar) - //v.emitter.EmitABC(runtime.OpWhileLoopNext) - //v.defineVariable(index) + counterReg := v.registers.Allocate(State) + + // Create initial value for the loop counter + v.emitter.EmitA(runtime.OpWhileLoopInit, counterReg) + // Loop data source to iterate over + cond := ctx.Expression().Accept(v).(runtime.Operand) + + // jumpPlaceholder is a placeholder for the exit jump position + loop.Jump = v.emitter.EmitJumpAB(runtime.OpWhileLoopNext, counterReg, cond, jumpPlaceholder) + + counterVar := ctx.GetCounterVariable().GetText() + + // declare counter variable + valReg := v.symbols.DefineVariable(counterVar) + v.emitter.EmitAB(runtime.OpWhileLoopValue, valReg, counterReg) } // body @@ -201,16 +196,30 @@ func (v *visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} returnRuleCtx.Accept(v) } - v.emitter.EmitJump(runtime.OpJump, loop.Jump) + if isForLoop { + v.emitter.EmitJump(runtime.OpJump, loop.Jump) + } else { + v.emitter.EmitJump(runtime.OpJump, loop.Jump-1) + } // TODO: Do not allocate for pass-through loops dst := v.registers.Allocate(Temp) if loop.Allocate { - v.emitter.EmitAB(runtime.OpLoopFinalize, dst, dsReg) - v.emitter.PatchJump(loop.Jump) + // TODO: Reuse the dsReg register + v.emitter.EmitAB(runtime.OpLoopEnd, dst, dsReg) + + if isForLoop { + v.emitter.PatchJump(loop.Jump) + } else { + v.emitter.PatchJumpAB(loop.Jump) + } } else { - v.emitter.PatchJumpx(loop.Jump, 1) + if isForLoop { + v.emitter.PatchJumpNext(loop.Jump) + } else { + v.emitter.PatchJumpNextAB(loop.Jump) + } } v.loops.ExitLoop() @@ -718,7 +727,7 @@ func (v *visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { right := ctx.GetRight().Accept(v).(runtime.Operand) // And move the result to the destination register v.emitter.EmitAB(runtime.OpMove, dst, right) - v.emitter.PatchJump(end) + v.emitter.PatchJumpNext(end) return dst } @@ -735,7 +744,7 @@ func (v *visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { right := ctx.GetRight().Accept(v).(runtime.Operand) // And move the result to the destination register v.emitter.EmitAB(runtime.OpMove, dst, right) - v.emitter.PatchJump(end) + v.emitter.PatchJumpNext(end) return dst } @@ -767,8 +776,8 @@ func (v *visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { } // Jump over false branch - end := v.emitter.EmitJumpc(runtime.OpJump, jumpPlaceholder, dst) - v.emitter.PatchJump(otherwise) + end := v.emitter.EmitJump(runtime.OpJump, jumpPlaceholder) + v.emitter.PatchJumpNext(otherwise) // False branch if onFalse := ctx.GetOnFalse(); onFalse != nil { @@ -781,7 +790,7 @@ func (v *visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { } } - v.emitter.PatchJump(end) + v.emitter.PatchJumpNext(end) return dst } @@ -959,7 +968,7 @@ func (v *visitor) visitFunctionCall(ctx *fql.FunctionCallContext, safeCall bool) } } - nameAndDest := v.loadConstant(values.NewString(name)) + nameAndDest := v.loadConstant(values.NewString(strings.ToUpper(name))) if !safeCall { v.emitter.EmitAs(runtime.OpCall, nameAndDest, seq) diff --git a/pkg/runtime/opcode.go b/pkg/runtime/opcode.go index 3e990ab6..09b94c0d 100644 --- a/pkg/runtime/opcode.go +++ b/pkg/runtime/opcode.go @@ -51,12 +51,16 @@ const ( OpCall OpCallSafe - OpLoopInit // Creates a loop result dataset + OpLoopBegin // Creates a loop result dataset OpLoopPush - OpLoopFinalize + OpLoopEnd - OpForLoopCall // Creates an iterator for a loop + OpForLoopInit // Creates an iterator for a loop OpForLoopNext OpForLoopValue OpForLoopKey + + OpWhileLoopInit + OpWhileLoopNext + OpWhileLoopValue ) diff --git a/pkg/runtime/values/noop_iter.go b/pkg/runtime/values/noop_iter.go new file mode 100644 index 00000000..dcc7f423 --- /dev/null +++ b/pkg/runtime/values/noop_iter.go @@ -0,0 +1,26 @@ +package values + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" +) + +type noopIter struct{} + +var NoopIter = &noopIter{} + +func (it *noopIter) HasNext(ctx context.Context) (bool, error) { + return false, nil +} + +func (it *noopIter) Next(_ context.Context) error { + return nil +} + +func (it *noopIter) Value() core.Value { + return None +} + +func (it *noopIter) Key() core.Value { + return None +} diff --git a/pkg/runtime/vm.go b/pkg/runtime/vm.go index 6d7639d5..a4172d92 100644 --- a/pkg/runtime/vm.go +++ b/pkg/runtime/vm.go @@ -272,12 +272,12 @@ loop: } else { return nil, err } - case OpLoopInit: + case OpLoopBegin: reg[dst] = NewDataSet(src1 == 1) - case OpLoopFinalize: + case OpLoopEnd: ds := reg[src1].(*DataSet) reg[dst] = ds.ToArray() - case OpForLoopCall: + case OpForLoopInit: input := reg[src1] switch src := input.(type) { @@ -290,7 +290,12 @@ loop: reg[dst] = values.NewBoxedValue(iterator) default: - return nil, core.TypeError(src, types.Iterable) + if tryCatch(vm.pc) { + // Fall back to an empty iterator + reg[dst] = values.NewBoxedValue(values.NoopIter) + } else { + return nil, core.TypeError(src, types.Iterable) + } } case OpForLoopNext: boxed := reg[src1] @@ -317,6 +322,18 @@ loop: // TODO: Remove boxed value!!! iter := reg[src1].(*values.Boxed).Unwrap().(core.Iterator) reg[dst] = iter.Key() + case OpWhileLoopInit: + reg[dst] = values.ZeroInt + case OpWhileLoopNext: + cond := values.ToBoolean(reg[src1]) + + if cond { + reg[dst] = operators.Increment(reg[dst]) + } else { + vm.pc = int(src2) + } + case OpWhileLoopValue: + reg[dst] = reg[src1] case OpLoopPush: ds := reg[dst].(*DataSet) ds.Push(reg[src1])