creature/creature.go

267 lines
6.7 KiB
Go
Raw Normal View History

2022-08-28 17:25:00 +00:00
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.
2022-08-28 17:25:00 +00:00
type Machine struct {
// Program is not modified by the machine, and can be freely set before
// the machine is started.
2022-08-28 21:52:39 +00:00
Program []int
stack []int
block []int
counter int
pointer int
2022-08-28 21:52:39 +00:00
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.
2022-08-28 21:52:39 +00:00
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.
2022-08-28 21:52:39 +00:00
func (err Error) Error() (description string) {
switch err {
case ErrorIDTaken:
description = "this ID is taken by another function"
}
return
2022-08-28 17:25:00 +00:00
}
2022-08-28 21:35:30 +00:00
// Reset resets the stack of the machine. This should be called before Execute,
// since Execute does not reset the stack.
2022-08-28 21:52:39 +00:00
func (machine *Machine) Reset() {
machine.stack = nil
2022-08-28 21:35:30 +00:00
machine.pointer = 0
}
2022-08-28 17:30:54 +00:00
// 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.
2022-08-28 21:52:39 +00:00
func (machine *Machine) Execute(offset int) {
2022-08-28 17:25:00 +00:00
machine.counter = offset
for machine.counter < len(machine.Program) {
2022-08-28 17:30:54 +00:00
switch machine.instruction() {
2022-08-28 17:25:00 +00:00
case 0x0:
2022-08-28 17:30:54 +00:00
// PUSH
// push the next word in program memory onto the stack
2022-08-28 21:52:39 +00:00
machine.counter++
2022-08-28 17:30:54 +00:00
machine.Push(machine.instruction())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x1:
2022-08-28 17:30:54 +00:00
// POP
// pop the top word off of the stack, and discard it
machine.Pop()
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x2:
// LOAD
// push the word at an address onto the stack
machine.Push(machine.Peek(machine.Pop()))
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x3:
// STOR
// store a word at an address
machine.Poke(machine.Pop(), machine.Pop())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x4:
// ADD
// adds the last two words on the stack
machine.Push(machine.Pop() + machine.Pop())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x5:
// SUB
2022-08-28 20:59:11 +00:00
// subtracts the second to last word on the stack from
// the last word on the stack
machine.Push(machine.Pop() - machine.Pop())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x6:
// MUL
// multiplies the last two words on the stack
machine.Push(machine.Pop() * machine.Pop())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x7:
// DIV
2022-08-28 20:59:11 +00:00
// divides the last word on the stack by the second to
// last word on the stack
machine.Push(machine.Pop() / machine.Pop())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x8:
2022-08-28 21:03:15 +00:00
// EQ
2022-08-28 20:59:11 +00:00
// checks if the last two words on the stack are equal
2022-08-28 21:03:15 +00:00
equal := 0
2022-08-28 21:52:39 +00:00
if machine.Pop() == machine.Pop() {
equal = 1
}
2022-08-28 21:03:15 +00:00
machine.Push(equal)
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0x9:
2022-08-28 21:03:15 +00:00
// GT
// checks if the last word on the stack is greater than
// the second to last word on the stack
greater := 0
2022-08-28 21:52:39 +00:00
if machine.Pop() > machine.Pop() {
greater = 1
}
2022-08-28 21:03:15 +00:00
machine.Push(greater)
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0xa:
2022-08-28 21:03:15 +00:00
// LT
// checks if the last word on the stack is less than the
// second to last word on the stack
less := 0
2022-08-28 21:52:39 +00:00
if machine.Pop() > machine.Pop() {
less = 1
}
2022-08-28 21:03:15 +00:00
machine.Push(less)
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0xb:
2022-08-28 21:03:15 +00:00
// NEQ
// checks if the last two words on the stack are not
// equal
notEqual := 0
2022-08-28 21:52:39 +00:00
if machine.Pop() != machine.Pop() {
notEqual = 1
}
2022-08-28 21:03:15 +00:00
machine.Push(notEqual)
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
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())
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
case 0xd:
// HALT
// stops execution
return
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
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
}
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
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()
2022-08-28 21:52:39 +00:00
if machine.functions == nil {
break
}
function, exists := machine.functions[id]
if exists {
function(machine)
}
2022-08-28 17:25:00 +00:00
}
2022-08-28 21:52:39 +00:00
machine.counter++
2022-08-28 17:25:00 +00:00
}
}
2022-08-28 17:30:54 +00:00
// Instruction returns the current instruction in program memory.
2022-08-28 21:52:39 +00:00
func (machine *Machine) instruction() (instruction int) {
2022-08-28 17:30:54 +00:00
instruction = machine.Program[machine.counter]
return
}
2022-08-28 17:25:00 +00:00
// 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.
2022-08-28 21:52:39 +00:00
func (machine *Machine) reallocateStack() {
reallocatedStack := make([]int, machine.pointer*3/2)
2022-08-28 17:25:00 +00:00
copy(reallocatedStack, machine.stack)
machine.stack = reallocatedStack
}
// Push pushes a word onto the stack, increasing the stack pointer and
// reallocating the stack if necessary.
2022-08-28 21:52:39 +00:00
func (machine *Machine) Push(word int) {
machine.pointer++
2022-08-28 17:25:00 +00:00
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.
2022-08-28 21:52:39 +00:00
func (machine *Machine) Pop() (word int) {
2022-08-28 17:25:00 +00:00
word = machine.stack[machine.pointer]
2022-08-28 21:52:39 +00:00
machine.pointer--
2022-08-28 17:25:00 +00:00
2022-08-28 21:52:39 +00:00
if machine.pointer < len(machine.stack)/3 {
2022-08-28 17:25:00 +00:00
machine.reallocateStack()
}
2022-08-28 21:52:39 +00:00
2022-08-28 17:25:00 +00:00
return
}
2022-08-28 21:52:39 +00:00
// 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
}
2022-08-28 21:52:39 +00:00
// Poke sets the value at address in the block to word.
func (machine *Machine) Poke(address int, word int) {
if address >= len(machine.block) {
2022-08-28 21:52:39 +00:00
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.
2022-08-28 21:52:39 +00:00
func (machine *Machine) Register(id int, function MachineFunction) (err error) {
if machine.functions == nil {
2022-08-28 21:52:39 +00:00
machine.functions = make(map[int]MachineFunction)
}
_, exists := machine.functions[id]
if exists {
err = ErrorIDTaken
return
}
machine.functions[id] = function
return
}
2022-08-28 21:36:45 +00:00
// Unregister removes a function that is registered at the specified ID.
2022-08-28 21:52:39 +00:00
func (machine *Machine) Unregister(id int) {
if machine.functions == nil {
return
}
2022-08-28 21:36:45 +00:00
delete(machine.functions, id)
}
// LoadMemory loads the contents of block into the machine's memory.
2022-08-28 21:52:39 +00:00
func (machine *Machine) LoadMemory(block []int) {
machine.block = make([]int, len(block))
copy(machine.block, block)
}
2022-08-28 21:52:39 +00:00
// Milk milks the stack machine.
func (machine *Machine) Milk() {}