diff --git a/Cargo.lock b/Cargo.lock index d451514..507a331 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,35 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "backtrace" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "day1" version = "0.1.0" @@ -43,6 +73,26 @@ name = "either" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "failure" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itertools" version = "0.8.2" @@ -51,10 +101,78 @@ dependencies = [ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libc" +version = "0.2.66" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "shared" version = "0.1.0" +dependencies = [ + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" +"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" "checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 288c30c..ab445b3 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +failure = "0.1.6" diff --git a/shared/src/intcode.rs b/shared/src/intcode.rs index 6420045..2f7dce2 100644 --- a/shared/src/intcode.rs +++ b/shared/src/intcode.rs @@ -1,15 +1,80 @@ -const OP_ADD: CELL = 1; -const OP_MUL: CELL = 2; -const OP_INPUT: CELL = 3; -const OP_OUTPUT: CELL = 4; -const OP_JUMP_IF_TRUE: CELL = 5; -const OP_JUMP_IF_FALSE: CELL = 6; -const OP_LESS_THAN: CELL = 7; -const OP_EQUAL: CELL = 8; -const OP_HALT: CELL = 99; +use failure::Error; + +#[repr(u8)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)] +pub enum ParameterMode { + Position = 0, + Immediate = 1, +} + +impl ParameterMode { + pub fn decode(code: CELL) -> Result<(Self, CELL), Error> { + let rem = code / 10; + let mode = match code % 10 { + 0 => Self::Position, + 1 => Self::Immediate, + m => failure::bail!("Unknown parameter mode: {}", m), + }; + Ok((mode, rem)) + } +} + +#[repr(u8)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)] +pub enum Instruction { + Add = 1, + Mul = 2, + In = 3, + Out = 4, + JumpIfTrue = 5, + JumpIfFalse = 6, + LessThan = 7, + Equal = 8, + Halt = 99, +} + +impl Instruction { + pub fn decode(opcode: CELL) -> Result<(Self, CELL), Error> { + let rem = opcode / 100; + let instr = match opcode % 100 { + 1 => Self::Add, + 2 => Self::Mul, + 3 => Self::In, + 4 => Self::Out, + 5 => Self::JumpIfTrue, + 6 => Self::JumpIfFalse, + 7 => Self::LessThan, + 8 => Self::Equal, + 99 => Self::Halt, + n => failure::bail!("Unknown instruction code {}", n), + }; + Ok((instr, rem)) + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)] +pub struct OpCode { + pub instruction: Instruction, + pub modes: [ParameterMode; 3], +} + +impl OpCode { + pub fn decode(opcode: CELL) -> Result { + let (instruction, code) = Instruction::decode(opcode)?; + let (mode0, code) = ParameterMode::decode(code)?; + let (mode1, code) = ParameterMode::decode(code)?; + let (mode2, code) = ParameterMode::decode(code)?; + failure::ensure!(code == 0); + let modes = [mode0, mode1, mode2]; + + Ok(OpCode { + instruction, + modes, + }) + } +} pub type CELL = i64; -type Mode = u8; #[derive(PartialEq, Eq, Clone, Debug)] pub enum SimulateStep { @@ -39,97 +104,102 @@ impl SimulateStep { #[derive(PartialEq, Eq, Clone, Debug)] pub struct IntCode { + /// instruction pointer pub ip: usize, + /// next value to use for an "IN" (3) instruction (which will clear it) pub next_input: Option, + /// memory of machine (both code and data) pub data: Vec, } impl IntCode { #[inline(always)] - fn fetch(&mut self, mode: Mode) -> CELL { + fn fetch(&mut self, modes: &mut &[ParameterMode]) -> CELL { + let mode = modes[0]; + *modes = &modes[1..]; let imm = self.data[self.ip]; self.ip += 1; match mode { - 1 => imm, - _ => self.data[imm as usize], + ParameterMode::Position => self.data[imm as usize], + ParameterMode::Immediate => imm, } } + #[inline(always)] - fn store(&mut self, mode: Mode, value: CELL) { + fn store(&mut self, modes: &mut &[ParameterMode], value: CELL) { + let mode = modes[0]; + *modes = &modes[1..]; let addr = self.ip; self.ip += 1; match mode { - 1 => self.data[addr] = value, - _ => { + ParameterMode::Immediate => self.data[addr] = value, + ParameterMode::Position => { let addr = self.data[addr] as usize; self.data[addr] = value; }, } } - fn binop(&mut self, op: F, modes: [Mode; 3]) + fn binop(&mut self, op: F, modes: &mut &[ParameterMode]) where F: FnOnce(CELL, CELL) -> CELL, { - let src1 = self.fetch(modes[0]); - let src2 = self.fetch(modes[1]); - self.store(modes[2], op(src1, src2)); + let src1 = self.fetch(modes); + let src2 = self.fetch(modes); + self.store(modes, op(src1, src2)); } + /// run simple machine without I/O to completion pub fn simulate(&mut self) { assert_eq!(self.ip, 0); assert!(self.next_input.is_none()); self.simulate_step().expect_finished(); } + /// run a machine until: + /// - it needs input, but `next_input` is `None` + /// - it outputs a value + /// - it finishes pub fn simulate_step(&mut self) -> SimulateStep { loop { - let modes = self.data[self.ip]; + let opcode = OpCode::decode(self.data[self.ip]).unwrap(); + let modes = &mut &opcode.modes[..]; self.ip += 1; - let op = modes % 100; - let modes = modes / 100; - let mode1 = (modes % 10) as Mode; - let modes = modes / 10; - let mode2 = (modes % 10) as Mode; - let modes = modes / 10; - let mode3 = (modes % 10) as Mode; - let modes = [mode1, mode2, mode3]; - match op { - OP_ADD => self.binop(|a, b| a + b, modes), - OP_MUL => self.binop(|a, b| a * b, modes), - OP_LESS_THAN => self.binop(|a, b| if a < b { 1 } else { 0 }, modes), - OP_EQUAL => self.binop(|a, b| if a == b { 1 } else { 0 }, modes), - OP_INPUT => { + match opcode.instruction { + Instruction::Add => self.binop(|a, b| a + b, modes), + Instruction::Mul => self.binop(|a, b| a * b, modes), + Instruction::LessThan => self.binop(|a, b| if a < b { 1 } else { 0 }, modes), + Instruction::Equal => self.binop(|a, b| if a == b { 1 } else { 0 }, modes), + Instruction::In => { if let Some(v) = self.next_input.take() { - self.store(mode1, v); + self.store(modes, v); } else { self.ip -= 1; // go back to opcode return SimulateStep::WaitForInput; } }, - OP_OUTPUT => { - return SimulateStep::Output(self.fetch(mode1)); + Instruction::Out => { + return SimulateStep::Output(self.fetch(modes)); }, - OP_HALT => { + Instruction::Halt => { self.ip -= 1; // make re-running this step end up here again return SimulateStep::Finished; }, - OP_JUMP_IF_TRUE => { - if self.fetch(mode1) != 0 { - self.ip = self.fetch(mode2) as usize; + Instruction::JumpIfTrue => { + if self.fetch(modes) != 0 { + self.ip = self.fetch(modes) as usize; } else { - self.ip += 1; // skip target + self.ip += 1; // skip parameter for target } }, - OP_JUMP_IF_FALSE => { - if self.fetch(mode1) == 0 { - self.ip = self.fetch(mode2) as usize; + Instruction::JumpIfFalse => { + if self.fetch(modes) == 0 { + self.ip = self.fetch(modes) as usize; } else { - self.ip += 1; // skip target + self.ip += 1; // skip parameter for target } }, - _ => panic!("invalid opcode: {}", op), } } }