aurora-modules 0.1.0

Git, filesystem, system, network, archive, docker, unix, crypto, calculator, QR, color, timer, notes, clipboard, and text processing modules
Documentation
use aurora_core::{AuroraResult, Pipeline, Value};

#[derive(Debug, Clone, Copy, PartialEq)]
enum Token {
    Number(f64),
    Plus,
    Minus,
    Star,
    Slash,
    Percent,
    Caret,
    LParen,
    RParen,
}

pub fn calc_eval(expr: &[String]) -> AuroraResult<Pipeline> {
    let input = expr.join(" ");
    let tokens = tokenize(&input)?;
    let result = evaluate(&tokens)?;

    Ok(Pipeline::table(
        vec!["expression".into(), "result".into()],
        vec![vec![
            Value::String(input),
            Value::Float(result),
        ]],
    ))
}

fn tokenize(input: &str) -> AuroraResult<Vec<Token>> {
    let mut tokens = Vec::new();
    let chars: Vec<char> = input.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        let c = chars[i];
        if c.is_whitespace() {
            i += 1;
            continue;
        }
        if c.is_ascii_digit() || c == '.' {
            let mut num = String::new();
            while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
                num.push(chars[i]);
                i += 1;
            }
            let n: f64 = num.parse()
                .map_err(|_| aurora_core::AuroraError::InvalidInput(
                    format!("invalid number: {}", num)
                ))?;
            tokens.push(Token::Number(n));
            continue;
        }
        match c {
            '+' => tokens.push(Token::Plus),
            '-' => tokens.push(Token::Minus),
            '*' => tokens.push(Token::Star),
            '/' => tokens.push(Token::Slash),
            '%' => tokens.push(Token::Percent),
            '^' => tokens.push(Token::Caret),
            '(' => tokens.push(Token::LParen),
            ')' => tokens.push(Token::RParen),
            _ => return Err(aurora_core::AuroraError::InvalidInput(
                format!("unexpected character: {}", c)
            )),
        }
        i += 1;
    }

    Ok(tokens)
}

fn precedence(op: Token) -> i32 {
    match op {
        Token::Plus | Token::Minus => 1,
        Token::Star | Token::Slash | Token::Percent => 2,
        Token::Caret => 3,
        _ => 0,
    }
}

fn apply_op(op: Token, a: f64, b: f64) -> f64 {
    match op {
        Token::Plus => a + b,
        Token::Minus => a - b,
        Token::Star => a * b,
        Token::Slash => a / b,
        Token::Percent => a % b,
        Token::Caret => a.powf(b),
        _ => a,
    }
}

fn evaluate(tokens: &[Token]) -> AuroraResult<f64> {
    let mut values: Vec<f64> = Vec::new();
    let mut ops: Vec<Token> = Vec::new();

    let mut i = 0;
    while i < tokens.len() {
        match tokens[i] {
            Token::Number(n) => values.push(n),
            Token::LParen => ops.push(Token::LParen),
            Token::RParen => {
                loop {
                    let op = ops.pop().ok_or_else(|| {
                        aurora_core::AuroraError::InvalidInput("mismatched parentheses".into())
                    })?;
                    match op {
                        Token::LParen => break,
                        _ => {
                            let b = values.pop().ok_or_else(|| {
                                aurora_core::AuroraError::InvalidInput("expression error".into())
                            })?;
                            let a = values.pop().ok_or_else(|| {
                                aurora_core::AuroraError::InvalidInput("expression error".into())
                            })?;
                            values.push(apply_op(op, a, b));
                        }
                    }
                }
            }
            ref op @ (Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Percent | Token::Caret) => {
                while let Some(top) = ops.last() {
                    if *top == Token::LParen {
                        break;
                    }
                    if precedence(*top) >= precedence(*op) {
                        let top_op = ops.pop().ok_or_else(|| {
                            aurora_core::AuroraError::InvalidInput("expression error".into())
                        })?;
                        let b = values.pop().ok_or_else(|| {
                            aurora_core::AuroraError::InvalidInput("expression error".into())
                        })?;
                        let a = values.pop().ok_or_else(|| {
                            aurora_core::AuroraError::InvalidInput("expression error".into())
                        })?;
                        values.push(apply_op(top_op, a, b));
                    } else {
                        break;
                    }
                }
                ops.push(*op);
            }
        }
        i += 1;
    }

    while let Some(op) = ops.pop() {
        let b = values.pop().ok_or_else(|| {
            aurora_core::AuroraError::InvalidInput("expression error".into())
        })?;
        let a = values.pop().ok_or_else(|| {
            aurora_core::AuroraError::InvalidInput("expression error".into())
        })?;
        values.push(apply_op(op, a, b));
    }

    values.pop().ok_or_else(|| {
        aurora_core::AuroraError::InvalidInput("empty expression".into())
    })
}