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) // 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 ) // 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" } 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. func (machine *Machine) Execute (offset int) { machine.counter = offset for { switch machine.instruction() { case 0x0: // PUSH // push the next word in program memory onto the stack machine.counter ++ machine.Push(machine.instruction()) case 0x1: // POP // pop the top word off of the stack, and discard it machine.Pop() case 0x2: // LOAD // push the word at an address onto the stack machine.Push(machine.Peek(machine.Pop())) case 0x3: // STOR // store a word at an address machine.Poke(machine.Pop(), machine.Pop()) case 0x4: // ADD // adds the last two words on the stack machine.Push(machine.Pop() + machine.Pop()) case 0x5: // SUB // subtracts the second to last word on the stack from // the last word on the stack machine.Push(machine.Pop() - machine.Pop()) case 0x6: // MUL // multiplies the last two words on the stack machine.Push(machine.Pop() * machine.Pop()) case 0x7: // DIV // divides the last word on the stack by the second to // last word on the stack machine.Push(machine.Pop() / machine.Pop()) case 0x8: // 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 0x9: // 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 0xa: // 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 0xb: // 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 0xc: // 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 0xd: // HALT // stops execution return case 0xe: // 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 0xf: // 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 { function(machine) } } machine.counter ++ } } // 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 * 3 / 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() } } // 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) { 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 } } // 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 } // Milk milks the stack machine func (machine *Machine) Milk () {}