<commit_before>use tokenizer::Token;
use std::boxed::Box;

#[derive(Debug, PartialEq, Clone)]
pub enum Expression {
    Char(char),
    Numeral,
    Lowercase,
    Uppercase,
    Wildcard,

    // Any of the expressions will match
    Any(Vec<Expression>),

    // Any of the expressions will cause it to fail
    None(Vec<Expression>),

    // All of the expressions must match in order
    All(Vec<Expression>),

    // Modifiers:
    NoneOrMore(Box<Expression>),
    OneOrMore(Box<Expression>),
    NoneOrOne(Box<Expression>),
}

fn build_expression(tokens: Vec<Token>, within_brackets: bool,
                    allow_brackets: bool) -> Result<Expression, &'static str> {
    let mut itr = tokens.iter();

    // We use this as a stack.
    let mut exprs: Vec<Expression> = Vec::new();

    loop {

        // If there are no more tokens, return what we have
        let next_token: Token = match itr.next() {
            None => return Ok(Expression::All(exprs)),
            Some(t) => t.clone()
        };

        // Deal with the next token...
        match next_token {
            Token::Wildcard => exprs.push(Expression::Wildcard),
            Token::NoneOrMore => {
                // Pop the last expression off the stack -- if none
                // exists, ignore this character...
                if let Some(last_expr) = exprs.pop() {
                    let can_multiply = last_expr.is_multiplyable();
                    if can_multiply && !within_brackets {
                        exprs.push(Expression::NoneOrMore(
                            Box::new(last_expr)))
                    } else {
                        exprs.push(last_expr); // If not, put it back
                    }
                }
            }

            Token::OneOrMore => {
                // Pop the last expression off the stack -- if none
                // exists, ignore this character...
                if let Some(last_expr) = exprs.pop() {
                    let can_multiply = last_expr.is_multiplyable();
                    if can_multiply && !within_brackets {
                        exprs.push(Expression::OneOrMore(
                            Box::new(last_expr)))
                    } else {
                        exprs.push(last_expr); // If not, put it back
                    }
                }
            }

            Token::NoneOrOne => {
                // Pop the last expression off the stack -- if none
                // exists, ignore this character...
                if let Some(last_expr) = exprs.pop() {
                    let can_multiply = last_expr.is_multiplyable();
                    if can_multiply && !within_brackets {
                        exprs.push(Expression::NoneOrOne(
                            Box::new(last_expr)))
                    } else {
                        exprs.push(last_expr); // If not, put it back
                    }
                }
            }

            Token::NumeralRange => {
                if within_brackets {
                    exprs.push(Expression::Numeral);
                } else {
                    exprs.push(Expression::Char('0'));
                    exprs.push(Expression::Char('-'));
                    exprs.push(Expression::Char('9'));
                }
            },

            Token::LowercaseRange => {
                if within_brackets {
                    exprs.push(Expression::Lowercase);
                } else {
                    exprs.push(Expression::Char('a'));
                    exprs.push(Expression::Char('-'));
                    exprs.push(Expression::Char('z'));
                }
            },

            Token::UppercaseRange => {
                if within_brackets {
                    exprs.push(Expression::Uppercase);
                } else {
                    exprs.push(Expression::Char('A'));
                    exprs.push(Expression::Char('-'));
                    exprs.push(Expression::Char('Z'));
                }
            },

            Token::OpenBracket => {
                if !allow_brackets {
                    return Err("Nested brackets!");
                }

                // Search for a closing bracket -- if none is found,
                // then our regex is malformed and report it as such
                let bracketed: Vec<Token> = itr
                    .clone()
                    .take_while(|&t: &&Token| *t != Token::ClosedBracket)
                    .map(|t: &Token| t.clone())
                    .collect();

                // Advance the iterator past the tokens inside the brackets...
                for _ in 0..(bracketed.len()) { itr.next(); }

                if let Some(&Token::ClosedBracket) = itr.next() {
                    // If they begin with an inversion operator, then we want
                    // none-of the sub expressions, otherwise we want any of them...
                    if !bracketed.is_empty() && bracketed[0] == Token::BracketInversion {
                        let btns = bracketed[1..].to_vec();
                        match build_expression(btns, true, false) {
                            Err(s) => return Err(s),
                            Ok(Expression::All(e)) => exprs.push(Expression::None(e)),
                            Ok(e) => exprs.push(Expression::None(vec![e]))
                        }
                    } else {
                        match build_expression(bracketed, true, false) {
                            Err(s) => return Err(s),
                            Ok(Expression::All(e)) => exprs.push(Expression::Any(e)),
                            Ok(e) => exprs.push(Expression::Any(vec![e]))
                        }
                    }
                } else {
                    return Err("Bracket never closed!");
                }
            }

            Token::OpenParen => {
                // Search for a closing parenthesis -- if none is found,
                // then our regex is malformed
                let mut depth = 1;
                let grouped: Vec<Token> = itr
                    .clone()
                    .take_while(|&t: &&Token| {
                        if *t == Token::ClosedParen {
                            depth -= 1;
                        } else if *t == Token::OpenParen {
                            depth += 1;
                        }
                        depth > 0
                    })
                    .map(|t: &Token| t.clone())
                    .collect();

                // Skip the ones that we grouped together...
                for _ in 0..(grouped.len()) { itr.next(); }

                // Consume the closed parenthesis and keep going
                if let Some(&Token::ClosedParen) = itr.next() {
                    // We're just grouping everything as one expression...
                    let e = try!(build_expression(grouped, false, allow_brackets));
                    exprs.push(e)
                } else {
                    return Err("Parenthesis never closed!");
                }
            }

            // We should never encounter a closed parenthesis, bracket inversion, or closed bracket...
            Token::ClosedBracket => return Err("Unexpected closing bracket!"),
            Token::ClosedParen => return Err("Unexpected closing parenthesis!"),

            // This should really never happen
            // !FIXME! Maybe this should panic?
            Token::BracketInversion => return Err("Internal error: Unexpected bracket inversion!"),

            Token::Char(c) => exprs.push(Expression::Char(c)),
        }
    }
}

impl Expression {
    pub fn new(tokens: Vec<Token>) -> Result<Expression, &'static str> {
        build_expression(tokens, false, true)
    }

    // Returns true if the expression can be multiplied by the
    // operators: +, ?, and *
    // Effectively the answer is that they can't be used on each other,
    // so you should place parentheses around something if you want to
    // combine them.
    fn is_multiplyable(&self) -> bool {
        match *self {
            Expression::NoneOrMore(_) => false,
            Expression::OneOrMore(_) => false,
            Expression::NoneOrOne(_) => false,
            _ => true
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokenizer::unsafe_parse;

    #[test]
    fn it_can_make_an_empty_expr() {
        assert_eq!(Expression::new(unsafe_parse("")).unwrap(),
                   Expression::All(vec![]));
    }

    #[test]
    fn it_can_make_a_simple_expr() {
        assert_eq!(Expression::new(unsafe_parse("hello")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('h'),
                       Expression::Char('e'),
                       Expression::Char('l'),
                       Expression::Char('l'),
                       Expression::Char('o')]));
    }

    #[test]
    fn it_can_make_a_wildcard_expr() {
        assert_eq!(Expression::new(unsafe_parse("h.llo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('h'),
                       Expression::Wildcard,
                       Expression::Char('l'),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse(".*")).unwrap(),
                   Expression::All(vec![
                       Expression::NoneOrMore(
                           Box::new(Expression::Wildcard))]));
    }

    #[test]
    fn it_can_make_a_multiplying_expr() {
        assert_eq!(Expression::new(unsafe_parse("+ello")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('e'),
                       Expression::Char('l'),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("h?l*o")).unwrap(),
                   Expression::All(vec![
                       Expression::NoneOrOne(
                           Box::new(Expression::Char('h'))),
                       Expression::NoneOrMore(
                           Box::new(Expression::Char('l'))),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("h+l++o")).unwrap(),
                   Expression::All(vec![
                       Expression::OneOrMore(
                           Box::new(Expression::Char('h'))),
                       Expression::OneOrMore(
                           Box::new(Expression::Char('l'))),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("h+l*+o")).unwrap(),
                   Expression::All(vec![
                       Expression::OneOrMore(
                           Box::new(Expression::Char('h'))),
                       Expression::NoneOrMore(
                           Box::new(Expression::Char('l'))),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("+*?+*+*???*+")).unwrap(),
                   Expression::All(vec![]));

        assert_eq!(Expression::new(unsafe_parse("+*?+*+a*???*+")).unwrap(),
                   Expression::All(vec![
                       Expression::NoneOrMore(
                           Box::new(Expression::Char('a')))]));
    }

    #[test]
    fn it_groups_parenthesized_expressions() {
        assert_eq!(Expression::new(unsafe_parse("()")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![])]));

        assert_eq!(Expression::new(unsafe_parse("((((()))))")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![
                           Expression::All(vec![
                               Expression::All(vec![
                                   Expression::All(vec![
                                       Expression::All(vec![])])])])])]));

        assert_eq!(Expression::new(unsafe_parse("()()")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![]),
                       Expression::All(vec![])]));

        assert!(Expression::new(unsafe_parse(")(")).is_err());
        assert!(Expression::new(unsafe_parse("(")).is_err());
        assert!(Expression::new(unsafe_parse(")")).is_err());
        assert!(Expression::new(unsafe_parse("(()")).is_err());
        assert!(Expression::new(unsafe_parse("()(()")).is_err());
        assert!(Expression::new(unsafe_parse(")()")).is_err());

        assert_eq!(Expression::new(unsafe_parse("(\\)\\()")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![
                           Expression::Char(')'),
                           Expression::Char('(')])]));

        assert_eq!(Expression::new(unsafe_parse("a(\\)\\()b")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('a'),
                       Expression::All(vec![
                           Expression::Char(')'),
                           Expression::Char('(')]),
                       Expression::Char('b')]));
    }

    #[test]
    fn it_prevents_non_bracket_range_exprs() {
        assert_eq!(Expression::new(unsafe_parse("ea-zlo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('e'),
                       Expression::Char('a'),
                       Expression::Char('-'),
                       Expression::Char('z'),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("a-Zo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('a'),
                       Expression::Char('-'),
                       Expression::Char('Z'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("0-9(A-Z)lo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('0'),
                       Expression::Char('-'),
                       Expression::Char('9'),
                       Expression::All(vec![
                           Expression::Char('A'),
                           Expression::Char('-'),
                           Expression::Char('Z')]),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("0-9")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('0'),
                       Expression::Char('-'),
                       Expression::Char('9')]));
    }

    #[test]
    fn it_allows_bracket_exprs() {
        assert_eq!(Expression::new(unsafe_parse("[0-9]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![Expression::Numeral])]));

        assert_eq!(Expression::new(unsafe_parse("[0-9a-z]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![
                           Expression::Numeral,
                           Expression::Lowercase])]));

        assert_eq!(Expression::new(unsafe_parse("[^abc]")).unwrap(),
                   Expression::All(vec![
                       Expression::None(vec![
                           Expression::Char('a'),
                           Expression::Char('b'),
                           Expression::Char('c')])]));

        // Don't worry about duplicates in brackets, they are
        // redundant and serve no functional difference.
        assert_eq!(Expression::new(unsafe_parse("[lol]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![
                           Expression::Char('l'),
                           Expression::Char('o'),
                           Expression::Char('l')])]));

        // Multipliers in brackets make no sense, but they aren't
        // ambiguous so we can just ignore them.
        assert_eq!(Expression::new(unsafe_parse("[l+ol*]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![
                           Expression::Char('l'),
                           Expression::Char('o'),
                           Expression::Char('l')])]));

        assert_eq!(Expression::new(unsafe_parse("(.[(0-9)])")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![
                           Expression::Wildcard,
                           Expression::Any(vec![
                               Expression::All(vec![
                                   Expression::Char('0'),
                                   Expression::Char('-'),
                                   Expression::Char('9')])])])]));
    }

    #[test]
    fn it_properly_checks_for_nested_brackets() {
        // Multipliers in brackets make no sense, but they aren't
        // ambiguous so we can just ignore them.
        assert!(Expression::new(unsafe_parse("[[lol]]")).is_err());
        assert!(Expression::new(unsafe_parse("[lo[l]]")).is_err());
        assert!(Expression::new(unsafe_parse("([)]")).is_err());
        assert!(Expression::new(unsafe_parse("(.[0-9)]")).is_err());
    }

    #[test]
    fn it_allows_mixed_parentheses_and_brackets() {
        assert_eq!(Expression::new(unsafe_parse("()[]")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![]),
                       Expression::Any(vec![])]));

        assert_eq!(Expression::new(unsafe_parse("[]()")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![]),
                       Expression::All(vec![])]));

        assert_eq!(Expression::new(unsafe_parse("l[(o)]l")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::Any(vec![
                           Expression::All(vec![
                               Expression::Char('o')])]),
                       Expression::Char('l')]));

        assert_eq!(Expression::new(unsafe_parse("l[0-9(o)]l")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::Any(vec![
                           Expression::Numeral,
                           Expression::All(vec![
                               Expression::Char('o')])]),
                       Expression::Char('l')]));

        assert_eq!(Expression::new(unsafe_parse("l[^0-9(o)]l")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::None(vec![
                           Expression::Numeral,
                           Expression::All(vec![
                               Expression::Char('o')])]),
                       Expression::Char('l')]));

        assert_eq!(Expression::new(unsafe_parse("l[0-9^(o)](.)")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::Any(vec![
                           Expression::Numeral,
                           Expression::Char('^'),
                           Expression::All(vec![
                               Expression::Char('o')])]),
                       Expression::All(vec![
                           Expression::Wildcard])]));
    }
}
<commit_msg>On second thought, give special characters equal treatment in brackets<commit_after>use tokenizer::Token;
use std::boxed::Box;

#[derive(Debug, PartialEq, Clone)]
pub enum Expression {
    Char(char),
    Numeral,
    Lowercase,
    Uppercase,
    Wildcard,

    // Any of the expressions will match
    Any(Vec<Expression>),

    // Any of the expressions will cause it to fail
    None(Vec<Expression>),

    // All of the expressions must match in order
    All(Vec<Expression>),

    // Modifiers:
    NoneOrMore(Box<Expression>),
    OneOrMore(Box<Expression>),
    NoneOrOne(Box<Expression>),
}

fn build_expression(tokens: Vec<Token>, within_brackets: bool,
                    allow_brackets: bool) -> Result<Expression, &'static str> {
    let mut itr = tokens.iter();

    // We use this as a stack.
    let mut exprs: Vec<Expression> = Vec::new();

    loop {

        // If there are no more tokens, return what we have
        let next_token: Token = match itr.next() {
            None => return Ok(Expression::All(exprs)),
            Some(t) => t.clone()
        };

        // Deal with the next token...
        match next_token {
            Token::Wildcard => if within_brackets {
                exprs.push(Expression::Char('.'));
            } else {
                exprs.push(Expression::Wildcard);
            },
            Token::NoneOrMore => {
                // Pop the last expression off the stack -- if none
                // exists, ignore this character...
                if within_brackets {
                    exprs.push(Expression::Char('*'));
                } else {
                    if let Some(last_expr) = exprs.pop() {
                        if last_expr.is_multiplyable() {
                            exprs.push(Expression::NoneOrMore(
                                Box::new(last_expr)))
                        } else {
                            exprs.push(last_expr); // If not, put it back
                        }
                    }
                }
            }

            Token::OneOrMore => {
                if within_brackets {
                    exprs.push(Expression::Char('+'));
                } else {
                    // Pop the last expression off the stack -- if none
                    // exists, ignore this character...
                    if let Some(last_expr) = exprs.pop() {
                        if last_expr.is_multiplyable() {
                            exprs.push(Expression::OneOrMore(
                                Box::new(last_expr)))
                        } else {
                            exprs.push(last_expr); // If not, put it back
                        }
                    }
                }
            }

            Token::NoneOrOne => {
                if within_brackets {
                    exprs.push(Expression::Char('?'));
                } else {
                    // Pop the last expression off the stack -- if none
                    // exists, ignore this character...
                    if let Some(last_expr) = exprs.pop() {
                        if last_expr.is_multiplyable() {
                            exprs.push(Expression::NoneOrOne(
                                Box::new(last_expr)))
                        } else {
                            exprs.push(last_expr); // If not, put it back
                        }
                    }
                }
            }

            Token::NumeralRange => {
                if within_brackets {
                    exprs.push(Expression::Numeral);
                } else {
                    exprs.push(Expression::Char('0'));
                    exprs.push(Expression::Char('-'));
                    exprs.push(Expression::Char('9'));
                }
            },

            Token::LowercaseRange => {
                if within_brackets {
                    exprs.push(Expression::Lowercase);
                } else {
                    exprs.push(Expression::Char('a'));
                    exprs.push(Expression::Char('-'));
                    exprs.push(Expression::Char('z'));
                }
            },

            Token::UppercaseRange => {
                if within_brackets {
                    exprs.push(Expression::Uppercase);
                } else {
                    exprs.push(Expression::Char('A'));
                    exprs.push(Expression::Char('-'));
                    exprs.push(Expression::Char('Z'));
                }
            },

            Token::OpenBracket => {
                if !allow_brackets {
                    return Err("Nested brackets!");
                }

                // Search for a closing bracket -- if none is found,
                // then our regex is malformed and report it as such
                let bracketed: Vec<Token> = itr
                    .clone()
                    .take_while(|&t: &&Token| *t != Token::ClosedBracket)
                    .map(|t: &Token| t.clone())
                    .collect();

                // Advance the iterator past the tokens inside the brackets...
                for _ in 0..(bracketed.len()) { itr.next(); }

                if let Some(&Token::ClosedBracket) = itr.next() {
                    // If they begin with an inversion operator, then we want
                    // none-of the sub expressions, otherwise we want any of them...
                    if !bracketed.is_empty() && bracketed[0] == Token::BracketInversion {
                        let btns = bracketed[1..].to_vec();
                        match build_expression(btns, true, false) {
                            Err(s) => return Err(s),
                            Ok(Expression::All(e)) => exprs.push(Expression::None(e)),
                            Ok(e) => exprs.push(Expression::None(vec![e]))
                        }
                    } else {
                        match build_expression(bracketed, true, false) {
                            Err(s) => return Err(s),
                            Ok(Expression::All(e)) => exprs.push(Expression::Any(e)),
                            Ok(e) => exprs.push(Expression::Any(vec![e]))
                        }
                    }
                } else {
                    return Err("Bracket never closed!");
                }
            }

            Token::OpenParen => {
                if within_brackets {
                    exprs.push(Expression::Char('('));
                } else {
                    // Search for a closing parenthesis -- if none is found,
                    // then our regex is malformed
                    let mut depth = 1;
                    let grouped: Vec<Token> = itr
                        .clone()
                        .take_while(|&t: &&Token| {
                            if *t == Token::ClosedParen {
                                depth -= 1;
                            } else if *t == Token::OpenParen {
                                depth += 1;
                            }
                            depth > 0
                        })
                        .map(|t: &Token| t.clone())
                        .collect();

                    // Skip the ones that we grouped together...
                    for _ in 0..(grouped.len()) { itr.next(); }

                    // Consume the closed parenthesis and keep going
                    if let Some(&Token::ClosedParen) = itr.next() {
                        // We're just grouping everything as one expression...
                        let e = try!(build_expression(grouped, false, allow_brackets));
                        exprs.push(e)
                    } else {
                        return Err("Parenthesis never closed!");
                    }
                }
            }

            // We should never encounter a closed parenthesis, bracket inversion, or closed bracket...
            Token::ClosedBracket => return Err("Unexpected closing bracket!"),
            Token::ClosedParen => if within_brackets {
                exprs.push(Expression::Char(')'));
            } else {
                return Err("Unexpected closing parenthesis!")
            },

            // This should really never happen
            // !FIXME! Maybe this should panic?
            Token::BracketInversion => return Err("Internal error: Unexpected bracket inversion!"),

            Token::Char(c) => exprs.push(Expression::Char(c)),
        }
    }
}

impl Expression {
    pub fn new(tokens: Vec<Token>) -> Result<Expression, &'static str> {
        build_expression(tokens, false, true)
    }

    // Returns true if the expression can be multiplied by the
    // operators: +, ?, and *
    // Effectively the answer is that they can't be used on each other,
    // so you should place parentheses around something if you want to
    // combine them.
    fn is_multiplyable(&self) -> bool {
        match *self {
            Expression::NoneOrMore(_) => false,
            Expression::OneOrMore(_) => false,
            Expression::NoneOrOne(_) => false,
            _ => true
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokenizer::Token;
    use tokenizer::parse_string;

    fn unsafe_parse(s: &str) -> Vec<Token> {
        match parse_string(s.to_string()) {
            Ok(t) => t,
            Err(e) => panic!(e)
        }
    }

    #[test]
    fn it_can_make_an_empty_expr() {
        assert_eq!(Expression::new(unsafe_parse("")).unwrap(),
                   Expression::All(vec![]));
    }

    #[test]
    fn it_can_make_a_simple_expr() {
        assert_eq!(Expression::new(unsafe_parse("hello")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('h'),
                       Expression::Char('e'),
                       Expression::Char('l'),
                       Expression::Char('l'),
                       Expression::Char('o')]));
    }

    #[test]
    fn it_can_make_a_wildcard_expr() {
        assert_eq!(Expression::new(unsafe_parse("h.llo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('h'),
                       Expression::Wildcard,
                       Expression::Char('l'),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse(".*")).unwrap(),
                   Expression::All(vec![
                       Expression::NoneOrMore(
                           Box::new(Expression::Wildcard))]));
    }

    #[test]
    fn it_can_make_a_multiplying_expr() {
        assert_eq!(Expression::new(unsafe_parse("+ello")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('e'),
                       Expression::Char('l'),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("h?l*o")).unwrap(),
                   Expression::All(vec![
                       Expression::NoneOrOne(
                           Box::new(Expression::Char('h'))),
                       Expression::NoneOrMore(
                           Box::new(Expression::Char('l'))),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("h+l++o")).unwrap(),
                   Expression::All(vec![
                       Expression::OneOrMore(
                           Box::new(Expression::Char('h'))),
                       Expression::OneOrMore(
                           Box::new(Expression::Char('l'))),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("h+l*+o")).unwrap(),
                   Expression::All(vec![
                       Expression::OneOrMore(
                           Box::new(Expression::Char('h'))),
                       Expression::NoneOrMore(
                           Box::new(Expression::Char('l'))),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("+*?+*+*???*+")).unwrap(),
                   Expression::All(vec![]));

        assert_eq!(Expression::new(unsafe_parse("+*?+*+a*???*+")).unwrap(),
                   Expression::All(vec![
                       Expression::NoneOrMore(
                           Box::new(Expression::Char('a')))]));
    }

    #[test]
    fn it_groups_parenthesized_expressions() {
        assert_eq!(Expression::new(unsafe_parse("()")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![])]));

        assert_eq!(Expression::new(unsafe_parse("((((()))))")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![
                           Expression::All(vec![
                               Expression::All(vec![
                                   Expression::All(vec![
                                       Expression::All(vec![])])])])])]));

        assert_eq!(Expression::new(unsafe_parse("()()")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![]),
                       Expression::All(vec![])]));

        assert!(Expression::new(unsafe_parse(")(")).is_err());
        assert!(Expression::new(unsafe_parse("(")).is_err());
        assert!(Expression::new(unsafe_parse(")")).is_err());
        assert!(Expression::new(unsafe_parse("(()")).is_err());
        assert!(Expression::new(unsafe_parse("()(()")).is_err());
        assert!(Expression::new(unsafe_parse(")()")).is_err());

        assert_eq!(Expression::new(unsafe_parse("(\\)\\()")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![
                           Expression::Char(')'),
                           Expression::Char('(')])]));

        assert_eq!(Expression::new(unsafe_parse("a(\\)\\()b")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('a'),
                       Expression::All(vec![
                           Expression::Char(')'),
                           Expression::Char('(')]),
                       Expression::Char('b')]));
    }

    #[test]
    fn it_prevents_non_bracket_range_exprs() {
        assert_eq!(Expression::new(unsafe_parse("ea-zlo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('e'),
                       Expression::Char('a'),
                       Expression::Char('-'),
                       Expression::Char('z'),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("a-Zo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('a'),
                       Expression::Char('-'),
                       Expression::Char('Z'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("0-9(A-Z)lo")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('0'),
                       Expression::Char('-'),
                       Expression::Char('9'),
                       Expression::All(vec![
                           Expression::Char('A'),
                           Expression::Char('-'),
                           Expression::Char('Z')]),
                       Expression::Char('l'),
                       Expression::Char('o')]));

        assert_eq!(Expression::new(unsafe_parse("0-9")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('0'),
                       Expression::Char('-'),
                       Expression::Char('9')]));
    }

    #[test]
    fn it_allows_bracket_exprs() {
        assert_eq!(Expression::new(unsafe_parse("[0-9]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![Expression::Numeral])]));

        assert_eq!(Expression::new(unsafe_parse("[0-9a-z]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![
                           Expression::Numeral,
                           Expression::Lowercase])]));

        assert_eq!(Expression::new(unsafe_parse("[^abc]")).unwrap(),
                   Expression::All(vec![
                       Expression::None(vec![
                           Expression::Char('a'),
                           Expression::Char('b'),
                           Expression::Char('c')])]));

        // Don't worry about duplicates in brackets, they are
        // redundant and serve no functional difference.
        assert_eq!(Expression::new(unsafe_parse("[lol]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![
                           Expression::Char('l'),
                           Expression::Char('o'),
                           Expression::Char('l')])]));

        // Multipliers in brackets make no sense, but they aren't
        // ambiguous so we can just ignore them.
        assert_eq!(Expression::new(unsafe_parse("[l+ol*]")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![
                           Expression::Char('l'),
                           Expression::Char('+'),
                           Expression::Char('o'),
                           Expression::Char('l'),
                           Expression::Char('*')])]));

        assert_eq!(Expression::new(unsafe_parse("(.[(0-9)])")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![
                           Expression::Wildcard,
                           Expression::Any(vec![
                               Expression::Char('('),
                               Expression::Numeral,
                               Expression::Char(')')])])]));
    }

    #[test]
    fn it_properly_checks_for_nested_brackets() {
        // Multipliers in brackets make no sense, but they aren't
        // ambiguous so we can just ignore them.
        assert!(Expression::new(unsafe_parse("[[lol]]")).is_err());
        assert!(Expression::new(unsafe_parse("[lo[l]]")).is_err());
        assert!(Expression::new(unsafe_parse("([)]")).is_err());
        assert!(Expression::new(unsafe_parse("(.[0-9)]")).is_err());
    }

    #[test]
    fn it_allows_mixed_parentheses_and_brackets() {
        assert_eq!(Expression::new(unsafe_parse("()[]")).unwrap(),
                   Expression::All(vec![
                       Expression::All(vec![]),
                       Expression::Any(vec![])]));

        assert_eq!(Expression::new(unsafe_parse("[]()")).unwrap(),
                   Expression::All(vec![
                       Expression::Any(vec![]),
                       Expression::All(vec![])]));

        assert_eq!(Expression::new(unsafe_parse("l[(o)]l")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::Any(vec![
                           Expression::Char('('),
                           Expression::Char('o'),
                           Expression::Char(')')]),
                       Expression::Char('l')]));

        assert_eq!(Expression::new(unsafe_parse("l[0-9(o)]l")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::Any(vec![
                           Expression::Numeral,
                           Expression::Char('('),
                           Expression::Char('o'),
                           Expression::Char(')')]),
                       Expression::Char('l')]));

        assert_eq!(Expression::new(unsafe_parse("l[^0-9(o)]l")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::None(vec![
                           Expression::Numeral,
                           Expression::Char('('),
                           Expression::Char('o'),
                           Expression::Char(')')]),
                       Expression::Char('l')]));

        assert_eq!(Expression::new(unsafe_parse("l[0-9^(o)](.)")).unwrap(),
                   Expression::All(vec![
                       Expression::Char('l'),
                       Expression::Any(vec![
                           Expression::Numeral,
                           Expression::Char('^'),
                           Expression::Char('('),
                           Expression::Char('o'),
                           Expression::Char(')')]),
                       Expression::All(vec![
                           Expression::Wildcard])]));
    }
}
<|endoftext|>


<commit_before>use std::fs;
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::cmp;
use std::fmt;

pub struct Cursor {
    pub x: i32,
    pub y: i32,
    y_offset: i32,
}

impl fmt::Display for Cursor {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "({}, {}, o={})", self.x, self.y, self.y_offset)
    }
}

impl Cursor {
    fn curr_len(&self, lines: &Vec<String>) -> i32 {
        lines[self.y as usize - 1].len() as i32
    }

    fn move_left(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if self.x > 1 {
            self.x -= 1;
        } else if self.y > 1 {
            self.move_up(dim, lines);
            self.x = self.curr_len(lines) + 1;
        }
    }

    fn move_right(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if self.x <= self.curr_len(lines) {
            self.x += 1;
        } else if self.y < lines.len() as i32 {
            self.move_home(dim, lines);
            self.move_down(dim, lines);
        }
    }

    fn move_up(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if self.x > dim.0 {
            self.x -= dim.0;
        } else if self.y > 1 {
            self.y -= 1;
            let line_len = self.curr_len(lines);
            let extra_lines = line_len / dim.0;
            self.y_offset -= extra_lines;
            self.x += extra_lines * dim.0;
            if self.x > line_len + 1 {
                self.x = line_len + 1;
            }
        }
    }

    fn move_down(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if (self.x - 1) / dim.0 < self.curr_len(lines) / dim.0 {
            self.x += dim.0;
        } else if self.y < lines.len() as i32 {
            self.y += 1;
            while self.x > dim.0 {
                self.x -= dim.0;
                self.y_offset += 1;
            }
        }
        if self.x > self.curr_len(lines) + 1 {
            self.x = self.curr_len(lines) + 1;
        }
    }

    fn move_home(&mut self, dim: (i32, i32), _: &Vec<String>) {
        // Depend on truncation here to clamp to lower multiple of screen width
        self.x = ((self.x - 1) / dim.0) * dim.0 + 1;
    }

    fn move_end(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        self.x = ((self.x - 1) / dim.0 + 1) * dim.0;
        if self.x > self.curr_len(lines) + 1 {
            self.x = self.curr_len(lines) + 1;
        }
    }

    fn project(&self, dim: (i32, i32)) -> Cursor {
        Cursor {
            x: (self.x - 1) % dim.0 + 1,
            y: self.y + (self.x - 1) / dim.0 + self.y_offset,
            y_offset: 0,
        }
    }

    fn recompute_offset(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        self.y_offset = 0;
        for i in 0..(self.y - 1) {
            self.y_offset += lines[i as usize].len() as i32 / dim.0;
        }
    }
}

pub struct File {
    pub name: String,
    pub lines: Vec<String>,
    caret: Cursor,
    window_top: Cursor,
    last_dim: (i32, i32),
    misc: String,
}

impl File {
    pub fn debug(&self, dim: (i32, i32)) -> String {
        format!("Caret {}, Top {}, Cursor {} {}",
            self.caret, self.window_top, self.cursor(dim), self.misc
        )
    }

    pub fn empty() -> File {
        File {
            name: String::from(""),
            lines: vec![String::from("")],
            caret: Cursor { x: 1, y: 1, y_offset: 0 },
            window_top: Cursor { x: 1, y: 1, y_offset: 0 },
            last_dim: (0, 0),
            misc: String::from(""),
        }
    }

    pub fn open(path: &str) -> File {
        let f = fs::File::open(path).expect("Could not open file");
        let f = io::BufReader::new(f);

        File {
            name: String::from(Path::new(path).file_name().unwrap().to_string_lossy()),
            lines: f.lines().map(|r| r.unwrap()).collect(),
            caret: Cursor { x: 1, y: 1, y_offset: 0 },
            window_top: Cursor { x: 1, y: 1, y_offset: 0 },
            last_dim: (0, 0),
            misc: String::from(""),
        }
    }

    pub fn save(&self, path: &str) {
        let f = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(path)
            .expect("Could not open file");
        let mut f = io::BufWriter::new(f);

        for line in self.lines.iter() {
            write!(f, "{}\n", line).unwrap();
        }
    }

    pub fn cursor(&self, dim: (i32, i32)) -> Cursor {
        let projected_caret = self.caret.project(dim);
        let projected_top = self.window_top.project(dim);
        Cursor {
            x: projected_caret.x,
            y: projected_caret.y - projected_top.y + 1,
            y_offset: 0,
        }
    }

    pub fn lineno_chars(&self) -> i32 {
        format!("{}", self.lines.len()).len() as i32
    }

    pub fn wrapped_lines(&self, dim: (i32, i32)) -> Vec<(Option<u16>, String)> {
        let mut result = vec![];
        let width = dim.0 as usize;
        let top_y = self.window_top.y as usize - 1;
        let top_extra = (self.window_top.x - 1) / dim.0;
        for (line_number, raw_line) in self.lines.iter().enumerate().skip(top_y) {
            let mut line_start = 0;
            let mut line_end = cmp::min(raw_line.len(), width);
            result.push((Some(line_number as u16),
                         String::from(&raw_line[line_start..line_end])));
            while line_end < raw_line.len() {
                line_start = line_end;
                line_end += cmp::min(raw_line.len() - line_end, width);
                result.push((None,
                             String::from(&raw_line[line_start..line_end])));
            }
            if result.len() as i32 >= dim.1 + top_extra {
                break;
            }
        }
        result.truncate((dim.1 + top_extra) as usize);
        result.drain(..top_extra as usize);
        result
    }

    fn current_line(&self) -> &String {
        &self.lines[self.caret.y as usize - 1]
    }

    fn recompute_offsets(&mut self, dim: (i32, i32)) {
        if dim != self.last_dim {
            self.caret.recompute_offset(dim, &self.lines);
            self.window_top.recompute_offset(dim, &self.lines);
            self.last_dim = dim;
        }
    }

    pub fn refresh(&mut self, dim: (i32, i32)) {
        self.recompute_offsets(dim);
    }

    pub fn move_cursor_left(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_left(dim, &self.lines);
        if self.cursor(dim).y < 1 {
            self.window_top.move_up(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_right(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_right(dim, &self.lines);
        if self.cursor(dim).y > dim.1 {
            self.window_top.move_down(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_up(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_up(dim, &self.lines);
        if self.cursor(dim).y < 1 {
            self.window_top.move_up(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_down(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_down(dim, &self.lines);
        if self.cursor(dim).y > dim.1 {
            self.window_top.move_down(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_home(&mut self, dim: (i32, i32)) {
        self.recompute_offsets(dim);
        self.caret.move_home(dim, &self.lines);
    }

    pub fn move_cursor_end(&mut self, dim: (i32, i32)) {
        self.recompute_offsets(dim);
        self.caret.move_end(dim, &self.lines);
    }

    pub fn page_up(&mut self, dim: (i32, i32)) {
        for _ in 0..dim.1 {
            self.move_cursor_up(dim);
        }
    }

    pub fn page_down(&mut self, dim: (i32, i32)) {
        for _ in 0..dim.1 {
            self.move_cursor_down(dim);
        }
    }

    pub fn insert(&mut self, dim: (i32, i32), c: char) {
        {
            let x = self.caret.x - 1;
            let line = &mut self.lines[self.caret.y as usize - 1];
            let pos = if x > line.len() as i32 {
                self.caret.x = line.len() as i32 - 1;
                line.len()
            } else {
                x as usize
            };
            line.insert(pos, c);
        }
        self.move_cursor_right(dim);
    }

    pub fn delete(&mut self, _: (i32, i32)) {
        let x = self.caret.x as usize - 1;
        if x == self.current_line().len() {
            let y = self.caret.y as usize - 1;
            if y < self.lines.len() - 1 {
                let next_line = self.lines.remove(y + 1);
                self.lines[y].push_str(&next_line);
            }
        } else {
            let line = &mut self.lines[self.caret.y as usize - 1];
            line.remove(x);
        }
    }

    pub fn backspace(&mut self, dim: (i32, i32)) {
        self.move_cursor_left(dim);
        self.delete(dim);
    }

    pub fn insert_newline(&mut self, dim: (i32, i32)) {
        let after = {
            let before = &mut self.lines[self.caret.y as usize - 1];
            before.split_off(self.caret.x as usize - 1)
        };
        self.lines.insert(self.caret.y as usize, after);
        self.move_cursor_right(dim);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn load_save_preserves_everything() {
        let f = File::open("README.md");
        f.save("readme.bak");
        let orig = fs::File::open("README.md").unwrap();
        let new = fs::File::open("readme.bak").unwrap();
        for (b1, b2) in orig.bytes().zip(new.bytes()) {
            assert_eq!(b1.unwrap(), b2.unwrap());
        }
        fs::remove_file("readme.bak").unwrap();
    }
}
<commit_msg>Fix file saving weirdness<commit_after>use std::fs;
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::cmp;
use std::fmt;

pub struct Cursor {
    pub x: i32,
    pub y: i32,
    y_offset: i32,
}

impl fmt::Display for Cursor {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "({}, {}, o={})", self.x, self.y, self.y_offset)
    }
}

impl Cursor {
    fn curr_len(&self, lines: &Vec<String>) -> i32 {
        lines[self.y as usize - 1].len() as i32
    }

    fn move_left(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if self.x > 1 {
            self.x -= 1;
        } else if self.y > 1 {
            self.move_up(dim, lines);
            self.x = self.curr_len(lines) + 1;
        }
    }

    fn move_right(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if self.x <= self.curr_len(lines) {
            self.x += 1;
        } else if self.y < lines.len() as i32 {
            self.move_home(dim, lines);
            self.move_down(dim, lines);
        }
    }

    fn move_up(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if self.x > dim.0 {
            self.x -= dim.0;
        } else if self.y > 1 {
            self.y -= 1;
            let line_len = self.curr_len(lines);
            let extra_lines = line_len / dim.0;
            self.y_offset -= extra_lines;
            self.x += extra_lines * dim.0;
            if self.x > line_len + 1 {
                self.x = line_len + 1;
            }
        }
    }

    fn move_down(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        if (self.x - 1) / dim.0 < self.curr_len(lines) / dim.0 {
            self.x += dim.0;
        } else if self.y < lines.len() as i32 {
            self.y += 1;
            while self.x > dim.0 {
                self.x -= dim.0;
                self.y_offset += 1;
            }
        }
        if self.x > self.curr_len(lines) + 1 {
            self.x = self.curr_len(lines) + 1;
        }
    }

    fn move_home(&mut self, dim: (i32, i32), _: &Vec<String>) {
        // Depend on truncation here to clamp to lower multiple of screen width
        self.x = ((self.x - 1) / dim.0) * dim.0 + 1;
    }

    fn move_end(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        self.x = ((self.x - 1) / dim.0 + 1) * dim.0;
        if self.x > self.curr_len(lines) + 1 {
            self.x = self.curr_len(lines) + 1;
        }
    }

    fn project(&self, dim: (i32, i32)) -> Cursor {
        Cursor {
            x: (self.x - 1) % dim.0 + 1,
            y: self.y + (self.x - 1) / dim.0 + self.y_offset,
            y_offset: 0,
        }
    }

    fn recompute_offset(&mut self, dim: (i32, i32), lines: &Vec<String>) {
        self.y_offset = 0;
        for i in 0..(self.y - 1) {
            self.y_offset += lines[i as usize].len() as i32 / dim.0;
        }
    }
}

pub struct File {
    pub name: String,
    pub lines: Vec<String>,
    caret: Cursor,
    window_top: Cursor,
    last_dim: (i32, i32),
    misc: String,
}

impl File {
    pub fn debug(&self, dim: (i32, i32)) -> String {
        format!("Caret {}, Top {}, Cursor {} {}",
            self.caret, self.window_top, self.cursor(dim), self.misc
        )
    }

    pub fn empty() -> File {
        File {
            name: String::from(""),
            lines: vec![String::from("")],
            caret: Cursor { x: 1, y: 1, y_offset: 0 },
            window_top: Cursor { x: 1, y: 1, y_offset: 0 },
            last_dim: (0, 0),
            misc: String::from(""),
        }
    }

    pub fn open(path: &str) -> File {
        let f = fs::File::open(path).expect("Could not open file");
        let f = io::BufReader::new(f);

        File {
            name: String::from(Path::new(path).file_name().unwrap().to_string_lossy()),
            lines: f.lines().map(|r| r.unwrap()).collect(),
            caret: Cursor { x: 1, y: 1, y_offset: 0 },
            window_top: Cursor { x: 1, y: 1, y_offset: 0 },
            last_dim: (0, 0),
            misc: String::from(""),
        }
    }

    pub fn save(&self, path: &str) {
        let f = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(path)
            .expect("Could not open file");
        let mut f = io::BufWriter::new(f);

        for line in self.lines.iter() {
            write!(f, "{}\n", line).unwrap();
        }
    }

    pub fn cursor(&self, dim: (i32, i32)) -> Cursor {
        let projected_caret = self.caret.project(dim);
        let projected_top = self.window_top.project(dim);
        Cursor {
            x: projected_caret.x,
            y: projected_caret.y - projected_top.y + 1,
            y_offset: 0,
        }
    }

    pub fn lineno_chars(&self) -> i32 {
        format!("{}", self.lines.len()).len() as i32
    }

    pub fn wrapped_lines(&self, dim: (i32, i32)) -> Vec<(Option<u16>, String)> {
        let mut result = vec![];
        let width = dim.0 as usize;
        let top_y = self.window_top.y as usize - 1;
        let top_extra = (self.window_top.x - 1) / dim.0;
        for (line_number, raw_line) in self.lines.iter().enumerate().skip(top_y) {
            let mut line_start = 0;
            let mut line_end = cmp::min(raw_line.len(), width);
            result.push((Some(line_number as u16),
                         String::from(&raw_line[line_start..line_end])));
            while line_end < raw_line.len() {
                line_start = line_end;
                line_end += cmp::min(raw_line.len() - line_end, width);
                result.push((None,
                             String::from(&raw_line[line_start..line_end])));
            }
            if result.len() as i32 >= dim.1 + top_extra {
                break;
            }
        }
        result.truncate((dim.1 + top_extra) as usize);
        result.drain(..top_extra as usize);
        result
    }

    fn current_line(&self) -> &String {
        &self.lines[self.caret.y as usize - 1]
    }

    fn recompute_offsets(&mut self, dim: (i32, i32)) {
        if dim != self.last_dim {
            self.caret.recompute_offset(dim, &self.lines);
            self.window_top.recompute_offset(dim, &self.lines);
            self.last_dim = dim;
        }
    }

    pub fn refresh(&mut self, dim: (i32, i32)) {
        self.recompute_offsets(dim);
    }

    pub fn move_cursor_left(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_left(dim, &self.lines);
        if self.cursor(dim).y < 1 {
            self.window_top.move_up(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_right(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_right(dim, &self.lines);
        if self.cursor(dim).y > dim.1 {
            self.window_top.move_down(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_up(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_up(dim, &self.lines);
        if self.cursor(dim).y < 1 {
            self.window_top.move_up(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_down(&mut self, dim: (i32, i32)) -> bool {
        self.recompute_offsets(dim);
        self.caret.move_down(dim, &self.lines);
        if self.cursor(dim).y > dim.1 {
            self.window_top.move_down(dim, &self.lines);
            true
        } else {
            false
        }
    }

    pub fn move_cursor_home(&mut self, dim: (i32, i32)) {
        self.recompute_offsets(dim);
        self.caret.move_home(dim, &self.lines);
    }

    pub fn move_cursor_end(&mut self, dim: (i32, i32)) {
        self.recompute_offsets(dim);
        self.caret.move_end(dim, &self.lines);
    }

    pub fn page_up(&mut self, dim: (i32, i32)) {
        for _ in 0..dim.1 {
            self.move_cursor_up(dim);
        }
    }

    pub fn page_down(&mut self, dim: (i32, i32)) {
        for _ in 0..dim.1 {
            self.move_cursor_down(dim);
        }
    }

    pub fn insert(&mut self, dim: (i32, i32), c: char) {
        {
            let x = self.caret.x - 1;
            let line = &mut self.lines[self.caret.y as usize - 1];
            let pos = if x > line.len() as i32 {
                self.caret.x = line.len() as i32 - 1;
                line.len()
            } else {
                x as usize
            };
            line.insert(pos, c);
        }
        self.move_cursor_right(dim);
    }

    pub fn delete(&mut self, _: (i32, i32)) {
        let x = self.caret.x as usize - 1;
        if x == self.current_line().len() {
            let y = self.caret.y as usize - 1;
            if y < self.lines.len() - 1 {
                let next_line = self.lines.remove(y + 1);
                self.lines[y].push_str(&next_line);
            }
        } else {
            let line = &mut self.lines[self.caret.y as usize - 1];
            line.remove(x);
        }
    }

    pub fn backspace(&mut self, dim: (i32, i32)) {
        self.move_cursor_left(dim);
        self.delete(dim);
    }

    pub fn insert_newline(&mut self, dim: (i32, i32)) {
        let after = {
            let before = &mut self.lines[self.caret.y as usize - 1];
            before.split_off(self.caret.x as usize - 1)
        };
        self.lines.insert(self.caret.y as usize, after);
        self.move_cursor_right(dim);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn load_save_preserves_everything() {
        let f = File::open("README.md");
        f.save("readme.bak");
        let orig = fs::File::open("README.md").unwrap();
        let new = fs::File::open("readme.bak").unwrap();
        for (b1, b2) in orig.bytes().zip(new.bytes()) {
            assert_eq!(b1.unwrap(), b2.unwrap());
        }
        fs::remove_file("readme.bak").unwrap();
    }
}
<|endoftext|>


<commit_before>//! Functions for creating and evaluating [Haar-like features](https://en.wikipedia.org/wiki/Haar-like_features).

use image::{
    GenericImage,
    Luma
};
use itertools::Itertools;
use std::collections::HashMap;
use std::ops::Mul;

/// Whether the top left region in a Haar filter is counted
/// with positive or negative sign.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Sign { Positive, Negative }

/// A Haar filter whose value on an integral image is the weighted sum
/// of the values of the integral image at the given points.
// TODO: these structs are pretty big. Look into instead just storing
// TODO: the offsets between sample points. We should only need 10 bytes/filter,
// TODO: meaning we could fit a typical cascade in L1 cache.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct HaarFilter {
    points: [u32; 18],
    weights: [i8; 9],
    count: usize
}

impl HaarFilter {

    /// Evaluates the Haar filter on an integral image.
    pub fn evaluate(&self, integral: &I ) -> i32
        where I: GenericImage<Pixel=Luma<u32>> {

        let mut sum = 0i32;
        for i in 0..self.count {
            let p = integral.get_pixel(self.points[2 * i], self.points[2 * i + 1])[0];
            sum += p as i32 * self.weights[i] as i32;
        }
        sum
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B   C
    ///       +   -
    ///     D   E   F
    ///

    /// A = (top, left), B.x = left + dx1, C.x = B.x + dx2, and D.y = A.y + dy.
    pub fn two_region_horizontal(top: u32, left: u32, dx1: u32, dx2: u32, dy: u32, sign: Sign)
        -> HaarFilter {

        combine_alternating(&[
            eval_points(top, left,       dx1, dy),
            eval_points(top, left + dx1, dx2, dy)]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B
    ///       +
    ///     C   D
    ///       -
    ///     E   F
    ///

    /// A = (top, left), B.x = left + dx, C.y = top + dy1, and E.y = C.y + dy2.
    pub fn two_region_vertical(top: u32, left: u32, dx: u32, dy1: u32, dy2: u32, sign: Sign)
        -> HaarFilter {

        combine_alternating(&[
            eval_points(top,       left, dx, dy1),
            eval_points(top + dy1, left, dx, dy2)]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B   C   D
    ///       +   -   +
    ///     E   F   G   H
    ///

    /// A = (top, left), B.x = left + dx1, C.x = B.x + dx2, D.x = C.x + dx3, and E.y = top + dy.
    pub fn three_region_horizontal(
        top: u32, left: u32, dx1: u32, dx2: u32, dx3: u32, dy: u32, sign: Sign)
            -> HaarFilter {

        combine_alternating(&[
            eval_points(top, left,             dx1, dy),
            eval_points(top, left + dx1,       dx2, dy),
            eval_points(top, left + dx1 + dx2, dx3, dy),
            ]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B
    ///       +
    ///     C   D
    ///       -
    ///     E   F
    ///       +
    ///     G   H
    ///

    /// A = (top, left), B.x = left + dx, C.y = top + dy1, E.y = C.y + dy2, and G.y = E.y + dy3.
    pub fn three_region_vertical(
        top: u32, left: u32, dx: u32, dy1: u32, dy2: u32, dy3: u32, sign: Sign)
            -> HaarFilter {

        combine_alternating(&[
            eval_points(top,             left, dx, dy1),
            eval_points(top + dy1,       left, dx, dy2),
            eval_points(top + dy1 + dy2, left, dx, dy3),
            ]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B   C
    ///       +   -
    ///     D   E   F
    ///       -   +
    ///     G   H   I
    ///

    /// A = (top, left), B.x = left + dx1, C.x = B.x + dx2, D.y = top + dy1, and G.y = D.y + dy2.
    pub fn four_region(
        top: u32, left: u32, dx1: u32, dx2: u32, dy1: u32, dy2: u32, sign: Sign)
            -> HaarFilter {

        combine_alternating(&[
            eval_points(top,       left,       dx1, dy1),
            eval_points(top,       left + dx1, dx2, dy1),
            eval_points(top + dy1, left,       dx1, dy2),
            eval_points(top + dy1, left + dx1, dx2, dy2),
            ]) * multiplier(sign)
    }
}

/// See comment on eval_points.
struct EvalPoints {
    points: [(u32, u32); 4],
    weights: [i8; 4]
}

impl EvalPoints {
    fn new(points: [(u32, u32); 4], weights: [i8; 4]) -> EvalPoints {
        EvalPoints { points: points, weights: weights }
    }
}

impl Mul<i8> for HaarFilter {
    type Output = HaarFilter;

    fn mul(self, rhs: i8) -> HaarFilter {
        let mut copy = self;
        for i in 0..copy.weights.len() {
            copy.weights[i] *= rhs;
        }
        copy
    }
}

/// Points at which to evaluate an integral image to produce the sum of the
/// pixel intensities of all points within a rectangle. Only valid when the
/// rectangle is wholly contained in the image boundaries. The only non-trivial
/// cases are when the rectangle touches the left or top image boundaries. In this
/// case we need to evaluate fewer than four points, and return weights of zero
/// for the other points.
fn eval_points(top: u32, left: u32, width: u32, height: u32) -> EvalPoints {

    let mut points = [
        (0u32, 0u32),
        (0u32, 0u32),
        (0u32, 0u32),
        (left + width - 1, top + height - 1)];

    let mut weights = [0i8, 0i8, 0i8, 1i8];

    if top > 0 && left > 0 {
        points[0] = (left - 1, top - 1);
        weights[0] = 1i8;
    }
    if top > 0 {
        points[1] = (left + width - 1, top - 1);
        weights[1] = -1i8;
    }
    if left > 0 {
        points[2] = (left - 1, top + height - 1);
        weights[2] = -1i8;
    }

    EvalPoints::new(points, weights)
}

/// Combine sets of evaluation points with alternating signs.
/// The first entry of rects is counted with positive sign.
// TODO: check that we don't have too many distinct points. This
// TODO: function isn't exported, so we just need to check the HaarFilter uses
// TODO: haven't messed anything up.
fn combine_alternating(rects: &[EvalPoints]) -> HaarFilter {

    // Aggregate weights of all points, remove any with zero weight, and
    // order lexicographically by location.
    let mut sign = 1i8;
    let sorted_points = rects
        .iter()
        .fold(HashMap::new(), |mut acc, rect| {
            for i in 0..4 {
                *acc.entry(rect.points[i]).or_insert(0) += sign * rect.weights[i];
            }
            sign *= -1i8;
            acc
            })
        .into_iter()
        .filter(|kv| kv.1 != 0)
        .sorted_by(|a, b| Ord::cmp(&((a.0).1, (a.0).0), &((b.0).1, (b.0).0)));

    let mut count = 0;
    let mut points = [0u32; 18];
    let mut weights = [0i8; 9];

    for pw in sorted_points {
        points[2 * count] = (pw.0).0;
        points[2 * count + 1] = (pw.0).1;
        weights[count] = pw.1;
        count += 1;
    }

    HaarFilter {
        points: points,
        weights: weights,
        count: count }
}

fn multiplier(sign: Sign) -> i8 {
    if sign == Sign::Positive {1} else {-1}
}

#[cfg(test)]
mod test {

    use super::{
        combine_alternating,
        EvalPoints,
        HaarFilter,
        Sign
    };
    use image::{
        ImageBuffer
    };
    use integralimage::{
        integral_image
    };

    #[test]
    fn test_combine_alternating() {
        // Three region horizontally aligned filter:
        // A   B   C   D
        //   +   -   +
        // E   F   G   H
        // I(A) - 2I(B) + 2I(C) - I(D) - I(E) + 2I(F) - 2I(G) + I(H).

        let a = (0, 0);
        let b = (1, 0);
        let c = (2, 0);
        let d = (3, 0);
        let e = (0, 1);
        let f = (1, 1);
        let g = (2, 1);
        let h = (3, 1);

        let left  = EvalPoints::new([a, b, e, f], [1, -1, -1, 1]);
        let mid   = EvalPoints::new([b, c, f, g], [1, -1, -1, 1]);
        let right = EvalPoints::new([c, d, g, h], [1, -1, -1, 1]);

        let filter = combine_alternating(&[left, mid, right]);
        let expected = HaarFilter {
            points: [0, 0, 1, 0, 2, 0, 3, 0, 0, 1, 1, 1, 2, 1, 3, 1, 0, 0],
            weights: [1, -2, 2, -1, -1, 2, -2, 1, 0],
            count: 8
        };

        assert_eq!(filter, expected);
    }

    #[test]
    fn test_two_region_horizontal() {
        // Two region horizontally aligned filter:
        // A   B   C
        //   +   -
        // D   E   F
        // I(A) - 2I(B) + I(C) - I(D) + 2I(E) - I(F)
        let image = ImageBuffer::from_raw(5, 5, vec![
            1u8,     2u8, 3u8,     4u8,     5u8,
                 /***+++++++++*****-----***/
            6u8, /**/7u8, 8u8,/**/ 9u8, /**/0u8,
            9u8, /**/8u8, 7u8,/**/ 6u8, /**/5u8,
            4u8, /**/3u8, 2u8,/**/ 1u8, /**/0u8,
                 /***+++++++++*****-----***/
            6u8, 5u8, 4u8, 2u8, 1u8]).unwrap();

        let integral = integral_image(&image);

        let filter = HaarFilter::two_region_horizontal(
            1, 1, 2u32, 1u32, 3u32, Sign::Positive);

        let value = filter.evaluate(&integral);

        assert_eq!(value, 19i32);
    }
}
<commit_msg>Add test<commit_after>//! Functions for creating and evaluating [Haar-like features](https://en.wikipedia.org/wiki/Haar-like_features).

use image::{
    GenericImage,
    Luma
};
use itertools::Itertools;
use std::collections::HashMap;
use std::ops::Mul;

/// Whether the top left region in a Haar filter is counted
/// with positive or negative sign.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Sign { Positive, Negative }

/// A Haar filter whose value on an integral image is the weighted sum
/// of the values of the integral image at the given points.
// TODO: these structs are pretty big. Look into instead just storing
// TODO: the offsets between sample points. We should only need 10 bytes/filter,
// TODO: meaning we could fit a typical cascade in L1 cache.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct HaarFilter {
    points: [u32; 18],
    weights: [i8; 9],
    count: usize
}

impl HaarFilter {

    /// Evaluates the Haar filter on an integral image.
    pub fn evaluate(&self, integral: &I ) -> i32
        where I: GenericImage<Pixel=Luma<u32>> {

        let mut sum = 0i32;
        for i in 0..self.count {
            let p = integral.get_pixel(self.points[2 * i], self.points[2 * i + 1])[0];
            sum += p as i32 * self.weights[i] as i32;
        }
        sum
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B   C
    ///       +   -
    ///     D   E   F
    ///

    /// A = (top, left), B.x = left + dx1, C.x = B.x + dx2, and D.y = A.y + dy.
    pub fn two_region_horizontal(top: u32, left: u32, dx1: u32, dx2: u32, dy: u32, sign: Sign)
        -> HaarFilter {

        combine_alternating(&[
            eval_points(top, left,       dx1, dy),
            eval_points(top, left + dx1, dx2, dy)]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B
    ///       +
    ///     C   D
    ///       -
    ///     E   F
    ///

    /// A = (top, left), B.x = left + dx, C.y = top + dy1, and E.y = C.y + dy2.
    pub fn two_region_vertical(top: u32, left: u32, dx: u32, dy1: u32, dy2: u32, sign: Sign)
        -> HaarFilter {

        combine_alternating(&[
            eval_points(top,       left, dx, dy1),
            eval_points(top + dy1, left, dx, dy2)]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B   C   D
    ///       +   -   +
    ///     E   F   G   H
    ///

    /// A = (top, left), B.x = left + dx1, C.x = B.x + dx2, D.x = C.x + dx3, and E.y = top + dy.
    pub fn three_region_horizontal(
        top: u32, left: u32, dx1: u32, dx2: u32, dx3: u32, dy: u32, sign: Sign)
            -> HaarFilter {

        combine_alternating(&[
            eval_points(top, left,             dx1, dy),
            eval_points(top, left + dx1,       dx2, dy),
            eval_points(top, left + dx1 + dx2, dx3, dy),
            ]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B
    ///       +
    ///     C   D
    ///       -
    ///     E   F
    ///       +
    ///     G   H
    ///

    /// A = (top, left), B.x = left + dx, C.y = top + dy1, E.y = C.y + dy2, and G.y = E.y + dy3.
    pub fn three_region_vertical(
        top: u32, left: u32, dx: u32, dy1: u32, dy2: u32, dy3: u32, sign: Sign)
            -> HaarFilter {

        combine_alternating(&[
            eval_points(top,             left, dx, dy1),
            eval_points(top + dy1,       left, dx, dy2),
            eval_points(top + dy1 + dy2, left, dx, dy3),
            ]) * multiplier(sign)
    }

    /// Returns the following feature (with signs reversed if Sign == Sign::Negative).
    ///

    ///     A   B   C
    ///       +   -
    ///     D   E   F
    ///       -   +
    ///     G   H   I
    ///

    /// A = (top, left), B.x = left + dx1, C.x = B.x + dx2, D.y = top + dy1, and G.y = D.y + dy2.
    pub fn four_region(
        top: u32, left: u32, dx1: u32, dx2: u32, dy1: u32, dy2: u32, sign: Sign)
            -> HaarFilter {

        combine_alternating(&[
            eval_points(top,       left,       dx1, dy1),
            eval_points(top,       left + dx1, dx2, dy1),
            eval_points(top + dy1, left,       dx1, dy2),
            eval_points(top + dy1, left + dx1, dx2, dy2),
            ]) * multiplier(sign)
    }
}

/// See comment on eval_points.
struct EvalPoints {
    points: [(u32, u32); 4],
    weights: [i8; 4]
}

impl EvalPoints {
    fn new(points: [(u32, u32); 4], weights: [i8; 4]) -> EvalPoints {
        EvalPoints { points: points, weights: weights }
    }
}

impl Mul<i8> for HaarFilter {
    type Output = HaarFilter;

    fn mul(self, rhs: i8) -> HaarFilter {
        let mut copy = self;
        for i in 0..copy.weights.len() {
            copy.weights[i] *= rhs;
        }
        copy
    }
}

/// Points at which to evaluate an integral image to produce the sum of the
/// pixel intensities of all points within a rectangle. Only valid when the
/// rectangle is wholly contained in the image boundaries. The only non-trivial
/// cases are when the rectangle touches the left or top image boundaries. In this
/// case we need to evaluate fewer than four points, and return weights of zero
/// for the other points.
fn eval_points(top: u32, left: u32, width: u32, height: u32) -> EvalPoints {

    let mut points = [
        (0u32, 0u32),
        (0u32, 0u32),
        (0u32, 0u32),
        (left + width - 1, top + height - 1)];

    let mut weights = [0i8, 0i8, 0i8, 1i8];

    if top > 0 && left > 0 {
        points[0] = (left - 1, top - 1);
        weights[0] = 1i8;
    }
    if top > 0 {
        points[1] = (left + width - 1, top - 1);
        weights[1] = -1i8;
    }
    if left > 0 {
        points[2] = (left - 1, top + height - 1);
        weights[2] = -1i8;
    }

    EvalPoints::new(points, weights)
}

/// Combine sets of evaluation points with alternating signs.
/// The first entry of rects is counted with positive sign.
// TODO: check that we don't have too many distinct points. This
// TODO: function isn't exported, so we just need to check the HaarFilter uses
// TODO: haven't messed anything up.
fn combine_alternating(rects: &[EvalPoints]) -> HaarFilter {

    // Aggregate weights of all points, remove any with zero weight, and
    // order lexicographically by location.
    let mut sign = 1i8;
    let sorted_points = rects
        .iter()
        .fold(HashMap::new(), |mut acc, rect| {
            for i in 0..4 {
                *acc.entry(rect.points[i]).or_insert(0) += sign * rect.weights[i];
            }
            sign *= -1i8;
            acc
            })
        .into_iter()
        .filter(|kv| kv.1 != 0)
        .sorted_by(|a, b| Ord::cmp(&((a.0).1, (a.0).0), &((b.0).1, (b.0).0)));

    let mut count = 0;
    let mut points = [0u32; 18];
    let mut weights = [0i8; 9];

    for pw in sorted_points {
        points[2 * count] = (pw.0).0;
        points[2 * count + 1] = (pw.0).1;
        weights[count] = pw.1;
        count += 1;
    }

    HaarFilter {
        points: points,
        weights: weights,
        count: count }
}

fn multiplier(sign: Sign) -> i8 {
    if sign == Sign::Positive {1} else {-1}
}

#[cfg(test)]
mod test {

    use super::{
        combine_alternating,
        EvalPoints,
        HaarFilter,
        Sign
    };
    use image::{
        ImageBuffer
    };
    use integralimage::{
        integral_image
    };

    #[test]
    fn test_combine_alternating() {
        // Three region horizontally aligned filter:
        // A   B   C   D
        //   +   -   +
        // E   F   G   H
        // I(A) - 2I(B) + 2I(C) - I(D) - I(E) + 2I(F) - 2I(G) + I(H).

        let a = (0, 0);
        let b = (1, 0);
        let c = (2, 0);
        let d = (3, 0);
        let e = (0, 1);
        let f = (1, 1);
        let g = (2, 1);
        let h = (3, 1);

        let left  = EvalPoints::new([a, b, e, f], [1, -1, -1, 1]);
        let mid   = EvalPoints::new([b, c, f, g], [1, -1, -1, 1]);
        let right = EvalPoints::new([c, d, g, h], [1, -1, -1, 1]);

        let filter = combine_alternating(&[left, mid, right]);
        let expected = HaarFilter {
            points: [0, 0, 1, 0, 2, 0, 3, 0, 0, 1, 1, 1, 2, 1, 3, 1, 0, 0],
            weights: [1, -2, 2, -1, -1, 2, -2, 1, 0],
            count: 8
        };

        assert_eq!(filter, expected);
    }

    #[test]
    fn test_two_region_horizontal() {
        // Two region horizontally aligned filter:
        // A   B   C
        //   +   -
        // D   E   F
        let image = ImageBuffer::from_raw(5, 5, vec![
            1u8,     2u8, 3u8,     4u8,     5u8,
                 /***+++++++++*****-----***/
            6u8, /**/7u8, 8u8,/**/ 9u8, /**/0u8,
            9u8, /**/8u8, 7u8,/**/ 6u8, /**/5u8,
            4u8, /**/3u8, 2u8,/**/ 1u8, /**/0u8,
                 /***+++++++++*****-----***/
            6u8, 5u8, 4u8, 2u8, 1u8]).unwrap();

        let integral = integral_image(&image);

        let filter = HaarFilter::two_region_horizontal(
            1, 1, 2u32, 1u32, 3u32, Sign::Positive);

        let value = filter.evaluate(&integral);

        assert_eq!(value, 19i32);
    }

    #[test]
    fn test_three_region_vertical() {
        // Three region vertically aligned filter:
        // A   B
        //   +
        // C   D
        //   -
        // E   F
        //   +
        // G   H
        let image = ImageBuffer::from_raw(5, 5, vec![
        /*****************/
        /*-*/1u8, 2u8,/*-*/ 3u8, 4u8, 5u8,
        /*****************/
        /*+*/6u8, 7u8,/*+*/ 8u8, 9u8, 0u8,
        /*+*/9u8, 8u8,/*+*/ 7u8, 6u8, 5u8,
        /*****************/
        /*-*/4u8, 3u8,/*-*/ 2u8, 1u8, 0u8,
        /*****************/
             6u8, 5u8,      4u8, 2u8, 1u8]).unwrap();

        let integral = integral_image(&image);

        let filter = HaarFilter::three_region_vertical(
            0, 0, 2u32, 1u32, 2u32, 1u32, Sign::Negative);

        let value = filter.evaluate(&integral);

        assert_eq!(value, 20i32);
    }
}
<|endoftext|>