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 MOD Opcode = 0xF ) // 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 case MOD: // Set AC to the remainder of AC divided by the value at // X if memoryValue == 0 { machine.accumulator = 0 } else { machine.accumulator %= memoryValue } 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 } } }