package elm import ( "testing" "github.com/stretchr/testify/assert" ) func All[T any](ts []T, pred func(T) bool) bool { for _, t := range ts { if !pred(t) { return false } } return true } func IsZero(b ElmValue) bool { return b.Number == int64(0) } func TestHaltingDoesNothing(t *testing.T) { m := Constructor() m.Push(Inst{Operation: HALT}) m.Execute() assert.Equal(t, int64(0), m.ip, "Instruction pointer should not be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.True(t, All(m.stack, IsZero), "Stack should be empty"); } func TestPush(t* testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 69}) m.Push(Inst{Operation: HALT}) m.Execute() assert.Equal(t, int64(1), m.ip, "Instruction pointer should be incremented"); assert.False(t, m.isHalted, "Virtual Machine should not be Halted"); assert.Equal(t, int64(69), m.stack[0].Number, "Stack should contain value"); } func TestPushingSequence(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 69}) m.Push(Inst{Operation: PUSH, Operand: 42}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(2), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(69), m.stack[0].Number, "Stack should contain value"); assert.Equal(t, int64(42), m.stack[1].Number, "Stack should contain value"); assert.Equal(t, int64(0), m.stack[3].Number, "Stack should be zero after"); } func TestAdd(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 1}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: ADD}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(3), m.stack[0].Number, "Stack should contain value"); // todo: should pass that test? //assert.Equal(t, int64(0), m.stack[1].Number, "Stack should be zero after"); } func TestMul(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 3}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: MUL}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(6), m.stack[0].Number, "Stack should contain value"); // todo: should pass that test? //assert.Equal(t, int64(0), m.stack[1].Number, "Stack should be zero after"); } func TestSub(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 3}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: SUB}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(1), m.stack[0].Number, "Stack should contain value"); // todo: should pass that test? //assert.Equal(t, int64(0), m.stack[1].Number, "Stack should be zero after"); } func TestDiv(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: DIV}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(3), m.stack[0].Number, "Stack should contain value"); // todo: should pass that test? //assert.Equal(t, int64(0), m.stack[1].Number, "Stack should be zero after"); } func TestAddNeedTwoItems(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: ADD}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestSubNeedTwoItems(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: SUB}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestMulNeedTwoItems(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: MUL}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestDivNeedTwoItems(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: DIV}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestDivNeedNonZeroValues(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: PUSH, Operand: 0}) m.Push(Inst{Operation: DIV}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestDivNeedNonZeroValues2(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 0}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: DIV}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestPop(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: POP}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(2), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.True(t, All(m.stack, IsZero), "Stack should be empty"); } func TestPopNeedsNonEmptyStack(t *testing.T) { m := Constructor() m.Push(Inst{Operation: POP}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestDup(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: DUP}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(2), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(6), m.stack[0].Number, "Stack should contain initial value"); assert.Equal(t, int64(6), m.stack[1].Number, "Stack should contain duplicated value"); assert.Equal(t, int64(0), m.stack[2].Number, "Stack should be zero after"); } func TestDupByPointer(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: DUP, Operand: 1}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(6), m.stack[0].Number, "Stack should contain initial value"); assert.Equal(t, int64(2), m.stack[1].Number, "Stack should contain initial value"); assert.Equal(t, int64(6), m.stack[2].Number, "Stack should contain right duplicated value"); assert.Equal(t, int64(0), m.stack[3].Number, "Stack should be zero after"); } func TestEquals(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: EQ}) m.Push(Inst{Operation: PUSH, Operand: 2}) m.Push(Inst{Operation: PUSH, Operand: 1}) m.Push(Inst{Operation: EQ}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(6), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(1), m.stack[0].Number, "First quality should be true"); assert.Equal(t, int64(0), m.stack[1].Number, "Second quality should not be true"); assert.Equal(t, int64(0), m.stack[3].Number, "Stack should be zero after"); } func TestEqualsNeedTwoItems(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: DIV}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestJump(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: JMP, Operand: 3}) m.Push(Inst{Operation: PUSH, Operand: 5}) m.Push(Inst{Operation: PUSH, Operand: 3}) m.Push(Inst{Operation: HALT}) err := m.Run() notEqualToFive := func (x ElmValue) bool { return x.Number != int64(5) } assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(4), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.True(t, All(m.stack, notEqualToFive), "Stack should not contain skipped value"); assert.Equal(t, int64(6), m.stack[0].Number, "Stack should contain pushed value"); assert.Equal(t, int64(3), m.stack[1].Number, "Stack should contain pushed value"); assert.Equal(t, int64(0), m.stack[2].Number, "Stack should be zero afterall"); } func TestJumpDontPassIllegalAccess(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: JMP, Operand: 3}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestJumpCanPassLastInstruction(t * testing.T) { m := Constructor() m.Push(Inst{Operation: JMP, Operand: 2}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(2), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); } func TestJumpDontPassIllegalAccess2(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: JMP, Operand: -1}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestJumpIfDontPassWithoutStackValue(t * testing.T) { m := Constructor() m.Push(Inst{Operation: JMPIF, Operand: 2}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestJumpIfDontPassIfNotTrue(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 0}) m.Push(Inst{Operation: JMPIF, Operand: 3}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(0), m.stack[0].Number, "") assert.Equal(t, int64(6), m.stack[1].Number, "Should not skip the pushed value") } func TestJumpIfPassIfTrue(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 1}) m.Push(Inst{Operation: JMPIF, Operand: 3}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: PUSH, Operand: 69}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(4), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(69), m.stack[0].Number, "Should skip the pushed value") assert.Equal(t, int64(0), m.stack[1].Number, "Be zero afterall") } func TestJumpIfDontPassIllegalAccess2(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: JMPIF, Operand: -1}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestJumpIfDontPassIllegalAccess(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: JMPIF, Operand: 3}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestCallDontPassIllegallAccess(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: CALL, Operand: 3}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestCallDontPassIllegallAccess2(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: CALL, Operand: -3}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestCallPass(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 1}) m.Push(Inst{Operation: CALL, Operand: 3}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: PUSH, Operand: 69}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(4), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(1), m.stack[0].Number, "Should skip the pushed value") assert.Equal(t, int64(69), m.stack[1].Number, "Should skip the pushed value") assert.Equal(t, 2, len(m.stackFrame), "Should create the function stack frame") assert.Equal(t, int64(0), m.stack[2].Number, "Should be zero afterall") } func TestRetPass(t * testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 1}) m.Push(Inst{Operation: CALL, Operand: 4}) m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: HALT}) m.Push(Inst{Operation: PUSH, Operand: 69}) m.Push(Inst{Operation: RET}) err := m.Run() assert.Nil(t, err, "Should be successfull") assert.Equal(t, int64(3), m.ip, "Instruction pointer should be incremented"); assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int64(1), m.stack[0].Number, "Should skip the pushed value") assert.Equal(t, int64(69), m.stack[1].Number, "Should skip the pushed value") assert.Equal(t, int64(6), m.stack[2].Number, "Should skip the pushed value") assert.Equal(t, int64(0), m.stack[3].Number, "Should be zero afterall") assert.Equal(t, 1, len(m.stackFrame), "Should pop the function stack frame") } func TestStoreWontPassIfStackEmpty(t *testing.T) { m := Constructor() m.Push(Inst{Operation: STORE, Operand: 1}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestStoreWontPassIfNegativeOperand(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: STORE, Operand: -1}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.NotNil(t, err, "Should be errored"); } func TestStoreShouldStoreOnTheFrame(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: STORE, Operand: 0}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should not be errored"); assert.Equal(t, int64(0), m.sp, "Should decrement the stack pointer") assert.Equal(t, int64(6), m.stackFrame[0].Locals[0].Number, "Should contain the value") assert.Equal(t, int64(2), m.ip, "Instruction pointer should be incremented"); } func TestGetShouldGetVariableFromFrame(t *testing.T) { m := Constructor() m.Push(Inst{Operation: PUSH, Operand: 6}) m.Push(Inst{Operation: STORE, Operand: 0}) m.Push(Inst{Operation: PUSH, Operand: 1}) m.Push(Inst{Operation: GET, Operand: 0}) m.Push(Inst{Operation: HALT}) err := m.Run() assert.Nil(t, err, "Should not be errored"); assert.Equal(t, int64(6), m.stack[1].Number, "Should contain the value") assert.Equal(t, int64(4), m.ip, "Instruction pointer should be incremented"); }