package creature // Word is a type constraint defining possible word types for the Machine. type Word interface { int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | int | uint } // 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[WORD Word] struct { program []WORD stack []WORD block []WORD counter WORD pointer WORD functions map[WORD]MachineFunction[WORD] } // 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[WORD Word] func(machine *Machine[WORD]) (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[WORD]) 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[WORD]) Execute(offset WORD) (err error) { machine.counter = offset for int(machine.counter) < len(machine.program) { switch machine.instruction() { case PUSH: machine.counter++ machine.Push(machine.instruction()) case POP: machine.Pop() case PEEK: machine.Push(machine.Peek(machine.Pop())) case POKE: address := machine.Pop() word := machine.Pop() machine.Poke(address, word) case ADD: right := machine.Pop() left := machine.Pop() machine.Push(left + right) case SUB: right := machine.Pop() left := machine.Pop() machine.Push(left - right) case MUL: right := machine.Pop() left := machine.Pop() machine.Push(left * right) case DIV: right := machine.Pop() left := machine.Pop() if right == 0 { err = ErrorDivideByZero return } machine.Push(left / right) case EQ: right := machine.Pop() left := machine.Pop() equal := 0 if left == right { equal = 1 } machine.Push(WORD(equal)) case GT: right := machine.Pop() left := machine.Pop() greater := 0 if left > right { greater = 1 } machine.Push(WORD(greater)) case LT: right := machine.Pop() left := machine.Pop() less := 0 if left < right { less = 1 } machine.Push(WORD(less)) case NEQ: right := machine.Pop() left := machine.Pop() notEqual := 0 if left != right { notEqual = 1 } machine.Push(WORD(notEqual)) case MOD: right := machine.Pop() left := machine.Pop() machine.Push(left % right) case HALT: return case JMP: jumpTo := machine.Pop() if machine.Pop() != 0 { machine.counter = jumpTo - 1 } case CAL: 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[WORD]) instruction() (instruction WORD) { 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[WORD]) reallocateStack() { reallocatedStack := make([]WORD, 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[WORD]) Push(word WORD) { machine.pointer++ if len(machine.stack) <= int(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[WORD]) Pop() (word WORD) { if int(machine.pointer) <= 0 || int(machine.pointer) >= len(machine.stack) { return } word = machine.stack[machine.pointer] machine.pointer-- if int(machine.pointer) < len(machine.stack)/3 { machine.reallocateStack() } return } // Peek returns the word at address in the block. func (machine *Machine[WORD]) Peek(address WORD) (word WORD) { if int(address) < len(machine.block) { word = machine.block[address] } return } // Poke sets the value at address in the block to word. func (machine *Machine[WORD]) Poke(address WORD, word WORD) { if int(address) >= len(machine.block) { reallocatedBlock := make([]WORD, address*3/2+1) 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[WORD]) Register( id WORD, function MachineFunction[WORD], ) ( err error, ) { if machine.functions == nil { machine.functions = make(map[WORD]MachineFunction[WORD]) } _, 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[WORD]) Unregister(id WORD) { if machine.functions == nil { return } delete(machine.functions, id) } // LoadProgram loads the contents of program into the machine's program memory. func (machine *Machine[WORD]) LoadProgram(program []WORD) { machine.program = make([]WORD, len(program)) copy(machine.program, program) } // LoadMemory loads the contents of block into the machine's memory. func (machine *Machine[WORD]) LoadMemory(block []WORD) { machine.block = make([]WORD, len(block)) copy(machine.block, block) } // Milk milks the stack machine. func (machine *Machine[WORD]) Milk() {}