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}