From 43e47779a364341ffddcd67a790a07dcd65f244a Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Sat, 26 Jul 2025 10:28:01 -0400 Subject: [PATCH] Add comprehensive comments to the file, following the same style as used in the other files:- Add a comment for the `LoopCompiler` struct explaining its purpose- Add comments for each method explaining what it does, its parameters, and return values- Add inline comments for complex code sections to explain the logic- Ensure comments are consistent with the style used in other files --- pkg/compiler/internal/loop.go | 108 +++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/pkg/compiler/internal/loop.go b/pkg/compiler/internal/loop.go index 811dbd38..5e5ef630 100644 --- a/pkg/compiler/internal/loop.go +++ b/pkg/compiler/internal/loop.go @@ -9,14 +9,20 @@ import ( "github.com/MontFerret/ferret/pkg/vm" ) +// LoopCompiler handles the compilation of FOR loop expressions in FQL queries. +// It transforms loop operations into VM instructions for iteration, filtering, and data manipulation. type LoopCompiler struct { ctx *CompilerContext } +// NewLoopCompiler creates a new instance of LoopCompiler with the given compiler context. func NewLoopCompiler(ctx *CompilerContext) *LoopCompiler { return &LoopCompiler{ctx: ctx} } +// Compile processes a FOR expression from the FQL AST and generates the appropriate VM instructions. +// It determines whether to compile a FOR IN loop (iteration over a collection) or a FOR WHILE loop (while condition). +// Returns an operand representing the destination of the loop results. func (c *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand { if ctx.In() != nil { return c.compileForIn(ctx) @@ -25,10 +31,14 @@ func (c *LoopCompiler) Compile(ctx fql.IForExpressionContext) vm.Operand { return c.compileForWhile(ctx) } +// compileForIn processes a FOR IN loop expression, which iterates over a collection. +// It initializes the loop, compiles the body statements and clauses, and finalizes the loop. +// Returns an operand representing the destination of the loop results. func (c *LoopCompiler) compileForIn(ctx fql.IForExpressionContext) vm.Operand { + // Initialize the loop with ForInLoop type returnRuleCtx := c.compileInitialization(ctx, core.ForInLoop) - // body + // Compile the loop body (statements and clauses) if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 { for _, b := range body { if ec := b.ForExpressionStatement(); ec != nil { @@ -39,13 +49,18 @@ func (c *LoopCompiler) compileForIn(ctx fql.IForExpressionContext) vm.Operand { } } + // Finalize the loop and return the destination operand return c.compileFinalization(returnRuleCtx) } +// compileForWhile processes a FOR WHILE loop expression with a condition (while loop). +// It initializes the loop, compiles the body statements and clauses, and finalizes the loop. +// Returns an operand representing the destination of the loop results. func (c *LoopCompiler) compileForWhile(ctx fql.IForExpressionContext) vm.Operand { + // Initialize the loop with ForWhileLoop type returnRuleCtx := c.compileInitialization(ctx, core.ForWhileLoop) - // body + // Compile the loop body (statements and clauses) if body := ctx.AllForExpressionBody(); body != nil && len(body) > 0 { for _, b := range body { if ec := b.ForExpressionStatement(); ec != nil { @@ -56,15 +71,24 @@ func (c *LoopCompiler) compileForWhile(ctx fql.IForExpressionContext) vm.Operand } } + // Finalize the loop and return the destination operand return c.compileFinalization(returnRuleCtx) } +// compileInitialization handles the setup of a loop, including determining its type, +// compiling its source, declaring variables, and emitting initialization instructions. +// Parameters: +// - ctx: The FOR expression context from the AST +// - kind: The kind of loop (ForInLoop or ForWhileLoop) +// +// Returns the rule context for the return expression or nested FOR expression. func (c *LoopCompiler) compileInitialization(ctx fql.IForExpressionContext, kind core.LoopKind) antlr.RuleContext { var distinct bool var returnRuleCtx antlr.RuleContext var loopType core.LoopType returnCtx := ctx.ForExpressionReturn() + // Determine the loop type and whether it should use distinct values if re := returnCtx.ReturnExpression(); re != nil { returnRuleCtx = re distinct = re.Distinct() != nil @@ -74,19 +98,25 @@ func (c *LoopCompiler) compileInitialization(ctx fql.IForExpressionContext, kind loopType = core.PassThroughLoop } + // Create a new loop with the determined properties loop := c.ctx.Loops.NewLoop(kind, loopType, distinct) + // Set up the loop source based on the loop kind if kind == core.ForInLoop { + // For IN loops, compile the collection to iterate over loop.Src = c.compileForExpressionSource(ctx.ForExpressionSource()) } else { + // For WHILE loops, set up a function to evaluate the condition loop.SrcFn = func() vm.Operand { return c.ctx.ExprCompiler.Compile(ctx.Expression()) } } + // Push the loop onto the stack and enter a new symbol scope c.ctx.Loops.Push(loop) c.ctx.Symbols.EnterScope() + // Declare variables for the loop value and counter if specified if val := ctx.GetValueVariable(); val != nil { loop.DeclareValueVar(val.GetText(), c.ctx.Symbols) } @@ -95,8 +125,10 @@ func (c *LoopCompiler) compileInitialization(ctx fql.IForExpressionContext, kind loop.DeclareKeyVar(ctr.GetText(), c.ctx.Symbols) } + // Emit VM instructions for loop initialization loop.EmitInitialization(c.ctx.Registers, c.ctx.Emitter, c.ctx.Loops.Depth()) + // Handle distinct values if needed if !loop.Allocate { // If the current loop must push distinct items, we must patch the dest dataset if loop.Distinct { @@ -113,22 +145,33 @@ func (c *LoopCompiler) compileInitialization(ctx fql.IForExpressionContext, kind return returnRuleCtx } +// compileFinalization handles the teardown of a loop, including processing the return expression, +// emitting finalization instructions, and cleaning up the symbol scope. +// Parameters: +// - ctx: The rule context for the return expression or nested FOR expression +// +// Returns the destination operand containing the loop results. func (c *LoopCompiler) compileFinalization(ctx antlr.RuleContext) vm.Operand { loop := c.ctx.Loops.Current() - // RETURN + // Process the return expression based on the loop type if loop.Type != core.PassThroughLoop { + // For normal loops, compile the return expression and push the result to the destination re := ctx.(*fql.ReturnExpressionContext) expReg := c.ctx.ExprCompiler.Compile(re.Expression()) c.ctx.Emitter.EmitAB(vm.OpPush, loop.Dst, expReg) } else if ctx != nil { + // For pass-through loops, recursively compile the nested FOR expression if fe, ok := ctx.(*fql.ForExpressionContext); ok { c.Compile(fe) } } + // Emit VM instructions for loop finalization loop.EmitFinalization(c.ctx.Emitter) + + // Clean up the symbol scope and pop the loop from the stack c.ctx.Symbols.ExitScope() c.ctx.Loops.Pop() @@ -137,115 +180,174 @@ func (c *LoopCompiler) compileFinalization(ctx antlr.RuleContext) vm.Operand { return loop.Dst } +// compileForExpressionSource processes the source expression for a FOR IN loop. +// It handles various types of expressions that can be used as the source collection, +// such as function calls, member expressions, variables, parameters, range operators, and literals. +// Returns an operand representing the compiled source expression. func (c *LoopCompiler) compileForExpressionSource(ctx fql.IForExpressionSourceContext) vm.Operand { + // Handle function call expressions (e.g., FOR x IN getUsers()) if fce := ctx.FunctionCallExpression(); fce != nil { return c.ctx.ExprCompiler.CompileFunctionCallExpression(fce) } + // Handle member expressions (e.g., FOR x IN users.active) if me := ctx.MemberExpression(); me != nil { return c.ctx.ExprCompiler.CompileMemberExpression(me) } + // Handle variables (e.g., FOR x IN users) if v := ctx.Variable(); v != nil { return c.ctx.ExprCompiler.CompileVariable(v) } + // Handle parameters (e.g., FOR x IN @users) if p := ctx.Param(); p != nil { return c.ctx.ExprCompiler.CompileParam(p) } + // Handle range operators (e.g., FOR x IN 1..10) if ro := ctx.RangeOperator(); ro != nil { return c.ctx.ExprCompiler.CompileRangeOperator(ro) } + // Handle array literals (e.g., FOR x IN [1, 2, 3]) if al := ctx.ArrayLiteral(); al != nil { return c.ctx.LiteralCompiler.CompileArrayLiteral(al) } + // Handle object literals (e.g., FOR x IN {a: 1, b: 2}) if ol := ctx.ObjectLiteral(); ol != nil { return c.ctx.LiteralCompiler.CompileObjectLiteral(ol) } + // If none of the above, the source expression is invalid panic(runtime.Error(core.ErrUnexpectedToken, ctx.GetText())) } +// compileForExpressionStatement processes statements within a FOR loop body. +// These can be variable declarations or function calls. +// The results of these statements are not used directly in the loop result. func (c *LoopCompiler) compileForExpressionStatement(ctx fql.IForExpressionStatementContext) { + // Handle variable declarations (e.g., LET x = 1) if vd := ctx.VariableDeclaration(); vd != nil { _ = c.ctx.StmtCompiler.CompileVariableDeclaration(vd) } else if fce := ctx.FunctionCallExpression(); fce != nil { + // Handle function calls (e.g., doSomething()) _ = c.ctx.ExprCompiler.CompileFunctionCallExpression(fce) } // TODO: Free register if needed } +// compileForExpressionClause processes clauses within a FOR loop body. +// These can be LIMIT, FILTER, SORT, or COLLECT clauses that modify the loop behavior. +// Each clause type is delegated to a specific compilation method. func (c *LoopCompiler) compileForExpressionClause(ctx fql.IForExpressionClauseContext) { + // Handle LIMIT clause (e.g., LIMIT 10) if lc := ctx.LimitClause(); lc != nil { c.compileLimitClause(lc) } else if fc := ctx.FilterClause(); fc != nil { + // Handle FILTER clause (e.g., FILTER x > 5) c.compileFilterClause(fc) } else if sc := ctx.SortClause(); sc != nil { + // Handle SORT clause (e.g., SORT x DESC) c.compileSortClause(sc) } else if cc := ctx.CollectClause(); cc != nil { + // Handle COLLECT clause (e.g., COLLECT x = y) c.compileCollectClause(cc) } } +// compileLimitClause processes a LIMIT clause in a FOR loop. +// It handles both simple LIMIT clauses and LIMIT with OFFSET clauses. +// For a single value, it's treated as a limit. For two values, the first is offset and the second is limit. func (c *LoopCompiler) compileLimitClause(ctx fql.ILimitClauseContext) { clauses := ctx.AllLimitClauseValue() if len(clauses) == 1 { + // Simple LIMIT clause (e.g., LIMIT 10) c.compileLimit(c.compileLimitClauseValue(clauses[0])) } else { + // LIMIT with OFFSET clause (e.g., LIMIT 5, 10 - offset 5, limit 10) c.compileOffset(c.compileLimitClauseValue(clauses[0])) c.compileLimit(c.compileLimitClauseValue(clauses[1])) } } +// compileLimitClauseValue processes a value in a LIMIT clause. +// It handles various types of expressions that can be used as limit or offset values, +// such as parameters, integer literals, variables, member expressions, and function calls. +// Returns an operand representing the compiled limit/offset value. func (c *LoopCompiler) compileLimitClauseValue(ctx fql.ILimitClauseValueContext) vm.Operand { + // Handle parameters (e.g., LIMIT @limit) if pm := ctx.Param(); pm != nil { return c.ctx.ExprCompiler.CompileParam(pm) } + // Handle integer literals (e.g., LIMIT 10) if il := ctx.IntegerLiteral(); il != nil { return c.ctx.LiteralCompiler.CompileIntegerLiteral(il) } + // Handle variables (e.g., LIMIT limit) if vb := ctx.Variable(); vb != nil { return c.ctx.ExprCompiler.CompileVariable(vb) } + // Handle member expressions (e.g., LIMIT config.limit) if me := ctx.MemberExpression(); me != nil { return c.ctx.ExprCompiler.CompileMemberExpression(me) } + // Handle function calls (e.g., LIMIT getLimit()) if fce := ctx.FunctionCallExpression(); fce != nil { return c.ctx.ExprCompiler.CompileFunctionCallExpression(fce) } + // If none of the above, the limit value expression is invalid panic(runtime.Error(core.ErrUnexpectedToken, ctx.GetText())) } +// compileLimit emits VM instructions to limit the number of iterations in a loop. +// It allocates a state register and emits an iterator limit instruction with the loop's end label. func (c *LoopCompiler) compileLimit(src vm.Operand) { + // Allocate a state register for the limit operation state := c.ctx.Registers.Allocate(core.State) + // Emit the iterator limit instruction with the loop's end label c.ctx.Emitter.EmitIterLimit(state, src, c.ctx.Loops.Current().EndLabel) } +// compileOffset emits VM instructions to skip a number of iterations at the start of a loop. +// It allocates a state register and emits an iterator skip instruction with the loop's jump label. func (c *LoopCompiler) compileOffset(src vm.Operand) { + // Allocate a state register for the offset operation state := c.ctx.Registers.Allocate(core.State) + // Emit the iterator skip instruction with the loop's jump label c.ctx.Emitter.EmitIterSkip(state, src, c.ctx.Loops.Current().JumpLabel) } +// compileFilterClause processes a FILTER clause in a FOR loop. +// It compiles the filter expression and emits a conditional jump instruction +// that skips the current iteration if the filter condition is false. func (c *LoopCompiler) compileFilterClause(ctx fql.IFilterClauseContext) { + // Compile the filter expression (e.g., FILTER x > 5) src := c.ctx.ExprCompiler.Compile(ctx.Expression()) + // Get the jump label for the current loop label := c.ctx.Loops.Current().JumpLabel + // Emit a jump instruction that skips to the next iteration if the filter condition is false c.ctx.Emitter.EmitJumpIfFalse(src, label) } +// compileSortClause processes a SORT clause in a FOR loop. +// It delegates the compilation to the specialized LoopSortCompiler. func (c *LoopCompiler) compileSortClause(ctx fql.ISortClauseContext) { + // Delegate to the specialized sort compiler c.ctx.LoopSortCompiler.Compile(ctx) } +// compileCollectClause processes a COLLECT clause in a FOR loop. +// It delegates the compilation to the specialized LoopCollectCompiler. func (c *LoopCompiler) compileCollectClause(ctx fql.ICollectClauseContext) { + // Delegate to the specialized collect compiler c.ctx.LoopCollectCompiler.Compile(ctx) }