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}