year2017/
day18.rs

1use std::collections::VecDeque;
2use utils::prelude::*;
3
4/// Interpreting assembly, with message passing between instances.
5#[derive(Clone, Debug)]
6pub struct Day18 {
7    instructions: Vec<Instruction>,
8}
9
10// Use an enum for registers to avoid bounds checks when accessing the register array, slightly
11// improving performance
12#[derive(Copy, Clone, Debug, PartialEq)]
13enum Register {
14    A,
15    B,
16    C,
17    D,
18    F,
19    I,
20    P,
21}
22
23// Use separate enum variants for register and constant operands to minimize branching in the
24// interpreter loop, again slightly improving performance
25#[derive(Copy, Clone, Debug, PartialEq)]
26enum Instruction {
27    Snd(Register),
28    SndN(i32), // Used in second example
29    Set(Register, Register),
30    SetN(Register, i32),
31    Add(Register, Register),
32    AddN(Register, i32),
33    Mul(Register, Register), // Used in first example
34    MulN(Register, i32),
35    Mod(Register, Register),
36    ModN(Register, i32),
37    Rcv(Register),
38    Jgz(Register, Register),
39    JgzN(Register, i32),
40    Jmp(i32), // Used for jgz $n where $n > 0
41    Noop(),   // Used for jgz $n where $n <= 0
42}
43
44#[derive(Debug)]
45struct Program<'a> {
46    instructions: &'a [Instruction],
47    pc: usize,
48    reg: [i64; 7],
49}
50
51impl Day18 {
52    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
53        let register = parser::literal_map!(
54            "a" => Register::A,
55            "b" => Register::B,
56            "c" => Register::C,
57            "d" => Register::D,
58            "f" => Register::F,
59            "i" => Register::I,
60            "p" => Register::P,
61        );
62
63        let instruction = parser::parse_tree!(
64            ("snd ") =>> {
65                (r @ register) => Instruction::Snd(r),
66                (v @ parser::i32()) => Instruction::SndN(v),
67            },
68            ("set ", r @ register, b' ') =>> {
69                (r2 @ register) => Instruction::Set(r, r2),
70                (v @ parser::i32()) => Instruction::SetN(r, v),
71            },
72            ("add ", r @ register, b' ') =>> {
73                (r2 @ register) => Instruction::Add(r, r2),
74                (v @ parser::i32()) => Instruction::AddN(r, v),
75            },
76            ("mul ", r @ register, b' ') =>> {
77                (r2 @ register) => Instruction::Mul(r, r2),
78                (v @ parser::i32()) => Instruction::MulN(r, v),
79            },
80            ("mod ", r @ register, b' ') =>> {
81                (r2 @ register) => Instruction::Mod(r, r2),
82                (v @ parser::i32()) => Instruction::ModN(r, v),
83            },
84            ("rcv ", r @ register) => Instruction::Rcv(r),
85            ("jgz ") =>> {
86                (r @ register, b' ') =>> {
87                    (r2 @ register) => Instruction::Jgz(r, r2),
88                    (v @ parser::i32()) => Instruction::JgzN(r, v),
89                },
90                (x @ parser::i32(), b' ', v @ parser::i32()) => {
91                    if x > 0 {
92                        Instruction::Jmp(v)
93                    } else {
94                        Instruction::Noop()
95                    }
96                },
97            },
98        );
99
100        Ok(Self {
101            instructions: instruction.parse_lines(input)?,
102        })
103    }
104
105    #[must_use]
106    pub fn part1(&self) -> i64 {
107        let mut program = Program::from_instructions(&self.instructions);
108        let mut last = None;
109
110        program.run(
111            |v| last = Some(v),
112            |v| {
113                if v == 0 {
114                    // If the register is zero, do nothing/set the register back to zero
115                    Some(0)
116                } else {
117                    // If the register is not zero, this is the first recovered frequency, so return
118                    // None to halt program execution
119                    None
120                }
121            },
122        );
123
124        last.unwrap()
125    }
126
127    #[must_use]
128    pub fn part2(&self) -> u32 {
129        let mut program0 = Program::from_instructions(&self.instructions);
130        let mut inbound0 = VecDeque::new();
131
132        let mut program1 = Program::from_instructions(&self.instructions);
133        let mut inbound1 = VecDeque::new();
134        let mut sent = 0;
135        program1.reg[Register::P as usize] = 1;
136
137        // Run both programs until they both fail to make progress. Use | instead of || so each
138        // iteration always attempts to run both
139        while program0.run(
140            |v| {
141                // Send to program 1
142                inbound1.push_back(v)
143            },
144            |_| {
145                // Receive from program 1
146                inbound0.pop_front()
147            },
148        ) | program1.run(
149            |v| {
150                // Send to program 0, keeping track of how many values are sent
151                sent += 1;
152                inbound0.push_back(v)
153            },
154            |_| {
155                // Receive from program 0
156                inbound1.pop_front()
157            },
158        ) {}
159
160        sent
161    }
162}
163
164impl<'a> Program<'a> {
165    fn from_instructions(instructions: &'a [Instruction]) -> Self {
166        Program {
167            instructions,
168            pc: 0,
169            reg: [0; 7],
170        }
171    }
172
173    fn run(&mut self, mut snd: impl FnMut(i64), mut rcv: impl FnMut(i64) -> Option<i64>) -> bool {
174        let mut progress = false;
175
176        while let Some(&instruction) = self.instructions.get(self.pc) {
177            match instruction {
178                Instruction::Snd(r) => snd(self.reg[r as usize]),
179                Instruction::SndN(v) => snd(v as i64),
180                Instruction::Set(r, r2) => self.reg[r as usize] = self.reg[r2 as usize],
181                Instruction::SetN(r, v) => self.reg[r as usize] = v as i64,
182                Instruction::Add(r, r2) => self.reg[r as usize] += self.reg[r2 as usize],
183                Instruction::AddN(r, v) => self.reg[r as usize] += v as i64,
184                Instruction::Mul(r, r2) => self.reg[r as usize] *= self.reg[r2 as usize],
185                Instruction::MulN(r, v) => self.reg[r as usize] *= v as i64,
186                Instruction::Mod(r, r2) => {
187                    self.reg[r as usize] = self.reg[r as usize].rem_euclid(self.reg[r2 as usize])
188                }
189                Instruction::ModN(r, v) => {
190                    self.reg[r as usize] = self.reg[r as usize].rem_euclid(v as i64)
191                }
192                Instruction::Rcv(r) => {
193                    if let Some(value) = rcv(self.reg[r as usize]) {
194                        self.reg[r as usize] = value;
195                    } else {
196                        break;
197                    }
198                }
199                Instruction::Jgz(r, ro) => {
200                    if self.reg[r as usize] > 0 {
201                        self.pc = self.pc.wrapping_add_signed(self.reg[ro as usize] as isize) - 1;
202                    }
203                }
204                Instruction::JgzN(r, v) => {
205                    if self.reg[r as usize] > 0 {
206                        self.pc = self.pc.wrapping_add_signed(v as isize) - 1;
207                    }
208                }
209                Instruction::Jmp(v) => {
210                    self.pc = self.pc.wrapping_add(v as usize) - 1;
211                }
212                Instruction::Noop() => {}
213            }
214
215            self.pc += 1;
216            progress = true;
217        }
218
219        progress
220    }
221}
222
223examples!(Day18 -> (i64, u32) [
224    {
225        input: "set a 1\n\
226            add a 2\n\
227            mul a a\n\
228            mod a 5\n\
229            snd a\n\
230            set a 0\n\
231            rcv a\n\
232            jgz a -1\n\
233            set a 1\n\
234            jgz a -2",
235        part1: 4,
236    },
237    {
238        input: "snd 1\n\
239            snd 2\n\
240            snd p\n\
241            rcv a\n\
242            rcv b\n\
243            rcv c\n\
244            rcv d",
245        part2: 3,
246    },
247]);