brainfuck_rs 0.2.0

brainfuck parser using nom
Documentation
use ast::*;
use nom::{IResult, ErrorKind};

pub fn skip_unknown(i: &[u8]) -> IResult<&[u8], &[u8]> {
    if i.len() == 0 {
        IResult::Done::<&[u8], &[u8]>(i, &i[0..0])
    } else {
        match i.iter().enumerate().position(|(_, item)| {
            match *item as char {
                '>'|'<'|'+'|'-'|'.'|','|'['|']' => true,
                _                               => false,
            }
        }) {
            Some(index) => IResult::Done(&i[index..], &i[..index]),
            None        => IResult::Done(&i[i.len()..], i),
        }
    }
}

macro_rules! strip_unknown (
    ($i: expr, $($args:tt)*) => ({
        use $crate::parser::skip_unknown;
        sep!($i, skip_unknown, $($args)*)
    });
);

pub fn next_token(i: &[u8]) -> IResult<&[u8], Option<Token>> {
    map!(i, strip_unknown!(take!(1)), |ch: &[u8]| Token::from_char(ch[0] as char))
}

pub fn parse_loop(mut i: &[u8]) -> IResult<&[u8], Block> {
    let mut block = Block::new();
    loop {
        let tk = next_token(i);
        match tk {
            IResult::Error(e)      => return IResult::Error(e),
            IResult::Incomplete(i) => return IResult::Incomplete(i),
            IResult::Done(_i, _o)  => {
                i = _i;
                if let Some(token) = _o {
                    match token {
                        Token::LShift   => block.push(Node::LShift),
                        Token::RShift   => block.push(Node::RShift),
                        Token::Plus     => block.push(Node::Inc),
                        Token::Minus    => block.push(Node::Dec),
                        Token::Dot      => block.push(Node::PutCh),
                        Token::Comma    => block.push(Node::GetCh),
                        Token::LBracket => {
                            let lp = parse_loop(i);
                            match lp {
                                IResult::Error(e)      => return IResult::Error(e),
                                IResult::Incomplete(i) => return IResult::Incomplete(i),
                                IResult::Done(_i, _o)  => {
                                    i = _i;
                                    block.push(Node::Loop(_o));
                                }
                            }
                        },
                        Token::RBracket => return IResult::Done(i, block),
                    }
                } else {
                    return IResult::Error(error_code!(ErrorKind::Custom(42)))
                }
            },
        }
    }
}

pub fn parse_node(i: &[u8]) -> IResult<&[u8], Node> {
    let tk = next_token(i);
    match tk {
        IResult::Error(e)      => IResult::Error(e),
        IResult::Incomplete(i) => IResult::Incomplete(i),
        IResult::Done(i, o)    => {
            if let Some(token) = o {
                match token {
                    Token::LShift   => IResult::Done(i, Node::LShift),
                    Token::RShift   => IResult::Done(i, Node::RShift),
                    Token::Plus     => IResult::Done(i, Node::Inc),
                    Token::Minus    => IResult::Done(i, Node::Dec),
                    Token::Dot      => IResult::Done(i, Node::PutCh),
                    Token::Comma    => IResult::Done(i, Node::GetCh),
                    Token::LBracket => map!(i, parse_loop, |block| Node::Loop(block)),
                    Token::RBracket => IResult::Error(error_code!(ErrorKind::Custom(42))),
                }
            } else {
                IResult::Error(error_code!(ErrorKind::Custom(42)))
            }
        },
    }
}

pub fn parse(i: &[u8]) -> Result<Block, ErrorKind<u32>> {
    map!(i, many0!(parse_node), From::from).to_result()
}

#[cfg(test)]
mod tests {
    use nom::IResult;

    const EMPTY: &'static [u8] = b"";

    #[test]
    fn test_strip_unknown() {
        let all:      &[u8]                 = b"This is a test +- [Does it work?\n]><\n";
        let expected: Vec<&[u8]>            = vec![b"+", b"-", b"[", b"]", b">", b"<"];
        let got: IResult<&[u8], Vec<&[u8]>> = many1!(all, strip_unknown!(take!(1)));

        assert_eq!(got, IResult::Done(EMPTY, expected));
    }
}