1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! Calculator library for evaluating freehand mathematical expressions.
//!
//! The general workflow goes like this:
//!
//! - Create a [`Context`]: a reusable type which contains expression history.
//!
//!   This type is parametrized by the numeric type which all calculations will use.
//!
//! - Parse an [`ast::Expr`] with [`ast::parser::ExprParser`].
//! - Evaluate that expression with [`Context::evaluate`].
//!
//! You can freely modify the parsed expression; the types in [`ast`] are all public.
//!
//! To enable calculation based on your custom numeric type, just impl [`types::Calcable`] for your type.

pub mod ast;
pub mod types;

use ast::{
    parser::{AnnotatedExprParser, ExprParser},
    AnnotatedError, ParseError as UserParseError,
};
use lalrpop_util::ParseError;
use num_runtime_fmt::Numeric;
use types::Calcable;

/// Calculation context.
///
/// Stores a history of calculated values, so that the history lookups (`@`) work properly.
/// Also reifies the numeric type backing the calculations.
#[derive(Default)]
pub struct Context<N> {
    pub history: Vec<N>,
}

#[derive(Debug, thiserror::Error)]
pub enum Error<N>
where
    N: std::fmt::Debug + Calcable,
    <N as Calcable>::Err: 'static,
{
    #[error("Parsing")]
    Parse(#[from] ParseError<usize, &'static str, UserParseError>),
    #[error("Evaluating")]
    Eval(#[source] <N as Calcable>::Err),
    #[error("Formatting")]
    Format(#[source] num_runtime_fmt::Error),
}

impl<N> From<AnnotatedError<N>> for Error<N>
where
    N: std::fmt::Debug + Calcable,
    <N as Calcable>::Err: 'static,
{
    fn from(err: AnnotatedError<N>) -> Self {
        match err {
            AnnotatedError::Calculation(err) => Self::Eval(err),
            AnnotatedError::Format(err) => Self::Format(err),
        }
    }
}

impl<N> Context<N>
where
    N: std::fmt::Debug + Calcable,
    <N as Calcable>::Err: 'static,
{
    /// Evaluate an expression in this context.
    ///
    /// This both returns the calculated value and stores a copy in the context's history.
    pub fn evaluate(&mut self, expr: &str) -> Result<N, Error<N>> {
        let parser = ExprParser::new();
        let expr = parser.parse(expr).map_err(|err| err.map_token(|_| ""))?;
        let result = expr.evaluate(self).map_err(Error::Eval)?;
        self.history.push(result.clone());
        Ok(result)
    }
}

impl<N> Context<N>
where
    N: std::fmt::Debug + Calcable + Numeric,
    <N as Calcable>::Err: 'static,
{
    /// Evaluate an annotated expression in this context.
    ///
    /// Annotations can include output formatting directives. Therefore, the return value
    /// is a formatted `String`.
    ///
    /// This also stores a copy in the context's history.
    pub fn evaluate_annotated(&mut self, expr: &str) -> Result<String, Error<N>> {
        let parser = AnnotatedExprParser::new();
        let expr = parser.parse(expr).map_err(|err| err.map_token(|_| ""))?;
        let (result, formatted) = expr.evaluate(self)?;
        self.history.push(result);
        Ok(formatted)
    }
}