rpnc 0.1.0

Reverse Polish Notation Calculator
Documentation
use std::io;

pub struct RPN {
    pos: usize,
    input: String,
}

impl RPN {
    fn new(input: String) -> Self {
        RPN { input, pos: 0 }
    }

    fn eof(&self) -> bool {
        self.pos >= self.input.len()
    }

    fn peek_char(&self) -> Option<char> {
        self.input[self.pos..].chars().next()
    }

    fn consume_char(&mut self) -> char {
        let mut iter = self.input[self.pos..].char_indices();
        let (_, cur_char) = iter.next().unwrap();
        let (next_pos, _) = iter.next().unwrap_or((1, ' '));
        self.pos += next_pos;
        return cur_char;
    }

    fn consume_while<F>(&mut self, test: F) -> String
    where
        F: Fn(char) -> bool,
    {
        let mut result = String::new();
        while !self.eof() && test(self.peek_char().unwrap()) {
            result.push(self.consume_char())
        }
        result
    }

    fn consume_num(&mut self) -> String {
        self.consume_while(|c| c.is_digit(10))
    }

    fn consume_whitespace(&mut self) {
        self.consume_while(|c| c.is_whitespace());
    }

    fn parse(&mut self) -> Result<Vec<f64>, io::Error> {
        let mut nums = vec![];
        while !self.eof() {
            self.consume_whitespace();
            let peek_char = match self.peek_char() {
                Some(c) => c,
                None => {
                    return Ok(nums);
                }
            };

            if peek_char.is_digit(10) {
                let num = self.consume_num();
                nums.push(num.parse().unwrap());
                continue;
            }

            let l2 = nums.pop().ok_or(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Found invalid input",
            ))?;
            let l1 = nums.pop().ok_or(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Found invalid input",
            ))?;

            match peek_char {
                '+' => nums.push(l1 + l2),
                '-' => nums.push(l1 - l2),
                '*' => nums.push(l1 * l2),
                '/' => nums.push(l1 / l2),
                '%' => nums.push(l1 % l2),
                _ => {}
            }
            self.consume_char();
        }
        Ok(nums)
    }

    pub fn calc(input: String) -> Result<f64, io::Error> {
        let nums = RPN::new(input).parse()?;
        if nums.len() == 1 {
            Ok(nums[0])
        } else {
            Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Input syntax could be wrong.",
            ))
        }
    }
}

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

    #[test]
    fn test_calc() {
        assert_eq!(RPN::calc(String::from("1 2 +")).unwrap(), 3.0);
        assert_eq!(RPN::calc(String::from("1 2 -")).unwrap(), -1.0);
        assert_eq!(RPN::calc(String::from("100 1 +")).unwrap(), 101.0);
        assert_eq!(
            RPN::calc(String::from("15 7 1 1 + - / 3 * 2 1 1 + + -")).unwrap(),
            5.0
        );
        assert_eq!(RPN::calc(String::from("1 2 /")).unwrap(), 0.5);
        assert!(RPN::calc(String::from("15 7")).is_err());
        assert!(RPN::calc(String::from("a b +")).is_err());
        assert!(RPN::calc(String::from("")).is_err());
    }
}