exp_rs/
engine.rs

1extern crate alloc;
2
3#[cfg(not(test))]
4use alloc::rc::Rc;
5#[cfg(test)]
6use std::rc::Rc;
7
8use crate::Real;
9#[cfg(not(test))]
10use crate::Vec;
11use crate::context::EvalContext;
12use crate::error::ExprError;
13use crate::lexer::{Lexer, Token};
14use crate::types::{AstExpr, TokenKind};
15use bumpalo::Bump;
16
17use alloc::borrow::Cow;
18#[cfg(not(test))]
19use alloc::format;
20use alloc::string::{String, ToString};
21
22/// Pratt parser for mathematical expressions
23#[cfg(not(test))]
24use alloc::collections::BTreeSet as HashSet;
25#[cfg(test)]
26use std::collections::HashSet;
27
28struct PrattParser<'input, 'arena> {
29    lexer: Lexer<'input>,
30    arena: &'arena Bump, // Arena is now mandatory
31    current: Option<Token>,
32    errors: Vec<ExprError>,
33    recursion_depth: usize,
34    max_recursion_depth: usize,
35    reserved_vars: Option<HashSet<Cow<'input, str>>>, // Parameter names to treat as variables, not functions
36    context_vars: Option<HashSet<Cow<'input, str>>>,  // Variable/constant names from context
37}
38
39/// Token binding powers for the Pratt parser
40#[derive(Debug, Clone, Copy)]
41struct BindingPower {
42    left: u8,
43    right: u8,
44}
45
46impl BindingPower {
47    const fn new(left: u8, right: u8) -> Self {
48        Self { left, right }
49    }
50
51    // For left-associative operators, right binding power is left + 1
52    const fn left_assoc(power: u8) -> Self {
53        Self::new(power, power + 1)
54    }
55
56    // For right-associative operators, right binding power is same as left
57    const fn right_assoc(power: u8) -> Self {
58        Self::new(power, power)
59    }
60}
61
62impl<'input, 'arena> PrattParser<'input, 'arena> {
63    fn new(input: &'input str, arena: &'arena Bump) -> Self {
64        let mut lexer = Lexer::new(input);
65        let current = lexer.next_token();
66        Self {
67            lexer,
68            arena,
69            current,
70            errors: Vec::new(),
71            recursion_depth: 0,
72            max_recursion_depth: 2000,
73            reserved_vars: None,
74            context_vars: None,
75        }
76    }
77
78    fn with_reserved_vars_and_context(
79        input: &'input str,
80        arena: &'arena Bump,
81        reserved_vars: Option<&'input [String]>,
82        context_vars: Option<&'input [String]>,
83    ) -> Self {
84        let mut parser = Self::new(input, arena);
85        if let Some(vars) = reserved_vars {
86            let mut set = HashSet::new();
87            for v in vars {
88                set.insert(Cow::Borrowed(v.as_str()));
89            }
90            parser.reserved_vars = Some(set);
91        }
92        if let Some(vars) = context_vars {
93            let mut set = HashSet::new();
94            for v in vars {
95                set.insert(Cow::Borrowed(v.as_str()));
96            }
97            parser.context_vars = Some(set);
98        }
99        parser
100    }
101
102    fn peek(&self) -> Option<&Token> {
103        self.current.as_ref()
104    }
105
106    fn next(&mut self) -> Option<Token> {
107        let tok = self.current.take();
108        self.current = self.lexer.next_token();
109        tok
110    }
111
112    fn expect(&mut self, kind: TokenKind, error_msg: &str) -> Result<Token, ExprError> {
113        if let Some(tok) = self.peek() {
114            if tok.kind == kind {
115                return Ok(self.next().unwrap());
116            }
117
118            // If we're expecting a closing parenthesis and don't find it,
119            // provide a more specific error message
120            if kind == TokenKind::Close {
121                let position = tok.position;
122                let found = tok.text.clone().unwrap_or_else(|| "unknown".to_string());
123                return Err(ExprError::UnmatchedParenthesis { position, found });
124            }
125        }
126
127        let position = self.peek().map(|t| t.position).unwrap_or(0);
128        let found = self
129            .peek()
130            .and_then(|t| t.text.clone())
131            .unwrap_or_else(|| "end of input".to_string());
132
133        let err = ExprError::Syntax(format!(
134            "{} at position {}, found '{}'",
135            error_msg, position, found
136        ));
137        self.errors.push(err.clone());
138        Err(err)
139    }
140
141    // Get binding power for an operator
142    fn get_binding_power(op: &str) -> Option<BindingPower> {
143        match op {
144            "," | ";" => Some(BindingPower::left_assoc(1)), // List separator (comma or semicolon)
145            "?" => Some(BindingPower::right_assoc(1)), // Ternary conditional operator (lowest precedence)
146            "||" => Some(BindingPower::left_assoc(2)), // Logical OR (lowest precedence)
147            "&&" => Some(BindingPower::left_assoc(3)), // Logical AND (higher than OR)
148            "|" => Some(BindingPower::left_assoc(4)),  // Bitwise OR
149            "&" => Some(BindingPower::left_assoc(6)),  // Bitwise AND
150            "==" | "!=" | "<" | ">" | "<=" | ">=" | "<>" => Some(BindingPower::left_assoc(7)), // Comparison
151            "<<" | ">>" | "<<<" | ">>>" => Some(BindingPower::left_assoc(8)), // Bit shifts
152            "+" | "-" => Some(BindingPower::left_assoc(9)), // Addition, subtraction
153            "*" | "/" | "%" => Some(BindingPower::left_assoc(10)), // Multiplication, division, modulo
154            "^" => Some(BindingPower::right_assoc(15)), // Exponentiation (right-associative, higher than unary)
155            "**" => Some(BindingPower::right_assoc(16)), // Exponentiation (right-associative, higher than ^)
156            ":" => None, // Colon is handled as part of the ternary operator, not independently
157            _ => None,
158        }
159    }
160
161    // Get binding power for a prefix operator
162    fn get_prefix_binding_power(op: &str) -> Option<u8> {
163        match op {
164            "+" | "-" | "~" => Some(14), // Must be lower than ^ and ** for correct -2^2 parsing
165            _ => None,
166        }
167    }
168
169    // Unified method for handling all postfix operations
170    fn parse_postfix(&mut self, lhs: AstExpr<'arena>) -> Result<AstExpr<'arena>, ExprError> {
171        let mut result = lhs;
172
173        // Keep applying postfix operators as long as they're available
174        loop {
175            if let Some(tok) = self.peek() {
176                match (tok.kind, tok.text.as_deref()) {
177                    (TokenKind::Open, Some("(")) => {
178                        // Function call
179                        result = self.parse_function_call(result)?;
180                    }
181                    (TokenKind::Open, Some("[")) => {
182                        // Array access
183                        result = self.parse_array_access(result)?;
184                    }
185                    (TokenKind::Operator, Some(".")) => {
186                        // Attribute access
187                        result = self.parse_attribute_access(result)?;
188                    }
189                    _ => break, // No more postfix operators
190                }
191            } else {
192                break;
193            }
194        }
195
196        Ok(result)
197    }
198
199    // Unified error handling for all parenthesis-like structures
200    fn expect_closing(
201        &mut self,
202        kind: TokenKind,
203        expected: &str,
204        opening_position: usize,
205    ) -> Result<(), ExprError> {
206        if let Some(tok) = self.peek() {
207            if tok.kind == kind {
208                self.next(); // Consume the closing token
209                return Ok(());
210            }
211
212            // If not the expected token, report an error
213            let position = tok.position;
214            let found = tok.text.clone().unwrap_or_else(|| "unknown".to_string());
215
216            return Err(ExprError::Syntax(format!(
217                "Expected {} at position {}, found '{}' (opening at position {})",
218                expected, position, found, opening_position
219            )));
220        }
221
222        // End of input
223        Err(ExprError::Syntax(format!(
224            "Expected {} but found end of input (opening at position {})",
225            expected, opening_position
226        )))
227    }
228
229    // Helper method for parsing parenthesized expressions
230    fn parse_parenthesized_expr(&mut self) -> Result<AstExpr<'arena>, ExprError> {
231        let open_position = self.peek().map(|t| t.position).unwrap_or(0);
232        self.next(); // consume '('
233
234        // Parse the expression inside the parentheses
235        // Always allow commas inside parentheses
236        let expr = self.parse_expr_unified(0, true)?;
237
238        // Always check for closing parenthesis
239        if let Some(tok) = self.peek() {
240            if tok.kind == TokenKind::Close {
241                self.next(); // Consume the closing parenthesis
242                return Ok(expr);
243            }
244
245            // If not a closing parenthesis, report an error
246            let position = tok.position;
247            let found = tok.text.clone().unwrap_or_else(|| "unknown".to_string());
248            return Err(ExprError::Syntax(format!(
249                "Expected closing parenthesis ')' but found '{}' at position {} (opening at position {})",
250                found, position, open_position
251            )));
252        }
253
254        // End of input
255        Err(ExprError::Syntax(format!(
256            "Expected closing parenthesis ')' but found end of input (opening at position {})",
257            open_position
258        )))
259    }
260
261    // Helper method for parsing function calls
262    fn parse_function_call(&mut self, expr: AstExpr<'arena>) -> Result<AstExpr<'arena>, ExprError> {
263        let name = match &expr {
264            AstExpr::Variable(name) => *name,
265            AstExpr::Attribute { attr, .. } => *attr,
266            _ => {
267                return Err(ExprError::Syntax(
268                    "Function call on non-function expression".to_string(),
269                ));
270            }
271        };
272
273        self.next(); // consume '('
274
275        let mut args = bumpalo::collections::Vec::new_in(self.arena);
276
277        // Parse arguments
278        if let Some(tok) = self.peek() {
279            if tok.kind != TokenKind::Close {
280                // Parse the first argument
281                let arg = self.parse_expr_unified(0, false)?;
282                args.push(arg);
283
284                // Check for comma or closing parenthesis
285                while let Some(next_tok) = self.peek() {
286                    if next_tok.kind == TokenKind::Separator
287                        && next_tok.text.as_deref() == Some(",")
288                    {
289                        self.next(); // consume ','
290
291                        // Parse the next argument
292                        let arg = self.parse_expr_unified(0, false)?;
293                        args.push(arg);
294                    } else if next_tok.kind == TokenKind::Close {
295                        break;
296                    } else {
297                        // Unexpected token - report error
298                        let position = next_tok.position;
299                        let found = next_tok
300                            .text
301                            .clone()
302                            .unwrap_or_else(|| "unknown".to_string());
303                        return Err(ExprError::Syntax(format!(
304                            "Expected ',' or ')' but found '{}' at position {} in function call",
305                            found, position
306                        )));
307                    }
308                }
309            }
310        }
311
312        // Check for closing parenthesis
313        if let Some(tok) = self.peek() {
314            if tok.kind == TokenKind::Close {
315                self.next(); // Consume the closing parenthesis
316            } else {
317                // If not a closing parenthesis, report an error
318                let position = tok.position;
319                let found = tok.text.clone().unwrap_or_else(|| "unknown".to_string());
320                return Err(ExprError::Syntax(format!(
321                    "Expected closing parenthesis ')' but found '{}' at position {} in function call",
322                    found, position
323                )));
324            }
325        } else {
326            // End of input - this is an error because we're missing a closing parenthesis
327            let open_position = self.lexer.get_original_input().len()
328                - self.lexer.get_remaining_input().unwrap_or("").len();
329            return Err(ExprError::UnmatchedParenthesis {
330                position: open_position,
331                found: "(".to_string(),
332            });
333        }
334
335        // Special handling for pow function to ensure it has 2 arguments
336        if name == "pow" && args.len() == 1 {
337            // If pow has only one argument, add a default second argument of 2.0
338            args.push(AstExpr::Constant(2.0));
339        } else if name == "atan2" && args.len() == 1 {
340            // If atan2 has only one argument, add a default second argument of 1.0
341            args.push(AstExpr::Constant(1.0));
342        }
343
344        // Special handling for polynomial function: always 1 argument, do not treat as built-in
345        if name == "polynomial" && args.len() == 1 {
346            // No-op, just clarity: polynomial(x)
347        }
348
349        Ok(AstExpr::Function {
350            name,
351            args: args.into_bump_slice(),
352        })
353    }
354
355    // Helper method for parsing array access
356    fn parse_array_access(&mut self, expr: AstExpr<'arena>) -> Result<AstExpr<'arena>, ExprError> {
357        let name = match &expr {
358            AstExpr::Variable(name) => name,
359            _ => {
360                let position = self.peek().map(|t| t.position).unwrap_or(0);
361                return Err(ExprError::Syntax(format!(
362                    "Array access on non-array expression at position {}",
363                    position
364                )));
365            }
366        };
367
368        let open_position = self.peek().map(|t| t.position).unwrap_or(0);
369        self.next(); // consume '['
370
371        // Parse index expression
372        let index = self.parse_expr_unified(0, true)?;
373
374        // Always expect closing bracket
375        self.expect_closing(TokenKind::Close, "closing bracket ']'", open_position)?;
376
377        Ok(AstExpr::Array {
378            name,
379            index: self.arena.alloc(index),
380        })
381    }
382
383    // Helper method for parsing attribute access
384    fn parse_attribute_access(
385        &mut self,
386        expr: AstExpr<'arena>,
387    ) -> Result<AstExpr<'arena>, ExprError> {
388        let dot_position = self.peek().map(|t| t.position).unwrap_or(0);
389        self.next(); // consume '.'
390
391        // Expect identifier
392        let attr_tok = self.expect(TokenKind::Variable, "Expected attribute name")?;
393
394        let attr = self.arena.alloc_str(&attr_tok.text.unwrap_or_default());
395
396        #[cfg(test)]
397        println!("Parsing attribute access: expr={:?}, attr={}", expr, attr);
398
399        // Only allow attribute access on variables
400        match expr {
401            AstExpr::Variable(base) => {
402                #[cfg(test)]
403                println!("Creating attribute node: {}.{}", base, attr);
404
405                let result = AstExpr::Attribute { base, attr };
406                // Apply any postfix operators to the attribute access result
407                self.parse_postfix(result)
408            }
409            _ => {
410                #[cfg(test)]
411                println!("Error: Attribute access on non-variable expression");
412
413                Err(ExprError::Syntax(format!(
414                    "Attribute access on non-object expression at position {}",
415                    dot_position
416                )))
417            }
418        }
419    }
420
421    // Unified method for parsing expressions with a flag for comma handling
422    fn parse_expr_unified(
423        &mut self,
424        min_bp: u8,
425        allow_comma: bool,
426    ) -> Result<AstExpr<'arena>, ExprError> {
427        // Check recursion depth to prevent stack overflow
428        self.recursion_depth += 1;
429        if self.recursion_depth > self.max_recursion_depth {
430            self.recursion_depth -= 1;
431            return Err(ExprError::RecursionLimit(format!(
432                "Expression too complex: exceeded maximum recursion depth of {}",
433                self.max_recursion_depth
434            )));
435        }
436
437        // Parse prefix or primary expression
438        let mut lhs = self.parse_prefix_or_primary(allow_comma)?;
439
440        // Apply postfix operators (function calls, array access, attribute access)
441        lhs = self.parse_postfix(lhs)?;
442
443        // Parse infix operators
444        lhs = self.parse_infix_operators(lhs, min_bp, allow_comma)?;
445
446        // Juxtaposition parsing disabled - use standard function call syntax with parentheses
447
448        // Always decrement the recursion depth before returning
449        self.recursion_depth -= 1;
450
451        Ok(lhs)
452    }
453
454    fn parse_prefix_or_primary(&mut self, allow_comma: bool) -> Result<AstExpr<'arena>, ExprError> {
455        if let Some(tok) = self.peek() {
456            // Check for error tokens and report them immediately
457            if tok.kind == TokenKind::Error {
458                return Err(ExprError::Syntax(format!(
459                    "Unexpected token '{}' at position {}",
460                    tok.text.as_deref().unwrap_or("unknown"),
461                    tok.position
462                )));
463            }
464            if tok.kind == TokenKind::Operator {
465                let op = tok.text.as_deref().unwrap_or("");
466                let op_position = tok.position;
467                if let Some(r_bp) = Self::get_prefix_binding_power(op) {
468                    // Make a copy of the operator for later use
469                    let op_str = String::from(op);
470
471                    // Consume the operator token
472                    self.next();
473
474                    // Handle the case where there's nothing after the operator
475                    if self.peek().is_none() {
476                        return Err(ExprError::Syntax(format!(
477                            "Expected expression after '{}' at position {}",
478                            op_str, op_position
479                        )));
480                    }
481
482                    // Parse the right-hand side expression
483                    let rhs = self.parse_expr_unified(r_bp, allow_comma)?;
484
485                    // Create the appropriate AST node
486                    if op_str == "-" {
487                        let mut args = bumpalo::collections::Vec::new_in(self.arena);
488                        args.push(rhs);
489                        Ok(AstExpr::Function {
490                            name: self.arena.alloc_str("neg"),
491                            args: args.into_bump_slice(),
492                        })
493                    } else {
494                        // Unary + is a no-op
495                        Ok(rhs)
496                    }
497                } else {
498                    self.parse_primary()
499                }
500            } else {
501                self.parse_primary()
502            }
503        } else {
504            self.parse_primary()
505        }
506    }
507
508    // Helper method for parsing ternary expressions (condition ? true_expr : false_expr)
509    fn parse_ternary_op(
510        &mut self,
511        condition: AstExpr<'arena>,
512        allow_comma: bool,
513    ) -> Result<AstExpr<'arena>, ExprError> {
514        // We've already consumed the '?' at this point
515
516        // Parse the true branch (what to evaluate if condition is true)
517        let true_branch = self.parse_expr_unified(0, allow_comma)?;
518
519        // Expect a colon separating the true and false branches
520        if let Some(tok) = self.peek() {
521            if tok.kind == TokenKind::Operator && tok.text.as_deref() == Some(":") {
522                self.next(); // Consume the ':'
523            } else {
524                return Err(ExprError::Syntax(format!(
525                    "Expected ':' in ternary expression, found '{}'",
526                    tok.text.clone().unwrap_or_else(|| "unknown".to_string())
527                )));
528            }
529        } else {
530            return Err(ExprError::Syntax(
531                "Expected ':' in ternary expression, found end of input".to_string(),
532            ));
533        }
534
535        // Parse the false branch (what to evaluate if condition is false)
536        let false_branch = self.parse_expr_unified(0, allow_comma)?;
537
538        Ok(AstExpr::Conditional {
539            condition: self.arena.alloc(condition),
540            true_branch: self.arena.alloc(true_branch),
541            false_branch: self.arena.alloc(false_branch),
542        })
543    }
544
545    fn parse_infix_operators(
546        &mut self,
547        mut lhs: AstExpr<'arena>,
548        min_bp: u8,
549        allow_comma: bool,
550    ) -> Result<AstExpr<'arena>, ExprError> {
551        loop {
552            // Get the next operator
553            let op_text = if let Some(tok) = self.peek() {
554                if tok.kind == TokenKind::Operator {
555                    tok.text.as_deref().unwrap_or("")
556                } else if tok.kind == TokenKind::Separator
557                    && (tok.text.as_deref() == Some(",") || tok.text.as_deref() == Some(";"))
558                {
559                    // Only treat comma or semicolon as an operator if allowed
560                    if allow_comma {
561                        tok.text.as_deref().unwrap_or("")
562                    } else {
563                        break;
564                    }
565                } else {
566                    break;
567                }
568            } else {
569                break;
570            };
571
572            // Make a copy of the operator for later use
573            let op = String::from(op_text);
574
575            // Special case for ternary operator
576            if op == "?" {
577                // Get binding power
578                let Some(bp) = Self::get_binding_power(&op) else {
579                    break;
580                };
581
582                // Check minimum binding power
583                if bp.left < min_bp {
584                    break;
585                }
586
587                // Consume the ? operator
588                self.next();
589
590                // Parse the rest of the ternary expression
591                lhs = self.parse_ternary_op(lhs, allow_comma)?;
592                continue;
593            }
594
595            // Special case for logical operators
596            if op == "&&" || op == "||" {
597                // Get binding power - these should already be defined in get_binding_power
598                let Some(bp) = Self::get_binding_power(&op) else {
599                    break;
600                };
601
602                // Check minimum binding power
603                if bp.left < min_bp {
604                    break;
605                }
606
607                // Consume the operator
608                self.next();
609
610                // Parse the right side with appropriate binding power
611                let rhs = self.parse_expr_unified(bp.right, allow_comma)?;
612
613                // Create a LogicalOp node instead of a Function node
614                lhs = AstExpr::LogicalOp {
615                    op: if op == "&&" {
616                        crate::types::LogicalOperator::And
617                    } else {
618                        crate::types::LogicalOperator::Or
619                    },
620                    left: self.arena.alloc(lhs),
621                    right: self.arena.alloc(rhs),
622                };
623                continue;
624            }
625
626            // Get binding power for regular (non-logical) operator
627            let Some(bp) = Self::get_binding_power(&op) else {
628                break;
629            };
630
631            // If the operator's left binding power is less than the minimum, we're done
632            if bp.left < min_bp {
633                break;
634            }
635
636            // Consume the operator
637            self.next();
638
639            // Special case for right-associative power operators
640            let rhs = if op == "^" || op == "**" {
641                self.parse_expr_unified(bp.right - 1, allow_comma)?
642            } else {
643                self.parse_expr_unified(bp.right, allow_comma)?
644            };
645
646            // Create a function node for the operator
647            let mut args = bumpalo::collections::Vec::new_in(self.arena);
648            args.push(lhs);
649            args.push(rhs);
650            lhs = AstExpr::Function {
651                name: self.arena.alloc_str(&op),
652                args: args.into_bump_slice(),
653            };
654        }
655        Ok(lhs)
656    }
657
658    #[allow(dead_code)]
659    fn parse_juxtaposition(
660        &mut self,
661        lhs: AstExpr<'arena>,
662        _allow_comma: bool,
663    ) -> Result<AstExpr<'arena>, ExprError> {
664        let mut lhs = lhs;
665        if let Some(tok) = self.peek() {
666            let is_valid_lhs = matches!(&lhs, AstExpr::Variable(_));
667            let is_valid_rhs = matches!(
668                tok.kind,
669                TokenKind::Number | TokenKind::Variable | TokenKind::Open
670            ) || (tok.kind == TokenKind::Operator
671                && (tok.text.as_deref() == Some("-")
672                    || tok.text.as_deref() == Some("+")
673                    || tok.text.as_deref() == Some("~")));
674
675            // If lhs is a variable and is in reserved_vars or context_vars, do NOT allow juxtaposition
676            let is_reserved_var = match &lhs {
677                AstExpr::Variable(name) => {
678                    let reserved = self
679                        .reserved_vars
680                        .as_ref()
681                        .map(|s| s.iter().any(|v| v.as_ref() == *name))
682                        .unwrap_or(false);
683                    let in_context = self
684                        .context_vars
685                        .as_ref()
686                        .map(|s| s.iter().any(|v| v.as_ref() == *name))
687                        .unwrap_or(false);
688                    reserved || in_context
689                }
690                _ => false,
691            };
692
693            if is_valid_lhs && is_valid_rhs && !is_reserved_var {
694                // Get the function name (variable)
695                let func_name = match &lhs {
696                    AstExpr::Variable(name) => name,
697                    _ => unreachable!(),
698                };
699                // Parse only a primary expression for juxtaposition to avoid consuming operators
700                // This ensures "sin 1 + 2" is parsed as "(sin 1) + 2" not "sin(1 + 2)"
701                let arg = self.parse_primary()?;
702
703                // Create a function node
704                let mut args = bumpalo::collections::Vec::new_in(self.arena);
705                args.push(arg);
706                lhs = AstExpr::Function {
707                    name: func_name,
708                    args: args.into_bump_slice(),
709                };
710            }
711        }
712        Ok(lhs)
713    }
714
715    // Parse an expression with the given minimum binding power
716    fn parse_expr(&mut self, min_bp: u8) -> Result<AstExpr<'arena>, ExprError> {
717        self.parse_expr_unified(min_bp, true)
718    }
719
720    // Parse a primary expression (number, variable, parenthesized expression)
721    fn parse_primary(&mut self) -> Result<AstExpr<'arena>, ExprError> {
722        let tok = match self.peek() {
723            Some(tok) => tok,
724            None => return Err(ExprError::Syntax("Unexpected end of input".to_string())),
725        };
726
727        match tok.kind {
728            TokenKind::Number => {
729                let val = tok.value.unwrap_or(0.0);
730                self.next();
731                Ok(AstExpr::Constant(val))
732            }
733            TokenKind::Variable => {
734                let name = match &tok.text {
735                    Some(name) => self.arena.alloc_str(name),
736                    None => return Err(ExprError::Syntax("Variable name is missing".to_string())),
737                };
738                self.next();
739                Ok(AstExpr::Variable(name))
740            }
741            TokenKind::Open if tok.text.as_deref() == Some("(") => self.parse_parenthesized_expr(),
742            TokenKind::Close => {
743                // This is a closing parenthesis without a matching opening parenthesis
744                let position = tok.position;
745                let found = tok.text.clone().unwrap_or_else(|| ")".to_string());
746                Err(ExprError::Syntax(format!(
747                    "Unexpected closing parenthesis at position {}: '{}'",
748                    position, found
749                )))
750            }
751            _ => {
752                let position = tok.position;
753                let found = tok.text.clone().unwrap_or_else(|| "unknown".to_string());
754                Err(ExprError::Syntax(format!(
755                    "Unexpected token at position {}: '{}'",
756                    position, found
757                )))
758            }
759        }
760    }
761
762    // The parse_postfix method is no longer needed as its functionality
763    // has been integrated into parse_expr_unified_inner
764
765    // Check if the expression is too long
766    fn check_expression_length(&self, input: &str) -> Result<(), ExprError> {
767        const MAX_EXPRESSION_LENGTH: usize = 10000; // Reasonable limit
768        if input.len() > MAX_EXPRESSION_LENGTH {
769            return Err(ExprError::Syntax(format!(
770                "Expression too long: {} characters (maximum is {})",
771                input.len(),
772                MAX_EXPRESSION_LENGTH
773            )));
774        }
775        Ok(())
776    }
777
778    // Parse a complete expression
779    fn parse(&mut self) -> Result<AstExpr<'arena>, ExprError> {
780        // Check expression length
781        if let Some(remaining) = self.lexer.get_remaining_input() {
782            self.check_expression_length(remaining)?;
783        }
784
785        // Reset recursion depth before parsing
786        self.recursion_depth = 0;
787
788        // Parse the expression
789        let expr = self.parse_expr(0)?;
790
791        #[cfg(test)]
792        println!("Parsed expression: {:?}", expr);
793
794        // Check for unexpected trailing tokens
795        if let Some(tok) = self.peek() {
796            // Handle error tokens - return an error instead of skipping
797            if tok.kind == TokenKind::Error {
798                return Err(ExprError::Syntax(format!(
799                    "Unexpected token '{}' at position {}",
800                    tok.text.as_deref().unwrap_or("unknown"),
801                    tok.position
802                )));
803            }
804            // Skip trailing whitespace
805            if tok.kind == TokenKind::Operator
806                && tok.text.as_deref().is_some_and(|t| t.trim().is_empty())
807            {
808                self.next();
809            } else if tok.kind == TokenKind::Close {
810                // For expressions like "1)", it's an error
811                return Err(ExprError::Syntax(format!(
812                    "Unexpected closing parenthesis at position {}: check for balanced parentheses",
813                    tok.position
814                )));
815            } else {
816                // Any other trailing token is an error
817                return Err(ExprError::Syntax(format!(
818                    "Unexpected token at position {}: '{}'",
819                    tok.position,
820                    tok.text.clone().unwrap_or_else(|| "unknown".to_string())
821                )));
822            }
823        }
824
825        Ok(expr)
826    }
827}
828
829/// Parse an expression string into an AST.
830///
831/// This is the primary parsing function that requires an explicit arena for memory allocation.
832/// For parsing expression function bodies with parameters, use `parse_expression_with_parameters`.
833///
834/// # Supported Features
835///
836/// - Arithmetic operators: +, -, *, /, %, ^, **
837/// - Comparison operators: ==, !=, <, >, <=, >=, <>
838/// - Logical operators: &&, ||
839/// - Ternary conditional: condition ? true_expr : false_expr
840/// - Function calls: func(arg1, arg2)
841/// - Variables and constants
842/// - Array access: array[index]
843/// - Attribute access: object.attribute
844///
845/// # Examples
846///
847/// ```
848/// use exp_rs::engine::parse_expression;
849/// use bumpalo::Bump;
850///
851/// let arena = Bump::new();
852///
853/// // Basic arithmetic
854/// let expr = parse_expression("2 + 3 * 4", &arena).unwrap();
855///
856/// // Ternary conditional
857/// let expr = parse_expression("x > 0 ? 1 : -1", &arena).unwrap();
858///
859/// // Function calls
860/// let expr = parse_expression("sin(x) + cos(y)", &arena).unwrap();
861/// ```
862pub fn parse_expression<'arena>(
863    input: &str,
864    arena: &'arena Bump,
865) -> Result<AstExpr<'arena>, ExprError> {
866    parse_expression_arena_with_context(input, arena, None, None)
867}
868
869/// Parse an expression with function parameters that should be treated as variables.
870///
871/// This function is specifically designed for parsing expression function bodies where
872/// certain identifiers (the function parameters) should always be treated as variables,
873/// not as function calls. This prevents "x(y)" from being parsed as a function call
874/// when "x" is a parameter.
875///
876/// # Examples
877///
878/// ```
879/// use exp_rs::engine::parse_expression_with_parameters;
880/// use bumpalo::Bump;
881///
882/// let arena = Bump::new();
883///
884/// // Parse expression function body with parameters
885/// let params = vec!["x".to_string(), "y".to_string()];
886/// let expr = parse_expression_with_parameters("x * y + 2", &arena, &params).unwrap();
887/// // In this case, 'x' and 'y' are treated as variables, not potential function calls
888///
889/// // Without this, "x(y)" would be parsed as a function call
890/// let expr = parse_expression_with_parameters("x(y)", &arena, &params).unwrap();
891/// // This correctly parses as multiplication due to juxtaposition: x * y
892/// ```
893pub fn parse_expression_with_parameters<'arena>(
894    input: &str,
895    arena: &'arena Bump,
896    parameters: &[String],
897) -> Result<AstExpr<'arena>, ExprError> {
898    parse_expression_arena_with_context(input, arena, Some(parameters), None)
899}
900
901/// Parse an expression with reserved variables and context variable names.
902///
903/// This is the most configurable parsing function that allows specifying both:
904/// - reserved_vars: Variables that should not be treated as functions in implicit calls
905/// - context_vars: Variables from the evaluation context that should be recognized
906///
907/// Supports all expression features, including ternary operators.
908///
909/// # Parameters
910///
911/// * `input` - The expression string to parse
912/// * `arena` - The arena to allocate AST nodes in
913/// * `reserved_vars` - Optional list of variable names that should not be treated as functions
914/// * `context_vars` - Optional list of variable names from the evaluation context
915pub fn parse_expression_arena_with_context<'arena>(
916    input: &str,
917    arena: &'arena Bump,
918    reserved_vars: Option<&[String]>,
919    context_vars: Option<&[String]>,
920) -> Result<AstExpr<'arena>, ExprError> {
921    // Ternary operators (? :) are now supported
922    // Comparison operators (<, >, <=, >=, ==, !=) are also supported
923
924    // The lexer now properly handles decimal numbers starting with a dot
925    let mut parser =
926        PrattParser::with_reserved_vars_and_context(input, arena, reserved_vars, context_vars);
927    parser.parse()
928}
929
930/// Interprets a string as a mathematical expression, evaluates it, and returns the result.
931///
932/// This is the primary function for evaluating expressions. It parses the expression string,
933/// builds an Abstract Syntax Tree (AST), and then evaluates the AST using the provided context.
934///
935/// # Parameters
936///
937/// * `expression`: The mathematical expression to evaluate as a string
938/// * `ctx`: An optional evaluation context containing variables, constants, and functions
939///
940/// # Returns
941///
942/// * `Ok(value)`: The result of evaluating the expression
943/// * `Err(error)`: An error describing what went wrong during parsing or evaluation
944///
945/// # Examples
946///
947/// Basic usage without context:
948///
949/// ```
950/// use exp_rs::engine::interp;
951///
952/// // Evaluate a simple expression
953/// let result = interp("2 + 3 * 4", None).unwrap();
954/// assert_eq!(result, 14.0);
955///
956/// # #[cfg(feature = "libm")]
957/// # {
958/// // Using built-in functions and constants
959/// let result = interp("sin(pi/6) + cos(pi/3)", None).unwrap();
960/// assert!((result - 1.0).abs() < 0.0001);
961/// # }
962/// ```
963///
964/// Using a context with variables:
965///
966/// ```
967/// use exp_rs::context::EvalContext;
968/// use exp_rs::engine::interp;
969/// use std::rc::Rc;
970///
971/// let mut ctx = EvalContext::new();
972/// ctx.set_parameter("x", 5.0);
973/// ctx.set_parameter("y", 10.0);
974///
975/// let result = interp("x + y", Some(Rc::new(ctx))).unwrap();
976/// assert_eq!(result, 15.0);
977/// ```
978///
979/// Error handling:
980///
981/// ```
982/// use exp_rs::engine::interp;
983/// use exp_rs::error::ExprError;
984///
985/// match interp("2 + * 3", None) {
986///     Ok(_) => panic!("Expected an error"),
987///     Err(ExprError::Syntax(_)) => {
988///         // This is expected - there's a syntax error in the expression
989///     }
990///     Err(e) => panic!("Unexpected error: {:?}", e),
991/// }
992/// ```
993pub fn interp<'a>(expression: &str, ctx: Option<Rc<EvalContext>>) -> crate::error::Result<Real> {
994    use alloc::rc::Rc;
995
996    // Create a new context if none provided
997    let eval_ctx = match ctx {
998        Some(ctx_rc) => ctx_rc,
999        None => {
1000            // Use the default EvalContext::new() which will auto-register functions
1001            // based on the feature flags/test environment
1002            let new_ctx = EvalContext::new();
1003            Rc::new(new_ctx)
1004        }
1005    };
1006
1007    // Use Expression for consistent implementation
1008    let arena = Bump::new();
1009    crate::expression::Expression::eval_with_context(expression, &eval_ctx, &arena)
1010}
1011
1012#[cfg(test)]
1013use std::format;
1014#[cfg(test)]
1015use std::vec::Vec;
1016
1017#[cfg(test)]
1018mod tests {
1019    use super::*;
1020    use crate::context::EvalContext;
1021    use crate::functions::{log, sin};
1022    use bumpalo::Bump;
1023
1024    // Simple test helper to parse with a temporary arena
1025    fn parse_test(expr: &str) -> Result<AstExpr<'static>, ExprError> {
1026        use std::cell::RefCell;
1027
1028        thread_local! {
1029            static TEST_ARENA: RefCell<Bump> = RefCell::new(Bump::with_capacity(64 * 1024));
1030        }
1031
1032        TEST_ARENA.with(|arena| {
1033            let arena = arena.borrow();
1034            let ast = parse_expression(expr, &*arena)?;
1035            // SAFETY: Only safe for tests - extends lifetime to 'static
1036            Ok(unsafe { std::mem::transmute::<AstExpr<'_>, AstExpr<'static>>(ast) })
1037        })
1038    }
1039
1040    // Tests for the ternary operator
1041    #[test]
1042    fn test_ternary_operator_parsing() {
1043        // Basic ternary expression
1044        let ast = parse_test("x > 0 ? 1 : -1").unwrap();
1045        match ast {
1046            AstExpr::Conditional {
1047                condition,
1048                true_branch,
1049                false_branch,
1050            } => {
1051                // Verify condition is "x > 0"
1052                match *condition {
1053                    AstExpr::Function { name, args } => {
1054                        assert_eq!(name, ">");
1055                        assert_eq!(args.len(), 2);
1056                    }
1057                    _ => panic!("Expected function node for condition"),
1058                }
1059
1060                // Verify true branch is "1"
1061                match *true_branch {
1062                    AstExpr::Constant(val) => assert_eq!(val, 1.0),
1063                    _ => panic!("Expected constant for true branch"),
1064                }
1065
1066                // Verify false branch is "-1"
1067                match *false_branch {
1068                    AstExpr::Function { name, args } => {
1069                        assert_eq!(name, "neg");
1070                        assert_eq!(args.len(), 1);
1071                    }
1072                    _ => panic!("Expected function node for false branch"),
1073                }
1074            }
1075            _ => panic!("Expected conditional node"),
1076        }
1077    }
1078
1079    #[test]
1080    fn test_ternary_operator_evaluation() {
1081        // Create a context with the necessary operators for the test
1082        let mut ctx = EvalContext::new();
1083
1084        // Make sure the context has the ">" operator registered
1085        #[cfg(not(feature = "libm"))]
1086        {
1087            ctx.register_native_function(">", 2, |args| if args[0] > args[1] { 1.0 } else { 0.0 });
1088            ctx.register_native_function("*", 2, |args| args[0] * args[1]);
1089            ctx.register_native_function("+", 2, |args| args[0] + args[1]);
1090        }
1091
1092        let _ = ctx.set_parameter("x", 5.0);
1093        let result = interp("x > 0 ? 10 : 20", Some(Rc::new(ctx))).unwrap();
1094        assert_eq!(result, 10.0);
1095
1096        // Test with false condition
1097        let mut ctx = EvalContext::new();
1098
1099        // Make sure the context has the ">" operator registered
1100        #[cfg(not(feature = "libm"))]
1101        {
1102            ctx.register_native_function(">", 2, |args| if args[0] > args[1] { 1.0 } else { 0.0 });
1103            ctx.register_native_function("*", 2, |args| args[0] * args[1]);
1104            ctx.register_native_function("+", 2, |args| args[0] + args[1]);
1105        }
1106
1107        let _ = ctx.set_parameter("x", -5.0);
1108        let result = interp("x > 0 ? 10 : 20", Some(Rc::new(ctx))).unwrap();
1109        assert_eq!(result, 20.0);
1110
1111        // Create a context for the rest of the tests
1112        let ctx = EvalContext::new();
1113
1114        // Make sure the context has all the necessary operators registered
1115        #[cfg(not(feature = "libm"))]
1116        {
1117            ctx.register_native_function(">", 2, |args| if args[0] > args[1] { 1.0 } else { 0.0 });
1118            ctx.register_native_function("*", 2, |args| args[0] * args[1]);
1119            ctx.register_native_function("+", 2, |args| args[0] + args[1]);
1120        }
1121
1122        let ctx_rc = Rc::new(ctx);
1123
1124        // Test with nested ternary
1125        let result = interp("1 ? 2 ? 3 : 4 : 5", Some(ctx_rc.clone())).unwrap();
1126        assert_eq!(result, 3.0);
1127
1128        // Test with complex expressions in branches
1129        let result = interp("0 ? 10 : 2 * 3 + 4", Some(ctx_rc.clone())).unwrap();
1130        assert_eq!(result, 10.0);
1131
1132        // Test with complex expressions in condition
1133        let result = interp("2 * 3 > 5 ? 1 : 0", Some(ctx_rc)).unwrap();
1134        assert_eq!(result, 1.0);
1135    }
1136
1137    #[test]
1138    fn test_ternary_operator_short_circuit() {
1139        // Context with a variable that will cause division by zero in the false branch
1140        let mut ctx = EvalContext::new();
1141
1142        // Make sure the context has all the necessary operators registered
1143        #[cfg(not(feature = "libm"))]
1144        {
1145            ctx.register_native_function("/", 2, |args| args[0] / args[1]);
1146        }
1147
1148        let _ = ctx.set_parameter("x", 0.0);
1149
1150        // This should not cause a division by zero error because the condition is true,
1151        // so the false branch (x's value / 0) is never evaluated due to short-circuit
1152        let result = interp("1 ? 42 : 1/x", Some(Rc::new(ctx.clone()))).unwrap();
1153        assert_eq!(result, 42.0);
1154
1155        // This should return infinity because the condition is false,
1156        // so the false branch (1/0) is evaluated
1157        let result = interp("0 ? 42 : 1/x", Some(Rc::new(ctx))).unwrap();
1158
1159        #[cfg(feature = "f32")]
1160        assert!(
1161            result.is_infinite() && result.is_sign_positive(),
1162            "1/0 should be positive infinity"
1163        );
1164        #[cfg(not(feature = "f32"))]
1165        assert!(
1166            result.is_infinite() && result.is_sign_positive(),
1167            "1/0 should be positive infinity"
1168        );
1169    }
1170
1171    #[test]
1172    fn test_ternary_operator_precedence() {
1173        // Create a context with the necessary operators for the test
1174        let ctx = EvalContext::new();
1175
1176        // Make sure the context has all the necessary operators registered
1177        #[cfg(not(feature = "libm"))]
1178        {
1179            ctx.register_native_function(">", 2, |args| if args[0] > args[1] { 1.0 } else { 0.0 });
1180            ctx.register_native_function("+", 2, |args| args[0] + args[1]);
1181            ctx.register_native_function("&&", 2, |args| {
1182                if args[0] != 0.0 && args[1] != 0.0 {
1183                    1.0
1184                } else {
1185                    0.0
1186                }
1187            });
1188        }
1189
1190        let ctx_rc = Rc::new(ctx);
1191
1192        // Test that ternary has lower precedence than comparison
1193        let result = interp("2 > 1 ? 3 : 4", Some(ctx_rc.clone())).unwrap();
1194        assert_eq!(result, 3.0);
1195
1196        // Test that ternary has lower precedence than addition/subtraction
1197        let result = interp("1 + 2 ? 3 : 4", Some(ctx_rc.clone())).unwrap();
1198        assert_eq!(result, 3.0);
1199
1200        // Test that ternary has lower precedence than logical operators
1201        let result = interp("1 && 0 ? 3 : 4", Some(ctx_rc.clone())).unwrap();
1202        assert_eq!(result, 4.0);
1203
1204        // Test right associativity with nested ternary
1205        let result = interp("1 ? 2 : 3 ? 4 : 5", Some(ctx_rc)).unwrap();
1206        assert_eq!(result, 2.0);
1207    }
1208
1209    // Helper function to print AST for debugging
1210    #[allow(dead_code)]
1211    fn debug_ast(expr: &AstExpr<'_>, indent: usize) -> String {
1212        let spaces = " ".repeat(indent);
1213        match expr {
1214            AstExpr::Constant(val) => format!("{}Constant({})", spaces, val),
1215            AstExpr::Variable(name) => format!("{}Variable({})", spaces, name),
1216            AstExpr::Function { name, args } => {
1217                let mut result = format!("{}Function({}, [\n", spaces, name);
1218                for arg in args.iter() {
1219                    result.push_str(&format!("{},\n", debug_ast(arg, indent + 2)));
1220                }
1221                result.push_str(&format!("{}])", spaces));
1222                result
1223            }
1224            AstExpr::Array { name, index } => {
1225                format!(
1226                    "{}Array({}, {})",
1227                    spaces,
1228                    name,
1229                    debug_ast(index, indent + 2)
1230                )
1231            }
1232            AstExpr::Attribute { base, attr } => {
1233                format!("{}Attribute({}, {})", spaces, base, attr)
1234            }
1235            AstExpr::LogicalOp { op, left, right } => {
1236                let op_str = match op {
1237                    crate::types::LogicalOperator::And => "&&",
1238                    crate::types::LogicalOperator::Or => "||",
1239                };
1240                format!(
1241                    "{}LogicalOp({}, \n{},\n{})",
1242                    spaces,
1243                    op_str,
1244                    debug_ast(left, indent + 2),
1245                    debug_ast(right, indent + 2)
1246                )
1247            }
1248            AstExpr::Conditional {
1249                condition,
1250                true_branch,
1251                false_branch,
1252            } => {
1253                let mut result = format!("{}Conditional(\n", spaces);
1254                result.push_str(&format!(
1255                    "{}condition: {},\n",
1256                    spaces.clone() + "  ",
1257                    debug_ast(condition, indent + 4)
1258                ));
1259                result.push_str(&format!(
1260                    "{}true_branch: {},\n",
1261                    spaces.clone() + "  ",
1262                    debug_ast(true_branch, indent + 4)
1263                ));
1264                result.push_str(&format!(
1265                    "{}false_branch: {}\n",
1266                    spaces.clone() + "  ",
1267                    debug_ast(false_branch, indent + 4)
1268                ));
1269                result.push_str(&format!("{})", spaces));
1270                result
1271            }
1272        }
1273    }
1274
1275    #[test]
1276    fn test_unknown_variable_and_function_eval() {
1277        // Test that using 'sin' as a variable (when it's a function) produces an error
1278        let arena = bumpalo::Bump::new();
1279        let sin_var_ast = parse_expression("sin", &arena).unwrap();
1280        let err = crate::eval::ast::eval_ast(&sin_var_ast, None, &arena).unwrap_err();
1281
1282        // Accept any error type, just verify it's an error when using a function name as a variable
1283        println!("Error when evaluating 'sin' as a variable: {:?}", err);
1284        // No specific assertion on error type, just ensure it's an error
1285    }
1286
1287    // Removed test_parse_postfix_chained_juxtaposition - testing manual AST construction
1288    // which is no longer relevant with arena-based parsing
1289
1290    #[test]
1291    fn test_pow_arity_ast() {
1292        // Since we now automatically add a second argument to pow(2),
1293        // we need to modify this test to check for 2 arguments
1294        let ast = parse_test("pow(2)").unwrap_or_else(|e| panic!("Parse error: {}", e));
1295        println!("AST for pow(2): {:?}", ast);
1296
1297        match ast {
1298            AstExpr::Function { ref name, ref args } if *name == "pow" => {
1299                assert_eq!(args.len(), 2); // Changed from 1 to 2
1300                match &args[0] {
1301                    AstExpr::Constant(c) => assert_eq!(*c, 2.0),
1302                    _ => panic!("Expected constant as pow arg"),
1303                }
1304                // Check the second argument is 2.0 (default)
1305                match &args[1] {
1306                    AstExpr::Constant(c) => assert_eq!(*c, 2.0),
1307                    _ => panic!("Expected constant as second pow arg"),
1308                }
1309            }
1310            _ => panic!("Expected function node for pow"),
1311        }
1312    }
1313
1314    #[test]
1315    fn test_parse_postfix_array_and_attribute_access() {
1316        // Test array indexing through parsing
1317        let arena = Bump::new();
1318        let ast = parse_expression("sin(arr[0])", &arena).unwrap();
1319
1320        // Verify the parsed structure
1321        match &ast {
1322            AstExpr::Function { name, args } => {
1323                assert_eq!(*name, "sin");
1324                assert_eq!(args.len(), 1);
1325                match &args[0] {
1326                    AstExpr::Array { name, index } => {
1327                        assert_eq!(*name, "arr");
1328                        match **index {
1329                            AstExpr::Constant(val) => assert_eq!(val, 0.0),
1330                            _ => panic!("Expected constant as array index"),
1331                        }
1332                    }
1333                    _ => panic!("Expected array as argument to sin"),
1334                }
1335            }
1336            _ => panic!("Expected function node for sin(arr[0])"),
1337        }
1338
1339        // Test attribute access through parsing
1340        // Note: foo.bar(x) would parse as a function call with foo.bar as the function name
1341        // For pure attribute access, test foo.bar
1342        let ast2 = parse_expression("foo.bar", &arena).unwrap();
1343
1344        // Verify the parsed structure
1345        match &ast2 {
1346            AstExpr::Attribute { base, attr } => {
1347                assert_eq!(*base, "foo");
1348                assert_eq!(*attr, "bar");
1349            }
1350            _ => panic!("Expected attribute node for foo.bar"),
1351        }
1352    }
1353
1354    #[test]
1355    fn test_parse_postfix_function_call_after_attribute() {
1356        let ast = parse_test("foo.bar(1)").unwrap();
1357        match ast {
1358            AstExpr::Function { name, args } => {
1359                assert_eq!(name, "bar");
1360                assert_eq!(args.len(), 1);
1361                match &args[0] {
1362                    AstExpr::Constant(val) => assert_eq!(*val, 1.0),
1363                    _ => panic!("Expected constant as argument to foo.bar"),
1364                }
1365            }
1366            _ => panic!("Expected function node for foo.bar(1)"),
1367        }
1368    }
1369
1370    #[test]
1371    fn test_parse_postfix_array_access_complex_index() {
1372        let ast = parse_test("arr[1+2*3]").unwrap();
1373        match ast {
1374            AstExpr::Array { name, index } => {
1375                assert_eq!(name, "arr");
1376                match *index {
1377                    AstExpr::Function {
1378                        name: ref n,
1379                        args: ref a,
1380                    } if *n == "+" => {
1381                        assert_eq!(a.len(), 2);
1382                    }
1383                    _ => panic!("Expected function as array index"),
1384                }
1385            }
1386            _ => panic!("Expected array AST node"),
1387        }
1388    }
1389
1390    #[test]
1391    fn test_atan2_function() {
1392        // Test atan2 with explicit arguments - atan2(y,x)
1393        let result = interp("atan2(1,2)", None).unwrap();
1394        println!("atan2(1,2) = {}", result);
1395        // For point (2,1), the angle is arctan(1/2) ≈ 0.4636 radians
1396        assert!(
1397            (result - 0.4636).abs() < 1e-3,
1398            "atan2(1,2) should be approximately 0.4636"
1399        );
1400
1401        // Test atan2 with swapped arguments
1402        let result2 = interp("atan2(2,1)", None).unwrap();
1403        println!("atan2(2,1) = {}", result2);
1404        // For point (1,2), the angle is arctan(2/1) ≈ 1.1071 radians
1405        assert!(
1406            (result2 - 1.1071).abs() < 1e-3,
1407            "atan2(2,1) should be approximately 1.1071"
1408        );
1409
1410        // Test atan2(1,1) which should be π/4
1411        let result3 = interp("atan2(1,1)", None).unwrap();
1412        println!("atan2(1,1) = {}", result3);
1413        assert!(
1414            (result3 - 0.7854).abs() < 1e-3,
1415            "atan2(1,1) should be approximately 0.7854 (π/4)"
1416        );
1417    }
1418
1419    #[test]
1420    fn test_pow_arity_eval() {
1421        // Since we now automatically add a second argument to pow(2),
1422        // we need to modify this test to check that it evaluates correctly
1423        let result = interp("pow(2)", None).unwrap();
1424        println!("pow(2) = {}", result); // Debug output
1425        assert_eq!(result, 4.0); // pow(2, 2) = 4.0
1426
1427        // Let's also test that pow with explicit arguments works
1428        let result2 = interp("pow(2, 3)", None).unwrap();
1429        println!("pow(2, 3) = {}", result2); // Debug output
1430        assert_eq!(result2, 8.0); // pow(2, 3) = 8.0
1431    }
1432
1433    #[test]
1434    #[cfg(feature = "libm")] // This test requires libm for built-in sin/cos/abs
1435    fn test_function_composition() {
1436        // Test function composition through parsing and evaluation
1437
1438        // Test sin(0.5)
1439        let result = interp("sin(0.5)", None).unwrap();
1440        println!("sin 0.5 = {}", result);
1441        assert!(
1442            (result - sin(0.5, 0.0)).abs() < 1e-6,
1443            "sin(0.5) should work"
1444        );
1445
1446        // Test chained function calls: sin(cos(0))
1447        let result2 = interp("sin(cos(0))", None).unwrap();
1448        println!("sin(cos(0)) = {}", result2);
1449        assert!(
1450            (result2 - sin(1.0, 0.0)).abs() < 1e-6,
1451            "sin(cos(0)) should be sin(1)"
1452        );
1453
1454        // Test abs with negative number
1455        let result3 = interp("abs(-42)", None).unwrap();
1456        println!("abs(-42) = {}", result3);
1457        assert_eq!(result3, 42.0, "abs(-42) should be 42.0");
1458    }
1459
1460    #[test]
1461    #[cfg(not(feature = "libm"))] // Alternative test for non-libm builds
1462    fn test_function_composition_no_libm() {
1463        // Create a context with our own implementations
1464        let mut ctx = EvalContext::new();
1465        ctx.register_native_function("sin", 1, |args| args[0].sin());
1466        ctx.register_native_function("cos", 1, |args| args[0].cos());
1467        ctx.register_native_function("abs", 1, |args| args[0].abs());
1468
1469        let ctx_rc = Rc::new(ctx);
1470
1471        // Test sin(0.5)
1472        let result = interp("sin(0.5)", Some(ctx_rc.clone())).unwrap();
1473        println!("sin 0.5 = {}", result);
1474        assert!(
1475            (result - (0.5 as Real).sin()).abs() < 1e-6,
1476            "sin(0.5) should work"
1477        );
1478
1479        // Test chained function calls: sin(cos(0))
1480        let result2 = interp("sin(cos(0))", Some(ctx_rc.clone())).unwrap();
1481        println!("sin(cos(0)) = {}", result2);
1482        assert!(
1483            (result2 - (1.0 as Real).sin()).abs() < 1e-6,
1484            "sin(cos(0)) should be sin(1)"
1485        );
1486
1487        // Test abs with negative number
1488        let result3 = interp("abs(-42)", Some(ctx_rc)).unwrap();
1489        println!("abs(-42) = {}", result3);
1490        assert_eq!(result3, 42.0, "abs(-42) should be 42.0");
1491    }
1492
1493    #[test]
1494    fn test_juxtaposition_disabled() {
1495        // Test that juxtaposition is properly disabled - "sin x" should fail
1496        let ast = parse_test("sin x");
1497        match ast {
1498            Ok(_) => panic!("Expected parse error for 'sin x' since juxtaposition is disabled"),
1499            Err(e) => {
1500                println!("Expected parse error for 'sin x': {:?}", e);
1501                // This should fail since juxtaposition is disabled
1502                assert!(e.to_string().contains("Unexpected token"));
1503            }
1504        }
1505
1506        // For abs -42, we need to use parentheses to make it clear
1507        // that we want function application, not subtraction
1508        let ast2 = parse_test("abs(-42)");
1509        match ast2 {
1510            Ok(ast2) => {
1511                println!("AST for abs(-42): {:?}", ast2);
1512                match ast2 {
1513                    AstExpr::Function { ref name, ref args } if *name == "abs" => {
1514                        assert_eq!(args.len(), 1);
1515                        match &args[0] {
1516                            AstExpr::Function {
1517                                name: neg_name,
1518                                args: neg_args,
1519                            } if *neg_name == "neg" => {
1520                                assert_eq!(neg_args.len(), 1);
1521                                match &neg_args[0] {
1522                                    AstExpr::Constant(c) => assert_eq!(*c, 42.0),
1523                                    _ => panic!("Expected constant as neg arg"),
1524                                }
1525                            }
1526                            _ => panic!("Expected neg function as abs arg"),
1527                        }
1528                    }
1529                    _ => panic!("Expected function node for abs(-42)"),
1530                }
1531            }
1532            Err(e) => {
1533                println!("Parse error for 'abs(-42)': {:?}", e);
1534                panic!("Parse error: {:?}", e);
1535            }
1536        }
1537    }
1538
1539    #[test]
1540    #[cfg(feature = "libm")] // This test requires libm for built-in sin/asin
1541    fn test_function_recognition() {
1542        // Test function recognition through parsing and evaluation
1543
1544        // Test asin(sin(0.5))
1545        let result = interp("asin(sin(0.5))", None).unwrap();
1546        println!("asin(sin(0.5)) = {}", result);
1547        assert!((result - 0.5).abs() < 1e-6, "asin(sin(0.5)) should be 0.5");
1548
1549        // Test function recognition with parentheses
1550        let result2 = interp("sin(0.5)", None).unwrap();
1551        println!("sin(0.5) = {}", result2);
1552        assert!(
1553            (result2 - sin(0.5, 0.0)).abs() < 1e-6,
1554            "sin(0.5) should work"
1555        );
1556    }
1557
1558    #[test]
1559    #[cfg(not(feature = "libm"))] // Alternative test for non-libm builds
1560    fn test_function_recognition_no_libm() {
1561        // Create a context with our own implementation
1562        let mut ctx = EvalContext::new();
1563
1564        // Register sin and asin functions
1565        ctx.register_native_function("sin", 1, |args| args[0].sin());
1566        ctx.register_native_function("asin", 1, |args| args[0].asin());
1567
1568        // Convert to Rc
1569        let ctx_rc = Rc::new(ctx);
1570
1571        // Test asin(sin(0.5))
1572        let result = interp("asin(sin(0.5))", Some(ctx_rc.clone())).unwrap();
1573        println!("asin(sin(0.5)) = {}", result);
1574        assert!(
1575            (result - 0.5).abs() < 1e-2,
1576            "asin(sin(0.5)) should be approximately 0.5"
1577        );
1578
1579        // Test function recognition with parentheses
1580        let result2 = interp("sin(0.5)", Some(ctx_rc)).unwrap();
1581        println!("sin(0.5) = {}", result2);
1582        assert!(
1583            (result2 - (0.5 as Real).sin()).abs() < 1e-6,
1584            "sin(0.5) should work"
1585        );
1586    }
1587
1588    #[test]
1589    fn test_parse_postfix_attribute_on_function_result_should_error() {
1590        // This test verifies that attribute access on function results is rejected
1591        // Since juxtaposition is disabled, "sin x" will fail, so test with parentheses
1592        let _ast = parse_test("sin(x).foo");
1593
1594        // Currently, this might parse as sin(x.foo) due to precedence
1595        // Let's test with parentheses to be explicit
1596        let ast2 = parse_test("(sin(x)).foo");
1597        assert!(
1598            ast2.is_err(),
1599            "Attribute access on function result should be rejected"
1600        );
1601    }
1602
1603    #[test]
1604    fn test_parse_comma_in_parens_and_top_level() {
1605        let ast = parse_test("(1,2)");
1606        assert!(ast.is_ok(), "Comma in parens should be allowed");
1607        let ast2 = parse_test("1,2,3");
1608        assert!(ast2.is_ok(), "Top-level comma should be allowed");
1609        let ast3 = parse_test("(1,2),3");
1610        assert!(
1611            ast3.is_ok(),
1612            "Nested comma outside parens should be allowed"
1613        );
1614    }
1615
1616    #[test]
1617    fn test_deeply_nested_function_calls() {
1618        // Test with 10 levels of nesting
1619        let expr = "abs(abs(abs(abs(abs(abs(abs(abs(abs(abs(-12345))))))))))";
1620        let ast = parse_test(expr);
1621        assert!(
1622            ast.is_ok(),
1623            "Deeply nested function calls should be parsed correctly"
1624        );
1625
1626        // Test with unbalanced parentheses (missing one closing parenthesis)
1627        let unbalanced = "abs(abs(abs(abs(abs(abs(abs(abs(abs(abs(-12345)))))))))";
1628        let result = parse_test(unbalanced);
1629        assert!(result.is_err(), "Unbalanced parentheses should be detected");
1630        match result {
1631            Err(ExprError::UnmatchedParenthesis { position: _, found }) => {
1632                // This is the expected error
1633                assert_eq!(
1634                    found, "(",
1635                    "The unmatched parenthesis should be an opening one"
1636                );
1637            }
1638            _ => panic!("Expected UnmatchedParenthesis error for unbalanced parentheses"),
1639        }
1640    }
1641
1642    #[test]
1643    fn test_parse_binary_op_deep_right_assoc_pow() {
1644        let ast = parse_test("2^2^2^2^2").unwrap();
1645        fn count_right_assoc_pow(expr: &AstExpr<'_>) -> usize {
1646            match expr {
1647                AstExpr::Function { name, args } if *name == "^" && args.len() == 2 => {
1648                    1 + count_right_assoc_pow(&args[1])
1649                }
1650                _ => 0,
1651            }
1652        }
1653        let pow_depth = count_right_assoc_pow(&ast);
1654        assert_eq!(pow_depth, 4, "Should be right-associative chain of 4 '^'");
1655    }
1656
1657    #[test]
1658    fn test_deeply_nested_function_calls_with_debugging() {
1659        // Test with 10 levels of nesting
1660        let expr = "abs(abs(abs(abs(abs(abs(abs(abs(abs(abs(-12345))))))))))";
1661
1662        // Print the expression for debugging
1663        println!("Testing expression with debugging: {}", expr);
1664
1665        // Print all tokens for debugging
1666        let mut lexer = Lexer::new(expr);
1667        let mut tokens = Vec::new();
1668        while let Some(tok) = lexer.next_token() {
1669            tokens.push(tok);
1670        }
1671
1672        println!("Tokens:");
1673        for (i, token) in tokens.iter().enumerate() {
1674            println!("  {}: {:?}", i, token);
1675        }
1676
1677        // Count opening and closing parentheses
1678        let open_count = tokens
1679            .iter()
1680            .filter(|t| t.kind == TokenKind::Open && t.text.as_deref() == Some("("))
1681            .count();
1682        let close_count = tokens
1683            .iter()
1684            .filter(|t| t.kind == TokenKind::Close && t.text.as_deref() == Some(")"))
1685            .count();
1686
1687        println!("Opening parentheses: {}", open_count);
1688        println!("Closing parentheses: {}", close_count);
1689        assert_eq!(
1690            open_count, close_count,
1691            "Number of opening and closing parentheses should match"
1692        );
1693
1694        // Now try parsing
1695        let ast = parse_test(expr);
1696        assert!(
1697            ast.is_ok(),
1698            "Deeply nested function calls should be parsed correctly"
1699        );
1700    }
1701
1702    #[test]
1703    fn test_parse_binary_op_mixed_unary_and_power() {
1704        let ast = parse_test("-2^2").unwrap();
1705        match ast {
1706            AstExpr::Function { name, args } if name == "neg" => match &args[0] {
1707                AstExpr::Function {
1708                    name: n2,
1709                    args: args2,
1710                } if *n2 == "^" => {
1711                    assert_eq!(args2.len(), 2);
1712                }
1713                _ => panic!("Expected ^ as argument to neg"),
1714            },
1715            _ => panic!("Expected neg as top-level function"),
1716        }
1717        let ast2 = parse_test("(-2)^2").unwrap();
1718        match ast2 {
1719            AstExpr::Function { name, args } if name == "^" => match &args[0] {
1720                AstExpr::Function {
1721                    name: n2,
1722                    args: args2,
1723                } if *n2 == "neg" => {
1724                    assert_eq!(args2.len(), 1);
1725                }
1726                _ => panic!("Expected neg as left arg to ^"),
1727            },
1728            _ => panic!("Expected ^ as top-level function"),
1729        }
1730        let ast3 = parse_test("-2^-2").unwrap();
1731        match ast3 {
1732            AstExpr::Function { name, args } if name == "neg" => match &args[0] {
1733                AstExpr::Function {
1734                    name: n2,
1735                    args: args2,
1736                } if *n2 == "^" => {
1737                    assert_eq!(args2.len(), 2);
1738                }
1739                _ => panic!("Expected ^ as argument to neg"),
1740            },
1741            _ => panic!("Expected neg as top-level function"),
1742        }
1743    }
1744
1745    #[test]
1746    fn test_parse_binary_op_mixed_precedence() {
1747        let ast = parse_test("2+3*4^2-5/6").unwrap();
1748        match ast {
1749            AstExpr::Function { name, args } if name == "-" => {
1750                assert_eq!(args.len(), 2);
1751            }
1752            _ => panic!("Expected - as top-level function"),
1753        }
1754    }
1755
1756    #[test]
1757    fn test_parse_primary_paren_errors() {
1758        let ast = parse_test("((1+2)");
1759        assert!(ast.is_err(), "Unmatched parenthesis should be rejected");
1760        let ast2 = parse_test("1+)");
1761        assert!(ast2.is_err(), "Unmatched parenthesis should be rejected");
1762    }
1763
1764    #[test]
1765    fn test_parse_primary_variable_and_number_edge_cases() {
1766        let ast = parse_test("foo_bar123").unwrap();
1767        match ast {
1768            AstExpr::Variable(name) => assert_eq!(name, "foo_bar123"),
1769            _ => panic!("Expected variable node"),
1770        }
1771
1772        // Skip the .5 test for now as it's causing issues
1773        // We'll handle it in a separate test
1774
1775        let ast3 = parse_test("1e-2").unwrap();
1776        match ast3 {
1777            AstExpr::Constant(val) => assert!((val - 0.01).abs() < 1e-10),
1778            _ => panic!("Expected constant node"),
1779        }
1780
1781        let ast4 = parse_test("1.2e+3").unwrap();
1782        match ast4 {
1783            AstExpr::Constant(val) => assert!((val - 1200.0).abs() < 1e-10),
1784            _ => panic!("Expected constant node"),
1785        }
1786    }
1787
1788    #[test]
1789    fn test_parse_decimal_with_leading_dot() {
1790        // This test should now pass with our improved error handling
1791        let ast = parse_test(".5").unwrap_or_else(|e| panic!("Parse error: {}", e));
1792        match ast {
1793            AstExpr::Constant(val) => assert_eq!(val, 0.5),
1794            _ => panic!("Expected constant node"),
1795        }
1796    }
1797
1798    #[test]
1799    fn test_log() {
1800        // log(x) is base-10 logarithm in this library
1801        assert!((log(1000.0, 0.0) - 3.0).abs() < 1e-10);
1802        assert!((log(100.0, 0.0) - 2.0).abs() < 1e-10);
1803        assert!((log(10.0, 0.0) - 1.0).abs() < 1e-10);
1804    }
1805    #[test]
1806    fn test_eval_invalid_function_arity() {
1807        // Test with a function that doesn't have special handling for arity
1808        let result = interp("sin(1, 2)", None);
1809        assert!(result.is_err(), "sin(1, 2) should return an error");
1810
1811        if let Err(err) = result {
1812            match err {
1813                ExprError::InvalidFunctionCall {
1814                    name,
1815                    expected,
1816                    found,
1817                } => {
1818                    assert_eq!(name, "sin");
1819                    assert_eq!(expected, 1);
1820                    assert_eq!(found, 2);
1821                }
1822                _ => panic!(
1823                    "Expected InvalidFunctionCall error for sin(1, 2), got: {:?}",
1824                    err
1825                ),
1826            }
1827        }
1828
1829        // Test that pow with one argument works (special case)
1830        let result2 = interp("pow(2)", None).unwrap();
1831        assert_eq!(result2, 4.0); // pow(2, 2) = 4.0
1832    }
1833}