imagnum 0.2.5

A Rust library providing versatile numeric types supporting integers and floats designed for the Lucia programming language.
Documentation
use imagnum::{Float, Int, create_float, create_int, errors::get_error_message};
use std::io::{self, Write};

#[derive(Debug, Clone)]
enum Number {
    Int(Int),
    Float(Float),
}

impl Number {
    fn promote(&self) -> Result<Float, i16> {
        match self {
            Number::Int(i) => Ok(create_float(&i.to_string())),
            Number::Float(f) => Ok(f.clone()),
        }
    }

    fn display(&self) -> String {
        match self {
            Number::Int(i) => i.to_string(),
            Number::Float(f) => f.to_string(),
        }
    }

    fn add(self, other: Number) -> Result<Number, i16> {
        match (self, other) {
            (Number::Int(a), Number::Int(b)) => Ok(Number::Int((a + b)?)),
            (a, b) => Ok(Number::Float((a.promote()? + b.promote()?)?)),
        }
    }

    fn sub(self, other: Number) -> Result<Number, i16> {
        match (self, other) {
            (Number::Int(a), Number::Int(b)) => Ok(Number::Int((a - b)?)),
            (a, b) => Ok(Number::Float((a.promote()? - b.promote()?)?)),
        }
    }

    fn mul(self, other: Number) -> Result<Number, i16> {
        match (self, other) {
            (Number::Int(a), Number::Int(b)) => Ok(Number::Int((a * b)?)),
            (a, b) => Ok(Number::Float((a.promote()? * b.promote()?)?)),
        }
    }

    fn div(self, other: Number) -> Result<Number, i16> {
        Ok(Number::Float((self.promote()? / other.promote()?)?))
    }

    fn sqrt(self) -> Result<Number, i16> {
        let f = self.promote()?;
        let res = f.sqrt()?;
        Ok(Number::Float(res))
    }

    fn pow(self, other: Number) -> Result<Number, i16> {
        match (self, other) {
            (Number::Int(a), Number::Int(b)) => Ok(Number::Int(a.pow(&b)?)),
            (a, b) => Ok(Number::Float(a.promote()?.pow(&b.promote()?)?)),
        }
    }

    fn rem(self, other: Number) -> Result<Number, i16> {
        let f_self = self.promote()?;
        let f_other = other.promote()?;
        Ok(Number::Float((f_self % f_other)?))
    }

    fn round(self, decimals: usize) -> Result<Number, i16> {
        let f = self.promote()?;
        let rounded = f.round(decimals);
        Ok(Number::Float(rounded))
    }
    fn truncate(self, decimals: usize) -> Result<Number, i16> {
        let f = self.promote()?;
        let truncated = f.truncate(decimals);
        Ok(Number::Float(truncated))
    }
}

fn parse_token(token: &str) -> Result<Number, i16> {
    if token.contains('.') {
        Ok(Number::Float(create_float(token)))
    } else {
        Ok(Number::Int(create_int(token)))
    }
}

fn main() {
    loop {
        print!("calc> ");
        io::stdout().flush().unwrap();

        let mut line = String::new();
        if io::stdin().read_line(&mut line).is_err() {
            println!("input error");
            continue;
        }

        let tokens: Vec<&str> = line.trim().split_whitespace().collect();
        if tokens.is_empty() {
            continue;
        }

        if tokens.len() == 2 && tokens[1] == "sqrt" {
            match parse_token(tokens[0]) {
                Ok(num) => match num.sqrt() {
                    Ok(res) => println!("= {}", res.display()),
                    Err(code) => println!("error [{}]: {}", code, get_error_message(code)),
                },
                Err(code) => println!("error [{}]: {}", code, get_error_message(code)),
            }
            continue;
        }

        if tokens.len() < 3 {
            println!("format: num op num [op num ...]");
            continue;
        }

        let mut iter = tokens.into_iter();

        // Support unary +/- when the sign is separated by whitespace.
        // Examples supported now: `-5 * 2`, `3 + - 2`, `+7 - 1`.
        let first = iter.next().unwrap();

        let mut acc = if first == "-" || first == "+" {
            // Unary sign at start: combine with the next token if present
            match iter.next() {
                Some(next_tok) => {
                    let signed = if first == "-" {
                        format!("-{}", next_tok)
                    } else {
                        format!("{}", next_tok)
                    };
                    match parse_token(signed.as_str()) {
                        Ok(n) => n,
                        Err(code) => {
                            println!("error [{}]: {}", code, get_error_message(code));
                            continue;
                        }
                    }
                }
                None => {
                    println!("missing operand after unary '{}'", first);
                    continue;
                }
            }
        } else {
            match parse_token(first) {
                Ok(n) => n,
                Err(code) => {
                    println!("error [{}]: {}", code, get_error_message(code));
                    continue;
                }
            }
        };

        let mut error_occurred = false;

        while let Some(op) = iter.next() {
            if op == "sqrt" {
                match acc.clone().sqrt() {
                    Ok(res) => acc = res,
                    Err(code) => {
                        println!("error [{}]: {}", code, get_error_message(code));
                        error_occurred = true;
                        break;
                    }
                }
                continue;
            }

            if op == "round" {
                let rhs_opt = iter.next();
                let decimals = match rhs_opt {
                    Some(d_str) => match d_str.parse::<u32>() {
                        Ok(val) => val,
                        Err(_) => {
                            println!("invalid decimals argument for round: {}", d_str);
                            error_occurred = true;
                            break;
                        }
                    },
                    None => {
                        println!("missing decimals argument for round");
                        error_occurred = true;
                        break;
                    }
                };

                match acc.clone().round(decimals as usize) {
                    Ok(res) => acc = res,
                    Err(code) => {
                        println!("error [{}]: {}", code, get_error_message(code));
                        error_occurred = true;
                        break;
                    }
                }
                continue;
            }

            if op == "trunc" {
                let rhs_opt = iter.next();
                let decimals = match rhs_opt {
                    Some(d_str) => match d_str.parse::<u32>() {
                        Ok(val) => val,
                        Err(_) => {
                            println!("invalid decimals argument for round: {}", d_str);
                            error_occurred = true;
                            break;
                        }
                    },
                    None => {
                        println!("missing decimals argument for round");
                        error_occurred = true;
                        break;
                    }
                };

                match acc.clone().truncate(decimals as usize) {
                    Ok(res) => acc = res,
                    Err(code) => {
                        println!("error [{}]: {}", code, get_error_message(code));
                        error_occurred = true;
                        break;
                    }
                }
                continue;
            }

            if op == "int-like" {
                match acc.clone() {
                    Number::Int(i) => println!("{} is already an int-like number", i),
                    Number::Float(f) => {
                        let int_value = f.is_integer_like();
                        if int_value {
                            println!("{} is an int-like number", f);
                        } else {
                            println!("{} is not an int-like number", f);
                        }
                    }
                }
                continue;
            }

            // Fetch the right-hand side operand; allow unary +/- before the number.
            let rhs = match iter.next() {
                Some(t) => {
                    if t == "-" || t == "+" {
                        // unary sign before the actual operand
                        match iter.next() {
                            Some(next_tok) => {
                                let signed = if t == "-" {
                                    format!("-{}", next_tok)
                                } else {
                                    format!("{}", next_tok)
                                };
                                match parse_token(signed.as_str()) {
                                    Ok(n) => n,
                                    Err(code) => {
                                        println!("error [{}]: {}", code, get_error_message(code));
                                        error_occurred = true;
                                        break;
                                    }
                                }
                            }
                            None => {
                                println!("missing operand after unary '{}'", t);
                                error_occurred = true;
                                break;
                            }
                        }
                    } else {
                        match parse_token(t) {
                            Ok(n) => n,
                            Err(code) => {
                                println!("error [{}]: {}", code, get_error_message(code));
                                error_occurred = true;
                                break;
                            }
                        }
                    }
                }
                None => {
                    println!("missing operand after operator '{}'", op);
                    error_occurred = true;
                    break;
                }
            };

            let result = match op {
                "+" => acc.clone().add(rhs),
                "-" => acc.clone().sub(rhs),
                "*" => acc.clone().mul(rhs),
                "/" => acc.clone().div(rhs),
                "^" => acc.clone().pow(rhs),
                "%" => acc.clone().rem(rhs),
                _ => {
                    println!("unknown operator: {}", op);
                    error_occurred = true;
                    break;
                }
            };

            acc = match result {
                Ok(n) => n,
                Err(code) => {
                    println!("error [{}]: {}", code, get_error_message(code));
                    error_occurred = true;
                    break;
                }
            };
        }

        if !error_occurred {
            println!("{}= {}", " ".repeat(4), acc.display());
        }
    }
}