diff --git a/elm.go b/elm.go index a0b65c3..23fbf34 100644 --- a/elm.go +++ b/elm.go @@ -75,7 +75,7 @@ func (m* Machine) Execute() error { return errors.New("Empty Stack") } - m.stack[m.sp] = 0 + m.stack[m.sp - 1] = 0 m.sp-- m.ip++ case DUP: @@ -145,7 +145,7 @@ func (m* Machine) Execute() error { m.sp-- m.ip++ case JMP: - if (int32(len(m.program)) < instr.Operand || instr.Operand < 0) { + if (int32(len(m.program) - 1) < instr.Operand || instr.Operand < 0) { return errors.New("Illegal access") } @@ -155,7 +155,7 @@ func (m* Machine) Execute() error { return errors.New("Stack Underflow") } - if (int32(len(m.program)) <= instr.Operand || instr.Operand < 0) { + if (int32(len(m.program) - 1) < instr.Operand || instr.Operand < 0) { return errors.New("Illegal access") } @@ -169,6 +169,16 @@ func (m* Machine) Execute() error { return nil } +func (m* Machine) Run() error { + for !m.isHalted { + err := m.Execute() + if err != nil { + return err + } + } + return nil +} + func (m* Machine) Print() { fmt.Println("Stack:"); for i := 0; int32(i) < m.sp; i++ { diff --git a/elm_test.go b/elm_test.go new file mode 100644 index 0000000..cba14e1 --- /dev/null +++ b/elm_test.go @@ -0,0 +1,406 @@ +package main + +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 int32) bool { return b == int32(0) } + +func TestHaltingDoesNothing(t *testing.T) { + m := Constructor() + m.Push(Inst{Operation: HALT}) + + m.Execute() + + assert.Equal(t, int32(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, int32(1), m.ip, "Instruction pointer should be incremented"); + assert.False(t, m.isHalted, "Virtual Machine should not be Halted"); + assert.Equal(t, int32(69), m.stack[0], "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, int32(2), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(69), m.stack[0], "Stack should contain value"); + assert.Equal(t, int32(42), m.stack[1], "Stack should contain value"); + assert.Equal(t, int32(0), m.stack[3], "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, int32(3), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(3), m.stack[0], "Stack should contain value"); + // todo: should pass that test? + //assert.Equal(t, int32(0), m.stack[1], "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, int32(3), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(6), m.stack[0], "Stack should contain value"); + // todo: should pass that test? + //assert.Equal(t, int32(0), m.stack[1], "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, int32(3), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(1), m.stack[0], "Stack should contain value"); + // todo: should pass that test? + //assert.Equal(t, int32(0), m.stack[1], "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, int32(3), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(3), m.stack[0], "Stack should contain value"); + // todo: should pass that test? + //assert.Equal(t, int32(0), m.stack[1], "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, int32(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, int32(2), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(6), m.stack[0], "Stack should contain initial value"); + assert.Equal(t, int32(6), m.stack[1], "Stack should contain duplicated value"); + assert.Equal(t, int32(0), m.stack[2], "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, int32(3), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(6), m.stack[0], "Stack should contain initial value"); + assert.Equal(t, int32(2), m.stack[1], "Stack should contain initial value"); + assert.Equal(t, int32(6), m.stack[2], "Stack should contain right duplicated value"); + assert.Equal(t, int32(0), m.stack[3], "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, int32(6), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(1), m.stack[0], "First quality should be true"); + assert.Equal(t, int32(0), m.stack[1], "Second quality should not be true"); + assert.Equal(t, int32(0), m.stack[3], "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 int32) bool { return x != int32(5) } + + assert.Nil(t, err, "Should be successfull") + assert.Equal(t, int32(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, int32(6), m.stack[0], "Stack should contain pushed value"); + assert.Equal(t, int32(3), m.stack[1], "Stack should contain pushed value"); + assert.Equal(t, int32(0), m.stack[2], "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, int32(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, int32(3), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(0), m.stack[0], "") + assert.Equal(t, int32(6), m.stack[1], "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, int32(4), m.ip, "Instruction pointer should be incremented"); + assert.True(t, m.isHalted, "Virtual Machine should be Halted"); + assert.Equal(t, int32(69), m.stack[0], "Should skip the pushed value") + assert.Equal(t, int32(0), m.stack[1], "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"); +} diff --git a/go.mod b/go.mod index ca5f8d7..c5a7463 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module e1lama/elm go 1.20 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8cf6655 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=