diff --git a/elm.go b/elm.go index 0b42250..90b8f1c 100644 --- a/elm.go +++ b/elm.go @@ -28,6 +28,8 @@ const ( JMPIF Operation = 10 CALL Operation = 11 RET Operation = 12 + STORE Operation = 13 + GET Operation = 14 ) type Inst struct { @@ -206,6 +208,28 @@ func (m* Machine) Execute() error { currentFrame := m.stackFrame[len(m.stackFrame) - 1] m.stackFrame = m.stackFrame[:len(m.stackFrame) - 1] m.ip = int32(currentFrame.ReturnAddress) + case STORE: + if (m.sp < 1) { + return errors.New("Nothing to store on the stack") + } + + if (instr.Operand < 0) { + return errors.New("Incorrect variable address") + } + + currentFrame := m.stackFrame[len(m.stackFrame) - 1] + currentFrame.StoreVariable(int(instr.Operand), m.stack[m.sp - 1]) + m.sp-- + m.ip++ + case GET: + if (instr.Operand < 0) { + return errors.New("Incorrect variable address") + } + + currentFrame := m.stackFrame[len(m.stackFrame) - 1] + m.stack[m.sp] = currentFrame.GetVariable(int(instr.Operand)) + m.sp++ + m.ip++ } return nil } diff --git a/elm_test.go b/elm_test.go index 2e95868..9be45d8 100644 --- a/elm_test.go +++ b/elm_test.go @@ -442,6 +442,7 @@ func TestCallPass(t * testing.T) { assert.True(t, m.isHalted, "Virtual Machine should be Halted"); assert.Equal(t, int32(1), m.stack[0], "Should skip the pushed value") assert.Equal(t, int32(69), m.stack[1], "Should skip the pushed value") + assert.Equal(t, 2, len(m.stackFrame), "Should create the function stack frame") assert.Equal(t, int32(0), m.stack[2], "Should be zero afterall") } @@ -464,4 +465,61 @@ func TestRetPass(t * testing.T) { assert.Equal(t, int32(69), m.stack[1], "Should skip the pushed value") assert.Equal(t, int32(6), m.stack[2], "Should skip the pushed value") assert.Equal(t, int32(0), m.stack[3], "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, int32(0), m.sp, "Should decrement the stack pointer") + assert.Equal(t, int32(6), m.stackFrame[0].Locals[0], "Should contain the value") + assert.Equal(t, int32(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, int32(6), m.stack[1], "Should contain the value") + assert.Equal(t, int32(4), m.ip, "Instruction pointer should be incremented"); +} + +