feat: separate compiler executable
This commit is contained in:
319
core/elm.go
Normal file
319
core/elm.go
Normal file
@@ -0,0 +1,319 @@
|
||||
package elm
|
||||
import (
|
||||
// "unicode"
|
||||
// "bytes"
|
||||
// "bufio"
|
||||
// "strings"
|
||||
"fmt"
|
||||
//"os"
|
||||
"errors"
|
||||
// "encoding/binary"
|
||||
// "strconv"
|
||||
)
|
||||
|
||||
const STACK_SIZE = 1024
|
||||
|
||||
type Operation int32
|
||||
|
||||
const (
|
||||
HALT Operation = 0
|
||||
PUSH Operation = 1
|
||||
POP Operation = 2
|
||||
DUP Operation = 3
|
||||
ADD Operation = 4
|
||||
SUB Operation = 5
|
||||
MUL Operation = 6
|
||||
DIV Operation = 7
|
||||
EQ Operation = 8
|
||||
JMP Operation = 9
|
||||
JMPIF Operation = 10
|
||||
CALL Operation = 11
|
||||
RET Operation = 12
|
||||
STORE Operation = 13
|
||||
GET Operation = 14
|
||||
)
|
||||
|
||||
type Inst struct {
|
||||
Operation Operation
|
||||
Operand int32
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
Locals map[int]int32
|
||||
ReturnAddress int
|
||||
}
|
||||
|
||||
func CreateFrame(returnAddress int32) *Frame {
|
||||
f := Frame{}
|
||||
f.Locals = make(map[int]int32)
|
||||
f.ReturnAddress = int(returnAddress)
|
||||
|
||||
return &f
|
||||
}
|
||||
|
||||
func (f *Frame) StoreVariable(address int, value int32) {
|
||||
f.Locals[address] = value
|
||||
}
|
||||
|
||||
func (f *Frame) GetVariable(address int) int32 {
|
||||
return f.Locals[address]
|
||||
}
|
||||
|
||||
type Machine struct {
|
||||
program []Inst
|
||||
stack []int32
|
||||
stackFrame []*Frame
|
||||
ip int32
|
||||
sp int32
|
||||
isHalted bool;
|
||||
}
|
||||
|
||||
func Constructor() *Machine {
|
||||
m := Machine{}
|
||||
m.program = make([]Inst, 0)
|
||||
m.stack = make([]int32, STACK_SIZE)
|
||||
m.stackFrame = make([]*Frame, 0)
|
||||
m.stackFrame = append(m.stackFrame, CreateFrame(0))
|
||||
m.ip = 0
|
||||
m.sp = 0
|
||||
m.isHalted = false
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m* Machine) Push(i Inst) error {
|
||||
m.program = append(m.program, i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m* Machine) Execute() error {
|
||||
instr := m.program[m.ip]
|
||||
switch op := instr.Operation; op {
|
||||
case HALT:
|
||||
m.isHalted = true
|
||||
case PUSH:
|
||||
if (m.sp >= STACK_SIZE) {
|
||||
return errors.New("Stack Overflow");
|
||||
}
|
||||
|
||||
m.stack[m.sp] = instr.Operand
|
||||
m.sp++
|
||||
m.ip++
|
||||
case POP:
|
||||
if (m.sp <= 0) {
|
||||
return errors.New("Empty Stack")
|
||||
}
|
||||
|
||||
m.stack[m.sp - 1] = 0
|
||||
m.sp--
|
||||
m.ip++
|
||||
case DUP:
|
||||
if (m.sp - instr.Operand <= 0) {
|
||||
return errors.New("Stack Underflow")
|
||||
}
|
||||
|
||||
if (m.sp >= STACK_SIZE) {
|
||||
return errors.New("Stack Overflow");
|
||||
}
|
||||
|
||||
if (instr.Operand < 0) {
|
||||
return errors.New("Illegal access")
|
||||
}
|
||||
|
||||
m.stack[m.sp] = m.stack[m.sp - 1 - instr.Operand]
|
||||
m.sp++
|
||||
m.ip++
|
||||
case ADD:
|
||||
if (m.sp < 2) {
|
||||
return errors.New("Stack size is less than required to execute binary operation")
|
||||
}
|
||||
|
||||
m.stack[m.sp - 2] += m.stack[m.sp - 1];
|
||||
m.sp--
|
||||
m.ip++
|
||||
case SUB:
|
||||
if (m.sp < 2) {
|
||||
return errors.New("Stack size is less than required to execute binary operation")
|
||||
}
|
||||
|
||||
m.stack[m.sp - 2] -= m.stack[m.sp - 1];
|
||||
m.sp--
|
||||
m.ip++
|
||||
case MUL:
|
||||
if (m.sp < 2) {
|
||||
return errors.New("Stack size is less than required to execute binary operation")
|
||||
}
|
||||
|
||||
m.stack[m.sp - 2] *= m.stack[m.sp - 1];
|
||||
m.sp--
|
||||
m.ip++
|
||||
case DIV:
|
||||
if (m.sp < 2) {
|
||||
return errors.New("Stack size is less than required to execute binary operation")
|
||||
}
|
||||
|
||||
if (m.stack[m.sp - 2] == 0 || m.stack[m.sp - 1] == 0) {
|
||||
return errors.New("Divide by zero exception")
|
||||
}
|
||||
|
||||
m.stack[m.sp - 2] /= m.stack[m.sp - 1];
|
||||
m.sp--
|
||||
m.ip++
|
||||
case EQ:
|
||||
if (m.sp < 2) {
|
||||
return errors.New("Stack size is less than required to execute binary operation")
|
||||
}
|
||||
|
||||
eq := m.stack[m.sp - 1] == m.stack[m.sp - 2]
|
||||
if (eq) {
|
||||
m.stack[m.sp - 2] = 1
|
||||
} else {
|
||||
m.stack[m.sp - 2] = 0
|
||||
}
|
||||
|
||||
m.sp--
|
||||
m.ip++
|
||||
case JMP:
|
||||
if (int32(len(m.program) - 1) < instr.Operand || instr.Operand < 0) {
|
||||
return errors.New("Illegal access")
|
||||
}
|
||||
|
||||
m.ip = instr.Operand
|
||||
case JMPIF:
|
||||
if (m.sp < 1) {
|
||||
return errors.New("Stack Underflow")
|
||||
}
|
||||
|
||||
if (int32(len(m.program) - 1) < instr.Operand || instr.Operand < 0) {
|
||||
return errors.New("Illegal access")
|
||||
}
|
||||
|
||||
if (m.stack[m.sp - 1] >= 1) {
|
||||
m.sp--
|
||||
m.ip = instr.Operand
|
||||
} else {
|
||||
m.ip++
|
||||
}
|
||||
case CALL:
|
||||
if (int32(len(m.program) - 1) < instr.Operand || instr.Operand < 0) {
|
||||
return errors.New("Illegal access")
|
||||
}
|
||||
|
||||
m.stackFrame = append(m.stackFrame, CreateFrame(m.ip + 1))
|
||||
m.ip = instr.Operand
|
||||
case RET:
|
||||
if len(m.stackFrame) < 2 {
|
||||
return errors.New("Stackframe underflow")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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++ {
|
||||
fmt.Println(m.stack[i])
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func (m* Machine) DumpProgramBinary(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return errors.New("Couldn't open file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = binary.Write(f, binary.LittleEndian, m.program)
|
||||
if err != nil {
|
||||
return err
|
||||
//return errors.New("Couldn't write to file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m* Machine) LoadProgramFromBinary(filename string) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return errors.New("Couldn't open file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
program := make([]Inst, STACK_SIZE)
|
||||
err = binary.Read(f, binary.LittleEndian, &program)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.program = program
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
/*
|
||||
func main() {
|
||||
m := Constructor()
|
||||
|
||||
// err := m.InterpretProgramFromFile("program.lil")
|
||||
// if err != nil {
|
||||
// fmt.Fprintf(os.Stderr, "[ERR] %s\n", err);
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
stackCounter := 0
|
||||
jumpCounter := 0
|
||||
for !m.isHalted && stackCounter < 100 {
|
||||
|
||||
if (m.program[m.ip].Operation == JMPIF) {
|
||||
jumpCounter++
|
||||
fmt.Printf("Jump Counter: %d \n", jumpCounter)
|
||||
}
|
||||
|
||||
err = m.Execute()
|
||||
//m.Print()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ERR] %s\n", err);
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
stackCounter++
|
||||
}
|
||||
m.Print()
|
||||
}
|
||||
*/
|
||||
525
core/elm_test.go
Normal file
525
core/elm_test.go
Normal file
@@ -0,0 +1,525 @@
|
||||
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 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");
|
||||
}
|
||||
|
||||
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, int32(4), 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], "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")
|
||||
}
|
||||
|
||||
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, 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], "Should skip the pushed value")
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
10
core/go.mod
Normal file
10
core/go.mod
Normal file
@@ -0,0 +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
|
||||
)
|
||||
9
core/go.sum
Normal file
9
core/go.sum
Normal file
@@ -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=
|
||||
Reference in New Issue
Block a user