1pub mod allocators;
22
23use allocators::DynamicAllocator;
24use num::{
25 traits::{WrappingAdd, WrappingSub},
26 Unsigned,
27};
28use std::{
29 any::type_name,
30 convert::{TryFrom, TryInto},
31 fmt::Display,
32 fs::File,
33 io::{self, stdin, stdout, Read, Stdin, Stdout, Write},
34 iter::repeat,
35 marker::PhantomData,
36 path::Path,
37};
38
39#[derive(Clone, Copy, Debug)]
41pub enum Instruction {
42 IncrDP,
44
45 DecrDP,
47
48 Incr,
50
51 Decr,
53
54 Output,
56
57 Input,
59
60 JumpFwd,
62
63 JumpBack,
65}
66
67impl TryFrom<char> for Instruction {
68 type Error = ();
69
70 fn try_from(value: char) -> Result<Self, Self::Error> {
71 match value {
72 '>' => Ok(Instruction::IncrDP),
73 '<' => Ok(Instruction::DecrDP),
74 '+' => Ok(Instruction::Incr),
75 '-' => Ok(Instruction::Decr),
76 '.' => Ok(Instruction::Output),
77 ',' => Ok(Instruction::Input),
78 '[' => Ok(Instruction::JumpFwd),
79 ']' => Ok(Instruction::JumpBack),
80 _ => Err(()),
81 }
82 }
83}
84
85pub struct Program {
93 instructions: Vec<Instruction>,
94}
95
96impl From<&str> for Program {
97 fn from(input: &str) -> Self {
98 let instructions = input
99 .chars()
100 .filter_map(|c| Instruction::try_from(c).ok())
101 .collect();
102
103 Program { instructions }
104 }
105}
106
107pub trait BrainfuckCell:
111 Unsigned + Copy + Default + TryInto<u32> + From<u8> + WrappingAdd + WrappingSub + std::fmt::Debug
112{
113}
114
115impl<
116 T: Unsigned
117 + Copy
118 + Default
119 + TryInto<u32>
120 + From<u8>
121 + WrappingAdd
122 + WrappingSub
123 + std::fmt::Debug,
124 > BrainfuckCell for T
125{
126}
127
128#[derive(Debug)]
132pub struct OutOfBoundsAccess {
133 pub capacity: usize,
135
136 pub access: usize,
138}
139
140#[derive(Debug)]
142pub enum VMMemoryError {
143 OutOfBounds(OutOfBoundsAccess),
145}
146
147impl From<VMMemoryError> for BrainfuckExecutionError {
148 fn from(value: VMMemoryError) -> Self {
149 BrainfuckExecutionError::MemoryError(value)
150 }
151}
152
153pub trait BrainfuckAllocator {
156 fn ensure_capacity<T: BrainfuckCell>(
163 data: &mut Vec<T>,
164 min_size: usize,
165 ) -> Result<(), VMMemoryError>;
166}
167
168struct VirtualMachine<T: BrainfuckCell, A: BrainfuckAllocator, R: Read, W: Write> {
169 data_ptr: usize,
170 data: Vec<T>,
171 alloc: PhantomData<A>,
172 reader: R,
173 writer: W,
174}
175
176pub struct VMBuilder<
180 T: BrainfuckCell = u8,
181 A: BrainfuckAllocator = DynamicAllocator,
182 R: Read = Stdin,
183 W: Write = Stdout,
184> {
185 initial_size: usize,
186 celltype: PhantomData<T>,
187 allocator: PhantomData<A>,
188 reader: R,
189 writer: W,
190}
191
192impl VMBuilder {
193 pub fn new() -> VMBuilder {
195 VMBuilder::default()
196 }
197}
198
199impl Default for VMBuilder {
200 fn default() -> Self {
202 VMBuilder {
203 initial_size: 0,
204 celltype: PhantomData,
205 allocator: PhantomData,
206 reader: stdin(),
207 writer: stdout(),
208 }
209 }
210}
211
212impl<T: BrainfuckCell, A: BrainfuckAllocator, R: Read, W: Write> Display for VMBuilder<T, A, R, W> {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 write!(
215 f,
216 "VMBuilder<{}, {}, {}, {}> with initial size {}",
217 type_name::<T>(),
218 type_name::<A>(),
219 type_name::<R>(),
220 type_name::<W>(),
221 self.initial_size
222 )?;
223
224 Ok(())
225 }
226}
227
228impl<T: BrainfuckCell + 'static, A: BrainfuckAllocator + 'static, R: Read + 'static, W: Write + 'static>
229 VMBuilder<T, A, R, W>
230{
231 pub fn with_cell_type<U: BrainfuckCell>(self) -> VMBuilder<U, A, R, W> {
233 VMBuilder {
234 initial_size: self.initial_size,
235 celltype: PhantomData::<U>,
236 allocator: self.allocator,
237 reader: self.reader,
238 writer: self.writer,
239 }
240 }
241
242 pub fn with_allocator<U: BrainfuckAllocator>(self) -> VMBuilder<T, U, R, W> {
244 VMBuilder {
245 initial_size: self.initial_size,
246 celltype: self.celltype,
247 allocator: PhantomData::<U>,
248 reader: self.reader,
249 writer: self.writer,
250 }
251 }
252
253 pub fn with_preallocated_cells(self, num_preallocated: usize) -> VMBuilder<T, A, R, W> {
255 VMBuilder {
256 initial_size: num_preallocated,
257 ..self
258 }
259 }
260
261 pub fn with_reader<U: Read>(self, reader: U) -> VMBuilder<T, A, U, W> {
264 VMBuilder {
265 initial_size: self.initial_size,
266 celltype: self.celltype,
267 allocator: self.allocator,
268 reader,
269 writer: self.writer,
270 }
271 }
272
273 pub fn with_writer<U: Write>(self, writer: U) -> VMBuilder<T, A, R, U> {
276 VMBuilder {
277 initial_size: self.initial_size,
278 celltype: self.celltype,
279 allocator: self.allocator,
280 reader: self.reader,
281 writer,
282 }
283 }
284
285 pub fn build(self) -> Box<dyn BrainfuckVM> {
288 log::info!("Building Brainfuck VM with configuration: {}", self);
289
290 Box::new(VirtualMachine::<T, A, R, W>::new(
291 self.initial_size,
292 self.reader,
293 self.writer,
294 ))
295 }
296}
297
298#[derive(Debug)]
300pub enum MissingKind {
301 JumpFwd,
302 JumpBack,
303}
304
305#[derive(Debug)]
307pub enum BrainfuckExecutionError {
308 UnknownError,
310
311 IOError(io::Error),
313
314 JumpMismatchError(MissingKind),
316
317 MemoryError(VMMemoryError),
319
320 DataPointerOverflow,
322
323 DataPointerUnderflow,
325}
326
327impl Display for BrainfuckExecutionError {
328 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329 match self {
330 BrainfuckExecutionError::UnknownError => write!(f, "Unknown error"),
331 BrainfuckExecutionError::IOError(e) => write!(f, "I/O Error: {}", e),
332 BrainfuckExecutionError::JumpMismatchError(MissingKind::JumpBack) => {
333 write!(f, "Too few closing brackets")
334 }
335 BrainfuckExecutionError::JumpMismatchError(MissingKind::JumpFwd) => {
336 write!(f, "Too few opening brackets")
337 }
338 BrainfuckExecutionError::MemoryError(VMMemoryError::OutOfBounds(a)) => write!(
339 f,
340 "Out of bounds memory access at index {} (max size {})",
341 a.access, a.capacity
342 ),
343 BrainfuckExecutionError::DataPointerOverflow => write!(f, "Data pointer overflow!"),
344 BrainfuckExecutionError::DataPointerUnderflow => write!(f, "Data pointer underflow!"),
345 }
346 }
347}
348
349impl std::error::Error for BrainfuckExecutionError {
350 fn cause(&self) -> Option<&dyn std::error::Error> {
351 match self {
352 BrainfuckExecutionError::IOError(e) => Some(e),
353 _ => None,
354 }
355 }
356}
357
358impl From<()> for BrainfuckExecutionError {
359 fn from(_: ()) -> Self {
360 BrainfuckExecutionError::UnknownError
361 }
362}
363
364impl From<io::Error> for BrainfuckExecutionError {
365 fn from(value: io::Error) -> Self {
366 BrainfuckExecutionError::IOError(value)
367 }
368}
369
370impl<T: BrainfuckCell, Alloc: BrainfuckAllocator, R: Read, W: Write>
371 VirtualMachine<T, Alloc, R, W>
372{
373 fn new(init_size: usize, reader: R, writer: W) -> Self {
374 VirtualMachine {
375 data_ptr: 0,
376 data: repeat(T::default()).take(init_size).collect(),
377 alloc: PhantomData,
378 reader,
379 writer,
380 }
381 }
382
383 fn exec(
384 &mut self,
385 instrs: &[Instruction],
386 instr_ptr: usize,
387 ) -> Result<usize, BrainfuckExecutionError> {
388 let instr = instrs[instr_ptr];
389
390 log::debug!("Executing instruction {}: {:?}", instr_ptr, instr);
391
392 match instr {
393 Instruction::IncrDP => {
394 log::trace!("Old data pointer: {}", self.data_ptr);
395
396 self.data_ptr = self
397 .data_ptr
398 .checked_add(1)
399 .ok_or(BrainfuckExecutionError::DataPointerOverflow)?;
400
401 log::trace!("New data pointer: {}", self.data_ptr);
402
403 Ok(instr_ptr + 1)
404 }
405 Instruction::DecrDP => {
406 log::trace!("Old data pointer: {}", self.data_ptr);
407
408 self.data_ptr = self
409 .data_ptr
410 .checked_sub(1)
411 .ok_or(BrainfuckExecutionError::DataPointerUnderflow)?;
412
413 log::trace!("New data pointer: {}", self.data_ptr);
414
415 Ok(instr_ptr + 1)
416 }
417 Instruction::Incr => {
418 log::trace!("Incrementing cell {}", self.data_ptr);
419
420 Alloc::ensure_capacity(&mut self.data, self.data_ptr + 1)?;
421
422 log::trace!("Previous value: {:?}", self.data[self.data_ptr]);
423 self.data[self.data_ptr] = self.data[self.data_ptr].wrapping_add(&T::one());
424 log::trace!("New value: {:?}", self.data[self.data_ptr]);
425
426 Ok(instr_ptr + 1)
427 }
428 Instruction::Decr => {
429 log::trace!("Decrementing cell {}", self.data_ptr);
430
431 Alloc::ensure_capacity(&mut self.data, self.data_ptr + 1)?;
432
433 log::trace!("Previous value: {:?}", self.data[self.data_ptr]);
434 self.data[self.data_ptr] = self.data[self.data_ptr].wrapping_sub(&T::one());
435 log::trace!("New value: {:?}", self.data[self.data_ptr]);
436
437 Ok(instr_ptr + 1)
438 }
439 Instruction::Output => {
440 log::trace!("Outputting value at cell {}", self.data_ptr);
441
442 let val = self.data.get(self.data_ptr).cloned().unwrap_or_default();
443 let as_char: char = val
444 .try_into()
445 .ok()
446 .and_then(char::from_u32)
447 .unwrap_or(char::REPLACEMENT_CHARACTER);
448
449 log::trace!("Found value: {:?}, as char: {}", val, as_char);
450
451 write!(self.writer, "{}", as_char)?;
452
453 Ok(instr_ptr + 1)
454 }
455 Instruction::Input => {
456 log::trace!("Reading input into cell {}", self.data_ptr);
457
458 let mut buf = [0_u8; 1];
459 let num_read = self.reader.read(&mut buf)?;
460
461 if num_read == 1 {
462 log::trace!("Read byte: {}", buf[0]);
463
464 Alloc::ensure_capacity(&mut self.data, self.data_ptr + 1)?;
465
466 let conv_buf: T = buf[0].into();
467
468 log::trace!("Converted to cell type: {:?}", conv_buf);
469
470 self.data[self.data_ptr] = conv_buf;
471 } else {
472 log::debug!("Attempted to read input, but no input was available");
473 }
474
475 Ok(instr_ptr + 1)
476 }
477 Instruction::JumpFwd => {
478 let val = self.data.get(self.data_ptr).cloned().unwrap_or_default();
479
480 if val != T::zero() {
481 log::trace!(
482 "Value at cell {} is not zero, not jumping forward",
483 self.data_ptr
484 );
485 return Ok(instr_ptr + 1);
486 }
487
488 log::trace!("Value at cell {} is zero, jumping forward", self.data_ptr);
489
490 let mut closing_tag = instr_ptr + 1;
491 let mut tag_stack: usize = 1;
492
493 while closing_tag < instrs.len() {
494 match instrs[closing_tag] {
495 Instruction::JumpFwd => {
496 log::trace!(
497 "Encountered additional JumpFwd, increasing tag stack {}=>{}",
498 tag_stack,
499 tag_stack + 1
500 );
501 tag_stack += 1
502 }
503 Instruction::JumpBack => {
504 log::trace!(
505 "Encountered JumpBack, decreasing tag stack {}=>{}",
506 tag_stack,
507 tag_stack - 1
508 );
509 tag_stack -= 1;
510 if tag_stack == 0 {
511 log::trace!("Found matching JumpBack at {}", closing_tag);
512 return Ok(closing_tag);
513 }
514 }
515 _ => {}
516 }
517
518 closing_tag += 1;
519 }
520
521 log::error!("No matching JumpBack found for JumpFwd at {}", instr_ptr);
522
523 Err(BrainfuckExecutionError::JumpMismatchError(
524 MissingKind::JumpBack,
525 ))
526 }
527 Instruction::JumpBack => {
528 let val = self.data.get(self.data_ptr).cloned().unwrap_or_default();
529
530 if val == T::zero() {
531 log::trace!("Value at cell {} is zero, not jumping back", self.data_ptr);
532 return Ok(instr_ptr + 1);
533 }
534
535 if instr_ptr == 0 {
536 log::error!("Instruction pointer is already 0, no matching opening bracket can be found");
537
538 return Err(BrainfuckExecutionError::JumpMismatchError(
539 MissingKind::JumpFwd,
540 ));
541 }
542
543 let mut opening_tag = instr_ptr - 1;
544 let mut tag_stack: usize = 1;
545
546 while opening_tag > 0 {
547 match instrs[opening_tag] {
548 Instruction::JumpFwd => {
549 log::trace!(
550 "Encountered JumpFwd, decreasing tag stack {}=>{}",
551 tag_stack,
552 tag_stack - 1
553 );
554 tag_stack -= 1;
555 if tag_stack == 0 {
556 log::trace!("Found matching JumpFwd at {}", opening_tag);
557 return Ok(opening_tag);
558 }
559 }
560 Instruction::JumpBack => {
561 log::trace!(
562 "Encountered additional JumpBack, increasing tag stack {}=>{}",
563 tag_stack,
564 tag_stack + 1
565 );
566 tag_stack += 1
567 }
568 _ => {}
569 }
570
571 opening_tag -= 1;
572 }
573
574 log::error!("No matching JumpFwd found for JumpBack at {}", instr_ptr);
575
576 Err(BrainfuckExecutionError::JumpMismatchError(
577 MissingKind::JumpFwd,
578 ))
579 }
580 }
581 }
582}
583
584pub type BfResult = Result<(), BrainfuckExecutionError>;
586
587pub trait BrainfuckVM {
593 fn run_program(&mut self, program: &Program) -> BfResult;
601
602 fn reset_memory(&mut self);
607
608 fn run_string(&mut self, bf_str: &str) -> BfResult {
611 log::info!("Running string of {} bytes", bf_str.len());
612
613 let program: Program = bf_str.into();
614
615 self.run_program(&program)
616 }
617
618 fn run_file(&mut self, file: &mut File) -> BfResult {
623 log::info!(
624 "Running file of size {}",
625 file.metadata()
626 .ok()
627 .map(|meta| meta.len().to_string())
628 .unwrap_or("{unknown size}".to_owned())
629 );
630
631 let mut program_str = String::new();
632 file.read_to_string(&mut program_str)?;
633
634 self.run_string(&program_str)
635 }
636
637 fn run_from_path(&mut self, path: &Path) -> BfResult {
642 log::info!("Running program at path {:?}", path);
643
644 let mut file = File::open(path)?;
645
646 self.run_file(&mut file)
647 }
648}
649
650impl<T: BrainfuckCell, A: BrainfuckAllocator, R: Read, W: Write> BrainfuckVM
651 for VirtualMachine<T, A, R, W>
652{
653 fn reset_memory(&mut self) {
654 log::info!("Resetting VM memory cells");
655
656 self.data.iter_mut().for_each(|cell| *cell = T::default());
657 }
658
659 fn run_program(&mut self, program: &Program) -> Result<(), BrainfuckExecutionError> {
660 log::info!("Running program");
661
662 if program.instructions.is_empty() {
663 log::info!("Program empty, returning");
664 return Ok(());
665 }
666
667 self.data_ptr = 0;
668 let mut instr_ptr = 0;
669
670 while instr_ptr < program.instructions.len() {
671 instr_ptr = self.exec(&program.instructions, instr_ptr)?;
672 }
673
674 log::debug!("Flushing writer");
675 self.writer.flush()?;
676
677 Ok(())
678 }
679}