From c5f2b0a2530b559ac6dc5dd60ea9fb2f46a6b677 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 29 Nov 2021 10:17:04 +0800 Subject: [PATCH] Fix builds. --- src/api/deprecated.rs | 12 +-- src/ast.rs | 6 -- src/func/call.rs | 187 +--------------------------------- src/func/mod.rs | 1 + src/func/native.rs | 16 +-- src/func/script.rs | 196 ++++++++++++++++++++++++++++++++++++ src/module/resolvers/mod.rs | 11 +- src/optimizer.rs | 21 ++-- src/parser.rs | 12 ++- 9 files changed, 233 insertions(+), 229 deletions(-) create mode 100644 src/func/script.rs diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 1ad4fd9b..0673fe52 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -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 /// diff --git a/src/ast.rs b/src/ast.rs index bc6f99f2..0d50f6ec 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -63,9 +63,6 @@ pub struct ScriptFnDef { pub params: StaticVec, /// _(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]>>, } @@ -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. diff --git a/src/func/call.rs b/src/func/call.rs index 25079ab6..64bf299d 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -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, - 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, diff --git a/src/func/mod.rs b/src/func/mod.rs index 70596732..1d5a0841 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -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}; diff --git a/src/func/native.rs b/src/func/native.rs index cc4de60b..b0305cc7 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -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, diff --git a/src/func/script.rs b/src/func/script.rs new file mode 100644 index 00000000..5e32bfcd --- /dev/null +++ b/src/func/script.rs @@ -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, + 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 + } +} diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index ed46a379..087d8f7c 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -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. diff --git a/src/optimizer.rs b/src/optimizer.rs index 73d70857..a25f4d67 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -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"))] diff --git a/src/parser.rs b/src/parser.rs index f207c7fb..26f2d625 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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(), + )); } }