Skip to main content

symplex/output/
parse.rs

1//! Runtime expression parser.
2//!
3//! Parses mathematical expressions from strings into the arena.
4//! Uses a Pratt parser (precedence climbing) with the same grammar
5//! as the `expr!` proc macro:
6//!
7//! - Operators: `+`, `-`, `*`, `/`, `^` (right-associative)
8//! - Unary: `-` (prefix negation)
9//! - Functions: `sin`, `cos`, `tan`, `exp`, `ln`, `sqrt`, `abs`
10//! - Parentheses: `(`, `)`
11//! - Atoms: integer literals, symbol names
12//! - Constants: `pi`, `e`, `I`, `inf`, `nan`
13//!
14//! Three entry points share this grammar (SymPy: `sympify`, `parse_expr`):
15//!
16//! | Function | Result | Extra syntax |
17//! |----------|--------|--------------|
18//! | [`parse`] / [`Context::parse`] | [`Ex`] | juxtaposition of numbers, symbols and parentheses is multiplication (`2x`, `2 x`, `x y`, `2(x+1)`, `(x+1)(x-1)`, `2pi`) |
19//! | [`parse_bool`] / [`Context::parse_bool`] | [`BoolEx`] | relations `<` `<=` `>` `>=` `==` `!=`, connectives `&`/`&&`/`and`, `\|`/`\|\|`/`or`, prefix `~`/`!`/`not`, `True`/`False`, and the function forms `Eq(a, b)`, `Ne`, `Lt`, `Le`, `Gt`, `Ge`, `And(…)`, `Or(…)`, `Not(a)` |
20//! | [`parse_implicit`] / [`Context::parse_implicit`] | [`Ex`] | function application without parentheses (`sin x`, `2 sin x`, `sin 2x`) and `f(x)` as a product for unknown `f` (`x(x+1)`) |
21//!
22//! # Examples
23//!
24//! ```
25//! use symplex::prelude::*;
26//!
27//! let ctx = Context::new();
28//! let expr = symplex::parse::parse(&ctx, "x^2 + 2*x + 1").unwrap();
29//! assert_eq!(format!("{expr}"), "x^2 + 2*x + 1");
30//! ```
31
32use std::sync::Arc;
33
34use num_bigint::BigInt;
35use num_rational::Ratio;
36use smallvec::SmallVec;
37
38use crate::api::context::Context;
39use crate::api::expr::{BoolEx, Ex};
40use crate::base::arena::{
41    Arena, FN_AIRYAI, FN_AIRYAIPRIME, FN_AIRYBI, FN_AIRYBIPRIME, FN_ASSOC_LAGUERRE,
42    FN_ASSOC_LEGENDRE, FN_BETAINC, FN_BETAINC_REGULARIZED, FN_CHI, FN_DIRICHLET_ETA, FN_ELLIPTIC_E,
43    FN_ELLIPTIC_F, FN_ELLIPTIC_K, FN_ELLIPTIC_PI, FN_ERFCINV, FN_ERFI, FN_ERFINV, FN_EXPINT,
44    FN_FRESNELC, FN_FRESNELS, FN_GEGENBAUER, FN_JACOBI, FN_LOWERGAMMA, FN_POLYLOG, FN_SHI,
45    FN_UPPERGAMMA,
46};
47use crate::base::node::{ExprId, ExprNode};
48
49/// Error returned when parsing fails.
50#[derive(Debug, Clone)]
51pub struct ParseError {
52    /// Human-readable description of what went wrong.
53    pub message: String,
54    /// Byte offset in the input where the error occurred.
55    pub position: usize,
56}
57
58impl std::fmt::Display for ParseError {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(
61            f,
62            "parse error at position {}: {}",
63            self.position, self.message
64        )
65    }
66}
67
68impl std::error::Error for ParseError {}
69
70/// Parse a mathematical expression string into an `Ex`.
71///
72/// Uses the provided context for symbol and number interning.
73///
74/// # Errors
75///
76/// Returns `ParseError` if the string is not a valid expression.
77pub fn parse(ctx: &Context, input: &str) -> Result<Ex, ParseError> {
78    let id = parse_with_mode(ctx, input, Mode::STRICT)?;
79    // Construct an Ex from the ExprId. Ex fields are pub(crate), so this
80    // works from within the crate without needing to expose make_ex.
81    Ok(Ex::from_raw_parts(ctx.id, Arc::clone(&ctx.inner), id))
82}
83
84/// Parse a relation or Boolean combination of relations into a [`BoolEx`].
85///
86/// The numeric grammar of [`parse`] is extended with the comparison
87/// operators `<`, `<=`, `>`, `>=`, `==`, `!=`, the connectives `&`/`&&`/`and`
88/// and `|`/`||`/`or`, the prefix negation `~`/`!`/`not`, the constants
89/// `True`/`False`, and the function forms `Eq(a, b)`, `Ne`, `Lt`, `Le`,
90/// `Gt`, `Ge`, `And(a, b, …)`, `Or(…)`, `Not(a)`.  Precedence, loosest
91/// first: `or` < `and` < comparisons < `+ -` < `* /` < `^`; `not` is a
92/// prefix operator that applies to the following relation (`not x > 0` is
93/// `not (x > 0)`), and comparisons do not chain (`a < b < c` is an error —
94/// write `a < b & b < c`).  This is mathematical precedence, unlike
95/// SymPy's `sympify("x > 0 & x < 1")`, where Python binds `&` tighter than
96/// `>`.
97///
98/// # Errors
99///
100/// [`ParseError`] if the string is not well formed, or if it parses to a
101/// numeric expression rather than a relation.
102///
103/// # Examples
104///
105/// ```
106/// use symplex::prelude::*;
107///
108/// let ctx = Context::new();
109/// let p = symplex::parse::parse_bool(&ctx, "x > 0 & x < 1").unwrap();
110/// assert_eq!(p.to_string(), "x > 0 & 1 > x");
111/// assert!(symplex::parse::parse_bool(&ctx, "x + 1").is_err());
112/// ```
113pub fn parse_bool(ctx: &Context, input: &str) -> Result<BoolEx, ParseError> {
114    let id = parse_with_mode(ctx, input, Mode::RELATIONS)?;
115    Ok(BoolEx::from_raw_parts(ctx.id, Arc::clone(&ctx.inner), id))
116}
117
118/// Parse with implicit multiplication *and* implicit function application
119/// (SymPy: `parse_expr(s, transformations=implicit_multiplication_application)`).
120///
121/// [`parse`] already reads `2x`, `2 x`, `x y`, `2(x+1)` and `(x+1)(x-1)` as
122/// products.  This variant additionally accepts
123///
124/// - `sin x`, `2 sin x`, `sin 2x`, `sin x^2`: a textbook function name
125///   (trigonometric, hyperbolic and inverse trigonometric functions, `exp`,
126///   `ln`/`log`, `sqrt`, `cbrt`, `abs`, `floor`, `ceil`, `sign`, `gamma`,
127///   `erf`, `erfc`, `factorial`) applied without parentheses to the
128///   juxtaposed product that follows it, up to the next `+`, `-`,
129///   comparison, closing parenthesis, or function name;
130/// - `x(x+1)`, `f(x) g(x)`: an identifier that is *not* a known function,
131///   followed by `(`, is a symbol times the parenthesised group.
132///
133/// Ambiguities are resolved as follows:
134///
135/// | Input | Reading | Note |
136/// |-------|---------|------|
137/// | `x y z` | `x*y*z` | juxtaposition is left-associative |
138/// | `2 sin x` | `2*sin(x)` | a coefficient stays outside |
139/// | `sin 2x` | `sin(2*x)` | the argument is the whole following product |
140/// | `sin x^2` | `sin(x^2)` | `^` binds tighter than application |
141/// | `sin x cos y` | `sin(x)*cos(y)` | the argument stops at the next function name (SymPy reads `sin(x*cos(y))`) |
142/// | `sin x + 1` | `sin(x) + 1` | `+` ends the argument |
143/// | `sin x/2` | `sin(x/2)` | `/` is part of the product |
144/// | `f(x)` | `f*x` | `f` is not a known function |
145///
146/// Short names that double as common variables (`re`, `im`, `arg`, `li`,
147/// `zeta`, …) are *not* applied implicitly; write them with parentheses.
148/// The one-letter display aliases `C(n, k)`, `B(a, b)`, `W(x)` are
149/// ordinary symbols here (use `binomial`, `beta`, `lambertw`).  A textbook
150/// function name that is not followed by an operand is an error
151/// (`sin + 1`).
152///
153/// # Errors
154///
155/// [`ParseError`] if the string is not well formed.
156///
157/// # Examples
158///
159/// ```
160/// use symplex::prelude::*;
161///
162/// let ctx = Context::new();
163/// let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
164/// let e = symplex::parse::parse_implicit(&ctx, "2x + 3(y-1)").unwrap();
165/// assert_eq!(e, 2 * &x + 3 * (&y - 1));
166/// let s = symplex::parse::parse_implicit(&ctx, "2 sin x cos y").unwrap();
167/// assert_eq!(s, 2 * &x.sin() * &y.cos());
168/// ```
169pub fn parse_implicit(ctx: &Context, input: &str) -> Result<Ex, ParseError> {
170    let id = parse_with_mode(ctx, input, Mode::IMPLICIT)?;
171    Ok(Ex::from_raw_parts(ctx.id, Arc::clone(&ctx.inner), id))
172}
173
174/// Run the parser in `mode` and return the interned root.
175///
176/// In [`Mode::RELATIONS`] the root must be a Boolean node.
177fn parse_with_mode(ctx: &Context, input: &str, mode: Mode) -> Result<ExprId, ParseError> {
178    let mut parser = Parser::new(input, mode);
179    ctx.with_arena_mut(|arena| {
180        let result = parser.parse_expr(arena, 0)?;
181        // After parsing the expression, ensure we consumed everything.
182        if parser.current != Token::Eof {
183            return Err(ParseError {
184                message: format!("unexpected token {:?} after expression", parser.current),
185                position: parser.lexer.pos,
186            });
187        }
188        if mode.relations && !is_bool_node(arena, result) {
189            return Err(ParseError {
190                message: format!(
191                    "expected a relation or Boolean expression, got the numeric expression '{}'",
192                    arena.display(result)
193                ),
194                position: parser.lexer.pos,
195            });
196        }
197        Ok(result)
198    })
199}
200
201impl Context {
202    /// Parse a relation or Boolean combination of relations in this context
203    /// (SymPy: `sympify("x > 0")`).
204    ///
205    /// See [`parse::parse_bool`](crate::parse::parse_bool) for the grammar:
206    /// comparisons `<` `<=` `>` `>=` `==` `!=` bind tighter than `&`/`and`,
207    /// which binds tighter than `|`/`or`; `~`/`!`/`not` is prefix.
208    ///
209    /// # Errors
210    ///
211    /// [`SymplexError::ComputationFailed`](crate::base::errors::SymplexError::ComputationFailed)
212    /// with the parser's message if the string is not a well-formed relation.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use symplex::prelude::*;
218    ///
219    /// let ctx = Context::new();
220    /// let p = ctx.parse_bool("x > 0 & x < 1").unwrap();
221    /// assert_eq!(p.to_string(), "x > 0 & 1 > x");
222    /// assert_eq!(p.to_lean().unwrap(), "0 < x ∧ x < 1");
223    /// assert_eq!(ctx.parse_bool("not x == 1 or y >= 2").unwrap().to_string(), "!(x == 1) | y >= 2");
224    /// ```
225    pub fn parse_bool(
226        &self,
227        input: &str,
228    ) -> Result<crate::api::expr::BoolEx, crate::base::errors::SymplexError> {
229        parse_bool(self, input).map_err(|e| crate::base::errors::SymplexError::ComputationFailed {
230            operation: "parse_bool",
231            reason: e.to_string(),
232        })
233    }
234
235    /// Parse with implicit multiplication and implicit function application
236    /// (SymPy: `parse_expr(s, transformations=implicit_multiplication_application)`).
237    ///
238    /// See [`parse::parse_implicit`](crate::parse::parse_implicit) for the
239    /// rules and the ambiguities they resolve (`2 sin x` is `2*sin(x)`,
240    /// `sin 2x` is `sin(2*x)`, `sin x cos y` is `sin(x)*cos(y)`).
241    ///
242    /// # Errors
243    ///
244    /// [`SymplexError::ComputationFailed`](crate::base::errors::SymplexError::ComputationFailed)
245    /// with the parser's message if the string is not well formed.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// use symplex::prelude::*;
251    ///
252    /// let ctx = Context::new();
253    /// let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
254    /// assert_eq!(ctx.parse_implicit("2x + 3(y-1)").unwrap(), 2 * &x + 3 * (&y - 1));
255    /// assert_eq!(ctx.parse_implicit("sin 2x").unwrap(), (2 * &x).sin());
256    /// ```
257    pub fn parse_implicit(
258        &self,
259        input: &str,
260    ) -> Result<crate::api::expr::Ex, crate::base::errors::SymplexError> {
261        parse_implicit(self, input).map_err(|e| {
262            crate::base::errors::SymplexError::ComputationFailed {
263                operation: "parse_implicit",
264                reason: e.to_string(),
265            }
266        })
267    }
268}
269
270/// Which syntax extensions the parser accepts.
271#[derive(Clone, Copy)]
272struct Mode {
273    /// Comparison operators, Boolean connectives and `True`/`False`.
274    relations: bool,
275    /// Function application without parentheses; unknown `f(…)` is a product.
276    implicit_app: bool,
277}
278
279impl Mode {
280    const STRICT: Mode = Mode {
281        relations: false,
282        implicit_app: false,
283    };
284    const RELATIONS: Mode = Mode {
285        relations: true,
286        implicit_app: false,
287    };
288    const IMPLICIT: Mode = Mode {
289        relations: false,
290        implicit_app: true,
291    };
292}
293
294/// Is `id` a Boolean-sorted node (a relation, connective or truth value)?
295fn is_bool_node(arena: &Arena, id: ExprId) -> bool {
296    matches!(
297        arena.node(id),
298        ExprNode::BoolTrue
299            | ExprNode::BoolFalse
300            | ExprNode::Gt(_, _)
301            | ExprNode::Ge(_, _)
302            | ExprNode::Eq_(_, _)
303            | ExprNode::Ne(_, _)
304            | ExprNode::And(_)
305            | ExprNode::Or(_)
306            | ExprNode::Not(_)
307    )
308}
309
310/// Every function name the call tables (`call_1` … `call_4`, `min`/`max`,
311/// `Sum`/`Product`) accept, lower-cased.  Used by [`parse_implicit`] to tell
312/// `sin(x)` (a call) from `f(x)` (a product).
313const KNOWN_FUNCTIONS: &[&str] = &[
314    // call_1
315    "sin",
316    "cos",
317    "tan",
318    "exp",
319    "ln",
320    "log",
321    "sqrt",
322    "cbrt",
323    "abs",
324    "asin",
325    "arcsin",
326    "acos",
327    "arccos",
328    "atan",
329    "arctan",
330    "sinh",
331    "cosh",
332    "tanh",
333    "asinh",
334    "arcsinh",
335    "acosh",
336    "arccosh",
337    "atanh",
338    "arctanh",
339    "sign",
340    "sgn",
341    "floor",
342    "ceil",
343    "ceiling",
344    "gamma",
345    "erf",
346    "erfc",
347    "heaviside",
348    "diracdelta",
349    "dirac_delta",
350    "lambertw",
351    "w",
352    "factorial",
353    "digamma",
354    "loggamma",
355    "cot",
356    "sec",
357    "csc",
358    "coth",
359    "sech",
360    "csch",
361    "acot",
362    "arccot",
363    "re",
364    "im",
365    "conjugate",
366    "conj",
367    "arg",
368    "si",
369    "ci",
370    "ei",
371    "li",
372    "zeta",
373    "erfi",
374    "erfinv",
375    "erfcinv",
376    "e1",
377    "shi",
378    "chi",
379    "fresnels",
380    "fresnelc",
381    "dirichlet_eta",
382    "airyai",
383    "airybi",
384    "airyaiprime",
385    "airybiprime",
386    "elliptic_k",
387    "elliptic_e",
388    // call_2
389    "rootof",
390    "conditionset",
391    "integral",
392    "atan2",
393    "polygamma",
394    "kroneckerdelta",
395    "kronecker_delta",
396    "binomial",
397    "c",
398    "beta",
399    "b",
400    "besselj",
401    "bessely",
402    "besseli",
403    "besselk",
404    "expint",
405    "lowergamma",
406    "uppergamma",
407    "polylog",
408    "elliptic_f",
409    "elliptic_pi",
410    // call_3
411    "limit",
412    "laplacetransform",
413    "inverselaplacetransform",
414    "residue",
415    "dsolve",
416    "gegenbauer",
417    "assoc_legendre",
418    "assoc_laguerre",
419    // call_4
420    "series",
421    "jacobi",
422    "betainc",
423    "betainc_regularized",
424    // variadic / binder forms
425    "min",
426    "max",
427    "sum",
428    "product",
429];
430
431fn is_known_function(name_lower: &str) -> bool {
432    KNOWN_FUNCTIONS.contains(&name_lower)
433}
434
435/// Textbook one-argument functions that [`parse_implicit`] applies without
436/// parentheses (`sin x`).  Deliberately excludes short names that are also
437/// common variables (`re`, `im`, `arg`, `li`, `w`, `zeta`, `chi`, …).
438fn is_implicit_unary_function(name_lower: &str) -> bool {
439    matches!(
440        name_lower,
441        "sin"
442            | "cos"
443            | "tan"
444            | "cot"
445            | "sec"
446            | "csc"
447            | "sinh"
448            | "cosh"
449            | "tanh"
450            | "coth"
451            | "sech"
452            | "csch"
453            | "asin"
454            | "acos"
455            | "atan"
456            | "acot"
457            | "arcsin"
458            | "arccos"
459            | "arctan"
460            | "arccot"
461            | "asinh"
462            | "acosh"
463            | "atanh"
464            | "arcsinh"
465            | "arccosh"
466            | "arctanh"
467            | "exp"
468            | "ln"
469            | "log"
470            | "sqrt"
471            | "cbrt"
472            | "abs"
473            | "floor"
474            | "ceil"
475            | "ceiling"
476            | "sign"
477            | "sgn"
478            | "gamma"
479            | "erf"
480            | "erfc"
481            | "factorial"
482    )
483}
484
485/// The named constants (`pi`, `e`, `I`, `inf`, …), or `None` for a symbol.
486fn constant_of(arena: &Arena, name: &str) -> Option<ExprId> {
487    Some(match name {
488        "pi" | "Pi" | "PI" => arena.pi,
489        "e" | "E" => arena.e_const,
490        "I" | "i" => arena.i_unit,
491        "inf" | "oo" | "Inf" => arena.infinity,
492        "zoo" => arena.complex_infinity,
493        "nan" => arena.nan,
494        "EulerGamma" | "euler_gamma" => arena.euler_gamma,
495        "Catalan" => arena.catalan,
496        "GoldenRatio" | "golden_ratio" => arena.golden_ratio,
497        _ => return None,
498    })
499}
500
501// ═══════════════════════════════════════════════════════════════════════════
502// Tokenizer
503// ═══════════════════════════════════════════════════════════════════════════
504
505#[derive(Debug, Clone, PartialEq)]
506enum Token {
507    Int(BigInt),
508    Rational(Ratio<BigInt>),
509    Ident(String),
510    Plus,
511    Minus,
512    Star,
513    Slash,
514    Caret,
515    LParen,
516    RParen,
517    Comma,
518    /// Postfix factorial `!` (prefix logical negation in [`Mode::RELATIONS`]).
519    Bang,
520    /// `=` (only inside `Sum(body, k=lo..hi)` / `Product(…)`).
521    Eq,
522    /// `..` range separator (only inside `Sum` / `Product`).
523    DotDot,
524    /// `<`
525    Lt,
526    /// `<=`
527    Le,
528    /// `>`
529    Gt,
530    /// `>=`
531    Ge,
532    /// `==`
533    EqEq,
534    /// `!=`
535    Ne,
536    /// `&` or `&&`
537    Amp,
538    /// `|` or `||`
539    Pipe,
540    /// `~` (prefix logical negation)
541    Tilde,
542    Eof,
543}
544
545struct Lexer<'a> {
546    input: &'a str,
547    pos: usize,
548}
549
550impl<'a> Lexer<'a> {
551    fn new(input: &'a str) -> Self {
552        Lexer { input, pos: 0 }
553    }
554
555    fn skip_whitespace(&mut self) {
556        while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_whitespace() {
557            self.pos += 1;
558        }
559    }
560
561    /// Is the byte at the current position `b`?
562    fn peek_is(&self, b: u8) -> bool {
563        self.input.as_bytes().get(self.pos) == Some(&b)
564    }
565
566    fn next_token(&mut self) -> Result<Token, ParseError> {
567        self.skip_whitespace();
568        if self.pos >= self.input.len() {
569            return Ok(Token::Eof);
570        }
571
572        let b = self.input.as_bytes()[self.pos];
573        match b {
574            b'+' => {
575                self.pos += 1;
576                Ok(Token::Plus)
577            }
578            b'-' => {
579                self.pos += 1;
580                Ok(Token::Minus)
581            }
582            b'*' => {
583                self.pos += 1;
584                // Check for ** (SymPy-compatible power operator)
585                if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'*' {
586                    self.pos += 1;
587                    Ok(Token::Caret) // ** treated same as ^
588                } else {
589                    Ok(Token::Star)
590                }
591            }
592            b'/' => {
593                self.pos += 1;
594                Ok(Token::Slash)
595            }
596            b'^' => {
597                self.pos += 1;
598                Ok(Token::Caret)
599            }
600            b'(' => {
601                self.pos += 1;
602                Ok(Token::LParen)
603            }
604            b')' => {
605                self.pos += 1;
606                Ok(Token::RParen)
607            }
608            b',' => {
609                self.pos += 1;
610                Ok(Token::Comma)
611            }
612            b'!' => {
613                self.pos += 1;
614                if self.peek_is(b'=') {
615                    self.pos += 1;
616                    Ok(Token::Ne)
617                } else {
618                    Ok(Token::Bang)
619                }
620            }
621            b'=' => {
622                self.pos += 1;
623                if self.peek_is(b'=') {
624                    self.pos += 1;
625                    Ok(Token::EqEq)
626                } else {
627                    Ok(Token::Eq)
628                }
629            }
630            b'<' => {
631                self.pos += 1;
632                if self.peek_is(b'=') {
633                    self.pos += 1;
634                    Ok(Token::Le)
635                } else {
636                    Ok(Token::Lt)
637                }
638            }
639            b'>' => {
640                self.pos += 1;
641                if self.peek_is(b'=') {
642                    self.pos += 1;
643                    Ok(Token::Ge)
644                } else {
645                    Ok(Token::Gt)
646                }
647            }
648            b'&' => {
649                self.pos += 1;
650                if self.peek_is(b'&') {
651                    self.pos += 1;
652                }
653                Ok(Token::Amp)
654            }
655            b'|' => {
656                self.pos += 1;
657                if self.peek_is(b'|') {
658                    self.pos += 1;
659                }
660                Ok(Token::Pipe)
661            }
662            b'~' => {
663                self.pos += 1;
664                Ok(Token::Tilde)
665            }
666            b'.' if self.pos + 1 < self.input.len()
667                && self.input.as_bytes()[self.pos + 1] == b'.' =>
668            {
669                self.pos += 2;
670                Ok(Token::DotDot)
671            }
672            b'0'..=b'9' => {
673                let start = self.pos;
674                while self.pos < self.input.len()
675                    && self.input.as_bytes()[self.pos].is_ascii_digit()
676                {
677                    self.pos += 1;
678                }
679                // Check for decimal point followed by digits → Rational token
680                if self.pos < self.input.len()
681                    && self.input.as_bytes()[self.pos] == b'.'
682                    && self.pos + 1 < self.input.len()
683                    && self.input.as_bytes()[self.pos + 1].is_ascii_digit()
684                {
685                    self.pos += 1; // consume '.'
686                    let frac_start = self.pos;
687                    while self.pos < self.input.len()
688                        && self.input.as_bytes()[self.pos].is_ascii_digit()
689                    {
690                        self.pos += 1;
691                    }
692                    let decimal_places = self.pos - frac_start;
693                    let full_str = self.input[start..self.pos].replace('.', "");
694                    let numer = full_str.parse::<BigInt>().map_err(|e| ParseError {
695                        message: format!(
696                            "invalid number '{}': {}",
697                            &self.input[start..self.pos],
698                            e
699                        ),
700                        position: start,
701                    })?;
702                    let mut denom = BigInt::from(1);
703                    for _ in 0..decimal_places {
704                        denom *= 10;
705                    }
706                    let ratio = Ratio::new(numer, denom);
707                    return Ok(Token::Rational(ratio));
708                }
709                let s = &self.input[start..self.pos];
710                let n = s.parse::<BigInt>().map_err(|e| ParseError {
711                    message: format!("invalid integer '{}': {}", s, e),
712                    position: start,
713                })?;
714                Ok(Token::Int(n))
715            }
716            b'a'..=b'z' | b'A'..=b'Z' | b'_' => {
717                let start = self.pos;
718                while self.pos < self.input.len() {
719                    let c = self.input.as_bytes()[self.pos];
720                    if c.is_ascii_alphanumeric() || c == b'_' {
721                        self.pos += 1;
722                    } else {
723                        break;
724                    }
725                }
726                let s = self.input[start..self.pos].to_string();
727                Ok(Token::Ident(s))
728            }
729            _ => Err(ParseError {
730                message: format!("unexpected character '{}'", b as char),
731                position: self.pos,
732            }),
733        }
734    }
735}
736
737// ═══════════════════════════════════════════════════════════════════════════
738// Pratt parser
739// ═══════════════════════════════════════════════════════════════════════════
740
741// Binding powers `(left, right)` of the infix tiers, loosest first.  Only
742// the relative order matters; the Boolean tiers are reachable only in
743// `Mode::RELATIONS`.
744const BP_OR: (u8, u8) = (1, 2);
745const BP_AND: (u8, u8) = (3, 4);
746const BP_REL: (u8, u8) = (5, 6);
747const BP_ADD: (u8, u8) = (7, 8);
748const BP_MUL: (u8, u8) = (9, 10);
749/// Operand of prefix `-`: tighter than `*` and `/` but looser than `^`, so
750/// that `-x^2` parses as `-(x^2)`.
751const BP_NEG: u8 = 11;
752/// `^` is right-associative: the right binding power is below the left.
753const BP_POW: (u8, u8) = (14, 13);
754/// Operand of prefix `not`/`~`/`!`: one relation, not a whole conjunction
755/// (`not x > 0 & y > 0` is `(not x > 0) & (y > 0)`).
756const BP_NOT: u8 = BP_REL.0;
757
758/// An infix operator recognised by the Pratt loop.
759#[derive(Clone, Copy)]
760enum Infix {
761    Add,
762    Sub,
763    Mul,
764    Div,
765    Pow,
766    Rel(RelOp),
767    And,
768    Or,
769}
770
771#[derive(Clone, Copy)]
772enum RelOp {
773    Lt,
774    Le,
775    Gt,
776    Ge,
777    Eq,
778    Ne,
779}
780
781impl RelOp {
782    fn of(token: &Token) -> Option<RelOp> {
783        Some(match token {
784            Token::Lt => RelOp::Lt,
785            Token::Le => RelOp::Le,
786            Token::Gt => RelOp::Gt,
787            Token::Ge => RelOp::Ge,
788            Token::EqEq => RelOp::Eq,
789            Token::Ne => RelOp::Ne,
790            _ => return None,
791        })
792    }
793
794    fn text(self) -> &'static str {
795        match self {
796            RelOp::Lt => "<",
797            RelOp::Le => "<=",
798            RelOp::Gt => ">",
799            RelOp::Ge => ">=",
800            RelOp::Eq => "==",
801            RelOp::Ne => "!=",
802        }
803    }
804}
805
806/// Can `token` begin an operand (the argument of `sin x`)?
807fn starts_operand(token: &Token) -> bool {
808    matches!(
809        token,
810        Token::Int(_) | Token::Rational(_) | Token::Ident(_) | Token::LParen
811    )
812}
813
814struct Parser<'a> {
815    lexer: Lexer<'a>,
816    current: Token,
817    depth: usize,
818    mode: Mode,
819    /// Inside the parenthesis-free argument of `sin x`: a following
820    /// function name ends the argument instead of multiplying into it.
821    app_arg: bool,
822}
823
824impl<'a> Parser<'a> {
825    fn new(input: &'a str, mode: Mode) -> Self {
826        let mut lexer = Lexer::new(input);
827        let current = lexer.next_token().unwrap_or(Token::Eof);
828        Parser {
829            lexer,
830            current,
831            depth: 0,
832            mode,
833            app_arg: false,
834        }
835    }
836
837    fn error(&self, message: String) -> ParseError {
838        ParseError {
839            message,
840            position: self.lexer.pos,
841        }
842    }
843
844    /// Run `f` outside any `sin x` argument (inside parentheses or a call).
845    fn outside_app_arg<T>(
846        &mut self,
847        f: impl FnOnce(&mut Self) -> Result<T, ParseError>,
848    ) -> Result<T, ParseError> {
849        let saved = std::mem::replace(&mut self.app_arg, false);
850        let result = f(self);
851        self.app_arg = saved;
852        result
853    }
854
855    /// The operand of `+`, `-`, `*`, `/`, `^` or a comparison must be numeric.
856    fn numeric_operands(
857        &self,
858        arena: &Arena,
859        op: &str,
860        lhs: ExprId,
861        rhs: ExprId,
862    ) -> Result<(), ParseError> {
863        for id in [lhs, rhs] {
864            if is_bool_node(arena, id) {
865                return Err(self.error(format!(
866                    "'{op}' needs numeric operands, but '{}' is a Boolean expression",
867                    arena.display(id)
868                )));
869            }
870        }
871        Ok(())
872    }
873
874    /// The operand of `&`, `|` or `not` must be a relation or truth value.
875    fn boolean_operand(&self, arena: &Arena, op: &str, id: ExprId) -> Result<(), ParseError> {
876        if is_bool_node(arena, id) {
877            Ok(())
878        } else {
879            Err(self.error(format!(
880                "'{op}' needs Boolean operands (relations), but '{}' is numeric",
881                arena.display(id)
882            )))
883        }
884    }
885
886    /// Combine `lhs op rhs` into a node, checking the operand sorts.
887    fn combine(
888        &self,
889        arena: &mut Arena,
890        op: Infix,
891        lhs: ExprId,
892        rhs: ExprId,
893    ) -> Result<ExprId, ParseError> {
894        match op {
895            Infix::Add => {
896                self.numeric_operands(arena, "+", lhs, rhs)?;
897                Ok(arena.add(&[lhs, rhs]))
898            }
899            Infix::Sub => {
900                self.numeric_operands(arena, "-", lhs, rhs)?;
901                Ok(arena.sub(lhs, rhs))
902            }
903            Infix::Mul => {
904                self.numeric_operands(arena, "*", lhs, rhs)?;
905                Ok(arena.mul(&[lhs, rhs]))
906            }
907            Infix::Div => {
908                self.numeric_operands(arena, "/", lhs, rhs)?;
909                Ok(arena.div(lhs, rhs))
910            }
911            Infix::Pow => {
912                self.numeric_operands(arena, "^", lhs, rhs)?;
913                Ok(arena.pow(lhs, rhs))
914            }
915            Infix::Rel(rel) => {
916                self.numeric_operands(arena, rel.text(), lhs, rhs)?;
917                if RelOp::of(&self.current).is_some() {
918                    return Err(self.error(
919                        "chained comparisons are not supported; write 'a < b & b < c'".into(),
920                    ));
921                }
922                Ok(match rel {
923                    RelOp::Lt => arena.gt(rhs, lhs),
924                    RelOp::Le => arena.ge(rhs, lhs),
925                    RelOp::Gt => arena.gt(lhs, rhs),
926                    RelOp::Ge => arena.ge(lhs, rhs),
927                    RelOp::Eq => arena.eq_(lhs, rhs),
928                    RelOp::Ne => arena.ne_(lhs, rhs),
929                })
930            }
931            Infix::And => {
932                self.boolean_operand(arena, "&", lhs)?;
933                self.boolean_operand(arena, "&", rhs)?;
934                // Flatten `a & b & c` into one n-ary node, as `Ex::and` does.
935                let mut items: SmallVec<[ExprId; 4]> = match arena.node(lhs) {
936                    ExprNode::And(children) => children.iter().copied().collect(),
937                    _ => SmallVec::from_slice(&[lhs]),
938                };
939                items.push(rhs);
940                Ok(arena.and(&items))
941            }
942            Infix::Or => {
943                self.boolean_operand(arena, "|", lhs)?;
944                self.boolean_operand(arena, "|", rhs)?;
945                let mut items: SmallVec<[ExprId; 4]> = match arena.node(lhs) {
946                    ExprNode::Or(children) => children.iter().copied().collect(),
947                    _ => SmallVec::from_slice(&[lhs]),
948                };
949                items.push(rhs);
950                Ok(arena.or(&items))
951            }
952        }
953    }
954
955    fn advance(&mut self) -> Result<Token, ParseError> {
956        let old = std::mem::replace(&mut self.current, Token::Eof);
957        self.current = self.lexer.next_token()?;
958        Ok(old)
959    }
960
961    fn expect(&mut self, expected: &Token) -> Result<(), ParseError> {
962        if &self.current == expected {
963            self.advance()?;
964            Ok(())
965        } else {
966            Err(ParseError {
967                message: format!("expected {:?}, got {:?}", expected, self.current),
968                position: self.lexer.pos,
969            })
970        }
971    }
972
973    /// Parse an expression with minimum binding power `min_bp`.
974    fn parse_expr(&mut self, arena: &mut Arena, min_bp: u8) -> Result<ExprId, ParseError> {
975        self.depth += 1;
976        if self.depth > 128 {
977            return Err(ParseError {
978                message: "expression nesting too deep (max 128 levels)".into(),
979                position: self.lexer.pos,
980            });
981        }
982
983        // Prefix (atom or unary)
984        let mut lhs = self.parse_prefix(arena)?;
985
986        // Infix loop
987        loop {
988            // Postfix factorial binds tighter than every infix operator:
989            // `2^3!` is `2^(3!)` and `x!^2` is `(x!)^2`.
990            if self.current == Token::Bang {
991                self.advance()?;
992                lhs = arena.intern(ExprNode::Factorial(lhs));
993                continue;
994            }
995            let relations = self.mode.relations;
996            let (op, (l_bp, r_bp), implicit) = match &self.current {
997                Token::Plus => (Infix::Add, BP_ADD, false),
998                Token::Minus => (Infix::Sub, BP_ADD, false),
999                Token::Star => (Infix::Mul, BP_MUL, false),
1000                Token::Slash => (Infix::Div, BP_MUL, false),
1001                Token::Caret => (Infix::Pow, BP_POW, false),
1002                Token::Lt | Token::Le | Token::Gt | Token::Ge | Token::EqEq | Token::Ne
1003                    if relations =>
1004                {
1005                    match RelOp::of(&self.current) {
1006                        Some(rel) => (Infix::Rel(rel), BP_REL, false),
1007                        None => break,
1008                    }
1009                }
1010                Token::Amp if relations => (Infix::And, BP_AND, false),
1011                Token::Pipe if relations => (Infix::Or, BP_OR, false),
1012                Token::Ident(name) if relations && name == "and" => (Infix::And, BP_AND, false),
1013                Token::Ident(name) if relations && name == "or" => (Infix::Or, BP_OR, false),
1014                // `sin x cos y`: the argument of `sin` ends at the next
1015                // function name (which then multiplies `sin x` as a whole).
1016                Token::Ident(name)
1017                    if self.app_arg && is_implicit_unary_function(&name.to_ascii_lowercase()) =>
1018                {
1019                    break;
1020                }
1021                // Implicit multiplication: number, identifier, or '(' immediately
1022                // following a complete left-hand expression.
1023                Token::Int(_) | Token::Rational(_) | Token::Ident(_) | Token::LParen => {
1024                    (Infix::Mul, BP_MUL, true)
1025                }
1026                _ => break,
1027            };
1028
1029            if l_bp < min_bp {
1030                break;
1031            }
1032
1033            if !implicit {
1034                self.advance()?;
1035            }
1036            let rhs = self.parse_expr(arena, r_bp)?;
1037            lhs = self.combine(arena, op, lhs, rhs)?;
1038        }
1039
1040        self.depth -= 1;
1041        Ok(lhs)
1042    }
1043
1044    /// Parse a prefix expression (atom, unary minus, function call, parens).
1045    fn parse_prefix(&mut self, arena: &mut Arena) -> Result<ExprId, ParseError> {
1046        match self.current.clone() {
1047            Token::Int(n) => {
1048                self.advance()?;
1049                Ok(arena.big_int(n))
1050            }
1051            Token::Rational(ratio) => {
1052                self.advance()?;
1053                let nid = arena.intern_num(ratio);
1054                Ok(arena.intern(ExprNode::Num(nid)))
1055            }
1056            Token::Ident(name) => {
1057                self.advance()?;
1058                let name_lower = name.to_ascii_lowercase();
1059                // Check for function call: ident followed by '('.  With
1060                // implicit application, an identifier that is not a known
1061                // function (`x(x+1)`, `f(x)`) is a symbol and the '(' starts
1062                // an implicitly multiplied group; the one-letter display
1063                // aliases `C`, `B`, `W` count as symbols there too.
1064                let is_call = !self.mode.implicit_app
1065                    || (is_known_function(&name_lower) && name_lower.len() > 1);
1066                if self.current == Token::LParen && is_call {
1067                    return self.outside_app_arg(|p| p.parse_function_call(arena, &name));
1068                }
1069                if self.mode.relations {
1070                    match name.as_str() {
1071                        "True" | "true" => return Ok(arena.bool_true()),
1072                        "False" | "false" => return Ok(arena.bool_false()),
1073                        "not" => return self.parse_not(arena),
1074                        _ => {}
1075                    }
1076                }
1077                if self.mode.implicit_app && is_implicit_unary_function(&name_lower) {
1078                    // `sin x`, `sin 2x`, `sin x^2`: the argument is the
1079                    // following juxtaposed product.
1080                    if !starts_operand(&self.current) {
1081                        return Err(self.error(format!(
1082                            "function '{name}' needs an argument (write '{name}(x)' or '{name} x')"
1083                        )));
1084                    }
1085                    let saved = std::mem::replace(&mut self.app_arg, true);
1086                    let arg = self.parse_expr(arena, BP_MUL.0);
1087                    self.app_arg = saved;
1088                    return self.call_1(arena, &name, &name_lower, arg?);
1089                }
1090                match constant_of(arena, &name) {
1091                    Some(c) => Ok(c),
1092                    None => Ok(arena.symbol(&name)),
1093                }
1094            }
1095            Token::Minus => {
1096                self.advance()?;
1097                let operand = self.parse_expr(arena, BP_NEG)?;
1098                if is_bool_node(arena, operand) {
1099                    return Err(self.error(format!(
1100                        "'-' needs a numeric operand, but '{}' is a Boolean expression",
1101                        arena.display(operand)
1102                    )));
1103                }
1104                Ok(arena.neg(operand))
1105            }
1106            Token::Tilde | Token::Bang if self.mode.relations => {
1107                self.advance()?;
1108                self.parse_not(arena)
1109            }
1110            Token::LParen => {
1111                self.advance()?;
1112                let inner = self.outside_app_arg(|p| p.parse_expr(arena, 0))?;
1113                self.expect(&Token::RParen)?;
1114                Ok(inner)
1115            }
1116            other => Err(ParseError {
1117                message: format!("expected expression, got {:?}", other),
1118                position: self.lexer.pos,
1119            }),
1120        }
1121    }
1122
1123    /// Prefix `not`/`~`/`!` (the keyword or symbol already consumed): the
1124    /// operand is one relation.
1125    fn parse_not(&mut self, arena: &mut Arena) -> Result<ExprId, ParseError> {
1126        let operand = self.parse_expr(arena, BP_NOT)?;
1127        self.boolean_operand(arena, "not", operand)?;
1128        Ok(arena.not(operand))
1129    }
1130
1131    /// Largest number of arguments accepted by any function.
1132    const MAX_FN_ARGS: usize = 32;
1133
1134    fn parse_function_call(&mut self, arena: &mut Arena, name: &str) -> Result<ExprId, ParseError> {
1135        self.expect(&Token::LParen)?;
1136
1137        if self.current == Token::RParen {
1138            self.advance()?;
1139            return Err(ParseError {
1140                message: format!("function '{}' requires an argument", name),
1141                position: self.lexer.pos,
1142            });
1143        }
1144
1145        // Case-insensitive function name matching for SymPy compatibility
1146        let name_lower = name.to_ascii_lowercase();
1147        let mut args: Vec<ExprId> = vec![self.parse_expr(arena, 0)?];
1148
1149        // `Sum(body, k=lo..hi)` / `Product(body, k=lo..hi)` — the display form.
1150        if matches!(name_lower.as_str(), "sum" | "product") && self.current == Token::Comma {
1151            self.advance()?;
1152            let var = self.parse_expr(arena, 0)?;
1153            if self.current == Token::Eq {
1154                self.advance()?;
1155                let lo = self.parse_expr(arena, 0)?;
1156                self.expect(&Token::DotDot)?;
1157                let hi = self.parse_expr(arena, 0)?;
1158                self.expect(&Token::RParen)?;
1159                return self.make_sum_product(arena, name, &name_lower, args[0], var, lo, hi);
1160            }
1161            args.push(var);
1162        }
1163
1164        while self.current == Token::Comma {
1165            self.advance()?;
1166            if args.len() >= Self::MAX_FN_ARGS {
1167                return Err(ParseError {
1168                    message: format!(
1169                        "function '{}' has too many arguments (max {})",
1170                        name,
1171                        Self::MAX_FN_ARGS
1172                    ),
1173                    position: self.lexer.pos,
1174                });
1175            }
1176            args.push(self.parse_expr(arena, 0)?);
1177        }
1178        self.expect(&Token::RParen)?;
1179
1180        // SymPy's function forms of the relations and connectives
1181        // (`Eq(x, 1)`, `And(a, b, c)`, `Not(a)`), only when parsing a relation.
1182        if self.mode.relations
1183            && let Some(result) = self.call_boolean(arena, name, &name_lower, &args)?
1184        {
1185            return Ok(result);
1186        }
1187
1188        // Variadic functions.
1189        if matches!(name_lower.as_str(), "min" | "max") {
1190            if args.len() < 2 {
1191                return Err(ParseError {
1192                    message: format!("function '{}' requires at least 2 arguments", name),
1193                    position: self.lexer.pos,
1194                });
1195            }
1196            let ids: SmallVec<[ExprId; 4]> = args.iter().copied().collect();
1197            return Ok(arena.intern(if name_lower == "min" {
1198                ExprNode::Min(ids)
1199            } else {
1200                ExprNode::Max(ids)
1201            }));
1202        }
1203
1204        match args.len() {
1205            1 => self.call_1(arena, name, &name_lower, args[0]),
1206            2 => self.call_2(arena, name, &name_lower, args[0], args[1]),
1207            3 => self.call_3(arena, name, &name_lower, args[0], args[1], args[2]),
1208            4 => self.call_4(arena, name, &name_lower, args[0], args[1], args[2], args[3]),
1209            n => Err(ParseError {
1210                message: format!(
1211                    "unknown {n}-argument function '{}'. Only min and max take more than 4 arguments",
1212                    name
1213                ),
1214                position: self.lexer.pos,
1215            }),
1216        }
1217    }
1218
1219    /// `Eq`/`Ne`/`Lt`/`Le`/`Gt`/`Ge(a, b)`, `And`/`Or(a, b, …)`, `Not(a)`
1220    /// (SymPy spellings; `Mode::RELATIONS` only).  `Ok(None)` for any other
1221    /// name.
1222    fn call_boolean(
1223        &self,
1224        arena: &mut Arena,
1225        name: &str,
1226        name_lower: &str,
1227        args: &[ExprId],
1228    ) -> Result<Option<ExprId>, ParseError> {
1229        let rel = match name_lower {
1230            "eq" => Some(RelOp::Eq),
1231            "ne" => Some(RelOp::Ne),
1232            "lt" => Some(RelOp::Lt),
1233            "le" => Some(RelOp::Le),
1234            "gt" => Some(RelOp::Gt),
1235            "ge" => Some(RelOp::Ge),
1236            _ => None,
1237        };
1238        if let Some(rel) = rel {
1239            let [lhs, rhs] = args else {
1240                return Err(self.error(format!(
1241                    "'{name}' takes exactly 2 arguments, got {}",
1242                    args.len()
1243                )));
1244            };
1245            self.numeric_operands(arena, rel.text(), *lhs, *rhs)?;
1246            return Ok(Some(match rel {
1247                RelOp::Lt => arena.gt(*rhs, *lhs),
1248                RelOp::Le => arena.ge(*rhs, *lhs),
1249                RelOp::Gt => arena.gt(*lhs, *rhs),
1250                RelOp::Ge => arena.ge(*lhs, *rhs),
1251                RelOp::Eq => arena.eq_(*lhs, *rhs),
1252                RelOp::Ne => arena.ne_(*lhs, *rhs),
1253            }));
1254        }
1255        match name_lower {
1256            "and" | "or" => {
1257                for &a in args {
1258                    self.boolean_operand(arena, name, a)?;
1259                }
1260                Ok(Some(if name_lower == "and" {
1261                    arena.and(args)
1262                } else {
1263                    arena.or(args)
1264                }))
1265            }
1266            "not" => {
1267                let [a] = args else {
1268                    return Err(self.error(format!(
1269                        "'{name}' takes exactly 1 argument, got {}",
1270                        args.len()
1271                    )));
1272                };
1273                self.boolean_operand(arena, name, *a)?;
1274                Ok(Some(arena.not(*a)))
1275            }
1276            _ => Ok(None),
1277        }
1278    }
1279
1280    /// Build `Sum`/`Product` after validating that the index is a symbol.
1281    #[allow(clippy::too_many_arguments)]
1282    fn make_sum_product(
1283        &self,
1284        arena: &mut Arena,
1285        name: &str,
1286        name_lower: &str,
1287        body: ExprId,
1288        var: ExprId,
1289        lo: ExprId,
1290        hi: ExprId,
1291    ) -> Result<ExprId, ParseError> {
1292        if !matches!(arena.node(var), ExprNode::Symbol(_)) {
1293            return Err(ParseError {
1294                message: format!(
1295                    "the index of '{}' must be a symbol, got '{}'",
1296                    name,
1297                    arena.display(var)
1298                ),
1299                position: self.lexer.pos,
1300            });
1301        }
1302        Ok(arena.intern(if name_lower == "sum" {
1303            ExprNode::Sum(body, var, lo, hi)
1304        } else {
1305            ExprNode::Product_(body, var, lo, hi)
1306        }))
1307    }
1308
1309    #[allow(clippy::too_many_arguments)]
1310    fn call_4(
1311        &self,
1312        arena: &mut Arena,
1313        name: &str,
1314        name_lower: &str,
1315        arg: ExprId,
1316        arg2: ExprId,
1317        arg3: ExprId,
1318        arg4: ExprId,
1319    ) -> Result<ExprId, ParseError> {
1320        match name_lower {
1321            "series" => Ok(arena.intern(ExprNode::Series(arg, arg2, arg3, arg4))),
1322            // `Integral(f, x, a, b)` — the Display form of a definite integral;
1323            // the constructor applies only the cheap folds.
1324            "integral" => Ok(arena.definite_integral(arg, arg2, arg3, arg4)),
1325            // SymPy-style `Sum(f, k, a, b)` / `Product(f, k, a, b)`.
1326            "sum" | "product" => {
1327                self.make_sum_product(arena, name, name_lower, arg, arg2, arg3, arg4)
1328            }
1329            "jacobi" => Ok(apply_named(arena, FN_JACOBI, &[arg, arg2, arg3, arg4])),
1330            // SymPy-style `betainc(a, b, x1, x2)` / `betainc_regularized(a, b, x1, x2)`.
1331            "betainc" => Ok(apply_named(arena, FN_BETAINC, &[arg, arg2, arg3, arg4])),
1332            "betainc_regularized" => Ok(apply_named(
1333                arena,
1334                FN_BETAINC_REGULARIZED,
1335                &[arg, arg2, arg3, arg4],
1336            )),
1337            _ => Err(ParseError {
1338                message: format!(
1339                    "unknown 4-argument function '{}'. Supported: Series, Sum, Product, Integral, \
1340                     jacobi, betainc, betainc_regularized",
1341                    name
1342                ),
1343                position: self.lexer.pos,
1344            }),
1345        }
1346    }
1347
1348    fn call_3(
1349        &self,
1350        arena: &mut Arena,
1351        name: &str,
1352        name_lower: &str,
1353        arg: ExprId,
1354        arg2: ExprId,
1355        arg3: ExprId,
1356    ) -> Result<ExprId, ParseError> {
1357        match name_lower {
1358            "limit" => Ok(arena.intern(ExprNode::Limit(arg, arg2, arg3))),
1359            "laplacetransform" => Ok(arena.intern(ExprNode::LaplaceTransform(arg, arg2, arg3))),
1360            "inverselaplacetransform" => {
1361                Ok(arena.intern(ExprNode::InverseLaplaceTransform(arg, arg2, arg3)))
1362            }
1363            "residue" => Ok(arena.intern(ExprNode::Residue(arg, arg2, arg3))),
1364            "dsolve" => Ok(arena.intern(ExprNode::DSolve(arg, arg2, arg3))),
1365            // Orthogonal polynomials with a parameter: (n, param, x).
1366            "gegenbauer" => Ok(apply_named(arena, FN_GEGENBAUER, &[arg, arg2, arg3])),
1367            "assoc_legendre" => Ok(apply_named(arena, FN_ASSOC_LEGENDRE, &[arg, arg2, arg3])),
1368            "assoc_laguerre" => Ok(apply_named(arena, FN_ASSOC_LAGUERRE, &[arg, arg2, arg3])),
1369            _ => Err(ParseError {
1370                message: format!(
1371                    "unknown 3-argument function '{}'. Supported: Limit, LaplaceTransform, \
1372                     InverseLaplaceTransform, Residue, DSolve, min, max, gegenbauer, \
1373                     assoc_legendre, assoc_laguerre",
1374                    name
1375                ),
1376                position: self.lexer.pos,
1377            }),
1378        }
1379    }
1380
1381    fn call_2(
1382        &self,
1383        arena: &mut Arena,
1384        name: &str,
1385        name_lower: &str,
1386        arg: ExprId,
1387        arg2: ExprId,
1388    ) -> Result<ExprId, ParseError> {
1389        match name_lower {
1390            "log" => {
1391                // log(x, base) = ln(x) / ln(base)
1392                let ln_x = arena.ln(arg);
1393                let ln_base = arena.ln(arg2);
1394                Ok(arena.div(ln_x, ln_base))
1395            }
1396            "rootof" => Ok(arena.intern(ExprNode::RootOf(arg, arg2))),
1397            "conditionset" => Ok(arena.intern(ExprNode::ConditionSet(arg, arg2))),
1398            // `Integral(f, x)` — the Display form of an indefinite integral.
1399            "integral" => Ok(arena.intern(ExprNode::Integral(arg, arg2))),
1400            "atan2" => Ok(arena.atan2(arg, arg2)),
1401            "polygamma" => Ok(arena.polygamma(arg, arg2)),
1402            "kroneckerdelta" | "kronecker_delta" => Ok(arena.kronecker_delta(arg, arg2)),
1403            // Combinatorics / special functions (`C(n, k)` and `B(a, b)` are
1404            // the display forms).
1405            "binomial" | "c" => Ok(arena.binomial(arg, arg2)),
1406            "beta" | "b" => Ok(arena.beta(arg, arg2)),
1407            // Bessel functions: order first, as in SymPy and in the display.
1408            "besselj" => Ok(arena.besselj(arg, arg2)),
1409            "bessely" => Ok(arena.bessely(arg, arg2)),
1410            "besseli" => Ok(arena.besseli(arg, arg2)),
1411            "besselk" => Ok(arena.besselk(arg, arg2)),
1412            // More special functions (0.9): parameter first, as in SymPy.
1413            "expint" => Ok(apply_named(arena, FN_EXPINT, &[arg, arg2])),
1414            "lowergamma" => Ok(apply_named(arena, FN_LOWERGAMMA, &[arg, arg2])),
1415            "uppergamma" => Ok(apply_named(arena, FN_UPPERGAMMA, &[arg, arg2])),
1416            "polylog" => Ok(apply_named(arena, FN_POLYLOG, &[arg, arg2])),
1417            "elliptic_f" => Ok(apply_named(arena, FN_ELLIPTIC_F, &[arg, arg2])),
1418            "elliptic_pi" => Ok(apply_named(arena, FN_ELLIPTIC_PI, &[arg, arg2])),
1419            _ => Err(ParseError {
1420                message: format!(
1421                    "unknown 2-argument function '{}'. Supported: log, atan2, polygamma, \
1422                     binomial, beta, besselj, bessely, besseli, besselk, expint, lowergamma, \
1423                     uppergamma, polylog, elliptic_f, elliptic_pi, min, max, KroneckerDelta, \
1424                     RootOf, ConditionSet, Integral",
1425                    name
1426                ),
1427                position: self.lexer.pos,
1428            }),
1429        }
1430    }
1431
1432    fn call_1(
1433        &self,
1434        arena: &mut Arena,
1435        name: &str,
1436        name_lower: &str,
1437        arg: ExprId,
1438    ) -> Result<ExprId, ParseError> {
1439        match name_lower {
1440            "sin" => Ok(arena.sin(arg)),
1441            "cos" => Ok(arena.cos(arg)),
1442            "tan" => Ok(arena.tan(arg)),
1443            "exp" => Ok(arena.exp(arg)),
1444            "ln" | "log" => Ok(arena.ln(arg)),
1445            "sqrt" => Ok(arena.sqrt(arg)),
1446            "cbrt" => Ok(arena.cbrt(arg)),
1447            "abs" => Ok(arena.abs(arg)),
1448            "asin" | "arcsin" => Ok(arena.asin(arg)),
1449            "acos" | "arccos" => Ok(arena.acos(arg)),
1450            "atan" | "arctan" => Ok(arena.atan(arg)),
1451            "sinh" => Ok(arena.sinh(arg)),
1452            "cosh" => Ok(arena.cosh(arg)),
1453            "tanh" => Ok(arena.tanh(arg)),
1454            "asinh" | "arcsinh" => Ok(arena.asinh(arg)),
1455            "acosh" | "arccosh" => Ok(arena.acosh(arg)),
1456            "atanh" | "arctanh" => Ok(arena.atanh(arg)),
1457            "sign" | "sgn" => Ok(arena.sign(arg)),
1458            "floor" => Ok(arena.floor(arg)),
1459            "ceil" | "ceiling" => Ok(arena.ceiling(arg)),
1460            "gamma" => Ok(arena.intern(crate::base::node::ExprNode::Gamma(arg))),
1461            "erf" => Ok(arena.intern(crate::base::node::ExprNode::Erf(arg))),
1462            "erfc" => Ok(arena.intern(crate::base::node::ExprNode::Erfc(arg))),
1463            "heaviside" => Ok(arena.intern(crate::base::node::ExprNode::Heaviside(arg))),
1464            "diracdelta" | "dirac_delta" => {
1465                Ok(arena.intern(crate::base::node::ExprNode::DiracDelta(arg)))
1466            }
1467            "lambertw" | "w" => Ok(arena.intern(crate::base::node::ExprNode::LambertW(arg))),
1468            "factorial" => Ok(arena.intern(crate::base::node::ExprNode::Factorial(arg))),
1469            "digamma" => Ok(arena.intern(crate::base::node::ExprNode::Digamma(arg))),
1470            "loggamma" => Ok(arena.intern(crate::base::node::ExprNode::LogGamma(arg))),
1471            // Reciprocal trig / hyperbolic functions (no dedicated nodes;
1472            // the same forms `Ex::cot` & co. build).
1473            "cot" => {
1474                let c = arena.cos(arg);
1475                let s = arena.sin(arg);
1476                Ok(arena.div(c, s))
1477            }
1478            "sec" => {
1479                let c = arena.cos(arg);
1480                Ok(arena.div(arena.one, c))
1481            }
1482            "csc" => {
1483                let s = arena.sin(arg);
1484                Ok(arena.div(arena.one, s))
1485            }
1486            "coth" => {
1487                let c = arena.cosh(arg);
1488                let s = arena.sinh(arg);
1489                Ok(arena.div(c, s))
1490            }
1491            "sech" => {
1492                let c = arena.cosh(arg);
1493                Ok(arena.div(arena.one, c))
1494            }
1495            "csch" => {
1496                let s = arena.sinh(arg);
1497                Ok(arena.div(arena.one, s))
1498            }
1499            "acot" | "arccot" => {
1500                let inv = arena.div(arena.one, arg);
1501                Ok(arena.atan(inv))
1502            }
1503            // Complex analysis
1504            "re" => Ok(arena.re(arg)),
1505            "im" => Ok(arena.im(arg)),
1506            "conjugate" | "conj" => Ok(arena.conjugate(arg)),
1507            "arg" => Ok(arena.arg(arg)),
1508            // Special functions (0.2)
1509            "si" => Ok(arena.si(arg)),
1510            "ci" => Ok(arena.ci(arg)),
1511            "ei" => Ok(arena.ei(arg)),
1512            "li" => Ok(arena.li(arg)),
1513            "zeta" => Ok(arena.zeta(arg)),
1514            // More special functions (0.9)
1515            "erfi" => Ok(apply_named(arena, FN_ERFI, &[arg])),
1516            "erfinv" => Ok(apply_named(arena, FN_ERFINV, &[arg])),
1517            "erfcinv" => Ok(apply_named(arena, FN_ERFCINV, &[arg])),
1518            "e1" => Ok(apply_named(arena, FN_EXPINT, &[arena.one, arg])),
1519            "shi" => Ok(apply_named(arena, FN_SHI, &[arg])),
1520            "chi" => Ok(apply_named(arena, FN_CHI, &[arg])),
1521            "fresnels" => Ok(apply_named(arena, FN_FRESNELS, &[arg])),
1522            "fresnelc" => Ok(apply_named(arena, FN_FRESNELC, &[arg])),
1523            "dirichlet_eta" => Ok(apply_named(arena, FN_DIRICHLET_ETA, &[arg])),
1524            "airyai" => Ok(apply_named(arena, FN_AIRYAI, &[arg])),
1525            "airybi" => Ok(apply_named(arena, FN_AIRYBI, &[arg])),
1526            "airyaiprime" => Ok(apply_named(arena, FN_AIRYAIPRIME, &[arg])),
1527            "airybiprime" => Ok(apply_named(arena, FN_AIRYBIPRIME, &[arg])),
1528            "elliptic_k" => Ok(apply_named(arena, FN_ELLIPTIC_K, &[arg])),
1529            "elliptic_e" => Ok(apply_named(arena, FN_ELLIPTIC_E, &[arg])),
1530            _ => Err(ParseError {
1531                message: format!(
1532                    "unknown function '{}'. Supported: sin, cos, tan, cot, sec, csc, exp, ln, log, \
1533                     sqrt, cbrt, abs, asin, acos, atan, acot, sinh, cosh, tanh, coth, sech, csch, \
1534                     asinh, acosh, atanh, sign, floor, ceil, gamma, erf, erfc, heaviside, \
1535                     diracdelta, lambertw, factorial, digamma, loggamma, re, im, conjugate, arg, \
1536                     Si, Ci, Ei, li, zeta, polygamma, binomial, beta, besselj, bessely, besseli, \
1537                     besselk, erfi, erfinv, erfcinv, E1, expint, Shi, Chi, fresnels, fresnelc, \
1538                     lowergamma, uppergamma, polylog, dirichlet_eta, airyai, airybi, \
1539                     airyaiprime, airybiprime, elliptic_k, elliptic_e, elliptic_f, elliptic_pi, \
1540                     gegenbauer, jacobi, assoc_legendre, assoc_laguerre, betainc, \
1541                     betainc_regularized, min, max, \
1542                     KroneckerDelta, Limit, RootOf, ConditionSet, LaplaceTransform, \
1543                     InverseLaplaceTransform, Residue, DSolve, Series, Sum, Product, Integral",
1544                    name
1545                ),
1546                position: self.lexer.pos,
1547            }),
1548        }
1549    }
1550}
1551
1552/// Intern a library `Apply(name, args)` node (the 0.9 special functions,
1553/// which have no dedicated `Arena` constructors).
1554fn apply_named(arena: &mut Arena, name: &str, args: &[ExprId]) -> ExprId {
1555    let sid = arena.symbols.intern(name);
1556    arena.intern(ExprNode::Apply(sid, args.iter().copied().collect()))
1557}
1558
1559// ═══════════════════════════════════════════════════════════════════════════
1560// Tests
1561// ═══════════════════════════════════════════════════════════════════════════
1562
1563#[cfg(test)]
1564mod tests {
1565    use super::*;
1566    use crate::api::context::Context;
1567
1568    fn parse_and_display(input: &str) -> String {
1569        let ctx = Context::new();
1570        let ex = parse(&ctx, input).unwrap();
1571        format!("{ex}")
1572    }
1573
1574    #[test]
1575    fn parse_integer() {
1576        assert_eq!(parse_and_display("42"), "42");
1577    }
1578
1579    #[test]
1580    fn parse_symbol() {
1581        assert_eq!(parse_and_display("x"), "x");
1582    }
1583
1584    #[test]
1585    fn parse_addition() {
1586        assert_eq!(parse_and_display("x + y"), "x + y");
1587    }
1588
1589    #[test]
1590    fn parse_polynomial() {
1591        let s = parse_and_display("x^2 + 2*x + 1");
1592        assert!(s.contains("x^2") && s.contains("2*x"), "got: {s}");
1593    }
1594
1595    #[test]
1596    fn parse_function_sin() {
1597        assert_eq!(parse_and_display("sin(x)"), "sin(x)");
1598    }
1599
1600    #[test]
1601    fn parse_nested_functions() {
1602        assert_eq!(parse_and_display("sin(cos(x))"), "sin(cos(x))");
1603    }
1604
1605    #[test]
1606    fn parse_negation() {
1607        let s = parse_and_display("-x");
1608        assert!(s.contains("x") && s.starts_with('-'), "got: {s}");
1609    }
1610
1611    #[test]
1612    fn parse_power_right_assoc() {
1613        // x^2^3 should parse as x^(2^3) due to right-associativity
1614        let s = parse_and_display("x^2^3");
1615        assert!(s.contains("x"), "got: {s}");
1616    }
1617
1618    #[test]
1619    fn parse_constant_pi() {
1620        assert_eq!(parse_and_display("pi"), "pi");
1621    }
1622
1623    #[test]
1624    fn parse_precedence() {
1625        // 2 + 3*x should parse as 2 + (3*x)
1626        let s = parse_and_display("2 + 3*x");
1627        assert!(s.contains("3*x"), "got: {s}");
1628    }
1629
1630    #[test]
1631    fn parse_parens() {
1632        let s = parse_and_display("(x + 1)^2");
1633        assert!(
1634            s.contains("(1 + x)^2") || s.contains("(x + 1)^2"),
1635            "got: {s}"
1636        );
1637    }
1638
1639    #[test]
1640    fn parse_division() {
1641        let s = parse_and_display("x / y");
1642        // Division may be displayed as x*y^(-1) or x*1/y etc.
1643        assert!(
1644            s.contains("1/y") || s.contains("x*1/y") || s.contains("x/y") || s.contains("y^(-1)"),
1645            "got: {s}"
1646        );
1647    }
1648
1649    #[test]
1650    fn parse_empty_string_error() {
1651        let ctx = Context::new();
1652        assert!(parse(&ctx, "").is_err());
1653    }
1654
1655    #[test]
1656    fn parse_unknown_function_error() {
1657        let ctx = Context::new();
1658        assert!(parse(&ctx, "foo(x)").is_err());
1659    }
1660
1661    #[test]
1662    fn parse_unary_minus_precedence() {
1663        // -x^2 should parse as -(x^2), not (-x)^2
1664        let s = parse_and_display("-x^2");
1665        // Should be displayed as -x^2 (meaning -(x^2))
1666        assert!(s.contains("x^2"), "got: {s}");
1667    }
1668
1669    #[test]
1670    fn parse_subtraction() {
1671        let s = parse_and_display("x - y");
1672        // Subtraction is x + (-y), display may vary
1673        assert!(s.contains("x") && s.contains("y"), "got: {s}");
1674    }
1675
1676    #[test]
1677    fn parse_multiple_operations() {
1678        let s = parse_and_display("2*x + 3*y - z");
1679        assert!(
1680            s.contains("2*x") && s.contains("3*y") && s.contains("z"),
1681            "got: {s}"
1682        );
1683    }
1684
1685    #[test]
1686    fn parse_constant_e() {
1687        assert_eq!(parse_and_display("e"), "E");
1688    }
1689
1690    #[test]
1691    fn parse_exp_function() {
1692        let s = parse_and_display("exp(x)");
1693        // exp(x) may display as E^x or exp(x) depending on arena
1694        assert!(s.contains("x"), "got: {s}");
1695    }
1696
1697    #[test]
1698    fn parse_sqrt_function() {
1699        let s = parse_and_display("sqrt(x)");
1700        // sqrt(x) may display as x^(1/2) or sqrt(x)
1701        assert!(s.contains("x"), "got: {s}");
1702    }
1703
1704    #[test]
1705    fn parse_complex_nested() {
1706        let s = parse_and_display("sin(x^2 + 1)");
1707        assert!(s.contains("sin"), "got: {s}");
1708    }
1709
1710    #[test]
1711    fn parse_trailing_garbage_error() {
1712        let ctx = Context::new();
1713        // With implicit multiplication, "x y" is now valid (x*y).
1714        // Use truly invalid trailing tokens instead.
1715        assert!(parse(&ctx, "x )").is_err());
1716    }
1717
1718    #[test]
1719    fn parse_unmatched_paren_error() {
1720        let ctx = Context::new();
1721        assert!(parse(&ctx, "(x + 1").is_err());
1722    }
1723
1724    #[test]
1725    fn parse_unexpected_char_error() {
1726        let ctx = Context::new();
1727        assert!(parse(&ctx, "x & y").is_err());
1728    }
1729
1730    #[test]
1731    fn parse_empty_function_call_error() {
1732        let ctx = Context::new();
1733        assert!(parse(&ctx, "sin()").is_err());
1734    }
1735
1736    #[test]
1737    fn parse_abs_function() {
1738        let s = parse_and_display("abs(x)");
1739        assert!(s.contains("x"), "got: {s}");
1740    }
1741
1742    #[test]
1743    fn parse_ln_function() {
1744        let s = parse_and_display("ln(x)");
1745        assert!(s.contains("x"), "got: {s}");
1746    }
1747
1748    #[test]
1749    fn parse_log_alias() {
1750        let s = parse_and_display("log(x)");
1751        assert!(s.contains("x"), "got: {s}");
1752    }
1753
1754    #[test]
1755    fn parse_constant_infinity() {
1756        let s = parse_and_display("inf");
1757        assert!(
1758            s.contains("oo") || s.contains("inf") || s.contains("∞"),
1759            "got: {s}"
1760        );
1761    }
1762
1763    #[test]
1764    fn parse_deeply_nested_parens() {
1765        let s = parse_and_display("((((x))))");
1766        assert_eq!(s, "x");
1767    }
1768
1769    #[test]
1770    fn parse_chained_additions() {
1771        let s = parse_and_display("a + b + c + d");
1772        assert!(
1773            s.contains("a") && s.contains("b") && s.contains("c") && s.contains("d"),
1774            "got: {s}"
1775        );
1776    }
1777
1778    #[test]
1779    fn parse_chained_multiplications() {
1780        let s = parse_and_display("a * b * c");
1781        assert!(
1782            s.contains("a") && s.contains("b") && s.contains("c"),
1783            "got: {s}"
1784        );
1785    }
1786
1787    #[test]
1788    fn parse_mixed_precedence() {
1789        // a + b * c should parse as a + (b*c)
1790        let s = parse_and_display("a + b * c");
1791        assert!(s.contains("b*c") || s.contains("c*b"), "got: {s}");
1792    }
1793
1794    // ═══════════════════════════════════════════════════════════════════
1795    // New feature tests
1796    // ═══════════════════════════════════════════════════════════════════
1797
1798    #[test]
1799    fn parse_float() {
1800        let ctx = Context::new();
1801        let result = parse(&ctx, "3.14").unwrap();
1802        let s = format!("{result}");
1803        // 3.14 should parse as 314/100 = 157/50
1804        assert!(
1805            s.contains("157") || s.contains("3.14") || s.contains("314"),
1806            "got: {s}"
1807        );
1808    }
1809
1810    #[test]
1811    fn parse_implicit_mul_number_var() {
1812        let ctx = Context::new();
1813        let result = parse(&ctx, "2x").unwrap();
1814        let s = format!("{result}");
1815        assert!(s.contains("2") && s.contains("x"), "2x should be 2*x: {s}");
1816    }
1817
1818    #[test]
1819    fn parse_implicit_mul_var_paren() {
1820        // ident + '(' is always treated as a function call, so x(x+1) errors
1821        // (x is not a known function). Use number*paren or paren*paren instead.
1822        let ctx = Context::new();
1823        assert!(parse(&ctx, "x(x+1)").is_err());
1824    }
1825
1826    #[test]
1827    fn parse_constant_pi_variants() {
1828        assert_eq!(parse_and_display("pi"), "pi");
1829        assert_eq!(parse_and_display("Pi"), "pi");
1830        assert_eq!(parse_and_display("PI"), "pi");
1831    }
1832
1833    #[test]
1834    fn parse_constant_i_unit() {
1835        let ctx = Context::new();
1836        let result = parse(&ctx, "I").unwrap();
1837        assert_eq!(format!("{result}"), "I");
1838    }
1839
1840    #[test]
1841    fn parse_constant_i_lowercase() {
1842        let ctx = Context::new();
1843        let result = parse(&ctx, "i").unwrap();
1844        assert_eq!(format!("{result}"), "I");
1845    }
1846
1847    #[test]
1848    fn parse_euler_formula() {
1849        let ctx = Context::new();
1850        let result = parse(&ctx, "exp(I*pi)").unwrap();
1851        let s = format!("{result}");
1852        assert!(s.contains("I") && s.contains("pi"), "got: {s}");
1853    }
1854
1855    #[test]
1856    fn parse_float_times_var() {
1857        let ctx = Context::new();
1858        let result = parse(&ctx, "2.5*x").unwrap();
1859        let s = format!("{result}");
1860        assert!(
1861            s.contains("x") && (s.contains("5/2") || s.contains("2.5") || s.contains("5*1/2")),
1862            "got: {s}"
1863        );
1864    }
1865
1866    #[test]
1867    fn parse_log_two_args() {
1868        let ctx = Context::new();
1869        // log(x, 2) = ln(x) / ln(2)
1870        let result = parse(&ctx, "log(x, 2)").unwrap();
1871        let s = format!("{result}");
1872        assert!(s.contains("x"), "log(x,2) should parse: {s}");
1873    }
1874
1875    #[test]
1876    fn parse_implicit_mul_number_paren() {
1877        let ctx = Context::new();
1878        let result = parse(&ctx, "3(x+1)").unwrap();
1879        let s = format!("{result}");
1880        assert!(
1881            s.contains("3") && s.contains("x"),
1882            "3(x+1) should be 3*(x+1): {s}"
1883        );
1884    }
1885
1886    #[test]
1887    fn parse_implicit_mul_paren_paren() {
1888        let ctx = Context::new();
1889        let result = parse(&ctx, "(a)(b)").unwrap();
1890        let s = format!("{result}");
1891        assert!(
1892            s.contains("a") && s.contains("b"),
1893            "(a)(b) should be a*b: {s}"
1894        );
1895    }
1896
1897    #[test]
1898    fn parse_implicit_mul_coeff_pi() {
1899        let ctx = Context::new();
1900        let result = parse(&ctx, "2pi").unwrap();
1901        let s = format!("{result}");
1902        assert!(
1903            s.contains("2") && s.contains("pi"),
1904            "2pi should be 2*pi: {s}"
1905        );
1906    }
1907
1908    // ═══════════════════════════════════════════════════════════════════════
1909    // Arbitrary-precision integer tests
1910    // ═══════════════════════════════════════════════════════════════════════
1911
1912    #[test]
1913    fn parse_large_integer() {
1914        let ctx = Context::new();
1915        let result = parse(&ctx, "99999999999999999999999999999").unwrap();
1916        let s = format!("{result}");
1917        assert_eq!(s, "99999999999999999999999999999");
1918    }
1919
1920    #[test]
1921    fn parse_integer_beyond_i64_max() {
1922        let ctx = Context::new();
1923        // i64::MAX = 9223372036854775807 — this is one more
1924        let result = parse(&ctx, "9223372036854775808").unwrap();
1925        let s = format!("{result}");
1926        assert_eq!(
1927            s, "9223372036854775808",
1928            "should handle integers > i64::MAX"
1929        );
1930    }
1931
1932    #[test]
1933    fn parse_integer_beyond_i128_max() {
1934        let ctx = Context::new();
1935        // i128::MAX ≈ 1.7e38 — this is well beyond
1936        let big = "123456789012345678901234567890123456789012345678901234567890";
1937        let result = parse(&ctx, big).unwrap();
1938        let s = format!("{result}");
1939        assert_eq!(s, big, "should handle integers > i128::MAX");
1940    }
1941
1942    #[test]
1943    fn parse_large_integer_arithmetic() {
1944        let ctx = Context::new();
1945        // (10^30)^2 should be 10^60
1946        let result = parse(&ctx, "1000000000000000000000000000000^2").unwrap();
1947        let evaled = result.eval();
1948        let s = format!("{evaled}");
1949        assert_eq!(
1950            s, "1000000000000000000000000000000000000000000000000000000000000",
1951            "large integer exponentiation should be exact"
1952        );
1953    }
1954
1955    #[test]
1956    fn parse_negative_large_integer() {
1957        let ctx = Context::new();
1958        let result = parse(&ctx, "-99999999999999999999999999999").unwrap();
1959        let s = format!("{result}");
1960        assert_eq!(s, "-99999999999999999999999999999");
1961    }
1962
1963    // ═══════════════════════════════════════════════════════════════════════
1964    // Arbitrary-precision decimal / rational tests
1965    // ═══════════════════════════════════════════════════════════════════════
1966
1967    #[test]
1968    fn parse_many_decimal_places() {
1969        let ctx = Context::new();
1970        // This should not panic (was overflowing i64 before)
1971        let result = parse(&ctx, "1.0000000000000000000001");
1972        assert!(
1973            result.is_ok(),
1974            "parsing many decimal places should not panic"
1975        );
1976    }
1977
1978    #[test]
1979    fn parse_decimal_exact_rational_simple() {
1980        let ctx = Context::new();
1981        // 0.5 should be exactly 1/2
1982        let result = parse(&ctx, "0.5").unwrap();
1983        let s = format!("{result}");
1984        assert_eq!(s, "1/2", "0.5 should parse as exact rational 1/2, got: {s}");
1985    }
1986
1987    #[test]
1988    fn parse_decimal_exact_rational_quarter() {
1989        let ctx = Context::new();
1990        // 0.25 should be exactly 1/4
1991        let result = parse(&ctx, "0.25").unwrap();
1992        let s = format!("{result}");
1993        assert_eq!(
1994            s, "1/4",
1995            "0.25 should parse as exact rational 1/4, got: {s}"
1996        );
1997    }
1998
1999    #[test]
2000    fn parse_decimal_exact_rational_third_approx() {
2001        let ctx = Context::new();
2002        // 0.333 should be exactly 333/1000
2003        let result = parse(&ctx, "0.333").unwrap();
2004        let s = format!("{result}");
2005        assert_eq!(
2006            s, "333/1000",
2007            "0.333 should parse as exact rational 333/1000, got: {s}"
2008        );
2009    }
2010
2011    #[test]
2012    fn parse_decimal_preserves_all_digits() {
2013        let ctx = Context::new();
2014        // 1.00000000000000000000000000000000001 — 34 zeros then 1
2015        // numerator = 100000000000000000000000000000000001
2016        // denominator = 10^35
2017        // This must NOT lose any precision
2018        let input = "1.00000000000000000000000000000000001";
2019        let result = parse(&ctx, input).unwrap();
2020        // Multiply by 10^35 — should give exactly 100000000000000000000000000000000001
2021        let big_denom = parse(&ctx, "100000000000000000000000000000000000").unwrap();
2022        let product = &result * &big_denom;
2023        let s = format!("{}", product.eval());
2024        assert_eq!(
2025            s, "100000000000000000000000000000000001",
2026            "1.00000000000000000000000000000000001 * 10^35 should be exact, got: {s}"
2027        );
2028    }
2029
2030    #[test]
2031    fn parse_decimal_20_places_exact() {
2032        let ctx = Context::new();
2033        // bc: 314159265358979323846 / 2 = 157079632679489661923
2034        // bc: 100000000000000000000 / 2 = 50000000000000000000
2035        // GCD(314159265358979323846, 100000000000000000000) = 2
2036        // Reduced: 157079632679489661923/50000000000000000000
2037        let result = parse(&ctx, "3.14159265358979323846").unwrap();
2038        let s = format!("{result}");
2039        assert_eq!(
2040            s, "157079632679489661923/50000000000000000000",
2041            "20-digit decimal should be exact reduced rational"
2042        );
2043    }
2044
2045    #[test]
2046    fn parse_decimal_20_places_multiply_back() {
2047        let ctx = Context::new();
2048        // Verify: 157079632679489661923/50000000000000000000 * 50000000000000000000
2049        //       = 157079632679489661923 (bc-verified)
2050        let result = parse(&ctx, "3.14159265358979323846").unwrap();
2051        let denom = parse(&ctx, "50000000000000000000").unwrap();
2052        let product = (&result * &denom).eval();
2053        let s = format!("{product}");
2054        assert_eq!(
2055            s, "157079632679489661923",
2056            "rational * denominator should recover exact numerator"
2057        );
2058    }
2059
2060    #[test]
2061    fn parse_decimal_50_places_exact() {
2062        let ctx = Context::new();
2063        // 50 decimal places — well beyond any fixed-precision type
2064        // Must parse without error AND produce an exact rational
2065        let input = "3.14159265358979323846264338327950288419716939937510";
2066        let result = parse(&ctx, input).unwrap();
2067        // Multiply by 10^50 to recover the exact numerator
2068        // bc: the full numerator is 314159265358979323846264338327950288419716939937510
2069        let big = parse(&ctx, "100000000000000000000000000000000000000000000000000").unwrap();
2070        let product = (&result * &big).eval();
2071        let s = format!("{product}");
2072        assert_eq!(
2073            s, "314159265358979323846264338327950288419716939937510",
2074            "50-digit decimal * 10^50 must recover exact integer (bc-verified)"
2075        );
2076    }
2077
2078    #[test]
2079    fn parse_decimal_large_integer_part_and_fraction_exact() {
2080        let ctx = Context::new();
2081        // 123456789012345678901234567890.123456789012345678901234567890
2082        // = 123456789012345678901234567890123456789012345678901234567890 / 10^30
2083        // Verify by multiplying by 10^30
2084        let result = parse(
2085            &ctx,
2086            "123456789012345678901234567890.123456789012345678901234567890",
2087        )
2088        .unwrap();
2089        let denom = parse(&ctx, "1000000000000000000000000000000").unwrap(); // 10^30
2090        let product = (&result * &denom).eval();
2091        let s = format!("{product}");
2092        assert_eq!(
2093            s, "123456789012345678901234567890123456789012345678901234567890",
2094            "large decimal * 10^30 must recover exact integer (bc-verified)"
2095        );
2096    }
2097
2098    #[test]
2099    fn parse_decimal_used_in_arithmetic() {
2100        let ctx = Context::new();
2101        // 0.1 + 0.2 should be exactly 3/10 (no floating-point 0.30000000000000004 nonsense)
2102        let result = parse(&ctx, "0.1 + 0.2").unwrap();
2103        let s = format!("{result}");
2104        assert_eq!(s, "3/10", "0.1 + 0.2 should be exactly 3/10, got: {s}");
2105    }
2106
2107    #[test]
2108    fn parse_decimal_multiplication_exact() {
2109        let ctx = Context::new();
2110        // 0.1 * 0.1 should be exactly 1/100
2111        let result = parse(&ctx, "0.1 * 0.1").unwrap();
2112        let s = format!("{result}");
2113        assert_eq!(s, "1/100", "0.1 * 0.1 should be exactly 1/100, got: {s}");
2114    }
2115
2116    #[test]
2117    fn parse_decimal_vs_fraction_equivalence() {
2118        let ctx = Context::new();
2119        // 2.5 * x should be the same as 5/2 * x
2120        let x = ctx.symbol("x");
2121        let from_decimal = parse(&ctx, "2.5 * x").unwrap();
2122        let five_halves = ctx.rational(5, 2);
2123        let from_fraction = &five_halves * &x;
2124        assert_eq!(
2125            from_decimal, from_fraction,
2126            "2.5*x and (5/2)*x should be identical expressions"
2127        );
2128    }
2129
2130    // ═══════════════════════════════════════════════════════════════════════
2131    // Edge cases and robustness
2132    // ═══════════════════════════════════════════════════════════════════════
2133
2134    #[test]
2135    fn parse_zero_integer() {
2136        let ctx = Context::new();
2137        let result = parse(&ctx, "0").unwrap();
2138        let s = format!("{result}");
2139        assert_eq!(s, "0");
2140    }
2141
2142    #[test]
2143    fn parse_zero_decimal() {
2144        let ctx = Context::new();
2145        let result = parse(&ctx, "0.0").unwrap();
2146        let s = format!("{result}");
2147        assert_eq!(s, "0", "0.0 should parse as 0, got: {s}");
2148    }
2149
2150    #[test]
2151    fn parse_leading_zeros_integer() {
2152        let ctx = Context::new();
2153        let result = parse(&ctx, "007").unwrap();
2154        let s = format!("{result}");
2155        assert_eq!(s, "7", "007 should parse as 7, got: {s}");
2156    }
2157
2158    #[test]
2159    fn parse_leading_zeros_decimal() {
2160        let ctx = Context::new();
2161        let result = parse(&ctx, "0.00100").unwrap();
2162        let s = format!("{result}");
2163        assert_eq!(s, "1/1000", "0.00100 should parse as 1/1000, got: {s}");
2164    }
2165
2166    #[test]
2167    fn parse_deep_nesting_limit() {
2168        let ctx = Context::new();
2169        let deep = "(".repeat(300) + "1" + &")".repeat(300);
2170        let result = parse(&ctx, &deep);
2171        assert!(
2172            result.is_err(),
2173            "deeply nested input should return error, not stack overflow"
2174        );
2175    }
2176
2177    #[test]
2178    fn parse_moderate_nesting_ok() {
2179        let ctx = Context::new();
2180        // 50 levels of nesting should be fine (well under 256 limit)
2181        let expr = "(".repeat(50) + "x + 1" + &")".repeat(50);
2182        let result = parse(&ctx, &expr);
2183        assert!(result.is_ok(), "50 levels of nesting should be fine");
2184    }
2185
2186    #[test]
2187    fn parse_decimal_with_variable_exact_coeff() {
2188        let ctx = Context::new();
2189        // bc: 314/100 = 157/50 (GCD=2). So 3.14*x = 157/50*x
2190        let result = parse(&ctx, "3.14 * x").unwrap();
2191        let s = format!("{result}");
2192        assert_eq!(
2193            s, "157/50*x",
2194            "3.14*x should have exact coefficient 157/50, got: {s}"
2195        );
2196    }
2197
2198    #[test]
2199    fn parse_integer_one() {
2200        let ctx = Context::new();
2201        let result = parse(&ctx, "1").unwrap();
2202        let s = format!("{result}");
2203        assert_eq!(s, "1");
2204    }
2205
2206    #[test]
2207    fn parse_negative_decimal() {
2208        let ctx = Context::new();
2209        // -0.5 = -5/10 = -1/2
2210        let result = parse(&ctx, "-0.5").unwrap();
2211        let s = format!("{result}");
2212        assert_eq!(s, "-1/2", "-0.5 should parse as -1/2, got: {s}");
2213    }
2214
2215    // ═══════════════════════════════════════════════════════════════════════
2216    // bc-verified: arithmetic on parsed arbitrary-precision values
2217    // ═══════════════════════════════════════════════════════════════════════
2218
2219    #[test]
2220    fn parse_beyond_i64_arithmetic() {
2221        let ctx = Context::new();
2222        // bc: 9223372036854775808 + 1 = 9223372036854775809
2223        let result = parse(&ctx, "9223372036854775808 + 1").unwrap();
2224        let s = format!("{result}");
2225        assert_eq!(
2226            s, "9223372036854775809",
2227            "i64::MAX+1 + 1 should be exact (bc-verified)"
2228        );
2229    }
2230
2231    #[test]
2232    fn parse_beyond_i128() {
2233        let ctx = Context::new();
2234        // i128::MAX = 170141183460469231731687303715884105727
2235        // bc: 170141183460469231731687303715884105727 + 1 = 170141183460469231731687303715884105728
2236        let result = parse(&ctx, "170141183460469231731687303715884105728").unwrap();
2237        let s = format!("{result}");
2238        assert_eq!(
2239            s, "170141183460469231731687303715884105728",
2240            "i128::MAX+1 should parse and display exactly"
2241        );
2242    }
2243
2244    #[test]
2245    fn parse_huge_integer_squared() {
2246        let ctx = Context::new();
2247        // bc: 99999999999999999999999999999 * 99999999999999999999999999999
2248        //   = 9999999999999999999999999999800000000000000000000000000001
2249        let base = parse(&ctx, "99999999999999999999999999999").unwrap();
2250        let squared = base.powi(2).eval();
2251        let s = format!("{squared}");
2252        assert_eq!(
2253            s, "9999999999999999999999999999800000000000000000000000000001",
2254            "(10^29 - 1)^2 should be exact (bc-verified)"
2255        );
2256    }
2257
2258    #[test]
2259    fn parse_tiny_number_exact() {
2260        let ctx = Context::new();
2261        // 0.000000000000000000000000000000000001 = 1/10^36
2262        // bc: 10^36 = 1000000000000000000000000000000000000
2263        // Verify: multiply by 10^36 should give exactly 1
2264        let tiny = parse(&ctx, "0.000000000000000000000000000000000001").unwrap();
2265        let big = parse(&ctx, "1000000000000000000000000000000000000").unwrap();
2266        let product = (&tiny * &big).eval();
2267        let s = format!("{product}");
2268        assert_eq!(s, "1", "10^-36 * 10^36 should be exactly 1 (bc-verified)");
2269    }
2270
2271    #[test]
2272    fn parse_tiny_number_display() {
2273        let ctx = Context::new();
2274        // 0.000000000000000000000000000000000001 = 1/10^36
2275        let tiny = parse(&ctx, "0.000000000000000000000000000000000001").unwrap();
2276        let s = format!("{tiny}");
2277        assert_eq!(
2278            s, "1/1000000000000000000000000000000000000",
2279            "10^-36 should display as exact fraction (bc-verified)"
2280        );
2281    }
2282
2283    #[test]
2284    fn parse_large_integer_addition() {
2285        let ctx = Context::new();
2286        // bc: 123456789012345678901234567890 + 1 = 123456789012345678901234567891
2287        let result = parse(&ctx, "123456789012345678901234567890 + 1").unwrap();
2288        let s = format!("{result}");
2289        assert_eq!(
2290            s, "123456789012345678901234567891",
2291            "large integer + 1 should be exact (bc-verified)"
2292        );
2293    }
2294
2295    // ═══════════════════════════════════════════════════════════════════════════
2296    // SymPy compatibility
2297    // ═══════════════════════════════════════════════════════════════════════════
2298
2299    #[test]
2300    fn parse_sympy_power_operator() {
2301        assert_eq!(parse_and_display("x**2"), parse_and_display("x^2"));
2302    }
2303
2304    #[test]
2305    fn parse_sympy_double_star_in_expression() {
2306        assert_eq!(
2307            parse_and_display("3*x**2 + 2"),
2308            parse_and_display("3*x^2 + 2")
2309        );
2310    }
2311
2312    #[test]
2313    fn parse_sympy_abs_capital() {
2314        assert_eq!(parse_and_display("Abs(x)"), parse_and_display("abs(x)"));
2315    }
2316
2317    #[test]
2318    fn parse_sympy_full_expression() {
2319        let ctx = Context::new();
2320        // SymPy output for integrate(x*ln(x))
2321        let result = parse(&ctx, "x**2*log(x)/2 - x**2/4").unwrap();
2322        let s = format!("{result}");
2323        assert!(
2324            s.contains("ln") && s.contains("x"),
2325            "should parse SymPy integrate output: {s}"
2326        );
2327    }
2328
2329    #[test]
2330    fn parse_sympy_trig_identity() {
2331        let ctx = Context::new();
2332        let result = parse(&ctx, "sin(x)**2 + cos(x)**2").unwrap();
2333        // Should simplify to 1 via full_simplify
2334        let simplified = result.simplify();
2335        assert_eq!(format!("{simplified}"), "1");
2336    }
2337
2338    // ── 0.2 nodes ──────────────────────────────────────────────────────────
2339
2340    #[test]
2341    fn parse_named_constants() {
2342        let ctx = Context::new();
2343        assert_eq!(parse(&ctx, "EulerGamma").unwrap(), ctx.euler_gamma());
2344        assert_eq!(parse(&ctx, "Catalan").unwrap(), ctx.catalan());
2345        assert_eq!(parse(&ctx, "GoldenRatio").unwrap(), ctx.golden_ratio());
2346        assert_eq!(parse(&ctx, "zoo").unwrap(), ctx.complex_infinity());
2347        assert_eq!(
2348            parse_and_display("EulerGamma + Catalan"),
2349            "EulerGamma + Catalan"
2350        );
2351    }
2352
2353    #[test]
2354    fn parse_complex_functions() {
2355        let ctx = Context::new();
2356        let z = ctx.symbol("z");
2357        assert_eq!(parse(&ctx, "re(z)").unwrap(), z.re());
2358        assert_eq!(parse(&ctx, "im(z)").unwrap(), z.im());
2359        assert_eq!(parse(&ctx, "conjugate(z)").unwrap(), z.conjugate());
2360        assert_eq!(parse(&ctx, "conj(z)").unwrap(), z.conjugate());
2361        assert_eq!(parse(&ctx, "arg(z)").unwrap(), z.arg());
2362        assert_eq!(parse_and_display("Re(3 + 4*I)"), "3");
2363        assert_eq!(parse_and_display("im(3 + 4*I)"), "4");
2364    }
2365
2366    #[test]
2367    fn parse_special_functions() {
2368        let ctx = Context::new();
2369        let x = ctx.symbol("x");
2370        let n = ctx.symbol("n");
2371        assert_eq!(parse(&ctx, "Si(x)").unwrap(), x.si());
2372        assert_eq!(parse(&ctx, "Ci(x)").unwrap(), x.ci());
2373        assert_eq!(parse(&ctx, "Ei(x)").unwrap(), x.ei());
2374        assert_eq!(parse(&ctx, "li(x)").unwrap(), x.li());
2375        assert_eq!(parse(&ctx, "zeta(x)").unwrap(), x.zeta());
2376        assert_eq!(parse(&ctx, "polygamma(n, x)").unwrap(), x.polygamma(&n));
2377        assert_eq!(
2378            parse(&ctx, "KroneckerDelta(n, x)").unwrap(),
2379            x.kronecker_delta(&n)
2380        );
2381        assert_eq!(
2382            parse(&ctx, "kronecker_delta(n, x)").unwrap(),
2383            x.kronecker_delta(&n)
2384        );
2385        assert_eq!(parse_and_display("zeta(2)"), "1/6*pi^2");
2386        assert_eq!(parse_and_display("Si(0)"), "0");
2387        assert_eq!(parse_and_display("atan2(1, 1)"), "atan2(1, 1)");
2388    }
2389
2390    // ── 0.9.1: relations, connectives, implicit application ───────────────
2391
2392    #[test]
2393    fn parse_bool_relations_and_connectives() {
2394        let ctx = Context::new();
2395        let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
2396        let zero = ctx.int(0);
2397        let one = ctx.int(1);
2398        assert_eq!(parse_bool(&ctx, "x > 0").unwrap(), x.gt(&zero));
2399        assert_eq!(parse_bool(&ctx, "x >= 0").unwrap(), x.ge(&zero));
2400        assert_eq!(parse_bool(&ctx, "x < 1").unwrap(), x.lt(&one));
2401        assert_eq!(parse_bool(&ctx, "x <= 1").unwrap(), x.le(&one));
2402        assert_eq!(parse_bool(&ctx, "x == 1").unwrap(), x.eq_expr(&one));
2403        assert_eq!(parse_bool(&ctx, "x != 1").unwrap(), x.ne_expr(&one));
2404        assert_eq!(
2405            parse_bool(&ctx, "x > 0 & x < 1").unwrap(),
2406            x.gt(&zero).and(&x.lt(&one))
2407        );
2408        assert_eq!(
2409            parse_bool(&ctx, "x > 0 && x < 1").unwrap(),
2410            parse_bool(&ctx, "x > 0 and x < 1").unwrap()
2411        );
2412        assert_eq!(
2413            parse_bool(&ctx, "x > 0 | y > 0").unwrap(),
2414            x.gt(&zero).or(&y.gt(&zero))
2415        );
2416        assert_eq!(
2417            parse_bool(&ctx, "x > 0 || y > 0").unwrap(),
2418            parse_bool(&ctx, "x > 0 or y > 0").unwrap()
2419        );
2420        assert_eq!(parse_bool(&ctx, "~(x > 0)").unwrap(), x.gt(&zero).not());
2421        assert_eq!(parse_bool(&ctx, "!(x > 0)").unwrap(), x.gt(&zero).not());
2422        assert_eq!(parse_bool(&ctx, "not x > 0").unwrap(), x.gt(&zero).not());
2423        assert_eq!(parse_bool(&ctx, "True").unwrap().to_string(), "True");
2424        assert_eq!(parse_bool(&ctx, "false").unwrap().to_string(), "False");
2425    }
2426
2427    #[test]
2428    fn parse_bool_precedence() {
2429        let ctx = Context::new();
2430        let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
2431        let zero = ctx.int(0);
2432        let one = ctx.int(1);
2433        // `and` binds tighter than `or`; comparisons tighter than both.
2434        assert_eq!(
2435            parse_bool(&ctx, "x > 0 & x < 1 | y == 0").unwrap(),
2436            x.gt(&zero).and(&x.lt(&one)).or(&y.eq_expr(&zero))
2437        );
2438        assert_eq!(
2439            parse_bool(&ctx, "x > 0 | x < 1 & y == 0").unwrap(),
2440            x.gt(&zero).or(&x.lt(&one).and(&y.eq_expr(&zero)))
2441        );
2442        // Arithmetic binds tighter than comparisons.
2443        assert_eq!(
2444            parse_bool(&ctx, "x + 1 > 2*y").unwrap(),
2445            (&x + 1).gt(&(2 * &y))
2446        );
2447        // `not` takes one relation, not the whole conjunction.
2448        assert_eq!(
2449            parse_bool(&ctx, "not x > 0 & y > 0").unwrap(),
2450            x.gt(&zero).not().and(&y.gt(&zero))
2451        );
2452        // `a & b & c` is one n-ary node.
2453        assert_eq!(
2454            parse_bool(&ctx, "x > 0 & y > 0 & x < 1")
2455                .unwrap()
2456                .to_string(),
2457            "x > 0 & y > 0 & 1 > x"
2458        );
2459        // Parentheses regroup.
2460        assert_eq!(
2461            parse_bool(&ctx, "(x > 0 | y > 0) & x < 1").unwrap(),
2462            x.gt(&zero).or(&y.gt(&zero)).and(&x.lt(&one))
2463        );
2464    }
2465
2466    #[test]
2467    fn parse_bool_function_forms() {
2468        let ctx = Context::new();
2469        let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
2470        let one = ctx.int(1);
2471        assert_eq!(parse_bool(&ctx, "Eq(x, 1)").unwrap(), x.eq_expr(&one));
2472        assert_eq!(parse_bool(&ctx, "Ne(x, 1)").unwrap(), x.ne_expr(&one));
2473        assert_eq!(parse_bool(&ctx, "Lt(x, 1)").unwrap(), x.lt(&one));
2474        assert_eq!(parse_bool(&ctx, "Le(x, 1)").unwrap(), x.le(&one));
2475        assert_eq!(parse_bool(&ctx, "Gt(x, 1)").unwrap(), x.gt(&one));
2476        assert_eq!(parse_bool(&ctx, "Ge(x, 1)").unwrap(), x.ge(&one));
2477        assert_eq!(
2478            parse_bool(&ctx, "And(x > 1, y > 1)").unwrap(),
2479            x.gt(&one).and(&y.gt(&one))
2480        );
2481        assert_eq!(
2482            parse_bool(&ctx, "Or(x > 1, y > 1)").unwrap(),
2483            x.gt(&one).or(&y.gt(&one))
2484        );
2485        assert_eq!(parse_bool(&ctx, "Not(x > 1)").unwrap(), x.gt(&one).not());
2486    }
2487
2488    #[test]
2489    fn parse_bool_errors() {
2490        let ctx = Context::new();
2491        // A numeric expression is not a relation.
2492        assert!(parse_bool(&ctx, "x + 1").is_err());
2493        // Chained comparisons.
2494        assert!(parse_bool(&ctx, "0 < x < 1").is_err());
2495        // Sort errors.
2496        assert!(parse_bool(&ctx, "(x > 0) + 1").is_err());
2497        assert!(parse_bool(&ctx, "x & y").is_err());
2498        assert!(parse_bool(&ctx, "not x").is_err());
2499        assert!(parse_bool(&ctx, "(x > 0) > 1").is_err());
2500        assert!(parse_bool(&ctx, "-(x > 0)").is_err());
2501        // Trailing garbage.
2502        assert!(parse_bool(&ctx, "x > 0 &").is_err());
2503    }
2504
2505    #[test]
2506    fn parse_strict_rejects_relations_and_keeps_keywords_as_symbols() {
2507        let ctx = Context::new();
2508        assert!(parse(&ctx, "x > 0").is_err());
2509        assert!(parse(&ctx, "x & y").is_err());
2510        assert!(parse(&ctx, "~x").is_err());
2511        assert!(parse(&ctx, "x != 1").is_err());
2512        // In the numeric grammar `and`, `True` are ordinary identifiers.
2513        assert_eq!(parse(&ctx, "and").unwrap(), ctx.symbol("and"));
2514        assert_eq!(parse(&ctx, "True").unwrap(), ctx.symbol("True"));
2515        // `Sum(body, k=lo..hi)` still uses a single `=`.
2516        assert!(parse(&ctx, "Sum(k, k=1..3)").is_ok());
2517    }
2518
2519    #[test]
2520    fn parse_implicit_application() {
2521        let ctx = Context::new();
2522        let (x, y) = (ctx.symbol("x"), ctx.symbol("y"));
2523        assert_eq!(parse_implicit(&ctx, "sin x").unwrap(), x.sin());
2524        assert_eq!(parse_implicit(&ctx, "2 sin x").unwrap(), 2 * &x.sin());
2525        assert_eq!(parse_implicit(&ctx, "sin 2x").unwrap(), (2 * &x).sin());
2526        assert_eq!(parse_implicit(&ctx, "sin x^2").unwrap(), x.powi(2).sin());
2527        assert_eq!(
2528            parse_implicit(&ctx, "sin x cos y").unwrap(),
2529            &x.sin() * &y.cos()
2530        );
2531        assert_eq!(parse_implicit(&ctx, "sin x + 1").unwrap(), &x.sin() + 1);
2532        assert_eq!(parse_implicit(&ctx, "sin x/2").unwrap(), (&x / 2).sin());
2533        assert_eq!(parse_implicit(&ctx, "sin x y").unwrap(), (&x * &y).sin());
2534        assert_eq!(parse_implicit(&ctx, "exp (x) y").unwrap(), &x.exp() * &y);
2535        assert_eq!(parse_implicit(&ctx, "sqrt 2").unwrap(), ctx.int(2).sqrt());
2536        assert_eq!(parse_implicit(&ctx, "ln x^2").unwrap(), x.powi(2).ln());
2537        // Parenthesised calls still work and reset the argument scope.
2538        assert_eq!(
2539            parse_implicit(&ctx, "sin(x cos y)").unwrap(),
2540            (&x * &y.cos()).sin()
2541        );
2542    }
2543
2544    #[test]
2545    fn parse_implicit_products() {
2546        let ctx = Context::new();
2547        let (x, y, z) = (ctx.symbol("x"), ctx.symbol("y"), ctx.symbol("z"));
2548        assert_eq!(
2549            parse_implicit(&ctx, "2x + 3(y-1)").unwrap(),
2550            2 * &x + 3 * (&y - 1)
2551        );
2552        assert_eq!(parse_implicit(&ctx, "x y z").unwrap(), &x * &y * &z);
2553        assert_eq!(parse_implicit(&ctx, "x(x+1)").unwrap(), &x * (&x + 1));
2554        assert_eq!(
2555            parse_implicit(&ctx, "(x+1)(x-1)").unwrap(),
2556            (&x + 1) * (&x - 1)
2557        );
2558        // Unknown `f(x)` is a product; constants multiply groups too; the
2559        // one-letter aliases `C`/`B`/`W` are ordinary symbols here.
2560        let f = ctx.symbol("f");
2561        assert_eq!(parse_implicit(&ctx, "f(x)").unwrap(), &f * &x);
2562        let c = ctx.symbol("c");
2563        assert_eq!(parse_implicit(&ctx, "c(x+1)").unwrap(), &c * (&x + 1));
2564        assert_eq!(
2565            parse_implicit(&ctx, "binomial(x, 2)").unwrap(),
2566            parse(&ctx, "C(x, 2)").unwrap()
2567        );
2568        assert_eq!(parse_implicit(&ctx, "pi(x)").unwrap(), ctx.pi() * &x);
2569        assert_eq!(parse_implicit(&ctx, "2 pi x").unwrap(), 2 * ctx.pi() * &x);
2570    }
2571
2572    #[test]
2573    fn parse_implicit_errors_and_limits() {
2574        let ctx = Context::new();
2575        // A textbook function name without an argument.
2576        assert!(parse_implicit(&ctx, "sin + 1").is_err());
2577        assert!(parse_implicit(&ctx, "sin").is_err());
2578        // Ambiguous short names need parentheses: `re x` is `re*x`.
2579        let (re, x) = (ctx.symbol("re"), ctx.symbol("x"));
2580        assert_eq!(parse_implicit(&ctx, "re x").unwrap(), &re * &x);
2581        assert_eq!(parse_implicit(&ctx, "re(x)").unwrap(), x.re());
2582        // Relations are not part of the implicit grammar.
2583        assert!(parse_implicit(&ctx, "x > 0").is_err());
2584    }
2585
2586    #[test]
2587    fn known_functions_table_matches_call_tables() {
2588        // Every name in `KNOWN_FUNCTIONS` is accepted by some call table.
2589        let ctx = Context::new();
2590        for name in KNOWN_FUNCTIONS {
2591            let ok = [
2592                format!("{name}(x)"),
2593                format!("{name}(x, y)"),
2594                format!("{name}(x, y, z)"),
2595                format!("{name}(x, y, z, w)"),
2596            ]
2597            .iter()
2598            .any(|s| parse(&ctx, s).is_ok());
2599            assert!(ok, "`{name}` is listed but no arity parses");
2600        }
2601        for name in KNOWN_FUNCTIONS {
2602            assert!(
2603                !is_implicit_unary_function(name) || parse(&ctx, &format!("{name}(x)")).is_ok(),
2604                "`{name}` is applied implicitly but is not a unary function"
2605            );
2606        }
2607    }
2608}