mexico/mexigo/interpreter/machine.go
2019-12-08 17:55:49 +01:00

161 lines
4.6 KiB
Go

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
}