aoc19intcode/
lib.rs

1//! # A standalone Advent of Code 2019 Intcode VM implementation
2//!
3//! That's it. IDK what else to tell ya.
4//!
5//! # Examples
6//!
7//! ```
8//! use aoc19intcode::IntcodeVM;
9//! assert_eq!(
10//!     IntcodeVM::from_prog(&[2, 4, 4, 5, 99, 0])
11//!         .run_prog()
12//!         .unwrap()[&5],
13//!     9801
14//! );
15//! ```
16
17use std::{
18    cmp::{Ordering, PartialEq},
19    collections::HashMap,
20    fmt,
21    ops::{Add, Index, IndexMut, Mul},
22    sync::mpsc::{Receiver, Sender},
23};
24
25#[derive(Clone, Copy, PartialEq, Debug)]
26enum Mode {
27    Position,
28    Immediate,
29    Relative,
30}
31
32type Cell = i64;
33type Tape = HashMap<usize, Cell>;
34
35/// A VM that runs Intcode programs.
36///
37/// # Examples
38///
39/// ```
40/// use aoc19intcode::IntcodeVM;
41/// assert_eq!(
42///     IntcodeVM::from_prog(&[2, 4, 4, 5, 99, 0])
43///         .run_prog()
44///         .expect("Tried to run a halted program.")[&5],
45///     9801
46/// );
47/// ```
48/// ```
49/// use std::{thread, sync::mpsc};
50/// use aoc19intcode::IntcodeVM;
51/// let input = [2, 4, 4, 5, 99, 0];
52/// let (mtx, prx) = mpsc::channel();
53/// let (ptx, mrx) = mpsc::channel();
54/// let (ftx, frx) = mpsc::channel();
55/// mtx.send(2).expect("Failed to send an input to the VM.");
56/// thread::spawn(move || {
57///     IntcodeVM::with_inp_flag(&input, Some(prx), Some(ptx), Some(ftx))
58///         .run_prog()
59/// });
60/// for _ in frx.recv() {
61///     println!("received {}", mrx.recv().expect("VM disconnected."));
62/// }
63/// ```
64#[derive(Debug, Default)]
65pub struct IntcodeVM {
66    tape: Tape,
67    ip: usize,
68    rp: isize,
69    input: Option<Receiver<Cell>>,
70    output: Option<Sender<Cell>>,
71    inp_wait_flag: Option<Sender<()>>,
72    halted: bool,
73}
74
75trait Flags {
76    fn flags(&self) -> Result<(Mode, Mode, Mode), IntcodeError>;
77}
78
79impl Flags for Cell {
80    fn flags(&self) -> Result<(Mode, Mode, Mode), IntcodeError> {
81        let flag = |argpos| match self / 10_i64.pow(argpos) % 10 {
82            0 => Ok(Mode::Position),
83            1 => Ok(Mode::Immediate),
84            2 => Ok(Mode::Relative),
85            _ => Err(IntcodeError::InvalidInstruction),
86        };
87        Ok((flag(2)?, flag(3)?, flag(4)?))
88    }
89}
90
91impl IntcodeVM {
92    /// Returns a new VM with a program and no I/O controls.
93    pub fn from_prog(prog: &[Cell]) -> Self {
94        Self {
95            tape: prog.iter().copied().enumerate().collect(),
96            ..Default::default()
97        }
98    }
99
100    /// Returns a new VM with a program and I/O controls.
101    pub fn with_io(
102        prog: &[Cell],
103        input: Option<Receiver<Cell>>,
104        output: Option<Sender<Cell>>,
105    ) -> Self {
106        Self {
107            tape: prog.iter().copied().enumerate().collect(),
108            input,
109            output,
110            ..Default::default()
111        }
112    }
113
114    /// Returns a new VM with a program and I/O controls, includes an input
115    /// waiting flag sender that sends a unit (`()`) when the VM is expecting
116    /// input.
117    pub fn with_inp_flag(
118        prog: &[Cell],
119        input: Option<Receiver<Cell>>,
120        output: Option<Sender<Cell>>,
121        inp_wait_flag: Option<Sender<()>>,
122    ) -> Self {
123        Self {
124            tape: prog.iter().copied().enumerate().collect(),
125            input,
126            output,
127            inp_wait_flag,
128            ..Default::default()
129        }
130    }
131
132    /// Runs the VM.
133    pub fn run_prog(&mut self) -> Result<Tape, IntcodeError> {
134        if self.halted {
135            return Err(IntcodeError::RanHaltedVM);
136        }
137        while !self.halted {
138            self.step()?;
139        }
140        Ok(self.tape.clone())
141    }
142
143    // Runs one instruction.
144    pub fn step(&mut self) -> Result<Tape, IntcodeError> {
145        if self.halted {
146            return Err(IntcodeError::RanHaltedVM);
147        }
148        match self[self.ip] % 100 {
149            1 => self.maths(<i64 as Add>::add)?,
150            2 => self.maths(<i64 as Mul>::mul)?,
151            3 => self.input()?,
152            4 => self.outpt()?,
153            5 => self.test0(<i64 as PartialEq>::ne)?,
154            6 => self.test0(<i64 as PartialEq>::eq)?,
155            7 => self.test2(Ordering::Less)?,
156            8 => self.test2(Ordering::Equal)?,
157            9 => self.relpt()?,
158            99 => self.halted = true,
159            _ => return Err(IntcodeError::InvalidInstruction),
160        }
161
162        Ok(self.tape.clone())
163    }
164
165    fn get(&mut self, mode: Mode, pad: usize) -> Cell {
166        let in1 = self.ip + pad;
167        let in2 = match mode {
168            Mode::Immediate => return self[in1],
169            Mode::Position => self[in1] as usize,
170            Mode::Relative => (self[in1] as isize + self.rp) as usize,
171        };
172        *self.tape.entry(in2).or_default()
173    }
174
175    fn maths(&mut self, op: fn(Cell, Cell) -> Cell) -> Result<(), IntcodeError> {
176        let (one, two, three) = self[self.ip].flags()?;
177        let arg1 = self.get(one, 1);
178        let arg2 = self.get(two, 2);
179        let rel = if three == Mode::Relative { self.rp } else { 0 };
180        let loc = (self[self.ip + 3] + rel as i64) as usize;
181        self[loc] = op(arg1, arg2);
182        self.ip += 4;
183        Ok(())
184    }
185
186    fn input(&mut self) -> Result<(), IntcodeError> {
187        let (one, ..) = self[self.ip].flags()?;
188        let rel = if one == Mode::Relative { self.rp } else { 0 };
189        let loc = (self[self.ip + 1] + rel as i64) as usize;
190        if let Some(sender) = &self.inp_wait_flag {
191            sender.send(()).expect(
192                "Error: failed to send input waiting flag: \
193                    receiver disconnected.",
194            );
195        }
196        self[loc] = self
197            .input
198            .as_ref()
199            .expect("Error: found input instruction but no receiver was created.")
200            .recv()
201            .expect("Error: failed to receive input: sender disconnected.");
202        self.ip += 2;
203        Ok(())
204    }
205
206    fn outpt(&mut self) -> Result<(), IntcodeError> {
207        let (one, ..) = self[self.ip].flags()?;
208        let val = self.get(one, 1);
209        self.output
210            .as_ref()
211            .expect("Error: found output instruction but no sender was created.")
212            .send(val)
213            .expect("Error: failed to send output: receiver disconnected.");
214        self.ip += 2;
215        Ok(())
216    }
217
218    // compare a cell against 0
219    fn test0(&mut self, checker: fn(&Cell, &Cell) -> bool) -> Result<(), IntcodeError> {
220        let (one, two, _) = self[self.ip].flags()?;
221        let test = self.get(one, 1);
222        if checker(&test, &0) {
223            self.ip = self.get(two, 2) as usize;
224        } else {
225            self.ip += 3;
226        }
227        Ok(())
228    }
229
230    // compare 2 arguments against each other
231    fn test2(&mut self, mode: Ordering) -> Result<(), IntcodeError> {
232        let (one, two, three) = self[self.ip].flags()?;
233        let arg1 = self.get(one, 1);
234        let arg2 = self.get(two, 2);
235        let rel = if three == Mode::Relative { self.rp } else { 0 };
236        let loc = (self[self.ip + 3] + rel as i64) as usize;
237        let test = arg1.cmp(&arg2) == mode;
238        self[loc] = test as i64;
239        self.ip += 4;
240        Ok(())
241    }
242
243    fn relpt(&mut self) -> Result<(), IntcodeError> {
244        let (one, ..) = self[self.ip].flags()?;
245        let arg1 = self.get(one, 1);
246        self.rp += arg1 as isize;
247        self.ip += 2;
248        Ok(())
249    }
250}
251
252impl Index<usize> for IntcodeVM {
253    type Output = Cell;
254
255    fn index(&self, ix: usize) -> &Self::Output {
256        &self.tape[&ix]
257    }
258}
259
260impl IndexMut<usize> for IntcodeVM {
261    fn index_mut(&mut self, ix: usize) -> &mut Self::Output {
262        self.tape.entry(ix).or_default();
263        self.tape.get_mut(&ix).unwrap()
264    }
265}
266
267/// Possible errors.
268pub enum IntcodeError {
269    RanHaltedVM,
270    InvalidInstruction,
271}
272
273impl std::error::Error for IntcodeError {}
274
275impl fmt::Display for IntcodeError {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        let err_msg = match self {
278            IntcodeError::RanHaltedVM => "Error: tried to run an already halted intcode VM",
279            IntcodeError::InvalidInstruction => "Error: an instruction was malformed",
280        };
281        f.write_str(err_msg)
282    }
283}
284
285impl fmt::Debug for IntcodeError {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        let err_msg = match self {
288            IntcodeError::RanHaltedVM => "Error: tried to run an already halted intcode VM",
289            IntcodeError::InvalidInstruction => "Error: an instruction was malformed",
290        };
291        f.write_str(err_msg)
292    }
293}