use crate::ast::{
BinaryOp, Expression, InequalityOp, LogicalOp, MathConstant, MathFloat, SetOp, SetRelation,
UnaryOp,
};
use crate::error::{ParseError, ParseResult, Span};
use crate::parser::tokenizer::{tokenize, SpannedToken, Token};
use crate::ParserConfig;
pub fn parse(input: &str) -> ParseResult<Expression> {
parse_with_config(input, &ParserConfig::default())
}
pub fn parse_with_config(input: &str, config: &ParserConfig) -> ParseResult<Expression> {
let tokens = tokenize(input)?;
let parser = TextParser::new(tokens, config.clone());
parser.parse()
}
struct TextParser {
tokens: Vec<SpannedToken>,
pos: usize,
config: ParserConfig,
}
impl TextParser {
fn new(tokens: Vec<SpannedToken>, config: ParserConfig) -> Self {
Self {
tokens,
pos: 0,
config,
}
}
fn peek(&self) -> Option<&SpannedToken> {
self.tokens.get(self.pos)
}
fn next(&mut self) -> Option<SpannedToken> {
let token = self.tokens.get(self.pos).cloned();
if token.is_some() {
self.pos += 1;
}
token
}
fn current_span(&self) -> Span {
self.peek().map(|token| token.span).unwrap_or_else(|| {
if let Some(last_token) = self.tokens.last() {
Span::at(last_token.span.end)
} else {
Span::start()
}
})
}
fn check(&self, expected: &Token) -> bool {
self.peek()
.map(|token| &token.value == expected)
.unwrap_or(false)
}
fn should_insert_implicit_mult(&self, _left: &Expression) -> bool {
if !self.config.implicit_multiplication {
return false;
}
let next_token = match self.peek() {
Some(token) => &token.value,
None => return false,
};
matches!(next_token, Token::Identifier(_) | Token::LParen)
}
fn consume(&mut self, expected: Token) -> ParseResult<Span> {
if let Some(token) = self.next() {
if token.value == expected {
Ok(token.span)
} else {
Err(ParseError::unexpected_token(
vec![format!("{}", expected)],
format!("{}", token.value),
Some(token.span),
))
}
} else {
Err(ParseError::unexpected_eof(
vec![format!("{}", expected)],
Some(self.current_span()),
))
}
}
fn parse(mut self) -> ParseResult<Expression> {
let expr = self.parse_expression()?;
if self.peek().is_some() {
let token = self.peek().unwrap();
return Err(ParseError::unexpected_token(
vec!["end of input"],
format!("{}", token.value),
Some(token.span),
));
}
Ok(expr)
}
fn parse_expression(&mut self) -> ParseResult<Expression> {
self.parse_logical()
}
fn parse_logical(&mut self) -> ParseResult<Expression> {
if let Some(token) = self.peek() {
if matches!(token.value, Token::Not) {
self.next(); let operand = self.parse_logical()?; return Ok(Expression::Logical {
op: LogicalOp::Not,
operands: vec![operand],
});
}
}
let mut left = self.parse_quantifier()?;
while let Some(token) = self.peek() {
let op = match &token.value {
Token::Iff => LogicalOp::Iff,
Token::Implies => LogicalOp::Implies,
Token::Or => LogicalOp::Or,
Token::And => LogicalOp::And,
_ => break,
};
self.next(); let right = self.parse_quantifier()?;
left = Expression::Logical {
op,
operands: vec![left, right],
};
}
Ok(left)
}
fn parse_quantifier(&mut self) -> ParseResult<Expression> {
if let Some(token) = self.peek() {
match &token.value {
Token::ForAll => {
self.next(); return self.parse_quantifier_body(false);
}
Token::Exists => {
self.next(); return self.parse_quantifier_body(true);
}
_ => {}
}
}
self.parse_set_operation()
}
fn parse_quantifier_body(&mut self, is_exists: bool) -> ParseResult<Expression> {
let variable = if let Some(token) = self.peek() {
if let Token::Identifier(name) = &token.value {
let var = name.clone();
self.next();
var
} else {
return Err(ParseError::unexpected_token(
vec!["variable"],
format!("{}", token.value),
Some(token.span),
));
}
} else {
return Err(ParseError::unexpected_eof(
vec!["variable"],
Some(self.current_span()),
));
};
let domain = if let Some(token) = self.peek() {
if matches!(token.value, Token::In) {
self.next(); Some(Box::new(self.parse_set_operation()?))
} else {
None
}
} else {
None
};
self.consume(Token::Comma)?;
let body = Box::new(self.parse_logical()?);
if is_exists {
Ok(Expression::Exists {
variable,
domain,
body,
unique: false,
})
} else {
Ok(Expression::ForAll {
variable,
domain,
body,
})
}
}
fn parse_set_operation(&mut self) -> ParseResult<Expression> {
let mut left = self.parse_relation()?;
while let Some(token) = self.peek() {
match &token.value {
Token::Union => {
self.next();
let right = self.parse_relation()?;
left = Expression::SetOperation {
op: SetOp::Union,
left: Box::new(left),
right: Box::new(right),
};
}
Token::Intersect => {
self.next();
let right = self.parse_relation()?;
left = Expression::SetOperation {
op: SetOp::Intersection,
left: Box::new(left),
right: Box::new(right),
};
}
Token::In => {
self.next();
let right = self.parse_relation()?;
left = Expression::SetRelationExpr {
relation: SetRelation::In,
element: Box::new(left),
set: Box::new(right),
};
}
Token::NotIn => {
self.next();
let right = self.parse_relation()?;
left = Expression::SetRelationExpr {
relation: SetRelation::NotIn,
element: Box::new(left),
set: Box::new(right),
};
}
_ => break,
}
}
Ok(left)
}
fn parse_relation(&mut self) -> ParseResult<Expression> {
let left = self.parse_additive()?;
if let Some(token) = self.peek() {
let relation = match &token.value {
Token::Equals => Some(None), Token::Less => Some(Some(InequalityOp::Lt)),
Token::Greater => Some(Some(InequalityOp::Gt)),
Token::LessEq => Some(Some(InequalityOp::Le)),
Token::GreaterEq => Some(Some(InequalityOp::Ge)),
Token::NotEquals => Some(Some(InequalityOp::Ne)),
_ => None,
};
if let Some(rel_op) = relation {
self.next(); let right = self.parse_additive()?;
if let Some(next_token) = self.peek() {
if matches!(
next_token.value,
Token::Equals
| Token::Less
| Token::Greater
| Token::LessEq
| Token::GreaterEq
| Token::NotEquals
) {
return Err(ParseError::custom(
"chained relations are not supported; use explicit grouping (e.g., (a < b) and (b < c))".to_string(),
Some(next_token.span),
));
}
}
return Ok(match rel_op {
None => Expression::Equation {
left: Box::new(left),
right: Box::new(right),
},
Some(op) => Expression::Inequality {
op,
left: Box::new(left),
right: Box::new(right),
},
});
}
}
Ok(left)
}
fn parse_additive(&mut self) -> ParseResult<Expression> {
let mut left = self.parse_multiplicative()?;
while let Some(token) = self.peek() {
let op = match &token.value {
Token::Plus => BinaryOp::Add,
Token::Minus => BinaryOp::Sub,
_ => break,
};
self.next(); let right = self.parse_multiplicative()?;
left = Expression::Binary {
op,
left: Box::new(left),
right: Box::new(right),
};
}
Ok(left)
}
fn parse_multiplicative(&mut self) -> ParseResult<Expression> {
let mut left = self.parse_unary()?;
loop {
let op = if let Some(token) = self.peek() {
match &token.value {
Token::Star => Some(BinaryOp::Mul),
Token::Slash => Some(BinaryOp::Div),
Token::Percent => Some(BinaryOp::Mod),
_ => None,
}
} else {
None
};
if let Some(op) = op {
self.next(); let right = self.parse_unary()?;
left = Expression::Binary {
op,
left: Box::new(left),
right: Box::new(right),
};
} else if self.should_insert_implicit_mult(&left) {
let right = self.parse_unary()?;
left = Expression::Binary {
op: BinaryOp::Mul,
left: Box::new(left),
right: Box::new(right),
};
} else {
break;
}
}
Ok(left)
}
fn parse_unary(&mut self) -> ParseResult<Expression> {
if let Some(token) = self.peek() {
let op = match &token.value {
Token::Minus => Some(UnaryOp::Neg),
Token::Plus => Some(UnaryOp::Pos),
_ => None,
};
if let Some(op) = op {
self.next(); let operand = self.parse_unary()?;
if matches!(op, UnaryOp::Neg)
&& matches!(operand, Expression::Constant(MathConstant::Infinity))
{
return Ok(Expression::Constant(MathConstant::NegInfinity));
}
return Ok(Expression::Unary {
op,
operand: Box::new(operand),
});
}
}
self.parse_power()
}
fn parse_power(&mut self) -> ParseResult<Expression> {
let left = self.parse_postfix()?;
if self.check(&Token::Caret) || self.check(&Token::DoubleStar) {
self.next(); let right = self.parse_power()?;
Ok(Expression::Binary {
op: BinaryOp::Pow,
left: Box::new(left),
right: Box::new(right),
})
} else {
Ok(left)
}
}
fn parse_postfix(&mut self) -> ParseResult<Expression> {
let mut expr = self.parse_primary()?;
while self.check(&Token::Bang) {
self.next(); expr = Expression::Unary {
op: UnaryOp::Factorial,
operand: Box::new(expr),
};
}
Ok(expr)
}
fn parse_primary(&mut self) -> ParseResult<Expression> {
let token = self.peek().ok_or_else(|| {
ParseError::unexpected_eof(vec!["expression"], Some(self.current_span()))
})?;
match &token.value {
Token::Integer(n) => {
let value = *n;
self.next();
Ok(Expression::Integer(value))
}
Token::Float(f) => {
let value = *f;
self.next();
Ok(Expression::Float(MathFloat::from(value)))
}
Token::Identifier(name) => {
let mut name = name.clone();
self.next();
if self.check(&Token::Underscore) {
self.next();
let subscript = if let Some(token) = self.peek() {
match &token.value {
Token::Integer(n) => {
let sub = n.to_string();
self.next();
sub
}
Token::Identifier(id) => {
let sub = id.clone();
self.next();
sub
}
_ => {
return Err(ParseError::unexpected_token(
vec!["number", "identifier"],
format!("{}", token.value),
Some(token.span),
));
}
}
} else {
return Err(ParseError::unexpected_eof(
vec!["subscript"],
Some(self.current_span()),
));
};
name = format!("{}_{}", name, subscript);
}
if self.check(&Token::LParen) {
self.parse_function_args(name)
} else {
Ok(self.identifier_to_expression(name))
}
}
Token::Pi => {
self.next();
Ok(Expression::Constant(MathConstant::Pi))
}
Token::Infinity => {
self.next();
Ok(Expression::Constant(MathConstant::Infinity))
}
Token::Sqrt => {
self.next();
let arg = if self.check(&Token::LParen) {
self.next(); let expr = self.parse_expression()?;
self.consume(Token::RParen)?;
expr
} else {
self.parse_primary()?
};
Ok(Expression::Function {
name: "sqrt".to_string(),
args: vec![arg],
})
}
Token::Dot => {
self.next();
self.parse_binary_vector_op("dot")
}
Token::Cross => {
self.next();
self.parse_binary_vector_op("cross")
}
Token::Grad => {
self.next();
self.parse_unary_vector_calculus("grad")
}
Token::Div => {
self.next();
self.parse_unary_vector_calculus("div")
}
Token::Curl => {
self.next();
self.parse_unary_vector_calculus("curl")
}
Token::Laplacian => {
self.next();
self.parse_unary_vector_calculus("laplacian")
}
Token::LParen => {
self.next(); let expr = self.parse_expression()?;
self.consume(Token::RParen)?;
Ok(expr)
}
_ => {
let token = self.next().unwrap();
Err(ParseError::unexpected_token(
vec!["number", "variable", "("],
format!("{}", token.value),
Some(token.span),
))
}
}
}
fn parse_function_args(&mut self, name: String) -> ParseResult<Expression> {
self.consume(Token::LParen)?;
let mut args = Vec::new();
if self.check(&Token::RParen) {
let span = self.current_span();
return Err(ParseError::unexpected_token(
vec!["expression"],
")".to_string(),
Some(span),
));
}
args.push(self.parse_expression()?);
while self.check(&Token::Comma) {
self.next(); args.push(self.parse_expression()?);
}
self.consume(Token::RParen)?;
Ok(Expression::Function { name, args })
}
fn parse_binary_vector_op(&mut self, op_name: &str) -> ParseResult<Expression> {
self.consume(Token::LParen)?;
let left = Box::new(self.parse_expression()?);
self.consume(Token::Comma)?;
let right = Box::new(self.parse_expression()?);
self.consume(Token::RParen)?;
match op_name {
"dot" => Ok(Expression::DotProduct { left, right }),
"cross" => Ok(Expression::CrossProduct { left, right }),
_ => unreachable!(),
}
}
fn parse_unary_vector_calculus(&mut self, op_name: &str) -> ParseResult<Expression> {
self.consume(Token::LParen)?;
let arg = Box::new(self.parse_expression()?);
self.consume(Token::RParen)?;
match op_name {
"grad" => Ok(Expression::Gradient { expr: arg }),
"div" => Ok(Expression::Divergence { field: arg }),
"curl" => Ok(Expression::Curl { field: arg }),
"laplacian" => Ok(Expression::Laplacian { expr: arg }),
_ => unreachable!(),
}
}
fn identifier_to_expression(&self, name: String) -> Expression {
match name.as_str() {
"pi" => Expression::Constant(MathConstant::Pi),
"e" => Expression::Constant(MathConstant::E),
"i" => Expression::Constant(MathConstant::I),
"inf" => Expression::Constant(MathConstant::Infinity),
_ => Expression::Variable(name),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_integer() {
let expr = parse("42").unwrap();
assert_eq!(expr, Expression::Integer(42));
}
#[test]
fn test_parse_negative_integer() {
let expr = parse("-17").unwrap();
assert!(matches!(
expr,
Expression::Unary {
op: UnaryOp::Neg,
..
}
));
}
#[test]
fn test_parse_float() {
let expr = parse("3.14").unwrap();
assert!(matches!(expr, Expression::Float(_)));
}
#[test]
fn test_parse_variable() {
let expr = parse("x").unwrap();
assert_eq!(expr, Expression::Variable("x".to_string()));
}
#[test]
fn test_parse_constant_pi() {
let expr = parse("pi").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::Pi));
}
#[test]
fn test_parse_constant_e() {
let expr = parse("e").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::E));
}
#[test]
fn test_parse_constant_i() {
let expr = parse("i").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::I));
}
#[test]
fn test_parse_constant_inf() {
let expr = parse("inf").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::Infinity));
}
#[test]
fn test_parse_constant_neg_inf() {
let expr = parse("-inf").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::NegInfinity));
}
#[test]
fn test_parse_neg_unicode_infinity() {
let expr = parse("-\u{221E}").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::NegInfinity));
}
#[test]
fn test_parse_simple_addition() {
let expr = parse("2 + 3").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
#[test]
fn test_parse_simple_subtraction() {
let expr = parse("5 - 3").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Sub,
..
}
));
}
#[test]
fn test_parse_simple_multiplication() {
let expr = parse("2 * 3").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
}
#[test]
fn test_parse_simple_division() {
let expr = parse("6 / 2").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Div,
..
}
));
}
#[test]
fn test_parse_modulo() {
let expr = parse("7 % 3").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Mod,
..
}
));
}
#[test]
fn test_parse_power() {
let expr = parse("2 ^ 3").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
#[test]
fn test_operator_precedence_mul_over_add() {
let expr = parse("2 + 3 * 4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
}
_ => panic!("Expected addition at top level"),
}
}
#[test]
fn test_operator_precedence_power_over_mul() {
let expr = parse("2 * 3 ^ 4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected multiplication at top level"),
}
}
#[test]
fn test_power_right_associative() {
let expr = parse("2 ^ 3 ^ 4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected power at top level"),
}
}
#[test]
fn test_parse_double_star_power() {
let expr = parse("2**3").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
#[test]
fn test_double_star_with_variable() {
let expr = parse("x**2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected power"),
}
}
#[test]
fn test_double_star_right_associative() {
let expr = parse("2**3**4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected power at top level"),
}
}
#[test]
fn test_star_vs_double_star_in_expression() {
let expr = parse("2*3**4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected multiplication at top level"),
}
}
#[test]
fn test_mixed_caret_and_double_star() {
let expr = parse("2^3**4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected power at top level"),
}
}
#[test]
fn test_parentheses_override_precedence() {
let expr = parse("(2 + 3) * 4").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert!(matches!(*right, Expression::Integer(4)));
}
_ => panic!("Expected multiplication at top level"),
}
}
#[test]
fn test_unary_negation() {
let expr = parse("-5").unwrap();
assert!(matches!(
expr,
Expression::Unary {
op: UnaryOp::Neg,
..
}
));
}
#[test]
fn test_unary_positive() {
let expr = parse("+5").unwrap();
assert!(matches!(
expr,
Expression::Unary {
op: UnaryOp::Pos,
..
}
));
}
#[test]
fn test_factorial() {
let expr = parse("5!").unwrap();
assert!(matches!(
expr,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
}
#[test]
fn test_double_factorial() {
let expr = parse("5!!").unwrap();
match expr {
Expression::Unary {
op: UnaryOp::Factorial,
operand,
} => {
assert!(matches!(
*operand,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
}
_ => panic!("Expected factorial"),
}
}
#[test]
fn test_function_call_no_args_errors() {
let result = parse("f()");
assert!(result.is_err());
}
#[test]
fn test_function_call_one_arg() {
let expr = parse("sin(x)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Variable(_)));
}
_ => panic!("Expected function call"),
}
}
#[test]
fn test_function_call_multiple_args() {
let expr = parse("max(1, 2, 3)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "max");
assert_eq!(args.len(), 3);
}
_ => panic!("Expected function call"),
}
}
#[test]
fn test_nested_function_calls() {
let expr = parse("sin(cos(x))").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Function { .. }));
}
_ => panic!("Expected function call"),
}
}
#[test]
fn test_function_with_expression_args() {
let expr = parse("pow(x, 2 + 3)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "pow");
assert_eq!(args.len(), 2);
assert!(matches!(args[0], Expression::Variable(_)));
assert!(matches!(args[1], Expression::Binary { .. }));
}
_ => panic!("Expected function call"),
}
}
#[test]
fn test_complex_expression() {
let expr = parse("(2 + 3) * sin(x) - 4^2").unwrap();
assert!(matches!(expr, Expression::Binary { .. }));
}
#[test]
fn test_trig_functions() {
let expr = parse("sin(x)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected function"),
}
let expr = parse("cos(2*pi)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "cos");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Binary { .. }));
}
_ => panic!("Expected function"),
}
let expr = parse("tan(x)").unwrap();
match expr {
Expression::Function { name, .. } => {
assert_eq!(name, "tan");
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_inverse_trig_functions() {
let expr = parse("asin(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "asin"),
_ => panic!("Expected function"),
}
let expr = parse("acos(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "acos"),
_ => panic!("Expected function"),
}
let expr = parse("atan(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "atan"),
_ => panic!("Expected function"),
}
let expr = parse("atan2(y, x)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "atan2");
assert_eq!(args.len(), 2);
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_hyperbolic_functions() {
let expr = parse("sinh(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "sinh"),
_ => panic!("Expected function"),
}
let expr = parse("cosh(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "cosh"),
_ => panic!("Expected function"),
}
let expr = parse("tanh(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "tanh"),
_ => panic!("Expected function"),
}
}
#[test]
fn test_logarithmic_functions() {
let expr = parse("log(2, 8)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "log");
assert_eq!(args.len(), 2);
}
_ => panic!("Expected function"),
}
let expr = parse("ln(x)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "ln");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected function"),
}
let expr = parse("exp(-x)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "exp");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Unary { .. }));
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_other_functions() {
let expr = parse("sqrt(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "sqrt"),
_ => panic!("Expected function"),
}
let expr = parse("abs(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "abs"),
_ => panic!("Expected function"),
}
let expr = parse("floor(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "floor"),
_ => panic!("Expected function"),
}
let expr = parse("ceil(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "ceil"),
_ => panic!("Expected function"),
}
let expr = parse("sgn(x)").unwrap();
match expr {
Expression::Function { name, .. } => assert_eq!(name, "sgn"),
_ => panic!("Expected function"),
}
}
#[test]
fn test_multi_argument_functions() {
let expr = parse("max(a, b)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "max");
assert_eq!(args.len(), 2);
}
_ => panic!("Expected function"),
}
let expr = parse("min(a, b, c)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "min");
assert_eq!(args.len(), 3);
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_deeply_nested_functions() {
let expr = parse("sin(cos(x))").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
match &args[0] {
Expression::Function { name, args } => {
assert_eq!(name, "cos");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected nested function"),
}
}
_ => panic!("Expected function"),
}
let expr = parse("max(min(a, b), c)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "max");
assert_eq!(args.len(), 2);
match &args[0] {
Expression::Function { name, .. } => assert_eq!(name, "min"),
_ => panic!("Expected nested function"),
}
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_functions_with_complex_expressions() {
let expr = parse("sin(x + y)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
assert!(matches!(
args[0],
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected function"),
}
let expr = parse("log(2, x^2 + 1)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "log");
assert_eq!(args.len(), 2);
assert!(matches!(args[0], Expression::Integer(2)));
assert!(matches!(
args[1],
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected function"),
}
let expr = parse("sqrt(x^2 + y^2)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sqrt");
assert_eq!(args.len(), 1);
assert!(matches!(
args[0],
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_custom_function_names() {
let expr = parse("myFunc(x)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "myFunc");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected function"),
}
let expr = parse("customFunction(a, b, c)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "customFunction");
assert_eq!(args.len(), 3);
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_function_in_complex_expression() {
let expr = parse("2 * sin(x) + cos(y)").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
match *left {
Expression::Binary {
op: BinaryOp::Mul, ..
} => {}
_ => panic!("Expected multiplication on left"),
}
match *right {
Expression::Function { name, .. } => assert_eq!(name, "cos"),
_ => panic!("Expected function on right"),
}
}
_ => panic!("Expected binary addition"),
}
let expr = parse("pow(x, 2)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "pow");
assert_eq!(args.len(), 2);
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_nested_parentheses() {
let expr = parse("((2 + 3) * (4 + 5))").unwrap();
assert!(matches!(expr, Expression::Binary { .. }));
}
#[test]
fn test_multiple_unary_operators() {
let expr = parse("--5").unwrap();
match expr {
Expression::Unary {
op: UnaryOp::Neg,
operand,
} => {
assert!(matches!(
*operand,
Expression::Unary {
op: UnaryOp::Neg,
..
}
));
}
_ => panic!("Expected negation"),
}
}
#[test]
fn test_whitespace_handling() {
let expr1 = parse("2+3").unwrap();
let expr2 = parse("2 + 3").unwrap();
let expr3 = parse(" 2 + 3 ").unwrap();
assert_eq!(expr1, expr2);
assert_eq!(expr2, expr3);
}
#[test]
fn test_empty_string() {
let result = parse("");
assert!(result.is_err());
}
#[test]
fn test_trailing_operator() {
let result = parse("2 +");
assert!(result.is_err());
}
#[test]
fn test_missing_operand() {
let result = parse("+ 2");
assert!(result.is_ok());
}
#[test]
fn test_unmatched_parenthesis() {
let result = parse("(2 + 3");
assert!(result.is_err());
}
#[test]
fn test_extra_closing_parenthesis() {
let result = parse("2 + 3)");
assert!(result.is_err());
}
#[test]
fn test_parse_simple_equation() {
let expr = parse("x = 5").unwrap();
match expr {
Expression::Equation { left, right } => {
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(5));
}
_ => panic!("Expected Equation variant"),
}
}
#[test]
fn test_parse_inequality_less_than() {
let expr = parse("x < 5").unwrap();
match expr {
Expression::Inequality { op, left, right } => {
assert_eq!(op, InequalityOp::Lt);
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(5));
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_greater_than() {
let expr = parse("x > 0").unwrap();
match expr {
Expression::Inequality { op, left, right } => {
assert_eq!(op, InequalityOp::Gt);
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(0));
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_less_equal() {
let expr = parse("x <= 3").unwrap();
match expr {
Expression::Inequality { op, left, right } => {
assert_eq!(op, InequalityOp::Le);
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(3));
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_greater_equal() {
let expr = parse("x >= -1").unwrap();
match expr {
Expression::Inequality { op, .. } => {
assert_eq!(op, InequalityOp::Ge);
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_not_equal() {
let expr = parse("x != 0").unwrap();
match expr {
Expression::Inequality { op, left, right } => {
assert_eq!(op, InequalityOp::Ne);
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(0));
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_unicode_le() {
let expr = parse("x ≤ 3").unwrap();
match expr {
Expression::Inequality { op, .. } => {
assert_eq!(op, InequalityOp::Le);
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_unicode_ge() {
let expr = parse("x ≥ -1").unwrap();
match expr {
Expression::Inequality { op, .. } => {
assert_eq!(op, InequalityOp::Ge);
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_inequality_unicode_ne() {
let expr = parse("a ≠b").unwrap();
match expr {
Expression::Inequality { op, .. } => {
assert_eq!(op, InequalityOp::Ne);
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_parse_complex_equation() {
let expr = parse("2*x + 1 = 5").unwrap();
match expr {
Expression::Equation { left, right } => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert_eq!(*right, Expression::Integer(5));
}
_ => panic!("Expected Equation variant"),
}
}
#[test]
fn test_parse_complex_inequality() {
let expr = parse("a + b < c + d").unwrap();
match expr {
Expression::Inequality { op, left, right } => {
assert_eq!(op, InequalityOp::Lt);
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_chained_relation_error() {
let result = parse("a < b < c");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = e.to_string();
assert!(error_msg.contains("chained relations"));
}
}
#[test]
fn test_relation_precedence_over_addition() {
let expr = parse("2 + 3 = 5").unwrap();
match expr {
Expression::Equation { left, right } => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert_eq!(*right, Expression::Integer(5));
}
_ => panic!("Expected Equation variant"),
}
}
#[test]
fn test_relation_with_parentheses() {
let expr = parse("(x + 1) > y").unwrap();
match expr {
Expression::Inequality { op, left, right } => {
assert_eq!(op, InequalityOp::Gt);
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert_eq!(*right, Expression::Variable("y".to_string()));
}
_ => panic!("Expected Inequality variant"),
}
}
#[test]
fn test_implicit_mult_number_variable() {
let config = ParserConfig::default();
let expr = parse_with_config("2x", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
assert_eq!(*right, Expression::Variable("x".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_float_variable() {
let config = ParserConfig::default();
let expr = parse_with_config("3.14r", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(*left, Expression::Float(_)));
assert_eq!(*right, Expression::Variable("r".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_number_parens() {
let config = ParserConfig::default();
let expr = parse_with_config("2(x+1)", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_variable_variable() {
let config = ParserConfig::default();
let expr = parse_with_config("x y", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Variable("y".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_variable_chain() {
let config = ParserConfig::default();
let expr = parse_with_config("x y z", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert_eq!(*right, Expression::Variable("z".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_constant_variable() {
let config = ParserConfig::default();
let expr = parse_with_config("pi x", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Constant(MathConstant::Pi));
assert_eq!(*right, Expression::Variable("x".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
#[ignore] fn test_implicit_mult_parens_parens() {
let config = ParserConfig::default();
let expr = parse_with_config("(a)(b)", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("a".to_string()));
assert_eq!(*right, Expression::Variable("b".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_parens_variable() {
let config = ParserConfig::default();
let expr = parse_with_config("(a)x", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("a".to_string()));
assert_eq!(*right, Expression::Variable("x".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_complex_expression() {
let config = ParserConfig::default();
let expr = parse_with_config("2x + 3y", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
}
_ => panic!("Expected addition"),
}
}
#[test]
fn test_implicit_mult_with_power() {
let config = ParserConfig::default();
let expr = parse_with_config("2x^2", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_no_implicit_mult_function_call() {
let config = ParserConfig::default();
let expr = parse_with_config("sin(x)", &config).unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected function call, not implicit multiplication"),
}
}
#[test]
fn test_implicit_mult_disabled() {
let config = ParserConfig {
implicit_multiplication: false,
..ParserConfig::default()
};
let result = parse_with_config("2x", &config);
assert!(result.is_err());
}
#[test]
#[ignore] fn test_implicit_mult_mixed_with_explicit() {
let config = ParserConfig::default();
let expr = parse_with_config("2x * 3y", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_parenthesized_sum() {
let config = ParserConfig::default();
let expr = parse_with_config("(a + b)(c + d)", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_number_function() {
let config = ParserConfig::default();
let expr = parse_with_config("2sin(x)", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
match *right {
Expression::Function { name, .. } => assert_eq!(name, "sin"),
_ => panic!("Expected function on right"),
}
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_default_config() {
let expr = parse("2x").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul, ..
} => {}
_ => panic!("Expected multiplication with default config"),
}
}
#[test]
fn test_implicit_mult_precedence() {
let config = ParserConfig::default();
let expr = parse_with_config("2x + 1", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert_eq!(*right, Expression::Integer(1));
}
_ => panic!("Expected addition at top level"),
}
}
}
#[cfg(test)]
mod extended_basic_arithmetic {
use super::*;
#[test]
fn test_scientific_notation_positive_exponent() {
let expr = parse("1.5e10").unwrap();
match expr {
Expression::Float(f) => {
assert_eq!(f.value(), 1.5e10);
}
_ => panic!("Expected float with scientific notation"),
}
}
#[test]
fn test_scientific_notation_negative_exponent() {
let expr = parse("2.5e-5").unwrap();
match expr {
Expression::Float(f) => {
assert_eq!(f.value(), 2.5e-5);
}
_ => panic!("Expected float with negative exponent"),
}
}
#[test]
fn test_scientific_notation_uppercase_e() {
let expr = parse("3.14E8").unwrap();
match expr {
Expression::Float(f) => {
assert_eq!(f.value(), 3.14e8);
}
_ => panic!("Expected float with uppercase E"),
}
}
#[test]
fn test_scientific_notation_with_positive_sign() {
let expr = parse("1e+3").unwrap();
match expr {
Expression::Float(f) => {
assert_eq!(f.value(), 1000.0);
}
_ => panic!("Expected float"),
}
}
#[test]
fn test_very_large_integer() {
let expr = parse("9223372036854775807").unwrap(); assert!(matches!(expr, Expression::Integer(_)));
}
#[test]
fn test_zero() {
let expr = parse("0").unwrap();
assert_eq!(expr, Expression::Integer(0));
}
#[test]
fn test_zero_float() {
let expr = parse("0.0").unwrap();
match expr {
Expression::Float(f) => {
assert_eq!(f.value(), 0.0);
}
_ => panic!("Expected float"),
}
}
#[test]
fn test_negative_zero() {
let expr = parse("-0").unwrap();
match expr {
Expression::Unary {
op: UnaryOp::Neg,
operand,
} => {
assert_eq!(*operand, Expression::Integer(0));
}
_ => panic!("Expected negation of zero"),
}
}
#[test]
fn test_mixed_int_float_operations() {
let expr = parse("2 + 3.5").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(*right, Expression::Float(_)));
}
_ => panic!("Expected addition"),
}
}
#[test]
fn test_division_by_zero_parses() {
let expr = parse("1 / 0").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Div,
..
}
));
}
}
#[cfg(test)]
mod extended_operator_precedence {
use super::*;
#[test]
fn test_unary_minus_with_power() {
let expr = parse("-x^2").unwrap();
match expr {
Expression::Unary {
op: UnaryOp::Neg,
operand,
} => {
match *operand {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected power as operand"),
}
}
_ => panic!("Expected negation of power"),
}
}
#[test]
fn test_parenthesized_negation_with_power() {
let expr = parse("(-x)^2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Unary {
op: UnaryOp::Neg,
..
}
));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected power of negation"),
}
}
#[test]
fn test_factorial_then_addition() {
let expr = parse("5! + 1").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
assert_eq!(*right, Expression::Integer(1));
}
_ => panic!("Expected addition"),
}
}
#[test]
fn test_factorial_then_multiplication() {
let expr = parse("5! * 2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_factorial_then_division() {
let expr = parse("5! / 2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Div,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected division"),
}
}
#[test]
fn test_complex_precedence_chain() {
let expr = parse("a + b * c ^ d").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("a".to_string()));
match *right {
Expression::Binary {
op: BinaryOp::Mul,
left: mul_left,
right: mul_right,
} => {
assert_eq!(*mul_left, Expression::Variable("b".to_string()));
assert!(matches!(
*mul_right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected multiplication on right"),
}
}
_ => panic!("Expected addition at top level"),
}
}
#[test]
fn test_left_associativity_of_subtraction() {
let expr = parse("10 - 5 - 2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Sub,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Sub,
..
}
));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected subtraction"),
}
}
#[test]
fn test_left_associativity_of_division() {
let expr = parse("20 / 4 / 2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Div,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Div,
..
}
));
assert_eq!(*right, Expression::Integer(2));
}
_ => panic!("Expected division"),
}
}
#[test]
fn test_multiple_unary_negations() {
let expr = parse("---5").unwrap();
match expr {
Expression::Unary {
op: UnaryOp::Neg,
operand,
} => match *operand {
Expression::Unary {
op: UnaryOp::Neg,
operand: inner,
} => {
assert!(matches!(
*inner,
Expression::Unary {
op: UnaryOp::Neg,
..
}
));
}
_ => panic!("Expected nested negation"),
},
_ => panic!("Expected negation"),
}
}
#[test]
fn test_mixed_unary_operators() {
let expr = parse("-+5").unwrap();
match expr {
Expression::Unary {
op: UnaryOp::Neg,
operand,
} => {
assert!(matches!(
*operand,
Expression::Unary {
op: UnaryOp::Pos,
..
}
));
}
_ => panic!("Expected negation of positive"),
}
}
#[test]
fn test_triple_factorial() {
let expr = parse("5!!!").unwrap();
let mut current = &expr;
let mut factorial_count = 0;
while let Expression::Unary {
op: UnaryOp::Factorial,
operand,
} = current
{
factorial_count += 1;
current = operand;
}
assert_eq!(factorial_count, 3);
assert_eq!(*current, Expression::Integer(5));
}
#[test]
fn test_negative_exponent() {
let result = parse("2^-3");
assert!(result.is_err(), "2^-3 should require parentheses");
let expr = parse("2^(-3)").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
assert!(matches!(
*right,
Expression::Unary {
op: UnaryOp::Neg,
..
}
));
}
_ => panic!("Expected power"),
}
}
#[test]
fn test_power_zero_and_one() {
let expr = parse("x^0").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(0));
}
_ => panic!("Expected power"),
}
let expr = parse("x^1").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("x".to_string()));
assert_eq!(*right, Expression::Integer(1));
}
_ => panic!("Expected power"),
}
}
}
#[cfg(test)]
mod extended_functions {
use super::*;
#[test]
fn test_many_function_arguments() {
let expr = parse("f(a, b, c, d, e, f, g, h, i, j)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "f");
assert_eq!(args.len(), 10);
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_deeply_nested_functions_three_levels() {
let expr = parse("f(g(h(x)))").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "f");
assert_eq!(args.len(), 1);
match &args[0] {
Expression::Function { name, args } => {
assert_eq!(name, "g");
assert_eq!(args.len(), 1);
match &args[0] {
Expression::Function { name, args } => {
assert_eq!(name, "h");
assert_eq!(args.len(), 1);
assert_eq!(args[0], Expression::Variable("x".to_string()));
}
_ => panic!("Expected third level function"),
}
}
_ => panic!("Expected second level function"),
}
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_function_with_factorial_argument() {
let expr = parse("sin(5!)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sin");
assert_eq!(args.len(), 1);
assert!(matches!(
args[0],
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_function_with_equation_argument() {
let expr = parse("solve(x = 5)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "solve");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Equation { .. }));
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_function_with_inequality_argument() {
let expr = parse("filter(x > 0)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "filter");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Inequality { .. }));
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_function_with_all_operator_types() {
let expr = parse("f(a+b, c*d, e^f, g!)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "f");
assert_eq!(args.len(), 4);
assert!(matches!(
args[0],
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert!(matches!(
args[1],
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert!(matches!(
args[2],
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
assert!(matches!(
args[3],
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_function_with_nested_parentheses_argument() {
let expr = parse("f(((x)))").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "f");
assert_eq!(args.len(), 1);
assert_eq!(args[0], Expression::Variable("x".to_string()));
}
_ => panic!("Expected function"),
}
}
}
#[cfg(test)]
mod extended_implicit_multiplication {
use super::*;
#[test]
fn test_implicit_mult_factorial_then_variable() {
let config = ParserConfig::default();
let expr = parse_with_config("5!x", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
assert_eq!(*right, Expression::Variable("x".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_factorial_then_parens() {
let config = ParserConfig::default();
let expr = parse_with_config("5!(x+1)", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Unary {
op: UnaryOp::Factorial,
..
}
));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_chained_three_variables() {
let config = ParserConfig::default();
let expr = parse_with_config("a b c", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert_eq!(*right, Expression::Variable("c".to_string()));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_constant_then_function() {
let config = ParserConfig::default();
let expr = parse_with_config("pi sin(x)", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Constant(MathConstant::Pi));
assert!(matches!(*right, Expression::Function { .. }));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_multiple_numbers_fails() {
let config = ParserConfig::default();
let result = parse_with_config("2 3", &config);
assert!(result.is_err());
}
#[test]
fn test_implicit_mult_power_chain() {
let config = ParserConfig::default();
let expr = parse_with_config("2x^3", &config).unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert_eq!(*left, Expression::Integer(2));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Pow,
..
}
));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_implicit_mult_with_relation() {
let config = ParserConfig::default();
let expr = parse_with_config("2x = 5", &config).unwrap();
match expr {
Expression::Equation { left, right } => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
assert_eq!(*right, Expression::Integer(5));
}
_ => panic!("Expected equation"),
}
}
}
#[cfg(test)]
mod extended_error_cases {
use super::*;
#[test]
fn test_error_leading_comma_in_function() {
let result = parse("f(,x)");
assert!(result.is_err());
}
#[test]
fn test_error_trailing_comma_in_function() {
let result = parse("f(x,)");
assert!(result.is_err());
}
#[test]
fn test_error_double_comma_in_function() {
let result = parse("f(x,,y)");
assert!(result.is_err());
}
#[test]
fn test_error_lone_star_operator() {
let result = parse("*");
assert!(result.is_err());
}
#[test]
fn test_error_lone_slash_operator() {
let result = parse("/");
assert!(result.is_err());
}
#[test]
fn test_error_lone_caret_operator() {
let result = parse("^");
assert!(result.is_err());
}
#[test]
fn test_error_lone_percent_operator() {
let result = parse("%");
assert!(result.is_err());
}
#[test]
fn test_error_empty_parens_not_function() {
let result = parse("()");
assert!(result.is_err());
}
#[test]
fn test_error_just_whitespace() {
let result = parse(" ");
assert!(result.is_err());
}
#[test]
fn test_error_mismatched_brackets() {
let result = parse("[1]");
assert!(result.is_err());
}
#[test]
fn test_error_double_operators() {
let result = parse("2 */ 3");
assert!(result.is_err(), "Mix of * and / should be an error");
let result = parse("2 ^^ 3");
assert!(result.is_err(), "Double caret should be an error");
}
#[test]
fn test_error_triple_equals() {
let result = parse("x === 5");
assert!(result.is_err());
}
#[test]
fn test_error_incomplete_inequality() {
let result = parse("x <");
assert!(result.is_err());
}
#[test]
fn test_error_parentheses_mismatch_extra_open() {
let result = parse("((2 + 3)");
assert!(result.is_err());
}
#[test]
fn test_error_parentheses_mismatch_extra_close() {
let result = parse("(2 + 3))");
assert!(result.is_err());
}
#[test]
fn test_error_invalid_function_name() {
let config = ParserConfig {
implicit_multiplication: false,
..ParserConfig::default()
};
let result = parse_with_config("2func(x)", &config);
assert!(result.is_err());
}
#[test]
fn test_error_unclosed_function_call() {
let result = parse("sin(x");
assert!(result.is_err());
}
#[test]
fn test_error_comma_outside_function() {
let result = parse("x, y");
assert!(result.is_err());
}
}
#[cfg(test)]
mod stress_tests {
use super::*;
#[test]
fn test_deeply_nested_parentheses() {
let expr = parse("((((((((((x))))))))))").unwrap();
assert_eq!(expr, Expression::Variable("x".to_string()));
}
#[test]
fn test_very_long_expression() {
let expr = parse("1+2+3+4+5+6+7+8+9+10").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
#[test]
fn test_complex_nested_expression() {
let expr = parse("2*sin(x^2 + 1)! - cos(y)/(z + 1)").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Sub,
..
}
));
}
#[test]
fn test_many_nested_functions() {
let expr = parse("f(g(x), h(y), i(z), j(a), k(b))").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "f");
assert_eq!(args.len(), 5);
for arg in args {
assert!(matches!(arg, Expression::Function { .. }));
}
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_alternating_operators() {
let expr = parse("a + b - c + d - e").unwrap();
assert!(matches!(
expr,
Expression::Binary {
op: BinaryOp::Sub,
..
}
));
}
#[test]
fn test_complex_precedence_expression() {
let expr = parse("((a + b) * c - d / e) ^ f").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Pow,
left,
right,
} => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Sub,
..
}
));
assert_eq!(*right, Expression::Variable("f".to_string()));
}
_ => panic!("Expected power"),
}
}
#[test]
fn test_parse_unicode_pi() {
let expr = parse("2*Ï€").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Mul,
left,
right,
} => {
assert!(matches!(*left, Expression::Integer(2)));
assert!(matches!(*right, Expression::Constant(MathConstant::Pi)));
}
_ => panic!("Expected multiplication"),
}
}
#[test]
fn test_parse_unicode_infinity() {
let expr = parse("∞").unwrap();
assert_eq!(expr, Expression::Constant(MathConstant::Infinity));
}
#[test]
fn test_parse_unicode_sqrt() {
let expr = parse("√4").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sqrt");
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expression::Integer(4)));
}
_ => panic!("Expected sqrt function call"),
}
}
#[test]
fn test_parse_unicode_sqrt_with_parens() {
let expr = parse("√(x+1)").unwrap();
match expr {
Expression::Function { name, args } => {
assert_eq!(name, "sqrt");
assert_eq!(args.len(), 1);
assert!(matches!(
args[0],
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected sqrt function call"),
}
}
#[test]
fn test_parse_subscript_with_single_digit() {
let expr = parse("x_1").unwrap();
assert_eq!(expr, Expression::Variable("x_1".to_string()));
}
#[test]
fn test_parse_subscript_with_identifier() {
let expr = parse("alpha_i").unwrap();
assert_eq!(expr, Expression::Variable("alpha_i".to_string()));
}
#[test]
fn test_parse_subscript_with_multiple_digits() {
let expr = parse("x_12").unwrap();
assert_eq!(expr, Expression::Variable("x_12".to_string()));
}
#[test]
fn test_parse_subscript_with_multi_char() {
let expr = parse("x_ij").unwrap();
assert_eq!(expr, Expression::Variable("x_ij".to_string()));
}
#[test]
fn test_parse_subscript_in_expression() {
let expr = parse("x_1 + y_2").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add,
left,
right,
} => {
assert_eq!(*left, Expression::Variable("x_1".to_string()));
assert_eq!(*right, Expression::Variable("y_2".to_string()));
}
_ => panic!("Expected addition"),
}
}
#[test]
fn test_parse_subscript_round_trip() {
let input = "x_1";
let expr = parse(input).unwrap();
let output = format!("{}", expr);
assert_eq!(output, input);
}
}
#[cfg(test)]
mod vector_operations {
use super::*;
#[test]
fn test_parse_dot_product() {
let expr = parse("dot(u, v)").unwrap();
match expr {
Expression::DotProduct { left, right } => {
assert_eq!(*left, Expression::Variable("u".to_string()));
assert_eq!(*right, Expression::Variable("v".to_string()));
}
_ => panic!("Expected DotProduct, got {:?}", expr),
}
}
#[test]
fn test_parse_cross_product() {
let expr = parse("cross(u, v)").unwrap();
match expr {
Expression::CrossProduct { left, right } => {
assert_eq!(*left, Expression::Variable("u".to_string()));
assert_eq!(*right, Expression::Variable("v".to_string()));
}
_ => panic!("Expected CrossProduct, got {:?}", expr),
}
}
#[test]
fn test_parse_dot_product_with_expressions() {
let expr = parse("dot(a + b, c * d)").unwrap();
match expr {
Expression::DotProduct { left, right } => {
assert!(matches!(
*left,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
assert!(matches!(
*right,
Expression::Binary {
op: BinaryOp::Mul,
..
}
));
}
_ => panic!("Expected DotProduct"),
}
}
}
#[cfg(test)]
mod vector_calculus {
use super::*;
#[test]
fn test_parse_gradient() {
let expr = parse("grad(f)").unwrap();
match expr {
Expression::Gradient { expr } => {
assert_eq!(*expr, Expression::Variable("f".to_string()));
}
_ => panic!("Expected Gradient, got {:?}", expr),
}
}
#[test]
fn test_parse_divergence() {
let expr = parse("div(F)").unwrap();
match expr {
Expression::Divergence { field } => {
assert_eq!(*field, Expression::Variable("F".to_string()));
}
_ => panic!("Expected Divergence, got {:?}", expr),
}
}
#[test]
fn test_parse_curl() {
let expr = parse("curl(F)").unwrap();
match expr {
Expression::Curl { field } => {
assert_eq!(*field, Expression::Variable("F".to_string()));
}
_ => panic!("Expected Curl, got {:?}", expr),
}
}
#[test]
fn test_parse_laplacian() {
let expr = parse("laplacian(f)").unwrap();
match expr {
Expression::Laplacian { expr } => {
assert_eq!(*expr, Expression::Variable("f".to_string()));
}
_ => panic!("Expected Laplacian, got {:?}", expr),
}
}
#[test]
fn test_parse_vector_calculus_with_expression() {
let expr = parse("grad(x^2 + y^2)").unwrap();
match expr {
Expression::Gradient { expr } => {
assert!(matches!(
*expr,
Expression::Binary {
op: BinaryOp::Add,
..
}
));
}
_ => panic!("Expected Gradient"),
}
}
}
#[cfg(test)]
mod quantifiers {
use super::*;
#[test]
fn test_parse_forall_without_domain() {
let expr = parse("forall x, x > 0").unwrap();
match expr {
Expression::ForAll {
variable,
domain,
body,
} => {
assert_eq!(variable, "x");
assert!(domain.is_none());
assert!(matches!(*body, Expression::Inequality { .. }));
}
_ => panic!("Expected ForAll, got {:?}", expr),
}
}
#[test]
fn test_parse_forall_with_domain() {
let expr = parse("forall x in S, x > 0").unwrap();
match expr {
Expression::ForAll {
variable,
domain,
body,
} => {
assert_eq!(variable, "x");
assert!(domain.is_some());
assert_eq!(*domain.unwrap(), Expression::Variable("S".to_string()));
assert!(matches!(*body, Expression::Inequality { .. }));
}
_ => panic!("Expected ForAll, got {:?}", expr),
}
}
#[test]
fn test_parse_exists_without_domain() {
let expr = parse("exists x, x = 0").unwrap();
match expr {
Expression::Exists {
variable,
domain,
body,
unique,
} => {
assert_eq!(variable, "x");
assert!(domain.is_none());
assert!(!unique);
assert!(matches!(*body, Expression::Equation { .. }));
}
_ => panic!("Expected Exists, got {:?}", expr),
}
}
#[test]
fn test_parse_exists_with_domain() {
let expr = parse("exists y in R, y^2 = 2").unwrap();
match expr {
Expression::Exists {
variable,
domain,
body,
unique,
} => {
assert_eq!(variable, "y");
assert!(domain.is_some());
assert_eq!(*domain.unwrap(), Expression::Variable("R".to_string()));
assert!(!unique);
assert!(matches!(*body, Expression::Equation { .. }));
}
_ => panic!("Expected Exists, got {:?}", expr),
}
}
}
#[cfg(test)]
mod set_operations {
use super::*;
#[test]
fn test_parse_union() {
let expr = parse("A union B").unwrap();
match expr {
Expression::SetOperation { op, left, right } => {
assert_eq!(op, SetOp::Union);
assert_eq!(*left, Expression::Variable("A".to_string()));
assert_eq!(*right, Expression::Variable("B".to_string()));
}
_ => panic!("Expected SetOperation, got {:?}", expr),
}
}
#[test]
fn test_parse_intersect() {
let expr = parse("A intersect B").unwrap();
match expr {
Expression::SetOperation { op, left, right } => {
assert_eq!(op, SetOp::Intersection);
assert_eq!(*left, Expression::Variable("A".to_string()));
assert_eq!(*right, Expression::Variable("B".to_string()));
}
_ => panic!("Expected SetOperation, got {:?}", expr),
}
}
#[test]
fn test_parse_set_membership() {
let expr = parse("x in S").unwrap();
match expr {
Expression::SetRelationExpr {
relation,
element,
set,
} => {
assert_eq!(relation, SetRelation::In);
assert_eq!(*element, Expression::Variable("x".to_string()));
assert_eq!(*set, Expression::Variable("S".to_string()));
}
_ => panic!("Expected SetRelationExpr, got {:?}", expr),
}
}
#[test]
fn test_parse_set_non_membership() {
let expr = parse("x notin S").unwrap();
match expr {
Expression::SetRelationExpr {
relation,
element,
set,
} => {
assert_eq!(relation, SetRelation::NotIn);
assert_eq!(*element, Expression::Variable("x".to_string()));
assert_eq!(*set, Expression::Variable("S".to_string()));
}
_ => panic!("Expected SetRelationExpr, got {:?}", expr),
}
}
#[test]
fn test_parse_chained_set_operations() {
let expr = parse("A union B intersect C").unwrap();
match expr {
Expression::SetOperation {
op: SetOp::Intersection,
left,
right,
} => {
assert!(matches!(
*left,
Expression::SetOperation {
op: SetOp::Union,
..
}
));
assert_eq!(*right, Expression::Variable("C".to_string()));
}
_ => panic!("Expected chained SetOperation, got {:?}", expr),
}
}
}
#[cfg(test)]
mod logical_operators {
use super::*;
#[test]
fn test_parse_and() {
let expr = parse("P and Q").unwrap();
match expr {
Expression::Logical { op, operands } => {
assert_eq!(op, LogicalOp::And);
assert_eq!(operands.len(), 2);
assert_eq!(operands[0], Expression::Variable("P".to_string()));
assert_eq!(operands[1], Expression::Variable("Q".to_string()));
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
#[test]
fn test_parse_or() {
let expr = parse("P or Q").unwrap();
match expr {
Expression::Logical { op, operands } => {
assert_eq!(op, LogicalOp::Or);
assert_eq!(operands.len(), 2);
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
#[test]
fn test_parse_not() {
let expr = parse("not P").unwrap();
match expr {
Expression::Logical { op, operands } => {
assert_eq!(op, LogicalOp::Not);
assert_eq!(operands.len(), 1);
assert_eq!(operands[0], Expression::Variable("P".to_string()));
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
#[test]
fn test_parse_implies() {
let expr = parse("P implies Q").unwrap();
match expr {
Expression::Logical { op, operands } => {
assert_eq!(op, LogicalOp::Implies);
assert_eq!(operands.len(), 2);
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
#[test]
fn test_parse_iff() {
let expr = parse("P iff Q").unwrap();
match expr {
Expression::Logical { op, operands } => {
assert_eq!(op, LogicalOp::Iff);
assert_eq!(operands.len(), 2);
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
#[test]
fn test_parse_complex_logical_expression() {
let expr = parse("P and Q or R").unwrap();
match expr {
Expression::Logical {
op: LogicalOp::Or,
operands,
} => {
assert_eq!(operands.len(), 2);
assert!(matches!(
operands[0],
Expression::Logical {
op: LogicalOp::And,
..
}
));
assert_eq!(operands[1], Expression::Variable("R".to_string()));
}
_ => panic!("Expected Logical with Or, got {:?}", expr),
}
}
#[test]
fn test_parse_not_with_expression() {
let expr = parse("not (x > 0)").unwrap();
match expr {
Expression::Logical { op, operands } => {
assert_eq!(op, LogicalOp::Not);
assert_eq!(operands.len(), 1);
assert!(matches!(operands[0], Expression::Inequality { .. }));
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
}
#[cfg(test)]
mod operator_precedence_extended {
use super::*;
#[test]
fn test_arithmetic_before_logical() {
let expr = parse("x + 1 > 0 and y < 2").unwrap();
match expr {
Expression::Logical {
op: LogicalOp::And,
operands,
} => {
assert_eq!(operands.len(), 2);
assert!(matches!(operands[0], Expression::Inequality { .. }));
assert!(matches!(operands[1], Expression::Inequality { .. }));
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
#[test]
fn test_set_operations_before_logical() {
let expr = parse("x in A and y in B").unwrap();
match expr {
Expression::Logical {
op: LogicalOp::And,
operands,
} => {
assert_eq!(operands.len(), 2);
assert!(matches!(operands[0], Expression::SetRelationExpr { .. }));
assert!(matches!(operands[1], Expression::SetRelationExpr { .. }));
}
_ => panic!("Expected Logical, got {:?}", expr),
}
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_complex_expression_with_all_features() {
let expr = parse("forall x in S, grad(f) > 0 and x notin T").unwrap();
match expr {
Expression::ForAll {
variable,
domain,
body,
} => {
assert_eq!(variable, "x");
assert!(domain.is_some());
match *body {
Expression::Logical {
op: LogicalOp::And,
operands,
} => {
assert_eq!(operands.len(), 2);
assert!(matches!(operands[0], Expression::Inequality { .. }));
assert!(matches!(operands[1], Expression::SetRelationExpr { .. }));
}
_ => panic!("Expected Logical in body"),
}
}
_ => panic!("Expected ForAll"),
}
}
#[test]
fn test_vector_operations_in_equations() {
let expr = parse("dot(u, v) = 0").unwrap();
match expr {
Expression::Equation { left, right } => {
assert!(matches!(*left, Expression::DotProduct { .. }));
assert_eq!(*right, Expression::Integer(0));
}
_ => panic!("Expected Equation"),
}
}
#[test]
fn test_nested_quantifiers() {
let expr = parse("forall x, exists y, x + y = 0").unwrap();
match expr {
Expression::ForAll { body, .. } => match *body {
Expression::Exists { body, .. } => {
assert!(matches!(*body, Expression::Equation { .. }));
}
_ => panic!("Expected Exists in body"),
},
_ => panic!("Expected ForAll"),
}
}
#[test]
fn test_vector_calculus_with_sets() {
let expr = parse("div(F) in R").unwrap();
match expr {
Expression::SetRelationExpr {
relation,
element,
set,
} => {
assert_eq!(relation, SetRelation::In);
assert!(matches!(*element, Expression::Divergence { .. }));
assert_eq!(*set, Expression::Variable("R".to_string()));
}
_ => panic!("Expected SetRelationExpr"),
}
}
#[test]
fn test_logical_with_arithmetic() {
let expr = parse("x^2 + y^2 < 1 or x > 2").unwrap();
match expr {
Expression::Logical {
op: LogicalOp::Or,
operands,
} => {
assert_eq!(operands.len(), 2);
assert!(matches!(operands[0], Expression::Inequality { .. }));
assert!(matches!(operands[1], Expression::Inequality { .. }));
}
_ => panic!("Expected Logical"),
}
}
#[test]
fn test_backward_compatibility_arithmetic() {
let expr = parse("2 + 3 * 4^5").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add, ..
} => {}
_ => panic!("Expected binary addition"),
}
}
#[test]
fn test_backward_compatibility_functions() {
let expr = parse("sin(x) + cos(y)").unwrap();
match expr {
Expression::Binary {
op: BinaryOp::Add, ..
} => {}
_ => panic!("Expected binary addition"),
}
}
}