1use std::error::Error;
4
5use async_trait::async_trait;
6use serde_json::{json, Value};
7
8use super::Tool;
9
10#[derive(Debug, Clone, PartialEq, thiserror::Error)]
12#[non_exhaustive]
13pub enum CalculatorError {
14 #[error("unexpected {found} at position {position} in `{expression}`")]
15 Syntax {
16 expression: String,
17 position: usize,
18 found: String,
19 },
20 #[error("unknown name `{0}`; available functions: {FUNCTIONS}; constants: pi, e, tau")]
21 UnknownName(String),
22 #[error("{function}() takes {expected} argument(s), got {got}")]
23 Arity {
24 function: String,
25 expected: &'static str,
26 got: usize,
27 },
28 #[error("division by zero")]
29 DivisionByZero,
30 #[error("the result is not a finite number")]
31 NotFinite,
32 #[error("expression is nested too deeply")]
33 TooDeep,
34}
35
36const FUNCTIONS: &str = "sqrt, cbrt, abs, exp, ln, log (natural, or log(x, base)), log10, log2, \
37 sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, floor, ceil, round, trunc, \
38 pow, min, max, hypot, factorial";
39
40const MAX_DEPTH: usize = 200;
42
43pub fn evaluate_expression(expression: &str) -> Result<f64, CalculatorError> {
57 let mut parser = Parser {
58 expression,
59 tokens: tokenize(expression)?,
60 position: 0,
61 depth: 0,
62 };
63 let value = parser.expression()?;
64 if let Some((position, token)) = parser.tokens.get(parser.position) {
65 return Err(parser.unexpected(*position, token));
66 }
67 if value.is_finite() {
68 Ok(value)
69 } else {
70 Err(CalculatorError::NotFinite)
71 }
72}
73
74pub fn format_number(value: f64) -> String {
78 if value == value.trunc() && value.abs() < 1e21 {
79 return format!("{:.0}", value + 0.0);
81 }
82 let rounded: f64 = format!("{value:.11e}").parse().unwrap_or(value);
83 rounded.to_string()
84}
85
86#[derive(Debug, Clone, PartialEq)]
87enum Token {
88 Number(f64),
89 Name(String),
90 Op(&'static str),
91 Open,
92 Close,
93 Comma,
94}
95
96impl std::fmt::Display for Token {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self {
99 Token::Number(n) => write!(f, "number {n}"),
100 Token::Name(n) => write!(f, "`{n}`"),
101 Token::Op(op) => write!(f, "`{op}`"),
102 Token::Open => write!(f, "`(`"),
103 Token::Close => write!(f, "`)`"),
104 Token::Comma => write!(f, "`,`"),
105 }
106 }
107}
108
109fn tokenize(expression: &str) -> Result<Vec<(usize, Token)>, CalculatorError> {
110 let syntax = |position: usize, found: String| CalculatorError::Syntax {
111 expression: expression.to_string(),
112 position,
113 found,
114 };
115 let chars: Vec<(usize, char)> = expression.char_indices().collect();
116 let mut tokens = Vec::new();
117 let mut i = 0;
118 while i < chars.len() {
119 let (position, c) = chars[i];
120 let next = chars.get(i + 1).map(|(_, c)| *c);
121 let (token, len) = match c {
122 c if c.is_whitespace() => {
123 i += 1;
124 continue;
125 }
126 '0'..='9' | '.' => {
127 let mut end = i;
128 while end < chars.len() && (chars[end].1.is_ascii_digit() || chars[end].1 == '.') {
129 end += 1;
130 }
131 if end < chars.len() && matches!(chars[end].1, 'e' | 'E') {
133 let mut exp = end + 1;
134 if exp < chars.len() && matches!(chars[exp].1, '+' | '-') {
135 exp += 1;
136 }
137 if exp < chars.len() && chars[exp].1.is_ascii_digit() {
138 while exp < chars.len() && chars[exp].1.is_ascii_digit() {
139 exp += 1;
140 }
141 end = exp;
142 }
143 }
144 let text: String = chars[i..end].iter().map(|(_, c)| c).collect();
145 let number = text
146 .parse()
147 .map_err(|_| syntax(position, format!("`{text}`")))?;
148 (Token::Number(number), end - i)
149 }
150 c if c.is_alphabetic() || c == '_' => {
151 let mut end = i;
152 while end < chars.len()
153 && (chars[end].1.is_alphanumeric() || matches!(chars[end].1, '_' | '.'))
154 {
155 end += 1;
156 }
157 let text: String = chars[i..end].iter().map(|(_, c)| c).collect();
158 let name = text.strip_prefix("math.").unwrap_or(&text).to_lowercase();
159 (Token::Name(name), end - i)
160 }
161 '*' if next == Some('*') => (Token::Op("^"), 2),
162 '/' if next == Some('/') => (Token::Op("//"), 2),
163 '+' => (Token::Op("+"), 1),
164 '-' | '−' => (Token::Op("-"), 1),
165 '*' | '×' => (Token::Op("*"), 1),
166 '/' | '÷' => (Token::Op("/"), 1),
167 '%' => (Token::Op("%"), 1),
168 '^' => (Token::Op("^"), 1),
169 '(' => (Token::Open, 1),
170 ')' => (Token::Close, 1),
171 ',' => (Token::Comma, 1),
172 other => return Err(syntax(position, format!("`{other}`"))),
173 };
174 tokens.push((position, token));
175 i += len;
176 }
177 Ok(tokens)
178}
179
180struct Parser<'a> {
181 expression: &'a str,
182 tokens: Vec<(usize, Token)>,
183 position: usize,
184 depth: usize,
185}
186
187impl Parser<'_> {
188 fn peek(&self) -> Option<&Token> {
189 self.tokens.get(self.position).map(|(_, t)| t)
190 }
191
192 fn eat_op(&mut self, ops: &[&'static str]) -> Option<&'static str> {
193 match self.peek() {
194 Some(Token::Op(op)) if ops.contains(op) => {
195 let op = *op;
196 self.position += 1;
197 Some(op)
198 }
199 _ => None,
200 }
201 }
202
203 fn unexpected(&self, position: usize, token: &Token) -> CalculatorError {
204 CalculatorError::Syntax {
205 expression: self.expression.to_string(),
206 position,
207 found: token.to_string(),
208 }
209 }
210
211 fn unexpected_here(&self) -> CalculatorError {
212 match self.tokens.get(self.position) {
213 Some((position, token)) => self.unexpected(*position, token),
214 None => CalculatorError::Syntax {
215 expression: self.expression.to_string(),
216 position: self.expression.len(),
217 found: "end of expression".to_string(),
218 },
219 }
220 }
221
222 fn expect(&mut self, token: Token) -> Result<(), CalculatorError> {
223 if self.peek() == Some(&token) {
224 self.position += 1;
225 Ok(())
226 } else {
227 Err(self.unexpected_here())
228 }
229 }
230
231 fn expression(&mut self) -> Result<f64, CalculatorError> {
233 self.depth += 1;
234 if self.depth > MAX_DEPTH {
235 return Err(CalculatorError::TooDeep);
236 }
237 let mut value = self.term()?;
238 while let Some(op) = self.eat_op(&["+", "-"]) {
239 let rhs = self.term()?;
240 value = if op == "+" { value + rhs } else { value - rhs };
241 }
242 self.depth -= 1;
243 Ok(value)
244 }
245
246 fn term(&mut self) -> Result<f64, CalculatorError> {
248 let mut value = self.unary()?;
249 while let Some(op) = self.eat_op(&["*", "/", "//", "%"]) {
250 let rhs = self.unary()?;
251 if op != "*" && rhs == 0.0 {
252 return Err(CalculatorError::DivisionByZero);
253 }
254 value = match op {
255 "*" => value * rhs,
256 "/" => value / rhs,
257 "//" => (value / rhs).floor(),
258 _ => value - rhs * (value / rhs).floor(),
260 };
261 }
262 Ok(value)
263 }
264
265 fn unary(&mut self) -> Result<f64, CalculatorError> {
267 self.depth += 1;
268 if self.depth > MAX_DEPTH {
269 return Err(CalculatorError::TooDeep);
270 }
271 let value = match self.eat_op(&["+", "-"]) {
272 Some("-") => -self.unary()?,
273 Some(_) => self.unary()?,
274 None => self.power()?,
275 };
276 self.depth -= 1;
277 Ok(value)
278 }
279
280 fn power(&mut self) -> Result<f64, CalculatorError> {
282 let base = self.primary()?;
283 if self.eat_op(&["^"]).is_some() {
284 let exponent = self.unary()?;
285 return Ok(base.powf(exponent));
286 }
287 Ok(base)
288 }
289
290 fn primary(&mut self) -> Result<f64, CalculatorError> {
292 let Some((_, token)) = self.tokens.get(self.position).cloned() else {
293 return Err(self.unexpected_here());
294 };
295 match token {
296 Token::Number(n) => {
297 self.position += 1;
298 Ok(n)
299 }
300 Token::Open => {
301 self.position += 1;
302 let value = self.expression()?;
303 self.expect(Token::Close)?;
304 Ok(value)
305 }
306 Token::Name(name) => {
307 self.position += 1;
308 if self.peek() == Some(&Token::Open) {
309 self.position += 1;
310 let mut args = Vec::new();
311 if self.peek() != Some(&Token::Close) {
312 args.push(self.expression()?);
313 while self.peek() == Some(&Token::Comma) {
314 self.position += 1;
315 args.push(self.expression()?);
316 }
317 }
318 self.expect(Token::Close)?;
319 call(&name, &args)
320 } else {
321 constant(&name)
322 }
323 }
324 _ => Err(self.unexpected_here()),
325 }
326 }
327}
328
329fn constant(name: &str) -> Result<f64, CalculatorError> {
330 match name {
331 "pi" => Ok(std::f64::consts::PI),
332 "e" => Ok(std::f64::consts::E),
333 "tau" => Ok(std::f64::consts::TAU),
334 _ => Err(CalculatorError::UnknownName(name.to_string())),
335 }
336}
337
338fn call(name: &str, args: &[f64]) -> Result<f64, CalculatorError> {
339 let arity = |expected: &'static str| CalculatorError::Arity {
340 function: name.to_string(),
341 expected,
342 got: args.len(),
343 };
344 let one = |f: fn(f64) -> f64| match args {
345 [x] => Ok(f(*x)),
346 _ => Err(arity("1")),
347 };
348 let two = |f: fn(f64, f64) -> f64| match args {
349 [x, y] => Ok(f(*x, *y)),
350 _ => Err(arity("2")),
351 };
352 match name {
353 "sqrt" => one(f64::sqrt),
354 "cbrt" => one(f64::cbrt),
355 "abs" => one(f64::abs),
356 "exp" => one(f64::exp),
357 "ln" => one(f64::ln),
358 "log" => match args {
359 [x] => Ok(x.ln()),
360 [x, base] => Ok(x.log(*base)),
361 _ => Err(arity("1 or 2")),
362 },
363 "log10" => one(f64::log10),
364 "log2" => one(f64::log2),
365 "sin" => one(f64::sin),
366 "cos" => one(f64::cos),
367 "tan" => one(f64::tan),
368 "asin" => one(f64::asin),
369 "acos" => one(f64::acos),
370 "atan" => one(f64::atan),
371 "atan2" => two(f64::atan2),
372 "sinh" => one(f64::sinh),
373 "cosh" => one(f64::cosh),
374 "tanh" => one(f64::tanh),
375 "floor" => one(f64::floor),
376 "ceil" => one(f64::ceil),
377 "round" => match args {
379 [x] => Ok(x.round()),
380 [x, digits] => {
381 let scale = 10f64.powi(*digits as i32);
382 Ok((x * scale).round() / scale)
383 }
384 _ => Err(arity("1 or 2")),
385 },
386 "trunc" => one(f64::trunc),
387 "pow" => two(f64::powf),
388 "hypot" => two(f64::hypot),
389 "min" | "max" if args.is_empty() => Err(arity("at least 1")),
390 "min" => Ok(args.iter().copied().fold(f64::INFINITY, f64::min)),
391 "max" => Ok(args.iter().copied().fold(f64::NEG_INFINITY, f64::max)),
392 "factorial" => match args {
393 [n] if *n >= 0.0 && n.fract() == 0.0 && *n <= 170.0 => {
394 Ok((1..=*n as u32).map(f64::from).product())
395 }
396 [_] => Err(CalculatorError::NotFinite),
397 _ => Err(arity("1")),
398 },
399 _ => Err(CalculatorError::UnknownName(name.to_string())),
400 }
401}
402
403#[derive(Debug, Clone, Default)]
409pub struct Calculator;
410
411impl Calculator {
412 pub fn new() -> Self {
413 Self
414 }
415}
416
417#[async_trait]
418impl Tool for Calculator {
419 fn name(&self) -> String {
420 "calculator".to_string()
421 }
422
423 fn description(&self) -> String {
424 "Evaluates a math expression and returns the result. Use it for any arithmetic. \
425 Supports + - * / % ^ (power), parentheses, the constants pi and e, and functions \
426 like sqrt, abs, ln, log10, sin, cos, round, min, max and factorial. \
427 Example input: (1234.5 * 3) / sqrt(2)"
428 .to_string()
429 }
430
431 fn parameters(&self) -> Value {
432 json!({
433 "type": "object",
434 "properties": {
435 "input": {
436 "type": "string",
437 "description": "The math expression to evaluate, e.g. (1234.5 * 3) / sqrt(2)"
438 }
439 },
440 "required": ["input"]
441 })
442 }
443
444 async fn run(&self, input: Value) -> Result<String, Box<dyn Error>> {
445 let expression = input
447 .as_str()
448 .map(str::to_string)
449 .unwrap_or_else(|| input.to_string());
450 let expression = expression.trim().trim_matches('`').trim();
451 Ok(format_number(evaluate_expression(expression)?))
452 }
453}
454
455#[cfg(test)]
456mod tests;