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}