mathhook_core/
formatter.rs

1//! Formatting traits for mathematical expressions
2
3pub mod latex;
4pub mod simple;
5pub mod wolfram;
6
7pub use latex::LaTeXFormatter;
8pub use simple::SimpleFormatter;
9pub use wolfram::WolframFormatter;
10
11use crate::core::Expression;
12use std::fmt;
13/// Mathematical language/format for expressions
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum MathLanguage {
16    #[default]
17    LaTeX,
18    Wolfram,
19    Simple,
20    Human,
21    Json,
22    Markdown,
23}
24
25impl MathLanguage {
26    /// Convert to format string for conditional formatting
27    pub fn as_str(self) -> &'static str {
28        match self {
29            Self::LaTeX => "latex",
30            Self::Wolfram => "wolfram",
31            Self::Simple => "human", // Simple maps to human in the format! macro
32            Self::Human => "human",
33            Self::Json => "json",
34            Self::Markdown => "markdown",
35        }
36    }
37}
38
39/// Structured error type for formatting operations
40#[derive(Debug, Clone, PartialEq)]
41pub enum FormattingError {
42    /// Recursion depth limit exceeded during formatting
43    RecursionLimitExceeded { depth: usize, limit: usize },
44    /// Expression type not supported for target format
45    UnsupportedExpression {
46        expr_type: String,
47        target_format: MathLanguage,
48    },
49    /// Too many terms in operation (performance limit)
50    TooManyTerms { count: usize, limit: usize },
51    /// Invalid mathematical construct
52    InvalidMathConstruct { reason: String },
53    /// Memory limit exceeded
54    MemoryLimitExceeded,
55    /// Serialization error (for JSON format)
56    SerializationError { message: String },
57}
58
59impl fmt::Display for FormattingError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            Self::RecursionLimitExceeded { depth, limit } => {
63                write!(f, "Recursion limit exceeded: {} > {}", depth, limit)
64            }
65            Self::UnsupportedExpression {
66                expr_type,
67                target_format,
68            } => {
69                write!(
70                    f,
71                    "Unsupported expression type '{}' for format {:?}",
72                    expr_type, target_format
73                )
74            }
75            Self::TooManyTerms { count, limit } => {
76                write!(f, "Too many terms: {} > {}", count, limit)
77            }
78            Self::InvalidMathConstruct { reason } => {
79                write!(f, "Invalid mathematical construct: {}", reason)
80            }
81            Self::MemoryLimitExceeded => {
82                write!(f, "Memory limit exceeded during formatting")
83            }
84            Self::SerializationError { message } => {
85                write!(f, "Serialization error: {}", message)
86            }
87        }
88    }
89}
90
91impl std::error::Error for FormattingError {}
92
93/// Context for formatting operations
94pub trait FormattingContext: Default + Clone {
95    fn target_format(&self) -> MathLanguage {
96        MathLanguage::default()
97    }
98}
99
100/// Base trait for all formatters
101pub trait ExpressionFormatter<C: FormattingContext> {
102    fn format(&self, context: &C) -> Result<String, FormattingError>;
103}
104
105impl<C: FormattingContext> ExpressionFormatter<C> for Expression {
106    fn format(&self, context: &C) -> Result<String, FormattingError> {
107        match context.target_format() {
108            MathLanguage::Simple | MathLanguage::Human => {
109                let simple_context = simple::SimpleContext::default();
110                self.to_simple(&simple_context)
111            }
112            MathLanguage::Wolfram => {
113                let wolfram_context = wolfram::WolframContext::default();
114                self.to_wolfram(&wolfram_context)
115            }
116            MathLanguage::Json => {
117                // Use serde_json for JSON formatting
118                serde_json::to_string_pretty(self).map_err(|e| {
119                    FormattingError::SerializationError {
120                        message: e.to_string(),
121                    }
122                })
123            }
124            MathLanguage::Markdown => {
125                // Use LaTeX formatting wrapped in markdown math blocks
126                let latex_context = latex::LaTeXContext::default();
127                let latex_result = self.to_latex(latex_context)?;
128                Ok(format!("$${}$$", latex_result))
129            }
130            // Default to LaTeX for all other cases including MathLanguage::LaTeX
131            _ => {
132                let latex_context = latex::LaTeXContext::default();
133                self.to_latex(latex_context)
134            }
135        }
136    }
137}
138
139/// Convenient formatting methods for Expression without requiring context
140impl Expression {
141    /// Format expression using default LaTeX formatting
142    ///
143    /// This is the most convenient way to format expressions when you don't need
144    /// specific formatting options. Always uses LaTeX format.
145    ///
146    /// # Examples
147    /// ```rust
148    /// use mathhook_core::core::Expression;
149    /// use mathhook_core::{expr};
150    ///
151    /// let x_expr = expr!(x);
152    /// let formatted = x_expr.format().unwrap();
153    /// // Returns LaTeX formatted string
154    /// ```
155    pub fn format(&self) -> Result<String, FormattingError> {
156        let latex_context = latex::LaTeXContext::default();
157        self.to_latex(latex_context)
158    }
159
160    /// Format expression with specific language/format
161    ///
162    /// # Examples
163    /// ```rust
164    /// use mathhook_core::core::Expression;
165    /// use mathhook_core::formatter::MathLanguage;
166    /// use mathhook_core::expr;
167    ///
168    /// let x_expr = expr!(x);
169    /// let latex = x_expr.format_as(MathLanguage::LaTeX).unwrap();
170    /// let simple = x_expr.format_as(MathLanguage::Simple).unwrap();
171    /// let wolfram = x_expr.format_as(MathLanguage::Wolfram).unwrap();
172    /// ```
173    pub fn format_as(&self, language: MathLanguage) -> Result<String, FormattingError> {
174        match language {
175            MathLanguage::Simple | MathLanguage::Human => {
176                let simple_context = simple::SimpleContext::default();
177                self.to_simple(&simple_context)
178            }
179            MathLanguage::Wolfram => {
180                let wolfram_context = wolfram::WolframContext::default();
181                self.to_wolfram(&wolfram_context)
182            }
183            MathLanguage::Json => serde_json::to_string_pretty(self).map_err(|e| {
184                FormattingError::SerializationError {
185                    message: e.to_string(),
186                }
187            }),
188            MathLanguage::Markdown => {
189                let latex_context = latex::LaTeXContext::default();
190                let latex_result = self.to_latex(latex_context)?;
191                Ok(format!("$${}$$", latex_result))
192            }
193            // Default to LaTeX
194            _ => {
195                let latex_context = latex::LaTeXContext::default();
196                self.to_latex(latex_context)
197            }
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::core::Expression;
206    use crate::expr;
207
208    /// Test context that defaults to LaTeX
209    #[derive(Debug, Default, Clone)]
210    struct TestContext;
211
212    impl FormattingContext for TestContext {}
213
214    #[test]
215    fn test_format_defaults_to_latex() {
216        let x_expr = expr!(x);
217        // Should use LaTeX formatting by default
218        let result = ExpressionFormatter::format(&x_expr, &TestContext);
219        assert!(result.is_ok());
220    }
221
222    #[test]
223    fn test_format_without_context() {
224        let x_expr = expr!(x);
225
226        // Should work without providing context (defaults to LaTeX)
227        let result = x_expr.format();
228        assert!(result.is_ok());
229
230        // Test format_as method
231        let latex_result = x_expr.format_as(MathLanguage::LaTeX);
232        assert!(latex_result.is_ok());
233
234        let simple_result = x_expr.format_as(MathLanguage::Simple);
235        assert!(simple_result.is_ok());
236    }
237
238    #[test]
239    fn test_comprehensive_formatting() {
240        use crate::core::expression::RelationType;
241
242        // Test interval formatting
243        let interval = Expression::interval(expr!(0), expr!(10), true, false);
244
245        let latex_interval = interval.format().unwrap();
246        assert!(latex_interval.contains("[0"));
247        assert!(latex_interval.contains("10)"));
248
249        let simple_interval = interval.format_as(MathLanguage::Simple).unwrap();
250        assert!(simple_interval.contains("[0"));
251        assert!(simple_interval.contains("10)"));
252
253        // Test relation formatting
254        let relation = Expression::relation(expr!(x), expr!(5), RelationType::Greater);
255
256        let latex_relation = relation.format().unwrap();
257        assert!(latex_relation.contains("x"));
258        assert!(latex_relation.contains("5"));
259
260        let simple_relation = relation.format_as(MathLanguage::Simple).unwrap();
261        assert!(simple_relation.contains("x > 5"));
262
263        // Test piecewise formatting
264        let piecewise = Expression::piecewise(
265            vec![(expr!(x), expr!(1)), (expr!(y), expr!(2))],
266            Some(expr!(0)),
267        );
268
269        let latex_piecewise = piecewise.format().unwrap();
270        assert!(latex_piecewise.contains("\\begin{cases}"));
271
272        let simple_piecewise = piecewise.format_as(MathLanguage::Simple).unwrap();
273        assert!(simple_piecewise.contains("if"));
274        assert!(simple_piecewise.contains("otherwise"));
275    }
276}