mathhook_core/formatter/
wolfram.rs

1use super::{FormattingContext, FormattingError};
2use crate::core::expression::smart_display::SmartDisplayFormatter;
3use crate::core::expression::{CalculusData, RelationType};
4use crate::core::{Expression, Number};
5use crate::functions::intelligence::get_universal_registry;
6
7const MAX_RECURSION_DEPTH: usize = 1000;
8const MAX_TERMS_PER_OPERATION: usize = 10000;
9
10/// Wolfram formatting context
11#[derive(Debug, Default, Clone)]
12pub struct WolframContext {
13    pub needs_parentheses: bool,
14}
15
16impl FormattingContext for WolframContext {}
17
18/// Format the expression to Wolfram Language
19pub trait WolframFormatter {
20    /// Format an Expression as Wolfram Language notation
21    ///
22    /// Converts mathematical expressions into Wolfram Language format suitable for
23    /// use in Mathematica and other Wolfram products.
24    ///
25    /// # Arguments
26    /// * `context` - Wolfram formatting configuration
27    ///
28    /// # Context Options
29    /// * `needs_parentheses` - Whether to wrap the entire expression in parentheses
30    ///
31    /// # Examples
32    /// ```
33    /// use mathhook_core::{Expression, expr};
34    /// use mathhook_core::formatter::wolfram::{WolframFormatter, WolframContext};
35    ///
36    /// let expression = expr!(x ^ 2);
37    /// let context = WolframContext::default();
38    /// let result = expression.to_wolfram(&context).unwrap();
39    /// assert!(result.contains("Power"));
40    /// assert!(result.contains("x"));
41    /// ```
42    ///
43    /// # Error Handling
44    /// Returns error messages for expressions that exceed safety limits:
45    /// - Maximum recursion depth (1000 levels)
46    /// - Maximum terms per operation (10000 terms)
47    fn to_wolfram(&self, context: &WolframContext) -> Result<String, FormattingError> {
48        self.to_wolfram_with_depth(context, 0)
49    }
50
51    /// Format with explicit recursion depth tracking
52    ///
53    /// Internal method that provides stack overflow protection by tracking
54    /// recursion depth. This method returns a Result to allow proper error
55    /// propagation during recursive formatting.
56    ///
57    /// # Arguments
58    /// * `context` - Wolfram formatting configuration
59    /// * `depth` - Current recursion depth (starts at 0)
60    ///
61    /// # Returns
62    /// * `Ok(String)` - Successfully formatted Wolfram expression
63    /// * `Err(String)` - Error message if limits exceeded
64    ///
65    /// # Safety Limits
66    /// * Maximum recursion depth: 1000 levels
67    /// * Maximum terms per operation: 10000 terms/factors/arguments
68    fn to_wolfram_with_depth(
69        &self,
70        context: &WolframContext,
71        depth: usize,
72    ) -> Result<String, FormattingError>;
73
74    /// Convert function to Wolfram Language with depth tracking
75    fn format_function_with_depth(
76        &self,
77        name: &str,
78        args: &[Expression],
79        context: &WolframContext,
80        depth: usize,
81    ) -> Result<String, FormattingError>;
82}
83
84impl WolframFormatter for Expression {
85    fn to_wolfram_with_depth(
86        &self,
87        context: &WolframContext,
88        depth: usize,
89    ) -> Result<String, FormattingError> {
90        if depth > MAX_RECURSION_DEPTH {
91            return Err(FormattingError::RecursionLimitExceeded {
92                depth,
93                limit: MAX_RECURSION_DEPTH,
94            });
95        }
96
97        match self {
98            Expression::Number(Number::Integer(n)) => Ok(n.to_string()),
99            Expression::Number(Number::BigInteger(n)) => Ok(n.to_string()),
100            Expression::Number(Number::Rational(r)) => {
101                if r.denom() == &num_bigint::BigInt::from(1) {
102                    Ok(r.numer().to_string())
103                } else {
104                    // Use Power[denominator, -1] for proper Wolfram syntax
105                    Ok(format!("Times[{}, Power[{}, -1]]", r.numer(), r.denom()))
106                }
107            }
108            Expression::Number(Number::Float(f)) => Ok(f.to_string()),
109            Expression::Symbol(s) => Ok(s.name().to_owned()),
110            Expression::Add(terms) => {
111                if terms.len() > MAX_TERMS_PER_OPERATION {
112                    return Err(FormattingError::TooManyTerms {
113                        count: terms.len(),
114                        limit: MAX_TERMS_PER_OPERATION,
115                    });
116                }
117
118                if terms.len() == 1 {
119                    terms[0].to_wolfram_with_depth(context, depth + 1)
120                } else if terms.len() == 2
121                    && SmartDisplayFormatter::is_negated_expression(&terms[1])
122                {
123                    // Smart subtraction detection: x + (-1 * y) → Subtract[x, y]
124                    if let Some(positive_part) =
125                        SmartDisplayFormatter::extract_negated_expression(&terms[1])
126                    {
127                        Ok(format!(
128                            "Subtract[{}, {}]",
129                            terms[0].to_wolfram_with_depth(context, depth + 1)?,
130                            positive_part.to_wolfram_with_depth(context, depth + 1)?
131                        ))
132                    } else {
133                        let mut term_strs = Vec::with_capacity(terms.len());
134                        for term in terms.iter() {
135                            term_strs.push(term.to_wolfram_with_depth(context, depth + 1)?);
136                        }
137                        Ok(format!("Plus[{}]", term_strs.join(", ")))
138                    }
139                } else {
140                    let mut term_strs = Vec::with_capacity(terms.len());
141                    for term in terms.iter() {
142                        term_strs.push(term.to_wolfram_with_depth(context, depth + 1)?);
143                    }
144                    Ok(format!("Plus[{}]", term_strs.join(", ")))
145                }
146            }
147            Expression::Mul(factors) => {
148                if factors.len() > MAX_TERMS_PER_OPERATION {
149                    return Err(FormattingError::TooManyTerms {
150                        count: factors.len(),
151                        limit: MAX_TERMS_PER_OPERATION,
152                    });
153                }
154
155                if factors.len() == 1 {
156                    factors[0].to_wolfram_with_depth(context, depth + 1)
157                } else if let Some((dividend, divisor)) =
158                    SmartDisplayFormatter::extract_division_parts(factors)
159                {
160                    // Smart division detection: x * y^(-1) → Divide[x, y]
161                    Ok(format!(
162                        "Divide[{}, {}]",
163                        dividend.to_wolfram_with_depth(context, depth + 1)?,
164                        divisor.to_wolfram_with_depth(context, depth + 1)?
165                    ))
166                } else {
167                    let mut factor_strs = Vec::with_capacity(factors.len());
168                    for factor in factors.iter() {
169                        factor_strs.push(factor.to_wolfram_with_depth(context, depth + 1)?);
170                    }
171                    Ok(format!("Times[{}]", factor_strs.join(", ")))
172                }
173            }
174            Expression::Pow(base, exp) => Ok(format!(
175                "Power[{}, {}]",
176                base.to_wolfram_with_depth(context, depth + 1)?,
177                exp.to_wolfram_with_depth(context, depth + 1)?
178            )),
179            Expression::Function { name, args } => {
180                self.format_function_with_depth(name, args, context, depth + 1)
181            }
182            Expression::Complex(complex_data) => Ok(format!(
183                "Complex[{}, {}]",
184                complex_data
185                    .real
186                    .to_wolfram_with_depth(context, depth + 1)?,
187                complex_data
188                    .imag
189                    .to_wolfram_with_depth(context, depth + 1)?
190            )),
191            Expression::Matrix(_) => Ok("matrix".to_owned()),
192            Expression::Constant(c) => Ok(format!("{:?}", c)),
193            Expression::Relation(relation_data) => {
194                let left_wolfram = relation_data
195                    .left
196                    .to_wolfram_with_depth(context, depth + 1)?;
197                let right_wolfram = relation_data
198                    .right
199                    .to_wolfram_with_depth(context, depth + 1)?;
200                let operator = match relation_data.relation_type {
201                    RelationType::Equal => "Equal",
202                    RelationType::NotEqual => "Unequal",
203                    RelationType::Less => "Less",
204                    RelationType::LessEqual => "LessEqual",
205                    RelationType::Greater => "Greater",
206                    RelationType::GreaterEqual => "GreaterEqual",
207                    RelationType::Approximate => "TildeEqual",
208                    RelationType::Similar => "Tilde",
209                    RelationType::Proportional => "Proportional",
210                    RelationType::Congruent => "Congruent",
211                };
212                Ok(format!("{}[{}, {}]", operator, left_wolfram, right_wolfram))
213            }
214            Expression::Piecewise(piecewise_data) => {
215                let mut conditions = Vec::new();
216                let mut values = Vec::new();
217
218                for (condition, value) in &piecewise_data.pieces {
219                    conditions.push(condition.to_wolfram_with_depth(context, depth + 1)?);
220                    values.push(value.to_wolfram_with_depth(context, depth + 1)?);
221                }
222
223                if let Some(default_value) = &piecewise_data.default {
224                    conditions.push("True".to_owned());
225                    values.push(default_value.to_wolfram_with_depth(context, depth + 1)?);
226                }
227
228                let conditions_list = format!("{{{}}}", conditions.join(", "));
229                let values_list = format!("{{{}}}", values.join(", "));
230                Ok(format!("Piecewise[{}, {}]", values_list, conditions_list))
231            }
232            Expression::Set(elements) => {
233                if elements.len() > MAX_TERMS_PER_OPERATION {
234                    return Err(FormattingError::TooManyTerms {
235                        count: elements.len(),
236                        limit: MAX_TERMS_PER_OPERATION,
237                    });
238                }
239
240                if elements.is_empty() {
241                    Ok("{}".to_owned())
242                } else {
243                    let mut element_strs = Vec::with_capacity(elements.len());
244                    for elem in elements.iter() {
245                        element_strs.push(elem.to_wolfram_with_depth(context, depth + 1)?);
246                    }
247                    Ok(format!("{{{}}}", element_strs.join(", ")))
248                }
249            }
250            Expression::Interval(_) => Ok("interval".to_owned()),
251            Expression::Calculus(calculus_data) => {
252                Ok(match calculus_data.as_ref() {
253                    CalculusData::Derivative {
254                        expression,
255                        variable,
256                        order,
257                    } => {
258                        if *order == 1 {
259                            format!(
260                                "D[{}, {}]",
261                                expression.to_wolfram_with_depth(context, depth + 1)?,
262                                variable.name()
263                            )
264                        } else {
265                            format!(
266                                "D[{}, {{{}, {}}}]",
267                                expression.to_wolfram_with_depth(context, depth + 1)?,
268                                variable.name(),
269                                order
270                            )
271                        }
272                    }
273                    CalculusData::Integral {
274                        integrand,
275                        variable,
276                        bounds,
277                    } => match bounds {
278                        None => format!(
279                            "Integrate[{}, {}]",
280                            integrand.to_wolfram_with_depth(context, depth + 1)?,
281                            variable.name()
282                        ),
283                        Some((start, end)) => format!(
284                            "Integrate[{}, {{{}, {}, {}}}]",
285                            integrand.to_wolfram_with_depth(context, depth + 1)?,
286                            variable.name(),
287                            start.to_wolfram_with_depth(context, depth + 1)?,
288                            end.to_wolfram_with_depth(context, depth + 1)?
289                        ),
290                    },
291                    CalculusData::Limit {
292                        expression,
293                        variable,
294                        point,
295                        direction: _,
296                    } => {
297                        // Simplified Wolfram limit format for roundtrip consistency
298                        format!(
299                            "Limit[{}, {} -> {}]",
300                            expression.to_wolfram_with_depth(context, depth + 1)?,
301                            variable.name(),
302                            point.to_wolfram_with_depth(context, depth + 1)?
303                        )
304                    }
305                    CalculusData::Sum {
306                        expression,
307                        variable,
308                        start,
309                        end,
310                    } => {
311                        format!(
312                            "Sum[{}, {{{}, {}, {}}}]",
313                            expression.to_wolfram_with_depth(context, depth + 1)?,
314                            variable.name(),
315                            start.to_wolfram_with_depth(context, depth + 1)?,
316                            end.to_wolfram_with_depth(context, depth + 1)?
317                        )
318                    }
319                    CalculusData::Product {
320                        expression,
321                        variable,
322                        start,
323                        end,
324                    } => {
325                        format!(
326                            "Product[{}, {{{}, {}, {}}}]",
327                            expression.to_wolfram_with_depth(context, depth + 1)?,
328                            variable.name(),
329                            start.to_wolfram_with_depth(context, depth + 1)?,
330                            end.to_wolfram_with_depth(context, depth + 1)?
331                        )
332                    }
333                })
334            }
335            Expression::MethodCall(method_data) => {
336                let object_str = method_data
337                    .object
338                    .to_wolfram_with_depth(context, depth + 1)?;
339                let args_str = method_data
340                    .args
341                    .iter()
342                    .map(|arg| arg.to_wolfram_with_depth(context, depth + 1))
343                    .collect::<Result<Vec<_>, _>>()?
344                    .join(", ");
345                Ok(format!(
346                    "{}.{}[{}]",
347                    object_str, method_data.method_name, args_str
348                ))
349            }
350        }
351    }
352
353    /// Convert function to Wolfram Language with depth tracking
354    fn format_function_with_depth(
355        &self,
356        name: &str,
357        args: &[Expression],
358        context: &WolframContext,
359        depth: usize,
360    ) -> Result<String, FormattingError> {
361        if args.len() > MAX_TERMS_PER_OPERATION {
362            return Err(FormattingError::TooManyTerms {
363                count: args.len(),
364                limit: MAX_TERMS_PER_OPERATION,
365            });
366        }
367
368        let registry = get_universal_registry();
369        let wolfram_name = registry
370            .get_properties(name)
371            .and_then(|props| match props {
372                crate::functions::properties::FunctionProperties::Elementary(elem_props) => {
373                    elem_props.wolfram_name
374                }
375                crate::functions::properties::FunctionProperties::Special(spec_props) => {
376                    spec_props.wolfram_name
377                }
378                _ => None,
379            })
380            .unwrap_or(name);
381
382        if args.is_empty() {
383            Ok(wolfram_name.to_owned())
384        } else {
385            let mut arg_strs = Vec::with_capacity(args.len());
386            for arg in args.iter() {
387                arg_strs.push(arg.to_wolfram_with_depth(context, depth + 1)?);
388            }
389            Ok(format!("{}[{}]", wolfram_name, arg_strs.join(", ")))
390        }
391    }
392}