mini_brainfuck/
brainfuck.rs

1// /$$$$$$$                     /$$            /$$$$$$                     /$$
2// | $$__  $$                   |__/           /$$__  $$                   | $$
3// | $$  \ $$  /$$$$$$  /$$$$$$  /$$ /$$$$$$$ | $$  \__//$$   /$$  /$$$$$$$| $$   /$$
4// | $$$$$$$  /$$__  $$|____  $$| $$| $$__  $$| $$$$   | $$  | $$ /$$_____/| $$  /$$/
5// | $$__  $$| $$  \__/ /$$$$$$$| $$| $$  \ $$| $$_/   | $$  | $$| $$      | $$$$$$/
6// | $$  \ $$| $$      /$$__  $$| $$| $$  | $$| $$     | $$  | $$| $$      | $$_  $$
7// | $$$$$$$/| $$     |  $$$$$$$| $$| $$  | $$| $$     |  $$$$$$/|  $$$$$$$| $$ \  $$
8// |_______/ |__/      \_______/|__/|__/  |__/|__/      \______/  \_______/|__/  \__/
9//
10// -----------------------------------------------------------------------------------------------
11// Copiright 2021 <mauro.balades@tutanota.com>
12//
13
14mod errors;
15
16use std::io::Read;
17use std::str::Chars;
18
19use crate::errors as bf_error;
20
21#[derive(Clone)]
22pub struct BFConfig {
23    // The level of verbosity (default to 0)
24    pub(crate) debug: i32,
25
26    // Add support for the `!` symbol.
27    pub(crate) exit_support: bool,
28}
29
30pub fn default_bf_config() -> BFConfig {
31    BFConfig {
32        debug: 0,
33        exit_support: false,
34    }
35}
36
37fn do_left_bracket(chars: Chars, index: i32) -> i32 {
38    let mut ix: i32 = index;
39    let mut open = 1;
40    while open != 0 {
41        ix += 1;
42
43        match chars.clone().nth(ix.try_into().unwrap()).unwrap() {
44            '[' => open += 1,
45            ']' => open -= 1,
46            _ => (),
47        }
48    }
49
50    return ix;
51}
52
53fn do_right_bracket(chars: Chars, index: i32) -> i32 {
54    let mut ix: i32 = index;
55    let mut close = 1;
56    while close != 0 {
57        ix -= 1;
58
59        if ix >= chars.clone().count().try_into().unwrap() {
60            bf_error::error(
61                "Syntax error".to_string(),
62                "couldn't find next matching ']'".to_string(),
63            );
64        }
65
66        match chars.clone().nth(ix.try_into().unwrap()).unwrap() {
67            '[' => close -= 1,
68            ']' => close += 1,
69            _ => (),
70        }
71    }
72
73    return ix;
74}
75
76pub fn brainfuck(programm: String, config: BFConfig) -> [u8; 3000] {
77    let mut cells: [u8; 3000] = [0; 3000];
78    let mut max: i32 = 1;
79    let possition: &mut usize = &mut 0;
80    let chars: Chars = programm.chars();
81
82    // configuration
83    let debug: i32 = config.debug;
84    let exit_support: bool = config.exit_support;
85
86    let mut index: i32 = 0;
87    while index < chars.clone().count().try_into().unwrap() {
88        let cur_char = chars.clone().nth(index.try_into().unwrap()).unwrap();
89
90        match cur_char {
91            // # does not actually appear in brainfuck's docs.
92            // This character is used to debug only supported
93            // in this interpreter (if debug option is set to
94            // true)
95            '#' => {
96                if debug > 0 {
97                    println!("{}", cells[*possition])
98                }
99            }
100
101            // `!` is a custom symbold, it can be used to exit
102            // the programm with code 2. You can use it by
103            // enable it by declaring exit_support as true
104            // in the configuration.
105            '!' => {
106                if exit_support {
107                    std::process::exit(2);
108                }
109            }
110
111            // Increment value by 1 in current cell possition.
112            // if the curren't value for the cell is 255,
113            // we will set it to 0.
114            '+' => {
115                let mut cell: u8 = cells[*possition];
116
117                if cell == 255 {
118                    cell = 0;
119                } else {
120                    cell = cell.wrapping_add(1);
121                }
122
123                cells[*possition] = cell;
124            }
125
126            // Decrement value by 1 in current cell possition
127            '-' => {
128                let mut cell: u8 = cells[*possition];
129
130                if cell == 0 {
131                    cell = 255;
132                } else {
133                    cell = cell.wrapping_sub(1);
134                }
135
136                cells[*possition] = cell;
137            }
138
139            // Move the current possition to the next cell
140            '>' => {
141                if *possition as i32 == 2999 {
142                    *possition = 0
143                } else {
144                    *possition += 1
145                }
146            }
147
148            // Go back one cell
149            '<' => {
150                if (*possition as i32) == 0 {
151                    *possition = 2999;
152                } else {
153                    *possition =
154                        *&mut ((*possition as usize).checked_sub(1)).unwrap_or_default() as usize;
155                }
156            }
157
158            // Print the current cell's ASCII value.
159            '.' => print!("{}", cells[*possition] as char),
160
161            // Set value from stdin to the current cell
162            ',' => {
163                // declare a new buffer array containing a
164                // 'u8' type number to store a character code
165                let mut buf = [0; 1];
166
167                // Read input and check if an error has occoured.
168                match std::io::stdin().read_exact(&mut buf) {
169                    Ok(_) => cells[*possition] = buf[0], // Add buffer from input
170                    Err(_) => {
171                        bf_error::error(
172                            "IO error".to_string(),
173                            "Error while trying to get an input from stdin".to_string(),
174                        );
175                    }
176                }
177            }
178
179            // Left brackets are like c while(cur_block_value != 0) loop.
180            '[' => {
181                if cells[*possition] == 0 {
182                    index = do_left_bracket(chars.clone(), index)
183                }
184            }
185
186            // if block currently pointed to's value is not zero, jump back to [
187            ']' => {
188                if cells[*possition] != 0 {
189                    index = do_right_bracket(chars.clone(), index)
190                }
191            }
192
193            // In Brainfuck, other ASCII characters that
194            // are not ["+", ",", "-", "<", ">", ".", "[", "]"]
195            // they are considered as comments, so we do nothing.
196            _ => (),
197        }
198
199        index += 1;
200
201        if (*possition as i32) + 1 > max {
202            max = (*possition as i32) + 1;
203        }
204    }
205
206    if debug > 1 {
207        println!("\n\n=== CELLS ===");
208        for i in 0..max {
209            println!("c[{}]: {}", i, cells[i as usize]);
210        }
211    }
212
213    return cells;
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn cell_add() {
222        let code: String = "+".to_string();
223        let config: BFConfig = BFConfig {
224            ..default_bf_config()
225        };
226        let cells = brainfuck(code, config);
227        let mut res: [u8; 3000] = [0; 3000];
228
229        res[0] = 1;
230        assert_eq!(res, cells);
231    }
232
233    #[test]
234    fn cell_sub() {
235        let code: String = "-".to_string();
236        let config: BFConfig = BFConfig {
237            ..default_bf_config()
238        };
239
240        let cells = brainfuck(code, config);
241        let mut res: [u8; 3000] = [0; 3000];
242
243        res[0] = 255;
244        assert_eq!(res, cells);
245    }
246
247    #[test]
248    fn cell_sub_plus() {
249        let code: String = "++-".to_string();
250        let config: BFConfig = BFConfig {
251            ..default_bf_config()
252        };
253
254        let cells = brainfuck(code, config);
255        let mut res: [u8; 3000] = [0; 3000];
256
257        res[0] = 1;
258        assert_eq!(res, cells);
259    }
260
261    #[test]
262    fn cell_left() {
263        let code: String = ">+".to_string();
264        let config: BFConfig = BFConfig {
265            ..default_bf_config()
266        };
267
268        let cells = brainfuck(code, config);
269        let mut res: [u8; 3000] = [0; 3000];
270
271        res[1] = 1;
272        assert_eq!(res, cells);
273    }
274
275    #[test]
276    fn cell_right() {
277        let code: String = "><+".to_string();
278        let config: BFConfig = BFConfig {
279            ..default_bf_config()
280        };
281
282        let cells = brainfuck(code, config);
283        let mut res: [u8; 3000] = [0; 3000];
284
285        res[0] = 1;
286        assert_eq!(res, cells);
287    }
288
289    #[test]
290    fn cell_right_end() {
291        let code: String = "<+".to_string();
292        let config: BFConfig = BFConfig {
293            ..default_bf_config()
294        };
295
296        let cells = brainfuck(code, config);
297        let mut res: [u8; 3000] = [0; 3000];
298
299        res[2999] = 1;
300        assert_eq!(res, cells);
301    }
302
303    #[test]
304    fn cell_left_end() {
305        let code: String = "<>+".to_string();
306        let config: BFConfig = BFConfig {
307            ..default_bf_config()
308        };
309
310        let cells = brainfuck(code, config);
311        let mut res: [u8; 3000] = [0; 3000];
312
313        res[0] = 1;
314        assert_eq!(res, cells);
315    }
316
317    #[test]
318    fn cell_loop() {
319        // Move 5 to the left
320        // Loop and adding 1 to c1
321        // and removing 1 to c2
322        let code: String = ">+++++[<+>-]".to_string();
323        let config: BFConfig = BFConfig {
324            ..default_bf_config()
325        };
326
327        let cells = brainfuck(code, config);
328        let mut res: [u8; 3000] = [0; 3000];
329
330        res[0] = 5;
331        assert_eq!(res, cells);
332    }
333
334    #[test]
335    fn comments() {
336        let code: String = "Lets add one (+) move to left (>) and add 2 (++)".to_string();
337        let config: BFConfig = BFConfig {
338            ..default_bf_config()
339        };
340
341        let cells = brainfuck(code, config);
342        let mut res: [u8; 3000] = [0; 3000];
343
344        res[0] = 1;
345        res[1] = 2;
346        assert_eq!(res, cells);
347    }
348}