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() {}