1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-13 19:52:52 +02:00

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

This commit is contained in:
Tim Voronov
2025-07-26 10:28:01 -04:00
parent e619212ac6
commit 43e47779a3

View File

@@ -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)
}