From 6c69a400835c8ce5f2dac2fe87064127fefa173a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 11 Sep 2020 22:32:59 +0800 Subject: [PATCH] Allow scripted functions in packages. --- RELEASES.md | 1 + src/fn_call.rs | 25 +++++++++++++++++++++---- src/module.rs | 41 +++++++++++++++++++++++++++++------------ tests/packages.rs | 24 +++++++++++++++++------- 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 69044fc5..902cd855 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,6 +8,7 @@ New features ------------ * Plugins support via procedural macros. +* Scripted functions are allowed in packages. Version 0.18.3 diff --git a/src/fn_call.rs b/src/fn_call.rs index e7c5fdc7..3728d4e8 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -444,7 +444,7 @@ impl Engine { //|| self.global_module.contains_fn(hash_script, pub_only) || self.global_module.contains_fn(hash_fn, pub_only) // Then check packages - //|| self.packages.contains_fn(hash_script, pub_only) + || self.packages.contains_fn(hash_script, pub_only) || self.packages.contains_fn(hash_fn, pub_only) } @@ -477,7 +477,17 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); - let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); + let hash_fn = calc_fn_hash( + empty(), + fn_name, + if args.is_empty() { + // Distinguish between a script function and a native function with no parameters + usize::MAX + } else { + args.len() + }, + arg_types, + ); match fn_name { // type_of @@ -514,9 +524,15 @@ impl Engine { // Normal script function call #[cfg(not(feature = "no_function"))] - _ if hash_script > 0 && lib.contains_fn(hash_script, pub_only) => { + _ if lib.contains_fn(hash_script, pub_only) + || self.packages.contains_fn(hash_script, pub_only) => + { // Get scripted function - let func = lib.get_fn(hash_script, pub_only).unwrap().get_fn_def(); + let func = lib + .get_fn(hash_script, pub_only) + .or_else(|| self.packages.get_fn(hash_script, pub_only)) + .unwrap() + .get_fn_def(); let scope = &mut Scope::new(); let mods = &mut Imports::new(); @@ -559,6 +575,7 @@ impl Engine { Ok((result, false)) } + // Normal native function call _ => self.call_native_fn( state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val, diff --git a/src/module.rs b/src/module.rs index c6e161b6..3513dd7f 100644 --- a/src/module.rs +++ b/src/module.rs @@ -247,9 +247,13 @@ impl Module { &mut self, hash_var: u64, ) -> Result<&mut Dynamic, Box> { - self.all_variables.get_mut(&hash_var).ok_or_else(|| { - EvalAltResult::ErrorVariableNotFound(String::new(), Position::none()).into() - }) + if hash_var == 0 { + Err(EvalAltResult::ErrorVariableNotFound(String::new(), Position::none()).into()) + } else { + self.all_variables.get_mut(&hash_var).ok_or_else(|| { + EvalAltResult::ErrorVariableNotFound(String::new(), Position::none()).into() + }) + } } /// Set a script-defined function into the module. @@ -354,7 +358,9 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { - if public_only { + if hash_fn == 0 { + false + } else if public_only { self.functions .get(&hash_fn) .map(|(_, access, _, _)| match access { @@ -383,7 +389,14 @@ impl Module { ) -> u64 { let name = name.into(); - let hash_fn = calc_fn_hash(empty(), &name, arg_types.len(), arg_types.iter().cloned()); + let args_len = if arg_types.is_empty() { + // Distinguish between a script function and a function with no parameters + usize::MAX + } else { + arg_types.len() + }; + + let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); let params = arg_types.into_iter().cloned().collect(); @@ -910,13 +923,17 @@ impl Module { /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> { - self.functions - .get(&hash_fn) - .and_then(|(_, access, _, f)| match access { - _ if !public_only => Some(f), - FnAccess::Public => Some(f), - FnAccess::Private => None, - }) + if hash_fn == 0 { + None + } else { + self.functions + .get(&hash_fn) + .and_then(|(_, access, _, f)| match access { + _ if !public_only => Some(f), + FnAccess::Public => Some(f), + FnAccess::Private => None, + }) + } } /// Get a modules-qualified function. diff --git a/tests/packages.rs b/tests/packages.rs index c08234fe..961d4b3e 100644 --- a/tests/packages.rs +++ b/tests/packages.rs @@ -1,10 +1,10 @@ -use rhai::{Engine, EvalAltResult, INT, Scope}; use rhai::packages::{Package, StandardPackage}; +use rhai::{Engine, EvalAltResult, Module, Scope, INT}; #[test] fn test_packages() -> Result<(), Box> { - let e = Engine::new(); - let ast = e.compile("x")?; + let engine = Engine::new(); + let ast = engine.compile("x")?; let std_pkg = StandardPackage::new(); let make_call = |x: INT| -> Result> { @@ -24,10 +24,20 @@ fn test_packages() -> Result<(), Box> { engine.eval_ast_with_scope::(&mut scope, &ast) }; - // The following loop creates 10,000 Engine instances! - for x in 0..10_000 { - assert_eq!(make_call(x)?, x); - } + assert_eq!(make_call(42)?, 42); + + Ok(()) +} + +#[test] +fn test_packages_with_script() -> Result<(), Box> { + let mut engine = Engine::new(); + let ast = engine.compile("fn foo(x) { x + 1 }")?; + let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; + + engine.load_package(module); + + assert_eq!(engine.eval::("foo(41)")?, 42); Ok(()) }