Fix builds.

This commit is contained in:
Stephen Chung 2021-11-29 10:17:04 +08:00
parent 95dc2ad502
commit c5f2b0a253
9 changed files with 233 additions and 229 deletions

View File

@ -222,14 +222,14 @@ impl NativeCallContext<'_> {
///
/// # WARNING
///
/// All arguments may be _consumed_, meaning that they may be replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
/// unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
/// Do not use the arguments after this call. If they are needed afterwards, clone them _before_
/// calling this function.
///
/// If `is_method` is [`true`], the first argument is assumed to be passed
/// by reference and is not consumed.
/// If `is_method` is [`true`], the first argument is assumed to be passed by reference and is
/// not consumed.
///
/// # Deprecated
///

View File

@ -63,9 +63,6 @@ pub struct ScriptFnDef {
pub params: StaticVec<Identifier>,
/// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
pub comments: Option<Box<[Box<str>]>>,
}
@ -100,15 +97,12 @@ pub struct ScriptFnMetadata<'a> {
/// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only.
///
/// Not available under `no_function`.
///
/// Block doc-comments are kept in a single string slice with line-breaks within.
///
/// Line doc-comments are kept in one string slice per line without the termination line-break.
///
/// Leading white-spaces are stripped, and each string slice always starts with the corresponding
/// doc-comment leader: `///` or `/**`.
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
pub comments: Vec<&'a str>,
/// Function access mode.

View File

@ -303,6 +303,7 @@ 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_native_fn(
&self,
@ -472,197 +473,13 @@ 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 `()`!
#[cfg(not(feature = "no_function"))]
pub(crate) fn call_script_fn(
&self,
scope: &mut Scope,
mods: &mut Imports,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
fn_def: &crate::ast::ScriptFnDef,
args: &mut FnCallArgs,
pos: Position,
rewind_scope: bool,
level: usize,
) -> RhaiResult {
#[inline(never)]
fn make_error(
name: String,
fn_def: &crate::ast::ScriptFnDef,
mods: &Imports,
err: Box<EvalAltResult>,
pos: Position,
) -> RhaiResult {
Err(EvalAltResult::ErrorInFunctionCall(
name,
fn_def
.lib
.as_ref()
.and_then(|m| m.id().map(|id| id.to_string()))
.or_else(|| mods.source.as_ref().map(|s| s.to_string()))
.unwrap_or_default(),
err,
pos,
)
.into())
}
assert!(fn_def.params.len() == args.len());
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut mods.num_operations, pos)?;
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if level > self.max_call_levels() {
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
}
let orig_scope_len = scope.len();
let orig_mods_len = mods.len();
// Put arguments into scope as variables
// Actually consume the arguments instead of cloning them
scope.extend(
fn_def
.params
.iter()
.zip(args.iter_mut().map(|v| mem::take(*v)))
.map(|(name, value)| {
let var_name: std::borrow::Cow<'_, str> =
crate::r#unsafe::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"))]
if !fn_def.mods.is_empty() {
fn_def
.mods
.iter_raw()
.for_each(|(n, m)| mods.push(n.clone(), m.clone()));
}
// Evaluate the function
let body = &fn_def.body;
let result = self
.eval_stmt_block(
scope,
mods,
state,
lib,
this_ptr,
body,
true,
rewind_scope,
level,
)
.or_else(|err| match *err {
// Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x),
// Error in sub function call
EvalAltResult::ErrorInFunctionCall(name, src, err, _) => {
let fn_name = if src.is_empty() {
format!("{} < {}", name, fn_def.name)
} else {
format!("{} @ '{}' < {}", name, src, fn_def.name)
};
make_error(fn_name, fn_def, mods, err, pos)
}
// 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`
_ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
});
// Remove all local variables
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())
}
mods.truncate(orig_mods_len);
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
result
}
// Does a scripted function exist?
#[cfg(not(feature = "no_function"))]
#[must_use]
pub(crate) fn has_script_fn(
&self,
mods: Option<&Imports>,
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
|| mods.map_or(false, |m| m.contains_fn(hash_script))
// 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
}
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
///
/// # 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 exec_fn_call(
&self,

View File

@ -8,6 +8,7 @@ pub mod hashing;
pub mod native;
pub mod plugin;
pub mod register;
pub mod script;
pub use args::FuncArgs;
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};

View File

@ -227,19 +227,19 @@ impl<'a> NativeCallContext<'a> {
}
/// Call a function inside the call context.
///
/// If `is_method_call` is [`true`], the first argument is assumed to be the
/// `this` pointer for a script-defined function (or the object of a method call).
/// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for
/// a script-defined function (or the object of a method call).
///
/// # WARNING
///
/// All arguments may be _consumed_, meaning that they may be replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
/// unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
/// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them
/// _before_ calling this function.
///
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed
/// by reference and is not consumed.
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is
/// not consumed.
pub fn call_fn_raw(
&self,
fn_name: impl AsRef<str>,

196
src/func/script.rs Normal file
View File

@ -0,0 +1,196 @@
//! Implement script function-calling mechanism for [`Engine`].
#![cfg(not(feature = "no_function"))]
use crate::ast::ScriptFnDef;
use crate::engine::{EvalState, Imports};
use crate::func::call::FnCallArgs;
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, StaticVec};
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,
mods: &mut Imports,
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,
mods: &Imports,
err: Box<EvalAltResult>,
pos: Position,
) -> RhaiResult {
Err(EvalAltResult::ErrorInFunctionCall(
name,
fn_def
.lib
.as_ref()
.and_then(|m| m.id().map(|id| id.to_string()))
.or_else(|| mods.source.as_ref().map(|s| s.to_string()))
.unwrap_or_default(),
err,
pos,
)
.into())
}
assert!(fn_def.params.len() == args.len());
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut mods.num_operations, pos)?;
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
// Check for stack overflow
#[cfg(not(feature = "unchecked"))]
if level > self.max_call_levels() {
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
}
let orig_scope_len = scope.len();
let orig_mods_len = mods.len();
// Put arguments into scope as variables
// Actually consume the arguments instead of cloning them
scope.extend(
fn_def
.params
.iter()
.zip(args.iter_mut().map(|v| mem::take(*v)))
.map(|(name, value)| {
let var_name: std::borrow::Cow<'_, str> =
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"))]
if !fn_def.mods.is_empty() {
fn_def
.mods
.iter_raw()
.for_each(|(n, m)| mods.push(n.clone(), m.clone()));
}
// Evaluate the function
let body = &fn_def.body;
let result = self
.eval_stmt_block(
scope,
mods,
state,
lib,
this_ptr,
body,
true,
rewind_scope,
level,
)
.or_else(|err| match *err {
// Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x),
// Error in sub function call
EvalAltResult::ErrorInFunctionCall(name, src, err, _) => {
let fn_name = if src.is_empty() {
format!("{} < {}", name, fn_def.name)
} else {
format!("{} @ '{}' < {}", name, src, fn_def.name)
};
make_error(fn_name, fn_def, mods, err, pos)
}
// 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`
_ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
});
// Remove all local variables
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())
}
mods.truncate(orig_mods_len);
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
result
}
// Does a scripted function exist?
#[must_use]
pub(crate) fn has_script_fn(
&self,
mods: Option<&Imports>,
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
|| mods.map_or(false, |m| m.contains_fn(hash_script))
// 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
}
}

View File

@ -3,19 +3,16 @@ use crate::{Engine, EvalAltResult, Module, Position, Shared, AST};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
mod dummy;
pub use dummy::DummyModuleResolver;
mod collection;
pub use collection::ModuleResolversCollection;
mod dummy;
mod file;
mod stat;
pub use collection::ModuleResolversCollection;
pub use dummy::DummyModuleResolver;
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
pub use file::FileModuleResolver;
mod stat;
pub use stat::StaticModuleResolver;
/// Trait that encapsulates a module resolution service.

View File

@ -40,11 +40,7 @@ pub enum OptimizationLevel {
impl Default for OptimizationLevel {
#[inline(always)]
fn default() -> Self {
if cfg!(feature = "no_optimize") {
Self::None
} else {
Self::Simple
}
Self::Simple
}
}
@ -1145,19 +1141,13 @@ pub fn optimize_into_ast(
>,
optimization_level: OptimizationLevel,
) -> AST {
let level = if cfg!(feature = "no_optimize") {
OptimizationLevel::default()
} else {
optimization_level
};
let mut statements = statements;
#[cfg(not(feature = "no_function"))]
let lib = {
let mut module = crate::Module::new();
if level != OptimizationLevel::None {
if optimization_level != OptimizationLevel::None {
// We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new();
@ -1189,7 +1179,8 @@ pub fn optimize_into_ast(
// Optimize the function body
let body = mem::take(fn_def.body.deref_mut());
*fn_def.body = optimize_top_level(body, engine, scope, lib2, level);
*fn_def.body =
optimize_top_level(body, engine, scope, lib2, optimization_level);
fn_def
})
@ -1208,7 +1199,7 @@ pub fn optimize_into_ast(
statements.shrink_to_fit();
AST::new(
match level {
match optimization_level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => optimize_top_level(
statements,
@ -1216,7 +1207,7 @@ pub fn optimize_into_ast(
&scope,
#[cfg(not(feature = "no_function"))]
&[&lib],
level,
optimization_level,
),
},
#[cfg(not(feature = "no_function"))]

View File

@ -3270,7 +3270,11 @@ impl Engine {
));
#[cfg(feature = "no_optimize")]
return Ok(AST::new(statements, crate::Module::new()));
return Ok(AST::new(
statements,
#[cfg(not(feature = "no_function"))]
crate::Module::new(),
));
}
/// Parse the global level statements.
@ -3371,6 +3375,10 @@ impl Engine {
#[cfg(feature = "no_optimize")]
#[cfg(feature = "no_function")]
return Ok(AST::new(statements, crate::Module::new()));
return Ok(AST::new(
statements,
#[cfg(not(feature = "no_function"))]
crate::Module::new(),
));
}
}