Compare commits
10 Commits
bbf055b7c7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
dfd21f0596
|
|||
|
692babe938
|
|||
|
8cd8859c85
|
|||
|
ab0efa2c0c
|
|||
|
a5e5523244
|
|||
|
ce8a04b439
|
|||
|
77322fa901
|
|||
|
9c8ae2aed3
|
|||
|
e3ed6a3af2
|
|||
|
0a8f8dfb8d
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/elm
|
/elm
|
||||||
|
*.bin
|
||||||
|
*.lil
|
||||||
BIN
compiler/compiler
Executable file
BIN
compiler/compiler
Executable file
Binary file not shown.
191
compiler/compiler.go
Normal file
191
compiler/compiler.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode"
|
||||||
|
"bytes"
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
. "e1lama/elm"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func Any[T any](ts []T, pred func(T) bool) bool {
|
||||||
|
for _, t := range ts {
|
||||||
|
if pred(t) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLabelledOperand(line string, preprocessed *PreprocessorResult) (int, error) {
|
||||||
|
op := 0;
|
||||||
|
dest := strings.Fields(line)[1]
|
||||||
|
containsLetter := Any([]rune(dest), unicode.IsLetter)
|
||||||
|
if (containsLetter) {
|
||||||
|
op = preprocessed.LabelMap[dest]
|
||||||
|
} else {
|
||||||
|
op, err := strconv.Atoi(dest)
|
||||||
|
if err != nil {
|
||||||
|
return op, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return op, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var InstructionTable map[string]Operation = map[string]Operation{
|
||||||
|
"HALT": 0,
|
||||||
|
"PUSH": 1,
|
||||||
|
"POP" : 2,
|
||||||
|
"DUP" : 3,
|
||||||
|
"ADD" : 4,
|
||||||
|
"SUB" : 5,
|
||||||
|
"MUL" : 6,
|
||||||
|
"DIV" : 7,
|
||||||
|
"EQ" : 8,
|
||||||
|
"JMP" : 9,
|
||||||
|
"JMPIF" : 10,
|
||||||
|
"CALL" : 11,
|
||||||
|
"RET" : 12,
|
||||||
|
"STORE" : 13,
|
||||||
|
"GET" : 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type PreprocessorResult struct {
|
||||||
|
LabelMap map[string]int
|
||||||
|
Listing *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func PreprocessAssembly(f *os.File) (*PreprocessorResult, error) {
|
||||||
|
labelMap := make(map[string]int)
|
||||||
|
lineCount := 0
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
if (strings.HasSuffix(line, ":")) {
|
||||||
|
labelMap[strings.Split(line, ":")[0]] = lineCount
|
||||||
|
} else {
|
||||||
|
_, ok := InstructionTable[strings.Fields(line)[0]]
|
||||||
|
if (ok) {
|
||||||
|
lineCount++
|
||||||
|
fmt.Fprintln(&buf, line)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Unknown instruction: " + line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PreprocessorResult{ LabelMap: labelMap, Listing: &buf }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProgramFromFile(m* Machine, f *os.File) error {
|
||||||
|
preprocessed, err := PreprocessAssembly(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(preprocessed.Listing)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if (strings.HasPrefix(line, "//")) {
|
||||||
|
continue;
|
||||||
|
} else if (strings.HasPrefix(line, "HALT")){
|
||||||
|
m.Push(Inst{Operation: HALT})
|
||||||
|
} else if (strings.HasPrefix(line, "PUSH")) {
|
||||||
|
op, err := strconv.Atoi(strings.Fields(line)[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: PUSH, Operand: int64(op)})
|
||||||
|
} else if (strings.HasPrefix(line, "POP")) {
|
||||||
|
m.Push(Inst{Operation: POP})
|
||||||
|
} else if (strings.HasPrefix(line, "DUP")) {
|
||||||
|
op, err := strconv.Atoi(strings.Fields(line)[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: DUP, Operand: int64(op)})
|
||||||
|
} else if (strings.HasPrefix(line, "ADD")) {
|
||||||
|
m.Push(Inst{Operation: ADD})
|
||||||
|
} else if (strings.HasPrefix(line, "SUB")) {
|
||||||
|
m.Push(Inst{Operation: SUB})
|
||||||
|
} else if (strings.HasPrefix(line, "DIV")) {
|
||||||
|
m.Push(Inst{Operation: DIV})
|
||||||
|
} else if (strings.HasPrefix(line, "MUL")) {
|
||||||
|
m.Push(Inst{Operation: MUL})
|
||||||
|
} else if (strings.HasPrefix(line, "JMPIF")) {
|
||||||
|
op, err := parseLabelledOperand(line, preprocessed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: JMPIF, Operand: int64(op)})
|
||||||
|
} else if (strings.HasPrefix(line, "JMP")) {
|
||||||
|
op, err := parseLabelledOperand(line, preprocessed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: JMP, Operand: int64(op)})
|
||||||
|
} else if (strings.HasPrefix(line, "EQ")) {
|
||||||
|
m.Push(Inst{Operation: EQ})
|
||||||
|
} else if (strings.HasPrefix(line, "CALL")) {
|
||||||
|
op, err := parseLabelledOperand(line, preprocessed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: CALL, Operand: int64(op)})
|
||||||
|
} else if (strings.HasPrefix(line, "RET")) {
|
||||||
|
m.Push(Inst{Operation: RET})
|
||||||
|
} else if (strings.HasPrefix(line, "STORE")) {
|
||||||
|
op, err := strconv.Atoi(strings.Fields(line)[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: STORE, Operand: int64(op)})
|
||||||
|
} else if (strings.HasPrefix(line, "GET")) {
|
||||||
|
op, err := strconv.Atoi(strings.Fields(line)[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Push(Inst{Operation: GET, Operand: int64(op)})
|
||||||
|
} else {
|
||||||
|
return errors.New("Unknown instruction: " + line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Open("program.lil")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[ERR] Couldn't open file: %s\n", err);
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
m := Constructor()
|
||||||
|
|
||||||
|
err = parseProgramFromFile(m, f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[ERR] %s\n", err);
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
9
compiler/go.mod
Normal file
9
compiler/go.mod
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module e1lama/compiler
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
replace e1lama/elm => ../core
|
||||||
|
|
||||||
|
require e1lama/elm v0.0.0-00010101000000-000000000000
|
||||||
|
|
||||||
|
require golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
||||||
6
compiler/go.sum
Normal file
6
compiler/go.sum
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||||
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
322
core/elm.go
Normal file
322
core/elm.go
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
package elm
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const STACK_SIZE = 1024
|
||||||
|
|
||||||
|
type Operation int64
|
||||||
|
|
||||||
|
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 int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Frame struct {
|
||||||
|
Locals map[int]ElmValue
|
||||||
|
ReturnAddress int
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateFrame(returnAddress int64) *Frame {
|
||||||
|
f := Frame{}
|
||||||
|
f.Locals = make(map[int]ElmValue)
|
||||||
|
f.ReturnAddress = int(returnAddress)
|
||||||
|
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Frame) StoreVariable(address int, value ElmValue) {
|
||||||
|
f.Locals[address] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Frame) GetVariable(address int) ElmValue {
|
||||||
|
return f.Locals[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Machine struct {
|
||||||
|
program []Inst
|
||||||
|
stack []ElmValue
|
||||||
|
stackFrame []*Frame
|
||||||
|
ip int64
|
||||||
|
sp int64
|
||||||
|
isHalted bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Constructor() *Machine {
|
||||||
|
m := Machine{}
|
||||||
|
m.program = make([]Inst, 0)
|
||||||
|
m.stack = make([]ElmValue, 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 add[T INumber] (a T, b T) T {return a + b}
|
||||||
|
func sub[T INumber] (a T, b T) T {return b - a}
|
||||||
|
func div[T INumber] (a T, b T) T {return b / a}
|
||||||
|
func mul[T INumber] (a T, b T) T {return a * b}
|
||||||
|
|
||||||
|
|
||||||
|
func (m* Machine) Execute() error {
|
||||||
|
instr := m.program[m.ip]
|
||||||
|
|
||||||
|
binaryOpNumber := func(fn func (int64, int64) int64) {
|
||||||
|
op1 := m.stack[m.sp - 1].Number
|
||||||
|
op2 := m.stack[m.sp - 2].Number
|
||||||
|
m.stack[m.sp - 2] = Number(fn(op1, op2))
|
||||||
|
m.sp--
|
||||||
|
}
|
||||||
|
|
||||||
|
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] = Number(instr.Operand)
|
||||||
|
m.sp++
|
||||||
|
m.ip++
|
||||||
|
case POP:
|
||||||
|
if (m.sp <= 0) {
|
||||||
|
return errors.New("Empty Stack")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.stack[m.sp - 1] = Number(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")
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryOpNumber(add[int64])
|
||||||
|
m.ip++
|
||||||
|
case SUB:
|
||||||
|
if (m.sp < 2) {
|
||||||
|
return errors.New("Stack size is less than required to execute binary operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryOpNumber(sub[int64])
|
||||||
|
m.ip++
|
||||||
|
case MUL:
|
||||||
|
if (m.sp < 2) {
|
||||||
|
return errors.New("Stack size is less than required to execute binary operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryOpNumber(mul[int64])
|
||||||
|
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].Number == 0 || m.stack[m.sp - 1].Number == 0) {
|
||||||
|
return errors.New("Divide by zero exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryOpNumber(div[int64])
|
||||||
|
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] = Number(1)
|
||||||
|
} else {
|
||||||
|
m.stack[m.sp - 2] = Number(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.sp--
|
||||||
|
m.ip++
|
||||||
|
case JMP:
|
||||||
|
if (int64(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 (int64(len(m.program) - 1) < instr.Operand || instr.Operand < 0) {
|
||||||
|
return errors.New("Illegal access")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.stack[m.sp - 1].Number >= 1) {
|
||||||
|
m.sp--
|
||||||
|
m.ip = instr.Operand
|
||||||
|
} else {
|
||||||
|
m.ip++
|
||||||
|
}
|
||||||
|
case CALL:
|
||||||
|
if (int64(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 = int64(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; int64(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 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
11
core/go.mod
Normal file
11
core/go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
11
core/go.sum
Normal file
11
core/go.sum
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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=
|
||||||
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||||
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||||
|
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=
|
||||||
26
core/values.go
Normal file
26
core/values.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package elm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type INumber interface {
|
||||||
|
constraints.Float | constraints.Integer
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElmValueType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
NULLPTR ElmValueType = 0
|
||||||
|
NUMBER ElmValueType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type ElmValue struct {
|
||||||
|
Type ElmValueType
|
||||||
|
Number int64
|
||||||
|
Null int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func Number(val int64) ElmValue {
|
||||||
|
return ElmValue{Type: NUMBER, Number: val }
|
||||||
|
}
|
||||||
204
elm.go
204
elm.go
@@ -1,204 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const STACK_SIZE = 1024
|
|
||||||
|
|
||||||
type Operation int
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
type Inst struct {
|
|
||||||
Operation Operation
|
|
||||||
Operand int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Machine struct {
|
|
||||||
program []Inst
|
|
||||||
stack []int
|
|
||||||
ip int
|
|
||||||
sp int
|
|
||||||
isHalted bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Constructor() *Machine {
|
|
||||||
m := Machine{}
|
|
||||||
m.program = make([]Inst, 0)
|
|
||||||
m.stack = make([]int, STACK_SIZE)
|
|
||||||
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] = 0
|
|
||||||
m.sp--
|
|
||||||
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++
|
|
||||||
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 (m.ip < 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 (m.ip < 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++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m* Machine) Print() {
|
|
||||||
fmt.Println("Stack:");
|
|
||||||
for i := 0; i < m.sp; i++ {
|
|
||||||
fmt.Println(m.stack[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
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: PUSH, Operand: 2})
|
|
||||||
// m.Push(Inst{Operation: SUB})
|
|
||||||
// m.Push(Inst{Operation: PUSH, Operand: 6})
|
|
||||||
// m.Push(Inst{Operation: MUL})
|
|
||||||
// m.Push(Inst{Operation: PUSH, Operand: 2})
|
|
||||||
// m.Push(Inst{Operation: DIV})
|
|
||||||
// m.Push(Inst{Operation: HALT})
|
|
||||||
|
|
||||||
m.Push(Inst{Operation: PUSH, Operand: 0})
|
|
||||||
m.Push(Inst{Operation: PUSH, Operand: 1})
|
|
||||||
m.Push(Inst{Operation: ADD})
|
|
||||||
m.Push(Inst{Operation: JMP, Operand: 1})
|
|
||||||
m.Push(Inst{Operation: HALT})
|
|
||||||
|
|
||||||
stackCounter := 0
|
|
||||||
for !m.isHalted && stackCounter < 69 {
|
|
||||||
m.Print()
|
|
||||||
err := m.Execute()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "[ERR] %s\n", err);
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
stackCounter++
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
interpreter/go.mod
Normal file
7
interpreter/go.mod
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module e1lama/interpreter
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
replace e1lama/elm => ../core
|
||||||
|
|
||||||
|
require e1lama/elm v0.0.0-00010101000000-000000000000
|
||||||
4
interpreter/go.sum
Normal file
4
interpreter/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
37
interpreter/interpreter.go
Normal file
37
interpreter/interpreter.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode"
|
||||||
|
"bytes"
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
. "e1lama/elm"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func LoadProgramFromBinary(m *Machine, f *os.File) error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Open("program.elb")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[ERR] Couldn't open file: %s\n", err);
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
m := Constructor()
|
||||||
|
|
||||||
|
err = LoadProgramFromBinary(m, f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[ERR] Error reading binary: %s\n", err);
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user