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#[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, current: Option<Token>,
32 errors: Vec<ExprError>,
33 recursion_depth: usize,
34 max_recursion_depth: usize,
35 reserved_vars: Option<HashSet<Cow<'input, str>>>, context_vars: Option<HashSet<Cow<'input, str>>>, }
38
39#[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 const fn left_assoc(power: u8) -> Self {
53 Self::new(power, power + 1)
54 }
55
56 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 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 fn get_binding_power(op: &str) -> Option<BindingPower> {
143 match op {
144 "," | ";" => Some(BindingPower::left_assoc(1)), "?" => Some(BindingPower::right_assoc(1)), "||" => Some(BindingPower::left_assoc(2)), "&&" => Some(BindingPower::left_assoc(3)), "|" => Some(BindingPower::left_assoc(4)), "&" => Some(BindingPower::left_assoc(6)), "==" | "!=" | "<" | ">" | "<=" | ">=" | "<>" => Some(BindingPower::left_assoc(7)), "<<" | ">>" | "<<<" | ">>>" => Some(BindingPower::left_assoc(8)), "+" | "-" => Some(BindingPower::left_assoc(9)), "*" | "/" | "%" => Some(BindingPower::left_assoc(10)), "^" => Some(BindingPower::right_assoc(15)), "**" => Some(BindingPower::right_assoc(16)), ":" => None, _ => None,
158 }
159 }
160
161 fn get_prefix_binding_power(op: &str) -> Option<u8> {
163 match op {
164 "+" | "-" | "~" => Some(14), _ => None,
166 }
167 }
168
169 fn parse_postfix(&mut self, lhs: AstExpr<'arena>) -> Result<AstExpr<'arena>, ExprError> {
171 let mut result = lhs;
172
173 loop {
175 if let Some(tok) = self.peek() {
176 match (tok.kind, tok.text.as_deref()) {
177 (TokenKind::Open, Some("(")) => {
178 result = self.parse_function_call(result)?;
180 }
181 (TokenKind::Open, Some("[")) => {
182 result = self.parse_array_access(result)?;
184 }
185 (TokenKind::Operator, Some(".")) => {
186 result = self.parse_attribute_access(result)?;
188 }
189 _ => break, }
191 } else {
192 break;
193 }
194 }
195
196 Ok(result)
197 }
198
199 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(); return Ok(());
210 }
211
212 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 Err(ExprError::Syntax(format!(
224 "Expected {} but found end of input (opening at position {})",
225 expected, opening_position
226 )))
227 }
228
229 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(); let expr = self.parse_expr_unified(0, true)?;
237
238 if let Some(tok) = self.peek() {
240 if tok.kind == TokenKind::Close {
241 self.next(); return Ok(expr);
243 }
244
245 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 Err(ExprError::Syntax(format!(
256 "Expected closing parenthesis ')' but found end of input (opening at position {})",
257 open_position
258 )))
259 }
260
261 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(); let mut args = bumpalo::collections::Vec::new_in(self.arena);
276
277 if let Some(tok) = self.peek() {
279 if tok.kind != TokenKind::Close {
280 let arg = self.parse_expr_unified(0, false)?;
282 args.push(arg);
283
284 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(); 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 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 if let Some(tok) = self.peek() {
314 if tok.kind == TokenKind::Close {
315 self.next(); } else {
317 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 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 if name == "pow" && args.len() == 1 {
337 args.push(AstExpr::Constant(2.0));
339 } else if name == "atan2" && args.len() == 1 {
340 args.push(AstExpr::Constant(1.0));
342 }
343
344 if name == "polynomial" && args.len() == 1 {
346 }
348
349 Ok(AstExpr::Function {
350 name,
351 args: args.into_bump_slice(),
352 })
353 }
354
355 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(); let index = self.parse_expr_unified(0, true)?;
373
374 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 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(); 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 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 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 fn parse_expr_unified(
423 &mut self,
424 min_bp: u8,
425 allow_comma: bool,
426 ) -> Result<AstExpr<'arena>, ExprError> {
427 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 let mut lhs = self.parse_prefix_or_primary(allow_comma)?;
439
440 lhs = self.parse_postfix(lhs)?;
442
443 lhs = self.parse_infix_operators(lhs, min_bp, allow_comma)?;
445
446 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 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 let op_str = String::from(op);
470
471 self.next();
473
474 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 let rhs = self.parse_expr_unified(r_bp, allow_comma)?;
484
485 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 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 fn parse_ternary_op(
510 &mut self,
511 condition: AstExpr<'arena>,
512 allow_comma: bool,
513 ) -> Result<AstExpr<'arena>, ExprError> {
514 let true_branch = self.parse_expr_unified(0, allow_comma)?;
518
519 if let Some(tok) = self.peek() {
521 if tok.kind == TokenKind::Operator && tok.text.as_deref() == Some(":") {
522 self.next(); } 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 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 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 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 let op = String::from(op_text);
574
575 if op == "?" {
577 let Some(bp) = Self::get_binding_power(&op) else {
579 break;
580 };
581
582 if bp.left < min_bp {
584 break;
585 }
586
587 self.next();
589
590 lhs = self.parse_ternary_op(lhs, allow_comma)?;
592 continue;
593 }
594
595 if op == "&&" || op == "||" {
597 let Some(bp) = Self::get_binding_power(&op) else {
599 break;
600 };
601
602 if bp.left < min_bp {
604 break;
605 }
606
607 self.next();
609
610 let rhs = self.parse_expr_unified(bp.right, allow_comma)?;
612
613 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 let Some(bp) = Self::get_binding_power(&op) else {
628 break;
629 };
630
631 if bp.left < min_bp {
633 break;
634 }
635
636 self.next();
638
639 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 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 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 let func_name = match &lhs {
696 AstExpr::Variable(name) => name,
697 _ => unreachable!(),
698 };
699 let arg = self.parse_primary()?;
702
703 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 fn parse_expr(&mut self, min_bp: u8) -> Result<AstExpr<'arena>, ExprError> {
717 self.parse_expr_unified(min_bp, true)
718 }
719
720 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 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 fn check_expression_length(&self, input: &str) -> Result<(), ExprError> {
767 const MAX_EXPRESSION_LENGTH: usize = 10000; 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 fn parse(&mut self) -> Result<AstExpr<'arena>, ExprError> {
780 if let Some(remaining) = self.lexer.get_remaining_input() {
782 self.check_expression_length(remaining)?;
783 }
784
785 self.recursion_depth = 0;
787
788 let expr = self.parse_expr(0)?;
790
791 #[cfg(test)]
792 println!("Parsed expression: {:?}", expr);
793
794 if let Some(tok) = self.peek() {
796 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 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 return Err(ExprError::Syntax(format!(
812 "Unexpected closing parenthesis at position {}: check for balanced parentheses",
813 tok.position
814 )));
815 } else {
816 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
829pub 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
869pub 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
901pub 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 let mut parser =
926 PrattParser::with_reserved_vars_and_context(input, arena, reserved_vars, context_vars);
927 parser.parse()
928}
929
930pub fn interp<'a>(expression: &str, ctx: Option<Rc<EvalContext>>) -> crate::error::Result<Real> {
994 use alloc::rc::Rc;
995
996 let eval_ctx = match ctx {
998 Some(ctx_rc) => ctx_rc,
999 None => {
1000 let new_ctx = EvalContext::new();
1003 Rc::new(new_ctx)
1004 }
1005 };
1006
1007 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 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 Ok(unsafe { std::mem::transmute::<AstExpr<'_>, AstExpr<'static>>(ast) })
1037 })
1038 }
1039
1040 #[test]
1042 fn test_ternary_operator_parsing() {
1043 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 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 match *true_branch {
1062 AstExpr::Constant(val) => assert_eq!(val, 1.0),
1063 _ => panic!("Expected constant for true branch"),
1064 }
1065
1066 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 let mut ctx = EvalContext::new();
1083
1084 #[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 let mut ctx = EvalContext::new();
1098
1099 #[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 let ctx = EvalContext::new();
1113
1114 #[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 let result = interp("1 ? 2 ? 3 : 4 : 5", Some(ctx_rc.clone())).unwrap();
1126 assert_eq!(result, 3.0);
1127
1128 let result = interp("0 ? 10 : 2 * 3 + 4", Some(ctx_rc.clone())).unwrap();
1130 assert_eq!(result, 10.0);
1131
1132 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 let mut ctx = EvalContext::new();
1141
1142 #[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 let result = interp("1 ? 42 : 1/x", Some(Rc::new(ctx.clone()))).unwrap();
1153 assert_eq!(result, 42.0);
1154
1155 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 let ctx = EvalContext::new();
1175
1176 #[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 let result = interp("2 > 1 ? 3 : 4", Some(ctx_rc.clone())).unwrap();
1194 assert_eq!(result, 3.0);
1195
1196 let result = interp("1 + 2 ? 3 : 4", Some(ctx_rc.clone())).unwrap();
1198 assert_eq!(result, 3.0);
1199
1200 let result = interp("1 && 0 ? 3 : 4", Some(ctx_rc.clone())).unwrap();
1202 assert_eq!(result, 4.0);
1203
1204 let result = interp("1 ? 2 : 3 ? 4 : 5", Some(ctx_rc)).unwrap();
1206 assert_eq!(result, 2.0);
1207 }
1208
1209 #[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 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 println!("Error when evaluating 'sin' as a variable: {:?}", err);
1284 }
1286
1287 #[test]
1291 fn test_pow_arity_ast() {
1292 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); match &args[0] {
1301 AstExpr::Constant(c) => assert_eq!(*c, 2.0),
1302 _ => panic!("Expected constant as pow arg"),
1303 }
1304 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 let arena = Bump::new();
1318 let ast = parse_expression("sin(arr[0])", &arena).unwrap();
1319
1320 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 let ast2 = parse_expression("foo.bar", &arena).unwrap();
1343
1344 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 let result = interp("atan2(1,2)", None).unwrap();
1394 println!("atan2(1,2) = {}", result);
1395 assert!(
1397 (result - 0.4636).abs() < 1e-3,
1398 "atan2(1,2) should be approximately 0.4636"
1399 );
1400
1401 let result2 = interp("atan2(2,1)", None).unwrap();
1403 println!("atan2(2,1) = {}", result2);
1404 assert!(
1406 (result2 - 1.1071).abs() < 1e-3,
1407 "atan2(2,1) should be approximately 1.1071"
1408 );
1409
1410 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 let result = interp("pow(2)", None).unwrap();
1424 println!("pow(2) = {}", result); assert_eq!(result, 4.0); let result2 = interp("pow(2, 3)", None).unwrap();
1429 println!("pow(2, 3) = {}", result2); assert_eq!(result2, 8.0); }
1432
1433 #[test]
1434 #[cfg(feature = "libm")] fn test_function_composition() {
1436 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 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 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"))] fn test_function_composition_no_libm() {
1463 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 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 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 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 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 assert!(e.to_string().contains("Unexpected token"));
1503 }
1504 }
1505
1506 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")] fn test_function_recognition() {
1542 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 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"))] fn test_function_recognition_no_libm() {
1561 let mut ctx = EvalContext::new();
1563
1564 ctx.register_native_function("sin", 1, |args| args[0].sin());
1566 ctx.register_native_function("asin", 1, |args| args[0].asin());
1567
1568 let ctx_rc = Rc::new(ctx);
1570
1571 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 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 let _ast = parse_test("sin(x).foo");
1593
1594 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 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 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 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 let expr = "abs(abs(abs(abs(abs(abs(abs(abs(abs(abs(-12345))))))))))";
1661
1662 println!("Testing expression with debugging: {}", expr);
1664
1665 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 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 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 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 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 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 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 let result2 = interp("pow(2)", None).unwrap();
1831 assert_eq!(result2, 4.0); }
1833}