1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! This crate includes an implementation of the Brainfuck esoteric language as a macro rule.
//!
//! A couple of limitations apply:
//! Anytime a series of characters encountered would be interpreted by the Rust compiler as a
//! symbol, they need to be separated by a space.
//!
//! i.e:
//! ">>" must be "> >"
//! "->" must be "- >"
//! "<<" must be "< <"
//! "<-" must be "< -"
//! ".." must be ". ."
//!
#[macro_export]
macro_rules! brainfuck {
    (@start $($sym:tt)*) => {{
        type Cell = u8;
        const MAX_SIZE: usize = 30_000;

        fn brainfuck() -> ::std::io::Result<Vec<Cell>> {
            use ::std::io;
            use ::std::io::prelude::*;

            fn _err() -> io::Error {
                io::Error::new(
                    io::ErrorKind::Other,
                    String::from("No more input available"),
                )
            }

            fn _inc(a: &mut [Cell], i: usize) {
                a[i] = a[i].wrapping_add(1);
            }

            fn _dec(a: &mut [Cell], i: usize) {
                a[i] = a[i].wrapping_sub(1);
            }

            let _r = &mut io::stdin();
            let _w = &mut io::stdout();

            let mut _tape: Vec<Cell> = ::std::iter::repeat(0).take(MAX_SIZE).collect();
            let mut _idx = 0;
            {
                let _tape = &mut _tape;
                brainfuck!(@fuckery (_tape, _idx, _r, _w, _inc, _dec, _err); ($($sym)*));
            }

            Ok(_tape)
        }
        let _ = brainfuck();
    }};

    (@fuckery $sym:tt; ()) => {};

    // Rules
    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); (> $($sym:tt)*)) => {
        $idx = ($idx + 1) % MAX_SIZE;
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($sym)*));
    };

    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); (< $($sym:tt)*)) => {
        $idx = ($idx + MAX_SIZE - 1) % MAX_SIZE;
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($sym)*));
    };

    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); (+ $($sym:tt)*)) => {
        $inc($tape, $idx);
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($sym)*));
    };

    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); (- $($sym:tt)*)) => {
        $dec($tape, $idx);
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($sym)*));
    };

    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); (. $($sym:tt)*)) => {
        $w.write_all(&$tape[$idx .. $idx + 1])?;
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($sym)*));
    };

    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); (, $($sym:tt)*)) => {
        match $r.read(&$tape[$idx .. $idx + 1])? {
            // Unable to read a byte. We're fucked. Return an error
            Ok(0) => Err($err()),

            // Otherwise, return whether or not we were able to read a single byte
            v => v
        }?;
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($sym)*));
    };

    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); ([ $($inner:tt)* ] $($tail:tt)*)) => {
        while $tape[$idx] != 0 {
            brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($inner)*));
        }
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($tail)*));
    };

    // Invalid characters are ignored
    (@fuckery ($tape:expr, $idx:expr, $r:expr, $w:expr, $inc:expr, $dec:expr, $err:expr); ($head:tt $($tail:tt)*)) => {
        brainfuck!(@fuckery ($tape, $idx, $r, $w, $inc, $dec, $err); ($($tail)*));
    };

    // Ignore any "initial comment loops"
    ([$($ignore:tt)*] $($sym:tt)*) => {
        brainfuck!($($sym)*)
    };

    ($($sym:tt)*) => {
        brainfuck!(@start $($sym)*)
    }

}