1
0
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:
copilot-swe-agent[bot]
2025-09-03 21:55:48 +00:00
parent 04b1f5bb75
commit 81551f98d4
2 changed files with 851 additions and 0 deletions

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

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