creature/creature.go

305 lines
7.0 KiB
Go
Raw Permalink Normal View History

2022-08-28 17:25:00 +00:00
package creature
2022-08-29 14:48:45 +00:00
// Word is a type constraint defining possible word types for the Machine.
type Word interface {
2022-09-08 18:43:19 +00:00
int8 | int16 | int32 | int64 |
uint8 | uint16 | uint32 | uint64 |
int | uint
2022-08-29 14:48:45 +00:00
}
// 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-09-08 18:43:19 +00:00
type Machine[WORD Word] struct {
program []WORD
2022-08-29 14:48:45 +00:00
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.
2022-09-08 18:43:19 +00:00
type MachineFunction[WORD Word] func(machine *Machine[WORD]) (stop bool)
2022-08-28 22:34:23 +00:00
// All supported opcodes
const (
PUSH = 0x0
POP = 0x1
PEEK = 0x2
POKE = 0x3
2022-08-28 22:34:23 +00:00
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
2022-08-28 22:20:09 +00:00
ErrorDivideByZero
)
// 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"
case ErrorUnknownInstruction:
description = "machine encountered unknown instruction"
2022-08-28 22:20:09 +00:00
case ErrorDivideByZero:
description = "cannot divide by zero"
}
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-29 14:48:45 +00:00
func (machine *Machine[WORD]) Reset() {
2022-08-28 21:52:39 +00:00
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. If an unknown instruction is encountered, the machine halts and
// returns an error.
2022-08-29 14:48:45 +00:00
func (machine *Machine[WORD]) Execute(offset WORD) (err error) {
2022-08-28 17:25:00 +00:00
machine.counter = offset
for int(machine.counter) < len(machine.program) {
2022-08-28 17:30:54 +00:00
switch machine.instruction() {
2022-08-28 22:34:23 +00:00
case PUSH:
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 22:34:23 +00:00
case POP:
2022-08-28 17:30:54 +00:00
machine.Pop()
2022-08-28 21:52:39 +00:00
case PEEK:
machine.Push(machine.Peek(machine.Pop()))
2022-08-28 21:52:39 +00:00
case POKE:
2022-08-29 04:43:13 +00:00
address := machine.Pop()
word := machine.Pop()
machine.Poke(address, word)
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case ADD:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
machine.Push(left + right)
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case SUB:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
machine.Push(left - right)
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case MUL:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
machine.Push(left * right)
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case DIV:
2022-08-28 22:20:09 +00:00
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
2022-08-28 23:59:00 +00:00
if right == 0 {
2022-08-28 22:20:09 +00:00
err = ErrorDivideByZero
return
}
machine.Push(left / right)
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case EQ:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
2022-08-28 21:03:15 +00:00
equal := 0
if left == right {
2022-08-28 21:52:39 +00:00
equal = 1
}
2022-08-29 14:48:45 +00:00
machine.Push(WORD(equal))
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case GT:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
2022-08-28 21:03:15 +00:00
greater := 0
if left > right {
2022-08-28 21:52:39 +00:00
greater = 1
}
2022-08-29 14:48:45 +00:00
machine.Push(WORD(greater))
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case LT:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
2022-08-28 21:03:15 +00:00
less := 0
if left < right {
2022-08-28 21:52:39 +00:00
less = 1
}
2022-08-29 14:48:45 +00:00
machine.Push(WORD(less))
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case NEQ:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
2022-08-28 21:03:15 +00:00
notEqual := 0
if left != right {
2022-08-28 21:52:39 +00:00
notEqual = 1
}
2022-08-29 14:48:45 +00:00
machine.Push(WORD(notEqual))
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case MOD:
right := machine.Pop()
2022-09-08 18:43:19 +00:00
left := machine.Pop()
machine.Push(left % right)
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case HALT:
return
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case JMP:
jumpTo := machine.Pop()
if machine.Pop() != 0 {
machine.counter = jumpTo - 1
}
2022-08-28 21:52:39 +00:00
2022-08-28 22:34:23 +00:00
case CAL:
id := machine.Pop()
2022-08-28 21:52:39 +00:00
if machine.functions == nil {
break
}
function, exists := machine.functions[id]
if exists {
if function(machine) {
return
}
}
default:
err = ErrorUnknownInstruction
return
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
}
return
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-29 14:48:45 +00:00
func (machine *Machine[WORD]) instruction() (instruction WORD) {
instruction = machine.program[machine.counter]
2022-08-28 17:30:54 +00:00
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-29 14:48:45 +00:00
func (machine *Machine[WORD]) reallocateStack() {
reallocatedStack := make([]WORD, machine.pointer*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-29 14:48:45 +00:00
func (machine *Machine[WORD]) Push(word WORD) {
2022-08-28 21:52:39 +00:00
machine.pointer++
2022-08-29 14:48:45 +00:00
if len(machine.stack) <= int(machine.pointer) {
2022-08-28 17:25:00 +00:00
machine.reallocateStack()
}
2022-08-28 22:26:44 +00:00
machine.stack[machine.pointer] = word
2022-08-28 17:25:00 +00:00
}
// Pop pops the last word off of the stack, and returns it, decreasing the stack
// pointer and reallocating the stack if necessary.
2022-08-29 14:48:45 +00:00
func (machine *Machine[WORD]) Pop() (word WORD) {
if int(machine.pointer) <= 0 || int(machine.pointer) >= len(machine.stack) {
return
}
2022-09-08 18:43:19 +00:00
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-29 14:48:45 +00:00
if int(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.
2022-08-29 14:48:45 +00:00
func (machine *Machine[WORD]) Peek(address WORD) (word WORD) {
if int(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.
2022-08-29 14:48:45 +00:00
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
}
2022-08-29 04:43:13 +00:00
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.
2022-09-08 18:43:19 +00:00
func (machine *Machine[WORD]) Register(
2022-08-29 14:48:45 +00:00
id WORD,
function MachineFunction[WORD],
) (
err error,
) {
if machine.functions == nil {
2022-08-29 14:48:45 +00:00
machine.functions = make(map[WORD]MachineFunction[WORD])
}
_, 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-29 14:48:45 +00:00
func (machine *Machine[WORD]) Unregister(id WORD) {
2022-08-28 21:52:39 +00:00
if machine.functions == nil {
return
}
2022-08-28 21:36:45 +00:00
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.
2022-08-29 14:48:45 +00:00
func (machine *Machine[WORD]) LoadMemory(block []WORD) {
machine.block = make([]WORD, len(block))
copy(machine.block, block)
}
2022-08-28 21:52:39 +00:00
// Milk milks the stack machine.
2022-08-29 14:48:45 +00:00
func (machine *Machine[WORD]) Milk() {}