1use std::fmt;
28use std::sync::{
29 atomic::{AtomicBool, Ordering},
30 Arc,
31};
32
33#[derive(Debug, thiserror::Error)]
35pub enum BrainfuckReaderError {
36 #[error("Pointer out of bounds at instruction {ip} (ptr={ptr}, op='{op}')")]
38 PointerOutOfBounds { ip: usize, ptr: usize, op: char },
39
40 #[error("Invalid character: '{ch}' at instruction {ip}")]
42 InvalidCharacter { ch: char, ip: usize },
43
44 #[error("Unmatched bracket {kind} at instruction {ip}")]
46 UnmatchedBrackets{ ip: usize, kind: UnmatchedBracketKind },
47
48 #[error("I/O error at instruction {ip}: {source}")]
50 IoError { ip: usize, #[source] source: std::io::Error },
51
52 #[error("Execution aborted: step limit exceeded ({limit})")]
54 StepLimitExceeded { limit: usize },
55
56 #[error("Execution aborted: cancelled")]
58 Canceled,
59}
60
61#[derive(Debug, Clone, Copy)]
63pub enum UnmatchedBracketKind {
64 Open,
65 Close,
66}
67
68impl fmt::Display for UnmatchedBracketKind {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 UnmatchedBracketKind::Open => write!(f, "'['"),
72 UnmatchedBracketKind::Close => write!(f, "']'"),
73 }
74 }
75}
76
77#[derive(Clone)]
79pub struct StepControl {
80 pub max_steps: Option<usize>,
81 pub cancel_flag: Arc<AtomicBool>,
82}
83
84impl StepControl {
85 pub fn new(max_steps: Option<usize>, cancel_flag: Arc<AtomicBool>) -> Self {
86 Self { max_steps, cancel_flag }
87 }
88}
89
90pub struct BrainfuckReader {
97 code: String,
98 memory: Vec<u8>,
99 pointer: usize,
100 output_sink: Option<Box<dyn Fn(&[u8]) + Send + Sync>>,
102 input_provider: Option<Box<dyn Fn() -> Option<u8> + Send + Sync>>,
103 tape_observer: Option<(usize, Box<dyn Fn(usize, usize, &[u8]) + Send + Sync>)>,
105}
106
107impl BrainfuckReader {
108 pub fn new(code: String) -> Self {
112 Self {
113 code,
114 memory: vec![0; 30000],
115 pointer: 0,
116 output_sink: None,
117 input_provider: None,
118 tape_observer: None,
119 }
120 }
121
122 pub fn new_with_memory(code: String, memory_size: usize) -> Self {
124 Self {
125 code,
126 memory: vec![0; memory_size],
127 pointer: 0,
128 output_sink: None,
129 input_provider: None,
130 tape_observer: None,
131 }
132 }
133
134 pub fn set_output_sink<F>(&mut self, sink: F)
137 where
138 F: Fn(&[u8]) + Send + Sync + 'static,
139 {
140 self.output_sink = Some(Box::new(sink));
141 }
142
143 pub fn set_input_provider<F>(&mut self, provider: F)
146 where
147 F: Fn() -> Option<u8> + Send + Sync + 'static,
148 {
149 self.input_provider = Some(Box::new(provider));
150 }
151
152 pub fn set_tape_observer<F>(&mut self, window_size: usize, observer: F)
154 where
155 F: Fn(usize, usize, &[u8]) + Send + Sync + 'static,
159 {
160 self.tape_observer = Some((window_size.max(1), Box::new(observer)));
161 }
162
163 fn execute(&mut self, debug: bool, step_control: Option<&StepControl>) -> Result<(), BrainfuckReaderError> {
165 let mut code_ptr = 0;
166 let chars: Vec<char> = self.code.chars().collect();
167 let code_len = chars.len();
168
169 let mut jump_map: Vec<Option<usize>> = vec![None; code_len];
173 {
174 let mut stack: Vec<usize> = Vec::new();
175 for (i, &c) in chars.iter().enumerate() {
176 if c == '[' {
177 stack.push(i);
178 } else if c == ']' {
179 let Some(open_index) = stack.pop() else {
180 return Err(BrainfuckReaderError::UnmatchedBrackets {
181 ip: i,
182 kind: UnmatchedBracketKind::Close,
183 });
184 };
185 jump_map[open_index] = Some(i);
186 jump_map[i] = Some(open_index);
187 }
188 }
189
190 if let Some(unmatched_open) = stack.last().copied() {
191 return Err(BrainfuckReaderError::UnmatchedBrackets {
192 ip: unmatched_open,
193 kind: UnmatchedBracketKind::Open,
194 })
195 }
196 }
197
198 let mut step: usize = 0;
199 if debug {
200 println!("STEP | IP | PTR | CELL | INSTR | ACTION");
201 println!("-----+-----+-----+------+-------+------------------------------------------------");
202 }
203
204 while code_ptr < code_len {
205 if let Some(ctrl) = step_control {
207 if ctrl.cancel_flag.load(Ordering::Relaxed) {
208 return Err(BrainfuckReaderError::Canceled);
209 }
210 }
211
212 if let Some(ctrl) = step_control {
214 if let Some(max) = ctrl.max_steps {
215 if step >= max {
216 return Err(BrainfuckReaderError::StepLimitExceeded { limit: max });
217 }
218 }
219 }
220
221 let instr = chars[code_ptr];
222 let (ptr_before, cell_before) = (self.pointer, self.memory[self.pointer]);
223 let mut action: Option<String> = if debug { Some(String::new()) } else { None };
224
225 match instr {
226 '>' => {
227 if self.pointer >= self.memory.len() - 1 {
228 return Err(BrainfuckReaderError::PointerOutOfBounds {
229 ip: code_ptr,
230 ptr: self.pointer,
231 op: instr,
232 });
233 }
234 self.pointer += 1;
235 if let Some(a) = action.as_mut() { *a = format!("Moved pointer head to index {}", self.pointer); }
236 }
237 '<' => {
238 if self.pointer == 0 {
239 return Err(BrainfuckReaderError::PointerOutOfBounds {
240 ip: code_ptr,
241 ptr: self.pointer,
242 op: instr,
243 });
244 }
245 self.pointer -= 1;
246 if let Some(a) = action.as_mut() { *a = format!("Moved pointer head to index {}", self.pointer); }
247 }
248 '+' => {
249 let after = self.memory[self.pointer].wrapping_add(1);
250 self.memory[self.pointer] = after;
251 if let Some(a) = action.as_mut() { *a = format!("Increment cell[{}] from {} to {}", ptr_before, cell_before, after); }
252 }
253 '-' => {
254 let after = self.memory[self.pointer].wrapping_sub(1);
255 self.memory[self.pointer] = after;
256 if let Some(a) = action.as_mut() { *a = format!("Decrement cell[{}] from {} to {}", ptr_before, cell_before, after); }
257 }
258 '.' => {
259 if debug {
260 if let Some(a) = action.as_mut() { *a = format!("Output byte '{}' (suppressed in debug)", self.memory[self.pointer] as char); }
261 } else {
262 if let Some(sink) = self.output_sink.as_ref() {
264 let b = [self.memory[self.pointer]];
265 (sink)(&b);
266 } else {
267 print!("{}", self.memory[self.pointer] as char);
268 }
269 }
270 }
271 ',' => {
272 if debug {
273 self.memory[self.pointer] = 0; if let Some(a) = action.as_mut() { *a = "Read byte from stdin -> simulated EOF (set cell to 0)".to_string(); }
275 } else {
276 if let Some(provider) = self.input_provider.as_ref() {
278 match (provider)() {
279 Some(b) => { self.memory[self.pointer] = b; }
280 None => { self.memory[self.pointer] = 0; } }
282 if let Some(a) = action.as_mut() { *a = format!("Read byte from input provider -> {}", self.memory[self.pointer]); }
283 } else {
284 use std::io::Read;
287 let mut buf = [0u8; 1];
288 match std::io::stdin().read(&mut buf) {
289 Ok(0) => {
290 self.memory[self.pointer] = 0;
292 }
293 Ok(_) => {
294 self.memory[self.pointer] = buf[0];
295 }
296 Err(e) => {
297 return Err(BrainfuckReaderError::IoError { ip: code_ptr, source: e });
298 }
299 }
300 if let Some(a) = action.as_mut() { *a = format!("Read byte from stdin -> {}", self.memory[self.pointer]); }
301 }
302 }
303 }
304 '[' => {
305 if self.memory[self.pointer] == 0 {
306 let j = jump_map[code_ptr].expect("validated bracket");
307 if let Some(a) = action.as_mut() { *a = format!("Cell is 0; jump forward to matching ']' at IP {}", j); }
308 code_ptr = j;
309 } else if let Some(a) = action.as_mut() {
310 *a = "Enter loop (cell != 0)".to_string();
311 }
312 }
313 ']' => {
314 if self.memory[self.pointer] != 0 {
315 let j = jump_map[code_ptr].expect("validated bracket");
316 if let Some(a) = action.as_mut() { *a = format!("Cell != 0; jump back to matching '[' at IP {}", j); }
317 code_ptr = j;
318 } else if let Some(a) = action.as_mut() {
319 *a = "Exit loop (cell is 0)".to_string();
320 }
321 }
322 _ => {
323 return Err(BrainfuckReaderError::InvalidCharacter { ch: instr, ip: code_ptr });
324 }
325 }
326
327 if let Some((win_size, observer)) = self.tape_observer.as_ref() {
329 let base = self.pointer.saturating_sub(self.pointer % *win_size);
330 let end = (base + *win_size).min(self.memory.len());
331 (observer)(self.pointer, base, &self.memory[base..end]);
332 }
333
334 if debug {
335 println!(
336 "{:<4} | {:<3} | {:<3} | {:<4} | {} | {}",
337 step,
338 code_ptr,
339 ptr_before,
340 cell_before,
341 instr,
342 action.unwrap_or_default()
343 );
344 step += 1;
345 }
346
347 step += 1;
349 code_ptr += 1;
351 }
352
353 Ok(())
354 }
355
356 pub fn run(&mut self) -> Result<(), BrainfuckReaderError> {
360 self.execute(false, None)
361 }
362
363 pub fn run_debug(&mut self) -> Result<(), BrainfuckReaderError> { self.execute(true, None) }
369
370 pub fn run_with_control(&mut self, step_control: StepControl) -> Result<(), BrainfuckReaderError> {
372 self.execute(false, Some(&step_control))
373 }
374
375 pub fn run_debug_with_control(&mut self, step_control: StepControl) -> Result<(), BrainfuckReaderError> {
377 self.execute(true, Some(&step_control))
378 }
379}
380
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn invalid_character_returns_error() {
388 let mut bf = BrainfuckReader::new_with_memory("+a+".to_string(), 10);
389 let result = bf.run();
390 assert!(matches!(result, Err(BrainfuckReaderError::InvalidCharacter { ch: 'a', .. })));
391 }
392
393 #[test]
394 fn unmatched_open_bracket_returns_error() {
395 let mut bf = BrainfuckReader::new_with_memory("[+".to_string(), 10);
397 let result = bf.run();
398 assert!(matches!(result, Err(BrainfuckReaderError::UnmatchedBrackets { kind: UnmatchedBracketKind::Open, .. })));
399 }
400
401 #[test]
402 fn left_pointer_out_of_bounds_errors() {
403 let mut bf = BrainfuckReader::new_with_memory("<".to_string(), 10);
404 let result = bf.run();
405 assert!(matches!(result, Err(BrainfuckReaderError::PointerOutOfBounds { op: '<', .. })));
406 }
407
408 #[test]
409 fn right_pointer_out_of_bounds_errors() {
410 let memory_size = 3;
412 let code = ">".repeat(memory_size);
413 let mut bf = BrainfuckReader::new_with_memory(code, memory_size);
414 let result = bf.run();
415 assert!(matches!(result, Err(BrainfuckReaderError::PointerOutOfBounds { op: '>', .. })));
416 }
417
418 #[test]
419 fn empty_loop_on_zero_cell_is_ok() {
420 let mut bf = BrainfuckReader::new_with_memory("[]".to_string(), 10);
421 let result = bf.run();
422 assert!(result.is_ok());
423 }
424
425 #[test]
426 fn simple_program_without_io_runs_ok() {
427 let mut bf = BrainfuckReader::new_with_memory("+++[-]".to_string(), 10);
430 let result = bf.run();
431 assert!(result.is_ok());
432 }
433
434 #[test]
435 fn wrapping_subtraction() {
436 let mut bf = BrainfuckReader::new_with_memory("-".to_string(), 1);
437 let result = bf.run();
438 assert!(result.is_ok());
439 assert_eq!(bf.memory[0], 255);
440 }
441
442 #[test]
443 fn wrapping_addition() {
444 let code = "+".repeat(256); let mut bf = BrainfuckReader::new_with_memory(code, 1);
446 let result = bf.run();
447 assert!(result.is_ok());
448 assert_eq!(bf.memory[0], 0);
449 }
450}