calc/
lib.rs

1//! Calculator library for evaluating freehand mathematical expressions.
2//!
3//! The general workflow goes like this:
4//!
5//! - Create a [`Context`]: a reusable type which contains expression history.
6//!
7//! - Parse an [`ast::Expr`] with [`ast::parser::ExprParser`].
8//! - Evaluate that expression with [`Context::evaluate`].
9//!
10//! You can freely modify the parsed expression; the types in [`ast`] are all public.
11//!
12//! To enable calculation based on your custom numeric type, just impl [`types::Calcable`] for your type.
13
14pub mod ast;
15mod value;
16
17use ast::{
18    parser::{AnnotatedExprParser, ExprParser},
19    AnnotatedError, ParseError as UserParseError,
20};
21use lalrpop_util::ParseError;
22pub(crate) use value::Result;
23pub use value::{ArithmeticError, Error as ValueError, ParseValueError, Value};
24
25/// Calculation context.
26///
27/// Stores a history of calculated values, so that the history lookups (`@`) work properly.
28/// Also reifies the numeric type backing the calculations.
29#[derive(Default)]
30pub struct Context {
31    pub history: Vec<Value>,
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum Error {
36    #[error("Parsing")]
37    Parse(#[from] ParseError<usize, &'static str, UserParseError>),
38    #[error("Evaluating")]
39    Eval(#[from] ValueError),
40    #[error("Formatting")]
41    Format(#[source] num_runtime_fmt::Error),
42}
43
44impl From<AnnotatedError> for Error {
45    fn from(err: AnnotatedError) -> Self {
46        match err {
47            AnnotatedError::Calculation(err) => Self::Eval(err),
48            AnnotatedError::Format(err) => Self::Format(err),
49        }
50    }
51}
52
53impl Context {
54    /// Evaluate an expression in this context.
55    ///
56    /// This both returns the calculated value and stores a copy in the context's history.
57    pub fn evaluate(&mut self, expr: &str) -> Result<Value, Error> {
58        let parser = ExprParser::new();
59        let expr = parser.parse(expr).map_err(|err| err.map_token(|_| ""))?;
60        let result = expr.evaluate(self).map_err(Error::Eval)?;
61        self.history.push(result);
62        Ok(result)
63    }
64}
65
66impl Context {
67    /// Evaluate an annotated expression in this context.
68    ///
69    /// Annotations can include output formatting directives. Therefore, the return value
70    /// is a formatted `String`.
71    ///
72    /// This also stores a copy in the context's history.
73    pub fn evaluate_annotated(&mut self, expr: &str) -> Result<String, Error> {
74        let parser = AnnotatedExprParser::new();
75        let expr = parser.parse(expr).map_err(|err| err.map_token(|_| ""))?;
76        let (result, formatted) = expr.evaluate(self)?;
77        self.history.push(result);
78        Ok(formatted)
79    }
80}