Merge pull request #635 from schungx/master

Fast Operators mode.
This commit is contained in:
Stephen Chung 2022-09-04 21:50:10 +08:00 committed by GitHub
commit aaa5254c29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 6994 additions and 324 deletions

View File

@ -4,6 +4,19 @@ Rhai Release Notes
Version 1.10.0 Version 1.10.0
============== ==============
This version introduces _Fast Operators_ mode, which is turned on by default but can be disabled via
a new options API: `Engine::set_fast_operators`.
_Fast Operators_ mode assumes that none of Rhai's built-in operators for standard data types are
overloaded by user-registered functions. In the vast majority of cases this should be so (really,
who overloads the `+` operator for integers anyway?).
This assumption allows the `Engine` to avoid checking for overloads for every single operator call.
This usually results in substantial speed improvements, especially for expressions.
Minimum Rust Version
--------------------
The minimum Rust version is now `1.61.0` in order to use some `const` generics. The minimum Rust version is now `1.61.0` in order to use some `const` generics.
Bug fixes Bug fixes
@ -21,6 +34,10 @@ Deprecated API
New features New features
------------ ------------
### Fast operators
* A new option `Engine::fast_operators` is introduced (default to `true`) to enable/disable _Fast Operators_ mode.
### Fallible type iterators ### Fallible type iterators
* For very special needs, the ability to register fallible type iterators is added. * For very special needs, the ability to register fallible type iterators is added.

View File

@ -235,6 +235,7 @@ fn multiple_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::multiple_fn_rename::my_adds); let m = rhai::exported_module!(crate::multiple_fn_rename::my_adds);
engine.register_global_module(m.into()); engine.register_global_module(m.into());
engine.set_fast_operators(false);
let output_array = engine.eval::<Array>( let output_array = engine.eval::<Array>(
" "

6480
scripts/all_in_one.d.rhai Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,8 @@
let now = timestamp(); let now = timestamp();
const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000 const ANSWER = 78_498;
const MAX_NUMBER_TO_CHECK = 1_000_000;
let prime_mask = []; let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK + 1, true); prime_mask.pad(MAX_NUMBER_TO_CHECK + 1, true);
@ -27,6 +28,6 @@ for p in 2..=MAX_NUMBER_TO_CHECK {
print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`); print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`);
print(`Run time = ${now.elapsed} seconds.`); print(`Run time = ${now.elapsed} seconds.`);
if total_primes_found != 78_498 { if total_primes_found != ANSWER {
print("The answer is WRONG! Should be 78,498!"); print(`The answer is WRONG! Should be ${ANSWER}!`);
} }

View File

@ -7,43 +7,52 @@ use std::prelude::v1::*;
bitflags! { bitflags! {
/// Bit-flags containing all language options for the [`Engine`]. /// Bit-flags containing all language options for the [`Engine`].
pub struct LangOptions: u8 { pub struct LangOptions: u16 {
/// Is `if`-expression allowed? /// Is `if`-expression allowed?
const IF_EXPR = 0b_0000_0001; const IF_EXPR = 0b_0000_0000_0001;
/// Is `switch` expression allowed? /// Is `switch` expression allowed?
const SWITCH_EXPR = 0b_0000_0010; const SWITCH_EXPR = 0b_0000_0000_0010;
/// Is statement-expression allowed? /// Is statement-expression allowed?
const STMT_EXPR = 0b_0000_0100; const STMT_EXPR = 0b_0000_0000_0100;
/// Is anonymous function allowed? /// Is anonymous function allowed?
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
const ANON_FN = 0b_0000_1000; const ANON_FN = 0b_0000_0000_1000;
/// Is looping allowed? /// Is looping allowed?
const LOOPING = 0b_0001_0000; const LOOPING = 0b_0000_0001_0000;
/// Is variables shadowing allowed? /// Is variables shadowing allowed?
const SHADOW = 0b_0010_0000; const SHADOW = 0b_0000_0010_0000;
/// Strict variables mode? /// Strict variables mode?
const STRICT_VAR = 0b_0100_0000; const STRICT_VAR = 0b_0000_0100_0000;
/// Raise error if an object map property does not exist? /// Raise error if an object map property does not exist?
/// Returns `()` if `false`. /// Returns `()` if `false`.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
const FAIL_ON_INVALID_MAP_PROPERTY = 0b_1000_0000; const FAIL_ON_INVALID_MAP_PROPERTY = 0b_0000_1000_0000;
/// Fast operators mode?
const FAST_OPS = 0b_0001_0000_0000;
} }
} }
impl LangOptions { impl LangOptions {
/// Create a new [`LangOptions`] with default values. /// Create a new [`LangOptions`] with default values.
#[inline(always)] #[inline(always)]
#[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::IF_EXPR | Self::SWITCH_EXPR | Self::STMT_EXPR | Self::LOOPING | Self::SHADOW | { Self::IF_EXPR
#[cfg(not(feature = "no_function"))] | Self::SWITCH_EXPR
{ | Self::STMT_EXPR
Self::ANON_FN | Self::LOOPING
| Self::SHADOW
| Self::FAST_OPS
| {
#[cfg(not(feature = "no_function"))]
{
Self::ANON_FN
}
#[cfg(feature = "no_function")]
{
Self::empty()
}
} }
#[cfg(feature = "no_function")]
{
Self::empty()
}
}
} }
} }
@ -158,4 +167,16 @@ impl Engine {
self.options self.options
.set(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY, enable); .set(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY, enable);
} }
/// Is fast operators mode enabled?
/// Default is `false`.
#[inline(always)]
#[must_use]
pub const fn fast_operators(&self) -> bool {
self.options.contains(LangOptions::FAST_OPS)
}
/// Set whether fast operators mode is enabled.
#[inline(always)]
pub fn set_fast_operators(&mut self, enable: bool) {
self.options.set(LangOptions::FAST_OPS, enable);
}
} }

View File

@ -190,6 +190,8 @@ pub struct FnCallExpr {
pub args: StaticVec<Expr>, pub args: StaticVec<Expr>,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture_parent_scope: bool, pub capture_parent_scope: bool,
/// Is this function call a native operator?
pub is_native_operator: bool,
/// [Position] of the function name. /// [Position] of the function name.
pub pos: Position, pub pos: Position,
} }
@ -204,6 +206,9 @@ impl fmt::Debug for FnCallExpr {
if self.capture_parent_scope { if self.capture_parent_scope {
ff.field("capture_parent_scope", &self.capture_parent_scope); ff.field("capture_parent_scope", &self.capture_parent_scope);
} }
if self.is_native_operator {
ff.field("is_native_operator", &self.is_native_operator);
}
ff.field("hash", &self.hashes) ff.field("hash", &self.hashes)
.field("name", &self.name) .field("name", &self.name)
.field("args", &self.args); .field("args", &self.args);
@ -662,6 +667,7 @@ impl Expr {
hashes: calc_fn_hash(f.fn_name(), 1).into(), hashes: calc_fn_hash(f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false, capture_parent_scope: false,
is_native_operator: false,
pos, pos,
} }
.into(), .into(),

View File

@ -3,11 +3,16 @@
use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::ast::{Expr, FnCallExpr, OpAssignment}; use crate::ast::{Expr, FnCallExpr, OpAssignment};
use crate::engine::{KEYWORD_THIS, OP_CONCAT}; use crate::engine::{KEYWORD_THIS, OP_CONCAT};
use crate::eval::FnResolutionCacheEntry;
use crate::func::{
calc_fn_params_hash, combine_hashes, gen_fn_call_signature, get_builtin_binary_op_fn,
CallableFunction, FnAny,
};
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR}; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
use std::num::NonZeroUsize;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{collections::btree_map::Entry, num::NonZeroUsize};
impl Engine { impl Engine {
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
@ -218,19 +223,78 @@ impl Engine {
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
let FnCallExpr { let FnCallExpr {
name, name, hashes, args, ..
#[cfg(not(feature = "no_module"))]
namespace,
capture_parent_scope: capture,
hashes,
args,
..
} = expr; } = expr;
// Short-circuit native binary operator call if under Fast Operators mode
if expr.is_native_operator && self.fast_operators() && (args.len() == 1 || args.len() == 2)
{
let mut lhs = self
.get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)?
.0
.flatten();
let mut rhs = if args.len() == 2 {
self.get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)?
.0
.flatten()
} else {
Dynamic::UNIT
};
let mut operands = [&mut lhs, &mut rhs];
let operands = if args.len() == 2 {
&mut operands[..]
} else {
&mut operands[0..1]
};
let hash = calc_fn_params_hash(operands.iter().map(|a| a.type_id()));
let hash = combine_hashes(hashes.native, hash);
let cache = caches.fn_resolution_cache_mut();
let func = if let Entry::Vacant(entry) = cache.entry(hash) {
let func = if args.len() == 2 {
get_builtin_binary_op_fn(&name, operands[0], operands[1])
} else {
None
};
if let Some(f) = func {
entry.insert(Some(FnResolutionCacheEntry {
func: CallableFunction::from_method(Box::new(f) as Box<FnAny>),
source: None,
}));
&cache.get(&hash).unwrap().as_ref().unwrap().func
} else {
let result = self.exec_fn_call(
None, global, caches, lib, name, *hashes, operands, false, false, pos,
level,
);
return result.map(|(v, ..)| v);
}
} else if let Some(entry) = cache.get(&hash).unwrap() {
&entry.func
} else {
let sig = gen_fn_call_signature(self, name, operands);
return Err(ERR::ErrorFunctionNotFound(sig, pos).into());
};
let context = (self, name, None, &*global, lib, pos, level).into();
let result = if func.is_plugin_fn() {
func.get_plugin_fn().unwrap().call(context, operands)
} else {
func.get_native_fn().unwrap()(context, operands)
};
return self.check_return_value(result, pos);
}
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if !namespace.is_empty() { if !expr.namespace.is_empty() {
// Qualified function call // Qualified function call
let hash = hashes.native; let hash = hashes.native;
let namespace = &expr.namespace;
return self.make_qualified_function_call( return self.make_qualified_function_call(
scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level, scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level,
@ -244,7 +308,18 @@ impl Engine {
); );
self.make_function_call( self.make_function_call(
scope, global, caches, lib, this_ptr, name, first_arg, args, *hashes, *capture, pos, scope,
global,
caches,
lib,
this_ptr,
name,
first_arg,
args,
*hashes,
expr.capture_parent_scope,
expr.is_native_operator,
pos,
level, level,
) )
} }

View File

@ -115,6 +115,143 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
} }; } };
} }
// Check for common patterns
if type1 == type2 {
if type1 == TypeId::of::<INT>() {
#[cfg(not(feature = "unchecked"))]
use crate::packages::arithmetic::arith_basic::INT::functions::*;
#[cfg(not(feature = "unchecked"))]
match op {
"+" => return Some(impl_op!(INT => add(as_int, as_int))),
"-" => return Some(impl_op!(INT => subtract(as_int, as_int))),
"*" => return Some(impl_op!(INT => multiply(as_int, as_int))),
"/" => return Some(impl_op!(INT => divide(as_int, as_int))),
"%" => return Some(impl_op!(INT => modulo(as_int, as_int))),
"**" => return Some(impl_op!(INT => power(as_int, as_int))),
">>" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
"<<" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+" => return Some(impl_op!(INT => as_int + as_int)),
"-" => return Some(impl_op!(INT => as_int - as_int)),
"*" => return Some(impl_op!(INT => as_int * as_int)),
"/" => return Some(impl_op!(INT => as_int / as_int)),
"%" => return Some(impl_op!(INT => as_int % as_int)),
"**" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
">>" => return Some(impl_op!(INT => as_int >> as_int)),
"<<" => return Some(impl_op!(INT => as_int << as_int)),
_ => (),
}
return match op {
"==" => Some(impl_op!(INT => as_int == as_int)),
"!=" => Some(impl_op!(INT => as_int != as_int)),
">" => Some(impl_op!(INT => as_int > as_int)),
">=" => Some(impl_op!(INT => as_int >= as_int)),
"<" => Some(impl_op!(INT => as_int < as_int)),
"<=" => Some(impl_op!(INT => as_int <= as_int)),
"&" => Some(impl_op!(INT => as_int & as_int)),
"|" => Some(impl_op!(INT => as_int | as_int)),
"^" => Some(impl_op!(INT => as_int ^ as_int)),
".." => Some(|_, args| {
let x = args[0].as_int().expect(BUILTIN);
let y = args[1].as_int().expect(BUILTIN);
Ok((x..y).into())
}),
"..=" => Some(|_, args| {
let x = args[0].as_int().expect(BUILTIN);
let y = args[1].as_int().expect(BUILTIN);
Ok((x..=y).into())
}),
_ => None,
};
}
if type1 == TypeId::of::<bool>() {
return match op {
"==" => Some(impl_op!(bool => as_bool == as_bool)),
"!=" => Some(impl_op!(bool => as_bool != as_bool)),
">" => Some(impl_op!(bool => as_bool > as_bool)),
">=" => Some(impl_op!(bool => as_bool >= as_bool)),
"<" => Some(impl_op!(bool => as_bool < as_bool)),
"<=" => Some(impl_op!(bool => as_bool <= as_bool)),
"&" => Some(impl_op!(bool => as_bool & as_bool)),
"|" => Some(impl_op!(bool => as_bool | as_bool)),
"^" => Some(impl_op!(bool => as_bool ^ as_bool)),
_ => None,
};
}
if type1 == TypeId::of::<ImmutableString>() {
return match op {
"+" => Some(impl_op!(ImmutableString + ImmutableString)),
"-" => Some(impl_op!(ImmutableString - ImmutableString)),
"==" => Some(impl_op!(ImmutableString == ImmutableString)),
"!=" => Some(impl_op!(ImmutableString != ImmutableString)),
">" => Some(impl_op!(ImmutableString > ImmutableString)),
">=" => Some(impl_op!(ImmutableString >= ImmutableString)),
"<" => Some(impl_op!(ImmutableString < ImmutableString)),
"<=" => Some(impl_op!(ImmutableString <= ImmutableString)),
OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))),
_ => None,
};
}
if type1 == TypeId::of::<char>() {
return match op {
"+" => Some(|_, args| {
let x = args[0].as_char().expect(BUILTIN);
let y = args[1].as_char().expect(BUILTIN);
Ok(format!("{x}{y}").into())
}),
"==" => Some(impl_op!(char => as_char == as_char)),
"!=" => Some(impl_op!(char => as_char != as_char)),
">" => Some(impl_op!(char => as_char > as_char)),
">=" => Some(impl_op!(char => as_char >= as_char)),
"<" => Some(impl_op!(char => as_char < as_char)),
"<=" => Some(impl_op!(char => as_char <= as_char)),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
return match op {
"+" => Some(|_, args| {
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN);
Ok(Dynamic::from_blob(if blob2.is_empty() {
blob1.clone()
} else if blob1.is_empty() {
blob2.clone()
} else {
let mut blob = blob1.clone();
blob.extend(blob2);
blob
}))
}),
"==" => Some(impl_op!(Blob == Blob)),
"!=" => Some(impl_op!(Blob != Blob)),
_ => None,
};
}
if type1 == TypeId::of::<()>() {
return match op {
"==" => Some(|_, _| Ok(Dynamic::TRUE)),
"!=" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
_ => None,
};
}
}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
macro_rules! impl_float { macro_rules! impl_float {
($x:ty, $xx:ident, $y:ty, $yy:ident) => { ($x:ty, $xx:ident, $y:ty, $yy:ident) => {
@ -406,141 +543,6 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
} }
// Beyond here, type1 == type2 // Beyond here, type1 == type2
if type1 == TypeId::of::<INT>() {
#[cfg(not(feature = "unchecked"))]
use crate::packages::arithmetic::arith_basic::INT::functions::*;
#[cfg(not(feature = "unchecked"))]
match op {
"+" => return Some(impl_op!(INT => add(as_int, as_int))),
"-" => return Some(impl_op!(INT => subtract(as_int, as_int))),
"*" => return Some(impl_op!(INT => multiply(as_int, as_int))),
"/" => return Some(impl_op!(INT => divide(as_int, as_int))),
"%" => return Some(impl_op!(INT => modulo(as_int, as_int))),
"**" => return Some(impl_op!(INT => power(as_int, as_int))),
">>" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
"<<" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+" => return Some(impl_op!(INT => as_int + as_int)),
"-" => return Some(impl_op!(INT => as_int - as_int)),
"*" => return Some(impl_op!(INT => as_int * as_int)),
"/" => return Some(impl_op!(INT => as_int / as_int)),
"%" => return Some(impl_op!(INT => as_int % as_int)),
"**" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
">>" => return Some(impl_op!(INT => as_int >> as_int)),
"<<" => return Some(impl_op!(INT => as_int << as_int)),
_ => (),
}
return match op {
"==" => Some(impl_op!(INT => as_int == as_int)),
"!=" => Some(impl_op!(INT => as_int != as_int)),
">" => Some(impl_op!(INT => as_int > as_int)),
">=" => Some(impl_op!(INT => as_int >= as_int)),
"<" => Some(impl_op!(INT => as_int < as_int)),
"<=" => Some(impl_op!(INT => as_int <= as_int)),
"&" => Some(impl_op!(INT => as_int & as_int)),
"|" => Some(impl_op!(INT => as_int | as_int)),
"^" => Some(impl_op!(INT => as_int ^ as_int)),
".." => Some(|_, args| {
let x = args[0].as_int().expect(BUILTIN);
let y = args[1].as_int().expect(BUILTIN);
Ok((x..y).into())
}),
"..=" => Some(|_, args| {
let x = args[0].as_int().expect(BUILTIN);
let y = args[1].as_int().expect(BUILTIN);
Ok((x..=y).into())
}),
_ => None,
};
}
if type1 == TypeId::of::<bool>() {
return match op {
"==" => Some(impl_op!(bool => as_bool == as_bool)),
"!=" => Some(impl_op!(bool => as_bool != as_bool)),
">" => Some(impl_op!(bool => as_bool > as_bool)),
">=" => Some(impl_op!(bool => as_bool >= as_bool)),
"<" => Some(impl_op!(bool => as_bool < as_bool)),
"<=" => Some(impl_op!(bool => as_bool <= as_bool)),
"&" => Some(impl_op!(bool => as_bool & as_bool)),
"|" => Some(impl_op!(bool => as_bool | as_bool)),
"^" => Some(impl_op!(bool => as_bool ^ as_bool)),
_ => None,
};
}
if type1 == TypeId::of::<ImmutableString>() {
return match op {
"+" => Some(impl_op!(ImmutableString + ImmutableString)),
"-" => Some(impl_op!(ImmutableString - ImmutableString)),
"==" => Some(impl_op!(ImmutableString == ImmutableString)),
"!=" => Some(impl_op!(ImmutableString != ImmutableString)),
">" => Some(impl_op!(ImmutableString > ImmutableString)),
">=" => Some(impl_op!(ImmutableString >= ImmutableString)),
"<" => Some(impl_op!(ImmutableString < ImmutableString)),
"<=" => Some(impl_op!(ImmutableString <= ImmutableString)),
OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))),
_ => None,
};
}
if type1 == TypeId::of::<char>() {
return match op {
"+" => Some(|_, args| {
let x = args[0].as_char().expect(BUILTIN);
let y = args[1].as_char().expect(BUILTIN);
Ok(format!("{x}{y}").into())
}),
"==" => Some(impl_op!(char => as_char == as_char)),
"!=" => Some(impl_op!(char => as_char != as_char)),
">" => Some(impl_op!(char => as_char > as_char)),
">=" => Some(impl_op!(char => as_char >= as_char)),
"<" => Some(impl_op!(char => as_char < as_char)),
"<=" => Some(impl_op!(char => as_char <= as_char)),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
return match op {
"+" => Some(|_, args| {
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN);
Ok(Dynamic::from_blob(if blob2.is_empty() {
blob1.clone()
} else if blob1.is_empty() {
blob2.clone()
} else {
let mut blob = blob1.clone();
blob.extend(blob2);
blob
}))
}),
"==" => Some(impl_op!(Blob == Blob)),
"!=" => Some(impl_op!(Blob != Blob)),
_ => None,
};
}
if type1 == TypeId::of::<()>() {
return match op {
"==" => Some(|_, _| Ok(Dynamic::TRUE)),
"!=" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
_ => None,
};
}
None None
} }
@ -594,6 +596,98 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
} }; } };
} }
// Check for common patterns
if type1 == type2 {
if type1 == TypeId::of::<INT>() {
#[cfg(not(feature = "unchecked"))]
use crate::packages::arithmetic::arith_basic::INT::functions::*;
#[cfg(not(feature = "unchecked"))]
match op {
"+=" => return Some(impl_op!(INT => add(as_int, as_int))),
"-=" => return Some(impl_op!(INT => subtract(as_int, as_int))),
"*=" => return Some(impl_op!(INT => multiply(as_int, as_int))),
"/=" => return Some(impl_op!(INT => divide(as_int, as_int))),
"%=" => return Some(impl_op!(INT => modulo(as_int, as_int))),
"**=" => return Some(impl_op!(INT => power(as_int, as_int))),
">>=" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
"<<=" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+=" => return Some(impl_op!(INT += as_int)),
"-=" => return Some(impl_op!(INT -= as_int)),
"*=" => return Some(impl_op!(INT *= as_int)),
"/=" => return Some(impl_op!(INT /= as_int)),
"%=" => return Some(impl_op!(INT %= as_int)),
"**=" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
">>=" => return Some(impl_op!(INT >>= as_int)),
"<<=" => return Some(impl_op!(INT <<= as_int)),
_ => (),
}
return match op {
"&=" => Some(impl_op!(INT &= as_int)),
"|=" => Some(impl_op!(INT |= as_int)),
"^=" => Some(impl_op!(INT ^= as_int)),
_ => None,
};
}
if type1 == TypeId::of::<bool>() {
return match op {
"&=" => Some(impl_op!(bool = x && as_bool)),
"|=" => Some(impl_op!(bool = x || as_bool)),
_ => None,
};
}
if type1 == TypeId::of::<char>() {
return match op {
"+=" => Some(|_, args| {
let y = args[1].as_char().expect(BUILTIN);
let x = &mut *args[0].write_lock::<Dynamic>().expect(BUILTIN);
Ok((*x = format!("{x}{y}").into()).into())
}),
_ => None,
};
}
if type1 == TypeId::of::<ImmutableString>() {
return match op {
"+=" => Some(|_, args| {
let (first, second) = args.split_first_mut().expect(BUILTIN);
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = std::mem::take(second[0]).cast::<ImmutableString>();
Ok((*x += y).into())
}),
"-=" => Some(|_, args| {
let (first, second) = args.split_first_mut().expect(BUILTIN);
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = std::mem::take(second[0]).cast::<ImmutableString>();
Ok((*x -= y).into())
}),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
return match op {
"+=" => Some(|_, args| {
let blob2 = std::mem::take(args[1]).cast::<Blob>();
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
}),
_ => None,
};
}
}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
macro_rules! impl_float { macro_rules! impl_float {
($x:ident, $xx:ident, $y:ty, $yy:ident) => { ($x:ident, $xx:ident, $y:ty, $yy:ident) => {
@ -752,100 +846,5 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
} }
} }
// No built-in op-assignments for different types.
if type2 != type1 {
return None;
}
// Beyond here, type1 == type2
if type1 == TypeId::of::<INT>() {
#[cfg(not(feature = "unchecked"))]
use crate::packages::arithmetic::arith_basic::INT::functions::*;
#[cfg(not(feature = "unchecked"))]
match op {
"+=" => return Some(impl_op!(INT => add(as_int, as_int))),
"-=" => return Some(impl_op!(INT => subtract(as_int, as_int))),
"*=" => return Some(impl_op!(INT => multiply(as_int, as_int))),
"/=" => return Some(impl_op!(INT => divide(as_int, as_int))),
"%=" => return Some(impl_op!(INT => modulo(as_int, as_int))),
"**=" => return Some(impl_op!(INT => power(as_int, as_int))),
">>=" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
"<<=" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+=" => return Some(impl_op!(INT += as_int)),
"-=" => return Some(impl_op!(INT -= as_int)),
"*=" => return Some(impl_op!(INT *= as_int)),
"/=" => return Some(impl_op!(INT /= as_int)),
"%=" => return Some(impl_op!(INT %= as_int)),
"**=" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
">>=" => return Some(impl_op!(INT >>= as_int)),
"<<=" => return Some(impl_op!(INT <<= as_int)),
_ => (),
}
return match op {
"&=" => Some(impl_op!(INT &= as_int)),
"|=" => Some(impl_op!(INT |= as_int)),
"^=" => Some(impl_op!(INT ^= as_int)),
_ => None,
};
}
if type1 == TypeId::of::<bool>() {
return match op {
"&=" => Some(impl_op!(bool = x && as_bool)),
"|=" => Some(impl_op!(bool = x || as_bool)),
_ => None,
};
}
if type1 == TypeId::of::<char>() {
return match op {
"+=" => Some(|_, args| {
let y = args[1].as_char().expect(BUILTIN);
let x = &mut *args[0].write_lock::<Dynamic>().expect(BUILTIN);
Ok((*x = format!("{x}{y}").into()).into())
}),
_ => None,
};
}
if type1 == TypeId::of::<ImmutableString>() {
return match op {
"+=" => Some(|_, args| {
let (first, second) = args.split_first_mut().expect(BUILTIN);
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = std::mem::take(second[0]).cast::<ImmutableString>();
Ok((*x += y).into())
}),
"-=" => Some(|_, args| {
let (first, second) = args.split_first_mut().expect(BUILTIN);
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
let y = std::mem::take(second[0]).cast::<ImmutableString>();
Ok((*x -= y).into())
}),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
return match op {
"+=" => Some(|_, args| {
let blob2 = std::mem::take(args[1]).cast::<Blob>();
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
}),
_ => None,
};
}
None None
} }

View File

@ -127,41 +127,48 @@ pub fn ensure_no_data_race(
Ok(()) Ok(())
} }
impl Engine { /// Generate the signature for a function call.
/// Generate the signature for a function call. #[inline]
#[inline] #[must_use]
#[must_use] pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String {
fn gen_call_signature( format!(
&self, "{fn_name} ({})",
#[cfg(not(feature = "no_module"))] namespace: &crate::ast::Namespace, args.iter()
fn_name: &str, .map(|a| if a.is::<ImmutableString>() {
args: &[&mut Dynamic], "&str | ImmutableString | String"
) -> String {
#[cfg(not(feature = "no_module"))]
let (ns, sep) = (
namespace.to_string(),
if namespace.is_empty() {
""
} else { } else {
crate::tokenizer::Token::DoubleColon.literal_syntax() engine.map_type_name(a.type_name())
}, })
); .collect::<FnArgsVec<_>>()
#[cfg(feature = "no_module")] .join(", ")
let (ns, sep) = ("", ""); )
}
format!( /// Generate the signature for a namespace-qualified function call.
"{ns}{sep}{fn_name} ({})", ///
args.iter() /// Not available under `no_module`.
.map(|a| if a.is::<ImmutableString>() { #[cfg(not(feature = "no_module"))]
"&str | ImmutableString | String" #[inline]
} else { #[must_use]
self.map_type_name(a.type_name()) pub fn gen_qualified_fn_call_signature(
}) engine: &Engine,
.collect::<FnArgsVec<_>>() namespace: &crate::ast::Namespace,
.join(", ") fn_name: &str,
) args: &[&mut Dynamic],
} ) -> String {
let (ns, sep) = (
namespace.to_string(),
if namespace.is_empty() {
""
} else {
crate::tokenizer::Token::DoubleColon.literal_syntax()
},
);
format!("{ns}{sep}{}", gen_fn_call_signature(engine, fn_name, args))
}
impl Engine {
/// Resolve a normal (non-qualified) function call. /// Resolve a normal (non-qualified) function call.
/// ///
/// Search order: /// Search order:
@ -174,26 +181,26 @@ impl Engine {
fn resolve_fn<'s>( fn resolve_fn<'s>(
&self, &self,
_global: &GlobalRuntimeState, _global: &GlobalRuntimeState,
state: &'s mut Caches, caches: &'s mut Caches,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_script: u64, hash_base: u64,
args: Option<&mut FnCallArgs>, args: Option<&mut FnCallArgs>,
allow_dynamic: bool, allow_dynamic: bool,
is_op_assignment: bool, is_op_assignment: bool,
) -> Option<&'s FnResolutionCacheEntry> { ) -> Option<&'s FnResolutionCacheEntry> {
if hash_script == 0 { if hash_base == 0 {
return None; return None;
} }
let mut hash = args.as_ref().map_or(hash_script, |args| { let mut hash = args.as_ref().map_or(hash_base, |args| {
combine_hashes( combine_hashes(
hash_script, hash_base,
calc_fn_params_hash(args.iter().map(|a| a.type_id())), calc_fn_params_hash(args.iter().map(|a| a.type_id())),
) )
}); });
let result = state let result = caches
.fn_resolution_cache_mut() .fn_resolution_cache_mut()
.entry(hash) .entry(hash)
.or_insert_with(|| { .or_insert_with(|| {
@ -248,21 +255,21 @@ impl Engine {
// Check `Dynamic` parameters for functions with parameters // Check `Dynamic` parameters for functions with parameters
if allow_dynamic && max_bitmask == 0 && num_args > 0 { if allow_dynamic && max_bitmask == 0 && num_args > 0 {
let is_dynamic = lib.iter().any(|&m| m.contains_dynamic_fn(hash_script)) let is_dynamic = lib.iter().any(|&m| m.contains_dynamic_fn(hash_base))
|| self || self
.global_modules .global_modules
.iter() .iter()
.any(|m| m.contains_dynamic_fn(hash_script)); .any(|m| m.contains_dynamic_fn(hash_base));
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let is_dynamic = is_dynamic let is_dynamic = is_dynamic
|| _global || _global
.iter_imports_raw() .iter_imports_raw()
.any(|(_, m)| m.contains_dynamic_fn(hash_script)) .any(|(_, m)| m.contains_dynamic_fn(hash_base))
|| self || self
.global_sub_modules .global_sub_modules
.values() .values()
.any(|m| m.contains_dynamic_fn(hash_script)); .any(|m| m.contains_dynamic_fn(hash_base));
// Set maximum bitmask when there are dynamic versions of the function // Set maximum bitmask when there are dynamic versions of the function
if is_dynamic { if is_dynamic {
@ -317,7 +324,7 @@ impl Engine {
} }
}), }),
); );
hash = combine_hashes(hash_script, hash_params); hash = combine_hashes(hash_base, hash_params);
bitmask += 1; bitmask += 1;
} }
@ -405,11 +412,9 @@ impl Engine {
let context = (self, name, source, &*global, lib, pos, level).into(); let context = (self, name, source, &*global, lib, pos, level).into();
let result = if func.is_plugin_fn() { let result = if func.is_plugin_fn() {
func.get_plugin_fn() func.get_plugin_fn().unwrap().call(context, args)
.expect("plugin function")
.call(context, args)
} else { } else {
func.get_native_fn().expect("native function")(context, args) func.get_native_fn().unwrap()(context, args)
}; };
// Restore the original reference // Restore the original reference
@ -541,16 +546,9 @@ impl Engine {
} }
// Raise error // Raise error
_ => Err(ERR::ErrorFunctionNotFound( _ => {
self.gen_call_signature( Err(ERR::ErrorFunctionNotFound(gen_fn_call_signature(self, name, args), pos).into())
#[cfg(not(feature = "no_module"))] }
&crate::ast::Namespace::NONE,
name,
args,
),
pos,
)
.into()),
} }
} }
@ -989,6 +987,7 @@ impl Engine {
args_expr: &[Expr], args_expr: &[Expr],
hashes: FnCallHashes, hashes: FnCallHashes,
capture_scope: bool, capture_scope: bool,
is_operator: bool,
pos: Position, pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
@ -1001,6 +1000,8 @@ impl Engine {
let redirected; // Handle call() - Redirect function call let redirected; // Handle call() - Redirect function call
match name { match name {
_ if is_operator => (),
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let arg = first_arg.unwrap(); let arg = first_arg.unwrap();
@ -1429,7 +1430,7 @@ impl Engine {
Some(f) => unreachable!("unknown function type: {:?}", f), Some(f) => unreachable!("unknown function type: {:?}", f),
None => Err(ERR::ErrorFunctionNotFound( None => Err(ERR::ErrorFunctionNotFound(
self.gen_call_signature(namespace, fn_name, &args), gen_qualified_fn_call_signature(self, namespace, fn_name, &args),
pos, pos,
) )
.into()), .into()),

View File

@ -13,7 +13,9 @@ pub mod script;
pub use args::FuncArgs; pub use args::FuncArgs;
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
pub use call::FnCallArgs; #[cfg(not(feature = "no_module"))]
pub use call::gen_qualified_fn_call_signature;
pub use call::{gen_fn_call_signature, FnCallArgs};
pub use callable_function::CallableFunction; pub use callable_function::CallableFunction;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use func::Func; pub use func::Func;

View File

@ -1181,7 +1181,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return; return;
} }
// Overloaded operators can override built-in. // Overloaded operators can override built-in.
_ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, &arg_types) => { _ if x.args.len() == 2 && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => {
if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1]) if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1])
.and_then(|f| { .and_then(|f| {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]

View File

@ -123,7 +123,7 @@ pub mod array_functions {
/// let x = [1, 2, 3]; /// let x = [1, 2, 3];
/// let y = [true, 'x']; /// let y = [true, 'x'];
/// ///
/// x.push(y); /// x.append(y);
/// ///
/// print(x); // prints "[1, 2, 3, true, 'x']" /// print(x); // prints "[1, 2, 3, true, 'x']"
/// ``` /// ```

View File

@ -614,8 +614,9 @@ impl Engine {
args.shrink_to_fit(); args.shrink_to_fit();
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: id, name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
is_native_operator: false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
hashes, hashes,
@ -687,6 +688,7 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
is_native_operator: false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
hashes, hashes,
@ -1920,6 +1922,7 @@ impl Engine {
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
args, args,
pos, pos,
is_native_operator: true,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1947,6 +1950,7 @@ impl Engine {
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
args, args,
pos, pos,
is_native_operator: true,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1965,6 +1969,7 @@ impl Engine {
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
args, args,
pos, pos,
is_native_operator: true,
..Default::default() ..Default::default()
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -2339,6 +2344,7 @@ impl Engine {
name: state.get_interned_string(op.as_ref()), name: state.get_interned_string(op.as_ref()),
hashes: FnCallHashes::from_native(hash), hashes: FnCallHashes::from_native(hash),
pos, pos,
is_native_operator: !is_valid_function_name(&op),
..Default::default() ..Default::default()
}; };

View File

@ -169,6 +169,22 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
" "
)?); )?);
let value = vec![
String::from("hello"),
String::from("world"),
String::from("foo"),
String::from("bar"),
];
let array: Dynamic = value.into();
assert_eq!(array.type_name(), "array");
let array = array.cast::<Array>();
assert_eq!(array[0].type_name(), "string");
assert_eq!(array.len(), 4);
Ok(()) Ok(())
} }

View File

@ -75,6 +75,17 @@ fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
format!("{s1} Foo!").into() format!("{s1} Foo!").into()
}); });
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
"helloworld"
);
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = (); x + y"#)?,
"hello"
);
engine.set_fast_operators(false);
assert_eq!( assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?, engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
"hello***world" "hello***world"

View File

@ -53,6 +53,13 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
engine.set_optimization_level(OptimizationLevel::Simple); engine.set_optimization_level(OptimizationLevel::Simple);
assert_eq!(
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
42
);
engine.set_fast_operators(false);
assert_eq!( assert_eq!(
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
123 123

View File

@ -75,12 +75,36 @@ fn make_greeting(n: impl std::fmt::Display) -> String {
gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String); gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String);
macro_rules! expand_enum {
($module:ident : $typ:ty => $($variant:ident),+) => {
#[export_module]
pub mod $module {
$(
#[allow(non_upper_case_globals)]
pub const $variant: $typ = <$typ>::$variant;
)*
}
};
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum MyEnum {
Foo,
Bar,
Baz,
Hello,
World,
}
expand_enum! { my_enum_module: MyEnum => Foo, Bar, Baz, Hello, World }
#[test] #[test]
fn test_plugins_package() -> Result<(), Box<EvalAltResult>> { fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut m = Module::new(); let mut m = Module::new();
combine_with_exported_module!(&mut m, "test", test::special_array_package); combine_with_exported_module!(&mut m, "test", test::special_array_package);
combine_with_exported_module!(&mut m, "enum", my_enum_module);
engine.register_global_module(m.into()); engine.register_global_module(m.into());
reg_functions!(engine += greet::single(INT, bool, char)); reg_functions!(engine += greet::single(INT, bool, char));
@ -104,11 +128,14 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
assert_eq!( assert_eq!(
engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?, engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?,
"6 kitties" "6 kitties"
); );
assert_eq!(engine.eval::<INT>("2 + 2")?, 4);
engine.set_fast_operators(false);
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
engine.register_static_module("test", exported_module!(test::special_array_package).into()); engine.register_static_module("test", exported_module!(test::special_array_package).into());