diff --git a/src/operator/display.rs b/src/operator/display.rs index 96b0987..b30a561 100644 --- a/src/operator/display.rs +++ b/src/operator/display.rs @@ -25,9 +25,17 @@ impl Display for Operator { Or => write!(f, "||"), Not => write!(f, "!"), - Tuple => write!(f, ", "), Assign => write!(f, " = "), + AddAssign => write!(f, " += "), + SubAssign => write!(f, " -= "), + MulAssign => write!(f, " *= "), + DivAssign => write!(f, " /= "), + ModAssign => write!(f, " %= "), + ExpAssign => write!(f, " ^= "), + AndAssign => write!(f, " &&= "), + OrAssign => write!(f, " ||= "), + Tuple => write!(f, ", "), Chain => write!(f, "; "), Const { value } => write!(f, "{}", value), diff --git a/src/operator/mod.rs b/src/operator/mod.rs index e833a1d..eb279c2 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -26,9 +26,17 @@ pub enum Operator { Or, Not, - Tuple, Assign, + AddAssign, + SubAssign, + MulAssign, + DivAssign, + ModAssign, + ExpAssign, + AndAssign, + OrAssign, + Tuple, Chain, Const { value: Value }, @@ -67,9 +75,10 @@ impl Operator { Or => 70, Not => 110, - Tuple => 40, - Assign => 50, + Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign + | AndAssign | OrAssign => 50, + Tuple => 40, Chain => 0, Const { value: _ } => 200, @@ -113,7 +122,8 @@ impl Operator { use crate::operator::Operator::*; match self { Add | Sub | Mul | Div | Mod | Exp | Eq | Neq | Gt | Lt | Geq | Leq | And | Or - | Assign => Some(2), + | Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign + | AndAssign | OrAssign => Some(2), Tuple | Chain => None, Not | Neg | RootNode => Some(1), Const { value: _ } => Some(0), @@ -420,8 +430,9 @@ impl Operator { Ok(Value::Boolean(false)) } }, + Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign + | AndAssign | OrAssign => Err(EvalexprError::ContextNotManipulable), Tuple => Ok(Value::Tuple(arguments.into())), - Assign => Err(EvalexprError::ContextNotManipulable), Chain => { if arguments.is_empty() { return Err(EvalexprError::wrong_operator_argument_amount(0, 1)); @@ -435,6 +446,8 @@ impl Operator { Ok(value.clone()) }, VariableIdentifier { identifier } => { + expect_operator_argument_amount(arguments.len(), 0)?; + if let Some(value) = context.get_value(&identifier).cloned() { Ok(value) } else { @@ -475,6 +488,35 @@ impl Operator { Ok(Value::Empty) }, + AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign | AndAssign + | OrAssign => { + expect_operator_argument_amount(arguments.len(), 2)?; + + let target = arguments[0].as_string()?; + let left_value = Operator::VariableIdentifier { + identifier: target.clone(), + } + .eval(&Vec::new(), context)?; + let arguments = vec![left_value, arguments[1].clone()]; + + let result = match self { + AddAssign => Operator::Add.eval(&arguments, context), + SubAssign => Operator::Sub.eval(&arguments, context), + MulAssign => Operator::Mul.eval(&arguments, context), + DivAssign => Operator::Div.eval(&arguments, context), + ModAssign => Operator::Mod.eval(&arguments, context), + ExpAssign => Operator::Exp.eval(&arguments, context), + AndAssign => Operator::And.eval(&arguments, context), + OrAssign => Operator::Or.eval(&arguments, context), + _ => unreachable!( + "Forgot to add a match arm for an assign operation: {}", + self + ), + }?; + context.set_value(target.into(), result)?; + + Ok(Value::Empty) + }, _ => self.eval(arguments, context), } } diff --git a/src/token/display.rs b/src/token/display.rs index ed8126e..35f2087 100644 --- a/src/token/display.rs +++ b/src/token/display.rs @@ -28,9 +28,19 @@ impl fmt::Display for Token { LBrace => write!(f, "("), RBrace => write!(f, ")"), + // Assignment + Assign => write!(f, "="), + PlusAssign => write!(f, "+="), + MinusAssign => write!(f, "-="), + StarAssign => write!(f, "*="), + SlashAssign => write!(f, "/="), + PercentAssign => write!(f, "%="), + HatAssign => write!(f, "^="), + AndAssign => write!(f, "&&="), + OrAssign => write!(f, "||="), + // Special Comma => write!(f, ","), - Assign => write!(f, "="), Semicolon => write!(f, ";"), // Values => write!(f, ""), Variables and Functions @@ -50,6 +60,12 @@ impl fmt::Display for PartialToken { Token(token) => token.fmt(f), Literal(literal) => literal.fmt(f), Whitespace => write!(f, " "), + Plus => write!(f, "+"), + Minus => write!(f, "-"), + Star => write!(f, "*"), + Slash => write!(f, "/"), + Percent => write!(f, "%"), + Hat => write!(f, "^"), Eq => write!(f, "="), ExclamationMark => write!(f, "!"), Gt => write!(f, ">"), diff --git a/src/token/mod.rs b/src/token/mod.rs index 05dc458..b802d77 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -30,9 +30,19 @@ pub enum Token { LBrace, RBrace, + // Assignment + Assign, + PlusAssign, + MinusAssign, + StarAssign, + SlashAssign, + PercentAssign, + HatAssign, + AndAssign, + OrAssign, + // Special Comma, - Assign, Semicolon, // Values, Variables and Functions @@ -47,6 +57,12 @@ pub enum Token { pub enum PartialToken { Token(Token), Literal(String), + Plus, + Minus, + Star, + Slash, + Percent, + Hat, Whitespace, Eq, ExclamationMark, @@ -59,12 +75,12 @@ pub enum PartialToken { // Make this a const fn as soon as match gets stable (issue #57563) fn char_to_partial_token(c: char) -> PartialToken { match c { - '+' => PartialToken::Token(Token::Plus), - '-' => PartialToken::Token(Token::Minus), - '*' => PartialToken::Token(Token::Star), - '/' => PartialToken::Token(Token::Slash), - '%' => PartialToken::Token(Token::Percent), - '^' => PartialToken::Token(Token::Hat), + '+' => PartialToken::Plus, + '-' => PartialToken::Minus, + '*' => PartialToken::Star, + '/' => PartialToken::Slash, + '%' => PartialToken::Percent, + '^' => PartialToken::Hat, '(' => PartialToken::Token(Token::LBrace), ')' => PartialToken::Token(Token::RBrace), @@ -114,9 +130,18 @@ impl Token { Token::RBrace => false, Token::Comma => false, - Token::Assign => false, Token::Semicolon => false, + Token::Assign => false, + Token::PlusAssign => false, + Token::MinusAssign => false, + Token::StarAssign => false, + Token::SlashAssign => false, + Token::PercentAssign => false, + Token::HatAssign => false, + Token::AndAssign => false, + Token::OrAssign => false, + Token::Identifier(_) => true, Token::Float(_) => true, Token::Int(_) => true, @@ -149,9 +174,18 @@ impl Token { Token::RBrace => true, Token::Comma => false, - Token::Assign => false, Token::Semicolon => false, + Token::Assign => false, + Token::PlusAssign => false, + Token::MinusAssign => false, + Token::StarAssign => false, + Token::SlashAssign => false, + Token::PercentAssign => false, + Token::HatAssign => false, + Token::AndAssign => false, + Token::OrAssign => false, + Token::Identifier(_) => true, Token::Float(_) => true, Token::Int(_) => true, @@ -159,6 +193,15 @@ impl Token { Token::String(_) => true, } } + + pub(crate) fn is_assignment(&self) -> bool { + use Token::*; + match self { + Assign | PlusAssign | MinusAssign | StarAssign | SlashAssign | PercentAssign + | HatAssign | AndAssign | OrAssign => true, + _ => false, + } + } } /// Parses an escape sequence within a string literal. @@ -228,6 +271,7 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult 0 { let first = tokens[0].clone(); let second = tokens.get(1).cloned(); + let third = tokens.get(2).cloned(); let mut cutoff = 2; result.extend( @@ -236,6 +280,48 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult match second { + Some(PartialToken::Eq) => Some(Token::PlusAssign), + _ => { + cutoff = 1; + Some(Token::Plus) + }, + }, + PartialToken::Minus => match second { + Some(PartialToken::Eq) => Some(Token::MinusAssign), + _ => { + cutoff = 1; + Some(Token::Minus) + }, + }, + PartialToken::Star => match second { + Some(PartialToken::Eq) => Some(Token::StarAssign), + _ => { + cutoff = 1; + Some(Token::Star) + }, + }, + PartialToken::Slash => match second { + Some(PartialToken::Eq) => Some(Token::SlashAssign), + _ => { + cutoff = 1; + Some(Token::Slash) + }, + }, + PartialToken::Percent => match second { + Some(PartialToken::Eq) => Some(Token::PercentAssign), + _ => { + cutoff = 1; + Some(Token::Percent) + }, + }, + PartialToken::Hat => match second { + Some(PartialToken::Eq) => Some(Token::HatAssign), + _ => { + cutoff = 1; + Some(Token::Hat) + }, + }, PartialToken::Literal(literal) => { cutoff = 1; if let Ok(number) = literal.parse::() { @@ -281,11 +367,23 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult match second { - Some(PartialToken::Ampersand) => Some(Token::And), + Some(PartialToken::Ampersand) => match third { + Some(PartialToken::Eq) => { + cutoff = 3; + Some(Token::AndAssign) + }, + _ => Some(Token::And), + }, _ => return Err(EvalexprError::unmatched_partial_token(first, second)), }, PartialToken::VerticalBar => match second { - Some(PartialToken::VerticalBar) => Some(Token::Or), + Some(PartialToken::VerticalBar) => match third { + Some(PartialToken::Eq) => { + cutoff = 3; + Some(Token::OrAssign) + }, + _ => Some(Token::Or), + }, _ => return Err(EvalexprError::unmatched_partial_token(first, second)), }, } diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 0793584..58b59d1 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -529,14 +529,23 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult Some(Node::new(Operator::Tuple)), Token::Assign => Some(Node::new(Operator::Assign)), + Token::PlusAssign => Some(Node::new(Operator::AddAssign)), + Token::MinusAssign => Some(Node::new(Operator::SubAssign)), + Token::StarAssign => Some(Node::new(Operator::MulAssign)), + Token::SlashAssign => Some(Node::new(Operator::DivAssign)), + Token::PercentAssign => Some(Node::new(Operator::ModAssign)), + Token::HatAssign => Some(Node::new(Operator::ExpAssign)), + Token::AndAssign => Some(Node::new(Operator::AndAssign)), + Token::OrAssign => Some(Node::new(Operator::OrAssign)), + + Token::Comma => Some(Node::new(Operator::Tuple)), Token::Semicolon => Some(Node::new(Operator::Chain)), Token::Identifier(identifier) => { let mut result = Some(Node::new(Operator::variable_identifier(identifier.clone()))); if let Some(next) = next { - if next == &Token::Assign { + if next.is_assignment() { result = Some(Node::new(Operator::value(identifier.clone().into()))); } else if next.is_leftsided_value() { result = Some(Node::new(Operator::function_identifier(identifier))); diff --git a/tests/integration.rs b/tests/integration.rs index 9a240ca..df5c3f6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -636,3 +636,62 @@ fn test_implicit_context() { Ok("xyzabc".to_string()) ); } + +#[test] +fn test_operator_assignments() { + let mut context = HashMapContext::new(); + assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("a += 5", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("a -= 5", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("a *= 5", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("b = 5.0", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("b /= 5", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("b %= 5", &mut context), Ok(())); + assert_eq!(eval_empty_with_context_mut("b ^= 5", &mut context), Ok(())); + assert_eq!( + eval_empty_with_context_mut("c = true", &mut context), + Ok(()) + ); + assert_eq!( + eval_empty_with_context_mut("c &&= false", &mut context), + Ok(()) + ); + assert_eq!( + eval_empty_with_context_mut("c ||= true", &mut context), + Ok(()) + ); + + let mut context = HashMapContext::new(); + assert_eq!(eval_int_with_context_mut("a = 5; a", &mut context), Ok(5)); + assert_eq!(eval_int_with_context_mut("a += 3; a", &mut context), Ok(8)); + assert_eq!(eval_int_with_context_mut("a -= 5; a", &mut context), Ok(3)); + assert_eq!(eval_int_with_context_mut("a *= 5; a", &mut context), Ok(15)); + assert_eq!( + eval_float_with_context_mut("b = 5.0; b", &mut context), + Ok(5.0) + ); + assert_eq!( + eval_float_with_context_mut("b /= 2; b", &mut context), + Ok(2.5) + ); + assert_eq!( + eval_float_with_context_mut("b %= 2; b", &mut context), + Ok(0.5) + ); + assert_eq!( + eval_float_with_context_mut("b ^= 2; b", &mut context), + Ok(0.25) + ); + assert_eq!( + eval_boolean_with_context_mut("c = true; c", &mut context), + Ok(true) + ); + assert_eq!( + eval_boolean_with_context_mut("c &&= false; c", &mut context), + Ok(false) + ); + assert_eq!( + eval_boolean_with_context_mut("c ||= true; c", &mut context), + Ok(true) + ); +}