mathhook_core/formatter/
simple.rs

1use super::{FormattingContext, FormattingError};
2use crate::core::expression::smart_display::SmartDisplayFormatter;
3use crate::core::expression::RelationType;
4use crate::core::{Expression, Number};
5
6const MAX_RECURSION_DEPTH: usize = 1000;
7const MAX_TERMS_PER_OPERATION: usize = 10000;
8
9/// Simple formatting context
10#[derive(Debug, Default, Clone)]
11pub struct SimpleContext {
12    /// Whether to use parentheses around negative numbers
13    pub parenthesize_negatives: bool,
14    /// Whether to use implicit multiplication (2x vs 2*x)
15    pub implicit_multiplication: bool,
16    /// Maximum precision for floating point numbers
17    pub float_precision: Option<usize>,
18    /// Whether to use Unicode symbols (× instead of *)
19    pub use_unicode: bool,
20}
21
22impl FormattingContext for SimpleContext {}
23
24/// Format the expression to Simple
25pub trait SimpleFormatter {
26    /// Format an Expression as simple mathematical notation
27    ///
28    /// Converts mathematical expressions into clean, readable text format
29    /// without LaTeX commands or complex markup. The output can be customized
30    /// using the provided context.
31    ///
32    /// # Arguments
33    /// * `context` - Formatting configuration controlling output style
34    ///
35    /// # Context Options
36    /// * `float_precision` - Number of decimal places for floating point numbers
37    /// * `use_unicode` - Whether to use Unicode symbols (× instead of *)
38    /// * `parenthesize_negatives` - Whether to wrap negative numbers in parentheses
39    /// * `implicit_multiplication` - Whether to use implicit multiplication (2x vs 2*x)
40    ///
41    /// # Examples
42    /// ```
43    /// use mathhook_core::{Expression, expr};
44    /// use mathhook_core::formatter::simple::{SimpleFormatter, SimpleContext};
45    ///
46    /// let expression = expr!(2 * x);
47    /// let context = SimpleContext::default();
48    /// let result = expression.to_simple(&context).unwrap();
49    /// assert!(result.contains("2"));
50    /// assert!(result.contains("x"));
51    ///
52    /// // With Unicode symbols
53    /// let context = SimpleContext { use_unicode: true, ..Default::default() };
54    /// let result = expression.to_simple(&context).unwrap();
55    /// assert!(result.contains("×"));
56    /// ```
57    ///
58    /// # Error Handling
59    /// Returns error messages for expressions that exceed safety limits:
60    /// - Maximum recursion depth (1000 levels)
61    /// - Maximum terms per operation (10000 terms)
62    fn to_simple(&self, context: &SimpleContext) -> Result<String, FormattingError> {
63        self.to_simple_with_depth(context, 0)
64    }
65
66    /// Format with explicit recursion depth tracking
67    ///
68    /// Internal method that provides stack overflow protection by tracking
69    /// recursion depth. This method returns a Result to allow proper error
70    /// propagation during recursive formatting.
71    ///
72    /// # Arguments
73    /// * `context` - Formatting configuration
74    /// * `depth` - Current recursion depth (starts at 0)
75    ///
76    /// # Returns
77    /// * `Ok(String)` - Successfully formatted expression
78    /// * `Err(String)` - Error message if limits exceeded
79    ///
80    /// # Safety Limits
81    /// * Maximum recursion depth: 1000 levels
82    /// * Maximum terms per operation: 10000 terms/factors/arguments
83    ///
84    /// # Examples
85    /// ```
86    /// use mathhook_core::core::Expression;
87    /// use mathhook_core::formatter::simple::{SimpleFormatter, SimpleContext};
88    ///
89    /// let expr = Expression::from("x + y");
90    /// let context = SimpleContext::default();
91    /// let result = expr.to_simple_with_depth(&context, 0);
92    /// assert!(result.is_ok());
93    /// assert_eq!(result.unwrap(), "x + y");
94    /// ```
95    fn to_simple_with_depth(
96        &self,
97        context: &SimpleContext,
98        depth: usize,
99    ) -> Result<String, FormattingError>;
100}
101
102impl SimpleFormatter for Expression {
103    fn to_simple_with_depth(
104        &self,
105        context: &SimpleContext,
106        depth: usize,
107    ) -> Result<String, FormattingError> {
108        if depth > MAX_RECURSION_DEPTH {
109            return Err(FormattingError::RecursionLimitExceeded {
110                depth,
111                limit: MAX_RECURSION_DEPTH,
112            });
113        }
114        match self {
115            Expression::Number(Number::Integer(n)) => Ok(n.to_string()),
116            Expression::Number(Number::BigInteger(n)) => Ok(n.to_string()),
117            Expression::Number(Number::Rational(r)) => {
118                if r.denom() == &num_bigint::BigInt::from(1) {
119                    Ok(r.numer().to_string())
120                } else {
121                    Ok(format!("{}/{}", r.numer(), r.denom()))
122                }
123            }
124            Expression::Number(Number::Float(f)) => {
125                if let Some(precision) = context.float_precision {
126                    Ok(format!("{:.1$}", f, precision))
127                } else {
128                    Ok(f.to_string())
129                }
130            }
131            Expression::Symbol(s) => Ok(s.name().to_owned()),
132            Expression::Add(terms) => {
133                if terms.len() > MAX_TERMS_PER_OPERATION {
134                    return Err(FormattingError::TooManyTerms {
135                        count: terms.len(),
136                        limit: MAX_TERMS_PER_OPERATION,
137                    });
138                }
139
140                let mut term_strs = Vec::with_capacity(terms.len());
141                for (i, term) in terms.iter().enumerate() {
142                    if i == 0 {
143                        let term_result = term.to_simple_with_depth(context, depth + 1)?;
144                        term_strs.push(term_result);
145                    } else {
146                        // Smart subtraction detection for Simple format
147                        if SmartDisplayFormatter::is_negated_expression(term) {
148                            if let Some(positive_part) =
149                                SmartDisplayFormatter::extract_negated_expression(term)
150                            {
151                                let positive_result =
152                                    positive_part.to_simple_with_depth(context, depth + 1)?;
153                                term_strs.push(format!(" - {}", positive_result));
154                            } else {
155                                let term_result = term.to_simple_with_depth(context, depth + 1)?;
156                                term_strs.push(format!(" + {}", term_result));
157                            }
158                        } else {
159                            let term_result = term.to_simple_with_depth(context, depth + 1)?;
160                            term_strs.push(format!(" + {}", term_result));
161                        }
162                    }
163                }
164                Ok(term_strs.join(""))
165            }
166            Expression::Mul(factors) => {
167                if factors.len() > MAX_TERMS_PER_OPERATION {
168                    return Err(FormattingError::TooManyTerms {
169                        count: factors.len(),
170                        limit: MAX_TERMS_PER_OPERATION,
171                    });
172                }
173
174                // Smart division detection for Simple format: x * y^(-1) → x / y
175                if let Some((dividend, divisor)) =
176                    SmartDisplayFormatter::extract_division_parts(factors)
177                {
178                    let dividend_str = dividend.to_simple_with_depth(context, depth + 1)?;
179                    let divisor_str = divisor.to_simple_with_depth(context, depth + 1)?;
180                    return Ok(format!("{} / {}", dividend_str, divisor_str));
181                }
182
183                let mut factor_strs = Vec::with_capacity(factors.len());
184                for f in factors.iter() {
185                    let factor_result = f.to_simple_with_depth(context, depth + 1)?;
186                    let needs_parens = matches!(f, Expression::Add(_));
187                    if needs_parens {
188                        factor_strs.push(format!("({})", factor_result));
189                    } else {
190                        factor_strs.push(factor_result);
191                    }
192                }
193                let separator = if context.use_unicode { " × " } else { " * " };
194                Ok(factor_strs.join(separator))
195            }
196            Expression::Pow(base, exp) => {
197                let base_simple = base.to_simple_with_depth(context, depth + 1)?;
198                let exp_simple = exp.to_simple_with_depth(context, depth + 1)?;
199                // Add parentheses around negative or complex exponents for clarity
200                if exp_simple.starts_with('-') || exp_simple.contains(' ') {
201                    Ok(format!("{}^({})", base_simple, exp_simple))
202                } else {
203                    Ok(format!("{}^{}", base_simple, exp_simple))
204                }
205            }
206            Expression::Function { name, args } => {
207                if args.is_empty() {
208                    Ok(name.clone())
209                } else {
210                    if args.len() > MAX_TERMS_PER_OPERATION {
211                        return Err(FormattingError::TooManyTerms {
212                            count: args.len(),
213                            limit: MAX_TERMS_PER_OPERATION,
214                        });
215                    }
216
217                    let mut arg_strs = Vec::with_capacity(args.len());
218                    for arg in args.iter() {
219                        arg_strs.push(arg.to_simple_with_depth(context, depth + 1)?);
220                    }
221                    Ok(format!("{}({})", name, arg_strs.join(", ")))
222                }
223            }
224            Expression::Interval(interval_data) => {
225                let start_bracket = if interval_data.start_inclusive {
226                    "["
227                } else {
228                    "("
229                };
230                let end_bracket = if interval_data.end_inclusive {
231                    "]"
232                } else {
233                    ")"
234                };
235                let start_simple = interval_data
236                    .start
237                    .to_simple_with_depth(context, depth + 1)?;
238                let end_simple = interval_data.end.to_simple_with_depth(context, depth + 1)?;
239                Ok(format!(
240                    "{}{}, {}{}",
241                    start_bracket, start_simple, end_simple, end_bracket
242                ))
243            }
244            Expression::Relation(relation_data) => {
245                let left_simple = relation_data
246                    .left
247                    .to_simple_with_depth(context, depth + 1)?;
248                let right_simple = relation_data
249                    .right
250                    .to_simple_with_depth(context, depth + 1)?;
251                let operator = match relation_data.relation_type {
252                    RelationType::Equal => "=",
253                    RelationType::NotEqual => {
254                        if context.use_unicode {
255                            "≠"
256                        } else {
257                            "!="
258                        }
259                    }
260                    RelationType::Less => "<",
261                    RelationType::LessEqual => {
262                        if context.use_unicode {
263                            "≤"
264                        } else {
265                            "<="
266                        }
267                    }
268                    RelationType::Greater => ">",
269                    RelationType::GreaterEqual => {
270                        if context.use_unicode {
271                            "≥"
272                        } else {
273                            ">="
274                        }
275                    }
276                    RelationType::Approximate => {
277                        if context.use_unicode {
278                            "≈"
279                        } else {
280                            "~="
281                        }
282                    }
283                    RelationType::Similar => {
284                        if context.use_unicode {
285                            "∼"
286                        } else {
287                            "~"
288                        }
289                    }
290                    RelationType::Proportional => {
291                        if context.use_unicode {
292                            "∝"
293                        } else {
294                            "prop"
295                        }
296                    }
297                    RelationType::Congruent => {
298                        if context.use_unicode {
299                            "≅"
300                        } else {
301                            "cong"
302                        }
303                    }
304                };
305                Ok(format!("{} {} {}", left_simple, operator, right_simple))
306            }
307            Expression::Piecewise(piecewise_data) => {
308                let mut result = String::from("{");
309
310                for (i, (condition, value)) in piecewise_data.pieces.iter().enumerate() {
311                    if i > 0 {
312                        result.push_str(", ");
313                    }
314                    let condition_simple = condition.to_simple_with_depth(context, depth + 1)?;
315                    let value_simple = value.to_simple_with_depth(context, depth + 1)?;
316                    result.push_str(&format!("{} if {}", value_simple, condition_simple));
317                }
318
319                if let Some(default_value) = &piecewise_data.default {
320                    let default_simple = default_value.to_simple_with_depth(context, depth + 1)?;
321                    result.push_str(&format!(", {} otherwise", default_simple));
322                }
323
324                result.push('}');
325                Ok(result)
326            }
327            Expression::MethodCall(method_data) => {
328                let object_str = method_data
329                    .object
330                    .to_simple_with_depth(context, depth + 1)?;
331                if method_data.args.is_empty() {
332                    Ok(format!("{}.{}()", object_str, method_data.method_name))
333                } else {
334                    let args_str = method_data
335                        .args
336                        .iter()
337                        .map(|arg| arg.to_simple_with_depth(context, depth + 1))
338                        .collect::<Result<Vec<_>, _>>()?
339                        .join(", ");
340                    Ok(format!(
341                        "{}.{}({})",
342                        object_str, method_data.method_name, args_str
343                    ))
344                }
345            }
346            _ => Ok("unknown".to_owned()),
347        }
348    }
349}