diff --git a/CHANGELOG.md b/CHANGELOG.md index 643e1074..812cfa50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug fixes * Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`. * Custom syntax starting with symbols now works correctly and no longer raises a parse error. * Comparing different custom types now works correctly when the appropriate comparison operators are registered. +* Op-assignments to bit flags or bit ranges now work correctly. Potentially breaking changes ---------------------------- diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 9a70177d..4846c9a7 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -7,6 +7,7 @@ use crate::ast::{ SwitchCasesCollection, }; use crate::func::{get_builtin_op_assignment_fn, get_hasher}; +use crate::tokenizer::Token; use crate::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT}; use std::hash::{Hash, Hasher}; @@ -130,38 +131,117 @@ impl Engine { if let Some((hash_x, hash, op_x, op_x_str, op, op_str)) = op_info.get_op_assignment_info() { let mut lock_guard = target.write_lock::().unwrap(); - let args = &mut [&mut *lock_guard, &mut new_val]; + let mut done = false; + // Short-circuit built-in op-assignments if under Fast Operators mode if self.fast_operators() { - if let Some((func, need_context)) = - get_builtin_op_assignment_fn(op_x, args[0], args[1]) - { - // Built-in found - auto_restore! { let orig_level = global.level; global.level += 1 } + #[allow(clippy::wildcard_imports)] + use Token::*; - let context = need_context - .then(|| (self, op_x_str, global.source(), &*global, pos).into()); - return func(context, args).map(|_| ()); + done = true; + + // For extremely simple primary data operations, do it directly + // to avoid the overhead of calling a function. + match (&mut lock_guard.0, &mut new_val.0) { + (Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_x { + AndAssign => *b1 = *b1 && *b2, + OrAssign => *b1 = *b1 || *b2, + XOrAssign => *b1 = *b1 ^ *b2, + _ => done = false, + }, + (Union::Int(n1, ..), Union::Int(n2, ..)) => { + #[cfg(not(feature = "unchecked"))] + #[allow(clippy::wildcard_imports)] + use crate::packages::arithmetic::arith_basic::INT::functions::*; + + #[cfg(not(feature = "unchecked"))] + match op_x { + PlusAssign => { + *n1 = add(*n1, *n2).map_err(|err| err.fill_position(pos))? + } + MinusAssign => { + *n1 = subtract(*n1, *n2).map_err(|err| err.fill_position(pos))? + } + MultiplyAssign => { + *n1 = multiply(*n1, *n2).map_err(|err| err.fill_position(pos))? + } + DivideAssign => { + *n1 = divide(*n1, *n2).map_err(|err| err.fill_position(pos))? + } + ModuloAssign => { + *n1 = modulo(*n1, *n2).map_err(|err| err.fill_position(pos))? + } + _ => done = false, + } + #[cfg(feature = "unchecked")] + match op_x { + PlusAssign => *n1 += *n2, + MinusAssign => *n1 -= *n2, + MultiplyAssign => *n1 *= *n2, + DivideAssign => *n1 /= *n2, + ModuloAssign => *n1 %= *n2, + _ => done = false, + } + } + #[cfg(not(feature = "no_float"))] + (Union::Float(f1, ..), Union::Float(f2, ..)) => match op_x { + PlusAssign => **f1 += **f2, + MinusAssign => **f1 -= **f2, + MultiplyAssign => **f1 *= **f2, + DivideAssign => **f1 /= **f2, + ModuloAssign => **f1 %= **f2, + _ => done = false, + }, + #[cfg(not(feature = "no_float"))] + (Union::Float(f1, ..), Union::Int(n2, ..)) => match op_x { + PlusAssign => **f1 += *n2 as crate::FLOAT, + MinusAssign => **f1 -= *n2 as crate::FLOAT, + MultiplyAssign => **f1 *= *n2 as crate::FLOAT, + DivideAssign => **f1 /= *n2 as crate::FLOAT, + ModuloAssign => **f1 %= *n2 as crate::FLOAT, + _ => done = false, + }, + _ => done = false, + } + + if !done { + if let Some((func, need_context)) = + get_builtin_op_assignment_fn(op_x, &*lock_guard, &new_val) + { + // We may not need to bump the level because built-in's do not need it. + //auto_restore! { let orig_level = global.level; global.level += 1 } + + let args = &mut [&mut *lock_guard, &mut new_val]; + let context = need_context + .then(|| (self, op_x_str, global.source(), &*global, pos).into()); + let _ = func(context, args).map_err(|err| err.fill_position(pos))?; + done = true; + } } } - let opx = Some(op_x); + if !done { + let opx = Some(op_x); + let args = &mut [&mut *lock_guard, &mut new_val]; - match self.exec_native_fn_call(global, caches, op_x_str, opx, hash_x, args, true, pos) { - Ok(_) => (), - Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) => + match self + .exec_native_fn_call(global, caches, op_x_str, opx, hash_x, args, true, pos) { - // Expand to `var = var op rhs` - let op = Some(op); + Ok(_) => (), + Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) => + { + // Expand to `var = var op rhs` + let op = Some(op); - *args[0] = self - .exec_native_fn_call(global, caches, op_str, op, hash, args, true, pos)? - .0; + *args[0] = self + .exec_native_fn_call(global, caches, op_str, op, hash, args, true, pos)? + .0; + } + Err(err) => return Err(err), } - Err(err) => return Err(err), - } - self.check_data_size(&*args[0], root.position())?; + self.check_data_size(&*args[0], root.position())?; + } } else { // Normal assignment match target { diff --git a/src/func/builtin.rs b/src/func/builtin.rs index d6b2a9e5..50f757f5 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -684,6 +684,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt return match op { AndAssign => impl_op!(bool = x && as_bool), OrAssign => impl_op!(bool = x || as_bool), + XOrAssign => impl_op!(bool = x ^ as_bool), _ => None, }; } diff --git a/src/func/call.rs b/src/func/call.rs index 5dc96184..079c095b 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1547,6 +1547,9 @@ impl Engine { /// # Main Entry-Point (`FnCallExpr`) /// /// Evaluate a function call expression. + /// + /// This method tries to short-circuit function resolution under Fast Operators mode if the + /// function call is an operator. pub(crate) fn eval_fn_call_expr( &self, global: &mut GlobalRuntimeState, @@ -1570,28 +1573,29 @@ impl Engine { let op_token = op_token.as_ref(); // Short-circuit native unary operator call if under Fast Operators mode - if op_token == Some(&Token::Bang) && self.fast_operators() && args.len() == 1 { + if self.fast_operators() && args.len() == 1 && op_token == Some(&Token::Bang) { let mut value = self .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .0 .flatten(); - match value.0 { - Union::Bool(b, ..) => return Ok((!b).into()), - _ => (), - } - - return value.as_bool().map(|r| (!r).into()).or_else(|_| { - let operand = &mut [&mut value]; - self.exec_fn_call( - global, caches, None, name, op_token, *hashes, operand, false, false, pos, - ) - .map(|(v, ..)| v) - }); + return match value.0 { + Union::Bool(b, ..) => Ok((!b).into()), + _ => { + let operand = &mut [&mut value]; + self.exec_fn_call( + global, caches, None, name, op_token, *hashes, operand, false, false, pos, + ) + .map(|(v, ..)| v) + } + }; } // Short-circuit native binary operator call if under Fast Operators mode if op_token.is_some() && self.fast_operators() && args.len() == 2 { + #[allow(clippy::wildcard_imports)] + use Token::*; + let mut lhs = self .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .0 @@ -1606,23 +1610,19 @@ impl Engine { // to avoid the overhead of calling a function. match (&lhs.0, &rhs.0) { (Union::Unit(..), Union::Unit(..)) => match op_token.unwrap() { - Token::EqualsTo => return Ok(Dynamic::TRUE), - Token::NotEqualsTo - | Token::GreaterThan - | Token::GreaterThanEqualsTo - | Token::LessThan - | Token::LessThanEqualsTo => return Ok(Dynamic::FALSE), + EqualsTo => return Ok(Dynamic::TRUE), + NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan + | LessThanEqualsTo => return Ok(Dynamic::FALSE), _ => (), }, (Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_token.unwrap() { - Token::EqualsTo => return Ok((*b1 == *b2).into()), - Token::NotEqualsTo => return Ok((*b1 != *b2).into()), - Token::GreaterThan - | Token::GreaterThanEqualsTo - | Token::LessThan - | Token::LessThanEqualsTo => return Ok(Dynamic::FALSE), - Token::Pipe => return Ok((*b1 || *b2).into()), - Token::Ampersand => return Ok((*b1 && *b2).into()), + EqualsTo => return Ok((*b1 == *b2).into()), + NotEqualsTo => return Ok((*b1 != *b2).into()), + GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => { + return Ok(Dynamic::FALSE) + } + Pipe => return Ok((*b1 || *b2).into()), + Ampersand => return Ok((*b1 && *b2).into()), _ => (), }, (Union::Int(n1, ..), Union::Int(n2, ..)) => { @@ -1632,87 +1632,87 @@ impl Engine { #[cfg(not(feature = "unchecked"))] match op_token.unwrap() { - Token::EqualsTo => return Ok((*n1 == *n2).into()), - Token::NotEqualsTo => return Ok((*n1 != *n2).into()), - Token::GreaterThan => return Ok((*n1 > *n2).into()), - Token::GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()), - Token::LessThan => return Ok((*n1 < *n2).into()), - Token::LessThanEqualsTo => return Ok((*n1 <= *n2).into()), - Token::Plus => return add(*n1, *n2).map(Into::into), - Token::Minus => return subtract(*n1, *n2).map(Into::into), - Token::Multiply => return multiply(*n1, *n2).map(Into::into), - Token::Divide => return divide(*n1, *n2).map(Into::into), - Token::Modulo => return modulo(*n1, *n2).map(Into::into), + EqualsTo => return Ok((*n1 == *n2).into()), + NotEqualsTo => return Ok((*n1 != *n2).into()), + GreaterThan => return Ok((*n1 > *n2).into()), + GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()), + LessThan => return Ok((*n1 < *n2).into()), + LessThanEqualsTo => return Ok((*n1 <= *n2).into()), + Plus => return add(*n1, *n2).map(Into::into), + Minus => return subtract(*n1, *n2).map(Into::into), + Multiply => return multiply(*n1, *n2).map(Into::into), + Divide => return divide(*n1, *n2).map(Into::into), + Modulo => return modulo(*n1, *n2).map(Into::into), _ => (), } #[cfg(feature = "unchecked")] match op_token.unwrap() { - Token::EqualsTo => return Ok((*n1 == *n2).into()), - Token::NotEqualsTo => return Ok((*n1 != *n2).into()), - Token::GreaterThan => return Ok((*n1 > *n2).into()), - Token::GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()), - Token::LessThan => return Ok((*n1 < *n2).into()), - Token::LessThanEqualsTo => return Ok((*n1 <= *n2).into()), - Token::Plus => return Ok((*n1 + *n2).into()), - Token::Minus => return Ok((*n1 - *n2).into()), - Token::Multiply => return Ok((*n1 * *n2).into()), - Token::Divide => return Ok((*n1 / *n2).into()), - Token::Modulo => return Ok((*n1 % *n2).into()), + EqualsTo => return Ok((*n1 == *n2).into()), + NotEqualsTo => return Ok((*n1 != *n2).into()), + GreaterThan => return Ok((*n1 > *n2).into()), + GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()), + LessThan => return Ok((*n1 < *n2).into()), + LessThanEqualsTo => return Ok((*n1 <= *n2).into()), + Plus => return Ok((*n1 + *n2).into()), + Minus => return Ok((*n1 - *n2).into()), + Multiply => return Ok((*n1 * *n2).into()), + Divide => return Ok((*n1 / *n2).into()), + Modulo => return Ok((*n1 % *n2).into()), _ => (), } } #[cfg(not(feature = "no_float"))] (Union::Float(f1, ..), Union::Float(f2, ..)) => match op_token.unwrap() { - Token::EqualsTo => return Ok((**f1 == **f2).into()), - Token::NotEqualsTo => return Ok((**f1 != **f2).into()), - Token::GreaterThan => return Ok((**f1 > **f2).into()), - Token::GreaterThanEqualsTo => return Ok((**f1 >= **f2).into()), - Token::LessThan => return Ok((**f1 < **f2).into()), - Token::LessThanEqualsTo => return Ok((**f1 <= **f2).into()), - Token::Plus => return Ok((**f1 + **f2).into()), - Token::Minus => return Ok((**f1 - **f2).into()), - Token::Multiply => return Ok((**f1 * **f2).into()), - Token::Divide => return Ok((**f1 / **f2).into()), - Token::Modulo => return Ok((**f1 % **f2).into()), + EqualsTo => return Ok((**f1 == **f2).into()), + NotEqualsTo => return Ok((**f1 != **f2).into()), + GreaterThan => return Ok((**f1 > **f2).into()), + GreaterThanEqualsTo => return Ok((**f1 >= **f2).into()), + LessThan => return Ok((**f1 < **f2).into()), + LessThanEqualsTo => return Ok((**f1 <= **f2).into()), + Plus => return Ok((**f1 + **f2).into()), + Minus => return Ok((**f1 - **f2).into()), + Multiply => return Ok((**f1 * **f2).into()), + Divide => return Ok((**f1 / **f2).into()), + Modulo => return Ok((**f1 % **f2).into()), _ => (), }, #[cfg(not(feature = "no_float"))] (Union::Float(f1, ..), Union::Int(n2, ..)) => match op_token.unwrap() { - Token::EqualsTo => return Ok((**f1 == (*n2 as crate::FLOAT)).into()), - Token::NotEqualsTo => return Ok((**f1 != (*n2 as crate::FLOAT)).into()), - Token::GreaterThan => return Ok((**f1 > (*n2 as crate::FLOAT)).into()), - Token::GreaterThanEqualsTo => return Ok((**f1 >= (*n2 as crate::FLOAT)).into()), - Token::LessThan => return Ok((**f1 < (*n2 as crate::FLOAT)).into()), - Token::LessThanEqualsTo => return Ok((**f1 <= (*n2 as crate::FLOAT)).into()), - Token::Plus => return Ok((**f1 + (*n2 as crate::FLOAT)).into()), - Token::Minus => return Ok((**f1 - (*n2 as crate::FLOAT)).into()), - Token::Multiply => return Ok((**f1 * (*n2 as crate::FLOAT)).into()), - Token::Divide => return Ok((**f1 / (*n2 as crate::FLOAT)).into()), - Token::Modulo => return Ok((**f1 % (*n2 as crate::FLOAT)).into()), + EqualsTo => return Ok((**f1 == (*n2 as crate::FLOAT)).into()), + NotEqualsTo => return Ok((**f1 != (*n2 as crate::FLOAT)).into()), + GreaterThan => return Ok((**f1 > (*n2 as crate::FLOAT)).into()), + GreaterThanEqualsTo => return Ok((**f1 >= (*n2 as crate::FLOAT)).into()), + LessThan => return Ok((**f1 < (*n2 as crate::FLOAT)).into()), + LessThanEqualsTo => return Ok((**f1 <= (*n2 as crate::FLOAT)).into()), + Plus => return Ok((**f1 + (*n2 as crate::FLOAT)).into()), + Minus => return Ok((**f1 - (*n2 as crate::FLOAT)).into()), + Multiply => return Ok((**f1 * (*n2 as crate::FLOAT)).into()), + Divide => return Ok((**f1 / (*n2 as crate::FLOAT)).into()), + Modulo => return Ok((**f1 % (*n2 as crate::FLOAT)).into()), _ => (), }, #[cfg(not(feature = "no_float"))] (Union::Int(n1, ..), Union::Float(f2, ..)) => match op_token.unwrap() { - Token::EqualsTo => return Ok(((*n1 as crate::FLOAT) == **f2).into()), - Token::NotEqualsTo => return Ok(((*n1 as crate::FLOAT) != **f2).into()), - Token::GreaterThan => return Ok(((*n1 as crate::FLOAT) > **f2).into()), - Token::GreaterThanEqualsTo => return Ok(((*n1 as crate::FLOAT) >= **f2).into()), - Token::LessThan => return Ok(((*n1 as crate::FLOAT) < **f2).into()), - Token::LessThanEqualsTo => return Ok(((*n1 as crate::FLOAT) <= **f2).into()), - Token::Plus => return Ok(((*n1 as crate::FLOAT) + **f2).into()), - Token::Minus => return Ok(((*n1 as crate::FLOAT) - **f2).into()), - Token::Multiply => return Ok(((*n1 as crate::FLOAT) * **f2).into()), - Token::Divide => return Ok(((*n1 as crate::FLOAT) / **f2).into()), - Token::Modulo => return Ok(((*n1 as crate::FLOAT) % **f2).into()), + EqualsTo => return Ok(((*n1 as crate::FLOAT) == **f2).into()), + NotEqualsTo => return Ok(((*n1 as crate::FLOAT) != **f2).into()), + GreaterThan => return Ok(((*n1 as crate::FLOAT) > **f2).into()), + GreaterThanEqualsTo => return Ok(((*n1 as crate::FLOAT) >= **f2).into()), + LessThan => return Ok(((*n1 as crate::FLOAT) < **f2).into()), + LessThanEqualsTo => return Ok(((*n1 as crate::FLOAT) <= **f2).into()), + Plus => return Ok(((*n1 as crate::FLOAT) + **f2).into()), + Minus => return Ok(((*n1 as crate::FLOAT) - **f2).into()), + Multiply => return Ok(((*n1 as crate::FLOAT) * **f2).into()), + Divide => return Ok(((*n1 as crate::FLOAT) / **f2).into()), + Modulo => return Ok(((*n1 as crate::FLOAT) % **f2).into()), _ => (), }, (Union::Str(s1, ..), Union::Str(s2, ..)) => match op_token.unwrap() { - Token::EqualsTo => return Ok((s1 == s2).into()), - Token::NotEqualsTo => return Ok((s1 != s2).into()), - Token::GreaterThan => return Ok((s1 > s2).into()), - Token::GreaterThanEqualsTo => return Ok((s1 >= s2).into()), - Token::LessThan => return Ok((s1 < s2).into()), - Token::LessThanEqualsTo => return Ok((s1 <= s2).into()), + EqualsTo => return Ok((s1 == s2).into()), + NotEqualsTo => return Ok((s1 != s2).into()), + GreaterThan => return Ok((s1 > s2).into()), + GreaterThanEqualsTo => return Ok((s1 >= s2).into()), + LessThan => return Ok((s1 < s2).into()), + LessThanEqualsTo => return Ok((s1 <= s2).into()), _ => (), }, _ => (), @@ -1723,8 +1723,8 @@ impl Engine { if let Some((func, need_context)) = get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1]) { - // Built-in found - auto_restore! { let orig_level = global.level; global.level += 1 } + // We may not need to bump the level because built-in's do not need it. + //auto_restore! { let orig_level = global.level; global.level += 1 } let context = need_context.then(|| (self, name.as_str(), None, &*global, pos).into());