diff --git a/Cargo.toml b/Cargo.toml index 6380d11b..6d48bc8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ num-traits = { version = "0.2.11", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] -default = ["serde"] +default = [] plugins = [] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync diff --git a/README.md b/README.md index c170099d..c8363ac2 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Features * Fairly efficient evaluation (1 million iterations in 0.4 sec on a single core, 2.3 GHz Linux VM). * Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to one single source file, all with names starting with `"unsafe_"`). -* Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature). +* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). * Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run. @@ -38,7 +38,7 @@ Features * Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). -* Serialization/deserialization support via [serde](https://crates.io/crates/serde) +* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). diff --git a/src/api.rs b/src/api.rs index 190e69db..49c7b486 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,10 +1,7 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{ - get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, - FN_IDX_SET, -}; +use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_native::{IteratorFn, SendSync}; @@ -19,6 +16,9 @@ use crate::utils::StaticVec; #[cfg(not(feature = "no_object"))] use crate::engine::Map; +#[cfg(not(feature = "no_function"))] +use crate::engine::get_script_function_by_signature; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -1189,6 +1189,7 @@ impl Engine { /// 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. + #[cfg(not(feature = "no_function"))] pub(crate) fn call_fn_dynamic_raw( &self, scope: &mut Scope, @@ -1240,6 +1241,7 @@ impl Engine { mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { + #[cfg(not(feature = "no_function"))] let lib = ast .lib() .iter_fn() @@ -1247,6 +1249,9 @@ impl Engine { .map(|(_, _, _, f)| f.get_fn_def().clone()) .collect(); + #[cfg(feature = "no_function")] + let lib = Default::default(); + let stmt = mem::take(ast.statements_mut()); optimize_into_ast(self, scope, stmt, lib, optimization_level) } diff --git a/src/engine.rs b/src/engine.rs index 27d35222..1de5d3d4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -216,6 +216,7 @@ impl State { } /// Get a script-defined function definition from a module. +#[cfg(not(feature = "no_function"))] pub fn get_script_function_by_signature<'a>( module: &'a Module, name: &str, @@ -767,22 +768,23 @@ impl Engine { .or_else(|| self.packages.get_fn(hash_fn)); if let Some(func) = func { - // Calling pure function but the first argument is a reference? - normalize_first_arg( - is_ref && (func.is_pure() || (func.is_script() && !is_method)), - &mut this_copy, - &mut old_this_ptr, - args, - ); + #[cfg(not(feature = "no_function"))] + 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(); + // Calling pure function but the first argument is a reference? + normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); + + #[cfg(not(feature = "no_function"))] if func.is_script() { // Run scripted function let fn_def = func.get_fn_def(); // Method call of script function - map first argument to `this` - if is_method { + return if is_method { let (first, rest) = args.split_at_mut(1); - return Ok(( + Ok(( self.call_script_fn( scope, mods, @@ -795,7 +797,7 @@ impl Engine { level, )?, false, - )); + )) } else { let result = self.call_script_fn( scope, mods, state, lib, &mut None, fn_name, fn_def, args, level, @@ -804,42 +806,42 @@ impl Engine { // Restore the original reference restore_first_arg(old_this_ptr, args); - return Ok((result, false)); + Ok((result, false)) }; - } else { - // Run external function - let result = func.get_native_fn()(self, args)?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT => ( - (self.print)(result.as_str().map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - KEYWORD_DEBUG => ( - (self.debug)(result.as_str().map_err(|typ| { - Box::new(EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(type_name::()).into(), - typ.into(), - Position::none(), - )) - })?) - .into(), - false, - ), - _ => (result, func.is_method()), - }); } + + // Run external function + let result = func.get_native_fn()(self, args)?; + + // Restore the original reference + restore_first_arg(old_this_ptr, args); + + // See if the function match print/debug (which requires special processing) + return Ok(match fn_name { + KEYWORD_PRINT => ( + (self.print)(result.as_str().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + KEYWORD_DEBUG => ( + (self.debug)(result.as_str().map_err(|typ| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + self.map_type_name(type_name::()).into(), + typ.into(), + Position::none(), + )) + })?) + .into(), + false, + ), + _ => (result, func.is_method()), + }); } // See if it is built in. @@ -2016,6 +2018,7 @@ impl Engine { }; match func { + #[cfg(not(feature = "no_function"))] Ok(f) if f.is_script() => { let args = args.as_mut(); let fn_def = f.get_fn_def(); diff --git a/src/fn_native.rs b/src/fn_native.rs index e41ae7db..a1110fd3 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -114,6 +114,7 @@ pub enum CallableFunction { /// An iterator function. Iterator(IteratorFn), /// A script-defined function. + #[cfg(not(feature = "no_function"))] Script(Shared), } @@ -123,6 +124,8 @@ impl fmt::Debug for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + + #[cfg(not(feature = "no_function"))] Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f), } } @@ -134,6 +137,8 @@ impl fmt::Display for CallableFunction { Self::Pure(_) => write!(f, "NativePureFunction"), Self::Method(_) => write!(f, "NativeMethod"), Self::Iterator(_) => write!(f, "NativeIterator"), + + #[cfg(not(feature = "no_function"))] CallableFunction::Script(s) => fmt::Display::fmt(s, f), } } @@ -144,24 +149,34 @@ impl CallableFunction { pub fn is_pure(&self) -> bool { match self { Self::Pure(_) => true, - Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Method(_) | Self::Iterator(_) => false, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, } } /// Is this a native Rust method function? pub fn is_method(&self) -> bool { match self { Self::Method(_) => true, - Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Pure(_) | Self::Iterator(_) => false, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, } } /// Is this an iterator function? pub fn is_iter(&self) -> bool { match self { Self::Iterator(_) => true, - Self::Pure(_) | Self::Method(_) | Self::Script(_) => false, + Self::Pure(_) | Self::Method(_) => false, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, } } /// Is this a Rhai-scripted function? + #[cfg(not(feature = "no_function"))] pub fn is_script(&self) -> bool { match self { Self::Script(_) => true, @@ -176,7 +191,10 @@ impl CallableFunction { pub fn get_native_fn(&self) -> &FnAny { match self { Self::Pure(f) | Self::Method(f) => f.as_ref(), - Self::Iterator(_) | Self::Script(_) => unreachable!(), + Self::Iterator(_) => unreachable!(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => unreachable!(), } } /// Get a shared reference to a script-defined function definition. @@ -184,6 +202,7 @@ impl CallableFunction { /// # Panics /// /// Panics if the `CallableFunction` is not `Script`. + #[cfg(not(feature = "no_function"))] pub fn get_shared_fn_def(&self) -> Shared { match self { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), @@ -195,6 +214,7 @@ impl CallableFunction { /// # Panics /// /// Panics if the `CallableFunction` is not `Script`. + #[cfg(not(feature = "no_function"))] pub fn get_fn_def(&self) -> &ScriptFnDef { match self { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(), @@ -209,7 +229,10 @@ impl CallableFunction { pub fn get_iter_fn(&self) -> IteratorFn { match self { Self::Iterator(f) => *f, - Self::Pure(_) | Self::Method(_) | Self::Script(_) => unreachable!(), + Self::Pure(_) | Self::Method(_) => unreachable!(), + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => unreachable!(), } } /// Create a new `CallableFunction::Pure`. @@ -228,12 +251,14 @@ impl From for CallableFunction { } } +#[cfg(not(feature = "no_function"))] impl From for CallableFunction { fn from(func: ScriptFnDef) -> Self { Self::Script(func.into()) } } +#[cfg(not(feature = "no_function"))] impl From> for CallableFunction { fn from(func: Shared) -> Self { Self::Script(func) diff --git a/src/lib.rs b/src/lib.rs index 50c574f7..0b825113 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,17 +125,23 @@ pub use parser::FLOAT; pub use module::ModuleResolver; /// Module containing all built-in _module resolvers_ available to Rhai. +/// +/// Not available under the `no_module` feature. #[cfg(not(feature = "no_module"))] pub mod module_resolvers { pub use crate::module::resolvers::*; } /// Serialization support for [`serde`](https://crates.io/crates/serde). +/// +/// Requires the `serde` feature. #[cfg(feature = "serde")] pub mod ser { pub use crate::serde::ser::to_dynamic; } /// Deserialization support for [`serde`](https://crates.io/crates/serde). +/// +/// Requires the `serde` feature. #[cfg(feature = "serde")] pub mod de { pub use crate::serde::de::from_dynamic; diff --git a/src/module.rs b/src/module.rs index c7198d83..7ae8d355 100644 --- a/src/module.rs +++ b/src/module.rs @@ -236,6 +236,7 @@ impl Module { /// Set a script-defined function into the module. /// /// If there is an existing function of the same name and number of arguments, it is replaced. + #[cfg(not(feature = "no_function"))] pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) { // None + function name + number of arguments. let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); @@ -876,6 +877,7 @@ impl Module { .functions .iter() .filter(|(_, (_, _, _, v))| match v { + #[cfg(not(feature = "no_function"))] CallableFunction::Script(ref f) => { filter(f.access, f.name.as_str(), f.params.len()) } @@ -893,6 +895,7 @@ impl Module { } /// Filter out the functions, retaining only some based on a filter predicate. + #[cfg(not(feature = "no_function"))] pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) { self.functions.retain(|_, (_, _, _, v)| match v { CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), @@ -930,6 +933,7 @@ impl Module { } /// Get an iterator over all script-defined functions in the module. + #[cfg(not(feature = "no_function"))] pub fn iter_script_fn<'a>(&'a self) -> impl Iterator> + 'a { self.functions .values() @@ -1014,6 +1018,7 @@ impl Module { Public => (), } + #[cfg(not(feature = "no_function"))] if func.is_script() { let fn_def = func.get_shared_fn_def(); // Qualifiers + function name + number of arguments. @@ -1024,20 +1029,21 @@ impl Module { empty(), ); functions.push((hash_qualified_script, fn_def.into())); - } else { - // Qualified Rust functions are indexed in two steps: - // 1) Calculate a hash in a similar manner to script-defined functions, - // i.e. qualifiers + function name + number of arguments. - let hash_qualified_script = - calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); - // 2) Calculate a second hash with no qualifiers, empty function name, - // zero number of arguments, and the actual list of argument `TypeId`'.s - let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; - - functions.push((hash_qualified_fn, func.clone())); + continue; } + + // Qualified Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + number of arguments. + let hash_qualified_script = + calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); + // 2) Calculate a second hash with no qualifiers, empty function name, + // zero number of arguments, and the actual list of argument `TypeId`'.s + let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); + // 3) The final hash is the XOR of the two hashes. + let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; + + functions.push((hash_qualified_fn, func.clone())); } } diff --git a/src/optimize.rs b/src/optimize.rs index c7a1ed71..5676bad4 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -551,11 +551,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) - if state.lib.iter_fn().find(|(_, _, _, f)| { + #[cfg(not(feature = "no_function"))] + let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); &fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) - }).is_some() { + }).is_some(); + + #[cfg(feature = "no_function")] + const has_script_fn: bool = false; + + if has_script_fn { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x);