marcie/marcie.go

194 lines
4.7 KiB
Go

package marcie
import "io"
// Machine represents a MARIE system. It can be used to run code.
type Machine struct {
memory [4096]int16
counter int16
accumulator int16
output io.Writer
input io.Reader
}
// Opcode represents an opcode for the machine.
type Opcode uint8
const (
JNS Opcode = 0x0
LOAD Opcode = 0x1
STORE Opcode = 0x2
ADD Opcode = 0x3
SUBT Opcode = 0x4
INPUT Opcode = 0x5
OUTPUT Opcode = 0x6
HALT Opcode = 0x7
SKIPCOND Opcode = 0x8
JUMP Opcode = 0x9
CLEAR Opcode = 0xA
ADDI Opcode = 0xB
JUMPI Opcode = 0xC
LOADI Opcode = 0xD
STOREI Opcode = 0xE
)
// Instruction provides a convenient way to create an instruction word
func Instruction (opcode Opcode, operand uint16) (instruction int16) {
instruction |= int16(uint16(opcode) << 12)
instruction |= int16(operand)
return
}
// Error is an error type that can be returned from the machine's methods if
// they experience problems.
type Error int
const (
ErrorUnknownInstruction Error = iota
)
// Error returns a textual description of the error.
func (err Error) Error() (description string) {
switch err {
case ErrorUnknownInstruction:
description = "machine encountered unknown instruction"
}
return
}
// 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 int16) (err error) {
machine.counter = offset
machine.accumulator = 0
for int(machine.counter) < len(machine.memory) {
word := machine.memory[machine.counter]
opcode := Opcode(word >> 12) & 0x000F
operand := word & 0x0FFF
memoryValue := int16(0)
if int(operand) < len(machine.memory) {
memoryValue = machine.memory[operand]
}
switch opcode {
case JNS:
// Store the PC at address X and jump to X+1
machine.memory[operand] = machine.counter
machine.counter = operand
case LOAD:
// Load contents of address X into AC
machine.accumulator = memoryValue
case STORE:
// Store the contents of AC at address X
machine.memory[operand] = machine.accumulator
case ADD:
// Add the contents of address X to AC
machine.accumulator += memoryValue
case SUBT:
// Subtract the contents of address X from AC
machine.accumulator -= memoryValue
case INPUT:
// Input a value from the keyboard into AC
buffer := []byte { 0 }
_, err = machine.input.Read(buffer)
if err != nil { return }
machine.accumulator = int16(buffer[0])
case OUTPUT:
// Output the value in AC to the display
buffer := []byte { byte(machine.accumulator) }
_, err = machine.output.Write(buffer)
if err != nil { return }
case HALT:
// Terminate program
return
case SKIPCOND:
// Skip next instruction on condition
condition := false
switch operand {
case 0x000:
condition = machine.accumulator < 0
case 0x400:
condition = machine.accumulator == 0
case 0x800:
condition = machine.accumulator > 0
}
if condition {
machine.counter ++
}
case JUMP:
// Load the value of X into PC
machine.counter = operand - 1
case CLEAR:
// Put all zeros in AC
machine.accumulator = 0
case ADDI:
// Add indirect: Use the value at X as the actual
// address of the data operand to add to AC
machine.accumulator += machine.memory[memoryValue]
case JUMPI:
// Jump indirect: Use the value at X as the address to
// jump to
machine.counter += machine.memory[memoryValue] - 1
case LOADI:
// Load indirect: Use the value at X as the address of
// the value to load
machine.accumulator = machine.memory[memoryValue]
case STOREI:
// Store indirect: Use X the value at X as the address
// of where to store the value
machine.memory[memoryValue] = machine.accumulator
default:
err = ErrorUnknownInstruction
}
machine.counter ++
}
return
}
// SetOutput sets the io.Writer that the machine will write to.
func (machine *Machine) SetOutput (writer io.Writer) {
machine.output = writer
}
// SetInput sets the io.Reader that the machine will read from.
func (machine *Machine) SetInput (reader io.Reader) {
machine.input = reader
}
// LoadProgram loads an image into the machine's memory. If the program is
// smaller than the machine's memory, the remaining cells are filled with
// zeroes. If the program is larger than the machine's memory, the remaining
// words are ignored.
func (machine *Machine) LoadProgram (program []int16) {
for index := 0; index < len(machine.memory); index ++ {
if index < len(program) {
machine.memory[index] = program[index]
} else {
machine.memory[index] = 0
}
}
}