Add short-circuits to op-assignments.

This commit is contained in:
Stephen Chung 2023-03-09 22:03:27 +08:00
parent bb404a415d
commit 906ab3a295
4 changed files with 193 additions and 111 deletions

View File

@ -14,6 +14,7 @@ Bug fixes
* Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`. * 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. * 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. * 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 Potentially breaking changes
---------------------------- ----------------------------

View File

@ -7,6 +7,7 @@ use crate::ast::{
SwitchCasesCollection, SwitchCasesCollection,
}; };
use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::func::{get_builtin_op_assignment_fn, get_hasher};
use crate::tokenizer::Token;
use crate::types::dynamic::{AccessMode, Union}; use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, ERR, INT};
use std::hash::{Hash, Hasher}; 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() { 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::<Dynamic>().unwrap(); let mut lock_guard = target.write_lock::<Dynamic>().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 self.fast_operators() {
if let Some((func, need_context)) = #[allow(clippy::wildcard_imports)]
get_builtin_op_assignment_fn(op_x, args[0], args[1]) use Token::*;
{
// Built-in found
auto_restore! { let orig_level = global.level; global.level += 1 }
let context = need_context done = true;
.then(|| (self, op_x_str, global.source(), &*global, pos).into());
return func(context, args).map(|_| ()); // 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) { match self
Ok(_) => (), .exec_native_fn_call(global, caches, op_x_str, opx, hash_x, args, true, pos)
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_x_str)) =>
{ {
// Expand to `var = var op rhs` Ok(_) => (),
let op = Some(op); 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 *args[0] = self
.exec_native_fn_call(global, caches, op_str, op, hash, args, true, pos)? .exec_native_fn_call(global, caches, op_str, op, hash, args, true, pos)?
.0; .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 { } else {
// Normal assignment // Normal assignment
match target { match target {

View File

@ -684,6 +684,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt
return match op { return match op {
AndAssign => impl_op!(bool = x && as_bool), AndAssign => impl_op!(bool = x && as_bool),
OrAssign => impl_op!(bool = x || as_bool), OrAssign => impl_op!(bool = x || as_bool),
XOrAssign => impl_op!(bool = x ^ as_bool),
_ => None, _ => None,
}; };
} }

View File

@ -1547,6 +1547,9 @@ impl Engine {
/// # Main Entry-Point (`FnCallExpr`) /// # Main Entry-Point (`FnCallExpr`)
/// ///
/// Evaluate a function call expression. /// 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( pub(crate) fn eval_fn_call_expr(
&self, &self,
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
@ -1570,28 +1573,29 @@ impl Engine {
let op_token = op_token.as_ref(); let op_token = op_token.as_ref();
// Short-circuit native unary operator call if under Fast Operators mode // 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 let mut value = self
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
.0 .0
.flatten(); .flatten();
match value.0 { return match value.0 {
Union::Bool(b, ..) => return Ok((!b).into()), Union::Bool(b, ..) => Ok((!b).into()),
_ => (), _ => {
} let operand = &mut [&mut value];
self.exec_fn_call(
return value.as_bool().map(|r| (!r).into()).or_else(|_| { global, caches, None, name, op_token, *hashes, operand, false, false, pos,
let operand = &mut [&mut value]; )
self.exec_fn_call( .map(|(v, ..)| v)
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 // Short-circuit native binary operator call if under Fast Operators mode
if op_token.is_some() && self.fast_operators() && args.len() == 2 { if op_token.is_some() && self.fast_operators() && args.len() == 2 {
#[allow(clippy::wildcard_imports)]
use Token::*;
let mut lhs = self let mut lhs = self
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
.0 .0
@ -1606,23 +1610,19 @@ impl Engine {
// to avoid the overhead of calling a function. // to avoid the overhead of calling a function.
match (&lhs.0, &rhs.0) { match (&lhs.0, &rhs.0) {
(Union::Unit(..), Union::Unit(..)) => match op_token.unwrap() { (Union::Unit(..), Union::Unit(..)) => match op_token.unwrap() {
Token::EqualsTo => return Ok(Dynamic::TRUE), EqualsTo => return Ok(Dynamic::TRUE),
Token::NotEqualsTo NotEqualsTo | GreaterThan | GreaterThanEqualsTo | LessThan
| Token::GreaterThan | LessThanEqualsTo => return Ok(Dynamic::FALSE),
| Token::GreaterThanEqualsTo
| Token::LessThan
| Token::LessThanEqualsTo => return Ok(Dynamic::FALSE),
_ => (), _ => (),
}, },
(Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_token.unwrap() { (Union::Bool(b1, ..), Union::Bool(b2, ..)) => match op_token.unwrap() {
Token::EqualsTo => return Ok((*b1 == *b2).into()), EqualsTo => return Ok((*b1 == *b2).into()),
Token::NotEqualsTo => return Ok((*b1 != *b2).into()), NotEqualsTo => return Ok((*b1 != *b2).into()),
Token::GreaterThan GreaterThan | GreaterThanEqualsTo | LessThan | LessThanEqualsTo => {
| Token::GreaterThanEqualsTo return Ok(Dynamic::FALSE)
| Token::LessThan }
| Token::LessThanEqualsTo => return Ok(Dynamic::FALSE), Pipe => return Ok((*b1 || *b2).into()),
Token::Pipe => return Ok((*b1 || *b2).into()), Ampersand => return Ok((*b1 && *b2).into()),
Token::Ampersand => return Ok((*b1 && *b2).into()),
_ => (), _ => (),
}, },
(Union::Int(n1, ..), Union::Int(n2, ..)) => { (Union::Int(n1, ..), Union::Int(n2, ..)) => {
@ -1632,87 +1632,87 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
match op_token.unwrap() { match op_token.unwrap() {
Token::EqualsTo => return Ok((*n1 == *n2).into()), EqualsTo => return Ok((*n1 == *n2).into()),
Token::NotEqualsTo => return Ok((*n1 != *n2).into()), NotEqualsTo => return Ok((*n1 != *n2).into()),
Token::GreaterThan => return Ok((*n1 > *n2).into()), GreaterThan => return Ok((*n1 > *n2).into()),
Token::GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()), GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()),
Token::LessThan => return Ok((*n1 < *n2).into()), LessThan => return Ok((*n1 < *n2).into()),
Token::LessThanEqualsTo => return Ok((*n1 <= *n2).into()), LessThanEqualsTo => return Ok((*n1 <= *n2).into()),
Token::Plus => return add(*n1, *n2).map(Into::into), Plus => return add(*n1, *n2).map(Into::into),
Token::Minus => return subtract(*n1, *n2).map(Into::into), Minus => return subtract(*n1, *n2).map(Into::into),
Token::Multiply => return multiply(*n1, *n2).map(Into::into), Multiply => return multiply(*n1, *n2).map(Into::into),
Token::Divide => return divide(*n1, *n2).map(Into::into), Divide => return divide(*n1, *n2).map(Into::into),
Token::Modulo => return modulo(*n1, *n2).map(Into::into), Modulo => return modulo(*n1, *n2).map(Into::into),
_ => (), _ => (),
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
match op_token.unwrap() { match op_token.unwrap() {
Token::EqualsTo => return Ok((*n1 == *n2).into()), EqualsTo => return Ok((*n1 == *n2).into()),
Token::NotEqualsTo => return Ok((*n1 != *n2).into()), NotEqualsTo => return Ok((*n1 != *n2).into()),
Token::GreaterThan => return Ok((*n1 > *n2).into()), GreaterThan => return Ok((*n1 > *n2).into()),
Token::GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()), GreaterThanEqualsTo => return Ok((*n1 >= *n2).into()),
Token::LessThan => return Ok((*n1 < *n2).into()), LessThan => return Ok((*n1 < *n2).into()),
Token::LessThanEqualsTo => return Ok((*n1 <= *n2).into()), LessThanEqualsTo => return Ok((*n1 <= *n2).into()),
Token::Plus => return Ok((*n1 + *n2).into()), Plus => return Ok((*n1 + *n2).into()),
Token::Minus => return Ok((*n1 - *n2).into()), Minus => return Ok((*n1 - *n2).into()),
Token::Multiply => return Ok((*n1 * *n2).into()), Multiply => return Ok((*n1 * *n2).into()),
Token::Divide => return Ok((*n1 / *n2).into()), Divide => return Ok((*n1 / *n2).into()),
Token::Modulo => return Ok((*n1 % *n2).into()), Modulo => return Ok((*n1 % *n2).into()),
_ => (), _ => (),
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Union::Float(f1, ..), Union::Float(f2, ..)) => match op_token.unwrap() { (Union::Float(f1, ..), Union::Float(f2, ..)) => match op_token.unwrap() {
Token::EqualsTo => return Ok((**f1 == **f2).into()), EqualsTo => return Ok((**f1 == **f2).into()),
Token::NotEqualsTo => return Ok((**f1 != **f2).into()), NotEqualsTo => return Ok((**f1 != **f2).into()),
Token::GreaterThan => return Ok((**f1 > **f2).into()), GreaterThan => return Ok((**f1 > **f2).into()),
Token::GreaterThanEqualsTo => return Ok((**f1 >= **f2).into()), GreaterThanEqualsTo => return Ok((**f1 >= **f2).into()),
Token::LessThan => return Ok((**f1 < **f2).into()), LessThan => return Ok((**f1 < **f2).into()),
Token::LessThanEqualsTo => return Ok((**f1 <= **f2).into()), LessThanEqualsTo => return Ok((**f1 <= **f2).into()),
Token::Plus => return Ok((**f1 + **f2).into()), Plus => return Ok((**f1 + **f2).into()),
Token::Minus => return Ok((**f1 - **f2).into()), Minus => return Ok((**f1 - **f2).into()),
Token::Multiply => return Ok((**f1 * **f2).into()), Multiply => return Ok((**f1 * **f2).into()),
Token::Divide => return Ok((**f1 / **f2).into()), Divide => return Ok((**f1 / **f2).into()),
Token::Modulo => return Ok((**f1 % **f2).into()), Modulo => return Ok((**f1 % **f2).into()),
_ => (), _ => (),
}, },
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Union::Float(f1, ..), Union::Int(n2, ..)) => match op_token.unwrap() { (Union::Float(f1, ..), Union::Int(n2, ..)) => match op_token.unwrap() {
Token::EqualsTo => return Ok((**f1 == (*n2 as crate::FLOAT)).into()), EqualsTo => return Ok((**f1 == (*n2 as crate::FLOAT)).into()),
Token::NotEqualsTo => return Ok((**f1 != (*n2 as crate::FLOAT)).into()), NotEqualsTo => return Ok((**f1 != (*n2 as crate::FLOAT)).into()),
Token::GreaterThan => return Ok((**f1 > (*n2 as crate::FLOAT)).into()), GreaterThan => return Ok((**f1 > (*n2 as crate::FLOAT)).into()),
Token::GreaterThanEqualsTo => return Ok((**f1 >= (*n2 as crate::FLOAT)).into()), GreaterThanEqualsTo => return Ok((**f1 >= (*n2 as crate::FLOAT)).into()),
Token::LessThan => return Ok((**f1 < (*n2 as crate::FLOAT)).into()), LessThan => return Ok((**f1 < (*n2 as crate::FLOAT)).into()),
Token::LessThanEqualsTo => return Ok((**f1 <= (*n2 as crate::FLOAT)).into()), LessThanEqualsTo => return Ok((**f1 <= (*n2 as crate::FLOAT)).into()),
Token::Plus => return Ok((**f1 + (*n2 as crate::FLOAT)).into()), Plus => return Ok((**f1 + (*n2 as crate::FLOAT)).into()),
Token::Minus => return Ok((**f1 - (*n2 as crate::FLOAT)).into()), Minus => return Ok((**f1 - (*n2 as crate::FLOAT)).into()),
Token::Multiply => return Ok((**f1 * (*n2 as crate::FLOAT)).into()), Multiply => return Ok((**f1 * (*n2 as crate::FLOAT)).into()),
Token::Divide => return Ok((**f1 / (*n2 as crate::FLOAT)).into()), Divide => return Ok((**f1 / (*n2 as crate::FLOAT)).into()),
Token::Modulo => return Ok((**f1 % (*n2 as crate::FLOAT)).into()), Modulo => return Ok((**f1 % (*n2 as crate::FLOAT)).into()),
_ => (), _ => (),
}, },
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Union::Int(n1, ..), Union::Float(f2, ..)) => match op_token.unwrap() { (Union::Int(n1, ..), Union::Float(f2, ..)) => match op_token.unwrap() {
Token::EqualsTo => return Ok(((*n1 as crate::FLOAT) == **f2).into()), EqualsTo => return Ok(((*n1 as crate::FLOAT) == **f2).into()),
Token::NotEqualsTo => return Ok(((*n1 as crate::FLOAT) != **f2).into()), NotEqualsTo => return Ok(((*n1 as crate::FLOAT) != **f2).into()),
Token::GreaterThan => return Ok(((*n1 as crate::FLOAT) > **f2).into()), GreaterThan => return Ok(((*n1 as crate::FLOAT) > **f2).into()),
Token::GreaterThanEqualsTo => return Ok(((*n1 as crate::FLOAT) >= **f2).into()), GreaterThanEqualsTo => return Ok(((*n1 as crate::FLOAT) >= **f2).into()),
Token::LessThan => return Ok(((*n1 as crate::FLOAT) < **f2).into()), LessThan => return Ok(((*n1 as crate::FLOAT) < **f2).into()),
Token::LessThanEqualsTo => return Ok(((*n1 as crate::FLOAT) <= **f2).into()), LessThanEqualsTo => return Ok(((*n1 as crate::FLOAT) <= **f2).into()),
Token::Plus => return Ok(((*n1 as crate::FLOAT) + **f2).into()), Plus => return Ok(((*n1 as crate::FLOAT) + **f2).into()),
Token::Minus => return Ok(((*n1 as crate::FLOAT) - **f2).into()), Minus => return Ok(((*n1 as crate::FLOAT) - **f2).into()),
Token::Multiply => return Ok(((*n1 as crate::FLOAT) * **f2).into()), Multiply => return Ok(((*n1 as crate::FLOAT) * **f2).into()),
Token::Divide => return Ok(((*n1 as crate::FLOAT) / **f2).into()), Divide => return Ok(((*n1 as crate::FLOAT) / **f2).into()),
Token::Modulo => 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() { (Union::Str(s1, ..), Union::Str(s2, ..)) => match op_token.unwrap() {
Token::EqualsTo => return Ok((s1 == s2).into()), EqualsTo => return Ok((s1 == s2).into()),
Token::NotEqualsTo => return Ok((s1 != s2).into()), NotEqualsTo => return Ok((s1 != s2).into()),
Token::GreaterThan => return Ok((s1 > s2).into()), GreaterThan => return Ok((s1 > s2).into()),
Token::GreaterThanEqualsTo => return Ok((s1 >= s2).into()), GreaterThanEqualsTo => return Ok((s1 >= s2).into()),
Token::LessThan => return Ok((s1 < s2).into()), LessThan => return Ok((s1 < s2).into()),
Token::LessThanEqualsTo => return Ok((s1 <= s2).into()), LessThanEqualsTo => return Ok((s1 <= s2).into()),
_ => (), _ => (),
}, },
_ => (), _ => (),
@ -1723,8 +1723,8 @@ impl Engine {
if let Some((func, need_context)) = if let Some((func, need_context)) =
get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1]) get_builtin_binary_op_fn(op_token.as_ref().unwrap(), operands[0], operands[1])
{ {
// Built-in found // 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 } //auto_restore! { let orig_level = global.level; global.level += 1 }
let context = let context =
need_context.then(|| (self, name.as_str(), None, &*global, pos).into()); need_context.then(|| (self, name.as_str(), None, &*global, pos).into());