Skip to main content

crypto_brainfuck/utils/
brainfuck.rs

1/// Maximum memory in bytes an interpreter can use.
2pub const MAX_MEMORY: usize = 30000;
3
4#[derive(Debug, Copy, Clone, Eq, PartialEq)]
5pub enum Op {
6    IncrementPtr,
7    DecrementPtr,
8    IncrementMemory,
9    DecrementMemory,
10    ReadByte,
11    WriteByte,
12    JumpForward,
13    JumpBackward,
14}
15
16impl Op {
17    fn from_char(character: char) -> Option<Self> {
18        match character {
19            '>' => Some(Op::IncrementPtr),
20            '<' => Some(Op::DecrementPtr),
21            '+' => Some(Op::IncrementMemory),
22            '-' => Some(Op::DecrementMemory),
23            '.' => Some(Op::WriteByte),
24            ',' => Some(Op::ReadByte),
25            '[' => Some(Op::JumpForward),
26            ']' => Some(Op::JumpBackward),
27            _ => None,
28        }
29    }
30}
31
32pub struct Program {
33    pub instructions: Vec<Op>,
34}
35
36impl Program {
37    pub fn from_string(string: &str) -> Self {
38        let ops: Vec<Op> = string.chars().map(|c| -> Option<Op> { Op::from_char(c) }).filter_map(|x| x).collect();
39
40        Program { instructions: ops }
41    }
42
43    pub fn find_matching_jump_end(&self, jump_start_pos: usize) -> usize {
44        let mut pos = jump_start_pos;
45        let mut level = 0;
46
47        loop {
48            match self.instructions[pos] {
49                Op::JumpForward => level += 1,
50                Op::JumpBackward => level -= 1,
51                _ => (),
52            }
53
54            if level == 0 {
55                return pos;
56            }
57            if pos >= self.instructions.len() {
58                panic!("unbalanced parentheses")
59            }
60            pos += 1
61        }
62    }
63
64    pub fn find_matching_jump_start(&self, jump_end_pos: usize) -> usize {
65        let mut pos = jump_end_pos;
66        let mut level = 0;
67
68        loop {
69            match self.instructions[pos] {
70                Op::JumpForward => level -= 1,
71                Op::JumpBackward => level += 1,
72                _ => (),
73            }
74
75            if level == 0 {
76                return pos;
77            }
78            if pos == 0 {
79                panic!("unbalanced parentheses")
80            }
81            pos -= 1
82        }
83    }
84}
85
86#[cfg(test)]
87mod test {
88    use super::*;
89
90    #[test]
91    fn check_supported_ops() {
92        let program = Program::from_string("><+-.,[]");
93
94        assert_eq!(program.instructions[0], Op::IncrementPtr);
95        assert_eq!(program.instructions[1], Op::DecrementPtr);
96        assert_eq!(program.instructions[2], Op::IncrementMemory);
97        assert_eq!(program.instructions[3], Op::DecrementMemory);
98        assert_eq!(program.instructions[4], Op::WriteByte);
99        assert_eq!(program.instructions[5], Op::ReadByte);
100        assert_eq!(program.instructions[6], Op::JumpForward);
101        assert_eq!(program.instructions[7], Op::JumpBackward);
102    }
103
104    #[test]
105    fn ignores_comments() {
106        let program = Program::from_string(">loop[-]");
107
108        assert_eq!(program.instructions.len(), 4);
109        assert_eq!(program.instructions[0], Op::IncrementPtr);
110        assert_eq!(program.instructions[1], Op::JumpForward);
111        assert_eq!(program.instructions[2], Op::DecrementMemory);
112        assert_eq!(program.instructions[3], Op::JumpBackward);
113    }
114
115    #[test]
116    fn find_matching_parentheses() {
117        let program = Program::from_string("[[][]]");
118
119        assert_eq!(program.find_matching_jump_end(0), 5);
120        assert_eq!(program.find_matching_jump_start(5), 0);
121
122        assert_eq!(program.find_matching_jump_end(3), 4);
123        assert_eq!(program.find_matching_jump_start(4), 3);
124    }
125}