Skip to main content

mathlex_eval/
error.rs

1use thiserror::Error;
2
3/// Errors that occur during AST compilation.
4#[derive(Debug, Error)]
5pub enum CompileError {
6    #[error("unsupported expression variant '{variant}': {context}")]
7    UnsupportedExpression {
8        variant: &'static str,
9        context: String,
10    },
11
12    #[error("unresolved variable '{name}'")]
13    UnresolvedVariable { name: String },
14
15    #[error("{construct} bounds must be integer: {bound}")]
16    NonIntegerBounds {
17        construct: &'static str,
18        bound: String,
19    },
20
21    #[error("unknown function '{name}'")]
22    UnknownFunction { name: String },
23
24    #[error("function '{function}' expects {expected} args, got {got}")]
25    ArityMismatch {
26        function: String,
27        expected: usize,
28        got: usize,
29    },
30
31    #[error("division by zero during constant folding")]
32    DivisionByZero,
33
34    #[error("numeric overflow during constant folding: {context}")]
35    NumericOverflow { context: String },
36}
37
38/// Errors that occur during expression evaluation.
39#[derive(Debug, Error)]
40pub enum EvalError {
41    #[error("unknown argument '{name}'")]
42    UnknownArgument { name: String },
43
44    #[error("missing argument '{name}'")]
45    MissingArgument { name: String },
46
47    #[error("division by zero")]
48    DivisionByZero,
49
50    #[error("numeric overflow")]
51    NumericOverflow,
52
53    #[error("incompatible array shapes: {details}")]
54    ShapeMismatch { details: String },
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn compile_error_unsupported_expression_display() {
63        let err = CompileError::UnsupportedExpression {
64            variant: "Vector",
65            context: "vector evaluation deferred to v1.x".into(),
66        };
67        assert_eq!(
68            err.to_string(),
69            "unsupported expression variant 'Vector': vector evaluation deferred to v1.x"
70        );
71    }
72
73    #[test]
74    fn compile_error_unresolved_variable_display() {
75        let err = CompileError::UnresolvedVariable { name: "x".into() };
76        assert_eq!(err.to_string(), "unresolved variable 'x'");
77    }
78
79    #[test]
80    fn compile_error_non_integer_bounds_display() {
81        let err = CompileError::NonIntegerBounds {
82            construct: "sum",
83            bound: "x + 1".into(),
84        };
85        assert_eq!(err.to_string(), "sum bounds must be integer: x + 1");
86    }
87
88    #[test]
89    fn compile_error_unknown_function_display() {
90        let err = CompileError::UnknownFunction {
91            name: "foobar".into(),
92        };
93        assert_eq!(err.to_string(), "unknown function 'foobar'");
94    }
95
96    #[test]
97    fn compile_error_arity_mismatch_display() {
98        let err = CompileError::ArityMismatch {
99            function: "sin".into(),
100            expected: 1,
101            got: 2,
102        };
103        assert_eq!(err.to_string(), "function 'sin' expects 1 args, got 2");
104    }
105
106    #[test]
107    fn compile_error_division_by_zero_display() {
108        let err = CompileError::DivisionByZero;
109        assert_eq!(err.to_string(), "division by zero during constant folding");
110    }
111
112    #[test]
113    fn compile_error_numeric_overflow_display() {
114        let err = CompileError::NumericOverflow {
115            context: "2^1024".into(),
116        };
117        assert_eq!(
118            err.to_string(),
119            "numeric overflow during constant folding: 2^1024"
120        );
121    }
122
123    #[test]
124    fn eval_error_unknown_argument_display() {
125        let err = EvalError::UnknownArgument { name: "z".into() };
126        assert_eq!(err.to_string(), "unknown argument 'z'");
127    }
128
129    #[test]
130    fn eval_error_missing_argument_display() {
131        let err = EvalError::MissingArgument { name: "x".into() };
132        assert_eq!(err.to_string(), "missing argument 'x'");
133    }
134
135    #[test]
136    fn eval_error_division_by_zero_display() {
137        let err = EvalError::DivisionByZero;
138        assert_eq!(err.to_string(), "division by zero");
139    }
140
141    #[test]
142    fn eval_error_numeric_overflow_display() {
143        let err = EvalError::NumericOverflow;
144        assert_eq!(err.to_string(), "numeric overflow");
145    }
146
147    #[test]
148    fn eval_error_shape_mismatch_display() {
149        let err = EvalError::ShapeMismatch {
150            details: "[3] vs [4]".into(),
151        };
152        assert_eq!(err.to_string(), "incompatible array shapes: [3] vs [4]");
153    }
154
155    #[test]
156    fn errors_implement_std_error() {
157        fn assert_error<E: std::error::Error>() {}
158        assert_error::<CompileError>();
159        assert_error::<EvalError>();
160    }
161}