tcal_rs 0.1.1

Number theory functions library - Rust port of libqalculate number theory module
use meval::eval_str;

pub struct Parser;

impl Parser {
    pub fn preprocess(expr: &str, last: Option<f64>) -> Result<String, String> {
        let mut result = expr.to_lowercase();

        if let Some(res) = last {
            result = result.replace("res", &res.to_string());
        }

        result = Self::replace_unicode_ops(&result);
        result = Self::convert_literals(&result)?;
        result = Self::process_power(&result)?;

        Ok(result)
    }

    fn replace_unicode_ops(expr: &str) -> String {
        expr.replace('¬', "~")
            .replace('', "|")
            .replace('', "&")
            .replace('', "^")
            .replace("xor", "^^")
            .replace("**", "^")
    }

    fn convert_literals(expr: &str) -> Result<String, String> {
        let mut out = expr.to_string();
        out = Self::convert_hex(&out)?;
        out = Self::convert_bin(&out)?;
        out = Self::convert_oct(&out)?;
        Ok(out)
    }

    fn convert_hex(expr: &str) -> Result<String, String> {
        let mut result = expr.to_string();
        while let Some(pos) = result.find("0x") {
            let end = pos + 2;
            let hex: String = result[end..]
                .chars()
                .take_while(|c| c.is_ascii_hexdigit())
                .collect();

            if let Ok(v) = i64::from_str_radix(&hex, 16) {
                result.replace_range(pos..end + hex.len(), &v.to_string());
            } else {
                break;
            }
        }
        Ok(result)
    }

    fn convert_bin(expr: &str) -> Result<String, String> {
        let mut result = expr.to_string();
        while let Some(pos) = result.find("0b") {
            let end = pos + 2;
            let bin: String = result[end..]
                .chars()
                .take_while(|c| *c == '0' || *c == '1')
                .collect();

            if let Ok(v) = i64::from_str_radix(&bin, 2) {
                result.replace_range(pos..end + bin.len(), &v.to_string());
            } else {
                break;
            }
        }
        Ok(result)
    }

    fn convert_oct(expr: &str) -> Result<String, String> {
        let mut result = expr.to_string();
        while let Some(pos) = result.find("0o") {
            let end = pos + 2;
            let oct: String = result[end..]
                .chars()
                .take_while(|c| *c >= '0' && *c <= '7')
                .collect();

            if let Ok(v) = i64::from_str_radix(&oct, 8) {
                result.replace_range(pos..end + oct.len(), &v.to_string());
            } else {
                break;
            }
        }
        Ok(result)
    }

    fn process_power(expr: &str) -> Result<String, String> {
        let mut out = expr.to_string();

        while let Some(pos) = out.find('^') {
            let (l, r, start, end) = Self::extract_operands(&out, pos)?;

            let lv: f64 = eval_str(&l).map_err(|e| e.to_string())?;
            let rv: f64 = eval_str(&r).map_err(|e| e.to_string())?;

            let res = lv.powf(rv);
            out.replace_range(start..end, &res.to_string());
        }

        Ok(out)
    }

    fn extract_operands(s: &str, pos: usize) -> Result<(String, String, usize, usize), String> {
        let left = s[..pos].trim();
        let right = s[pos + 1..].trim();

        Ok((left.to_string(), right.to_string(), 0, s.len()))
    }
}