Init commit

This commit is contained in:
2019-12-08 17:55:49 +01:00
commit 3c7376cca6
15 changed files with 977 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
package interpreter
type Codeline struct {
Linenumber int
Code string
}

View File

@@ -0,0 +1,20 @@
package interpreter
import (
"fmt"
"log"
)
func (t *Tape) DebugPrintTape() {
log.Printf("Tape is currently %d cells big", len(t.cells))
for i, v := range t.cells {
fmt.Printf("Cell %d: %d (%c)\n", i, v, v)
}
}
func (s * Stack) DebugPrintStack() {
log.Printf("Stack is currently %d entries big", len(s.values))
for i, v := range s.values {
fmt.Printf("Stack row %d: %d (%c)\n", i, v, v)
}
}

View File

@@ -0,0 +1,89 @@
package interpreter
import (
"fmt"
"github.com/pkg/errors"
)
type Interpreter struct {
machine Machine
program []Codeline
programCounter int
programPointer int
}
// Feeds a new mexico machine with given code.
func Run(commands []Codeline) error {
var i Interpreter
// Set the given code as commands for the interpreter
setCmdErr := i.SetCommands(commands)
if setCmdErr != nil {
return setCmdErr
}
// Let's run this program :)
return i.Run()
}
// Sets the given array as new program for the interpreter
func (i *Interpreter) SetCommands(commands []Codeline) error {
i.program = commands
i.programCounter = 0
return i.GoToNextCommand()
}
// Searches for the next command, starting from the current value of the programCounter.
// This may sound odd, because in most other architectures, this is just programCounter++, and there would be no need
// for a function like this. However, mexico has a BASIC-style program line numbering, means we need to search for the
// next line number containing code, because there may be one or more empty lines between the current and the next line.
// This is exactly what GoToNextCommand() does.
// If there is no next command, most likely because we reached the end of the program, an error is thrown.
func (i *Interpreter) GoToNextCommand() error {
// Iterate over all lines to find the first which has a greater line number than the current one
for index, line := range i.program {
if line.Linenumber >= i.programCounter {
// Found, set and return
i.programCounter = line.Linenumber
i.programPointer = index
return nil
}
}
// No next command found. Throw error.
return errors.New(fmt.Sprintf("Found no commands after line %d. Stopping.", i.programCounter))
}
// Runs the commands, unless an error is encountered, then it doesn't run the commands.
func (i *Interpreter) Run() error {
defer i.machine.Tape.DebugPrintTape()
defer i.machine.Stack.DebugPrintStack()
for {
// Get current command
cmd := i.program[i.programPointer]
// Run command in the machine
jumpLine, doJump, runErr := i.machine.RunCommand(cmd.Code)
if runErr != nil {
// Encountered an error during runtime, stop execution
return runErr
}
// Check if we should jump anywhere else than to the next code line
if doJump {
// Yes, do it then.
i.programCounter = jumpLine
} else {
// We are not asked to jump anywhere, move on to the next code line then.
i.programCounter++
}
// Skip empty lines if there are any.
nextCmdErr := i.GoToNextCommand()
if nextCmdErr != nil {
// Encountered error finding the next command, throw it
return nextCmdErr
}
}
}

View File

@@ -0,0 +1,160 @@
package interpreter
import (
"fmt"
"github.com/pkg/errors"
"os"
"strconv"
"strings"
)
type Machine struct {
Stack Stack
Tape Tape
}
// Runs the given command.
// Returns the next line (comparable to the 'Program Counter') to be executed, but just if doJump is true.
// May also return an error. It's advised to stop the execution of further commands if this command throws an error.
func (m *Machine) RunCommand(cmd string) (jumpLine int, doJump bool, execErr error) {
// Let's check which command we are told to run.
if cmd == "left" {
// Moves the tape head one cell to the left
m.Tape.MoveLeft()
} else if cmd == "right" {
// Moves the tape head one cell to the right
m.Tape.MoveRight()
} else if cmd == "pusht" {
// Reads the current cell value and pushes it on top of the stack
m.Stack.Push(m.Tape.Get())
} else if strings.HasPrefix(cmd, "push ") {
// Pushes the value n to the stack
// Cut "push " away and trim
strVal := strings.Trim(cmd[5:], " ")
// Convert to integer
intVal, atoiErr := strconv.Atoi(strVal)
if atoiErr != nil {
// Conversion failed.
execErr = errors.New(fmt.Sprintf("Tried to push non-integer value '%s' to the stack. %s", strVal, atoiErr.Error()))
return
} else {
// Conversion successful, push constant
m.Stack.Push(intVal)
}
} else if cmd == "pop" {
// Pops top stack value to the current cell
m.Tape.Set(m.Stack.Pop())
} else if cmd == "dup" {
// Duplicates the topmost stack value
val := m.Stack.Pop()
m.Stack.Push(val)
m.Stack.Push(val)
} else if cmd == "del" {
// Deletes the topmost stack value, ignoring its value
m.Stack.Pop()
} else if cmd == "eq" {
// Checks if stack[0] == stack[1]. Pushes 1 to the stack if equal, 0 otherwise
stack0 := m.Stack.Pop()
stack1 := m.Stack.Pop()
if stack0 == stack1 {
m.Stack.Push(1)
} else {
m.Stack.Push(0)
}
} else if cmd == "not" {
// Inverses stack[0]
stack0 := m.Stack.Pop()
if stack0 == 0 {
m.Stack.Push(1)
} else if stack0 == 1 {
m.Stack.Push(0)
} else {
// Not a binary number, not going to inverse it.
execErr = errors.New(fmt.Sprintf("Tried to inverse non-binary integer value '%d'", stack0))
return
}
} else if cmd == "gt" {
// Checks if stack[0] > stack[1]. Pushes 1 to the stack if greater, 0 otherwise
stack0 := m.Stack.Pop()
stack1 := m.Stack.Pop()
if stack0 > stack1 {
m.Stack.Push(1)
} else {
m.Stack.Push(0)
}
} else if cmd == "lt" {
// Checks if stack[0] < stack[1]. Pushes 1 to the stack if greater, 0 otherwise
stack0 := m.Stack.Pop()
stack1 := m.Stack.Pop()
if stack0 < stack1 {
m.Stack.Push(1)
} else {
m.Stack.Push(0)
}
} else if cmd == "add" {
// Calculates stack[0] + stack[1], and pushes the result to the stack
m.Stack.Push(m.Stack.Pop() + m.Stack.Pop())
} else if cmd == "sub" {
// Calculates stack[0] - stack[1], and pushes the result to the stack
m.Stack.Push(m.Stack.Pop() - m.Stack.Pop())
} else if cmd == "mult" {
// Calculates stack[0] * stack[1], and pushes the result to the stack
m.Stack.Push(m.Stack.Pop() * m.Stack.Pop())
} else if cmd == "div" {
// Calculates stack[0] / stack[1], and pushes the result to the stack
m.Stack.Push(m.Stack.Pop() / m.Stack.Pop())
} else if cmd == "mod" {
// Calculates stack[0] % stack[1], and pushes the result to the stack
m.Stack.Push(m.Stack.Pop() % m.Stack.Pop())
} else if cmd == "read" {
// Reads a character from the user, and pushes its char value to the stack
var readChar []byte
// Read character(s)
numChars, readErr := os.Stdin.Read(readChar)
if readErr != nil {
// Failed to read from stdin.
execErr = errors.New(fmt.Sprintf("Failed to read character from stdin: %s", readErr.Error()))
return
}
if numChars < 1 || readChar == nil {
// Didn't even read a single character.
execErr = errors.New("Failed to read character from stdin.")
return
}
// Push character to stack
m.Stack.Push(int(readChar[0]))
// Check if user typed more than asked for
if numChars > 1 {
// Ugh, what a spammer
execErr = errors.New("Read more than one character - ignoring all but the first character.")
return
}
} else if cmd == "print" {
// Prints stack[0] as a character
val := m.Stack.Pop()
fmt.Printf("%q (%d)\n", rune(val), val)
} else if cmd == "jmp" {
// Jumps to the line number specified by stack[0]
jumpLine = m.Stack.Pop()
doJump = true
} else if cmd == "jmpc" {
// Jumps to the line number specified by stack[0], if stack[1] is not 0.
jumpLine = m.Stack.Pop()
doJump = m.Stack.Pop() != 0
} else {
// ... no such command.
execErr = errors.New(fmt.Sprintf("Command not found: %s", cmd))
}
return
}

View File

@@ -0,0 +1,27 @@
package interpreter
import "log"
type Stack struct {
values []int
}
// Pushes the given value to the stack
func (s *Stack) Push(val int) {
s.values = append(s.values, val)
}
// Pops the top element from the stack and return its value
func (s *Stack) Pop() int {
// Check if the stack contains at least one element
if len(s.values) > 0 {
// There's at least one element, pop it: get value and delete element
val := s.values[len(s.values)-1]
s.values = s.values[:len(s.values)-1]
return val
}
// Stack is empty, but we should pop... Damn.
log.Panic("Tried to pop value from empty stack.")
return 0
}

View File

@@ -0,0 +1,51 @@
package interpreter
type Tape struct {
head uint
cells []int
}
// Sets the head to point to the specified position
func (t *Tape) SetHead(pos uint) {
t.GrowUpTo(pos)
t.head = pos
}
// Resizes the tape to the given size.
// If the cell array is already bigger than the specified size, nothing happens.
// If the cell array is smaller than the specified size, it's resized to the given size and filled with the value 0.
func (t *Tape) GrowUpTo(size uint) {
if uint(len(t.cells)) > size {
// Tape is already bigger. Do nothing.
return
}
// Append as many zeroes as required.
for uint(len(t.cells)) <= size {
t.cells = append(t.cells, 0)
}
}
// Moves the tape head left
func (t *Tape) MoveLeft() {
if t.head > 0 {
t.SetHead(t.head - 1)
}
}
// Moves the tape head left
func (t *Tape) MoveRight() {
t.SetHead(t.head + 1)
}
// Returns the current cell value
func (t *Tape) Get() int {
t.GrowUpTo(t.head)
return t.cells[t.head]
}
// Sets the current cell to the new value
func (t *Tape) Set(newVal int) {
t.GrowUpTo(t.head)
t.cells[t.head] = newVal
}