formula 0.1.0

A parser and evaluator of spreadsheet-like formulas
Documentation
use crate::{Expr, Formula, Result, Rule};
use pest::iterators::Pair;

impl Formula<'_> {
    #[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
    pub(crate) fn parse_num(pair: Pair<Rule>) -> Result<Expr> {
        let number = pair.as_str().trim().parse().unwrap();
        Ok(Expr::Number(number))
    }

    #[allow(clippy::unnecessary_wraps)]
    pub(crate) fn parse_string(pair: Pair<Rule>) -> Result<Expr> {
        let string = pair.into_inner().as_str().to_string();
        let string = string
            .replace("\\\'", "\'")
            .replace("\\\"", "\"")
            .replace("\\\\", "\"")
            .replace("\\b", "\u{0008}")
            .replace("\\f", "\u{000C}")
            .replace("\\\n", "\n")
            .replace("\\\r", "\r")
            .replace("\\\t", "\t");
        Ok(Expr::String(string))
    }

    #[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
    pub(crate) fn parse_true(_pair: Pair<Rule>) -> Result<Expr> {
        Ok(Expr::Bool(true))
    }

    #[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
    pub(crate) fn parse_false(_pair: Pair<Rule>) -> Result<Expr> {
        Ok(Expr::Bool(false))
    }

    #[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
    pub(crate) fn parse_array(pair: Pair<Rule>) -> Result<Expr> {
        let inner = pair.into_inner();

        let mut table = Vec::new();
        let mut row = Vec::new();

        for ipair in inner {
            let rule_type = ipair.as_rule();

            match rule_type {
                Rule::array_col_sep => {}
                Rule::array_row_sep => {
                    table.push(Expr::Array(row));
                    row = Vec::new();
                }
                _ => {
                    row.push(Self::parse_pair(ipair)?);
                }
            }
        }

        if table.is_empty() {
            Ok(Expr::Array(row))
        } else {
            table.push(Expr::Array(row));
            Ok(Expr::Array(table))
        }
    }
}

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

    #[test]
    #[allow(clippy::too_many_lines)]
    fn test_parse_basic_types() {
        let formula = Formula::new("=TRUE").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(value, Expr::Bool(true));

        let formula = Formula::new("=TRUE()").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(value, Expr::Bool(true));

        let formula = Formula::new("=25").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(value, Expr::Number(25.0));

        let formula = Formula::new("='TEST'").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(value, Expr::String("TEST".to_string()));

        let formula = Formula::new("='\\'TEST\\\"'").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(value, Expr::String("'TEST\"".to_string()));

        let formula = Formula::new("={}").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(value, Expr::Array(vec![]));

        let formula = Formula::new("={'TEST', 1}").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(
            value,
            Expr::Array(vec![Expr::String("TEST".to_string()), Expr::Number(1.0)]),
        );

        let formula = Formula::new("={'TEST', SUM(1,2); 2, TRUE}").unwrap();
        let value = formula.parse().unwrap();
        assert_eq!(
            value,
            Expr::Array(vec![
                Expr::Array(vec![Expr::String("TEST".to_string()), Expr::Number(3.0)]),
                Expr::Array(vec![Expr::Number(2.0), Expr::Bool(true)]),
            ])
        );
    }
}