precise_calc/
lib.rs

1//! This is a crate for parsing and evaluating math expressions.
2
3#![warn(missing_docs)]
4#![warn(rustdoc::missing_crate_level_docs)]
5
6use astro_float::{BigFloat, RoundingMode};
7use thiserror::Error;
8
9pub mod ast;
10mod builtins;
11pub mod context;
12pub mod eval;
13pub mod formatting;
14pub mod parser;
15
16/// The type of numbers used in expressions.
17pub type Number = BigFloat;
18
19/// Commonly used result of either a [Number] or [CalcError].
20pub type CalcResult = Result<Number, CalcError>;
21
22/// Precision of floating point numbers
23pub const PREC: usize = 128;
24
25/// The precision of floating point numbers when converted to base 10. Calculated by `log(2^PREC)`.
26pub const BASE_10_PREC: usize = 38; // log(2^PREC)
27
28/// The rounding mode of floating point numbers.
29pub const RM: RoundingMode = RoundingMode::ToEven;
30
31/// The error type returned when functions in this crate go wrong.
32#[derive(Error, Debug, Clone)]
33pub enum CalcError {
34    /// Symbol is not in context.
35    #[error("ERROR: name not found: {0}")]
36    NameNotFound(String),
37
38    /// Use tried to assign a value to a name that already has a value assigned to it.
39    #[error("ERROR: name already bound: {0}")]
40    NameAlreadyBound(String),
41
42    /// Function called with incorrect number of arguments. `IncorrectArity(expected, found)`.
43    #[error("ERROR: expected {0} arguments, found {1}")]
44    IncorrectArity(usize, usize),
45
46    /// There was an error when parsing a number.
47    #[error("ERROR: number parsing error")]
48    ParseNum,
49
50    /// There was a parsing error.
51    #[error("ERROR: parsing error")]
52    ParseError,
53
54    /// An error with IO like stdin not working.
55    #[error("ERROR: IO error")]
56    IOError,
57
58    /// Error in `astro_float`.
59    #[error("ERROR: unknown error")]
60    Unknown,
61}
62
63#[cfg(test)]
64mod tests {
65    use super::ast::*;
66    use super::context::Context;
67    use super::eval::*;
68    use crate::PREC;
69
70    use astro_float::BigFloat;
71
72    #[test]
73    fn test_atom_eval() {
74        let num = BigFloat::from_i32(123, PREC);
75
76        let mut ctx = Context::new();
77        ctx.bind_value("a".to_string(), num.clone())
78            .expect("failed to bind value");
79
80        let sym_atom = Atom::Symbol("a".to_string());
81        let res = eval_atom(&sym_atom, &ctx).expect("failed to evaluate symbol atom");
82        assert_eq!(num, res);
83
84        let num_atom = Atom::Num(num.clone());
85        let res = eval_atom(&num_atom, &ctx).expect("failed to evaluate number atom");
86        assert_eq!(num, res);
87    }
88
89    #[test]
90    fn test_expr_eval() {
91        let num = BigFloat::from_i32(123, PREC);
92        let num2 = BigFloat::from_i32(-123, PREC);
93        let num3 = BigFloat::from_i32(10, PREC);
94        let num4 = BigFloat::from_i32(20, PREC);
95        let num5 = BigFloat::from_i32(30, PREC);
96
97        let ctx = Context::new();
98
99        let expr = Expr::UnaryExpr {
100            op: UnaryOp::Negate,
101            data: Box::new(Expr::AtomExpr(Atom::Num(num))),
102        };
103        let res = eval_expr(&expr, &ctx).unwrap();
104        assert_eq!(res, num2);
105
106        let lhs = Expr::AtomExpr(Atom::Num(num3));
107        let rhs = Expr::AtomExpr(Atom::Num(num4));
108        let add_expr = Expr::BinaryExpr {
109            lhs: Box::new(lhs),
110            rhs: Box::new(rhs),
111            op: BinaryOp::Plus,
112        };
113        let res = eval_expr(&add_expr, &ctx).unwrap();
114        assert_eq!(res, num5);
115    }
116
117    #[test]
118    fn test_function_call() {
119        let num1 = BigFloat::from_i32(10, PREC);
120        let num2 = BigFloat::from_i32(20, PREC);
121        let num3 = BigFloat::from_i32(30, PREC);
122
123        let mut ctx = Context::new();
124
125        let func = UserFunc::new(
126            vec!["x".to_string(), "y".to_string()],
127            Expr::BinaryExpr {
128                lhs: Box::new(Expr::AtomExpr(Atom::Symbol("x".to_string()))),
129                rhs: Box::new(Expr::AtomExpr(Atom::Symbol("y".to_string()))),
130                op: BinaryOp::Plus,
131            },
132        );
133        ctx.bind_fn("f".to_string(), func).unwrap();
134
135        let func_call = Expr::FunctionCall {
136            function: "f".to_string(),
137            args: vec![
138                Expr::AtomExpr(Atom::Num(num1)),
139                Expr::AtomExpr(Atom::Num(num2)),
140            ],
141        };
142
143        let res = eval_expr(&func_call, &ctx).unwrap();
144
145        assert_eq!(res, num3);
146    }
147}