use byteorder::{ReadBytesExt, LE}; use std::io::Result; type Word = u16; //type Memory = [Word; Word::MAX as usize + 1]; struct Memory { words: [Word; Word::MAX as usize + 1], } impl Memory { fn get(&self, index: Word) -> Word { self.words[index as usize] } fn get_ref(&self, index: Word) -> Word { // equivalent to get(get(index)) self.words[self.words[index as usize] as usize] } fn put(&mut self, index: Word, value: Word) { self.words[index as usize] = value; } //fn put_ref(&mut self, index: Word, value: Word) {} fn new(path_str: &str) -> Memory { let mut file = match std::fs::File::open(path_str) { Result::Ok(x) => x, Result::Err(_) => panic!("could not open program"), }; let mut mem = Memory { words: [0x0; u16::MAX as usize + 1], }; let mut index = 0; loop { match file.read_u16::() { Result::Ok(word) => { mem.words[index] = word; //println!("DEBUG: WORD: {:#06x}", word); index += 1; } Result::Err(error) => match error.kind() { std::io::ErrorKind::UnexpectedEof => { //println!("DEBUG: EOF"); break; } other_error => panic!("Other error: {:?}", other_error), }, } } mem } } struct Cpu { //ax: Word, // Accumulator //bx: Word, // Base //cx: Word, // Counter //dx: Word, // Data //si: Word, // Source Index //di: Word, // Destination Index //bp: Word, // Base Pointer //sp: Word, // Stack Pointer ip: Word, // Instruction Pointer // Segment & Status registers ignored for now memory: Memory, // for convenience, the CPU has ownership of the memory //registers are part of the memory now... // TODO proper input handling (probably read from command line) input: Vec, input_index: usize, } impl Cpu { fn _read_input(&mut self) -> Word { // TODO length check or something... let byte = self.input[self.input_index] as Word; self.input_index += 1; //println!("DEBUG: read byte {}", byte); byte } fn _first_arg(&self) -> Word { self.memory .get(match self.memory.get(self.ip).checked_add(1) { Some(x) => x, None => panic!("malformed instruction at {}", self.memory.get(self.ip)), }) } fn _second_arg(&self) -> Word { self.memory .get(match self.memory.get(self.ip).checked_add(2) { Some(x) => x, None => panic!("malformed instruction at {}", self.memory.get(self.ip)), }) } fn step(&mut self) { //println!("DEBUG: IP: {}", self.memory.get(self.ip)); // fetch and decode instruction let (op, len) = match self.memory.get_ref(self.ip) { 0x10 => (Op::Input(self._first_arg()), 2), 0x11 => (Op::Output(self._first_arg()), 2), 0x20 => (Op::Mov(self._first_arg(), self._second_arg()), 3), 0x90 => (Op::Nop, 1), 0xFF => (Op::Exit(self._first_arg()), 2), x => panic!("unknown instruction {} at {}", x, self.memory.get(self.ip)), }; // increment ip self.memory .put(self.ip, self.memory.get(self.ip).wrapping_add(len)); // execute instruction match op { Op::Input(address) => { //println!("DEBUG: INPUT: {}", address); let x = self._read_input(); self.memory.put(address, x); } Op::Mov(dest, src) => self.memory.put(dest, self.memory.get(src)), Op::Nop => {} Op::Output(address) => { //println!("DEBUG: OUTPUT: {}", address); print!("{} ", self.memory.get(address)); } Op::Exit(status) => std::process::exit(status as i32), } } fn new(memory: Memory) -> Cpu { // TODO number registers, leave 0 for NULL // TODO either put at end of address space // or offset programs Cpu { ip: 9, memory, input: vec![54, 65, 73, 74], // TODO proper input from command line or stdin input_index: 0, } } } enum Op { Input(Word), Mov(Word, Word), Nop, Output(Word), Exit(Word), } /* impl Op { fn length(&self) -> Word { match *self { Op::Nop => 1, Op::Input(_) | Op::Output(_) => 2, Op::Mov(_, _) => 3, } } }*/ fn main() { let args: Vec = std::env::args().collect(); let memory = Memory::new(&args[1]); let mut cpu = Cpu::new(memory); for _ in 0..=255 { cpu.step(); } println!(); }