Skip to main content

advent_of_code/year2019/
int_code.rs

1use std::collections::VecDeque;
2
3pub type Word = i64;
4
5#[derive(Clone)]
6pub struct Program {
7    memory: Vec<Word>,
8    instruction_pointer: usize,
9    output_values: Vec<Word>,
10    input_values: VecDeque<Word>,
11    halted: bool,
12    requires_input_to: Option<usize>,
13    relative_base: Word,
14}
15
16enum Parameter {
17    Value(Word),
18    Address(usize),
19}
20
21impl Program {
22    pub fn parse(input: &str) -> Result<Self, String> {
23        let mut memory: Vec<Word> = Vec::new();
24        for word_string in input.trim().split(',') {
25            match word_string.parse::<Word>() {
26                Ok(value) => {
27                    memory.push(value);
28                }
29                Err(error) => {
30                    return Err(format!("Unable to parse program word ({error})"));
31                }
32            }
33        }
34        Ok(Self {
35            memory,
36            instruction_pointer: 0,
37            output_values: Vec::new(),
38            input_values: VecDeque::new(),
39            halted: false,
40            requires_input_to: None,
41            relative_base: 0,
42        })
43    }
44
45    pub const fn is_halted(&self) -> bool {
46        self.halted
47    }
48
49    pub fn run_until_halt_or_input(&mut self, max_instructions: u32) -> Result<(), String> {
50        if self.requires_input_to.is_some() {
51            return Err("Cannot run program requiring input".to_string());
52        } else if self.halted {
53            return Err("Cannot run halted program".to_string());
54        }
55
56        let mut current_instruction = 0;
57        while !self.halted && self.requires_input_to.is_none() {
58            self.evaluate()?;
59
60            current_instruction += 1;
61            if current_instruction == max_instructions {
62                return Err(format!("Aborted after {max_instructions} instructions"));
63            }
64        }
65        Ok(())
66    }
67
68    pub fn run_for_output(&mut self) -> Result<Vec<Word>, String> {
69        self.run_until_halt_or_input(1_000_000_000)?;
70        Ok(std::mem::take(&mut self.output_values))
71    }
72
73    pub fn run_for_output_limited(&mut self, max_instructions: u32) -> Result<Vec<Word>, String> {
74        self.run_until_halt_or_input(max_instructions)?;
75        Ok(std::mem::take(&mut self.output_values))
76    }
77
78    pub fn input(&mut self, input_value: Word) {
79        if let Some(save_address) = self.requires_input_to {
80            self.write_memory(save_address, input_value);
81            self.requires_input_to = None;
82        } else {
83            self.input_values.push_back(input_value);
84        }
85    }
86
87    pub fn input_string(&mut self, input_string: &str) {
88        input_string.bytes().for_each(|c| {
89            self.input(Word::from(c));
90        });
91    }
92
93    fn parameter_mode(
94        &self,
95        instruction: Word,
96        parameter_position: u32,
97    ) -> Result<Parameter, String> {
98        let parameter = self.read_memory(self.instruction_pointer + parameter_position as usize);
99        let divider = 10_i64.pow(parameter_position + 1);
100        let mode = ((instruction / divider) % 10) as u8;
101        let address = match mode {
102            1 => {
103                return Ok(Parameter::Value(parameter));
104            }
105            2 => parameter + self.relative_base,
106            _ => parameter,
107        };
108        if !(0..=10_000).contains(&address) {
109            return Err(format!("Bad address: {address}"));
110        }
111        Ok(Parameter::Address(address as usize))
112    }
113
114    fn output_location(&self, instruction: Word, parameter_position: u32) -> Result<usize, String> {
115        if let Parameter::Address(location) =
116            self.parameter_mode(instruction, parameter_position)?
117        {
118            return Ok(location);
119        }
120        Err("Invalid parameter mode for where to write".to_string())
121    }
122
123    fn parameter_value(&self, instruction: Word, parameter_position: u32) -> Result<Word, String> {
124        Ok(
125            match self.parameter_mode(instruction, parameter_position)? {
126                Parameter::Value(value) => value,
127                Parameter::Address(location) => self.read_memory(location),
128            },
129        )
130    }
131
132    fn evaluate(&mut self) -> Result<(), String> {
133        let instruction = self.read_memory(self.instruction_pointer);
134        let opcode = instruction % 100;
135
136        match opcode {
137            1 | 2 => {
138                let parameter1 = self.parameter_value(instruction, 1)?;
139                let parameter2 = self.parameter_value(instruction, 2)?;
140                let output_location = self.output_location(instruction, 3)?;
141                let value = if opcode == 1 {
142                    parameter1.checked_add(parameter2)
143                } else {
144                    parameter1.checked_mul(parameter2)
145                }
146                .ok_or("Overflow in program")?;
147
148                self.write_memory(output_location, value);
149                self.instruction_pointer += 4;
150            }
151            3 => {
152                // Takes a single integer as input and saves it to the address given by its only parameter.
153                let output_location = self.output_location(instruction, 1)?;
154                if let Some(input_value) = self.input_values.pop_front() {
155                    self.write_memory(output_location, input_value);
156                } else {
157                    self.requires_input_to = Some(output_location);
158                }
159                self.instruction_pointer += 2;
160            }
161            4 => {
162                // Opcode 4 outputs the value of its only parameter.
163                self.output_values
164                    .push(self.parameter_value(instruction, 1)?);
165                self.instruction_pointer += 2;
166            }
167            5 | 6 => {
168                // Opcode 5 is is jump-if-true: if the first parameter is non-zero, it sets the instruction pointer to the
169                // value from the second parameter. Otherwise, it does nothing.
170                // Opcode 6 is jump-if-false: if the first parameter is zero, it sets the instruction pointer
171                // to the value from the second parameter. Otherwise, it does nothing.
172                let jump_if = opcode == 5;
173                let parameter_1_true = self.parameter_value(instruction, 1)? != 0;
174                if parameter_1_true == jump_if {
175                    self.instruction_pointer = self.parameter_value(instruction, 2)? as usize;
176                } else {
177                    self.instruction_pointer += 3;
178                }
179            }
180            7 | 8 => {
181                // Opcode 7 is less than: if the first parameter is less than the second parameter,
182                // it stores 1 in the position given by the third parameter. Otherwise, it stores 0.
183                // Opcode 8 is equals: if the first parameter is equal to the second parameter,
184                // it stores 1 in the position given by the third parameter. Otherwise, it stores 0.
185                let parameter_1 = self.parameter_value(instruction, 1);
186                let parameter_2 = self.parameter_value(instruction, 2);
187                let output_value = i64::from(
188                    (opcode == 7 && (parameter_1 < parameter_2))
189                        || (opcode == 8 && (parameter_1 == parameter_2)),
190                );
191
192                let output_location = self.output_location(instruction, 3)?;
193                self.write_memory(output_location, output_value);
194                self.instruction_pointer += 4;
195            }
196            9 => {
197                self.relative_base += self.parameter_value(instruction, 1)?;
198                self.instruction_pointer += 2;
199            }
200            99 => {
201                self.halted = true;
202            }
203            _ => {
204                return Err(format!("Invalid opcode: {opcode}"));
205            }
206        }
207
208        Ok(())
209    }
210
211    pub fn read_memory(&self, address: usize) -> Word {
212        *self.memory.get(address).unwrap_or(&0_i64)
213    }
214
215    pub fn write_memory(&mut self, address: usize, value: Word) {
216        if self.memory.len() <= address {
217            self.memory.resize(address + 1, 0);
218        }
219        self.memory[address] = value;
220    }
221}