2021-11-29 03:17:04 +01:00
|
|
|
//! Implement script function-calling mechanism for [`Engine`].
|
|
|
|
#![cfg(not(feature = "no_function"))]
|
|
|
|
|
2021-12-20 15:13:00 +01:00
|
|
|
use super::call::FnCallArgs;
|
2021-11-29 03:17:04 +01:00
|
|
|
use crate::ast::ScriptFnDef;
|
2022-01-07 04:43:47 +01:00
|
|
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
2021-11-29 03:17:04 +01:00
|
|
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
2021-12-27 05:27:31 +01:00
|
|
|
use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR};
|
2021-11-29 03:17:04 +01:00
|
|
|
use std::mem;
|
|
|
|
#[cfg(feature = "no_std")]
|
|
|
|
use std::prelude::v1::*;
|
|
|
|
|
|
|
|
impl Engine {
|
|
|
|
/// Call a script-defined function.
|
|
|
|
///
|
|
|
|
/// If `rewind_scope` is `false`, arguments are removed from the scope but new variables are not.
|
|
|
|
///
|
|
|
|
/// # WARNING
|
|
|
|
///
|
|
|
|
/// Function call arguments may 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_script_fn(
|
|
|
|
&self,
|
|
|
|
scope: &mut Scope,
|
2021-12-27 16:03:30 +01:00
|
|
|
global: &mut GlobalRuntimeState,
|
2021-11-29 03:17:04 +01:00
|
|
|
state: &mut EvalState,
|
|
|
|
lib: &[&Module],
|
|
|
|
this_ptr: &mut Option<&mut Dynamic>,
|
|
|
|
fn_def: &ScriptFnDef,
|
|
|
|
args: &mut FnCallArgs,
|
|
|
|
pos: Position,
|
|
|
|
rewind_scope: bool,
|
|
|
|
level: usize,
|
|
|
|
) -> RhaiResult {
|
|
|
|
#[inline(never)]
|
|
|
|
fn make_error(
|
|
|
|
name: String,
|
|
|
|
fn_def: &ScriptFnDef,
|
2021-12-27 16:03:30 +01:00
|
|
|
global: &GlobalRuntimeState,
|
2021-12-25 16:49:14 +01:00
|
|
|
err: RhaiError,
|
2021-11-29 03:17:04 +01:00
|
|
|
pos: Position,
|
|
|
|
) -> RhaiResult {
|
2021-12-27 05:27:31 +01:00
|
|
|
Err(ERR::ErrorInFunctionCall(
|
2021-11-29 03:17:04 +01:00
|
|
|
name,
|
|
|
|
fn_def
|
|
|
|
.lib
|
|
|
|
.as_ref()
|
2022-01-01 10:20:00 +01:00
|
|
|
.and_then(|m| m.id().map(str::to_string))
|
|
|
|
.unwrap_or_else(|| global.source.to_string()),
|
2021-11-29 03:17:04 +01:00
|
|
|
err,
|
|
|
|
pos,
|
|
|
|
)
|
|
|
|
.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(fn_def.params.len() == args.len());
|
|
|
|
|
|
|
|
#[cfg(not(feature = "unchecked"))]
|
2021-12-27 16:03:30 +01:00
|
|
|
self.inc_operations(&mut global.num_operations, pos)?;
|
2021-11-29 03:17:04 +01:00
|
|
|
|
|
|
|
if fn_def.body.is_empty() {
|
|
|
|
return Ok(Dynamic::UNIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for stack overflow
|
|
|
|
#[cfg(not(feature = "unchecked"))]
|
|
|
|
if level > self.max_call_levels() {
|
2021-12-27 05:27:31 +01:00
|
|
|
return Err(ERR::ErrorStackOverflow(pos).into());
|
2021-11-29 03:17:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let orig_scope_len = scope.len();
|
2021-12-27 16:03:30 +01:00
|
|
|
let orig_mods_len = global.num_imported_modules();
|
2021-11-29 03:17:04 +01:00
|
|
|
|
|
|
|
// Put arguments into scope as variables
|
|
|
|
scope.extend(
|
|
|
|
fn_def
|
|
|
|
.params
|
|
|
|
.iter()
|
2022-01-15 03:18:16 +01:00
|
|
|
.zip(args.into_iter().map(|v| {
|
|
|
|
// Actually consume the arguments instead of cloning them
|
|
|
|
mem::take(*v)
|
|
|
|
}))
|
2021-11-29 03:17:04 +01:00
|
|
|
.map(|(name, value)| {
|
2022-01-15 03:18:16 +01:00
|
|
|
// Arguments are always removed at the end of the call,
|
|
|
|
// so this cast is safe.
|
|
|
|
let var_name: std::borrow::Cow<_> =
|
2021-11-29 03:17:04 +01:00
|
|
|
unsafe_cast_var_name_to_lifetime(name).into();
|
|
|
|
(var_name, value)
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Merge in encapsulated environment, if any
|
|
|
|
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
|
|
|
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
|
|
|
|
|
|
|
let lib = if let Some(ref fn_lib) = fn_def.lib {
|
|
|
|
if fn_lib.is_empty() {
|
|
|
|
lib
|
|
|
|
} else {
|
|
|
|
state.push_fn_resolution_cache();
|
|
|
|
lib_merged.push(fn_lib.as_ref());
|
|
|
|
lib_merged.extend(lib.iter().cloned());
|
|
|
|
&lib_merged
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lib
|
|
|
|
};
|
|
|
|
|
|
|
|
#[cfg(not(feature = "no_module"))]
|
2021-12-28 04:42:52 +01:00
|
|
|
if let Some(ref modules) = fn_def.global {
|
|
|
|
modules
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.for_each(|(n, m)| global.push_module(n, m));
|
2021-11-29 03:17:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Evaluate the function
|
|
|
|
let result = self
|
|
|
|
.eval_stmt_block(
|
|
|
|
scope,
|
2021-12-27 16:03:30 +01:00
|
|
|
global,
|
2021-11-29 03:17:04 +01:00
|
|
|
state,
|
|
|
|
lib,
|
|
|
|
this_ptr,
|
2021-12-28 04:42:52 +01:00
|
|
|
&fn_def.body,
|
2021-11-29 03:17:04 +01:00
|
|
|
rewind_scope,
|
|
|
|
level,
|
|
|
|
)
|
|
|
|
.or_else(|err| match *err {
|
|
|
|
// Convert return statement to return value
|
2021-12-27 05:27:31 +01:00
|
|
|
ERR::Return(x, _) => Ok(x),
|
2021-11-29 03:17:04 +01:00
|
|
|
// Error in sub function call
|
2021-12-27 05:27:31 +01:00
|
|
|
ERR::ErrorInFunctionCall(name, src, err, _) => {
|
2021-11-29 03:17:04 +01:00
|
|
|
let fn_name = if src.is_empty() {
|
|
|
|
format!("{} < {}", name, fn_def.name)
|
|
|
|
} else {
|
|
|
|
format!("{} @ '{}' < {}", name, src, fn_def.name)
|
|
|
|
};
|
|
|
|
|
2021-12-27 16:03:30 +01:00
|
|
|
make_error(fn_name, fn_def, global, err, pos)
|
2021-11-29 03:17:04 +01:00
|
|
|
}
|
|
|
|
// System errors are passed straight-through
|
|
|
|
mut err if err.is_system_exception() => {
|
|
|
|
err.set_position(pos);
|
|
|
|
Err(err.into())
|
|
|
|
}
|
|
|
|
// Other errors are wrapped in `ErrorInFunctionCall`
|
2021-12-27 16:03:30 +01:00
|
|
|
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
|
2021-11-29 03:17:04 +01:00
|
|
|
});
|
|
|
|
|
2021-12-28 04:42:52 +01:00
|
|
|
// Remove all local variables and imported modules
|
2021-11-29 03:17:04 +01:00
|
|
|
if rewind_scope {
|
|
|
|
scope.rewind(orig_scope_len);
|
|
|
|
} else if !args.is_empty() {
|
|
|
|
// Remove arguments only, leaving new variables in the scope
|
|
|
|
scope.remove_range(orig_scope_len, args.len())
|
|
|
|
}
|
2021-12-27 16:03:30 +01:00
|
|
|
global.truncate_modules(orig_mods_len);
|
2021-12-28 04:42:52 +01:00
|
|
|
|
|
|
|
// Restore state
|
2021-11-29 03:17:04 +01:00
|
|
|
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2021-12-17 09:07:13 +01:00
|
|
|
// Does a script-defined function exist?
|
2021-11-29 03:17:04 +01:00
|
|
|
#[must_use]
|
|
|
|
pub(crate) fn has_script_fn(
|
|
|
|
&self,
|
2021-12-27 16:03:30 +01:00
|
|
|
global: Option<&GlobalRuntimeState>,
|
2021-11-29 03:17:04 +01:00
|
|
|
state: &mut EvalState,
|
|
|
|
lib: &[&Module],
|
|
|
|
hash_script: u64,
|
|
|
|
) -> bool {
|
|
|
|
let cache = state.fn_resolution_cache_mut();
|
|
|
|
|
|
|
|
if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// First check script-defined functions
|
|
|
|
let result = lib.iter().any(|&m| m.contains_fn(hash_script))
|
|
|
|
// Then check the global namespace and packages
|
|
|
|
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script))
|
|
|
|
// Then check imported modules
|
2021-12-27 16:03:30 +01:00
|
|
|
|| global.map_or(false, |m| m.contains_fn(hash_script))
|
2021-11-29 03:17:04 +01:00
|
|
|
// Then check sub-modules
|
|
|
|
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));
|
|
|
|
|
|
|
|
if !result {
|
|
|
|
cache.insert(hash_script, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
}
|