adventofcode-2020/day-8/src/emulator.rs

188 lines
4.9 KiB
Rust

use std::collections::HashSet;
use snafu::Snafu;
pub type Program = Vec<(Instruction, i64)>;
#[derive(Debug, PartialEq, Snafu)]
pub enum EmulatorError {
#[snafu(display("Segmentation fault"))]
SegmentationFault,
#[snafu(display("Infinite loop: {}", accumulator))]
InfiniteLoop { accumulator: i64 },
}
#[derive(Debug, Snafu)]
pub enum ProgramParseError {
#[snafu(display("Invalid instruction given: {}", instruction))]
InvalidInstruction { instruction: String },
#[snafu(display("Invalid argument given: {}", argument))]
InvalidArgument { argument: String },
#[snafu(display("Unparseable line: {}", line))]
InvalidLine { line: String },
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Instruction {
Accumulate,
Jump,
NoOperation,
}
pub struct Emulator {
accumulator: i64,
program_counter: usize,
}
impl Emulator {
pub fn new() -> Self {
Self {
accumulator: 0,
program_counter: 0,
}
}
pub fn reset(&mut self) {
self.accumulator = 0;
self.program_counter = 0;
}
fn execute_instruction(
&mut self,
instruction: Instruction,
argument: i64,
) -> Result<(), EmulatorError> {
match instruction {
Instruction::Accumulate => self.accumulator += argument,
Instruction::Jump => {
self.program_counter = self.program_counter.wrapping_add(argument as usize - 1)
}
Instruction::NoOperation => {}
}
Ok(())
}
pub fn run_program_finitely(&mut self, program: &Program) -> Result<i64, EmulatorError> {
let mut executed = HashSet::new();
loop {
let (instruction, argument) = program
.get(self.program_counter)
.ok_or(EmulatorError::SegmentationFault)?;
executed.insert(self.program_counter);
self.execute_instruction(*instruction, *argument)?;
self.program_counter += 1;
if executed.contains(&self.program_counter) {
break Err(EmulatorError::InfiniteLoop {
accumulator: self.accumulator,
});
} else if self.program_counter >= program.len() {
break Ok(self.accumulator);
}
}
}
}
pub fn parse_code(input: &str) -> Result<Program, ProgramParseError> {
input
.lines()
.map(|instruction| {
let (name, argument): (&str, &str) = {
let temp: Vec<&str> = instruction.split(' ').collect();
if temp.len() != 2 {
Err(ProgramParseError::InvalidLine {
line: instruction.to_string(),
})
} else {
Ok((temp[0], temp[1]))
}
}?;
Ok((
match name {
"acc" => Instruction::Accumulate,
"jmp" => Instruction::Jump,
"nop" => Instruction::NoOperation,
_ => Err(ProgramParseError::InvalidInstruction {
instruction: name.to_string(),
})?,
},
argument
.parse()
.or(Err(ProgramParseError::InvalidArgument {
argument: argument.to_string(),
}))?,
))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
#[test]
fn test_parsing() -> Result<(), Box<dyn std::error::Error>> {
let input = indoc!(
"nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6
"
);
let code = parse_code(&input)?;
assert_eq!(
code,
vec![
(Instruction::NoOperation, 0),
(Instruction::Accumulate, 1),
(Instruction::Jump, 4),
(Instruction::Accumulate, 3),
(Instruction::Jump, -3),
(Instruction::Accumulate, -99),
(Instruction::Accumulate, 1),
(Instruction::Jump, -4),
(Instruction::Accumulate, 6),
]
);
Ok(())
}
#[test]
fn test_running() -> Result<(), Box<dyn std::error::Error>> {
let input = indoc!(
"nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6
"
);
let code = parse_code(&input)?;
let mut emulator = Emulator::new();
assert_eq!(
emulator.run_program_finitely(&code),
Err(EmulatorError::InfiniteLoop { accumulator: 5 })
);
Ok(())
}
}