rhai/src/func/script.rs

267 lines
8.9 KiB
Rust
Raw Normal View History

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};
use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, ERR};
2021-11-29 03:17:04 +01:00
use std::mem;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// # Main Entry-Point
///
2021-11-29 03:17:04 +01:00
/// 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,
rewind_scope: bool,
pos: Position,
2021-11-29 03:17:04 +01:00
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 {
let _fn_def = fn_def;
#[cfg(not(feature = "no_module"))]
let source = _fn_def
.environ
.as_ref()
.and_then(|environ| environ.lib.id().map(str::to_string));
#[cfg(feature = "no_module")]
let source = None;
2021-12-27 05:27:31 +01:00
Err(ERR::ErrorInFunctionCall(
2021-11-29 03:17:04 +01:00
name,
source.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
// 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
}
#[cfg(feature = "debugging")]
if self.debugger.is_none() && fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
#[cfg(not(feature = "debugging"))]
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
2021-11-29 03:17:04 +01:00
let orig_scope_len = scope.len();
2022-01-29 04:09:43 +01:00
#[cfg(not(feature = "no_module"))]
2022-01-24 10:04:40 +01:00
let orig_imports_len = global.num_imports();
2022-01-25 07:32:07 +01:00
2022-01-24 10:04:40 +01:00
#[cfg(feature = "debugging")]
2022-01-28 11:59:18 +01:00
let orig_call_stack_len = global.debugger.call_stack().len();
2021-11-29 03:17:04 +01:00
// Put arguments into scope as variables
scope.extend(fn_def.params.iter().cloned().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
2022-01-24 10:04:40 +01:00
// Push a new call stack frame
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.push_call_stack_frame(
fn_def.name.clone(),
scope
.iter()
.skip(orig_scope_len)
2022-02-08 02:02:15 +01:00
.map(|(.., v)| v.clone())
.collect(),
global.source.clone(),
pos,
);
}
2022-01-24 10:04:40 +01:00
2021-11-29 03:17:04 +01:00
// Merge in encapsulated environment, if any
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
#[cfg(not(feature = "no_module"))]
let mut lib_merged = crate::StaticVec::with_capacity(lib.len() + 1);
2021-11-29 03:17:04 +01:00
#[cfg(not(feature = "no_module"))]
let (lib, constants) = if let Some(crate::ast::EncapsulatedEnviron {
lib: ref fn_lib,
ref imports,
ref constants,
}) = fn_def.environ
{
for (n, m) in imports.iter().cloned() {
2022-01-28 11:59:18 +01:00
global.push_import(n, m)
}
(
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
},
Some(mem::replace(&mut global.constants, constants.clone())),
)
} else {
(lib, None)
};
2021-11-29 03:17:04 +01:00
#[cfg(feature = "debugging")]
2022-02-03 04:56:08 +01:00
{
let node = crate::ast::Stmt::Noop(fn_def.body.position());
self.run_debugger(scope, global, state, lib, this_ptr, &node, level)?;
}
2021-11-29 03:17:04 +01:00
// Evaluate the function
2022-02-02 07:57:30 +01:00
let mut _result = self
2021-11-29 03:17:04 +01:00
.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
2022-02-08 02:02:15 +01:00
ERR::Return(x, ..) => Ok(x),
2021-11-29 03:17:04 +01:00
// Error in sub function call
2022-02-08 02:02:15 +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
});
#[cfg(feature = "debugging")]
2022-02-04 05:04:33 +01:00
{
let trigger = match global.debugger.status {
crate::eval::DebuggerStatus::FunctionExit(n) => n >= level,
2022-02-08 02:02:15 +01:00
crate::eval::DebuggerStatus::Next(.., true) => true,
2022-02-04 05:04:33 +01:00
_ => false,
};
if trigger {
let node = crate::ast::Stmt::Noop(fn_def.body.end_position().or_else(pos));
let node = (&node).into();
let event = match _result {
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
};
match self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level)
{
Ok(_) => (),
Err(err) => _result = Err(err),
}
}
2022-02-02 07:57:30 +01:00
// Pop the call stack
global.debugger.rewind_call_stack(orig_call_stack_len);
}
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())
}
2022-01-29 04:09:43 +01:00
#[cfg(not(feature = "no_module"))]
2022-01-24 10:04:40 +01:00
global.truncate_imports(orig_imports_len);
2021-12-28 04:42:52 +01:00
// Restore constants
2022-01-30 16:28:02 +01:00
#[cfg(not(feature = "no_module"))]
if let Some(constants) = constants {
global.constants = constants;
}
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);
2022-02-02 07:57:30 +01:00
_result
2021-11-29 03:17:04 +01:00
}
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 {
2022-01-29 04:09:43 +01:00
let _global = global;
2021-11-29 03:17:04 +01:00
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
2022-01-29 04:09:43 +01:00
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script));
#[cfg(not(feature = "no_module"))]
let result = result ||
2021-11-29 03:17:04 +01:00
// Then check imported modules
2022-01-29 04:09:43 +01:00
_global.map_or(false, |m| m.contains_qualified_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
}
}