calc/
ast.rs

1use lalrpop_util::lalrpop_mod;
2use num_runtime_fmt::NumFmt;
3
4use crate::{
5    types::{Calcable, CalcableError},
6    Context,
7};
8
9// no point getting style warnings for generated code
10lalrpop_mod!(#[allow(clippy::all)] pub parser);
11
12/// Error encountered while parsing an expression
13#[derive(Debug, thiserror::Error)]
14pub enum ParseError {
15    #[error("index must fit into usize")]
16    Index(#[source] std::num::ParseIntError),
17    #[error("failed to parse format string")]
18    Format(#[from] num_runtime_fmt::parse::Error),
19}
20
21/// A prefix operator.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum PrefixOperator {
24    Negation,
25    Not,
26}
27
28impl PrefixOperator {
29    fn evaluate<N: Calcable>(&self, operand: N) -> Result<N, <N as Calcable>::Err> {
30        match self {
31            Self::Negation => operand.neg().ok_or_else(|| N::Err::unimplemented("-")),
32            Self::Not => operand.not().ok_or_else(|| N::Err::unimplemented("!")),
33        }
34    }
35}
36
37/// An infix operator.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum InfixOperator {
40    Add,
41    Sub,
42    Mul,
43    Div,
44    TruncDiv,
45    Pow,
46    Rem,
47    Lshift,
48    Rshift,
49    RotateL,
50    RotateR,
51    BitAnd,
52    BitOr,
53    BitXor,
54}
55
56impl InfixOperator {
57    fn evaluate<N: Calcable>(&self, left: N, right: N) -> Result<N, <N as Calcable>::Err> {
58        match self {
59            Self::Add => <N as Calcable>::add(left, right),
60            Self::Sub => <N as Calcable>::sub(left, right),
61            Self::Mul => <N as Calcable>::mul(left, right),
62            Self::Div => <N as Calcable>::div(left, right),
63            Self::TruncDiv => left.trunc_div(right),
64            Self::Pow => left.pow(right),
65            Self::Rem => left.rem(right),
66            Self::Lshift => left.shl(right),
67            Self::Rshift => left.shr(right),
68            Self::RotateL => left.rotate_left(right),
69            Self::RotateR => left.rotate_right(right),
70            Self::BitAnd => left
71                .bit_and(right)
72                .ok_or_else(|| N::Err::unimplemented("&")),
73            Self::BitOr => left.bit_or(right).ok_or_else(|| N::Err::unimplemented("|")),
74            Self::BitXor => left
75                .bit_xor(right)
76                .ok_or_else(|| N::Err::unimplemented("^")),
77        }
78    }
79}
80
81/// A function name.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Function {
84    Abs,
85    Ceil,
86    Floor,
87    Round,
88    Sin,
89    Cos,
90    Tan,
91    Sinh,
92    Cosh,
93    Tanh,
94    Asin,
95    Acos,
96    Atan,
97    Asinh,
98    Acosh,
99    Atanh,
100    Rad,
101    Deg,
102    Sqrt,
103    Cbrt,
104    Log,
105    Lg,
106    Ln,
107    Exp,
108}
109
110impl Function {
111    fn evaluate<N: Calcable>(&self, operand: N) -> Result<N, <N as Calcable>::Err> {
112        let (result, symbol) = match self {
113            Self::Abs => (operand.abs(), "abs"),
114            Self::Ceil => (operand.ceil(), "ceil"),
115            Self::Floor => (operand.floor(), "floor"),
116            Self::Round => (operand.round(), "round"),
117            Self::Sin => (operand.sin(), "sin"),
118            Self::Cos => (operand.cos(), "cos"),
119            Self::Tan => (operand.tan(), "tan"),
120            Self::Sinh => (operand.sinh(), "sinh"),
121            Self::Cosh => (operand.cosh(), "cosh"),
122            Self::Tanh => (operand.tanh(), "tanh"),
123            Self::Asin => (operand.asin(), "asin"),
124            Self::Acos => (operand.acos(), "acos"),
125            Self::Atan => (operand.atan(), "atan"),
126            Self::Asinh => (operand.asinh(), "asinh"),
127            Self::Acosh => (operand.acosh(), "acosh"),
128            Self::Atanh => (operand.atanh(), "atanh"),
129            Self::Rad => (operand.rad(), "rad"),
130            Self::Deg => (operand.deg(), "deg"),
131            Self::Sqrt => (operand.sqrt(), "sqrt"),
132            Self::Cbrt => (operand.cbrt(), "cbrt"),
133            Self::Log => (operand.log(), "log"),
134            Self::Lg => (operand.lg(), "lg"),
135            Self::Ln => (operand.ln(), "ln"),
136            Self::Exp => (operand.exp(), "exp"),
137        };
138        result.ok_or_else(|| N::Err::unimplemented(symbol))
139    }
140}
141
142/// A constant.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum Constant {
145    E,
146    Pi,
147}
148
149/// What kind of history lookup is desired.
150///
151/// Absolute history lookups begin at 0 and increment.
152/// Relative history lookups count backwards from the current expression.
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum HistoryIndexKind {
155    Relative,
156    Absolute,
157}
158
159/// A term in the expression.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum Term<'input> {
162    Literal(&'input str),
163    HexLiteral(&'input str),
164    OctLiteral(&'input str),
165    BinLiteral(&'input str),
166    Constant(Constant),
167    History(HistoryIndexKind, usize),
168}
169
170impl<'input> Term<'input> {
171    fn evaluate<N: Calcable>(&self, ctx: &Context<N>) -> Result<N, <N as Calcable>::Err> {
172        match self {
173            Self::Literal(s) => N::parse_decimal(s),
174            Self::HexLiteral(s) => N::parse_hex(s),
175            Self::OctLiteral(s) => N::parse_octal(s),
176            Self::BinLiteral(s) => N::parse_binary(s),
177            Self::Constant(Constant::E) => N::E.ok_or_else(|| N::Err::unimplemented("e")),
178            Self::Constant(Constant::Pi) => N::PI.ok_or_else(|| N::Err::unimplemented("pi")),
179            Self::History(kind, idx) => {
180                let real_idx = match kind {
181                    HistoryIndexKind::Absolute => *idx,
182                    HistoryIndexKind::Relative => {
183                        ctx.history.len().checked_sub(*idx).ok_or_else(|| {
184                            N::Err::history_out_of_bounds(*kind, *idx, ctx.history.len())
185                        })?
186                    }
187                };
188                match ctx.history.get(real_idx) {
189                    Some(n) => Ok(n.clone()),
190                    None => Err(N::Err::history_out_of_bounds(
191                        *kind,
192                        *idx,
193                        ctx.history.len(),
194                    )),
195                }
196            }
197        }
198    }
199}
200
201/// An expression or subexpression
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub enum Expr<'input> {
204    Term(Term<'input>),
205    Prefix(PrefixOperator, Box<Expr<'input>>),
206    Infix(Box<Expr<'input>>, InfixOperator, Box<Expr<'input>>),
207    Func(Function, Box<Expr<'input>>),
208    Group(Box<Expr<'input>>),
209}
210
211impl<'input> Expr<'input> {
212    /// Evaluate this expression into its mathematical result.
213    pub(crate) fn evaluate<N: Calcable>(
214        &self,
215        ctx: &Context<N>,
216    ) -> Result<N, <N as Calcable>::Err> {
217        match self {
218            Self::Term(term) => term.evaluate(ctx),
219            Self::Prefix(prefix, expr) => prefix.evaluate(expr.evaluate(ctx)?),
220            Self::Infix(left, infix, right) => {
221                infix.evaluate(left.evaluate(ctx)?, right.evaluate(ctx)?)
222            }
223            Self::Func(func, expr) => func.evaluate(expr.evaluate(ctx)?),
224            Self::Group(expr) => expr.evaluate(ctx),
225        }
226    }
227}
228
229/// Error produced by [`AnnotatedExpr`].
230#[derive(Debug, thiserror::Error)]
231pub enum AnnotatedError<N>
232where
233    N: std::fmt::Debug + Calcable,
234    <N as Calcable>::Err: 'static,
235{
236    #[error(transparent)]
237    Calculation(<N as Calcable>::Err),
238    #[error("failed to render calculation result in desired format")]
239    Format(#[from] num_runtime_fmt::Error),
240}
241
242/// An expression annotated with some metadata.
243pub struct AnnotatedExpr<'input> {
244    pub expr: Expr<'input>,
245    pub format: NumFmt,
246}
247
248impl<'input> AnnotatedExpr<'input> {
249    /// Evaluate this expression into its mathematical result.
250    ///
251    /// Return the result as a bare type and also formatted according to the
252    /// requested format string.
253    pub fn evaluate<N>(&self, ctx: &Context<N>) -> Result<(N, String), AnnotatedError<N>>
254    where
255        N: std::fmt::Debug + Calcable + num_runtime_fmt::Numeric,
256        <N as Calcable>::Err: 'static,
257    {
258        let value = self
259            .expr
260            .evaluate(ctx)
261            .map_err(AnnotatedError::Calculation)?;
262        let formatted = self.format.fmt(value.clone())?;
263        Ok((value, formatted))
264    }
265}