Simplify function calling.
This commit is contained in:
parent
a7ff207636
commit
cb005506e2
@ -1179,7 +1179,6 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Dynamic(Union::Array(mut rhs_value)) => {
|
Dynamic(Union::Array(mut rhs_value)) => {
|
||||||
let op = "==";
|
let op = "==";
|
||||||
let mut scope = Scope::new();
|
|
||||||
|
|
||||||
// Call the `==` operator to compare each value
|
// Call the `==` operator to compare each value
|
||||||
for value in rhs_value.iter_mut() {
|
for value in rhs_value.iter_mut() {
|
||||||
@ -1190,13 +1189,13 @@ impl Engine {
|
|||||||
let hash =
|
let hash =
|
||||||
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
|
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
|
||||||
|
|
||||||
let (r, _) = self
|
if self
|
||||||
.call_fn_raw(
|
.call_native_fn(state, lib, op, hash, args, false, false, def_value)
|
||||||
&mut scope, mods, state, lib, op, hash, args, false, false, false,
|
.map_err(|err| err.new_position(rhs.position()))?
|
||||||
def_value, level,
|
.0
|
||||||
)
|
.as_bool()
|
||||||
.map_err(|err| err.new_position(rhs.position()))?;
|
.unwrap_or(false)
|
||||||
if r.as_bool().unwrap_or(false) {
|
{
|
||||||
return Ok(true.into());
|
return Ok(true.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
192
src/fn_call.rs
192
src/fn_call.rs
@ -41,7 +41,7 @@ use crate::stdlib::{
|
|||||||
format,
|
format,
|
||||||
iter::{empty, once},
|
iter::{empty, once},
|
||||||
mem,
|
mem,
|
||||||
string::ToString,
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,43 +67,69 @@ fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function replaces the first argument of a method call with a clone copy.
|
/// A type that temporarily stores a mutable reference to a `Dynamic`,
|
||||||
/// This is to prevent a pure function unintentionally consuming the first argument.
|
/// replacing it with a cloned copy.
|
||||||
fn normalize_first_arg<'a>(
|
#[derive(Debug, Default)]
|
||||||
normalize: bool,
|
struct ArgBackup<'a> {
|
||||||
this_copy: &mut Dynamic,
|
orig_mut: Option<&'a mut Dynamic>,
|
||||||
old_this_ptr: &mut Option<&'a mut Dynamic>,
|
value_copy: 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`.
|
impl<'a> ArgBackup<'a> {
|
||||||
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) {
|
/// This function replaces the first argument of a method call with a clone copy.
|
||||||
if let Some(this_pointer) = old_this_ptr {
|
/// This is to prevent a pure function unintentionally consuming the first argument.
|
||||||
args[0] = this_pointer;
|
///
|
||||||
|
/// `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"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,34 +164,21 @@ impl Engine {
|
|||||||
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
|
/// 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.
|
/// 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 `()`!
|
/// **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,
|
&self,
|
||||||
_scope: &mut Scope,
|
|
||||||
_mods: &mut Imports,
|
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &Module,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hash_fn: u64,
|
hash_fn: u64,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
is_ref: bool,
|
is_ref: bool,
|
||||||
_is_method: bool,
|
|
||||||
pub_only: bool,
|
pub_only: bool,
|
||||||
def_val: Option<bool>,
|
def_val: Option<bool>,
|
||||||
_level: usize,
|
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
|
|
||||||
// Check for stack overflow
|
// Search for the native function
|
||||||
#[cfg(not(feature = "no_function"))]
|
// First search registered functions (can override packages)
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
if _level > self.limits.max_call_stack_depth {
|
|
||||||
return Err(Box::new(
|
|
||||||
EvalAltResult::ErrorStackOverflow(Position::none()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for the function
|
|
||||||
// First search registered native functions (can override packages)
|
|
||||||
// Then search packages
|
// Then search packages
|
||||||
let func = self
|
let func = self
|
||||||
.global_module
|
.global_module
|
||||||
@ -173,22 +186,19 @@ impl Engine {
|
|||||||
.or_else(|| self.packages.get_fn(hash_fn, pub_only));
|
.or_else(|| self.packages.get_fn(hash_fn, pub_only));
|
||||||
|
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
#[cfg(not(feature = "no_function"))]
|
assert!(func.is_native());
|
||||||
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();
|
|
||||||
|
|
||||||
let mut this_copy: Dynamic = Default::default();
|
|
||||||
let mut old_this_ptr: Option<&mut Dynamic> = None;
|
|
||||||
|
|
||||||
// Calling pure function but the first argument is a reference?
|
// Calling pure function but the first argument is a reference?
|
||||||
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args);
|
let mut backup: ArgBackup = Default::default();
|
||||||
|
backup.change_first_arg_to_copy(is_ref && func.is_pure(), args);
|
||||||
|
|
||||||
// Run external function
|
// Run external function
|
||||||
let result = func.get_native_fn()(self, lib, args)?;
|
let result = func.get_native_fn()(self, lib, args);
|
||||||
|
|
||||||
// Restore the original reference
|
// 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)
|
// See if the function match print/debug (which requires special processing)
|
||||||
return Ok(match fn_name {
|
return Ok(match fn_name {
|
||||||
@ -320,6 +330,17 @@ impl Engine {
|
|||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> 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;
|
let orig_scope_level = state.scope_level;
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
@ -382,7 +403,7 @@ impl Engine {
|
|||||||
|| self.packages.contains_fn(hash_fn, pub_only)
|
|| 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.
|
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
||||||
///
|
///
|
||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
@ -470,33 +491,26 @@ impl Engine {
|
|||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
// Normal call of script function - map first argument to `this`
|
// Normal call of script function - map first argument to `this`
|
||||||
let mut first_copy: Dynamic = Default::default();
|
|
||||||
let mut old_first: Option<&mut Dynamic> = None;
|
|
||||||
|
|
||||||
// The first argument is a reference?
|
// The first argument is a reference?
|
||||||
normalize_first_arg(is_ref, &mut first_copy, &mut old_first, args);
|
let mut backup: ArgBackup = Default::default();
|
||||||
|
backup.change_first_arg_to_copy(is_ref, args);
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope, mods, state, lib, &mut None, fn_name, func, args, level,
|
scope, mods, state, lib, &mut None, fn_name, func, args, level,
|
||||||
)?;
|
);
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
restore_first_arg(old_first, args);
|
backup.restore_first_arg(args);
|
||||||
|
|
||||||
result
|
result?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((result, false))
|
Ok((result, false))
|
||||||
}
|
}
|
||||||
// Normal native function call
|
// Normal native function call
|
||||||
_ => {
|
_ => self.call_native_fn(
|
||||||
let mut scope = Scope::new();
|
state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val,
|
||||||
let mut mods = Imports::new();
|
),
|
||||||
self.call_fn_raw(
|
|
||||||
&mut scope, &mut mods, state, lib, fn_name, hash_fn, args, is_ref, is_method,
|
|
||||||
pub_only, def_val, level,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,9 +522,21 @@ impl Engine {
|
|||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &Module,
|
||||||
script: &Dynamic,
|
script_expr: &Dynamic,
|
||||||
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> 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(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
typ.into(),
|
typ.into(),
|
||||||
@ -782,12 +808,12 @@ impl Engine {
|
|||||||
let expr = args_expr.get(0).unwrap();
|
let expr = args_expr.get(0).unwrap();
|
||||||
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
let result = self
|
let result = self
|
||||||
.eval_script_expr(scope, mods, state, lib, &script)
|
.eval_script_expr(scope, mods, state, lib, &script, level + 1)
|
||||||
.map_err(|err| err.new_position(expr.position()));
|
.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 {
|
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;
|
state.always_search = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,6 +280,16 @@ impl CallableFunction {
|
|||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Is this a native Rust function?
|
||||||
|
pub fn is_native(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Pure(_) | Self::Method(_) => true,
|
||||||
|
Self::Iterator(_) => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::Script(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Get the access mode.
|
/// Get the access mode.
|
||||||
pub fn access(&self) -> FnAccess {
|
pub fn access(&self) -> FnAccess {
|
||||||
match self {
|
match self {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
use crate::fn_native::FnPtr;
|
use crate::fn_native::FnPtr;
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
@ -132,22 +132,18 @@ fn call_fn_with_constant_arguments(
|
|||||||
|
|
||||||
state
|
state
|
||||||
.engine
|
.engine
|
||||||
.call_fn_raw(
|
.call_native_fn(
|
||||||
&mut Scope::new(),
|
|
||||||
&mut Imports::new(),
|
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
state.lib,
|
state.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
hash_fn,
|
hash_fn,
|
||||||
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
|
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
.map(|(v, _)| Some(v))
|
.ok()
|
||||||
.unwrap_or_else(|_| None)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize a statement.
|
/// Optimize a statement.
|
||||||
|
Loading…
Reference in New Issue
Block a user