From cf87b63720f91924db2bfe41c5bf8a0b5ee3a4cd Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Wed, 11 Jun 2025 08:02:05 -0400 Subject: [PATCH] Refactor compiler components; introduce CatchStack and enhance FuncContext for improved state management --- pkg/compiler/compiler.go | 2 +- pkg/compiler/internal/catch_stack.go | 47 + pkg/compiler/internal/collect_compiler.go | 56 + pkg/compiler/internal/context.go | 35 +- pkg/compiler/internal/emitter.go | 21 - pkg/compiler/internal/emitter_helpers.go | 57 +- pkg/compiler/internal/expr_compiler.go | 497 +++++ pkg/compiler/internal/helpers.go | 20 + pkg/compiler/internal/lit_compiler.go | 243 +++ pkg/compiler/internal/loop.go | 80 + pkg/compiler/internal/loop_compiler.go | 336 ++++ pkg/compiler/internal/loop_table.go | 55 - pkg/compiler/internal/stmt_compiler.go | 91 + pkg/compiler/internal/visitor.go | 2194 +++++---------------- pkg/compiler/internal/wait_compiler.go | 138 ++ pkg/parser/fql/fql_lexer.go | 3 +- pkg/vm/vm.go | 1 + 17 files changed, 2085 insertions(+), 1791 deletions(-) create mode 100644 pkg/compiler/internal/catch_stack.go create mode 100644 pkg/compiler/internal/collect_compiler.go create mode 100644 pkg/compiler/internal/expr_compiler.go create mode 100644 pkg/compiler/internal/lit_compiler.go create mode 100644 pkg/compiler/internal/loop.go create mode 100644 pkg/compiler/internal/loop_compiler.go create mode 100644 pkg/compiler/internal/stmt_compiler.go create mode 100644 pkg/compiler/internal/wait_compiler.go diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index fca50b29..3a07e4d3 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -75,7 +75,7 @@ func (c *Compiler) Compile(query string) (program *vm.Program, err error) { program = &vm.Program{} program.Bytecode = l.Emitter.Bytecode() program.Constants = l.Symbols.Constants() - program.CatchTable = l.CatchTable + program.CatchTable = l.CatchTable.All() program.Registers = l.Registers.Size() program.Params = l.Symbols.Params() diff --git a/pkg/compiler/internal/catch_stack.go b/pkg/compiler/internal/catch_stack.go new file mode 100644 index 00000000..b7bc2200 --- /dev/null +++ b/pkg/compiler/internal/catch_stack.go @@ -0,0 +1,47 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/vm" +) + +type CatchStack struct { + entries []vm.Catch +} + +func NewCatchStack() *CatchStack { + return &CatchStack{ + entries: make([]vm.Catch, 0), + } +} + +func (cs *CatchStack) Push(start, end, jump int) { + cs.entries = append(cs.entries, vm.Catch{start, end, jump}) +} + +func (cs *CatchStack) Pop() { + if len(cs.entries) > 0 { + cs.entries = cs.entries[:len(cs.entries)-1] + } +} + +func (cs *CatchStack) Find(pos int) (vm.Catch, bool) { + for _, c := range cs.entries { + if pos >= c[0] && pos <= c[1] { + return c, true + } + } + + return vm.Catch{}, false +} + +func (cs *CatchStack) Clear() { + cs.entries = cs.entries[:0] +} + +func (cs *CatchStack) Len() int { + return len(cs.entries) +} + +func (cs *CatchStack) All() []vm.Catch { + return cs.entries +} diff --git a/pkg/compiler/internal/collect_compiler.go b/pkg/compiler/internal/collect_compiler.go new file mode 100644 index 00000000..0b411fdd --- /dev/null +++ b/pkg/compiler/internal/collect_compiler.go @@ -0,0 +1,56 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/parser/fql" +) + +type CollectCompiler struct { + Ctx *FuncContext +} + +func NewCollectCompiler(ctx *FuncContext) *CollectCompiler { + return &CollectCompiler{Ctx: ctx} +} + +func (cc *CollectCompiler) Compile(ctx fql.ICollectClauseContext) { + //loop := cc.Ctx.Loops.Current() + //if loop == nil { + // panic("COLLECT clause must appear inside a loop") + //} + // + //// Grouping by key + //if group := ctx.CollectGrouping(); group != nil { + // // Example: COLLECT key = expr + // keyName := group.Variable().GetText() + // keyExpr := group.Expression() + // keyReg := cc.Ctx.ExprCompiler.Compile(keyExpr) + // + // loop.Result = cc.Ctx.Registers.Allocate(Result) + // + // cc.Ctx.Emitter.EmitABC(vm.OpCollect, loop.Result, keyReg, keyReg) // src1=key, src2=key (single-group) + // cc.Ctx.Symbols.DeclareLocal(keyName) + //} + // + //// Aggregation + //if agg := ctx.CollectAggregator(); agg != nil { + // for _, part := range agg.AllCollectGroupVariable() { + // name := part.Variable().GetText() + // expr := part.Expression() + // + // src := cc.Ctx.ExprCompiler.Compile(expr) + // dst := cc.Ctx.Registers.Allocate(Result) + // + // cc.Ctx.Emitter.EmitABC(vm.OpCollect, dst, src, src) + // cc.Ctx.Symbols.DeclareLocal(name) + // } + //} + // + //// Optional counter + //if counter := ctx.CollectCounter(); counter != nil { + // name := counter.Variable().GetText() + // dst := cc.Ctx.Registers.Allocate(Result) + // + // cc.Ctx.Emitter.EmitAB(vm.OpCount, dst, loop.Value) + // cc.Ctx.Symbols.DeclareLocal(name) + //} +} diff --git a/pkg/compiler/internal/context.go b/pkg/compiler/internal/context.go index 23ddf8bf..6017b5c0 100644 --- a/pkg/compiler/internal/context.go +++ b/pkg/compiler/internal/context.go @@ -1,22 +1,39 @@ package internal -import "github.com/MontFerret/ferret/pkg/vm" - +// FuncContext encapsulates the context and state required for compiling and managing functions during code processing. type FuncContext struct { Emitter *Emitter Registers *RegisterAllocator Symbols *SymbolTable Loops *LoopTable - CatchTable []vm.Catch + CatchTable *CatchStack + + ExprCompiler *ExprCompiler + LiteralCompiler *LiteralCompiler + StmtCompiler *StmtCompiler + LoopCompiler *LoopCompiler + CollectCompiler *CollectCompiler + WaitCompiler *WaitCompiler } +// NewFuncContext initializes and returns a new instance of FuncContext, setting up all required components for compilation. func NewFuncContext() *FuncContext { - registers := NewRegisterAllocator() - return &FuncContext{ + ctx := &FuncContext{ Emitter: NewEmitter(), - Registers: registers, - Symbols: NewSymbolTable(registers), - Loops: NewLoopTable(registers), - CatchTable: make([]vm.Catch, 0), + Registers: NewRegisterAllocator(), + Symbols: nil, // set later + Loops: nil, // set later + CatchTable: NewCatchStack(), } + ctx.Symbols = NewSymbolTable(ctx.Registers) + ctx.Loops = NewLoopTable(ctx.Registers) + + ctx.ExprCompiler = NewExprCompiler(ctx) + ctx.LiteralCompiler = NewLiteralCompiler(ctx) + ctx.StmtCompiler = NewStmtCompiler(ctx) + ctx.LoopCompiler = NewLoopCompiler(ctx) + ctx.CollectCompiler = NewCollectCompiler(ctx) + ctx.WaitCompiler = NewWaitCompiler(ctx) + + return ctx } diff --git a/pkg/compiler/internal/emitter.go b/pkg/compiler/internal/emitter.go index 9bdfef06..d4bad4aa 100644 --- a/pkg/compiler/internal/emitter.go +++ b/pkg/compiler/internal/emitter.go @@ -23,27 +23,6 @@ func (e *Emitter) Size() int { return len(e.instructions) } -// EmitJump emits a jump opcode. -func (e *Emitter) EmitJump(op vm.Opcode, pos int) int { - e.EmitA(op, vm.Operand(pos)) - - return len(e.instructions) - 1 -} - -// EmitJumpAB emits a jump opcode with a state and an argument. -func (e *Emitter) EmitJumpAB(op vm.Opcode, state, cond vm.Operand, pos int) int { - e.EmitABC(op, state, cond, vm.Operand(pos)) - - return len(e.instructions) - 1 -} - -// EmitJumpc emits a conditional jump opcode. -func (e *Emitter) EmitJumpc(op vm.Opcode, pos int, reg vm.Operand) int { - e.EmitAB(op, vm.Operand(pos), reg) - - return len(e.instructions) - 1 -} - // PatchSwapAB modifies an instruction at the given position to swap operands and update its operation and destination. func (e *Emitter) PatchSwapAB(pos int, op vm.Opcode, dst, src1 vm.Operand) { e.instructions[pos] = vm.Instruction{ diff --git a/pkg/compiler/internal/emitter_helpers.go b/pkg/compiler/internal/emitter_helpers.go index a0c903e8..e49da375 100644 --- a/pkg/compiler/internal/emitter_helpers.go +++ b/pkg/compiler/internal/emitter_helpers.go @@ -1,7 +1,6 @@ package internal import ( - "github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/vm" ) @@ -51,16 +50,40 @@ func (e *Emitter) EmitClose(reg vm.Operand) { e.EmitA(vm.OpClose, reg) } -func (e *Emitter) EmitLoadConst(dst vm.Operand, val runtime.Value, symbols *SymbolTable) { - e.EmitAB(vm.OpLoadConst, dst, symbols.AddConstant(val)) +func (e *Emitter) EmitLoadConst(dst vm.Operand, constant vm.Operand) { + e.EmitAB(vm.OpLoadConst, dst, constant) +} + +func (e *Emitter) EmitLoadGlobal(dst, constant vm.Operand) { + e.EmitAB(vm.OpLoadGlobal, dst, constant) +} + +func (e *Emitter) EmitLoadParam(constant vm.Operand) { + e.EmitA(vm.OpLoadParam, constant) +} + +func (e *Emitter) EmitBoolean(dst vm.Operand, value bool) { + if value { + e.EmitAB(vm.OpLoadBool, dst, 1) + } else { + e.EmitAB(vm.OpLoadBool, dst, 0) + } } // ─── Data Structures ────────────────────────────────────────────────────── +func (e *Emitter) EmitEmptyList(dst vm.Operand) { + e.EmitA(vm.OpList, dst) +} + func (e *Emitter) EmitList(dst vm.Operand, seq RegisterSequence) { e.EmitAs(vm.OpList, dst, seq) } +func (e *Emitter) EmitEmptyMap(dst vm.Operand) { + e.EmitA(vm.OpMap, dst) +} + func (e *Emitter) EmitMap(dst vm.Operand, seq RegisterSequence) { e.EmitAs(vm.OpMap, dst, seq) } @@ -129,6 +152,34 @@ func (e *Emitter) EmitLte(dst, a, b vm.Operand) { // ─── Control Flow ──────────────────────────────────────────────────────── +func (e *Emitter) EmitJump(pos int) int { + e.EmitA(vm.OpJump, vm.Operand(pos)) + + return len(e.instructions) - 1 +} + +// EmitJumpAB emits a jump opcode with a state and an argument. +func (e *Emitter) EmitJumpAB(op vm.Opcode, state, cond vm.Operand, pos int) int { + e.EmitABC(op, state, cond, vm.Operand(pos)) + + return len(e.instructions) - 1 +} + +// EmitJumpc emits a conditional jump opcode. +func (e *Emitter) EmitJumpc(op vm.Opcode, pos int, reg vm.Operand) int { + e.EmitAB(op, vm.Operand(pos), reg) + + return len(e.instructions) - 1 +} + +func (e *Emitter) EmitJumpIfFalse(cond vm.Operand, jumpTarget int) int { + return e.EmitJumpIf(cond, false, jumpTarget) +} + +func (e *Emitter) EmitJumpIfTrue(cond vm.Operand, jumpTarget int) int { + return e.EmitJumpIf(cond, true, jumpTarget) +} + func (e *Emitter) EmitJumpIf(cond vm.Operand, isTrue bool, jumpTarget int) int { if isTrue { return e.EmitJumpc(vm.OpJumpIfTrue, jumpTarget, cond) diff --git a/pkg/compiler/internal/expr_compiler.go b/pkg/compiler/internal/expr_compiler.go new file mode 100644 index 00000000..b5f5973d --- /dev/null +++ b/pkg/compiler/internal/expr_compiler.go @@ -0,0 +1,497 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/parser/fql" + "github.com/MontFerret/ferret/pkg/runtime" + "github.com/MontFerret/ferret/pkg/vm" + "regexp" + "strings" +) + +// Runtime functions +const ( + runtimeTypename = "TYPENAME" + runtimeLength = "LENGTH" + runtimeWait = "WAIT" +) + +type ExprCompiler struct { + ctx *FuncContext +} + +func NewExprCompiler(ctx *FuncContext) *ExprCompiler { + return &ExprCompiler{ctx: ctx} +} + +func (ec *ExprCompiler) Compile(ctx fql.IExpressionContext) vm.Operand { + if c := ctx.UnaryOperator(); c != nil { + return ec.compileUnary(c, ctx) + } + + if c := ctx.LogicalAndOperator(); c != nil { + return ec.compileLogicalAnd(ctx.Predicate()) + } + + if c := ctx.LogicalOrOperator(); c != nil { + return ec.compileLogicalOr(ctx.Predicate()) + } + + if c := ctx.GetTernaryOperator(); c != nil { + return ec.compileTernary(ctx) + } + + if c := ctx.Predicate(); c != nil { + return ec.compilePredicate(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +// TODO: Free temporary registers if needed +func (ec *ExprCompiler) compileUnary(ctx fql.IUnaryOperatorContext, parent fql.IExpressionContext) vm.Operand { + src := ec.Compile(parent.GetRight()) + dst := ec.ctx.Registers.Allocate(Temp) + + var op vm.Opcode + + if ctx.Not() != nil { + op = vm.OpNot + } else if ctx.Minus() != nil { + op = vm.OpFlipNegative + } else if ctx.Plus() != nil { + op = vm.OpFlipPositive + } else { + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) + } + + // We do not overwrite the source register + ec.ctx.Emitter.EmitAB(op, dst, src) + + return dst +} + +// TODO: Free temporary registers if needed +func (ec *ExprCompiler) compileLogicalAnd(ctx fql.IPredicateContext) vm.Operand { + dst := ec.ctx.Registers.Allocate(Temp) + + // Execute left expression + left := ec.compilePredicate(ctx.GetLeft()) + + // Execute left expression + ec.ctx.Emitter.EmitMove(dst, left) + + // Test if left is false and jump to the end + end := ec.ctx.Emitter.EmitJumpIfFalse(dst, jumpPlaceholder) + + // If left is true, execute right expression + right := ec.compilePredicate(ctx.GetRight()) + + // And move the result to the destination register + ec.ctx.Emitter.EmitMove(dst, right) + + ec.ctx.Emitter.PatchJumpNext(end) + + return dst +} + +// TODO: Free temporary registers if needed +func (ec *ExprCompiler) compileLogicalOr(ctx fql.IPredicateContext) vm.Operand { + dst := ec.ctx.Registers.Allocate(Temp) + + // Execute left expression + left := ec.compilePredicate(ctx.GetLeft()) + + // Execute left expression + ec.ctx.Emitter.EmitMove(dst, left) + + // Test if left is true and jump to the end + end := ec.ctx.Emitter.EmitJumpIfTrue(dst, jumpPlaceholder) + + // If left is false, execute right expression + right := ec.compilePredicate(ctx.GetRight()) + + // And move the result to the destination register + ec.ctx.Emitter.EmitMove(dst, right) + + ec.ctx.Emitter.PatchJumpNext(end) + + return dst +} + +// TODO: Free temporary registers if needed +func (ec *ExprCompiler) compileTernary(ctx fql.IExpressionContext) vm.Operand { + dst := ec.ctx.Registers.Allocate(Temp) + + // Compile condition and put result in dst + condReg := ec.Compile(ctx.GetCondition()) + ec.ctx.Emitter.EmitMove(dst, condReg) + + // Jump to 'false' branch if condition is false + otherwise := ec.ctx.Emitter.EmitJumpIfFalse(dst, jumpPlaceholder) + + // True branch + if onTrue := ctx.GetOnTrue(); onTrue != nil { + trueReg := ec.Compile(onTrue) + // Move result of true branch to dst + ec.ctx.Emitter.EmitMove(dst, trueReg) + } + + // Jump over false branch + end := ec.ctx.Emitter.EmitJump(jumpPlaceholder) + ec.ctx.Emitter.PatchJumpNext(otherwise) + + // False branch + if onFalse := ctx.GetOnFalse(); onFalse != nil { + falseReg := ec.Compile(onFalse) + // Move result of false branch to dst + ec.ctx.Emitter.EmitMove(dst, falseReg) + } + + ec.ctx.Emitter.PatchJumpNext(end) + + return dst +} + +// TODO: Free temporary registers if needed +func (ec *ExprCompiler) compilePredicate(ctx fql.IPredicateContext) vm.Operand { + if c := ctx.ExpressionAtom(); c != nil { + startCatch := ec.ctx.Emitter.Size() + reg := ec.compileAtom(c) + + if c.ErrorOperator() != nil { + jump := -1 + endCatch := ec.ctx.Emitter.Size() + + if c.ForExpression() != nil { + // We jump back to finalize the loop before exiting + jump = endCatch - 1 + } + + ec.ctx.CatchTable.Push(startCatch, endCatch, jump) + } + + return reg + } + + var opcode vm.Opcode + dest := ec.ctx.Registers.Allocate(Temp) + left := ec.compilePredicate(ctx.Predicate(0)) + right := ec.compilePredicate(ctx.Predicate(1)) + + if op := ctx.EqualityOperator(); op != nil { + switch ctx.GetText() { + case "==": + opcode = vm.OpEq + case "!=": + opcode = vm.OpNeq + case ">": + opcode = vm.OpGt + case ">=": + opcode = vm.OpGte + case "<": + opcode = vm.OpLt + case "<=": + opcode = vm.OpLte + default: + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) + } + } else if op := ctx.ArrayOperator(); op != nil { + // TODO: Implement me + panic(runtime.Error(runtime.ErrNotImplemented, "array operator")) + } else if op := ctx.InOperator(); op != nil { + if op.Not() == nil { + opcode = vm.OpIn + } else { + opcode = vm.OpNotIn + } + } else if op := ctx.LikeOperator(); op != nil { + if op.(*fql.LikeOperatorContext).Not() == nil { + opcode = vm.OpLike + } else { + opcode = vm.OpNotLike + } + } + + ec.ctx.Emitter.EmitABC(opcode, dest, left, right) + + return dest +} + +// TODO: Free temporary registers if needed +func (ec *ExprCompiler) compileAtom(ctx fql.IExpressionAtomContext) vm.Operand { + var opcode vm.Opcode + var isSet bool + + if op := ctx.MultiplicativeOperator(); op != nil { + isSet = true + + switch op.GetText() { + case "*": + opcode = vm.OpMulti + case "/": + opcode = vm.OpDiv + case "%": + opcode = vm.OpMod + default: + panic(runtime.Error(ErrUnexpectedToken, op.GetText())) + } + } else if op := ctx.AdditiveOperator(); op != nil { + isSet = true + + switch op.GetText() { + case "+": + opcode = vm.OpAdd + case "-": + opcode = vm.OpSub + default: + panic(runtime.Error(ErrUnexpectedToken, op.GetText())) + } + + } else if op := ctx.RegexpOperator(); op != nil { + isSet = true + + switch op.GetText() { + case "=~": + opcode = vm.OpRegexpPositive + case "!~": + opcode = vm.OpRegexpNegative + default: + panic(runtime.Error(ErrUnexpectedToken, op.GetText())) + } + } + + if isSet { + regLeft := ec.compileAtom(ctx.ExpressionAtom(0)) + regRight := ec.compileAtom(ctx.ExpressionAtom(1)) + dst := ec.ctx.Registers.Allocate(Temp) + + if opcode == vm.OpRegexpPositive || opcode == vm.OpRegexpNegative { + if regRight.IsConstant() { + val := ec.ctx.Symbols.Constant(regRight) + + // Verify that the expression is a valid regular expression + regexp.MustCompile(val.String()) + } + } + + ec.ctx.Emitter.EmitABC(opcode, dst, regLeft, regRight) + + return dst + } + + if c := ctx.FunctionCallExpression(); c != nil { + return ec.CompileFunctionCallExpression(c) + } else if c := ctx.RangeOperator(); c != nil { + return ec.CompileRangeOperator(c) + } else if c := ctx.Literal(); c != nil { + return ec.ctx.LiteralCompiler.Compile(c) + } else if c := ctx.Variable(); c != nil { + return ec.CompileVariable(c) + } else if c := ctx.MemberExpression(); c != nil { + return ec.CompileMemberExpression(c) + } else if c := ctx.Param(); c != nil { + return ec.CompileParam(c) + } else if c := ctx.ForExpression(); c != nil { + return ec.ctx.LoopCompiler.Compile(c) + } else if c := ctx.WaitForExpression(); c != nil { + return ec.ctx.WaitCompiler.Compile(c) + } else if c := ctx.Expression(); c != nil { + return ec.Compile(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (ec *ExprCompiler) CompileMemberExpression(ctx fql.IMemberExpressionContext) vm.Operand { + mes := ctx.MemberExpressionSource() + segments := ctx.AllMemberExpressionPath() + + var src1 vm.Operand + + if c := mes.Variable(); c != nil { + src1 = ec.CompileVariable(c) + } else if c := mes.Param(); c != nil { + src1 = ec.CompileParam(c) + } else if c := mes.ObjectLiteral(); c != nil { + src1 = ec.ctx.LiteralCompiler.CompileObjectLiteral(c) + } else if c := mes.ArrayLiteral(); c != nil { + src1 = ec.ctx.LiteralCompiler.CompileArrayLiteral(c) + } else if c := mes.FunctionCall(); c != nil { + // FOO()?.bar + segment := segments[0] + src1 = ec.CompileFunctionCall(c, segment.ErrorOperator() != nil) + } + + var dst vm.Operand + + for _, segment := range segments { + var src2 vm.Operand + p := segment.(*fql.MemberExpressionPathContext) + + if c := p.PropertyName(); c != nil { + src2 = ec.ctx.LiteralCompiler.CompilePropertyName(c) + } else if c := p.ComputedPropertyName(); c != nil { + src2 = ec.ctx.LiteralCompiler.CompileComputedPropertyName(c) + } + + dst = ec.ctx.Registers.Allocate(Temp) + + // TODO: Replace with EmitLoadKey + if p.ErrorOperator() != nil { + ec.ctx.Emitter.EmitLoadPropertyOptional(dst, src1, src2) + } else { + ec.ctx.Emitter.EmitLoadProperty(dst, src1, src2) + } + + src1 = dst + } + + return dst +} + +func (ec *ExprCompiler) CompileVariable(ctx fql.IVariableContext) vm.Operand { + // Just return the register / constant index + op, _, found := ec.ctx.Symbols.Resolve(ctx.GetText()) + + if !found { + panic(runtime.Error(ErrVariableNotFound, ctx.GetText())) + } + + if op.IsRegister() { + return op + } + + reg := ec.ctx.Registers.Allocate(Temp) + ec.ctx.Emitter.EmitLoadGlobal(reg, op) + + return reg +} + +func (ec *ExprCompiler) CompileParam(ctx fql.IParamContext) vm.Operand { + name := ctx.Identifier().GetText() + reg := ec.ctx.Symbols.BindParam(name) + ec.ctx.Emitter.EmitLoadParam(reg) + + return reg +} + +func (ec *ExprCompiler) CompileFunctionCallExpression(ctx fql.IFunctionCallExpressionContext) vm.Operand { + protected := ctx.ErrorOperator() != nil + call := ctx.FunctionCall() + + return ec.CompileFunctionCall(call, protected) +} + +func (ec *ExprCompiler) CompileFunctionCall(ctx fql.IFunctionCallContext, protected bool) vm.Operand { + name := ec.functionName(ctx) + seq := ec.CompileArgumentList(ctx.ArgumentList()) + + switch name { + case runtimeLength: + dst := ec.ctx.Registers.Allocate(Temp) + + if seq == nil || len(seq) != 1 { + panic(runtime.Error(runtime.ErrInvalidArgument, runtimeLength+": expected 1 argument")) + } + + ec.ctx.Emitter.EmitAB(vm.OpLength, dst, seq[0]) + + return dst + case runtimeTypename: + dst := ec.ctx.Registers.Allocate(Temp) + + if seq == nil || len(seq) != 1 { + panic(runtime.Error(runtime.ErrInvalidArgument, runtimeTypename+": expected 1 argument")) + } + + ec.ctx.Emitter.EmitAB(vm.OpType, dst, seq[0]) + + return dst + case runtimeWait: + if len(seq) != 1 { + panic(runtime.Error(runtime.ErrInvalidArgument, runtimeWait+": expected 1 argument")) + } + + ec.ctx.Emitter.EmitA(vm.OpSleep, seq[0]) + + return seq[0] + default: + dest := ec.ctx.Registers.Allocate(Temp) + ec.ctx.Emitter.EmitLoadConst(dest, ec.ctx.Symbols.AddConstant(name)) + + if !protected { + ec.ctx.Emitter.EmitAs(vm.OpCall, dest, seq) + } else { + ec.ctx.Emitter.EmitAs(vm.OpProtectedCall, dest, seq) + } + + return dest + } +} + +func (ec *ExprCompiler) CompileArgumentList(ctx fql.IArgumentListContext) RegisterSequence { + var seq RegisterSequence + // Get all array element expressions + exps := ctx.AllExpression() + size := len(exps) + + if size > 0 { + // Allocate seq for function arguments + seq = ec.ctx.Registers.AllocateSequence(size) + + // Evaluate each element into seq Registers + for i, exp := range exps { + // Compile expression and move to seq register + srcReg := ec.Compile(exp) + + // TODO: Figure out how to remove OpMove and use Registers returned from each expression + ec.ctx.Emitter.EmitMove(seq[i], srcReg) + + // Free source register if temporary + if srcReg.IsRegister() { + //ec.ctx.Registers.Free(srcReg) + } + } + } + + return seq +} + +func (ec *ExprCompiler) CompileRangeOperator(ctx fql.IRangeOperatorContext) vm.Operand { + dst := ec.ctx.Registers.Allocate(Temp) + start := ec.compileRangeOperand(ctx.GetLeft()) + end := ec.compileRangeOperand(ctx.GetRight()) + + ec.ctx.Emitter.EmitRange(dst, start, end) + + return dst +} + +func (ec *ExprCompiler) compileRangeOperand(ctx fql.IRangeOperandContext) vm.Operand { + if c := ctx.Variable(); c != nil { + return ec.CompileVariable(c) + } + + if c := ctx.Param(); c != nil { + return ec.CompileParam(c) + } + + if c := ctx.IntegerLiteral(); c != nil { + return ec.ctx.LiteralCompiler.CompileIntegerLiteral(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (ec *ExprCompiler) functionName(ctx fql.IFunctionCallContext) runtime.String { + var name string + funcNS := ctx.Namespace() + + if funcNS != nil { + name += funcNS.GetText() + } + + name += ctx.FunctionName().GetText() + + return runtime.NewString(strings.ToUpper(name)) +} diff --git a/pkg/compiler/internal/helpers.go b/pkg/compiler/internal/helpers.go index d27b3944..227a608e 100644 --- a/pkg/compiler/internal/helpers.go +++ b/pkg/compiler/internal/helpers.go @@ -1,6 +1,8 @@ package internal import ( + "github.com/MontFerret/ferret/pkg/vm" + "github.com/antlr4-go/antlr/v4" "strings" "github.com/MontFerret/ferret/pkg/runtime" @@ -8,6 +10,24 @@ import ( "github.com/pkg/errors" ) +func loadConstant(ctx *FuncContext, value runtime.Value) vm.Operand { + reg := ctx.Registers.Allocate(Temp) + ctx.Emitter.EmitLoadConst(reg, ctx.Symbols.AddConstant(value)) + return reg +} + +func sortDirection(dir antlr.TerminalNode) runtime.SortDirection { + if dir == nil { + return runtime.SortDirectionAsc + } + + if strings.ToLower(dir.GetText()) == "desc" { + return runtime.SortDirectionDesc + } + + return runtime.SortDirectionAsc +} + func copyFromNamespace(fns *runtime.Functions, namespace string) error { // In the name of the function "A::B::C", the namespace is "A::B", // not "A::B::". diff --git a/pkg/compiler/internal/lit_compiler.go b/pkg/compiler/internal/lit_compiler.go new file mode 100644 index 00000000..4aed131e --- /dev/null +++ b/pkg/compiler/internal/lit_compiler.go @@ -0,0 +1,243 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/parser/fql" + "github.com/MontFerret/ferret/pkg/runtime" + "github.com/MontFerret/ferret/pkg/vm" + "github.com/antlr4-go/antlr/v4" + "strconv" + "strings" +) + +type LiteralCompiler struct { + ctx *FuncContext +} + +func NewLiteralCompiler(ctx *FuncContext) *LiteralCompiler { + return &LiteralCompiler{ + ctx: ctx, + } +} + +func (lc *LiteralCompiler) Compile(ctx fql.ILiteralContext) vm.Operand { + if c := ctx.StringLiteral(); c != nil { + return lc.CompileStringLiteral(c) + } else if c := ctx.IntegerLiteral(); c != nil { + return lc.CompileIntegerLiteral(c) + } else if c := ctx.FloatLiteral(); c != nil { + return lc.CompileFloatLiteral(c) + } else if c := ctx.BooleanLiteral(); c != nil { + return lc.CompileBooleanLiteral(c) + } else if c := ctx.ArrayLiteral(); c != nil { + return lc.CompileArrayLiteral(c) + } else if c := ctx.ObjectLiteral(); c != nil { + return lc.CompileObjectLiteral(c) + } else if c := ctx.NoneLiteral(); c != nil { + return lc.CompileNoneLiteral(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (lc *LiteralCompiler) CompileStringLiteral(ctx fql.IStringLiteralContext) vm.Operand { + var b strings.Builder + + for _, child := range ctx.GetChildren() { + tree := child.(antlr.TerminalNode) + sym := tree.GetSymbol() + input := sym.GetInputStream() + + if input == nil { + continue + } + + size := input.Size() + // skip quotes + start := sym.GetStart() + 1 + stop := sym.GetStop() - 1 + + if stop >= size { + stop = size - 1 + } + + if start < size && stop < size { + for i := start; i <= stop; i++ { + c := input.GetText(i, i) + + switch c { + case "\\": + c2 := input.GetText(i, i+1) + + switch c2 { + case "\\n": + b.WriteString("\n") + case "\\t": + b.WriteString("\t") + default: + b.WriteString(c2) + } + + i++ + default: + b.WriteString(c) + } + } + } + } + + return loadConstant(lc.ctx, runtime.NewString(b.String())) +} + +func (lc *LiteralCompiler) CompileIntegerLiteral(ctx fql.IIntegerLiteralContext) vm.Operand { + val, err := strconv.Atoi(ctx.GetText()) + + if err != nil { + panic(err) + } + + return loadConstant(lc.ctx, runtime.NewInt(val)) +} + +func (lc *LiteralCompiler) CompileFloatLiteral(ctx fql.IFloatLiteralContext) vm.Operand { + val, err := strconv.ParseFloat(ctx.GetText(), 64) + + if err != nil { + panic(err) + } + + return loadConstant(lc.ctx, runtime.NewFloat(val)) +} + +func (lc *LiteralCompiler) CompileBooleanLiteral(ctx fql.IBooleanLiteralContext) vm.Operand { + reg := lc.ctx.Registers.Allocate(Temp) + + switch strings.ToLower(ctx.GetText()) { + case "true": + lc.ctx.Emitter.EmitBoolean(reg, true) + case "false": + lc.ctx.Emitter.EmitBoolean(reg, false) + default: + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) + } + + return reg +} + +func (lc *LiteralCompiler) CompileNoneLiteral(_ fql.INoneLiteralContext) vm.Operand { + reg := lc.ctx.Registers.Allocate(Temp) + lc.ctx.Emitter.EmitA(vm.OpLoadNone, reg) + + return reg +} + +func (lc *LiteralCompiler) CompileArrayLiteral(ctx fql.IArrayLiteralContext) vm.Operand { + // Allocate destination register for the array + destReg := lc.ctx.Registers.Allocate(Temp) + + if list := ctx.ArgumentList(); list != nil { + // Get all array element expressions + exps := list.(fql.IArgumentListContext).AllExpression() + size := len(exps) + + if size > 0 { + // Allocate seq for array elements + seq := lc.ctx.Registers.AllocateSequence(size) + + // Evaluate each element into seq Registers + for i, exp := range exps { + // Compile expression and move to seq register + srcReg := lc.ctx.ExprCompiler.Compile(exp) + + // TODO: Figure out how to remove OpMove and use Registers returned from each expression + lc.ctx.Emitter.EmitMove(seq[i], srcReg) + + // Free source register if temporary + if srcReg.IsRegister() { + //lc.ctx.Registers.Free(srcReg) + } + } + + // Initialize an array + lc.ctx.Emitter.EmitList(destReg, seq) + + // Free seq Registers + //lc.ctx.Registers.FreeSequence(seq) + + return destReg + } + } + + // Empty array + lc.ctx.Emitter.EmitEmptyList(destReg) + + return destReg +} + +func (lc *LiteralCompiler) CompileObjectLiteral(ctx fql.IObjectLiteralContext) vm.Operand { + dst := lc.ctx.Registers.Allocate(Temp) + assignments := ctx.AllPropertyAssignment() + size := len(assignments) + + if size == 0 { + lc.ctx.Emitter.EmitEmptyMap(dst) + + return dst + } + + seq := lc.ctx.Registers.AllocateSequence(len(assignments) * 2) + + for i := 0; i < size; i++ { + var propOp vm.Operand + var valOp vm.Operand + pac := assignments[i] + + if prop := pac.PropertyName(); prop != nil { + propOp = lc.CompilePropertyName(prop) + valOp = lc.ctx.ExprCompiler.Compile(pac.Expression()) + } else if comProp := pac.ComputedPropertyName(); comProp != nil { + propOp = lc.CompileComputedPropertyName(comProp) + valOp = lc.ctx.ExprCompiler.Compile(pac.Expression()) + } else if variable := pac.Variable(); variable != nil { + propOp = loadConstant(lc.ctx, runtime.NewString(variable.GetText())) + valOp = lc.ctx.ExprCompiler.CompileVariable(variable) + } + + regIndex := i * 2 + + lc.ctx.Emitter.EmitMove(seq[regIndex], propOp) + lc.ctx.Emitter.EmitMove(seq[regIndex+1], valOp) + + // Free source register if temporary + if propOp.IsRegister() { + //lc.ctx.Registers.Free(propOp) + } + } + + lc.ctx.Emitter.EmitMap(dst, seq) + + return dst +} + +func (lc *LiteralCompiler) CompilePropertyName(ctx fql.IPropertyNameContext) vm.Operand { + if str := ctx.StringLiteral(); str != nil { + return lc.CompileStringLiteral(str) + } + + var name string + + if id := ctx.Identifier(); id != nil { + name = id.GetText() + } else if word := ctx.SafeReservedWord(); word != nil { + name = word.GetText() + } else if word := ctx.UnsafeReservedWord(); word != nil { + name = word.GetText() + } else { + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) + } + + return loadConstant(lc.ctx, runtime.NewString(name)) +} + +func (lc *LiteralCompiler) CompileComputedPropertyName(ctx fql.IComputedPropertyNameContext) vm.Operand { + return lc.ctx.ExprCompiler.Compile(ctx.Expression()) +} diff --git a/pkg/compiler/internal/loop.go b/pkg/compiler/internal/loop.go new file mode 100644 index 00000000..374a707b --- /dev/null +++ b/pkg/compiler/internal/loop.go @@ -0,0 +1,80 @@ +package internal + +import "github.com/MontFerret/ferret/pkg/vm" + +type LoopType int + +const ( + NormalLoop LoopType = iota + PassThroughLoop + TemporalLoop +) + +type LoopKind int + +const ( + ForLoop LoopKind = iota + WhileLoop + DoWhileLoop +) + +type CollectorType int + +const ( + CollectorTypeCounter CollectorType = iota + CollectorTypeKey + CollectorTypeKeyCounter + CollectorTypeKeyGroup +) + +type Loop struct { + Type LoopType + Kind LoopKind + Distinct bool + Allocate bool + Jump int + JumpOffset int + + Src vm.Operand + Iterator vm.Operand + + ValueName string + Value vm.Operand + KeyName string + Key vm.Operand + + Result vm.Operand + ResultPos int +} + +func (l *Loop) DeclareKeyVar(name string, st *SymbolTable) { + if l.canBindVar(name) { + l.KeyName = name + l.Key = st.DeclareLocal(name) + } +} + +func (l *Loop) DeclareValueVar(name string, st *SymbolTable) { + if l.canBindVar(name) { + l.ValueName = name + l.Value = st.DeclareLocal(name) + } +} + +func (l *Loop) EmitValue(dst vm.Operand, emitter *Emitter) { + emitter.EmitIterValue(dst, l.Iterator) +} + +func (l *Loop) EmitKey(dst vm.Operand, emitter *Emitter) { + emitter.EmitIterKey(dst, l.Iterator) +} + +func (l *Loop) EmitFinalization(emitter *Emitter) { + emitter.EmitJump(l.Jump - l.JumpOffset) + emitter.EmitA(vm.OpClose, l.Iterator) + emitter.PatchJump(l.Jump) +} + +func (l *Loop) canBindVar(name string) bool { + return name != "" && name != ignorePseudoVariable +} diff --git a/pkg/compiler/internal/loop_compiler.go b/pkg/compiler/internal/loop_compiler.go new file mode 100644 index 00000000..8bb0ac2f --- /dev/null +++ b/pkg/compiler/internal/loop_compiler.go @@ -0,0 +1,336 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/parser/fql" + "github.com/MontFerret/ferret/pkg/runtime" + "github.com/MontFerret/ferret/pkg/vm" + "github.com/antlr4-go/antlr/v4" +) + +type LoopCompiler struct { + ctx *FuncContext +} + +func NewLoopCompiler(ctx *FuncContext) *LoopCompiler { + return &LoopCompiler{ctx: ctx} +} + +func (lc *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand { + var distinct bool + var returnRuleCtx antlr.RuleContext + var loopType LoopType + returnCtx := ctx.ForExpressionReturn() + + if c := returnCtx.ReturnExpression(); c != nil { + returnRuleCtx = c + distinct = c.Distinct() != nil + loopType = NormalLoop + } else if c := returnCtx.ForExpression(); c != nil { + returnRuleCtx = c + loopType = PassThroughLoop + } + + loop := lc.ctx.Loops.NewLoop(loopType, ForLoop, distinct) + lc.ctx.Symbols.EnterScope() + lc.ctx.Loops.Push(loop) + + if loop.Kind == ForLoop { + loop.Src = lc.CompileForExpressionSource(ctx.ForExpressionSource()) + + if val := ctx.GetValueVariable(); val != nil { + loop.DeclareValueVar(val.GetText(), lc.ctx.Symbols) + } + + if ctr := ctx.GetCounterVariable(); ctr != nil { + loop.DeclareKeyVar(ctr.GetText(), lc.ctx.Symbols) + } + } else { + } + + lc.emitLoopBegin(loop) + + // body + if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 { + for _, b := range body { + if c := b.ForExpressionStatement(); c != nil { + lc.CompileForExpressionStatement(c) + } else if c := b.ForExpressionClause(); c != nil { + lc.CompileForExpressionClause(c) + } + } + } + + loop = lc.ctx.Loops.Current() + + // RETURN + if loop.Type != PassThroughLoop { + c := returnRuleCtx.(*fql.ReturnExpressionContext) + expReg := lc.ctx.ExprCompiler.Compile(c.Expression()) + + lc.ctx.Emitter.EmitAB(vm.OpPush, loop.Result, expReg) + } else if returnRuleCtx != nil { + if c, ok := returnRuleCtx.(*fql.ForExpressionContext); ok { + lc.Compile(c) + } + } + + res := lc.emitLoopEnd(loop) + + lc.ctx.Symbols.ExitScope() + lc.ctx.Loops.Pop() + + return res +} + +func (lc *LoopCompiler) CompileForExpressionSource(ctx fql.IForExpressionSourceContext) vm.Operand { + if c := ctx.FunctionCallExpression(); c != nil { + return lc.ctx.ExprCompiler.CompileFunctionCallExpression(c) + } + + if c := ctx.MemberExpression(); c != nil { + return lc.ctx.ExprCompiler.CompileMemberExpression(c) + } + + if c := ctx.Variable(); c != nil { + return lc.ctx.ExprCompiler.CompileVariable(c) + } + + if c := ctx.Param(); c != nil { + return lc.ctx.ExprCompiler.CompileParam(c) + } + + if c := ctx.RangeOperator(); c != nil { + return lc.ctx.ExprCompiler.CompileRangeOperator(c) + } + + if c := ctx.ArrayLiteral(); c != nil { + return lc.ctx.LiteralCompiler.CompileArrayLiteral(c) + } + + if c := ctx.ObjectLiteral(); c != nil { + return lc.ctx.LiteralCompiler.CompileObjectLiteral(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (lc *LoopCompiler) CompileForExpressionStatement(ctx fql.IForExpressionStatementContext) { + if c := ctx.VariableDeclaration(); c != nil { + _ = lc.ctx.StmtCompiler.CompileVariableDeclaration(c) + } else if c := ctx.FunctionCallExpression(); c != nil { + _ = lc.ctx.ExprCompiler.CompileFunctionCallExpression(c) + + // TODO: Free register if needed + } +} + +func (lc *LoopCompiler) CompileForExpressionClause(ctx fql.IForExpressionClauseContext) { + if c := ctx.LimitClause(); c != nil { + lc.CompileLimitClause(c) + } else if c := ctx.FilterClause(); c != nil { + lc.CompileFilterClause(c) + } else if c := ctx.SortClause(); c != nil { + lc.CompileSortClause(c) + } else if c := ctx.CollectClause(); c != nil { + lc.CompileCollectClause(c) + } +} + +func (lc *LoopCompiler) CompileLimitClause(ctx fql.ILimitClauseContext) { + clauses := ctx.AllLimitClauseValue() + + if len(clauses) == 1 { + lc.CompileLimit(lc.CompileLimitClauseValue(clauses[0])) + } else { + lc.CompileOffset(lc.CompileLimitClauseValue(clauses[0])) + lc.CompileLimit(lc.CompileLimitClauseValue(clauses[1])) + } +} + +func (lc *LoopCompiler) CompileLimitClauseValue(ctx fql.ILimitClauseValueContext) vm.Operand { + if c := ctx.Param(); c != nil { + return lc.ctx.ExprCompiler.CompileParam(c) + } + + if c := ctx.IntegerLiteral(); c != nil { + return lc.ctx.LiteralCompiler.CompileIntegerLiteral(c) + } + + if c := ctx.Variable(); c != nil { + return lc.ctx.ExprCompiler.CompileVariable(c) + } + + if c := ctx.MemberExpression(); c != nil { + return lc.ctx.ExprCompiler.CompileMemberExpression(c) + } + + if c := ctx.FunctionCallExpression(); c != nil { + return lc.ctx.ExprCompiler.CompileFunctionCallExpression(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) + +} + +func (lc *LoopCompiler) CompileLimit(src vm.Operand) { + state := lc.ctx.Registers.Allocate(State) + lc.ctx.Emitter.EmitABx(vm.OpIterLimit, state, src, lc.ctx.Loops.Current().Jump) +} + +func (lc *LoopCompiler) CompileOffset(src vm.Operand) { + state := lc.ctx.Registers.Allocate(State) + lc.ctx.Emitter.EmitABx(vm.OpIterSkip, state, src, lc.ctx.Loops.Current().Jump) +} + +func (lc *LoopCompiler) CompileFilterClause(ctx fql.IFilterClauseContext) { + src := lc.ctx.ExprCompiler.Compile(ctx.Expression()) + lc.ctx.Emitter.EmitJumpIfFalse(src, lc.ctx.Loops.Current().Jump) +} + +func (lc *LoopCompiler) CompileSortClause(ctx fql.ISortClauseContext) { + loop := lc.ctx.Loops.Current() + + // We collect the sorting conditions (keys + // And wrap each loop element by a KeyValuePair + // Where a key is either a single value or a list of values + // These KeyValuePairs are then added to the dataset + kvKeyReg := lc.ctx.Registers.Allocate(Temp) + clauses := ctx.AllSortClauseExpression() + var directions []runtime.SortDirection + isSortMany := len(clauses) > 1 + + if isSortMany { + clausesRegs := make([]vm.Operand, len(clauses)) + directions = make([]runtime.SortDirection, len(clauses)) + // We create a sequence of Registers for the clauses + // To pack them into an array + keyRegs := lc.ctx.Registers.AllocateSequence(len(clauses)) + + for i, clause := range clauses { + clauseReg := lc.ctx.ExprCompiler.Compile(clause.Expression()) + lc.ctx.Emitter.EmitMove(keyRegs[i], clauseReg) + clausesRegs[i] = keyRegs[i] + directions[i] = sortDirection(clause.SortDirection()) + // TODO: Free Registers + } + + arrReg := lc.ctx.Registers.Allocate(Temp) + lc.ctx.Emitter.EmitAs(vm.OpList, arrReg, keyRegs) + lc.ctx.Emitter.EmitAB(vm.OpMove, kvKeyReg, arrReg) // TODO: Free Registers + } else { + clausesReg := lc.ctx.ExprCompiler.Compile(clauses[0].Expression()) + lc.ctx.Emitter.EmitAB(vm.OpMove, kvKeyReg, clausesReg) + } + + var kvValReg vm.Operand + + // In case the value is not used in the loop body, and only key is used + if loop.ValueName != "" { + kvValReg = loop.Value + } else { + // If so, we need to load it from the iterator + kvValReg = lc.ctx.Registers.Allocate(Temp) + loop.EmitValue(kvKeyReg, lc.ctx.Emitter) + } + + if isSortMany { + encoded := runtime.EncodeSortDirections(directions) + count := len(clauses) + + lc.ctx.Emitter.PatchSwapAxy(loop.ResultPos, vm.OpDataSetMultiSorter, loop.Result, encoded, count) + } else { + dir := sortDirection(clauses[0].SortDirection()) + lc.ctx.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetSorter, loop.Result, int(dir)) + } + + lc.ctx.Emitter.EmitABC(vm.OpPushKV, loop.Result, kvKeyReg, kvValReg) + loop.EmitFinalization(lc.ctx.Emitter) + + // Replace source with the Sorter + lc.ctx.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result) + + // Create a new loop + lc.emitLoopBegin(loop) +} + +func (lc *LoopCompiler) CompileCollectClause(ctx fql.ICollectClauseContext) { + lc.ctx.CollectCompiler.Compile(ctx) +} + +// emitIterValue emits an instruction to get the value from the iterator +func (lc *LoopCompiler) emitLoopBegin(loop *Loop) { + if loop.Allocate { + lc.ctx.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct) + loop.ResultPos = lc.ctx.Emitter.Size() - 1 + } + + loop.Iterator = lc.ctx.Registers.Allocate(State) + + if loop.Kind == ForLoop { + lc.ctx.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Src) + // jumpPlaceholder is a placeholder for the exit jump position + loop.Jump = lc.ctx.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator) + + if loop.Value != vm.NoopOperand { + lc.ctx.Emitter.EmitAB(vm.OpIterValue, loop.Value, loop.Iterator) + } + + if loop.Key != vm.NoopOperand { + lc.ctx.Emitter.EmitAB(vm.OpIterKey, loop.Key, loop.Iterator) + } + } else { + //counterReg := lc.ctx.Registers.Allocate(Storage) + // TODO: Set JumpOffset here + } +} + +// emitPatchLoop replaces the source of the loop with a modified dataset +func (lc *LoopCompiler) emitPatchLoop(loop *Loop) { + // Replace source with sorted array + lc.ctx.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result) + + lc.ctx.Symbols.ExitScope() + lc.ctx.Symbols.EnterScope() + + // Create new for loop + lc.emitLoopBegin(loop) +} + +func (lc *LoopCompiler) emitLoopEnd(loop *Loop) vm.Operand { + lc.ctx.Emitter.EmitJump(loop.Jump - loop.JumpOffset) + + // TODO: Do not allocate for pass-through Loops + dst := lc.ctx.Registers.Allocate(Temp) + + if loop.Allocate { + // TODO: Reuse the dsReg register + lc.ctx.Emitter.EmitA(vm.OpClose, loop.Iterator) + lc.ctx.Emitter.EmitAB(vm.OpMove, dst, loop.Result) + + if loop.Kind == ForLoop { + lc.ctx.Emitter.PatchJump(loop.Jump) + } else { + lc.ctx.Emitter.PatchJumpAB(loop.Jump) + } + } else { + if loop.Kind == ForLoop { + lc.ctx.Emitter.PatchJumpNext(loop.Jump) + } else { + lc.ctx.Emitter.PatchJumpNextAB(loop.Jump) + } + } + + return dst +} + +func (lc *LoopCompiler) loopKind(ctx *fql.ForExpressionContext) LoopKind { + if ctx.While() == nil { + return ForLoop + } + + if ctx.Do() == nil { + return WhileLoop + } + + return DoWhileLoop +} diff --git a/pkg/compiler/internal/loop_table.go b/pkg/compiler/internal/loop_table.go index b1d24d53..462c7399 100644 --- a/pkg/compiler/internal/loop_table.go +++ b/pkg/compiler/internal/loop_table.go @@ -7,61 +7,6 @@ import ( "github.com/MontFerret/ferret/pkg/vm" ) -type LoopType int - -const ( - NormalLoop LoopType = iota - PassThroughLoop - TemporalLoop -) - -type LoopKind int - -const ( - ForLoop LoopKind = iota - WhileLoop - DoWhileLoop -) - -type CollectorType int - -const ( - CollectorTypeCounter CollectorType = iota - CollectorTypeKey - CollectorTypeKeyCounter - CollectorTypeKeyGroup -) - -type Loop struct { - Type LoopType - Kind LoopKind - Distinct bool - Allocate bool - Jump int - JumpOffset int - - Src vm.Operand - Iterator vm.Operand - - ValueName string - Value vm.Operand - KeyName string - Key vm.Operand - - Result vm.Operand - ResultPos int -} - -func (l *Loop) BindKeyVar(name string, st *SymbolTable) { - l.KeyName = name - l.Key = st.DeclareLocal(name) -} - -func (l *Loop) BindValueVar(name string, st *SymbolTable) { - l.ValueName = name - l.Value = st.DeclareLocal(name) -} - type LoopTable struct { stack []*Loop registers *RegisterAllocator diff --git a/pkg/compiler/internal/stmt_compiler.go b/pkg/compiler/internal/stmt_compiler.go new file mode 100644 index 00000000..602bb644 --- /dev/null +++ b/pkg/compiler/internal/stmt_compiler.go @@ -0,0 +1,91 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/parser/fql" + "github.com/MontFerret/ferret/pkg/vm" +) + +type StmtCompiler struct { + ctx *FuncContext +} + +func NewStmtCompiler(ctx *FuncContext) *StmtCompiler { + return &StmtCompiler{ + ctx: ctx, + } +} + +func (sc *StmtCompiler) Compile(ctx fql.IBodyContext) { + for _, statement := range ctx.AllBodyStatement() { + sc.CompileBodyStatement(statement) + } +} + +func (sc *StmtCompiler) CompileBodyStatement(ctx fql.IBodyStatementContext) { + if c := ctx.VariableDeclaration(); c != nil { + sc.CompileVariableDeclaration(c) + } else if c := ctx.FunctionCallExpression(); c != nil { + sc.CompileFunctionCall(c) + } else if c := ctx.WaitForExpression(); c != nil { + sc.ctx.WaitCompiler.Compile(c) + } +} + +func (sc *StmtCompiler) CompileBodyExpression(ctx fql.IBodyExpressionContext) { + if c := ctx.ForExpression(); c != nil { + out := sc.ctx.LoopCompiler.Compile(c) + + if out != vm.NoopOperand { + sc.ctx.Emitter.EmitAB(vm.OpMove, vm.NoopOperand, out) + } + + sc.ctx.Emitter.Emit(vm.OpReturn) + } else if c := ctx.ReturnExpression(); c != nil { + valReg := sc.ctx.ExprCompiler.Compile(c.Expression()) + + if valReg.IsConstant() { + sc.ctx.Emitter.EmitAB(vm.OpLoadGlobal, vm.NoopOperand, valReg) + } else { + sc.ctx.Emitter.EmitMove(vm.NoopOperand, valReg) + } + + sc.ctx.Emitter.Emit(vm.OpReturn) + } +} + +func (sc *StmtCompiler) CompileVariableDeclaration(ctx fql.IVariableDeclarationContext) vm.Operand { + name := ignorePseudoVariable + + if id := ctx.Identifier(); id != nil { + name = id.GetText() + } else if reserved := ctx.SafeReservedWord(); reserved != nil { + name = reserved.GetText() + } + + src := sc.ctx.ExprCompiler.Compile(ctx.Expression()) + + if name != ignorePseudoVariable { + var dest vm.Operand + + if src.IsConstant() { + dest = sc.ctx.Symbols.DeclareGlobal(name) + tmp := sc.ctx.Registers.Allocate(Temp) + sc.ctx.Emitter.EmitAB(vm.OpLoadConst, tmp, src) + sc.ctx.Emitter.EmitAB(vm.OpStoreGlobal, dest, tmp) + } else if sc.ctx.Symbols.Scope() == 0 { + dest = sc.ctx.Symbols.DeclareGlobal(name) + sc.ctx.Emitter.EmitAB(vm.OpStoreGlobal, dest, src) + } else { + dest = sc.ctx.Symbols.DeclareLocal(name) + sc.ctx.Emitter.EmitAB(vm.OpMove, dest, src) + } + + return dest + } + + return vm.NoopOperand +} + +func (sc *StmtCompiler) CompileFunctionCall(ctx fql.IFunctionCallExpressionContext) vm.Operand { + return sc.ctx.ExprCompiler.CompileFunctionCallExpression(ctx) +} diff --git a/pkg/compiler/internal/visitor.go b/pkg/compiler/internal/visitor.go index 753a3414..054f13cb 100644 --- a/pkg/compiler/internal/visitor.go +++ b/pkg/compiler/internal/visitor.go @@ -1,26 +1,20 @@ package internal import ( - "regexp" - "strconv" - "strings" - "github.com/MontFerret/ferret/pkg/parser/fql" - "github.com/MontFerret/ferret/pkg/runtime" - "github.com/MontFerret/ferret/pkg/vm" - - "github.com/antlr4-go/antlr/v4" ) type Visitor struct { *fql.BaseFqlParserVisitor + ctx *FuncContext + Err error Src string Emitter *Emitter Registers *RegisterAllocator Symbols *SymbolTable Loops *LoopTable - CatchTable []vm.Catch + CatchTable *CatchStack } const ( @@ -28,25 +22,19 @@ const ( undefinedVariable = -1 ignorePseudoVariable = "_" pseudoVariable = "CURRENT" - forScope = "for" -) - -// Runtime functions -const ( - runtimeTypename = "TYPENAME" - runtimeLength = "LENGTH" - runtimeWait = "WAIT" ) func NewVisitor(src string) *Visitor { v := new(Visitor) v.BaseFqlParserVisitor = new(fql.BaseFqlParserVisitor) + v.ctx = NewFuncContext() + v.Src = src - v.Registers = NewRegisterAllocator() - v.Symbols = NewSymbolTable(v.Registers) - v.Loops = NewLoopTable(v.Registers) - v.Emitter = NewEmitter() - v.CatchTable = make([]vm.Catch, 0) + v.Registers = v.ctx.Registers + v.Symbols = v.ctx.Symbols + v.Loops = v.ctx.Loops + v.Emitter = v.ctx.Emitter + v.CatchTable = v.ctx.CatchTable return v } @@ -62,1689 +50,495 @@ func (v *Visitor) VisitProgram(ctx *fql.ProgramContext) interface{} { } func (v *Visitor) VisitBody(ctx *fql.BodyContext) interface{} { - for _, statement := range ctx.AllBodyStatement() { - statement.Accept(v) - } - - ctx.BodyExpression().Accept(v) + v.ctx.StmtCompiler.Compile(ctx) return nil } -func (v *Visitor) VisitBodyStatement(ctx *fql.BodyStatementContext) interface{} { - if c := ctx.VariableDeclaration(); c != nil { - return c.Accept(v) - } else if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } else if c := ctx.WaitForExpression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitBodyExpression(ctx *fql.BodyExpressionContext) interface{} { - if c := ctx.ForExpression(); c != nil { - out, ok := c.Accept(v).(vm.Operand) - - if ok && out != vm.NoopOperand { - v.Emitter.EmitAB(vm.OpMove, vm.NoopOperand, out) - } - - v.Emitter.Emit(vm.OpReturn) - - return out - } else if c := ctx.ReturnExpression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - func (v *Visitor) VisitHead(_ *fql.HeadContext) interface{} { return nil } -func (v *Visitor) VisitWaitForExpression(ctx *fql.WaitForExpressionContext) interface{} { - if ctx.Event() != nil { - return v.visitWaitForEventExpression(ctx) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) visitWaitForEventExpression(ctx *fql.WaitForExpressionContext) interface{} { - v.Symbols.EnterScope() - - srcReg := ctx.WaitForEventSource().Accept(v).(vm.Operand) - eventReg := ctx.WaitForEventName().Accept(v).(vm.Operand) - - var optsReg vm.Operand - - if opts := ctx.OptionsClause(); opts != nil { - optsReg = opts.Accept(v).(vm.Operand) - } - - var timeoutReg vm.Operand - - if timeout := ctx.TimeoutClause(); timeout != nil { - timeoutReg = timeout.Accept(v).(vm.Operand) - } - - streamReg := v.Registers.Allocate(Temp) - - // We move the source object to the stream register in order to re-use it in OpStream - v.Emitter.EmitAB(vm.OpMove, streamReg, srcReg) - v.Emitter.EmitABC(vm.OpStream, streamReg, eventReg, optsReg) - v.Emitter.EmitAB(vm.OpStreamIter, streamReg, timeoutReg) - - var valReg vm.Operand - - // Now we start iterating over the stream - jumpToNext := v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, streamReg) - - if filter := ctx.FilterClause(); filter != nil { - valReg = v.Symbols.DeclareLocal(pseudoVariable) - v.Emitter.EmitAB(vm.OpIterValue, valReg, streamReg) - - filter.Expression().Accept(v) - - v.Emitter.EmitJumpc(vm.OpJumpIfFalse, jumpToNext, valReg) - - // TODO: Do we need to use timeout here too? We can really get stuck in the loop if no event satisfies the filter - } - - // Clean up the stream - v.Emitter.EmitA(vm.OpClose, streamReg) - - v.Symbols.ExitScope() - - return nil -} - -func (v *Visitor) VisitWaitForEventName(ctx *fql.WaitForEventNameContext) interface{} { - if c := ctx.StringLiteral(); c != nil { - return c.Accept(v) - } - - if c := ctx.Variable(); c != nil { - return c.Accept(v) - } - - if c := ctx.Param(); c != nil { - return c.Accept(v) - } - - if c := ctx.MemberExpression(); c != nil { - return c.Accept(v) - } - - if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitWaitForEventSource(ctx *fql.WaitForEventSourceContext) interface{} { - if c := ctx.Variable(); c != nil { - return c.Accept(v) - } - - if c := ctx.MemberExpression(); c != nil { - return c.Accept(v) - } - - if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitTimeoutClauseContext(ctx *fql.TimeoutClauseContext) interface{} { - if c := ctx.IntegerLiteral(); c != nil { - return c.Accept(v) - } - - if c := ctx.Variable(); c != nil { - return c.Accept(v) - } - - if c := ctx.Param(); c != nil { - return c.Accept(v) - } - - if c := ctx.MemberExpression(); c != nil { - return c.Accept(v) - } - - if c := ctx.FunctionCall(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitOptionsClause(ctx *fql.OptionsClauseContext) interface{} { - if c := ctx.ObjectLiteral(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitForExpression(ctx *fql.ForExpressionContext) interface{} { - v.Symbols.EnterScope() - - var distinct bool - var returnRuleCtx antlr.RuleContext - returnCtx := ctx.ForExpressionReturn() - - if c := returnCtx.ReturnExpression(); c != nil { - returnRuleCtx = c - distinct = c.Distinct() != nil - } else if c := returnCtx.ForExpression(); c != nil { - returnRuleCtx = c - } - - loop := v.Loops.NewLoop(v.loopType(ctx), v.loopKind(ctx), distinct) - - if loop.Kind == ForLoop { - loop.Src = ctx.ForExpressionSource().Accept(v).(vm.Operand) - - if val := ctx.GetValueVariable(); val != nil { - if txt := val.GetText(); txt != "" && txt != ignorePseudoVariable { - loop.BindValueVar(txt, v.Symbols) - } - } - - if ctr := ctx.GetCounterVariable(); ctr != nil { - if txt := ctr.GetText(); txt != "" && txt != ignorePseudoVariable { - loop.BindKeyVar(txt, v.Symbols) - } - } - } else { - //srcExpr := ctx.Expression() - // - //// Create initial value for the loop counter - //v.Emitter.EmitA(runtime.OpWhileLoopPrep, counterReg) - //beforeExp := v.Emitter.Size() - //// Loop data source to iterate over - //cond := srcExpr.Accept(v).(runtime.Operand) - //jumpOffset = v.Emitter.Size() - beforeExp - // - //// 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) - } - - v.emitLoopBegin(loop) - - // body - if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 { - for _, b := range body { - b.Accept(v) - } - } - - loop = v.Loops.Current() - - // RETURN - if loop.Type != PassThroughLoop { - c := returnRuleCtx.(*fql.ReturnExpressionContext) - expReg := c.Expression().Accept(v).(vm.Operand) - - v.Emitter.EmitAB(vm.OpPush, loop.Result, expReg) - } else if returnRuleCtx != nil { - returnRuleCtx.Accept(v) - } - - res := v.emitLoopEnd(loop) - - v.Loops.Pop() - v.Symbols.ExitScope() - - return res -} - -func (v *Visitor) VisitForExpressionSource(ctx *fql.ForExpressionSourceContext) interface{} { - if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } - - if c := ctx.MemberExpression(); c != nil { - return c.Accept(v) - } - - if c := ctx.Variable(); c != nil { - return c.Accept(v) - } - - if c := ctx.Param(); c != nil { - return c.Accept(v) - } - - if c := ctx.RangeOperator(); c != nil { - return c.Accept(v) - } - - if c := ctx.ArrayLiteral(); c != nil { - return c.Accept(v) - } - - if c := ctx.ObjectLiteral(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitForExpressionBody(ctx *fql.ForExpressionBodyContext) interface{} { - if c := ctx.ForExpressionClause(); c != nil { - return c.Accept(v) - } - - if c := ctx.ForExpressionStatement(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitForExpressionClause(ctx *fql.ForExpressionClauseContext) interface{} { - if c := ctx.LimitClause(); c != nil { - return c.Accept(v) - } - - if c := ctx.FilterClause(); c != nil { - return c.Accept(v) - } - - if c := ctx.SortClause(); c != nil { - return c.Accept(v) - } - - if c := ctx.CollectClause(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitFilterClause(ctx *fql.FilterClauseContext) interface{} { - src1 := ctx.Expression().Accept(v).(vm.Operand) - v.Emitter.EmitJumpc(vm.OpJumpIfFalse, v.Loops.Current().Jump, src1) - - return nil -} - -func (v *Visitor) VisitLimitClause(ctx *fql.LimitClauseContext) interface{} { - clauses := ctx.AllLimitClauseValue() - - if len(clauses) == 1 { - return v.visitLimit(clauses[0].Accept(v).(vm.Operand)) - } else { - v.visitOffset(clauses[0].Accept(v).(vm.Operand)) - v.visitLimit(clauses[1].Accept(v).(vm.Operand)) - } - - return nil -} - -func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{} { - // TODO: Undefine original loop variables - loop := v.Loops.Current() - - // We collect the aggregation keys - // And wrap each loop element by a KeyValuePair - // Where a key is either a single value or a list of values - // These KeyValuePairs are then added to the dataset - var kvKeyReg, kvValReg vm.Operand - var groupSelectors []fql.ICollectSelectorContext - var isGrouping bool - grouping := ctx.CollectGrouping() - counter := ctx.CollectCounter() - aggregator := ctx.CollectAggregator() - - isCollecting := grouping != nil || counter != nil - - if isCollecting { - if grouping != nil { - isGrouping = true - groupSelectors = grouping.AllCollectSelector() - kvKeyReg = v.emitCollectGroupKeySelectors(groupSelectors) - } - - kvValReg = v.Registers.Allocate(Temp) - v.emitIterValue(loop, kvValReg) - - var projectionVariableName string - collectorType := CollectorTypeKey - - // If we have a collect group variable, we need to project it - if groupVar := ctx.CollectGroupVariable(); groupVar != nil { - // Projection can be either a default projection (identifier) or a custom projection (selector expression) - if identifier := groupVar.Identifier(); identifier != nil { - projectionVariableName = v.emitCollectDefaultGroupProjection(loop, kvValReg, identifier, groupVar.CollectGroupVariableKeeper()) - } else if selector := groupVar.CollectSelector(); selector != nil { - projectionVariableName = v.emitCollectCustomGroupProjection(loop, kvValReg, selector) - } - - collectorType = CollectorTypeKeyGroup - } else if counter != nil { - projectionVariableName = v.emitCollectCountProjection(loop, kvValReg, counter) - - if isGrouping { - collectorType = CollectorTypeKeyCounter - } else { - collectorType = CollectorTypeCounter - } - } - - // If we use aggregators, we need to collect group items by key - if aggregator != nil && collectorType != CollectorTypeKeyGroup { - // We need to patch the loop result to be a collector - collectorType = CollectorTypeKeyGroup - } - - // We replace DataSet initialization with Collector initialization - v.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetCollector, loop.Result, int(collectorType)) - v.Emitter.EmitABC(vm.OpPushKV, loop.Result, kvKeyReg, kvValReg) - v.emitIterJumpOrClose(loop) - - // Replace the source with the collector - v.emitPatchLoop(loop) - - // If the projection is used, we allocate a new register for the variable and put the iterator's value into it - if projectionVariableName != "" { - // Now we need to expand group variables from the dataset - v.emitIterKey(loop, kvValReg) - v.emitIterValue(loop, v.Symbols.DeclareLocal(projectionVariableName)) - } else { - v.emitIterKey(loop, kvKeyReg) - v.emitIterValue(loop, kvValReg) - } - } - - // Aggregation loop - if aggregator != nil { - v.emitCollectAggregator(aggregator, loop, isCollecting) - } - - // TODO: Reuse the Registers - v.Registers.Free(loop.Value) - v.Registers.Free(loop.Key) - loop.Value = vm.NoopOperand - loop.Key = vm.NoopOperand - - if isCollecting && isGrouping { - // Now we are defining new variables for the group selectors - v.emitCollectGroupKeySelectorVariables(groupSelectors, kvKeyReg, kvValReg, aggregator != nil) - } - - return nil -} - -func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentLoop *Loop, isCollected bool) { - var accums []vm.Operand - var loop *Loop - selectors := c.AllCollectAggregateSelector() - - // If data is collected, we need to allocate a temporary accumulators to store aggregation results - if isCollected { - // First of all, we allocate registers for accumulators - accums = make([]vm.Operand, len(selectors)) - - // We need to allocate a register for each accumulator - for i := 0; i < len(selectors); i++ { - reg := v.Registers.Allocate(Temp) - accums[i] = reg - // TODO: Select persistent List type, we do not know how many items we will have - v.Emitter.EmitA(vm.OpList, reg) - } - - loop = v.Loops.NewLoop(TemporalLoop, ForLoop, false) - - // Now we iterate over the grouped items - v.emitIterValue(parentLoop, loop.Iterator) - // We just re-use the same register - v.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Iterator) - // jumpPlaceholder is a placeholder for the exit aggrIterJump position - loop.Jump = v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator) - loop.ValueName = parentLoop.ValueName - } else { - loop = parentLoop - // Otherwise, we create a custom collector for aggregators - v.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetCollector, loop.Result, int(CollectorTypeKeyGroup)) - } - - // Store upper scope for aggregators - //mainScope := v.Symbols.Scope() - // Nested scope for aggregators - v.Symbols.EnterScope() - - aggrIterVal := v.Symbols.DeclareLocal(loop.ValueName) - v.Emitter.EmitAB(vm.OpIterValue, aggrIterVal, loop.Iterator) - - // Now we add value selectors to the accumulators - for i := 0; i < len(selectors); i++ { - selector := selectors[i] - fcx := selector.FunctionCallExpression() - args := fcx.FunctionCall().ArgumentList().AllExpression() - - if len(args) == 0 { - // TODO: Better error handling - panic("No arguments provided for the function call in the aggregate selector") - } - - if len(args) > 1 { - // TODO: Better error handling - panic("Too many arguments") - } - - resultReg := args[0].Accept(v).(vm.Operand) - - if isCollected { - v.Emitter.EmitAB(vm.OpPush, accums[i], resultReg) - } else { - aggrKeyName := selector.Identifier().GetText() - aggrKeyReg := v.loadConstant(runtime.String(aggrKeyName)) - v.Emitter.EmitABC(vm.OpPushKV, loop.Result, aggrKeyReg, resultReg) - v.Registers.Free(aggrKeyReg) - } - - v.Registers.Free(resultReg) - } - - // Now we can iterate over the grouped items - v.emitIterJumpOrClose(loop) - - // Now we can iterate over the selectors and execute the aggregation functions by passing the accumulators - // And define variables for each accumulator result - if isCollected { - for i, selector := range selectors { - fcx := selector.FunctionCallExpression() - // We won't make any checks here, as we already did it before - selectorVarName := selector.Identifier().GetText() - - // We execute the function call with the accumulator as an argument - accum := accums[i] - result := v.emitFunctionCall(fcx.FunctionCall(), fcx.ErrorOperator() != nil, RegisterSequence{accum}) - - // We define the variable for the selector result in the upper scope - // Since this temporary scope is only for aggregators and will be closed after the aggregation - varReg := v.Symbols.DeclareLocal(selectorVarName) - v.Emitter.EmitAB(vm.OpMove, varReg, result) - v.Registers.Free(result) - } - - v.Loops.Pop() - // Now close the aggregators scope - v.Symbols.ExitScope() - } else { - // Now close the aggregators scope - v.Symbols.ExitScope() - - parentLoop.ValueName = "" - parentLoop.KeyName = "" - - // Since we we in the middle of the loop, we need to patch the loop result - // Now we just create a range with 1 item to push the aggregated values to the dataset - // Replace source with sorted array - zero := v.loadConstant(runtime.Int(0)) - one := v.loadConstant(runtime.Int(1)) - aggregator := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpMove, aggregator, loop.Result) - v.Symbols.ExitScope() - - v.Symbols.EnterScope() - - // Create new for loop - v.Emitter.EmitABC(vm.OpRange, loop.Src, zero, one) - v.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct) - - // In case of non-collected aggregators, we just iterate over the grouped items - // Retrieve the grouped values by key, execute aggregation funcs and assign variable names to the results - for _, selector := range selectors { - fcx := selector.FunctionCallExpression() - // We won't make any checks here, as we already did it before - selectorVarName := selector.Identifier().GetText() - - // We execute the function call with the accumulator as an argument - key := v.loadConstant(runtime.String(selectorVarName)) - value := v.Registers.Allocate(Temp) - v.Emitter.EmitABC(vm.OpLoadKey, value, aggregator, key) - - result := v.emitFunctionCall(fcx.FunctionCall(), fcx.ErrorOperator() != nil, RegisterSequence{value}) - - // We define the variable for the selector result in the upper scope - // Since this temporary scope is only for aggregators and will be closed after the aggregation - varReg := v.Symbols.DeclareLocal(selectorVarName) - v.Emitter.EmitAB(vm.OpMove, varReg, result) - v.Registers.Free(result) - v.Registers.Free(value) - v.Registers.Free(key) - } - - v.Registers.Free(aggregator) - } - - // Free the registers for accumulators - for _, reg := range accums { - v.Registers.Free(reg) - } - - // Free the register for the iterator value - v.Registers.Free(aggrIterVal) -} - -func (v *Visitor) emitCollectGroupKeySelectors(selectors []fql.ICollectSelectorContext) vm.Operand { - if len(selectors) == 0 { - return vm.NoopOperand - } - - var kvKeyReg vm.Operand - - if len(selectors) > 1 { - // We create a sequence of Registers for the clauses - // To pack them into an array - selectorRegs := v.Registers.AllocateSequence(len(selectors)) - - for i, selector := range selectors { - reg := selector.Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, selectorRegs[i], reg) - // Free the register after moving its value to the sequence register - v.Registers.Free(reg) - } - - kvKeyReg = v.Registers.Allocate(Temp) - v.Emitter.EmitAs(vm.OpList, kvKeyReg, selectorRegs) - v.Registers.FreeSequence(selectorRegs) - } else { - kvKeyReg = selectors[0].Accept(v).(vm.Operand) - } - - return kvKeyReg -} - -func (v *Visitor) emitCollectGroupKeySelectorVariables(selectors []fql.ICollectSelectorContext, kvKeyReg, kvValReg vm.Operand, isAggregation bool) { - if len(selectors) > 1 { - variables := make([]vm.Operand, len(selectors)) - - for i, selector := range selectors { - name := selector.Identifier().GetText() - - if variables[i] == vm.NoopOperand { - variables[i] = v.Symbols.DeclareLocal(name) - } - - reg := kvValReg - - if isAggregation { - reg = kvKeyReg - } - - v.Emitter.EmitABC(vm.OpLoadIndex, variables[i], reg, v.loadConstant(runtime.Int(i))) - } - - // Free the register after moving its value to the variable - for _, reg := range variables { - v.Registers.Free(reg) - } - } else { - // Get the variable name - name := selectors[0].Identifier().GetText() - // Define a variable for each selector - varReg := v.Symbols.DeclareLocal(name) - - reg := kvValReg - - if isAggregation { - reg = kvKeyReg - } - - // If we have a single selector, we can just move the value - v.Emitter.EmitAB(vm.OpMove, varReg, reg) - } -} - -func (v *Visitor) emitCollectDefaultGroupProjection(loop *Loop, kvValReg vm.Operand, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string { - if keeper == nil { - seq := v.Registers.AllocateSequence(2) // Key and Value for Map - - // TODO: Review this. It's quite a questionable ArrangoDB feature of wrapping group items by a nested object - // We will keep it for now for backward compatibility. - v.loadConstantTo(runtime.String(loop.ValueName), seq[0]) // Map key - v.Emitter.EmitAB(vm.OpMove, seq[1], kvValReg) // Map value - v.Emitter.EmitAs(vm.OpMap, kvValReg, seq) - - v.Registers.FreeSequence(seq) - } else { - variables := keeper.AllIdentifier() - seq := v.Registers.AllocateSequence(len(variables) * 2) - - for i, j := 0, 0; i < len(variables); i, j = i+1, j+2 { - varName := variables[i].GetText() - v.loadConstantTo(runtime.String(varName), seq[j]) - - variable, _, found := v.Symbols.Resolve(varName) - - if !found { - panic("variable not found: " + varName) - } - - v.Emitter.EmitAB(vm.OpMove, seq[j+1], variable) - } - - v.Emitter.EmitAs(vm.OpMap, kvValReg, seq) - v.Registers.FreeSequence(seq) - } - - return identifier.GetText() -} - -func (v *Visitor) emitCollectCustomGroupProjection(_ *Loop, kvValReg vm.Operand, selector fql.ICollectSelectorContext) string { - selectorReg := selector.Expression().Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, kvValReg, selectorReg) - v.Registers.Free(selectorReg) - - return selector.Identifier().GetText() -} - -func (v *Visitor) emitCollectCountProjection(_ *Loop, _ vm.Operand, selector fql.ICollectCounterContext) string { - return selector.Identifier().GetText() -} - -func (v *Visitor) VisitCollectSelector(ctx *fql.CollectSelectorContext) interface{} { - if c := ctx.Expression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitSortClause(ctx *fql.SortClauseContext) interface{} { - loop := v.Loops.Current() - - // We collect the sorting conditions (keys - // And wrap each loop element by a KeyValuePair - // Where a key is either a single value or a list of values - // These KeyValuePairs are then added to the dataset - kvKeyReg := v.Registers.Allocate(Temp) - clauses := ctx.AllSortClauseExpression() - var directions []runtime.SortDirection - isSortMany := len(clauses) > 1 - - if isSortMany { - clausesRegs := make([]vm.Operand, len(clauses)) - directions = make([]runtime.SortDirection, len(clauses)) - // We create a sequence of Registers for the clauses - // To pack them into an array - keyRegs := v.Registers.AllocateSequence(len(clauses)) - - for i, clause := range clauses { - clauseReg := clause.Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, keyRegs[i], clauseReg) - clausesRegs[i] = keyRegs[i] - directions[i] = v.sortDirection(clause.SortDirection()) - // TODO: Free Registers - } - - arrReg := v.Registers.Allocate(Temp) - v.Emitter.EmitAs(vm.OpList, arrReg, keyRegs) - v.Emitter.EmitAB(vm.OpMove, kvKeyReg, arrReg) // TODO: Free Registers - } else { - clausesReg := clauses[0].Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, kvKeyReg, clausesReg) - } - - var kvValReg vm.Operand - - // In case the value is not used in the loop body, and only key is used - if loop.ValueName != "" { - kvValReg = loop.Value - } else { - // If so, we need to load it from the iterator - kvValReg = v.Registers.Allocate(Temp) - v.emitIterValue(loop, kvValReg) - } - - if isSortMany { - encoded := runtime.EncodeSortDirections(directions) - count := len(clauses) - - v.Emitter.PatchSwapAxy(loop.ResultPos, vm.OpDataSetMultiSorter, loop.Result, encoded, count) - } else { - dir := v.sortDirection(clauses[0].SortDirection()) - v.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetSorter, loop.Result, int(dir)) - } - - v.Emitter.EmitABC(vm.OpPushKV, loop.Result, kvKeyReg, kvValReg) - v.emitIterJumpOrClose(loop) - - // Replace source with the Sorter - v.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result) - - // Create a new loop - v.emitLoopBegin(loop) - - return nil -} - -func (v *Visitor) sortDirection(dir antlr.TerminalNode) runtime.SortDirection { - if dir == nil { - return runtime.SortDirectionAsc - } - - if strings.ToLower(dir.GetText()) == "desc" { - return runtime.SortDirectionDesc - } - - return runtime.SortDirectionAsc -} - -func (v *Visitor) VisitSortClauseExpression(ctx *fql.SortClauseExpressionContext) interface{} { - return ctx.Expression().Accept(v).(vm.Operand) -} - -func (v *Visitor) visitOffset(src1 vm.Operand) interface{} { - state := v.Registers.Allocate(State) - v.Emitter.EmitABx(vm.OpIterSkip, state, src1, v.Loops.Current().Jump) - - return state -} - -func (v *Visitor) visitLimit(src1 vm.Operand) interface{} { - state := v.Registers.Allocate(State) - v.Emitter.EmitABx(vm.OpIterLimit, state, src1, v.Loops.Current().Jump) - - return state -} - -func (v *Visitor) VisitLimitClauseValue(ctx *fql.LimitClauseValueContext) interface{} { - if c := ctx.IntegerLiteral(); c != nil { - return c.Accept(v) - } - - if c := ctx.Param(); c != nil { - return c.Accept(v) - } - - if c := ctx.Variable(); c != nil { - return c.Accept(v) - } - - if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } - - if c := ctx.MemberExpression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitForExpressionStatement(ctx *fql.ForExpressionStatementContext) interface{} { - if c := ctx.VariableDeclaration(); c != nil { - return c.Accept(v) - } - - if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitFunctionCallExpression(ctx *fql.FunctionCallExpressionContext) interface{} { - return v.visitFunctionCall(ctx.FunctionCall().(*fql.FunctionCallContext), ctx.ErrorOperator() != nil) -} - -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{} - - if c := mes.Variable(); c != nil { - mesOut = c.Accept(v) - } else if c := mes.Param(); c != nil { - mesOut = c.Accept(v) - } else if c := mes.ObjectLiteral(); c != nil { - mesOut = c.Accept(v) - } else if c := mes.ArrayLiteral(); c != nil { - mesOut = c.Accept(v) - } else if c := mes.FunctionCall(); c != nil { - // FOO()?.bar - segment := segments[0].(*fql.MemberExpressionPathContext) - mesOut = v.visitFunctionCall(c.(*fql.FunctionCallContext), segment.ErrorOperator() != nil) - } - - var dst vm.Operand - src1 := mesOut.(vm.Operand) - - for _, segment := range segments { - var out2 interface{} - p := segment.(*fql.MemberExpressionPathContext) - - if c := p.PropertyName(); c != nil { - out2 = c.Accept(v) - } else if c := p.ComputedPropertyName(); c != nil { - out2 = c.Accept(v) - } - - src2 := out2.(vm.Operand) - dst = v.Registers.Allocate(Temp) - - if p.ErrorOperator() != nil { - v.Emitter.EmitABC(vm.OpLoadPropertyOptional, dst, src1, src2) - } else { - v.Emitter.EmitABC(vm.OpLoadProperty, dst, src1, src2) - } - - src1 = dst - } - - return dst -} - -func (v *Visitor) VisitRangeOperator(ctx *fql.RangeOperatorContext) interface{} { - dst := v.Registers.Allocate(Temp) - start := ctx.GetLeft().Accept(v).(vm.Operand) - end := ctx.GetRight().Accept(v).(vm.Operand) - - v.Emitter.EmitABC(vm.OpRange, dst, start, end) - - return dst -} - -func (v *Visitor) VisitRangeOperand(ctx *fql.RangeOperandContext) interface{} { - if c := ctx.IntegerLiteral(); c != nil { - return c.Accept(v) - } - - if c := ctx.Variable(); c != nil { - return c.Accept(v) - } - - if c := ctx.Param(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitParam(ctx *fql.ParamContext) interface{} { - name := ctx.Identifier().GetText() - reg := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpLoadParam, reg, v.Symbols.BindParam(name)) - - return reg -} - -func (v *Visitor) VisitVariableDeclaration(ctx *fql.VariableDeclarationContext) interface{} { - name := ignorePseudoVariable - - if id := ctx.Identifier(); id != nil { - name = id.GetText() - } else if reserved := ctx.SafeReservedWord(); reserved != nil { - name = reserved.GetText() - } - - src := ctx.Expression().Accept(v).(vm.Operand) - - if name != ignorePseudoVariable { - var dest vm.Operand - - if src.IsConstant() { - dest = v.Symbols.DeclareGlobal(name) - tmp := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpLoadConst, tmp, src) - v.Emitter.EmitAB(vm.OpStoreGlobal, dest, tmp) - } else if v.Symbols.Scope() == 0 { - dest = v.Symbols.DeclareGlobal(name) - v.Emitter.EmitAB(vm.OpStoreGlobal, dest, src) - } else { - dest = v.Symbols.DeclareLocal(name) - v.Emitter.EmitAB(vm.OpMove, dest, src) - } - - return dest - } - - return vm.NoopOperand -} - -func (v *Visitor) VisitVariable(ctx *fql.VariableContext) interface{} { - // Just return the register / constant index - op, _, _ := v.Symbols.Resolve(ctx.GetText()) - - if op.IsRegister() { - return op - } - - reg := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpLoadGlobal, reg, op) - - return reg -} - -func (v *Visitor) VisitArrayLiteral(ctx *fql.ArrayLiteralContext) interface{} { - // Allocate destination register for the array - destReg := v.Registers.Allocate(Temp) - - 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 array elements - 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).(vm.Operand) - - // TODO: Figure out how to remove OpMove and use Registers returned from each expression - v.Emitter.EmitAB(vm.OpMove, seq[i], srcReg) - - // Free source register if temporary - if srcReg.IsRegister() { - //v.Registers.Free(srcReg) - } - } - - // Initialize an array - v.Emitter.EmitAs(vm.OpList, destReg, seq) - - // Free seq Registers - //v.Registers.FreeSequence(seq) - - return destReg - } - } - - // Empty array - v.Emitter.EmitA(vm.OpList, destReg) - - return destReg -} - -func (v *Visitor) VisitObjectLiteral(ctx *fql.ObjectLiteralContext) interface{} { - dst := v.Registers.Allocate(Temp) - assignments := ctx.AllPropertyAssignment() - size := len(assignments) - - if size == 0 { - v.Emitter.EmitA(vm.OpMap, dst) - - return dst - } - - seq := v.Registers.AllocateSequence(len(assignments) * 2) - - for i := 0; i < size; i++ { - var propOp vm.Operand - var valOp vm.Operand - pac := assignments[i].(*fql.PropertyAssignmentContext) - - if prop, ok := pac.PropertyName().(*fql.PropertyNameContext); ok { - propOp = prop.Accept(v).(vm.Operand) - valOp = pac.Expression().Accept(v).(vm.Operand) - } else if comProp, ok := pac.ComputedPropertyName().(*fql.ComputedPropertyNameContext); ok { - propOp = comProp.Accept(v).(vm.Operand) - valOp = pac.Expression().Accept(v).(vm.Operand) - } else if variable := pac.Variable(); variable != nil { - propOp = v.loadConstant(runtime.NewString(variable.GetText())) - valOp = variable.Accept(v).(vm.Operand) - } - - regIndex := i * 2 - - v.Emitter.EmitAB(vm.OpMove, seq[regIndex], propOp) - v.Emitter.EmitAB(vm.OpMove, seq[regIndex+1], valOp) - - // Free source register if temporary - if propOp.IsRegister() { - //v.Registers.Free(propOp) - } - } - - v.Emitter.EmitAs(vm.OpMap, dst, seq) - - return dst -} - -func (v *Visitor) VisitPropertyName(ctx *fql.PropertyNameContext) interface{} { - if str := ctx.StringLiteral(); str != nil { - return str.Accept(v) - } - - var name string - - if id := ctx.Identifier(); id != nil { - name = id.GetText() - } else if word := ctx.SafeReservedWord(); word != nil { - name = word.GetText() - } else if word := ctx.UnsafeReservedWord(); word != nil { - name = word.GetText() - } else { - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) - } - - return v.loadConstant(runtime.NewString(name)) -} - -func (v *Visitor) VisitComputedPropertyName(ctx *fql.ComputedPropertyNameContext) interface{} { - return ctx.Expression().Accept(v) -} - -func (v *Visitor) VisitStringLiteral(ctx *fql.StringLiteralContext) interface{} { - var b strings.Builder - - for _, child := range ctx.GetChildren() { - tree := child.(antlr.TerminalNode) - sym := tree.GetSymbol() - input := sym.GetInputStream() - - if input == nil { - continue - } - - size := input.Size() - // skip quotes - start := sym.GetStart() + 1 - stop := sym.GetStop() - 1 - - if stop >= size { - stop = size - 1 - } - - if start < size && stop < size { - for i := start; i <= stop; i++ { - c := input.GetText(i, i) - - switch c { - case "\\": - c2 := input.GetText(i, i+1) - - switch c2 { - case "\\n": - b.WriteString("\n") - case "\\t": - b.WriteString("\t") - default: - b.WriteString(c2) - } - - i++ - default: - b.WriteString(c) - } - } - } - } - - return v.loadConstant(runtime.NewString(b.String())) -} - -func (v *Visitor) VisitIntegerLiteral(ctx *fql.IntegerLiteralContext) interface{} { - val, err := strconv.Atoi(ctx.GetText()) - - if err != nil { - panic(err) - } - - reg := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpLoadConst, reg, v.Symbols.AddConstant(runtime.NewInt(val))) - - return reg -} - -func (v *Visitor) VisitFloatLiteral(ctx *fql.FloatLiteralContext) interface{} { - val, err := strconv.ParseFloat(ctx.GetText(), 64) - - if err != nil { - panic(err) - } - - reg := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpLoadConst, reg, v.Symbols.AddConstant(runtime.NewFloat(val))) - - return reg -} - -func (v *Visitor) VisitBooleanLiteral(ctx *fql.BooleanLiteralContext) interface{} { - reg := v.Registers.Allocate(Temp) - - switch strings.ToLower(ctx.GetText()) { - case "true": - v.Emitter.EmitAB(vm.OpLoadBool, reg, 1) - case "false": - v.Emitter.EmitAB(vm.OpLoadBool, reg, 0) - default: - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) - } - - return reg -} - -func (v *Visitor) VisitNoneLiteral(_ *fql.NoneLiteralContext) interface{} { - reg := v.Registers.Allocate(Temp) - v.Emitter.EmitA(vm.OpLoadNone, reg) - - return reg -} - -func (v *Visitor) VisitLiteral(ctx *fql.LiteralContext) interface{} { - if c := ctx.ArrayLiteral(); c != nil { - return c.Accept(v) - } else if c := ctx.ObjectLiteral(); c != nil { - return c.Accept(v) - } else if c := ctx.StringLiteral(); c != nil { - return c.Accept(v) - } else if c := ctx.IntegerLiteral(); c != nil { - return c.Accept(v) - } else if c := ctx.FloatLiteral(); c != nil { - return c.Accept(v) - } else if c := ctx.BooleanLiteral(); c != nil { - return c.Accept(v) - } else if c := ctx.NoneLiteral(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitReturnExpression(ctx *fql.ReturnExpressionContext) interface{} { - valReg := ctx.Expression().Accept(v).(vm.Operand) - - if valReg.IsConstant() { - v.Emitter.EmitAB(vm.OpLoadGlobal, vm.NoopOperand, valReg) - } else { - v.Emitter.EmitAB(vm.OpMove, vm.NoopOperand, valReg) - } - - v.Emitter.Emit(vm.OpReturn) - - return vm.NoopOperand -} - -func (v *Visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { - if uo := ctx.UnaryOperator(); uo != nil { - src := ctx.GetRight().Accept(v).(vm.Operand) - dst := v.Registers.Allocate(Temp) - - uoc := uo.(*fql.UnaryOperatorContext) - var op vm.Opcode - - if uoc.Not() != nil { - op = vm.OpNot - } else if uoc.Minus() != nil { - op = vm.OpFlipNegative - } else if uoc.Plus() != nil { - op = vm.OpFlipPositive - } else { - panic(runtime.Error(ErrUnexpectedToken, uoc.GetText())) - } - - // We do not overwrite the source register - v.Emitter.EmitAB(op, dst, src) - - return dst - } - - if op := ctx.LogicalAndOperator(); op != nil { - dst := v.Registers.Allocate(Temp) - // Execute left expression - left := ctx.GetLeft().Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, dst, left) - // Test if left is false and jump to the end - end := v.Emitter.EmitJumpc(vm.OpJumpIfFalse, jumpPlaceholder, dst) - // If left is true, execute right expression - right := ctx.GetRight().Accept(v).(vm.Operand) - // And move the result to the destination register - v.Emitter.EmitAB(vm.OpMove, dst, right) - v.Emitter.PatchJumpNext(end) - - return dst - } - - if op := ctx.LogicalOrOperator(); op != nil { - dst := v.Registers.Allocate(Temp) - // Execute left expression - left := ctx.GetLeft().Accept(v).(vm.Operand) - // Move the result to the destination register - v.Emitter.EmitAB(vm.OpMove, dst, left) - // Test if left is true and jump to the end - end := v.Emitter.EmitJumpc(vm.OpJumpIfTrue, jumpPlaceholder, dst) - // If left is false, execute right expression - right := ctx.GetRight().Accept(v).(vm.Operand) - // And move the result to the destination register - v.Emitter.EmitAB(vm.OpMove, dst, right) - v.Emitter.PatchJumpNext(end) - - return dst - } - - if op := ctx.GetTernaryOperator(); op != nil { - dst := v.Registers.Allocate(Temp) - - // Compile condition and put result in dst - condReg := ctx.GetCondition().Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, dst, condReg) - - // If condition was temporary, free it - if condReg.IsRegister() { - //v.Registers.Free(condReg) - } - - // Jump to 'false' branch if condition is false - otherwise := v.Emitter.EmitJumpc(vm.OpJumpIfFalse, jumpPlaceholder, dst) - - // True branch - if onTrue := ctx.GetOnTrue(); onTrue != nil { - trueReg := onTrue.Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, dst, trueReg) - - // Free temporary register if needed - if trueReg.IsRegister() { - //v.Registers.Free(trueReg) - } - } - - // Jump over false branch - end := v.Emitter.EmitJump(vm.OpJump, jumpPlaceholder) - v.Emitter.PatchJumpNext(otherwise) - - // False branch - if onFalse := ctx.GetOnFalse(); onFalse != nil { - falseReg := onFalse.Accept(v).(vm.Operand) - v.Emitter.EmitAB(vm.OpMove, dst, falseReg) - - // Free temporary register if needed - if falseReg.IsRegister() { - //v.Registers.Free(falseReg) - } - } - - v.Emitter.PatchJumpNext(end) - - return dst - } - - if c := ctx.Predicate(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) VisitPredicate(ctx *fql.PredicateContext) interface{} { - if c := ctx.ExpressionAtom(); c != nil { - startCatch := v.Emitter.Size() - reg := c.Accept(v) - - if c.ErrorOperator() != nil { - jump := -1 - endCatch := v.Emitter.Size() - - if c.ForExpression() != nil { - // We jump back to finalize the loop before exiting - jump = endCatch - 1 - } - - v.CatchTable = append(v.CatchTable, [3]int{startCatch, endCatch, jump}) - } - - return reg - } - - var opcode vm.Opcode - dest := v.Registers.Allocate(Temp) - left := ctx.Predicate(0).Accept(v).(vm.Operand) - right := ctx.Predicate(1).Accept(v).(vm.Operand) - - if op := ctx.EqualityOperator(); op != nil { - switch op.GetText() { - case "==": - opcode = vm.OpEq - case "!=": - opcode = vm.OpNeq - case ">": - opcode = vm.OpGt - case ">=": - opcode = vm.OpGte - case "<": - opcode = vm.OpLt - case "<=": - opcode = vm.OpLte - default: - panic(runtime.Error(ErrUnexpectedToken, op.GetText())) - } - } else if op := ctx.ArrayOperator(); op != nil { - // TODO: Implement me - panic(runtime.Error(runtime.ErrNotImplemented, "array operator")) - } else if op := ctx.InOperator(); op != nil { - if op.Not() == nil { - opcode = vm.OpIn - } else { - opcode = vm.OpNotIn - } - } else if op := ctx.LikeOperator(); op != nil { - if op.(*fql.LikeOperatorContext).Not() == nil { - opcode = vm.OpLike - } else { - opcode = vm.OpNotLike - } - } - - v.Emitter.EmitABC(opcode, dest, left, right) - - return dest -} - -func (v *Visitor) VisitExpressionAtom(ctx *fql.ExpressionAtomContext) interface{} { - var opcode vm.Opcode - var isSet bool - - if op := ctx.MultiplicativeOperator(); op != nil { - isSet = true - - switch op.GetText() { - case "*": - opcode = vm.OpMulti - case "/": - opcode = vm.OpDiv - case "%": - opcode = vm.OpMod - default: - panic(runtime.Error(ErrUnexpectedToken, op.GetText())) - } - } else if op := ctx.AdditiveOperator(); op != nil { - isSet = true - - switch op.GetText() { - case "+": - opcode = vm.OpAdd - case "-": - opcode = vm.OpSub - default: - panic(runtime.Error(ErrUnexpectedToken, op.GetText())) - } - - } else if op := ctx.RegexpOperator(); op != nil { - isSet = true - - switch op.GetText() { - case "=~": - opcode = vm.OpRegexpPositive - case "!~": - opcode = vm.OpRegexpNegative - default: - panic(runtime.Error(ErrUnexpectedToken, op.GetText())) - } - } - - if isSet { - regLeft := ctx.ExpressionAtom(0).Accept(v).(vm.Operand) - regRight := ctx.ExpressionAtom(1).Accept(v).(vm.Operand) - dst := v.Registers.Allocate(Temp) - - if opcode == vm.OpRegexpPositive || opcode == vm.OpRegexpNegative { - if regRight.IsConstant() { - val := v.Symbols.Constant(regRight) - - // Verify that the expression is a valid regular expression - regexp.MustCompile(val.String()) - } - } - - v.Emitter.EmitABC(opcode, dst, regLeft, regRight) - - return dst - } - - if c := ctx.FunctionCallExpression(); c != nil { - return c.Accept(v) - } else if c := ctx.RangeOperator(); c != nil { - return c.Accept(v) - } else if c := ctx.Literal(); c != nil { - return c.Accept(v) - } else if c := ctx.Variable(); c != nil { - return c.Accept(v) - } else if c := ctx.MemberExpression(); c != nil { - return c.Accept(v) - } else if c := ctx.Param(); c != nil { - return c.Accept(v) - } else if c := ctx.ForExpression(); c != nil { - return c.Accept(v) - } else if c := ctx.WaitForExpression(); c != nil { - return c.Accept(v) - } else if c := ctx.Expression(); c != nil { - return c.Accept(v) - } - - panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) -} - -func (v *Visitor) visitFunctionCall(ctx *fql.FunctionCallContext, protected bool) interface{} { - 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).(vm.Operand) - - // TODO: Figure out how to remove OpMove and use Registers returned from each expression - v.Emitter.EmitAB(vm.OpMove, seq[i], srcReg) - - // Free source register if temporary - if srcReg.IsRegister() { - //v.Registers.Free(srcReg) - } - } - } - } - - return v.emitFunctionCall(ctx, protected, seq) -} - -func (v *Visitor) emitFunctionCall(ctx fql.IFunctionCallContext, protected bool, seq RegisterSequence) vm.Operand { - name := v.functionName(ctx) - - switch name { - case runtimeLength: - dst := v.Registers.Allocate(Temp) - - if seq == nil || len(seq) != 1 { - panic(runtime.Error(runtime.ErrInvalidArgument, runtimeLength+": expected 1 argument")) - } - - v.Emitter.EmitAB(vm.OpLength, dst, seq[0]) - - return dst - case runtimeTypename: - dst := v.Registers.Allocate(Temp) - - if seq == nil || len(seq) != 1 { - panic(runtime.Error(runtime.ErrInvalidArgument, runtimeTypename+": expected 1 argument")) - } - - v.Emitter.EmitAB(vm.OpType, dst, seq[0]) - - return dst - case runtimeWait: - if len(seq) != 1 { - panic(runtime.Error(runtime.ErrInvalidArgument, runtimeWait+": expected 1 argument")) - } - - v.Emitter.EmitA(vm.OpSleep, seq[0]) - - return seq[0] - default: - nameAndDest := v.loadConstant(v.functionName(ctx)) - - if !protected { - v.Emitter.EmitAs(vm.OpCall, nameAndDest, seq) - } else { - v.Emitter.EmitAs(vm.OpProtectedCall, nameAndDest, seq) - } - - return nameAndDest - } -} - -func (v *Visitor) functionName(ctx fql.IFunctionCallContext) runtime.String { - var name string - funcNS := ctx.Namespace() - - if funcNS != nil { - name += funcNS.GetText() - } - - name += ctx.FunctionName().GetText() - - return runtime.NewString(strings.ToUpper(name)) -} - -// emitIterValue emits an instruction to get the value from the iterator -func (v *Visitor) emitLoopBegin(loop *Loop) { - if loop.Allocate { - v.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct) - loop.ResultPos = v.Emitter.Size() - 1 - } - - loop.Iterator = v.Registers.Allocate(State) - - if loop.Kind == ForLoop { - v.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Src) - // jumpPlaceholder is a placeholder for the exit jump position - loop.Jump = v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator) - - if loop.Value != vm.NoopOperand { - v.Emitter.EmitAB(vm.OpIterValue, loop.Value, loop.Iterator) - } - - if loop.Key != vm.NoopOperand { - v.Emitter.EmitAB(vm.OpIterKey, loop.Key, loop.Iterator) - } - } else { - //counterReg := v.Registers.Allocate(Storage) - // TODO: Set JumpOffset here - } -} - -// emitIterValue emits an instruction to get the value from the iterator -func (v *Visitor) emitIterValue(loop *Loop, reg vm.Operand) { - v.Emitter.EmitAB(vm.OpIterValue, reg, loop.Iterator) -} - -// emitIterKey emits an instruction to get the key from the iterator -func (v *Visitor) emitIterKey(loop *Loop, reg vm.Operand) { - v.Emitter.EmitAB(vm.OpIterKey, reg, loop.Iterator) -} - -// emitIterJumpOrClose emits an instruction to jump to the end of the loop or close the iterator -func (v *Visitor) emitIterJumpOrClose(loop *Loop) { - v.Emitter.EmitJump(vm.OpJump, loop.Jump-loop.JumpOffset) - v.Emitter.EmitA(vm.OpClose, loop.Iterator) - - if loop.Kind == ForLoop { - v.Emitter.PatchJump(loop.Jump) - } else { - v.Emitter.PatchJumpAB(loop.Jump) - } -} - -// emitPatchLoop replaces the source of the loop with a modified dataset -func (v *Visitor) emitPatchLoop(loop *Loop) { - // Replace source with sorted array - v.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result) - - v.Symbols.ExitScope() - v.Symbols.EnterScope() - - // Create new for loop - v.emitLoopBegin(loop) -} - -func (v *Visitor) emitLoopEnd(loop *Loop) vm.Operand { - v.Emitter.EmitJump(vm.OpJump, loop.Jump-loop.JumpOffset) - - // TODO: Do not allocate for pass-through Loops - dst := v.Registers.Allocate(Temp) - - if loop.Allocate { - // TODO: Reuse the dsReg register - v.Emitter.EmitA(vm.OpClose, loop.Iterator) - v.Emitter.EmitAB(vm.OpMove, dst, loop.Result) - - if loop.Kind == ForLoop { - v.Emitter.PatchJump(loop.Jump) - } else { - v.Emitter.PatchJumpAB(loop.Jump) - } - } else { - if loop.Kind == ForLoop { - v.Emitter.PatchJumpNext(loop.Jump) - } else { - v.Emitter.PatchJumpNextAB(loop.Jump) - } - } - - return dst -} - -func (v *Visitor) loopType(ctx *fql.ForExpressionContext) LoopType { - if c := ctx.ForExpressionReturn().ForExpression(); c == nil { - return NormalLoop - } - - return PassThroughLoop -} - -func (v *Visitor) loopKind(ctx *fql.ForExpressionContext) LoopKind { - if ctx.While() == nil { - return ForLoop - } - - if ctx.Do() == nil { - return WhileLoop - } - - return DoWhileLoop -} - -func (v *Visitor) loadConstant(constant runtime.Value) vm.Operand { - reg := v.Registers.Allocate(Temp) - v.Emitter.EmitAB(vm.OpLoadConst, reg, v.Symbols.AddConstant(constant)) - return reg -} - -func (v *Visitor) loadConstantTo(constant runtime.Value, reg vm.Operand) { - v.Emitter.EmitAB(vm.OpLoadConst, reg, v.Symbols.AddConstant(constant)) -} +//func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{} { +// // TODO: Undefine original loop variables +// loop := v.Loops.Current() +// +// // We collect the aggregation keys +// // And wrap each loop element by a KeyValuePair +// // Where a key is either a single value or a list of values +// // These KeyValuePairs are then added to the dataset +// var kvKeyReg, kvValReg vm.Operand +// var groupSelectors []fql.ICollectSelectorContext +// var isGrouping bool +// grouping := ctx.CollectGrouping() +// counter := ctx.CollectCounter() +// aggregator := ctx.CollectAggregator() +// +// isCollecting := grouping != nil || counter != nil +// +// if isCollecting { +// if grouping != nil { +// isGrouping = true +// groupSelectors = grouping.AllCollectSelector() +// kvKeyReg = v.emitCollectGroupKeySelectors(groupSelectors) +// } +// +// kvValReg = v.Registers.Allocate(Temp) +// v.emitIterValue(loop, kvValReg) +// +// var projectionVariableName string +// collectorType := CollectorTypeKey +// +// // If we have a collect group variable, we need to project it +// if groupVar := ctx.CollectGroupVariable(); groupVar != nil { +// // Projection can be either a default projection (identifier) or a custom projection (selector expression) +// if identifier := groupVar.Identifier(); identifier != nil { +// projectionVariableName = v.emitCollectDefaultGroupProjection(loop, kvValReg, identifier, groupVar.CollectGroupVariableKeeper()) +// } else if selector := groupVar.CollectSelector(); selector != nil { +// projectionVariableName = v.emitCollectCustomGroupProjection(loop, kvValReg, selector) +// } +// +// collectorType = CollectorTypeKeyGroup +// } else if counter != nil { +// projectionVariableName = v.emitCollectCountProjection(loop, kvValReg, counter) +// +// if isGrouping { +// collectorType = CollectorTypeKeyCounter +// } else { +// collectorType = CollectorTypeCounter +// } +// } +// +// // If we use aggregators, we need to collect group items by key +// if aggregator != nil && collectorType != CollectorTypeKeyGroup { +// // We need to patch the loop result to be a collector +// collectorType = CollectorTypeKeyGroup +// } +// +// // We replace DataSet initialization with Collector initialization +// v.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetCollector, loop.Result, int(collectorType)) +// v.Emitter.EmitABC(vm.OpPushKV, loop.Result, kvKeyReg, kvValReg) +// v.emitIterJumpOrClose(loop) +// +// // Replace the source with the collector +// v.emitPatchLoop(loop) +// +// // If the projection is used, we allocate a new register for the variable and put the iterator's value into it +// if projectionVariableName != "" { +// // Now we need to expand group variables from the dataset +// v.emitIterKey(loop, kvValReg) +// v.emitIterValue(loop, v.Symbols.DeclareLocal(projectionVariableName)) +// } else { +// v.emitIterKey(loop, kvKeyReg) +// v.emitIterValue(loop, kvValReg) +// } +// } +// +// // Aggregation loop +// if aggregator != nil { +// v.emitCollectAggregator(aggregator, loop, isCollecting) +// } +// +// // TODO: Reuse the Registers +// v.Registers.Free(loop.Value) +// v.Registers.Free(loop.Key) +// loop.Value = vm.NoopOperand +// loop.Key = vm.NoopOperand +// +// if isCollecting && isGrouping { +// // Now we are defining new variables for the group selectors +// v.emitCollectGroupKeySelectorVariables(groupSelectors, kvKeyReg, kvValReg, aggregator != nil) +// } +// +// return nil +//} +// +//func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentLoop *Loop, isCollected bool) { +// var accums []vm.Operand +// var loop *Loop +// selectors := c.AllCollectAggregateSelector() +// +// // If data is collected, we need to allocate a temporary accumulators to store aggregation results +// if isCollected { +// // First of all, we allocate registers for accumulators +// accums = make([]vm.Operand, len(selectors)) +// +// // We need to allocate a register for each accumulator +// for i := 0; i < len(selectors); i++ { +// reg := v.Registers.Allocate(Temp) +// accums[i] = reg +// // TODO: Select persistent List type, we do not know how many items we will have +// v.Emitter.EmitA(vm.OpList, reg) +// } +// +// loop = v.Loops.NewLoop(TemporalLoop, ForLoop, false) +// +// // Now we iterate over the grouped items +// v.emitIterValue(parentLoop, loop.Iterator) +// // We just re-use the same register +// v.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Iterator) +// // jumpPlaceholder is a placeholder for the exit aggrIterJump position +// loop.Jump = v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator) +// loop.ValueName = parentLoop.ValueName +// } else { +// loop = parentLoop +// // Otherwise, we create a custom collector for aggregators +// v.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetCollector, loop.Result, int(CollectorTypeKeyGroup)) +// } +// +// // Store upper scope for aggregators +// //mainScope := v.Symbols.Scope() +// // Nested scope for aggregators +// v.Symbols.EnterScope() +// +// aggrIterVal := v.Symbols.DeclareLocal(loop.ValueName) +// v.Emitter.EmitAB(vm.OpIterValue, aggrIterVal, loop.Iterator) +// +// // Now we add value selectors to the accumulators +// for i := 0; i < len(selectors); i++ { +// selector := selectors[i] +// fcx := selector.FunctionCallExpression() +// args := fcx.FunctionCall().ArgumentList().AllExpression() +// +// if len(args) == 0 { +// // TODO: Better error handling +// panic("No arguments provided for the function call in the aggregate selector") +// } +// +// if len(args) > 1 { +// // TODO: Better error handling +// panic("Too many arguments") +// } +// +// resultReg := args[0].Accept(v).(vm.Operand) +// +// if isCollected { +// v.Emitter.EmitAB(vm.OpPush, accums[i], resultReg) +// } else { +// aggrKeyName := selector.Identifier().GetText() +// aggrKeyReg := v.loadConstant(runtime.String(aggrKeyName)) +// v.Emitter.EmitABC(vm.OpPushKV, loop.Result, aggrKeyReg, resultReg) +// v.Registers.Free(aggrKeyReg) +// } +// +// v.Registers.Free(resultReg) +// } +// +// // Now we can iterate over the grouped items +// v.emitIterJumpOrClose(loop) +// +// // Now we can iterate over the selectors and execute the aggregation functions by passing the accumulators +// // And define variables for each accumulator result +// if isCollected { +// for i, selector := range selectors { +// fcx := selector.FunctionCallExpression() +// // We won't make any checks here, as we already did it before +// selectorVarName := selector.Identifier().GetText() +// +// // We execute the function call with the accumulator as an argument +// accum := accums[i] +// result := v.emitFunctionCall(fcx.FunctionCall(), fcx.ErrorOperator() != nil, RegisterSequence{accum}) +// +// // We define the variable for the selector result in the upper scope +// // Since this temporary scope is only for aggregators and will be closed after the aggregation +// varReg := v.Symbols.DeclareLocal(selectorVarName) +// v.Emitter.EmitAB(vm.OpMove, varReg, result) +// v.Registers.Free(result) +// } +// +// v.Loops.Pop() +// // Now close the aggregators scope +// v.Symbols.ExitScope() +// } else { +// // Now close the aggregators scope +// v.Symbols.ExitScope() +// +// parentLoop.ValueName = "" +// parentLoop.KeyName = "" +// +// // Since we we in the middle of the loop, we need to patch the loop result +// // Now we just create a range with 1 item to push the aggregated values to the dataset +// // Replace source with sorted array +// zero := v.loadConstant(runtime.Int(0)) +// one := v.loadConstant(runtime.Int(1)) +// aggregator := v.Registers.Allocate(Temp) +// v.Emitter.EmitAB(vm.OpMove, aggregator, loop.Result) +// v.Symbols.ExitScope() +// +// v.Symbols.EnterScope() +// +// // Create new for loop +// v.Emitter.EmitABC(vm.OpRange, loop.Src, zero, one) +// v.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct) +// +// // In case of non-collected aggregators, we just iterate over the grouped items +// // Retrieve the grouped values by key, execute aggregation funcs and assign variable names to the results +// for _, selector := range selectors { +// fcx := selector.FunctionCallExpression() +// // We won't make any checks here, as we already did it before +// selectorVarName := selector.Identifier().GetText() +// +// // We execute the function call with the accumulator as an argument +// key := v.loadConstant(runtime.String(selectorVarName)) +// value := v.Registers.Allocate(Temp) +// v.Emitter.EmitABC(vm.OpLoadKey, value, aggregator, key) +// +// result := v.emitFunctionCall(fcx.FunctionCall(), fcx.ErrorOperator() != nil, RegisterSequence{value}) +// +// // We define the variable for the selector result in the upper scope +// // Since this temporary scope is only for aggregators and will be closed after the aggregation +// varReg := v.Symbols.DeclareLocal(selectorVarName) +// v.Emitter.EmitAB(vm.OpMove, varReg, result) +// v.Registers.Free(result) +// v.Registers.Free(value) +// v.Registers.Free(key) +// } +// +// v.Registers.Free(aggregator) +// } +// +// // Free the registers for accumulators +// for _, reg := range accums { +// v.Registers.Free(reg) +// } +// +// // Free the register for the iterator value +// v.Registers.Free(aggrIterVal) +//} +// +//func (v *Visitor) emitCollectGroupKeySelectors(selectors []fql.ICollectSelectorContext) vm.Operand { +// if len(selectors) == 0 { +// return vm.NoopOperand +// } +// +// var kvKeyReg vm.Operand +// +// if len(selectors) > 1 { +// // We create a sequence of Registers for the clauses +// // To pack them into an array +// selectorRegs := v.Registers.AllocateSequence(len(selectors)) +// +// for i, selector := range selectors { +// reg := selector.Accept(v).(vm.Operand) +// v.Emitter.EmitAB(vm.OpMove, selectorRegs[i], reg) +// // Free the register after moving its value to the sequence register +// v.Registers.Free(reg) +// } +// +// kvKeyReg = v.Registers.Allocate(Temp) +// v.Emitter.EmitAs(vm.OpList, kvKeyReg, selectorRegs) +// v.Registers.FreeSequence(selectorRegs) +// } else { +// kvKeyReg = selectors[0].Accept(v).(vm.Operand) +// } +// +// return kvKeyReg +//} +// +//func (v *Visitor) emitCollectGroupKeySelectorVariables(selectors []fql.ICollectSelectorContext, kvKeyReg, kvValReg vm.Operand, isAggregation bool) { +// if len(selectors) > 1 { +// variables := make([]vm.Operand, len(selectors)) +// +// for i, selector := range selectors { +// name := selector.Identifier().GetText() +// +// if variables[i] == vm.NoopOperand { +// variables[i] = v.Symbols.DeclareLocal(name) +// } +// +// reg := kvValReg +// +// if isAggregation { +// reg = kvKeyReg +// } +// +// v.Emitter.EmitABC(vm.OpLoadIndex, variables[i], reg, v.loadConstant(runtime.Int(i))) +// } +// +// // Free the register after moving its value to the variable +// for _, reg := range variables { +// v.Registers.Free(reg) +// } +// } else { +// // Get the variable name +// name := selectors[0].Identifier().GetText() +// // Define a variable for each selector +// varReg := v.Symbols.DeclareLocal(name) +// +// reg := kvValReg +// +// if isAggregation { +// reg = kvKeyReg +// } +// +// // If we have a single selector, we can just move the value +// v.Emitter.EmitAB(vm.OpMove, varReg, reg) +// } +//} +// +//func (v *Visitor) emitCollectDefaultGroupProjection(loop *Loop, kvValReg vm.Operand, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string { +// if keeper == nil { +// seq := v.Registers.AllocateSequence(2) // Key and Value for Map +// +// // TODO: Review this. It's quite a questionable ArrangoDB feature of wrapping group items by a nested object +// // We will keep it for now for backward compatibility. +// v.loadConstantTo(runtime.String(loop.ValueName), seq[0]) // Map key +// v.Emitter.EmitAB(vm.OpMove, seq[1], kvValReg) // Map value +// v.Emitter.EmitAs(vm.OpMap, kvValReg, seq) +// +// v.Registers.FreeSequence(seq) +// } else { +// variables := keeper.AllIdentifier() +// seq := v.Registers.AllocateSequence(len(variables) * 2) +// +// for i, j := 0, 0; i < len(variables); i, j = i+1, j+2 { +// varName := variables[i].GetText() +// v.loadConstantTo(runtime.String(varName), seq[j]) +// +// variable, _, found := v.Symbols.Resolve(varName) +// +// if !found { +// panic("variable not found: " + varName) +// } +// +// v.Emitter.EmitAB(vm.OpMove, seq[j+1], variable) +// } +// +// v.Emitter.EmitAs(vm.OpMap, kvValReg, seq) +// v.Registers.FreeSequence(seq) +// } +// +// return identifier.GetText() +//} +// +//func (v *Visitor) emitCollectCustomGroupProjection(_ *Loop, kvValReg vm.Operand, selector fql.ICollectSelectorContext) string { +// selectorReg := selector.Expression().Accept(v).(vm.Operand) +// v.Emitter.EmitAB(vm.OpMove, kvValReg, selectorReg) +// v.Registers.Free(selectorReg) +// +// return selector.Identifier().GetText() +//} +// +//func (v *Visitor) emitCollectCountProjection(_ *Loop, _ vm.Operand, selector fql.ICollectCounterContext) string { +// return selector.Identifier().GetText() +//} +// +//func (v *Visitor) VisitCollectSelector(ctx *fql.CollectSelectorContext) interface{} { +// if c := ctx.Expression(); c != nil { +// return c.Accept(v) +// } +// +// panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +//} +// +//func (v *Visitor) VisitForExpressionStatement(ctx *fql.ForExpressionStatementContext) interface{} { +// if c := ctx.VariableDeclaration(); c != nil { +// return c.Accept(v) +// } +// +// if c := ctx.FunctionCallExpression(); c != nil { +// return c.Accept(v) +// } +// +// panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +//} +// +//func (v *Visitor) VisitExpression(ctx *fql.ExpressionContext) interface{} { +// return v.ctx.ExprCompiler.Compile(ctx) +//} +// +//// emitIterValue emits an instruction to get the value from the iterator +//func (v *Visitor) emitLoopBegin(loop *Loop) { +// if loop.Allocate { +// v.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct) +// loop.ResultPos = v.Emitter.Size() - 1 +// } +// +// loop.Iterator = v.Registers.Allocate(State) +// +// if loop.Kind == ForLoop { +// v.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Src) +// // jumpPlaceholder is a placeholder for the exit jump position +// loop.Jump = v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator) +// +// if loop.Value != vm.NoopOperand { +// v.Emitter.EmitAB(vm.OpIterValue, loop.Value, loop.Iterator) +// } +// +// if loop.Key != vm.NoopOperand { +// v.Emitter.EmitAB(vm.OpIterKey, loop.Key, loop.Iterator) +// } +// } else { +// //counterReg := v.Registers.Allocate(Storage) +// // TODO: Set JumpOffset here +// } +//} +// +//// emitIterValue emits an instruction to get the value from the iterator +//func (v *Visitor) emitIterValue(loop *Loop, reg vm.Operand) { +// v.Emitter.EmitAB(vm.OpIterValue, reg, loop.Iterator) +//} +// +//// emitIterKey emits an instruction to get the key from the iterator +//func (v *Visitor) emitIterKey(loop *Loop, reg vm.Operand) { +// v.Emitter.EmitAB(vm.OpIterKey, reg, loop.Iterator) +//} +// +//// emitIterJumpOrClose emits an instruction to jump to the end of the loop or close the iterator +//func (v *Visitor) emitIterJumpOrClose(loop *Loop) { +// v.Emitter.EmitJump(loop.Jump - loop.JumpOffset) +// v.Emitter.EmitA(vm.OpClose, loop.Iterator) +// +// if loop.Kind == ForLoop { +// v.Emitter.PatchJump(loop.Jump) +// } else { +// v.Emitter.PatchJumpAB(loop.Jump) +// } +//} +// +//// emitPatchLoop replaces the source of the loop with a modified dataset +//func (v *Visitor) emitPatchLoop(loop *Loop) { +// // Replace source with sorted array +// v.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result) +// +// v.Symbols.ExitScope() +// v.Symbols.EnterScope() +// +// // Create new for loop +// v.emitLoopBegin(loop) +//} +// +//func (v *Visitor) emitLoopEnd(loop *Loop) vm.Operand { +// v.Emitter.EmitJump(loop.Jump - loop.JumpOffset) +// +// // TODO: Do not allocate for pass-through Loops +// dst := v.Registers.Allocate(Temp) +// +// if loop.Allocate { +// // TODO: Reuse the dsReg register +// v.Emitter.EmitA(vm.OpClose, loop.Iterator) +// v.Emitter.EmitAB(vm.OpMove, dst, loop.Result) +// +// if loop.Kind == ForLoop { +// v.Emitter.PatchJump(loop.Jump) +// } else { +// v.Emitter.PatchJumpAB(loop.Jump) +// } +// } else { +// if loop.Kind == ForLoop { +// v.Emitter.PatchJumpNext(loop.Jump) +// } else { +// v.Emitter.PatchJumpNextAB(loop.Jump) +// } +// } +// +// return dst +//} +// +//func (v *Visitor) loadConstant(constant runtime.Value) vm.Operand { +// return loadConstant(v.ctx, constant) +//} +// +//func (v *Visitor) loadConstantTo(constant runtime.Value, reg vm.Operand) { +// v.Emitter.EmitAB(vm.OpLoadConst, reg, v.Symbols.AddConstant(constant)) +//} diff --git a/pkg/compiler/internal/wait_compiler.go b/pkg/compiler/internal/wait_compiler.go new file mode 100644 index 00000000..49427760 --- /dev/null +++ b/pkg/compiler/internal/wait_compiler.go @@ -0,0 +1,138 @@ +package internal + +import ( + "github.com/MontFerret/ferret/pkg/parser/fql" + "github.com/MontFerret/ferret/pkg/runtime" + "github.com/MontFerret/ferret/pkg/vm" +) + +type WaitCompiler struct { + ctx *FuncContext +} + +func NewWaitCompiler(ctx *FuncContext) *WaitCompiler { + return &WaitCompiler{ + ctx: ctx, + } +} + +func (wc *WaitCompiler) Compile(ctx fql.IWaitForExpressionContext) vm.Operand { + wc.ctx.Symbols.EnterScope() + + srcReg := wc.CompileWaitForEventSource(ctx.WaitForEventSource()) + eventReg := wc.CompileWaitForEventName(ctx.WaitForEventName()) + + var optsReg vm.Operand + + if opts := ctx.OptionsClause(); opts != nil { + optsReg = wc.CompileOptionsClause(opts) + } + + var timeoutReg vm.Operand + + if timeout := ctx.TimeoutClause(); timeout != nil { + timeoutReg = wc.CompileTimeoutClauseContext(timeout) + } + + streamReg := wc.ctx.Registers.Allocate(Temp) + + // We move the source object to the stream register in order to re-use it in OpStream + wc.ctx.Emitter.EmitMove(streamReg, srcReg) + wc.ctx.Emitter.EmitABC(vm.OpStream, streamReg, eventReg, optsReg) + wc.ctx.Emitter.EmitAB(vm.OpStreamIter, streamReg, timeoutReg) + + var valReg vm.Operand + + // Now we start iterating over the stream + jumpToNext := wc.ctx.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, streamReg) + + if filter := ctx.FilterClause(); filter != nil { + valReg = wc.ctx.Symbols.DeclareLocal(pseudoVariable) + wc.ctx.Emitter.EmitAB(vm.OpIterValue, valReg, streamReg) + + wc.ctx.ExprCompiler.Compile(filter.Expression()) + + wc.ctx.Emitter.EmitJumpc(vm.OpJumpIfFalse, jumpToNext, valReg) + + // TODO: Do we need to use timeout here too? We can really get stuck in the loop if no event satisfies the filter + } + + // Clean up the stream + wc.ctx.Emitter.EmitA(vm.OpClose, streamReg) + + wc.ctx.Symbols.ExitScope() + + return vm.NoopOperand +} + +func (wc *WaitCompiler) CompileWaitForEventName(ctx fql.IWaitForEventNameContext) vm.Operand { + if c := ctx.StringLiteral(); c != nil { + return wc.ctx.LiteralCompiler.CompileStringLiteral(c) + } + + if c := ctx.Variable(); c != nil { + return wc.ctx.ExprCompiler.CompileVariable(c) + } + + if c := ctx.Param(); c != nil { + return wc.ctx.ExprCompiler.CompileParam(c) + } + + if c := ctx.MemberExpression(); c != nil { + return wc.ctx.ExprCompiler.CompileMemberExpression(c) + } + + if c := ctx.FunctionCallExpression(); c != nil { + return wc.ctx.ExprCompiler.CompileFunctionCallExpression(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (wc *WaitCompiler) CompileWaitForEventSource(ctx fql.IWaitForEventSourceContext) vm.Operand { + if c := ctx.Variable(); c != nil { + return wc.ctx.ExprCompiler.CompileVariable(c) + } + + if c := ctx.MemberExpression(); c != nil { + return wc.ctx.ExprCompiler.CompileMemberExpression(c) + } + + if c := ctx.FunctionCallExpression(); c != nil { + return wc.ctx.ExprCompiler.CompileFunctionCallExpression(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (wc *WaitCompiler) CompileOptionsClause(ctx fql.IOptionsClauseContext) vm.Operand { + if c := ctx.ObjectLiteral(); c != nil { + return wc.ctx.LiteralCompiler.CompileObjectLiteral(c) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} + +func (wc *WaitCompiler) CompileTimeoutClauseContext(ctx fql.ITimeoutClauseContext) vm.Operand { + if c := ctx.IntegerLiteral(); c != nil { + return wc.ctx.LiteralCompiler.CompileIntegerLiteral(c) + } + + if c := ctx.Variable(); c != nil { + return wc.ctx.ExprCompiler.CompileVariable(c) + } + + if c := ctx.Param(); c != nil { + return wc.ctx.ExprCompiler.CompileParam(c) + } + + if c := ctx.MemberExpression(); c != nil { + return wc.ctx.ExprCompiler.CompileMemberExpression(c) + } + + if c := ctx.FunctionCall(); c != nil { + return wc.ctx.ExprCompiler.CompileFunctionCall(c, false) + } + + panic(runtime.Error(ErrUnexpectedToken, ctx.GetText())) +} diff --git a/pkg/parser/fql/fql_lexer.go b/pkg/parser/fql/fql_lexer.go index 852f6faf..8651be8e 100644 --- a/pkg/parser/fql/fql_lexer.go +++ b/pkg/parser/fql/fql_lexer.go @@ -4,10 +4,9 @@ package fql import ( "fmt" + "github.com/antlr4-go/antlr/v4" "sync" "unicode" - - "github.com/antlr4-go/antlr/v4" ) // Suppress unused import error diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index ddca4b0d..c5f24f4a 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -516,6 +516,7 @@ loop: } } + // TODO: Change. Add 'returnReg' to the closure. return vm.registers[NoopOperand], nil }