300 lines
7.4 KiB
Go
300 lines
7.4 KiB
Go
package creature
|
|
|
|
// Machine is a stack machine. It contains an array of integers as its program
|
|
// data, and provides methods to run this program data, as well as interact with
|
|
// it.
|
|
type Machine struct {
|
|
// Program is not modified by the machine, and can be freely set before
|
|
// the machine is started.
|
|
Program []int
|
|
|
|
stack []int
|
|
block []int
|
|
counter int
|
|
pointer int
|
|
functions map[int]MachineFunction
|
|
}
|
|
|
|
// MachineFunction is a function that can extend the functionality of the stack
|
|
// machine. It is passed a pointer to the machine that is calling it, and the
|
|
// machine will halt execution if true is returned.
|
|
type MachineFunction func(machine *Machine) (stop bool)
|
|
|
|
// All supported opcodes
|
|
const (
|
|
PUSH = 0x0
|
|
POP = 0x1
|
|
PEEK = 0x2
|
|
POKE = 0x3
|
|
ADD = 0x4
|
|
SUB = 0x5
|
|
MUL = 0x6
|
|
DIV = 0x7
|
|
EQ = 0x8
|
|
GT = 0x9
|
|
LT = 0xa
|
|
NEQ = 0xb
|
|
MOD = 0xc
|
|
HALT = 0xd
|
|
JMP = 0xe
|
|
CAL = 0xf
|
|
)
|
|
|
|
// Error is an error type that can be returned from the machine's methods if
|
|
// they experience problems.
|
|
type Error int
|
|
|
|
const (
|
|
// ErrorIDTaken is returned by Register when a new function is
|
|
// registered with the same ID as another one.
|
|
ErrorIDTaken Error = iota
|
|
ErrorUnknownInstruction
|
|
ErrorDivideByZero
|
|
)
|
|
|
|
// Error returns a textual description of the error.
|
|
func (err Error) Error() (description string) {
|
|
switch err {
|
|
case ErrorIDTaken:
|
|
description = "this ID is taken by another function"
|
|
case ErrorUnknownInstruction:
|
|
description = "machine encountered unknown instruction"
|
|
case ErrorDivideByZero:
|
|
description = "cannot divide by zero"
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Reset resets the stack of the machine. This should be called before Execute,
|
|
// since Execute does not reset the stack.
|
|
func (machine *Machine) Reset() {
|
|
machine.stack = nil
|
|
machine.pointer = 0
|
|
}
|
|
|
|
// Execute starts execution at the address specified by offset. The machine will
|
|
// run until a HALT instruction is encountered, or the end of program memory is
|
|
// reached. If an unknown instruction is encountered, the machine halts and
|
|
// returns an error.
|
|
func (machine *Machine) Execute(offset int) (err error) {
|
|
machine.counter = offset
|
|
|
|
for machine.counter < len(machine.Program) {
|
|
switch machine.instruction() {
|
|
case PUSH:
|
|
// push the next word in program memory onto the stack
|
|
machine.counter++
|
|
machine.Push(machine.instruction())
|
|
|
|
case POP:
|
|
// pop the top word off of the stack, and discard it
|
|
machine.Pop()
|
|
|
|
case PEEK:
|
|
// push the word at an address onto the stack
|
|
machine.Push(machine.Peek(machine.Pop()))
|
|
|
|
case POKE:
|
|
// store a word at an address
|
|
address := machine.Pop()
|
|
word := machine.Pop()
|
|
machine.Poke(address, word)
|
|
|
|
case ADD:
|
|
// adds the last two words on the stack
|
|
machine.Push(machine.Pop() + machine.Pop())
|
|
|
|
case SUB:
|
|
// subtracts the second to last word on the stack from
|
|
// the last word on the stack
|
|
machine.Push(machine.Pop() - machine.Pop())
|
|
|
|
case MUL:
|
|
// multiplies the last two words on the stack
|
|
machine.Push(machine.Pop() * machine.Pop())
|
|
|
|
case DIV:
|
|
// divides the last word on the stack by the second to
|
|
// last word on the stack
|
|
left := machine.Pop()
|
|
right := machine.Pop()
|
|
if right == 0 {
|
|
err = ErrorDivideByZero
|
|
return
|
|
}
|
|
machine.Push(left / right)
|
|
|
|
case EQ:
|
|
// checks if the last two words on the stack are equal
|
|
equal := 0
|
|
if machine.Pop() == machine.Pop() {
|
|
equal = 1
|
|
}
|
|
machine.Push(equal)
|
|
|
|
case GT:
|
|
// checks if the last word on the stack is greater than
|
|
// the second to last word on the stack
|
|
greater := 0
|
|
if machine.Pop() > machine.Pop() {
|
|
greater = 1
|
|
}
|
|
machine.Push(greater)
|
|
|
|
case LT:
|
|
// checks if the last word on the stack is less than the
|
|
// second to last word on the stack
|
|
less := 0
|
|
if machine.Pop() < machine.Pop() {
|
|
less = 1
|
|
}
|
|
machine.Push(less)
|
|
|
|
case NEQ:
|
|
// checks if the last two words on the stack are not
|
|
// equal
|
|
notEqual := 0
|
|
if machine.Pop() != machine.Pop() {
|
|
notEqual = 1
|
|
}
|
|
machine.Push(notEqual)
|
|
|
|
case MOD:
|
|
// performs a modulo operation of the second to last
|
|
// word on the stack to the last word on the stack
|
|
machine.Push(machine.Pop() % machine.Pop())
|
|
|
|
case HALT:
|
|
// stops execution
|
|
return
|
|
|
|
case JMP:
|
|
// jump to the address specified by the last word on the
|
|
// stack if the second to last word on the stack is
|
|
// nonzero
|
|
jumpTo := machine.Pop()
|
|
if machine.Pop() != 0 {
|
|
machine.counter = jumpTo - 1
|
|
}
|
|
|
|
case CAL:
|
|
// call an implementation-defined subroutine, with the
|
|
// id specified by the last word on the stack. this may
|
|
// push and pop various things from the stack
|
|
id := machine.Pop()
|
|
if machine.functions == nil {
|
|
break
|
|
}
|
|
function, exists := machine.functions[id]
|
|
if exists {
|
|
if function(machine) {
|
|
return
|
|
}
|
|
}
|
|
|
|
default:
|
|
err = ErrorUnknownInstruction
|
|
return
|
|
}
|
|
machine.counter++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Instruction returns the current instruction in program memory.
|
|
func (machine *Machine) instruction() (instruction int) {
|
|
instruction = machine.Program[machine.counter]
|
|
return
|
|
}
|
|
|
|
// reallocateStack changes the size of the stack to something reasonable. This
|
|
// should be called then the stack pointer is really small compared to the
|
|
// actual stack size, or the stack pointer is bigger than the stack.
|
|
func (machine *Machine) reallocateStack() {
|
|
reallocatedStack := make([]int, machine.pointer*2)
|
|
copy(reallocatedStack, machine.stack)
|
|
machine.stack = reallocatedStack
|
|
}
|
|
|
|
// Push pushes a word onto the stack, increasing the stack pointer and
|
|
// reallocating the stack if necessary.
|
|
func (machine *Machine) Push(word int) {
|
|
machine.pointer++
|
|
if len(machine.stack) <= machine.pointer {
|
|
machine.reallocateStack()
|
|
}
|
|
machine.stack[machine.pointer] = word
|
|
}
|
|
|
|
// Pop pops the last word off of the stack, and returns it, decreasing the stack
|
|
// pointer and reallocating the stack if necessary.
|
|
func (machine *Machine) Pop() (word int) {
|
|
if machine.pointer <= 0 || machine.pointer >= len(machine.stack) {
|
|
return
|
|
}
|
|
|
|
word = machine.stack[machine.pointer]
|
|
machine.pointer--
|
|
|
|
if machine.pointer < len(machine.stack)/3 {
|
|
machine.reallocateStack()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Peek returns the word at address in the block.
|
|
func (machine *Machine) Peek(address int) (word int) {
|
|
if address < len(machine.block) {
|
|
word = machine.block[address]
|
|
}
|
|
return
|
|
}
|
|
|
|
// Poke sets the value at address in the block to word.
|
|
func (machine *Machine) Poke(address int, word int) {
|
|
if address >= len(machine.block) {
|
|
reallocatedBlock := make([]int, address*3/2)
|
|
copy(reallocatedBlock, machine.block)
|
|
machine.block = reallocatedBlock
|
|
}
|
|
machine.block[address] = word
|
|
}
|
|
|
|
// Register registers a function at the specified ID. If there is already a
|
|
// function registered at that ID, this method will return an error.
|
|
func (machine *Machine) Register(id int, function MachineFunction) (err error) {
|
|
if machine.functions == nil {
|
|
machine.functions = make(map[int]MachineFunction)
|
|
}
|
|
|
|
_, exists := machine.functions[id]
|
|
if exists {
|
|
err = ErrorIDTaken
|
|
return
|
|
}
|
|
|
|
machine.functions[id] = function
|
|
|
|
return
|
|
}
|
|
|
|
// Unregister removes a function that is registered at the specified ID.
|
|
func (machine *Machine) Unregister(id int) {
|
|
if machine.functions == nil {
|
|
return
|
|
}
|
|
delete(machine.functions, id)
|
|
}
|
|
|
|
// LoadMemory loads the contents of block into the machine's memory.
|
|
func (machine *Machine) LoadMemory(block []int) {
|
|
machine.block = make([]int, len(block))
|
|
copy(machine.block, block)
|
|
}
|
|
|
|
// Milk milks the stack machine.
|
|
func (machine *Machine) Milk() {}
|