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