Skip to main content

aurora_modules/
calc.rs

1use aurora_core::{AuroraResult, Pipeline, Value};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4enum Token {
5    Number(f64),
6    Plus,
7    Minus,
8    Star,
9    Slash,
10    Percent,
11    Caret,
12    LParen,
13    RParen,
14}
15
16pub fn calc_eval(expr: &[String]) -> AuroraResult<Pipeline> {
17    let input = expr.join(" ");
18    let tokens = tokenize(&input)?;
19    let result = evaluate(&tokens)?;
20
21    Ok(Pipeline::table(
22        vec!["expression".into(), "result".into()],
23        vec![vec![
24            Value::String(input),
25            Value::Float(result),
26        ]],
27    ))
28}
29
30fn tokenize(input: &str) -> AuroraResult<Vec<Token>> {
31    let mut tokens = Vec::new();
32    let chars: Vec<char> = input.chars().collect();
33    let mut i = 0;
34
35    while i < chars.len() {
36        let c = chars[i];
37        if c.is_whitespace() {
38            i += 1;
39            continue;
40        }
41        if c.is_ascii_digit() || c == '.' {
42            let mut num = String::new();
43            while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
44                num.push(chars[i]);
45                i += 1;
46            }
47            let n: f64 = num.parse()
48                .map_err(|_| aurora_core::AuroraError::InvalidInput(
49                    format!("invalid number: {}", num)
50                ))?;
51            tokens.push(Token::Number(n));
52            continue;
53        }
54        match c {
55            '+' => tokens.push(Token::Plus),
56            '-' => tokens.push(Token::Minus),
57            '*' => tokens.push(Token::Star),
58            '/' => tokens.push(Token::Slash),
59            '%' => tokens.push(Token::Percent),
60            '^' => tokens.push(Token::Caret),
61            '(' => tokens.push(Token::LParen),
62            ')' => tokens.push(Token::RParen),
63            _ => return Err(aurora_core::AuroraError::InvalidInput(
64                format!("unexpected character: {}", c)
65            )),
66        }
67        i += 1;
68    }
69
70    Ok(tokens)
71}
72
73fn precedence(op: Token) -> i32 {
74    match op {
75        Token::Plus | Token::Minus => 1,
76        Token::Star | Token::Slash | Token::Percent => 2,
77        Token::Caret => 3,
78        _ => 0,
79    }
80}
81
82fn apply_op(op: Token, a: f64, b: f64) -> f64 {
83    match op {
84        Token::Plus => a + b,
85        Token::Minus => a - b,
86        Token::Star => a * b,
87        Token::Slash => a / b,
88        Token::Percent => a % b,
89        Token::Caret => a.powf(b),
90        _ => a,
91    }
92}
93
94fn evaluate(tokens: &[Token]) -> AuroraResult<f64> {
95    let mut values: Vec<f64> = Vec::new();
96    let mut ops: Vec<Token> = Vec::new();
97
98    let mut i = 0;
99    while i < tokens.len() {
100        match tokens[i] {
101            Token::Number(n) => values.push(n),
102            Token::LParen => ops.push(Token::LParen),
103            Token::RParen => {
104                loop {
105                    let op = ops.pop().ok_or_else(|| {
106                        aurora_core::AuroraError::InvalidInput("mismatched parentheses".into())
107                    })?;
108                    match op {
109                        Token::LParen => break,
110                        _ => {
111                            let b = values.pop().ok_or_else(|| {
112                                aurora_core::AuroraError::InvalidInput("expression error".into())
113                            })?;
114                            let a = values.pop().ok_or_else(|| {
115                                aurora_core::AuroraError::InvalidInput("expression error".into())
116                            })?;
117                            values.push(apply_op(op, a, b));
118                        }
119                    }
120                }
121            }
122            ref op @ (Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Percent | Token::Caret) => {
123                while let Some(top) = ops.last() {
124                    if *top == Token::LParen {
125                        break;
126                    }
127                    if precedence(*top) >= precedence(*op) {
128                        let top_op = ops.pop().ok_or_else(|| {
129                            aurora_core::AuroraError::InvalidInput("expression error".into())
130                        })?;
131                        let b = values.pop().ok_or_else(|| {
132                            aurora_core::AuroraError::InvalidInput("expression error".into())
133                        })?;
134                        let a = values.pop().ok_or_else(|| {
135                            aurora_core::AuroraError::InvalidInput("expression error".into())
136                        })?;
137                        values.push(apply_op(top_op, a, b));
138                    } else {
139                        break;
140                    }
141                }
142                ops.push(*op);
143            }
144        }
145        i += 1;
146    }
147
148    while let Some(op) = ops.pop() {
149        let b = values.pop().ok_or_else(|| {
150            aurora_core::AuroraError::InvalidInput("expression error".into())
151        })?;
152        let a = values.pop().ok_or_else(|| {
153            aurora_core::AuroraError::InvalidInput("expression error".into())
154        })?;
155        values.push(apply_op(op, a, b));
156    }
157
158    values.pop().ok_or_else(|| {
159        aurora_core::AuroraError::InvalidInput("empty expression".into())
160    })
161}