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