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
==============
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.
Bug fixes
@ -21,6 +34,10 @@ Deprecated API
New features
------------
### Fast operators
* A new option `Engine::fast_operators` is introduced (default to `true`) to enable/disable _Fast Operators_ mode.
### Fallible type iterators
* 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 m = rhai::exported_module!(crate::multiple_fn_rename::my_adds);
engine.register_global_module(m.into());
engine.set_fast_operators(false);
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();
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 = [];
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(`Run time = ${now.elapsed} seconds.`);
if total_primes_found != 78_498 {
print("The answer is WRONG! Should be 78,498!");
if total_primes_found != ANSWER {
print(`The answer is WRONG! Should be ${ANSWER}!`);
}

View File

@ -7,34 +7,43 @@ use std::prelude::v1::*;
bitflags! {
/// Bit-flags containing all language options for the [`Engine`].
pub struct LangOptions: u8 {
pub struct LangOptions: u16 {
/// Is `if`-expression allowed?
const IF_EXPR = 0b_0000_0001;
const IF_EXPR = 0b_0000_0000_0001;
/// Is `switch` expression allowed?
const SWITCH_EXPR = 0b_0000_0010;
const SWITCH_EXPR = 0b_0000_0000_0010;
/// Is statement-expression allowed?
const STMT_EXPR = 0b_0000_0100;
const STMT_EXPR = 0b_0000_0000_0100;
/// Is anonymous function allowed?
#[cfg(not(feature = "no_function"))]
const ANON_FN = 0b_0000_1000;
const ANON_FN = 0b_0000_0000_1000;
/// Is looping allowed?
const LOOPING = 0b_0001_0000;
const LOOPING = 0b_0000_0001_0000;
/// Is variables shadowing allowed?
const SHADOW = 0b_0010_0000;
const SHADOW = 0b_0000_0010_0000;
/// 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?
/// Returns `()` if `false`.
#[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 {
/// Create a new [`LangOptions`] with default values.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self::IF_EXPR | Self::SWITCH_EXPR | Self::STMT_EXPR | Self::LOOPING | Self::SHADOW | {
Self::IF_EXPR
| Self::SWITCH_EXPR
| Self::STMT_EXPR
| Self::LOOPING
| Self::SHADOW
| Self::FAST_OPS
| {
#[cfg(not(feature = "no_function"))]
{
Self::ANON_FN
@ -158,4 +167,16 @@ impl Engine {
self.options
.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>,
/// Does this function call capture the parent scope?
pub capture_parent_scope: bool,
/// Is this function call a native operator?
pub is_native_operator: bool,
/// [Position] of the function name.
pub pos: Position,
}
@ -204,6 +206,9 @@ impl fmt::Debug for FnCallExpr {
if 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)
.field("name", &self.name)
.field("args", &self.args);
@ -662,6 +667,7 @@ impl Expr {
hashes: calc_fn_hash(f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false,
is_native_operator: false,
pos,
}
.into(),

View File

@ -3,11 +3,16 @@
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::ast::{Expr, FnCallExpr, OpAssignment};
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::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
use std::num::NonZeroUsize;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{collections::btree_map::Entry, num::NonZeroUsize};
impl Engine {
/// Search for a module within an imports stack.
@ -218,19 +223,78 @@ impl Engine {
level: usize,
) -> RhaiResult {
let FnCallExpr {
name,
#[cfg(not(feature = "no_module"))]
namespace,
capture_parent_scope: capture,
hashes,
args,
..
name, hashes, args, ..
} = 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"))]
if !namespace.is_empty() {
if !expr.namespace.is_empty() {
// Qualified function call
let hash = hashes.native;
let namespace = &expr.namespace;
return self.make_qualified_function_call(
scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level,
@ -244,7 +308,18 @@ impl Engine {
);
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,
)
}

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"))]
macro_rules! impl_float {
($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
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
}
@ -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"))]
macro_rules! impl_float {
($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
}

View File

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

View File

@ -13,7 +13,9 @@ pub mod script;
pub use args::FuncArgs;
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;
#[cfg(not(feature = "no_function"))]
pub use func::Func;

View File

@ -1181,7 +1181,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return;
}
// 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])
.and_then(|f| {
#[cfg(not(feature = "no_function"))]

View File

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

View File

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

View File

@ -75,6 +75,17 @@ fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
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!(
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
"hello***world"

View File

@ -53,6 +53,13 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
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!(
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 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);
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]
fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let mut m = Module::new();
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());
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]; hi(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!(
engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?,
"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());