188 lines
4.9 KiB
Rust
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(())
|
|
}
|
|
}
|