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

Refactor FOR-WHILE loop compilation to ensure compatibility with FOR-IN loops, update value register resolution, and add integration tests for sorting scenarios.

This commit is contained in:
Tim Voronov
2025-07-02 16:33:50 -04:00
parent 802672711e
commit d2098cadb8
2 changed files with 259 additions and 7 deletions

View File

@@ -91,15 +91,19 @@ func (c *LoopSortCompiler) compileSingleSortKey(clause fql.ISortClauseExpression
// If the loop already has a value name, reuse it; otherwise, allocate a new register // If the loop already has a value name, reuse it; otherwise, allocate a new register
// and load the value from the iterator. // and load the value from the iterator.
func (c *LoopSortCompiler) resolveValueRegister(loop *core.Loop) vm.Operand { func (c *LoopSortCompiler) resolveValueRegister(loop *core.Loop) vm.Operand {
// If value is already used in the loop body, reuse the existing register if loop.Kind == core.ForInLoop {
if loop.ValueName != "" { // If value is already used in the loop body, reuse the existing register
return loop.Value if loop.ValueName != "" {
return loop.Value
}
// Otherwise, allocate a new register and load the value from iterator
kvValReg := c.ctx.Registers.Allocate(core.Temp)
loop.EmitValue(kvValReg, c.ctx.Emitter)
return kvValReg
} }
// Otherwise, allocate a new register and load the value from iterator return loop.Key
kvValReg := c.ctx.Registers.Allocate(core.Temp)
loop.EmitValue(kvValReg, c.ctx.Emitter)
return kvValReg
} }
// compileSorter configures a sorter for a loop based on provided sort clauses and directions. // compileSorter configures a sorter for a loop based on provided sort clauses and directions.
@@ -140,6 +144,15 @@ func (c *LoopSortCompiler) finalizeSorting(loop *core.Loop, kv *core.KV, sorter
c.ctx.Registers.Free(sorter) c.ctx.Registers.Free(sorter)
} }
if loop.Kind != core.ForInLoop {
// We switched from a ForWhileLoop to a ForInLoop because the underlying data is Iterable now.
loop.Kind = core.ForInLoop
loop.ValueName = loop.KeyName
loop.Value = loop.Key
loop.Key = vm.NoopOperand
loop.KeyName = ""
}
// Reinitialize the loop to iterate over sorted data // Reinitialize the loop to iterate over sorted data
loop.EmitInitialization(c.ctx.Registers, c.ctx.Emitter) loop.EmitInitialization(c.ctx.Registers, c.ctx.Emitter)
} }

View File

@@ -0,0 +1,239 @@
package vm_test
import (
"context"
"testing"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
)
func TestForWhileSort(t *testing.T) {
RunUseCases(t, []UseCase{
CaseArray(`
FOR i WHILE UNTIL(5)
SORT i DESC
RETURN i
`, []any{4, 3, 2, 1, 0}),
CaseArray(`
LET strs = ["foo", "bar", "qaz", "abc"]
FOR i WHILE UNTIL(4)
SORT strs[i]
RETURN i
`, []any{3, 1, 0, 2}),
CaseArray(`
LET strs = ["foo", "bar", "qaz", "abc"]
FOR i WHILE UNTIL(4)
SORT i DESC
RETURN strs[i]
`, []any{"abc", "qaz", "bar", "foo"}),
CaseArray(`
LET users = [
{
name: "Ron",
age: 31,
gender: "m"
},
{
name: "Angela",
age: 29,
gender: "f"
},
{
name: "Bob",
age: 36,
gender: "m"
}
]
FOR i WHILE UNTIL(3)
LET u = users[i]
SORT u.name
RETURN users[i]
`, []any{
map[string]any{"name": "Angela", "age": 29, "gender": "f"},
map[string]any{"name": "Bob", "age": 36, "gender": "m"},
map[string]any{"name": "Ron", "age": 31, "gender": "m"},
}),
CaseArray(`
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR i WHILE UNTIL(3)
LET u = users[i]
SORT u.age DESC
RETURN users[i]
`, []any{
map[string]any{"active": true, "age": 36, "gender": "m"},
map[string]any{"active": true, "age": 31, "gender": "m"},
map[string]any{"active": true, "age": 29, "gender": "f"},
}),
CaseArray(`
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 31,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR i WHILE UNTIL(4)
LET u = users[i]
SORT u.age, u.gender
RETURN users[i]`,
[]any{
map[string]any{"active": true, "age": 29, "gender": "f"},
map[string]any{"active": true, "age": 31, "gender": "f"},
map[string]any{"active": true, "age": 31, "gender": "m"},
map[string]any{"active": true, "age": 36, "gender": "m"},
}),
CaseArray(`
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 31,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR i WHILE UNTIL(4)
LET u = users[i]
LET x = "foo"
TEST(x)
SORT u.age, u.gender
RETURN users[i]
`, []any{
map[string]any{"active": true, "age": 29, "gender": "f"},
map[string]any{"active": true, "age": 31, "gender": "f"},
map[string]any{"active": true, "age": 31, "gender": "m"},
map[string]any{"active": true, "age": 36, "gender": "m"},
}),
CaseArray(`
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR i WHILE UNTIL(3)
LET u = users[i]
FILTER u.gender == "m"
SORT u.age
RETURN users[i]
`, []any{
map[string]any{"active": true, "age": 31, "gender": "m"},
map[string]any{"active": true, "age": 36, "gender": "m"},
}),
CaseArray(`
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
FOR i WHILE UNTIL(3)
SORT users[i].age
FILTER users[i].gender == "m"
RETURN users[i]
`, []any{
map[string]any{"active": true, "age": 31, "gender": "m"},
map[string]any{"active": true, "age": 36, "gender": "m"},
}),
CaseObject(`
LET users = [
{
active: true,
age: 31,
gender: "m"
},
{
active: true,
age: 29,
gender: "f"
},
{
active: true,
age: 36,
gender: "m"
}
]
LET sorted = (FOR i WHILE UNTIL(3)
SORT users[i].age
FILTER users[i].gender == "m"
RETURN users[i])
RETURN sorted[0]
`, map[string]any{"active": true, "age": 31, "gender": "m"}),
}, vm.WithFunctionSetter(func(fns runtime.Functions) {
fns.F().Set("TEST", func(ctx context.Context, args ...runtime.Value) (runtime.Value, error) {
return runtime.None, nil
})
fns.SetAll(ForWhileHelpers())
}))
}