187 lines
5.4 KiB
Rust
187 lines
5.4 KiB
Rust
use advent2020::errors::{ExecutionError, InstructionParseError, TopLevelError};
|
|
use std::{collections::HashSet, env};
|
|
use std::fmt;
|
|
use std::fs;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Clone)]
|
|
struct Machine {
|
|
instructions: Vec<Instruction>,
|
|
accumulator: isize,
|
|
location: isize,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum Instruction {
|
|
NOP(isize),
|
|
ACC(isize),
|
|
JMP(isize),
|
|
}
|
|
|
|
impl FromStr for Instruction {
|
|
type Err = InstructionParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut lowered = s.to_string();
|
|
|
|
lowered.make_ascii_lowercase();
|
|
|
|
let mut items = s.split(' ');
|
|
let instruction = items.next().ok_or(InstructionParseError::EmptyInstruction)?;
|
|
let operand = items.next().ok_or(InstructionParseError::MissingOperand(instruction.to_string()))?;
|
|
let operand_value = isize::from_str(operand)?;
|
|
|
|
match instruction {
|
|
"nop" => Ok(Instruction::NOP(operand_value)),
|
|
"acc" => Ok(Instruction::ACC(operand_value)),
|
|
"jmp" => Ok(Instruction::JMP(operand_value)),
|
|
_ => Err(InstructionParseError::UnknownOpcode(instruction.to_string())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Instruction {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Instruction::NOP(s) => write!(f, "NOP {:+}", s),
|
|
Instruction::ACC(s) => write!(f, "ACC {:+}", s),
|
|
Instruction::JMP(s) => write!(f, "JMP {:+}", s),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for Machine {
|
|
type Err = InstructionParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut instructions = Vec::new();
|
|
|
|
for line in s.lines() {
|
|
let instruction = Instruction::from_str(line)?;
|
|
instructions.push(instruction);
|
|
}
|
|
|
|
Ok(Machine {
|
|
instructions,
|
|
accumulator: 0,
|
|
location: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Machine {
|
|
fn pretty_print(&self) {
|
|
for (idx, instr) in self.instructions.iter().enumerate() {
|
|
let pointer = if (idx as isize) == self.location { "--> " } else { " " };
|
|
println!("{} {:04}: {}", pointer, idx, instr);
|
|
}
|
|
}
|
|
|
|
fn step(&mut self) -> Result<(), ExecutionError> {
|
|
if self.location < 0 || self.location >= (self.instructions.len() as isize) {
|
|
return Err(ExecutionError::NonExistentLocation(self.location));
|
|
}
|
|
|
|
match self.instructions[self.location as usize] {
|
|
Instruction::NOP(_) => self.location += 1,
|
|
Instruction::JMP(x) => self.location += x,
|
|
Instruction::ACC(x) => {
|
|
self.location += 1;
|
|
self.accumulator += x;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn terminates(&mut self) -> Result<(bool, isize), ExecutionError> {
|
|
let mut visited_locations = HashSet::new();
|
|
loop {
|
|
let current_location = self.location;
|
|
let current_accumulator = self.accumulator;
|
|
|
|
visited_locations.insert(current_location);
|
|
self.step()?;
|
|
|
|
if visited_locations.contains(&self.location) {
|
|
return Ok((false, current_accumulator));
|
|
}
|
|
|
|
if self.location == (self.instructions.len() as isize) {
|
|
return Ok((true, self.accumulator));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn variants(&self) -> VariantGenerator {
|
|
VariantGenerator {
|
|
next_offset: 0,
|
|
base_machine: self.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct VariantGenerator {
|
|
next_offset: usize,
|
|
base_machine: Machine,
|
|
}
|
|
|
|
impl Iterator for VariantGenerator {
|
|
type Item = Machine;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
loop {
|
|
if self.next_offset >= self.base_machine.instructions.len() {
|
|
return None;
|
|
}
|
|
|
|
match self.base_machine.instructions[self.next_offset] {
|
|
Instruction::ACC(_) => self.next_offset += 1,
|
|
Instruction::JMP(x) => {
|
|
let mut retval = self.base_machine.clone();
|
|
retval.instructions[self.next_offset] = Instruction::NOP(x);
|
|
self.next_offset += 1;
|
|
return Some(retval);
|
|
}
|
|
Instruction::NOP(x) => {
|
|
let mut retval = self.base_machine.clone();
|
|
retval.instructions[self.next_offset] = Instruction::JMP(x);
|
|
self.next_offset += 1;
|
|
return Some(retval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn real_main() -> Result<(), TopLevelError> {
|
|
let filename = env::args().skip(1).next().expect("No file argument given.");
|
|
let contents = fs::read_to_string(filename)?;
|
|
let machine = Machine::from_str(&contents)?;
|
|
|
|
machine.pretty_print();
|
|
|
|
// this is part 1
|
|
let (terminated, last_accum) = machine.clone().terminates()?;
|
|
if terminated {
|
|
println!("WARNING: Somehow the initial input terminated.");
|
|
}
|
|
println!("Last accumulator before looping forever: {}", last_accum);
|
|
|
|
// this is part 2
|
|
for mut variant in machine.variants() {
|
|
if let Ok((true, final_value)) = variant.terminates() {
|
|
println!("\nFound a variant that halts! Its last value is {}", final_value);
|
|
variant.pretty_print();
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn main() {
|
|
if let Err(e) = real_main() {
|
|
eprintln!("ERROR: {}", e);
|
|
}
|
|
}
|