mathhook_core/
formatter.rs1pub 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#[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 pub fn as_str(self) -> &'static str {
33 match self {
34 Self::LaTeX => "latex",
35 Self::Wolfram => "wolfram",
36 Self::Simple => "human", Self::Human => "human",
38 Self::Json => "json",
39 Self::Markdown => "markdown",
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub enum FormattingError {
47 RecursionLimitExceeded { depth: usize, limit: usize },
49 UnsupportedExpression {
51 expr_type: String,
52 target_format: MathLanguage,
53 },
54 TooManyTerms { count: usize, limit: usize },
56 InvalidMathConstruct { reason: String },
58 MemoryLimitExceeded,
60 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
98pub trait FormattingContext: Default + Clone {
100 fn target_format(&self) -> MathLanguage {
101 MathLanguage::default()
102 }
103}
104
105pub 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 serde_json::to_string_pretty(self).map_err(|e| {
124 FormattingError::SerializationError {
125 message: e.to_string(),
126 }
127 })
128 }
129 MathLanguage::Markdown => {
130 let latex_context = latex::LaTeXContext::default();
132 let latex_result = self.to_latex(latex_context)?;
133 Ok(format!("$${}$$", latex_result))
134 }
135 _ => {
137 let latex_context = latex::LaTeXContext::default();
138 self.to_latex(latex_context)
139 }
140 }
141 }
142}
143
144impl Expression {
146 pub fn format(&self) -> Result<String, FormattingError> {
161 let latex_context = latex::LaTeXContext::default();
162 self.to_latex(latex_context)
163 }
164
165 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 _ => {
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 #[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 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 let result = x_expr.format();
233 assert!(result.is_ok());
234
235 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 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 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 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}