mirror of
https://github.com/MontFerret/ferret.git
synced 2025-09-16 09:06:36 +02:00
Add comprehensive tests for ScopeProjection and Emitter components
Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com>
This commit is contained in:
487
pkg/compiler/internal/core/emitter_test.go
Normal file
487
pkg/compiler/internal/core/emitter_test.go
Normal file
@@ -0,0 +1,487 @@
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"github.com/MontFerret/ferret/pkg/vm"
|
||||
)
|
||||
|
||||
func TestEmitter(t *testing.T) {
|
||||
Convey("Emitter", t, func() {
|
||||
Convey("NewEmitter", func() {
|
||||
Convey("Should create a new emitter", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
So(emitter, ShouldNotBeNil)
|
||||
So(emitter.Size(), ShouldEqual, 0)
|
||||
So(emitter.Bytecode(), ShouldHaveLength, 0)
|
||||
So(emitter.Labels(), ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Size", func() {
|
||||
Convey("Should return the number of instructions", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
So(emitter.Size(), ShouldEqual, 0)
|
||||
|
||||
emitter.Emit(vm.OpReturn)
|
||||
So(emitter.Size(), ShouldEqual, 1)
|
||||
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(1))
|
||||
So(emitter.Size(), ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Bytecode", func() {
|
||||
Convey("Should return all instructions", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.Emit(vm.OpReturn)
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(1))
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 2)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpReturn)
|
||||
So(bytecode[1].Opcode, ShouldEqual, vm.OpLoadNone)
|
||||
So(bytecode[1].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".Position", func() {
|
||||
Convey("Should return current position", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
// Position should be -1 when no instructions
|
||||
So(emitter.Position(), ShouldEqual, -1)
|
||||
|
||||
emitter.Emit(vm.OpReturn)
|
||||
So(emitter.Position(), ShouldEqual, 0)
|
||||
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(1))
|
||||
So(emitter.Position(), ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Label Management", func() {
|
||||
Convey(".NewLabel", func() {
|
||||
Convey("Should create new labels with unique IDs", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
label1 := emitter.NewLabel("test1")
|
||||
label2 := emitter.NewLabel("test2")
|
||||
|
||||
So(label1, ShouldNotEqual, label2)
|
||||
})
|
||||
|
||||
Convey("Should create unnamed labels", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
label := emitter.NewLabel()
|
||||
|
||||
So(label, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should handle multiple name parts", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
label := emitter.NewLabel("part1", "part2", "part3")
|
||||
|
||||
So(label, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".MarkLabel", func() {
|
||||
Convey("Should mark label at current position", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("test")
|
||||
|
||||
emitter.Emit(vm.OpReturn)
|
||||
emitter.MarkLabel(label)
|
||||
|
||||
pos, found := emitter.LabelPosition(label)
|
||||
So(found, ShouldBeTrue)
|
||||
So(pos, ShouldEqual, 1) // Position after the OpReturn
|
||||
})
|
||||
|
||||
Convey("Should create labels map", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("test")
|
||||
|
||||
emitter.MarkLabel(label)
|
||||
|
||||
labels := emitter.Labels()
|
||||
So(labels, ShouldNotBeNil)
|
||||
So(len(labels), ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".LabelPosition", func() {
|
||||
Convey("Should return false for unmarked labels", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("test")
|
||||
|
||||
_, found := emitter.LabelPosition(label)
|
||||
So(found, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Should return correct position for marked labels", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("test")
|
||||
|
||||
emitter.Emit(vm.OpReturn)
|
||||
emitter.Emit(vm.OpReturn)
|
||||
emitter.MarkLabel(label)
|
||||
|
||||
pos, found := emitter.LabelPosition(label)
|
||||
So(found, ShouldBeTrue)
|
||||
So(pos, ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Instruction Emission", func() {
|
||||
Convey(".Emit", func() {
|
||||
Convey("Should emit instruction with no arguments", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.Emit(vm.OpReturn)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpReturn)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(0))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(0))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitA", func() {
|
||||
Convey("Should emit instruction with one operand", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(5))
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadNone)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(5))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(0))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAB", func() {
|
||||
Convey("Should emit instruction with two operands", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitAB(vm.OpMove, vm.Operand(1), vm.Operand(2))
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpMove)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAb", func() {
|
||||
Convey("Should emit instruction with boolean argument true", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitAb(vm.OpLoadBool, vm.Operand(1), true)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadBool)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
|
||||
Convey("Should emit instruction with boolean argument false", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitAb(vm.OpLoadBool, vm.Operand(1), false)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadBool)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(0))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAx", func() {
|
||||
Convey("Should emit instruction with integer argument", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitAx(vm.OpLoadConst, vm.Operand(1), 42)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadConst)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(42))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAxy", func() {
|
||||
Convey("Should emit instruction with two integer arguments", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitAxy(vm.OpCall, vm.Operand(1), 2, 3)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpCall)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(3))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAs", func() {
|
||||
Convey("Should emit instruction with register sequence", func() {
|
||||
emitter := core.NewEmitter()
|
||||
seq := core.RegisterSequence{vm.Operand(5), vm.Operand(6), vm.Operand(7)}
|
||||
|
||||
emitter.EmitAs(vm.OpCall, vm.Operand(1), seq)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpCall)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(5)) // First register
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(7)) // Last register
|
||||
})
|
||||
|
||||
Convey("Should handle nil sequence", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitAs(vm.OpCall, vm.Operand(1), nil)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpCall)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(0))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(0))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitABx", func() {
|
||||
Convey("Should emit instruction with operand and integer", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitABx(vm.OpLoadIndex, vm.Operand(1), vm.Operand(2), 42)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadIndex)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(42))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitABC", func() {
|
||||
Convey("Should emit instruction with three operands", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
emitter.EmitABC(vm.OpAdd, vm.Operand(1), vm.Operand(2), vm.Operand(3))
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpAdd)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(3))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("High-Level Emit Functions", func() {
|
||||
Convey("Should have convenience methods", func() {
|
||||
emitter := core.NewEmitter()
|
||||
ra := core.NewRegisterAllocator()
|
||||
|
||||
// Test various high-level emit functions if they exist
|
||||
So(func() {
|
||||
// These functions should exist based on the scope.go usage
|
||||
dst := ra.Allocate(core.Temp)
|
||||
args := ra.AllocateSequence(2)
|
||||
|
||||
// Test functions that should exist
|
||||
emitter.EmitArray(dst, args)
|
||||
emitter.EmitObject(dst, args)
|
||||
emitter.EmitMove(dst, args[0])
|
||||
|
||||
st := core.NewSymbolTable(ra)
|
||||
constOp := st.AddConstant(runtime.NewString("test"))
|
||||
emitter.EmitLoadConst(dst, constOp)
|
||||
}, ShouldNotPanic)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Instruction Modification", func() {
|
||||
Convey(".SwapAB", func() {
|
||||
Convey("Should modify instruction at label position", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("swap")
|
||||
|
||||
// Emit a placeholder instruction
|
||||
emitter.MarkLabel(label)
|
||||
emitter.EmitAB(vm.OpMove, vm.Operand(1), vm.Operand(2))
|
||||
|
||||
// Swap with different instruction
|
||||
emitter.SwapAB(label, vm.OpLoadBool, vm.Operand(3), vm.Operand(4))
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadBool)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(3))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(4))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".SwapAx", func() {
|
||||
Convey("Should modify instruction with integer argument", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("swap")
|
||||
|
||||
emitter.MarkLabel(label)
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(1))
|
||||
|
||||
emitter.SwapAx(label, vm.OpLoadConst, vm.Operand(2), 42)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadConst)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(42))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".SwapAxy", func() {
|
||||
Convey("Should modify instruction with two integer arguments", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("swap")
|
||||
|
||||
emitter.MarkLabel(label)
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(1))
|
||||
|
||||
emitter.SwapAxy(label, vm.OpCall, vm.Operand(1), 2, 3)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpCall)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(3))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".SwapAs", func() {
|
||||
Convey("Should modify instruction with register sequence", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("swap")
|
||||
seq := core.RegisterSequence{vm.Operand(5), vm.Operand(6), vm.Operand(7)}
|
||||
|
||||
emitter.MarkLabel(label)
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(1))
|
||||
|
||||
emitter.SwapAs(label, vm.OpCall, vm.Operand(1), seq)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 1)
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpCall)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(5))
|
||||
So(bytecode[0].Operands[2], ShouldEqual, vm.Operand(7))
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".InsertAx", func() {
|
||||
Convey("Should insert instruction at label position", func() {
|
||||
emitter := core.NewEmitter()
|
||||
label := emitter.NewLabel("insert")
|
||||
|
||||
emitter.MarkLabel(label)
|
||||
emitter.EmitA(vm.OpLoadBool, vm.Operand(1))
|
||||
|
||||
// Insert should add instruction and shift others
|
||||
emitter.InsertAx(label, vm.OpLoadConst, vm.Operand(2), 42)
|
||||
|
||||
bytecode := emitter.Bytecode()
|
||||
So(bytecode, ShouldHaveLength, 2)
|
||||
|
||||
// Inserted instruction should be first
|
||||
So(bytecode[0].Opcode, ShouldEqual, vm.OpLoadConst)
|
||||
So(bytecode[0].Operands[0], ShouldEqual, vm.Operand(2))
|
||||
So(bytecode[0].Operands[1], ShouldEqual, vm.Operand(42))
|
||||
|
||||
// Original instruction should be second
|
||||
So(bytecode[1].Opcode, ShouldEqual, vm.OpLoadBool)
|
||||
So(bytecode[1].Operands[0], ShouldEqual, vm.Operand(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Integration", func() {
|
||||
Convey("Should handle complex instruction sequences", func() {
|
||||
emitter := core.NewEmitter()
|
||||
|
||||
// Create labels
|
||||
startLabel := emitter.NewLabel("start")
|
||||
endLabel := emitter.NewLabel("end")
|
||||
|
||||
// Mark start
|
||||
emitter.MarkLabel(startLabel)
|
||||
|
||||
// Emit various instructions
|
||||
emitter.Emit(vm.OpReturn)
|
||||
emitter.EmitA(vm.OpLoadBool, vm.Operand(1))
|
||||
emitter.EmitAB(vm.OpMove, vm.Operand(2), vm.Operand(1))
|
||||
emitter.EmitABC(vm.OpAdd, vm.Operand(3), vm.Operand(1), vm.Operand(2))
|
||||
|
||||
// Mark end
|
||||
emitter.MarkLabel(endLabel)
|
||||
|
||||
// Verify
|
||||
So(emitter.Size(), ShouldEqual, 4)
|
||||
|
||||
startPos, found1 := emitter.LabelPosition(startLabel)
|
||||
endPos, found2 := emitter.LabelPosition(endLabel)
|
||||
|
||||
So(found1, ShouldBeTrue)
|
||||
So(found2, ShouldBeTrue)
|
||||
So(startPos, ShouldEqual, 0)
|
||||
So(endPos, ShouldEqual, 4)
|
||||
})
|
||||
|
||||
Convey("Should handle label patching", func() {
|
||||
emitter := core.NewEmitter()
|
||||
jumpLabel := emitter.NewLabel("jump_target")
|
||||
|
||||
// Emit some instructions
|
||||
emitter.EmitA(vm.OpLoadBool, vm.Operand(1))
|
||||
|
||||
// Later mark the label - this should trigger patching
|
||||
emitter.MarkLabel(jumpLabel)
|
||||
emitter.EmitA(vm.OpLoadNone, vm.Operand(2))
|
||||
|
||||
pos, found := emitter.LabelPosition(jumpLabel)
|
||||
So(found, ShouldBeTrue)
|
||||
So(pos, ShouldEqual, 1) // After first instruction
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
364
pkg/compiler/internal/core/scope_test.go
Normal file
364
pkg/compiler/internal/core/scope_test.go
Normal file
@@ -0,0 +1,364 @@
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime"
|
||||
"github.com/MontFerret/ferret/pkg/vm"
|
||||
)
|
||||
|
||||
func TestScopeProjection(t *testing.T) {
|
||||
Convey("ScopeProjection", t, func() {
|
||||
Convey("NewScopeProjection", func() {
|
||||
Convey("Should create a new scope projection", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
// Create some test variables
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(1)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(2)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
|
||||
So(sp, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAsArray", func() {
|
||||
Convey("Should emit scope variables as array", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
// Create some test variables
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(1)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(2)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
|
||||
// Should not panic
|
||||
So(func() { sp.EmitAsArray(dstReg) }, ShouldNotPanic)
|
||||
|
||||
// Should have generated some instructions
|
||||
So(emitter.Size(), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
Convey("Should handle empty scope", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
var scope []core.Variable
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
|
||||
// Should not panic with empty scope
|
||||
So(func() { sp.EmitAsArray(dstReg) }, ShouldNotPanic)
|
||||
So(emitter.Size(), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".EmitAsObject", func() {
|
||||
Convey("Should emit scope variables as object", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(1)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(2)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
|
||||
// Should not panic
|
||||
So(func() { sp.EmitAsObject(dstReg) }, ShouldNotPanic)
|
||||
So(emitter.Size(), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
Convey("Should handle empty scope efficiently", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
var scope []core.Variable
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
initialSize := emitter.Size()
|
||||
|
||||
sp.EmitAsObject(dstReg)
|
||||
|
||||
// Empty scope should generate minimal instructions
|
||||
So(emitter.Size(), ShouldEqual, initialSize+1) // Just one LoadObject instruction
|
||||
})
|
||||
|
||||
Convey("Should emit key-value pairs for variables", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "testVar", Type: core.TypeString, Register: vm.Operand(1)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
|
||||
sp.EmitAsObject(dstReg)
|
||||
|
||||
// Should have generated instructions for key and value
|
||||
So(emitter.Size(), ShouldBeGreaterThan, 2)
|
||||
|
||||
// Should have added the variable name as a constant
|
||||
constants := st.Constants()
|
||||
found := false
|
||||
for _, c := range constants {
|
||||
if str, ok := c.(runtime.String); ok && string(str) == "testVar" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
So(found, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".RestoreFromArray", func() {
|
||||
Convey("Should restore variables from array", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(0)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(0)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
srcReg := ra.Allocate(core.Temp)
|
||||
|
||||
// Should not panic
|
||||
So(func() { sp.RestoreFromArray(srcReg) }, ShouldNotPanic)
|
||||
|
||||
// Should have generated instructions
|
||||
So(emitter.Size(), ShouldBeGreaterThan, 0)
|
||||
|
||||
// Should have declared local variables
|
||||
locals := st.LocalVariables()
|
||||
So(len(locals), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
Convey("Should handle empty scope", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
var scope []core.Variable
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
srcReg := ra.Allocate(core.Temp)
|
||||
initialSize := emitter.Size()
|
||||
|
||||
sp.RestoreFromArray(srcReg)
|
||||
|
||||
// Empty scope should not generate many instructions
|
||||
So(emitter.Size(), ShouldEqual, initialSize)
|
||||
})
|
||||
|
||||
Convey("Should create indices as constants", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(0)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(0)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
srcReg := ra.Allocate(core.Temp)
|
||||
|
||||
sp.RestoreFromArray(srcReg)
|
||||
|
||||
// Should have added indices as constants
|
||||
constants := st.Constants()
|
||||
foundIndices := 0
|
||||
for _, c := range constants {
|
||||
if intVal, ok := c.(runtime.Int); ok && (intVal == 0 || intVal == 1) {
|
||||
foundIndices++
|
||||
}
|
||||
}
|
||||
So(foundIndices, ShouldBeGreaterThanOrEqualTo, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey(".RestoreFromObject", func() {
|
||||
Convey("Should restore variables from object", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(0)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(0)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
srcReg := ra.Allocate(core.Temp)
|
||||
|
||||
// Should not panic
|
||||
So(func() { sp.RestoreFromObject(srcReg) }, ShouldNotPanic)
|
||||
|
||||
// Should have generated instructions
|
||||
So(emitter.Size(), ShouldBeGreaterThan, 0)
|
||||
|
||||
// Should have declared local variables
|
||||
locals := st.LocalVariables()
|
||||
So(len(locals), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
Convey("Should handle empty scope", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
var scope []core.Variable
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
srcReg := ra.Allocate(core.Temp)
|
||||
initialSize := emitter.Size()
|
||||
|
||||
sp.RestoreFromObject(srcReg)
|
||||
|
||||
// Empty scope should not generate many instructions
|
||||
So(emitter.Size(), ShouldEqual, initialSize)
|
||||
})
|
||||
|
||||
Convey("Should create keys as constants", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "testVar1", Type: core.TypeString, Register: vm.Operand(0)},
|
||||
{Name: "testVar2", Type: core.TypeInt, Register: vm.Operand(0)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
srcReg := ra.Allocate(core.Temp)
|
||||
|
||||
sp.RestoreFromObject(srcReg)
|
||||
|
||||
// Should have added variable names as constants
|
||||
constants := st.Constants()
|
||||
foundKeys := 0
|
||||
for _, c := range constants {
|
||||
if str, ok := c.(runtime.String); ok {
|
||||
if string(str) == "testVar1" || string(str) == "testVar2" {
|
||||
foundKeys++
|
||||
}
|
||||
}
|
||||
}
|
||||
So(foundKeys, ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Integration", func() {
|
||||
Convey("Should handle complete projection cycle", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
// Original scope
|
||||
scope := []core.Variable{
|
||||
{Name: "var1", Type: core.TypeString, Register: vm.Operand(1)},
|
||||
{Name: "var2", Type: core.TypeInt, Register: vm.Operand(2)},
|
||||
{Name: "var3", Type: core.TypeFloat, Register: vm.Operand(3)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
|
||||
// Test array projection and restoration
|
||||
arrayReg := ra.Allocate(core.Temp)
|
||||
sp.EmitAsArray(arrayReg)
|
||||
|
||||
// Enter new scope
|
||||
st.EnterScope()
|
||||
sp.RestoreFromArray(arrayReg)
|
||||
|
||||
// Verify variables were restored
|
||||
locals := st.LocalVariables()
|
||||
So(len(locals), ShouldEqual, 3)
|
||||
|
||||
// Test object projection and restoration
|
||||
objectReg := ra.Allocate(core.Temp)
|
||||
sp.EmitAsObject(objectReg)
|
||||
|
||||
// Enter another new scope
|
||||
st.EnterScope()
|
||||
sp.RestoreFromObject(objectReg)
|
||||
|
||||
// Verify variables were restored again
|
||||
locals2 := st.LocalVariables()
|
||||
So(len(locals2), ShouldEqual, 3)
|
||||
})
|
||||
|
||||
Convey("Should handle single variable scope", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "singleVar", Type: core.TypeBool, Register: vm.Operand(1)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
|
||||
// Test all operations
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
|
||||
So(func() { sp.EmitAsArray(dstReg) }, ShouldNotPanic)
|
||||
So(func() { sp.EmitAsObject(dstReg) }, ShouldNotPanic)
|
||||
So(func() { sp.RestoreFromArray(dstReg) }, ShouldNotPanic)
|
||||
So(func() { sp.RestoreFromObject(dstReg) }, ShouldNotPanic)
|
||||
})
|
||||
|
||||
Convey("Should handle various variable types", func() {
|
||||
ra := core.NewRegisterAllocator()
|
||||
emitter := core.NewEmitter()
|
||||
st := core.NewSymbolTable(ra)
|
||||
|
||||
scope := []core.Variable{
|
||||
{Name: "strVar", Type: core.TypeString, Register: vm.Operand(1)},
|
||||
{Name: "intVar", Type: core.TypeInt, Register: vm.Operand(2)},
|
||||
{Name: "floatVar", Type: core.TypeFloat, Register: vm.Operand(3)},
|
||||
{Name: "boolVar", Type: core.TypeBool, Register: vm.Operand(4)},
|
||||
{Name: "listVar", Type: core.TypeList, Register: vm.Operand(5)},
|
||||
{Name: "mapVar", Type: core.TypeMap, Register: vm.Operand(6)},
|
||||
{Name: "anyVar", Type: core.TypeAny, Register: vm.Operand(7)},
|
||||
{Name: "unknownVar", Type: core.TypeUnknown, Register: vm.Operand(8)},
|
||||
}
|
||||
|
||||
sp := core.NewScopeProjection(ra, emitter, st, scope)
|
||||
dstReg := ra.Allocate(core.Temp)
|
||||
|
||||
// All operations should work regardless of variable types
|
||||
So(func() { sp.EmitAsArray(dstReg) }, ShouldNotPanic)
|
||||
So(func() { sp.EmitAsObject(dstReg) }, ShouldNotPanic)
|
||||
So(func() { sp.RestoreFromArray(dstReg) }, ShouldNotPanic)
|
||||
So(func() { sp.RestoreFromObject(dstReg) }, ShouldNotPanic)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user