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, Default)]
15pub enum MathLanguage {
16 #[default]
17 LaTeX,
18 Wolfram,
19 Simple,
20 Human,
21 Json,
22 Markdown,
23}
24
25impl MathLanguage {
26 pub fn as_str(self) -> &'static str {
28 match self {
29 Self::LaTeX => "latex",
30 Self::Wolfram => "wolfram",
31 Self::Simple => "human", Self::Human => "human",
33 Self::Json => "json",
34 Self::Markdown => "markdown",
35 }
36 }
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub enum FormattingError {
42 RecursionLimitExceeded { depth: usize, limit: usize },
44 UnsupportedExpression {
46 expr_type: String,
47 target_format: MathLanguage,
48 },
49 TooManyTerms { count: usize, limit: usize },
51 InvalidMathConstruct { reason: String },
53 MemoryLimitExceeded,
55 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
93pub trait FormattingContext: Default + Clone {
95 fn target_format(&self) -> MathLanguage {
96 MathLanguage::default()
97 }
98}
99
100pub 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 serde_json::to_string_pretty(self).map_err(|e| {
119 FormattingError::SerializationError {
120 message: e.to_string(),
121 }
122 })
123 }
124 MathLanguage::Markdown => {
125 let latex_context = latex::LaTeXContext::default();
127 let latex_result = self.to_latex(latex_context)?;
128 Ok(format!("$${}$$", latex_result))
129 }
130 _ => {
132 let latex_context = latex::LaTeXContext::default();
133 self.to_latex(latex_context)
134 }
135 }
136 }
137}
138
139impl Expression {
141 pub fn format(&self) -> Result<String, FormattingError> {
156 let latex_context = latex::LaTeXContext::default();
157 self.to_latex(latex_context)
158 }
159
160 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 _ => {
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 #[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 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 let result = x_expr.format();
228 assert!(result.is_ok());
229
230 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 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 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 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}