brainfoamkit_lib/program.rs
1// SPDX-FileCopyrightText: 2023 - 2024 Ali Sajid Imami
2//
3// SPDX-License-Identifier: Apache-2.0
4// SPDX-License-Identifier: MIT
5
6use std::{
7 fmt::{
8 self,
9 Display,
10 Formatter,
11 },
12 ops::Index,
13};
14
15use crate::Instruction;
16
17/// Structure to hold the program.
18///
19/// A `Program` is a series if instructions stored in the program stack.
20/// This struct allows us to conveniently read the program, modify it and save
21/// it back.
22///
23/// # Examples
24///
25/// ## Loading a `Program` from a series of instructions
26///
27/// ```
28/// use brainfoamkit_lib::{
29/// Instruction,
30/// Program,
31/// };
32///
33/// let instructions = vec![
34/// Instruction::IncrementPointer,
35/// Instruction::IncrementValue,
36/// Instruction::DecrementPointer,
37/// Instruction::DecrementValue,
38/// ];
39/// let mut program = Program::from(instructions);
40///
41/// assert_eq!(program.length(), Some(4));
42/// ```
43///
44/// ## Load a `Program` from a string
45///
46/// ```
47/// // TODO: Verify this example
48/// use brainfoamkit_lib::Program;
49///
50/// let program_string = ">>++<<--";
51/// let program = Program::from(program_string);
52///
53/// assert_eq!(program.length(), Some(8));
54/// ```
55///
56/// ## Get an instruction from a `Program`
57///
58/// ```
59/// // TODO: Verify this example
60/// use brainfoamkit_lib::{
61/// Instruction,
62/// Program,
63/// };
64///
65/// let program_string = ">+<-";
66///
67/// let mut program = Program::from(program_string);
68///
69/// assert_eq!(
70/// program.get_instruction(0),
71/// Some(Instruction::IncrementPointer)
72/// );
73/// assert_eq!(
74/// program.get_instruction(1),
75/// Some(Instruction::IncrementValue)
76/// );
77/// assert_eq!(
78/// program.get_instruction(2),
79/// Some(Instruction::DecrementPointer)
80/// );
81/// assert_eq!(
82/// program.get_instruction(3),
83/// Some(Instruction::DecrementValue)
84/// );
85/// assert_eq!(program.get_instruction(4), None);
86/// ```
87#[derive(PartialEq, Debug, Eq, Clone)]
88pub struct Program {
89 /// The instructions for the program
90 instructions: Vec<Instruction>,
91}
92
93impl Program {
94 /// Get an instruction from a `Program` at a specific index
95 ///
96 /// This method gets an instruction from the program at a specific index.
97 ///
98 /// # Arguments
99 ///
100 /// * `index` - The index of the instruction to get
101 ///
102 /// # Examples
103 ///
104 /// ```
105 /// use brainfoamkit_lib::{
106 /// Instruction,
107 /// Program,
108 /// };
109 ///
110 /// let instructions = ">>++<<--";
111 /// let program = Program::from(instructions);
112 ///
113 /// assert_eq!(
114 /// program.get_instruction(0),
115 /// Some(Instruction::IncrementPointer)
116 /// );
117 /// assert_eq!(
118 /// program.get_instruction(1),
119 /// Some(Instruction::IncrementPointer)
120 /// );
121 /// assert_eq!(
122 /// program.get_instruction(2),
123 /// Some(Instruction::IncrementValue)
124 /// );
125 /// assert_eq!(
126 /// program.get_instruction(3),
127 /// Some(Instruction::IncrementValue)
128 /// );
129 /// assert_eq!(
130 /// program.get_instruction(4),
131 /// Some(Instruction::DecrementPointer)
132 /// );
133 /// assert_eq!(
134 /// program.get_instruction(5),
135 /// Some(Instruction::DecrementPointer)
136 /// );
137 /// assert_eq!(
138 /// program.get_instruction(6),
139 /// Some(Instruction::DecrementValue)
140 /// );
141 /// assert_eq!(
142 /// program.get_instruction(7),
143 /// Some(Instruction::DecrementValue)
144 /// );
145 /// assert_eq!(program.get_instruction(8), None);
146 /// ```
147 ///
148 /// # Returns
149 ///
150 /// The `Instruction` at the given index
151 ///
152 /// # See Also
153 ///
154 /// * [`length()`](#method.length): Get the length of the program
155 #[must_use]
156 pub fn get_instruction(&self, index: usize) -> Option<Instruction> {
157 self.length().and_then(|length| {
158 if index >= length {
159 None
160 } else {
161 Some(self.instructions[index])
162 }
163 })
164 }
165
166 /// Find the matching `JumpBackward` instruction for the given `JumpForward`
167 /// instruction
168 ///
169 /// This method allows us to identify the boundaries of a given loop.
170 /// It will return the index of the matching `JumpBackward` instruction for
171 /// the given `JumpForward` instruction. It returns `None` if no
172 /// matching `JumpBackward` instruction is found or the instruction
173 /// at the given index is not a `JumpForward` instruction.
174 ///
175 /// # Examples
176 ///
177 /// ```
178 /// use brainfoamkit_lib::{
179 /// Instruction,
180 /// Program,
181 /// };
182 ///
183 /// let instructions = "[[]]";
184 /// let mut program = Program::from(instructions);
185 ///
186 /// assert_eq!(program.find_matching_bracket(0), Some(3));
187 /// assert_eq!(program.find_matching_bracket(1), Some(2));
188 /// ```
189 ///
190 /// # Returns
191 ///
192 /// The index of the matching bracket
193 ///
194 /// # See Also
195 ///
196 /// * [`length()`](#method.length): Get the length of the program
197 /// * [`get_instruction()`](#method.get_instruction): Get an instruction
198 /// from a `Program`
199 #[must_use]
200 pub fn find_matching_bracket(&self, index: usize) -> Option<usize> {
201 match self.get_instruction(index) {
202 Some(Instruction::JumpForward) => {
203 let mut bracket_counter = 0;
204 let mut index = index;
205
206 loop {
207 match self.instructions.get(index) {
208 Some(Instruction::JumpForward) => bracket_counter += 1,
209 Some(Instruction::JumpBackward) => bracket_counter -= 1,
210 _ => (),
211 }
212
213 if bracket_counter == 0 {
214 break;
215 }
216
217 index += 1;
218 }
219
220 Some(index)
221 }
222 _ => None,
223 }
224 }
225
226 /// Get the length of the program
227 ///
228 /// This method returns the length of the program.
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use brainfoamkit_lib::Program;
234 ///
235 /// let program_string = ">>++<<--";
236 /// let program = Program::from(program_string);
237 ///
238 /// assert_eq!(program.length(), Some(8));
239 /// ```
240 ///
241 /// # Returns
242 ///
243 /// The length of the program
244 #[must_use]
245 pub fn length(&self) -> Option<usize> {
246 if self.instructions.is_empty() {
247 None
248 } else {
249 Some(self.instructions.len())
250 }
251 }
252}
253
254impl Default for Program {
255 fn default() -> Self {
256 Self::from(vec![Instruction::NoOp; 10])
257 }
258}
259
260impl Display for Program {
261 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
262 for (index, instruction) in self.instructions.iter().enumerate() {
263 // Index should be zero padded to 4 digits
264 writeln!(f, "{index:04}: {instruction}")?;
265 }
266 Ok(())
267 }
268}
269
270impl Index<usize> for Program {
271 type Output = Instruction;
272
273 fn index(&self, index: usize) -> &Self::Output {
274 &self.instructions[index]
275 }
276}
277
278impl From<&str> for Program {
279 /// Load a `Program` from a string
280 ///
281 /// This method loads a `Program` from a string.
282 ///
283 /// # Arguments
284 ///
285 /// * `program` - A string containing the program to load
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// use brainfoamkit_lib::Program;
291 ///
292 /// let program_string = ">>++<<--";
293 /// let program = Program::from(program_string);
294 ///
295 /// assert_eq!(program.length(), Some(8));
296 /// ```
297 ///
298 /// # See Also
299 ///
300 /// * [`from()`](#method.from): Create a new `Program` from a series of
301 /// instructions
302 fn from(program: &str) -> Self {
303 let mut instructions = Vec::new();
304
305 for c in program.chars() {
306 instructions.push(Instruction::from_char(c));
307 }
308
309 Self { instructions }
310 }
311}
312
313impl From<Vec<Instruction>> for Program {
314 /// Create a new `Program` from a series of instructions
315 ///
316 /// This method creates a new `Program` from a series of instructions.
317 ///
318 /// # Arguments
319 ///
320 /// * `instructions` - A vector of `Instruction`s to load into the `Program`
321 ///
322 /// # Examples
323 ///
324 /// ```
325 /// use brainfoamkit_lib::{
326 /// Instruction,
327 /// Program,
328 /// };
329 ///
330 /// let instructions = vec![
331 /// Instruction::IncrementPointer,
332 /// Instruction::IncrementValue,
333 /// Instruction::DecrementPointer,
334 /// Instruction::DecrementValue,
335 /// ];
336 /// let program: Program = Program::from(instructions);
337 ///
338 /// assert_eq!(program.length(), Some(4));
339 /// ```
340 ///
341 /// # See Also
342 ///
343 /// * [`from()`](#method.from): Load a `Program` from a string
344 fn from(instructions: Vec<Instruction>) -> Self {
345 Self { instructions }
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_program_from() {
355 let instructions = vec![Instruction::NoOp];
356 let program = Program::from(instructions);
357
358 assert_eq!(program.instructions.len(), 1);
359 assert_eq!(program.length(), Some(1));
360 }
361
362 #[test]
363 fn test_program_load_from() {
364 let instructions = ">>++<<--";
365 let program = Program::from(instructions);
366
367 assert_eq!(program.instructions.len(), 8);
368 assert_eq!(program.length(), Some(8));
369 }
370
371 #[test]
372 fn test_program_length() {
373 let program = Program::from(">>++<<--");
374 assert_eq!(program.length(), Some(8));
375
376 let program = Program::from("");
377 assert_eq!(program.length(), None);
378 }
379
380 #[test]
381 fn test_program_default() {
382 let program = Program::default();
383
384 assert_eq!(program.instructions.len(), 10);
385 assert_eq!(program.length(), Some(10));
386 }
387
388 #[test]
389 fn test_program_display() {
390 let instructions = vec![Instruction::NoOp];
391 let program = Program::from(instructions);
392
393 assert_eq!(program.to_string(), "0000: NOOP\n");
394
395 let instructions = vec![Instruction::NoOp, Instruction::NoOp];
396 let program = Program::from(instructions);
397 assert_eq!(program.to_string(), "0000: NOOP\n0001: NOOP\n");
398 }
399
400 #[test]
401 fn test_program_find_matching_bracket() {
402 let instructions = "[]";
403 let program = Program::from(instructions);
404
405 assert_eq!(program.find_matching_bracket(0), Some(1));
406 }
407
408 #[test]
409 fn test_program_find_matching_bracket_nested() {
410 let instructions = "[[]]";
411 let program = Program::from(instructions);
412
413 assert_eq!(program.find_matching_bracket(0), Some(3));
414 }
415
416 // #[test]
417 // fn test_find_matching_bracket_no_match() {
418 // let instructions = "[";
419 // let program = Program::from(instructions);
420
421 // assert_eq!(program.find_matching_bracket(0), None);
422 // }
423
424 #[test]
425 fn test_find_matching_bracket_not_jump_forward() {
426 let instructions = "]";
427 let program = Program::from(instructions);
428
429 assert_eq!(program.find_matching_bracket(0), None);
430 }
431
432 #[test]
433 fn test_get_instruction() {
434 let instructions = vec![
435 Instruction::IncrementPointer,
436 Instruction::IncrementPointer,
437 Instruction::IncrementValue,
438 Instruction::IncrementValue,
439 Instruction::DecrementPointer,
440 Instruction::DecrementPointer,
441 Instruction::DecrementValue,
442 Instruction::DecrementValue,
443 ];
444 let program = Program::from(instructions);
445
446 assert_eq!(
447 program.get_instruction(0),
448 Some(Instruction::IncrementPointer)
449 );
450 assert_eq!(
451 program.get_instruction(1),
452 Some(Instruction::IncrementPointer)
453 );
454 assert_eq!(
455 program.get_instruction(2),
456 Some(Instruction::IncrementValue)
457 );
458 assert_eq!(
459 program.get_instruction(3),
460 Some(Instruction::IncrementValue)
461 );
462 assert_eq!(
463 program.get_instruction(4),
464 Some(Instruction::DecrementPointer)
465 );
466 assert_eq!(
467 program.get_instruction(5),
468 Some(Instruction::DecrementPointer)
469 );
470 assert_eq!(
471 program.get_instruction(6),
472 Some(Instruction::DecrementValue)
473 );
474 assert_eq!(
475 program.get_instruction(7),
476 Some(Instruction::DecrementValue)
477 );
478 assert_eq!(program.get_instruction(8), None);
479 }
480
481 #[test]
482 fn test_find_matching_bracket() {
483 let instructions = vec![
484 Instruction::JumpForward,
485 Instruction::JumpForward,
486 Instruction::JumpBackward,
487 Instruction::JumpBackward,
488 ];
489 let program = Program::from(instructions);
490
491 assert_eq!(program.find_matching_bracket(0), Some(3));
492 assert_eq!(program.find_matching_bracket(1), Some(2));
493 assert_eq!(program.find_matching_bracket(2), None);
494 assert_eq!(program.find_matching_bracket(3), None);
495 }
496
497 #[test]
498 fn test_default() {
499 let program = Program::default();
500 assert_eq!(program.length(), Some(10));
501 assert_eq!(program.get_instruction(0), Some(Instruction::NoOp));
502 assert_eq!(program.get_instruction(9), Some(Instruction::NoOp));
503 }
504
505 #[test]
506 fn test_index() {
507 let program = Program::from(">>++<<--");
508
509 assert_eq!(program[0], Instruction::IncrementPointer);
510 assert_eq!(program[1], Instruction::IncrementPointer);
511 assert_eq!(program[2], Instruction::IncrementValue);
512 assert_eq!(program[3], Instruction::IncrementValue);
513 assert_eq!(program[4], Instruction::DecrementPointer);
514 assert_eq!(program[5], Instruction::DecrementPointer);
515 assert_eq!(program[6], Instruction::DecrementValue);
516 assert_eq!(program[7], Instruction::DecrementValue);
517 }
518
519 #[test]
520 #[should_panic(expected = "index out of bounds")]
521 fn test_index_out_of_bounds() {
522 let program = Program::from(">>++<<--");
523 let _ = program[8];
524 }
525}