mipl 0.2.1

Minimal Imperative Parsing Library
Documentation
use mipl::prelude::*;

fn consume_fact(parser: &mut Parser) -> Option<f32> {
    let lparen_par = ExactMatch { matches: "(".to_string() };
    let rparen_par = ExactMatch { matches: ")".to_string() };

    if let Some(_lparen_str) = lparen_par.try_next(parser) {
        let res = eval(parser);
        let _rparen_str = rparen_par.try_next(parser)?;
        res
    } else {
        let any_str_matcher = AnyStrMatch;
        any_str_matcher.try_next(parser)?
            .parse::<f32>()
            .ok()
    }
}

fn consume_term(parser: &mut Parser) -> Option<f32> {
    let mut num: f32 = consume_fact(parser)?;

    let ops: Vec<String> = vec!["*", "/"]
        .into_iter()
        .map(String::from)
        .collect();
    let or_matcher = OrExactMatch::new(ops);
    while let Some(op) = or_matcher.try_next(parser) {
        match op.as_ref() {
            "*" => num *= consume_fact(parser)?,
            "/" => num /= consume_fact(parser)?,
            _ => panic!("Logically unexpected code path traced")
        }
    }

    Some(num)
}

fn consume_expr(parser: &mut Parser) -> Option<f32> {
    let mut term: f32 = consume_term(parser)?;

    let ops: Vec<String> = vec!["+", "-"]
        .into_iter()
        .map(String::from)
        .collect();
    let or_matcher = OrExactMatch::new(ops);
    while let Some(op) = or_matcher.try_next(parser) {
        match op.as_ref() {
            "+" => term += consume_term(parser)?,
            "-" => term -= consume_term(parser)?,
            _ => panic!("Logically unexpected code path traced")
        }
    }

    Some(term)
}

fn eval(parser: &mut Parser) -> Option<f32> {
    consume_expr(parser)
}

fn setup_parser(source: String) -> Parser {
    let d_vec: Vec<char> = vec![' ', '\n', '\t'];
    let d_del = DiscardDelimiters::new(d_vec);
    let k_vec: Vec<char> = vec!['+', '-', '*', '/', '(', ')'];
    let k_del = KeepDelimiters::new(k_vec);
    let del_param = DelimitersParam {
        keep: k_del, discard: d_del
    };

    Parser::from(
        source,
        del_param
    )
}

fn eval_expr(expr: String) {
    let mut parser = setup_parser(expr);

    match eval(&mut parser) {
        Some(f) => println!("{}", f),
        _ => println!("Error.")
    }
}

fn main() {
    use std::io::Write;
    loop {
        print!("[calc] ");
        std::io::stdout().flush().unwrap();
        let expr = {
            let mut buf = String::new();
            std::io::stdin().read_line(&mut buf).unwrap();
            buf
        };
        if expr.trim() == "exit" {
            break;
        }
        eval_expr(expr);
    }
}

#[cfg(test)]
mod tests {
    use mipl::parser;

    use super::*;

    #[test]
    fn test_multiplication() {
        let mut parser = setup_parser("6 * 11".to_string());
        let res = eval(&mut parser);
        assert_eq!(Some(66.0), res);
    }

    #[test]
    fn test_addition() {
        let mut parser = setup_parser("32 + 68".to_string());
        let res = eval(&mut parser);
        assert_eq!(Some(100.0), res);
    }

    #[test]
    fn test_division() {
        let mut parser = setup_parser("10 / 2".to_string());
        let res = eval(&mut parser);
        assert_eq!(Some(5.0), res);
    }

    #[test]
    fn test_subtraction() {
        let mut parser = setup_parser("43.3 - 100".to_string());
        let res = eval(&mut parser);
        assert_eq!(Some(-56.7), res);
    }

    #[test]
    fn test_parentheses() {
        let mut parser = setup_parser("2 * (3 + 2) * 2".to_string());
        let res = eval(&mut parser);
        assert_eq!(Some(20.0), res);
    }

    #[test]
    fn test_newline() {
        let mut parser = setup_parser("2 * \n (3 + 2)".to_string());
        println!("{:#?}", parser);
        let res = eval(&mut parser);
        assert_eq!(Some(10.0), res);
    }

    #[test]
    fn test_tab() {
        let mut parser = setup_parser("2 * \t (3 + 2)".to_string());
        println!("{:#?}", parser);
        let res= eval(&mut parser);
        assert_eq!(Some(10.0), res)
    }
}