mirror of
https://github.com/maride/mexico.git
synced 2024-11-10 05:24:25 +00:00
161 lines
4.6 KiB
Go
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
|
|
}
|