Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung
2020-08-04 16:51:22 +08:00
46 changed files with 2041 additions and 869 deletions

View File

@@ -4,8 +4,8 @@ use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::{
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG,
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED,
KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::error::ParseErrorType;
use crate::fn_native::{FnCallArgs, FnPtr};
@@ -14,6 +14,7 @@ use crate::optimize::OptimizationLevel;
use crate::parser::{Expr, ImmutableString, AST, INT};
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::stdlib::ops::Deref;
use crate::token::Position;
use crate::utils::StaticVec;
@@ -32,6 +33,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))]
use crate::engine::{Map, Target, FN_GET, FN_SET};
#[cfg(not(feature = "no_closure"))]
use crate::scope::Entry as ScopeEntry;
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
@@ -43,6 +47,9 @@ use crate::stdlib::{
vec::Vec,
};
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::{collections::HashSet, string::String};
/// Extract the property name from a getter function name.
#[inline(always)]
fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> {
@@ -65,48 +72,121 @@ fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> {
None
}
/// This function replaces the first argument of a method call with a clone copy.
/// This is to prevent a pure function unintentionally consuming the first argument.
fn normalize_first_arg<'a>(
normalize: bool,
this_copy: &mut Dynamic,
old_this_ptr: &mut Option<&'a mut Dynamic>,
args: &mut FnCallArgs<'a>,
) {
// Only do it for method calls with arguments.
if !normalize || args.is_empty() {
return;
}
// Clone the original value.
*this_copy = args[0].clone();
// Replace the first reference with a reference to the clone, force-casting the lifetime.
// Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`.
//
// # Safety
//
// Blindly casting a a reference to another lifetime saves on allocations and string cloning,
// but must be used with the utmost care.
//
// We can do this here because, at the end of this scope, we'd restore the original reference
// with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out".
let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe {
mem::transmute(this_copy)
});
*old_this_ptr = Some(this_pointer);
/// A type that temporarily stores a mutable reference to a `Dynamic`,
/// replacing it with a cloned copy.
#[derive(Debug, Default)]
struct ArgBackup<'a> {
orig_mut: Option<&'a mut Dynamic>,
value_copy: Dynamic,
}
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`.
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) {
if let Some(this_pointer) = old_this_ptr {
args[0] = this_pointer;
impl<'a> ArgBackup<'a> {
/// This function replaces the first argument of a method call with a clone copy.
/// This is to prevent a pure function unintentionally consuming the first argument.
///
/// `restore_first_arg` must be called before the end of the scope to prevent the shorter lifetime from leaking.
///
/// # Safety
///
/// This method blindly casts a reference to another lifetime, which saves allocation and string cloning.
///
/// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak.
fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) {
// Only do it for method calls with arguments.
if !normalize || args.is_empty() {
return;
}
// Clone the original value.
self.value_copy = args[0].clone();
// Replace the first reference with a reference to the clone, force-casting the lifetime.
// Must remember to restore it later with `restore_first_arg`.
//
// # Safety
//
// Blindly casting a reference to another lifetime saves allocation and string cloning,
// but must be used with the utmost care.
//
// We can do this here because, before the end of this scope, we'd restore the original reference
// via `restore_first_arg`. Therefore this shorter lifetime does not leak.
self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe {
mem::transmute(&mut self.value_copy)
}));
}
/// This function restores the first argument that was replaced by `change_first_arg_to_copy`.
///
/// # Safety
///
/// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting
/// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak.
fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) {
if let Some(this_pointer) = self.orig_mut.take() {
args[0] = this_pointer;
}
}
}
impl Drop for ArgBackup<'_> {
fn drop(&mut self) {
// Panic if the shorter lifetime leaks.
assert!(
self.orig_mut.is_none(),
"MutBackup::restore has not been called prior to existing this scope"
);
}
}
// Add captured variables into scope
#[cfg(not(feature = "no_closure"))]
fn add_captured_variables_into_scope<'s>(
externals: &HashSet<String>,
captured: Scope<'s>,
scope: &mut Scope<'s>,
) {
captured
.into_iter()
.filter(|ScopeEntry { name, .. }| externals.contains(name.as_ref()))
.for_each(
|ScopeEntry {
name, typ, value, ..
}| {
match typ {
ScopeEntryType::Normal => scope.push(name, value),
ScopeEntryType::Constant => scope.push_constant(name, value),
};
},
);
}
#[inline(always)]
pub fn ensure_no_data_race(
fn_name: &str,
args: &FnCallArgs,
is_ref: bool,
) -> Result<(), Box<EvalAltResult>> {
if cfg!(not(feature = "no_closure")) {
let skip = if is_ref { 1 } else { 0 };
if let Some((n, _)) = args
.iter()
.skip(skip)
.enumerate()
.find(|(_, a)| a.is_locked())
{
return Err(Box::new(EvalAltResult::ErrorDataRace(
format!("argument #{} of function '{}'", n + 1 + skip, fn_name),
Position::none(),
)));
}
}
Ok(())
}
impl Engine {
/// Universal method for calling functions either registered with the `Engine` or written in Rhai.
/// Call a native Rust function registered with the `Engine`.
/// Position in `EvalAltResult` is `None` and must be set afterwards.
///
/// ## WARNING
@@ -114,94 +194,33 @@ impl Engine {
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
/// All function arguments not in the first position are always passed by value and thus consumed.
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn call_fn_raw(
pub(crate) fn call_native_fn(
&self,
_scope: &mut Scope,
_mods: &mut Imports,
state: &mut State,
lib: &Module,
fn_name: &str,
(hash_fn, hash_script): (u64, u64),
hash_fn: u64,
args: &mut FnCallArgs,
is_ref: bool,
_is_method: bool,
pub_only: bool,
def_val: Option<bool>,
_level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?;
let native_only = hash_script == 0;
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if _level > self.limits.max_call_stack_depth {
return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()),
));
}
let mut this_copy: Dynamic = Default::default();
let mut old_this_ptr: Option<&mut Dynamic> = None;
// Search for the function
// First search in script-defined functions (can override built-in)
// Then search registered native functions (can override packages)
// Search for the native function
// First search registered functions (can override packages)
// Then search packages
// NOTE: We skip script functions for global_module and packages, and native functions for lib
let func = if !native_only {
lib.get_fn(hash_script, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only))
} else {
None
}
//.or_else(|| self.global_module.get_fn(hash_script, pub_only))
.or_else(|| self.global_module.get_fn(hash_fn, pub_only))
//.or_else(|| self.packages.get_fn(hash_script, pub_only))
.or_else(|| self.packages.get_fn(hash_fn, pub_only));
let func = self
.global_module
.get_fn(hash_fn, pub_only)
.or_else(|| self.packages.get_fn(hash_fn, pub_only));
if let Some(func) = func {
#[cfg(not(feature = "no_function"))]
let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !_is_method));
#[cfg(feature = "no_function")]
let need_normalize = is_ref && func.is_pure();
assert!(func.is_native());
// Calling pure function but the first argument is a reference?
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args);
#[cfg(not(feature = "no_function"))]
if func.is_script() {
// Run scripted function
let fn_def = func.get_fn_def();
// Method call of script function - map first argument to `this`
return if _is_method {
let (first, rest) = args.split_at_mut(1);
Ok((
self.call_script_fn(
_scope,
_mods,
state,
lib,
&mut Some(first[0]),
fn_name,
fn_def,
rest,
_level,
)?,
false,
))
} else {
let result = self.call_script_fn(
_scope, _mods, state, lib, &mut None, fn_name, fn_def, args, _level,
)?;
// Restore the original reference
restore_first_arg(old_this_ptr, args);
Ok((result, false))
};
}
let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref && func.is_pure(), args);
// Run external function
let result = if func.is_plugin_fn() {
@@ -211,7 +230,9 @@ impl Engine {
};
// Restore the original reference
restore_first_arg(old_this_ptr, args);
backup.restore_first_arg(args);
let result = result?;
// See if the function match print/debug (which requires special processing)
return Ok(match fn_name {
@@ -343,6 +364,17 @@ impl Engine {
args: &mut FnCallArgs,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)?;
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if level > self.limits.max_call_stack_depth {
return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()),
));
}
let orig_scope_level = state.scope_level;
state.scope_level += 1;
@@ -405,7 +437,7 @@ impl Engine {
|| self.packages.contains_fn(hash_fn, pub_only)
}
/// Perform an actual function call, taking care of special functions
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
/// Position in `EvalAltResult` is `None` and must be set afterwards.
///
/// ## WARNING
@@ -418,24 +450,28 @@ impl Engine {
state: &mut State,
lib: &Module,
fn_name: &str,
native_only: bool,
hash_script: u64,
args: &mut FnCallArgs,
is_ref: bool,
is_method: bool,
pub_only: bool,
_capture: Option<Scope>,
def_val: Option<bool>,
level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Check for data race.
if cfg!(not(feature = "no_closure")) {
ensure_no_data_race(fn_name, args, is_ref)?;
}
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types = args.iter().map(|a| a.type_id());
let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types);
let hashes = (hash_fn, if native_only { 0 } else { hash_script });
match fn_name {
// type_of
KEYWORD_TYPE_OF
if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) =>
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) =>
{
Ok((
self.map_type_name(args[0].type_name()).to_string().into(),
@@ -445,7 +481,7 @@ impl Engine {
// Fn
KEYWORD_FN_PTR
if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) =>
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) =>
{
Err(Box::new(EvalAltResult::ErrorRuntime(
"'Fn' should not be called in method style. Try Fn(...);".into(),
@@ -455,7 +491,7 @@ impl Engine {
// eval - reaching this point it must be a method-style call
KEYWORD_EVAL
if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) =>
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) =>
{
Err(Box::new(EvalAltResult::ErrorRuntime(
"'eval' should not be called in method style. Try eval(...);".into(),
@@ -463,15 +499,57 @@ impl Engine {
)))
}
// Normal function call
_ => {
let mut scope = Scope::new();
let mut mods = Imports::new();
self.call_fn_raw(
&mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method,
pub_only, def_val, level,
)
// Normal script function call
#[cfg(not(feature = "no_function"))]
_ if hash_script > 0 && lib.contains_fn(hash_script, pub_only) => {
// Get scripted function
let func = lib.get_fn(hash_script, pub_only).unwrap().get_fn_def();
let scope = &mut Scope::new();
let mods = &mut Imports::new();
// Add captured variables into scope
#[cfg(not(feature = "no_closure"))]
if let Some(captured) = _capture {
add_captured_variables_into_scope(&func.externals, captured, scope);
}
let result = if is_method {
// Method call of script function - map first argument to `this`
let (first, rest) = args.split_at_mut(1);
self.call_script_fn(
scope,
mods,
state,
lib,
&mut Some(first[0]),
fn_name,
func,
rest,
level,
)?
} else {
// Normal call of script function - map first argument to `this`
// The first argument is a reference?
let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args);
let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, func, args, level,
);
// Restore the original reference
backup.restore_first_arg(args);
result?
};
Ok((result, false))
}
// Normal native function call
_ => self.call_native_fn(
state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val,
),
}
}
@@ -483,9 +561,21 @@ impl Engine {
mods: &mut Imports,
state: &mut State,
lib: &Module,
script: &Dynamic,
script_expr: &Dynamic,
_level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
let script = script.as_str().map_err(|typ| {
self.inc_operations(state)?;
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if _level > self.limits.max_call_stack_depth {
return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()),
));
}
let script = script_expr.as_str().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
@@ -526,7 +616,7 @@ impl Engine {
state: &mut State,
lib: &Module,
name: &str,
hash: u64,
hash_script: u64,
target: &mut Target,
idx_val: Dynamic,
def_val: Option<bool>,
@@ -544,12 +634,16 @@ impl Engine {
let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// FnPtr call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name
let fn_name = fn_ptr.fn_name();
// Recalculate hash
let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty());
let hash = if native {
0
} else {
calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty())
};
// Arguments are passed as-is, adding the curried arguments
let mut arg_values = curry
.iter_mut()
@@ -559,7 +653,7 @@ impl Engine {
// Map it to name(args) in function-call style
self.exec_fn_call(
state, lib, fn_name, native, hash, args, false, false, pub_only, def_val, level,
state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level,
)
} else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object
@@ -568,7 +662,11 @@ impl Engine {
// Redirect function name
let fn_name = fn_ptr.get_fn_name().clone();
// Recalculate hash
let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty());
let hash = if native {
0
} else {
calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty())
};
// Replace the first argument with the object pointer, adding the curried arguments
let mut arg_values = once(obj)
.chain(curry.iter_mut())
@@ -578,11 +676,11 @@ impl Engine {
// Map it to name(args) in function-call style
self.exec_fn_call(
state, lib, &fn_name, native, hash, args, is_ref, true, pub_only, def_val, level,
state, lib, &fn_name, hash, args, is_ref, true, pub_only, None, def_val, level,
)
} else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
Ok((
FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(),
@@ -596,16 +694,22 @@ impl Engine {
.into(),
false,
))
} else if cfg!(not(feature = "no_closure"))
&& _fn_name == KEYWORD_IS_SHARED
&& idx.is_empty()
{
// take call
Ok((target.is_shared().into(), false))
} else {
#[cfg(not(feature = "no_object"))]
let redirected;
let mut _hash = hash;
let mut _hash = hash_script;
// Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))]
if let Some(map) = obj.downcast_ref::<Map>() {
if let Some(map) = obj.read_lock::<Map>() {
if let Some(val) = map.get(_fn_name) {
if let Some(f) = val.downcast_ref::<FnPtr>() {
if let Some(f) = val.read_lock::<FnPtr>() {
// Remap the function name
redirected = f.get_fn_name().clone();
_fn_name = &redirected;
@@ -615,12 +719,16 @@ impl Engine {
}
};
if native {
_hash = 0;
}
// Attached object pointer in front of the arguments
let mut arg_values = once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
let args = arg_values.as_mut();
self.exec_fn_call(
state, lib, _fn_name, native, _hash, args, is_ref, true, pub_only, def_val, level,
state, lib, _fn_name, _hash, args, is_ref, true, pub_only, None, def_val, level,
)
}?;
@@ -645,16 +753,17 @@ impl Engine {
name: &str,
args_expr: &[Expr],
def_val: Option<bool>,
mut hash: u64,
mut hash_script: u64,
native: bool,
pub_only: bool,
capture: bool,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
// Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash, pub_only) {
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
// Fn - only in function call style
let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
@@ -669,7 +778,8 @@ impl Engine {
))
})
.and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into);
.map(Into::<Dynamic>::into)
.map_err(|err| err.new_position(expr.position()));
}
}
@@ -701,27 +811,12 @@ impl Engine {
.into());
}
// Handle eval()
if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
// Handle is_shared()
if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
let expr = args_expr.get(0).unwrap();
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !self.has_override(lib, hash_fn, hash, pub_only) {
// eval - only in function call style
let prev_len = scope.len();
let expr = args_expr.get(0).unwrap();
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let result = self
.eval_script_expr(scope, mods, state, lib, &script)
.map_err(|err| err.new_position(expr.position()));
if scope.len() != prev_len {
// IMPORTANT! If the eval defines new variables in the current scope,
// all variable offsets from this point on will be mis-aligned.
state.always_search = true;
}
return result;
}
return Ok(value.is_shared().into());
}
// Handle call() - Redirect function call
@@ -732,7 +827,7 @@ impl Engine {
if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1
&& !self.has_override(lib, 0, hash, pub_only)
&& !self.has_override(lib, 0, hash_script, pub_only)
{
let expr = args_expr.get(0).unwrap();
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
@@ -746,7 +841,7 @@ impl Engine {
// Skip the first argument
args_expr = &args_expr.as_ref()[1..];
// Recalculate hash
hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
} else {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(),
@@ -756,20 +851,48 @@ impl Engine {
}
}
// Normal function call - except for Fn and eval (handled above)
// Handle eval()
if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
// eval - only in function call style
let prev_len = scope.len();
let expr = args_expr.get(0).unwrap();
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let result = self
.eval_script_expr(scope, mods, state, lib, &script, level + 1)
.map_err(|err| err.new_position(expr.position()));
// IMPORTANT! If the eval defines new variables in the current scope,
// all variable offsets from this point on will be mis-aligned.
if scope.len() != prev_len {
state.always_search = true;
}
return result;
}
}
// Normal function call - except for Fn, curry, call and eval (handled above)
let mut arg_values: StaticVec<_>;
let mut args: StaticVec<_>;
let mut is_ref = false;
let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() {
Some(scope.flatten_clone())
} else {
None
};
if args_expr.is_empty() && curry.is_empty() {
// No arguments
args = Default::default();
} else {
// See if the first argument is a variable, if so, convert to method-call style
// If the first argument is a variable, and there is no curried arguments, convert to method-call style
// in order to leverage potential &mut first argument and avoid cloning the value
match args_expr.get(0).unwrap() {
// func(x, ...) -> x.func(...)
lhs @ Expr::Variable(_) => {
lhs @ Expr::Variable(_) if curry.is_empty() => {
arg_values = args_expr
.iter()
.skip(1)
@@ -781,12 +904,14 @@ impl Engine {
self.inc_operations(state)
.map_err(|err| err.new_position(pos))?;
args = once(target)
.chain(curry.iter_mut())
.chain(arg_values.iter_mut())
.collect();
is_ref = true;
// Turn it into a method call only if the object is not shared
args = if target.is_shared() {
arg_values.insert(0, target.clone().clone_inner_data().unwrap());
arg_values.iter_mut().collect()
} else {
is_ref = true;
once(target).chain(arg_values.iter_mut()).collect()
};
}
// func(..., ...)
_ => {
@@ -800,9 +925,11 @@ impl Engine {
}
}
let hash = if native { 0 } else { hash_script };
let args = args.as_mut();
self.exec_fn_call(
state, lib, name, native, hash, args, is_ref, false, pub_only, def_val, level,
state, lib, name, hash, args, is_ref, false, pub_only, capture, def_val, level,
)
.map(|(v, _)| v)
}
@@ -821,10 +948,10 @@ impl Engine {
args_expr: &[Expr],
def_val: Option<bool>,
hash_script: u64,
_capture: bool,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
let modules = modules.as_ref().unwrap();
let mut arg_values: StaticVec<_>;
let mut args: StaticVec<_>;
@@ -890,12 +1017,22 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => {
let args = args.as_mut();
let fn_def = f.get_fn_def();
let mut scope = Scope::new();
let mut mods = Imports::new();
self.call_script_fn(
&mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level,
)
let func = f.get_fn_def();
let scope = &mut Scope::new();
let mods = &mut Imports::new();
// Add captured variables into scope
#[cfg(not(feature = "no_closure"))]
if _capture && !scope.is_empty() {
add_captured_variables_into_scope(
&func.externals,
scope.flatten_clone(),
scope,
);
}
self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level)
}
Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()),
Some(f) => f.get_native_fn()(self, lib, args.as_mut()),
@@ -938,30 +1075,30 @@ pub fn run_builtin_binary_op(
let x = x.clone().cast::<INT>();
let y = y.clone().cast::<INT>();
#[cfg(not(feature = "unchecked"))]
match op {
"+" => return add(x, y).map(Into::into).map(Some),
"-" => return sub(x, y).map(Into::into).map(Some),
"*" => return mul(x, y).map(Into::into).map(Some),
"/" => return div(x, y).map(Into::into).map(Some),
"%" => return modulo(x, y).map(Into::into).map(Some),
"~" => return pow_i_i(x, y).map(Into::into).map(Some),
">>" => return shr(x, y).map(Into::into).map(Some),
"<<" => return shl(x, y).map(Into::into).map(Some),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+" => return Ok(Some((x + y).into())),
"-" => return Ok(Some((x - y).into())),
"*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())),
"~" => return pow_i_i_u(x, y).map(Into::into).map(Some),
">>" => return shr_u(x, y).map(Into::into).map(Some),
"<<" => return shl_u(x, y).map(Into::into).map(Some),
_ => (),
if cfg!(not(feature = "unchecked")) {
match op {
"+" => return add(x, y).map(Into::into).map(Some),
"-" => return sub(x, y).map(Into::into).map(Some),
"*" => return mul(x, y).map(Into::into).map(Some),
"/" => return div(x, y).map(Into::into).map(Some),
"%" => return modulo(x, y).map(Into::into).map(Some),
"~" => return pow_i_i(x, y).map(Into::into).map(Some),
">>" => return shr(x, y).map(Into::into).map(Some),
"<<" => return shl(x, y).map(Into::into).map(Some),
_ => (),
}
} else {
match op {
"+" => return Ok(Some((x + y).into())),
"-" => return Ok(Some((x - y).into())),
"*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())),
"~" => return pow_i_i_u(x, y).map(Into::into).map(Some),
">>" => return shr_u(x, y).map(Into::into).map(Some),
"<<" => return shl_u(x, y).map(Into::into).map(Some),
_ => (),
}
}
match op {
@@ -989,8 +1126,8 @@ pub fn run_builtin_binary_op(
_ => (),
}
} else if args_type == TypeId::of::<ImmutableString>() {
let x = x.downcast_ref::<ImmutableString>().unwrap();
let y = y.downcast_ref::<ImmutableString>().unwrap();
let x = &*x.read_lock::<ImmutableString>().unwrap();
let y = &*y.read_lock::<ImmutableString>().unwrap();
match op {
"+" => return Ok(Some((x + y).into())),
@@ -1063,33 +1200,33 @@ pub fn run_builtin_op_assignment(
}
if args_type == TypeId::of::<INT>() {
let x = x.downcast_mut::<INT>().unwrap();
let y = y.clone().cast::<INT>();
let mut x = x.write_lock::<INT>().unwrap();
#[cfg(not(feature = "unchecked"))]
match op {
"+=" => return Ok(Some(*x = add(*x, y)?)),
"-=" => return Ok(Some(*x = sub(*x, y)?)),
"*=" => return Ok(Some(*x = mul(*x, y)?)),
"/=" => return Ok(Some(*x = div(*x, y)?)),
"%=" => return Ok(Some(*x = modulo(*x, y)?)),
"~=" => return Ok(Some(*x = pow_i_i(*x, y)?)),
">>=" => return Ok(Some(*x = shr(*x, y)?)),
"<<=" => return Ok(Some(*x = shl(*x, y)?)),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+=" => return Ok(Some(*x += y)),
"-=" => return Ok(Some(*x -= y)),
"*=" => return Ok(Some(*x *= y)),
"/=" => return Ok(Some(*x /= y)),
"%=" => return Ok(Some(*x %= y)),
"~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)),
">>=" => return Ok(Some(*x = shr_u(*x, y)?)),
"<<=" => return Ok(Some(*x = shl_u(*x, y)?)),
_ => (),
if cfg!(not(feature = "unchecked")) {
match op {
"+=" => return Ok(Some(*x = add(*x, y)?)),
"-=" => return Ok(Some(*x = sub(*x, y)?)),
"*=" => return Ok(Some(*x = mul(*x, y)?)),
"/=" => return Ok(Some(*x = div(*x, y)?)),
"%=" => return Ok(Some(*x = modulo(*x, y)?)),
"~=" => return Ok(Some(*x = pow_i_i(*x, y)?)),
">>=" => return Ok(Some(*x = shr(*x, y)?)),
"<<=" => return Ok(Some(*x = shl(*x, y)?)),
_ => (),
}
} else {
match op {
"+=" => return Ok(Some(*x += y)),
"-=" => return Ok(Some(*x -= y)),
"*=" => return Ok(Some(*x *= y)),
"/=" => return Ok(Some(*x /= y)),
"%=" => return Ok(Some(*x %= y)),
"~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)),
">>=" => return Ok(Some(*x = shr_u(*x, y)?)),
"<<=" => return Ok(Some(*x = shl_u(*x, y)?)),
_ => (),
}
}
match op {
@@ -1099,8 +1236,8 @@ pub fn run_builtin_op_assignment(
_ => (),
}
} else if args_type == TypeId::of::<bool>() {
let x = x.downcast_mut::<bool>().unwrap();
let y = y.clone().cast::<bool>();
let mut x = x.write_lock::<bool>().unwrap();
match op {
"&=" => return Ok(Some(*x = *x && y)),
@@ -1108,8 +1245,8 @@ pub fn run_builtin_op_assignment(
_ => (),
}
} else if args_type == TypeId::of::<ImmutableString>() {
let x = x.downcast_mut::<ImmutableString>().unwrap();
let y = y.downcast_ref::<ImmutableString>().unwrap();
let y = y.read_lock::<ImmutableString>().unwrap().deref().clone();
let mut x = x.write_lock::<ImmutableString>().unwrap();
match op {
"+=" => return Ok(Some(*x += y)),
@@ -1119,8 +1256,8 @@ pub fn run_builtin_op_assignment(
#[cfg(not(feature = "no_float"))]
if args_type == TypeId::of::<FLOAT>() {
let x = x.downcast_mut::<FLOAT>().unwrap();
let y = y.clone().cast::<FLOAT>();
let mut x = x.write_lock::<FLOAT>().unwrap();
match op {
"+=" => return Ok(Some(*x += y)),