use std::{
cmp::{Ordering, PartialEq},
collections::HashMap,
fmt,
ops::{Add, Index, IndexMut, Mul},
sync::mpsc::{Receiver, Sender},
};
#[derive(Clone, Copy, PartialEq, Debug)]
enum Mode {
Position,
Immediate,
Relative,
}
type Cell = i64;
type Tape = HashMap<usize, Cell>;
#[derive(Debug, Default)]
pub struct IntcodeVM {
tape: Tape,
ip: usize,
rp: isize,
input: Option<Receiver<Cell>>,
output: Option<Sender<Cell>>,
inp_wait_flag: Option<Sender<()>>,
halted: bool,
}
trait Flags {
fn flags(&self) -> Result<(Mode, Mode, Mode), IntcodeError>;
}
impl Flags for Cell {
fn flags(&self) -> Result<(Mode, Mode, Mode), IntcodeError> {
let flag = |argpos| match self / 10_i64.pow(argpos) % 10 {
0 => Ok(Mode::Position),
1 => Ok(Mode::Immediate),
2 => Ok(Mode::Relative),
_ => Err(IntcodeError::InvalidInstruction),
};
Ok((flag(2)?, flag(3)?, flag(4)?))
}
}
impl IntcodeVM {
pub fn from_prog(prog: &[Cell]) -> Self {
Self {
tape: prog.iter().copied().enumerate().collect(),
..Default::default()
}
}
pub fn with_io(
prog: &[Cell],
input: Option<Receiver<Cell>>,
output: Option<Sender<Cell>>,
) -> Self {
Self {
tape: prog.iter().copied().enumerate().collect(),
input,
output,
..Default::default()
}
}
pub fn with_inp_flag(
prog: &[Cell],
input: Option<Receiver<Cell>>,
output: Option<Sender<Cell>>,
inp_wait_flag: Option<Sender<()>>,
) -> Self {
Self {
tape: prog.iter().copied().enumerate().collect(),
input,
output,
inp_wait_flag,
..Default::default()
}
}
pub fn run_prog(&mut self) -> Result<Tape, IntcodeError> {
if self.halted {
return Err(IntcodeError::RanHaltedVM);
}
while !self.halted {
self.step()?;
}
Ok(self.tape.clone())
}
pub fn step(&mut self) -> Result<Tape, IntcodeError> {
if self.halted {
return Err(IntcodeError::RanHaltedVM);
}
match self[self.ip] % 100 {
1 => self.maths(<i64 as Add>::add)?,
2 => self.maths(<i64 as Mul>::mul)?,
3 => self.input()?,
4 => self.outpt()?,
5 => self.test0(<i64 as PartialEq>::ne)?,
6 => self.test0(<i64 as PartialEq>::eq)?,
7 => self.test2(Ordering::Less)?,
8 => self.test2(Ordering::Equal)?,
9 => self.relpt()?,
99 => self.halted = true,
_ => return Err(IntcodeError::InvalidInstruction),
}
Ok(self.tape.clone())
}
fn get(&mut self, mode: Mode, pad: usize) -> Cell {
let in1 = self.ip + pad;
let in2 = match mode {
Mode::Immediate => return self[in1],
Mode::Position => self[in1] as usize,
Mode::Relative => (self[in1] as isize + self.rp) as usize,
};
*self.tape.entry(in2).or_default()
}
fn maths(&mut self, op: fn(Cell, Cell) -> Cell) -> Result<(), IntcodeError> {
let (one, two, three) = self[self.ip].flags()?;
let arg1 = self.get(one, 1);
let arg2 = self.get(two, 2);
let rel = if three == Mode::Relative { self.rp } else { 0 };
let loc = (self[self.ip + 3] + rel as i64) as usize;
self[loc] = op(arg1, arg2);
self.ip += 4;
Ok(())
}
fn input(&mut self) -> Result<(), IntcodeError> {
let (one, ..) = self[self.ip].flags()?;
let rel = if one == Mode::Relative { self.rp } else { 0 };
let loc = (self[self.ip + 1] + rel as i64) as usize;
if let Some(sender) = &self.inp_wait_flag {
sender.send(()).expect(
"Error: failed to send input waiting flag: \
receiver disconnected.",
);
}
self[loc] = self
.input
.as_ref()
.expect("Error: found input instruction but no receiver was created.")
.recv()
.expect("Error: failed to receive input: sender disconnected.");
self.ip += 2;
Ok(())
}
fn outpt(&mut self) -> Result<(), IntcodeError> {
let (one, ..) = self[self.ip].flags()?;
let val = self.get(one, 1);
self.output
.as_ref()
.expect("Error: found output instruction but no sender was created.")
.send(val)
.expect("Error: failed to send output: receiver disconnected.");
self.ip += 2;
Ok(())
}
fn test0(&mut self, checker: fn(&Cell, &Cell) -> bool) -> Result<(), IntcodeError> {
let (one, two, _) = self[self.ip].flags()?;
let test = self.get(one, 1);
if checker(&test, &0) {
self.ip = self.get(two, 2) as usize;
} else {
self.ip += 3;
}
Ok(())
}
fn test2(&mut self, mode: Ordering) -> Result<(), IntcodeError> {
let (one, two, three) = self[self.ip].flags()?;
let arg1 = self.get(one, 1);
let arg2 = self.get(two, 2);
let rel = if three == Mode::Relative { self.rp } else { 0 };
let loc = (self[self.ip + 3] + rel as i64) as usize;
let test = arg1.cmp(&arg2) == mode;
self[loc] = test as i64;
self.ip += 4;
Ok(())
}
fn relpt(&mut self) -> Result<(), IntcodeError> {
let (one, ..) = self[self.ip].flags()?;
let arg1 = self.get(one, 1);
self.rp += arg1 as isize;
self.ip += 2;
Ok(())
}
}
impl Index<usize> for IntcodeVM {
type Output = Cell;
fn index(&self, ix: usize) -> &Self::Output {
&self.tape[&ix]
}
}
impl IndexMut<usize> for IntcodeVM {
fn index_mut(&mut self, ix: usize) -> &mut Self::Output {
self.tape.entry(ix).or_default();
self.tape.get_mut(&ix).unwrap()
}
}
pub enum IntcodeError {
RanHaltedVM,
InvalidInstruction,
}
impl std::error::Error for IntcodeError {}
impl fmt::Display for IntcodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let err_msg = match self {
IntcodeError::RanHaltedVM => "Error: tried to run an already halted intcode VM",
IntcodeError::InvalidInstruction => "Error: an instruction was malformed",
};
f.write_str(err_msg)
}
}
impl fmt::Debug for IntcodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let err_msg = match self {
IntcodeError::RanHaltedVM => "Error: tried to run an already halted intcode VM",
IntcodeError::InvalidInstruction => "Error: an instruction was malformed",
};
f.write_str(err_msg)
}
}