From aae9e43109409f5e9ae88be543e98e2f862b0a3a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 17:51:40 +0800 Subject: [PATCH] Implement module-qualified functions. --- src/engine.rs | 67 ++++++------ src/module.rs | 263 +++++++++++++++++++++++++++++++++++++++++++++-- src/parser.rs | 7 +- src/scope.rs | 2 +- tests/modules.rs | 53 +++++++--- 5 files changed, 329 insertions(+), 63 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 5be305ac..030fc85e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -395,7 +395,7 @@ fn search_scope<'a>( }; Ok(( - module.get_qualified_variable_mut(name, modules.as_ref(), pos)?, + module.get_qualified_var_mut(name, modules.as_ref(), pos)?, // Module variables are constant ScopeEntryType::Constant, )) @@ -469,13 +469,11 @@ impl Engine { } /// Universal method for calling functions either registered with the `Engine` or written in Rhai - // TODO - handle moduled function call pub(crate) fn call_fn_raw( &self, scope: Option<&mut Scope>, fn_lib: &FunctionsLib, fn_name: &str, - modules: &ModuleRef, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -643,7 +641,6 @@ impl Engine { &self, fn_lib: &FunctionsLib, fn_name: &str, - modules: &ModuleRef, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -651,28 +648,19 @@ impl Engine { ) -> Result> { match fn_name { // type_of - KEYWORD_TYPE_OF - if modules.is_none() - && args.len() == 1 - && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => - { + KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => { Ok(self.map_type_name(args[0].type_name()).to_string().into()) } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL - if modules.is_none() - && args.len() == 1 - && !self.has_override(fn_lib, KEYWORD_EVAL) => - { + KEYWORD_EVAL if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), pos, ))) } - // Normal method call - _ => self.call_fn_raw(None, fn_lib, fn_name, modules, args, def_val, pos, level), + _ => self.call_fn_raw(None, fn_lib, fn_name, args, def_val, pos, level), } } @@ -767,7 +755,7 @@ impl Engine { let def_val = def_val.as_deref(); // A function call is assumed to have side effects, so the value is changed // TODO - Remove assumption of side effects by checking whether the first parameter is &mut - self.exec_fn_call(fn_lib, fn_name, &None, &mut args, def_val, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_,_,_,_,_) => unreachable!(), @@ -788,13 +776,13 @@ impl Engine { Expr::Property(id, pos) if new_val.is_some() => { let fn_name = make_setter(id); let mut args = [obj, new_val.as_mut().unwrap()]; - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, true)) } // xxx.id Expr::Property(id, pos) => { let fn_name = make_getter(id); let mut args = [obj]; - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, false)) + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) } // {xxx:map}.idx_lhs[idx_expr] Expr::Index(dot_lhs, dot_rhs, pos) | @@ -824,7 +812,7 @@ impl Engine { let indexed_val = &mut (if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_getter(id); - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args[..1], None, *pos, 0)? + self.exec_fn_call(fn_lib, &fn_name, &mut args[..1], None, *pos, 0)? } else { // Syntax error return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -842,7 +830,7 @@ impl Engine { let fn_name = make_setter(id); // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; - self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).or_else(|err| match *err { + self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), err => Err(Box::new(err)) @@ -1073,7 +1061,7 @@ impl Engine { let pos = rhs.position(); if self - .call_fn_raw(None, fn_lib, "==", &None, args, def_value, pos, level)? + .call_fn_raw(None, fn_lib, "==", args, def_value, pos, level)? .as_bool() .unwrap_or(false) { @@ -1213,12 +1201,26 @@ impl Engine { let mut args: Vec<_> = arg_values.iter_mut().collect(); - // eval - only in function call style - if fn_name.as_ref() == KEYWORD_EVAL - && modules.is_none() + if let Some(modules) = modules { + // Module-qualified function call + let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + + let (id, root_pos) = modules.get(0); // First module + + let module = scope.find_module(id).ok_or_else(|| { + Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + })?; + match module.get_qualified_fn(fn_name, hash, modules.as_ref(), *pos) { + Ok(func) => func(&mut args, *pos) + .map_err(|err| EvalAltResult::set_position(err, *pos)), + Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), + Err(err) => Err(err), + } + } else if fn_name.as_ref() == KEYWORD_EVAL && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { + // eval - only in function call style let prev_len = scope.len(); // Evaluate the text string as a script @@ -1231,12 +1233,12 @@ impl Engine { state.always_search = true; } - return result; + result + } else { + // Normal function call - except for eval (handled above) + let def_value = def_val.as_deref(); + self.exec_fn_call(fn_lib, fn_name, &mut args, def_value, *pos, level) } - - // Normal function call - except for eval (handled above) - let def_value = def_val.as_deref(); - self.exec_fn_call(fn_lib, fn_name, modules, &mut args, def_value, *pos, level) } Expr::In(lhs, rhs, _) => { @@ -1472,8 +1474,9 @@ impl Engine { .try_cast::() { let mut module = Module::new(); - module.set_variable("kitty", "foo".to_string()); - module.set_variable("path", path); + module.set_var("kitty", "foo".to_string()); + module.set_var("path", path); + module.set_fn_1_mut("calc", |x: &mut String| Ok(x.len() as crate::parser::INT)); // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); diff --git a/src/module.rs b/src/module.rs index d7c48d3c..e7b2a51b 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,12 +1,14 @@ //! Module defining external-loaded modules for Rhai. use crate::any::{Dynamic, Variant}; -use crate::engine::{FnAny, FunctionsLib}; +use crate::calc_fn_hash; +use crate::engine::{FnAny, FnCallArgs, FunctionsLib}; use crate::result::EvalAltResult; use crate::token::Position; +use crate::token::Token; use crate::utils::StaticVec; -use crate::stdlib::{collections::HashMap, fmt, string::String}; +use crate::stdlib::{any::TypeId, collections::HashMap, fmt, iter::empty, mem, string::String}; /// An imported module, which may contain variables, sub-modules, /// external Rust functions, and script-defined functions. @@ -51,34 +53,34 @@ impl Module { } /// Does a variable exist in the module? - pub fn contains_variable(&self, name: &str) -> bool { + pub fn contains_var(&self, name: &str) -> bool { self.variables.contains_key(name) } /// Get the value of a module variable. - pub fn get_variable_value(&self, name: &str) -> Option { - self.get_variable(name).and_then(|v| v.try_cast::()) + pub fn get_var_value(&self, name: &str) -> Option { + self.get_var(name).and_then(|v| v.try_cast::()) } /// Get a module variable. - pub fn get_variable(&self, name: &str) -> Option { + pub fn get_var(&self, name: &str) -> Option { self.variables.get(name).cloned() } /// Get a mutable reference to a module variable. - pub fn get_variable_mut(&mut self, name: &str) -> Option<&mut Dynamic> { + pub fn get_var_mut(&mut self, name: &str) -> Option<&mut Dynamic> { self.variables.get_mut(name) } /// Set a variable into the module. /// /// If there is an existing variable of the same name, it is replaced. - pub fn set_variable, T: Into>(&mut self, name: K, value: T) { + pub fn set_var, T: Into>(&mut self, name: K, value: T) { self.variables.insert(name.into(), value.into()); } /// Get a mutable reference to a modules-qualified variable. - pub(crate) fn get_qualified_variable_mut( + pub(crate) fn get_qualified_var_mut( &mut self, name: &str, modules: &StaticVec<(String, Position)>, @@ -86,7 +88,7 @@ impl Module { ) -> Result<&mut Dynamic, Box> { Ok(self .get_qualified_module_mut(modules)? - .get_variable_mut(name) + .get_var_mut(name) .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?) } @@ -131,4 +133,245 @@ impl Module { Ok(module) } + + /// Does the particular Rust function exist in the module? + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + pub fn contains_fn(&self, hash: u64) -> bool { + self.functions.contains_key(&hash) + } + + /// Set a Rust function into the module, returning a hash key. + /// + /// If there is an existing Rust function of the same hash, it is replaced. + pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box) -> u64 { + let hash = calc_fn_hash(fn_name, params.iter().cloned()); + self.functions.insert(hash, func); + hash + } + + /// Set a Rust function taking no parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_0>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn() -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn() -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash(fn_name, empty()); + let f = move |_: &mut FnCallArgs, _: Position| func().map(|v| v.into()); + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking one parameter into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_1>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(A) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); + + let f = move |args: &mut FnCallArgs, _: Position| { + func(mem::take(args[0]).cast::()).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking one mutable parameter into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_1_mut>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); + + let f = move |args: &mut FnCallArgs, _: Position| { + func(args[0].downcast_mut::().unwrap()).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking two parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_2>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::()].iter().cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let a = mem::take(args[0]).cast::(); + let b = mem::take(args[1]).cast::(); + + func(a, b).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking two parameters (the first one mutable) into the module, + /// returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_2_mut>( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> Result> + + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::()].iter().cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let b = mem::take(args[1]).cast::(); + let a = args[0].downcast_mut::().unwrap(); + + func(a, b).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking three parameters into the module, returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_3< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Into, + >( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> Result> + 'static, + #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::(), TypeId::of::()] + .iter() + .cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let a = mem::take(args[0]).cast::(); + let b = mem::take(args[1]).cast::(); + let c = mem::take(args[2]).cast::(); + + func(a, b, c).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Set a Rust function taking three parameters (the first one mutable) into the module, + /// returning a hash key. + /// + /// If there is a similar existing Rust function, it is replaced. + pub fn set_fn_3_mut< + A: Variant + Clone, + B: Variant + Clone, + C: Variant + Clone, + T: Into, + >( + &mut self, + fn_name: &str, + #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> Result> + + 'static, + #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> Result> + + Send + + Sync + + 'static, + ) -> u64 { + let hash = calc_fn_hash( + fn_name, + [TypeId::of::(), TypeId::of::(), TypeId::of::()] + .iter() + .cloned(), + ); + + let f = move |args: &mut FnCallArgs, _: Position| { + let b = mem::take(args[1]).cast::(); + let c = mem::take(args[2]).cast::(); + let a = args[0].downcast_mut::().unwrap(); + + func(a, b, c).map(|v| v.into()) + }; + self.functions.insert(hash, Box::new(f)); + hash + } + + /// Get a Rust function. + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + pub fn get_fn(&self, hash: u64) -> Option<&Box> { + self.functions.get(&hash) + } + + /// Get a modules-qualified function. + /// + /// 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_qualified_fn( + &mut self, + name: &str, + hash: u64, + modules: &StaticVec<(String, Position)>, + pos: Position, + ) -> Result<&Box, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .get_fn(hash) + .ok_or_else(|| { + let mut fn_name: String = Default::default(); + + modules.iter().for_each(|(n, _)| { + fn_name.push_str(n); + fn_name.push_str(Token::DoubleColon.syntax().as_ref()); + }); + + fn_name.push_str(name); + + Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos)) + })?) + } } diff --git a/src/parser.rs b/src/parser.rs index 9cac3bef..4cb663d8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -589,13 +589,8 @@ impl Expr { _ => false, }, - Self::Variable(_, None, _, _) => match token { - Token::LeftBracket | Token::LeftParen => true, - #[cfg(not(feature = "no_module"))] - Token::DoubleColon => true, - _ => false, - }, Self::Variable(_, _, _, _) => match token { + Token::LeftBracket | Token::LeftParen => true, #[cfg(not(feature = "no_module"))] Token::DoubleColon => true, _ => false, diff --git a/src/scope.rs b/src/scope.rs index 56a0e6fb..a20afd44 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -172,7 +172,7 @@ impl<'a> Scope<'a> { /// Add (push) a new module to the Scope. /// /// Modules are used for accessing member variables, functions and plugins under a namespace. - pub(crate) fn push_module>>(&mut self, name: K, value: Module) { + pub fn push_module>>(&mut self, name: K, value: Module) { self.push_dynamic_value( name, EntryType::Module, diff --git a/tests/modules.rs b/tests/modules.rs index 4f17d635..5a62d942 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,33 +1,58 @@ #![cfg(not(feature = "no_module"))] -use rhai::{EvalAltResult, Module, Scope, INT}; +use rhai::{Engine, EvalAltResult, Module, Scope, INT}; #[test] fn test_module() { let mut module = Module::new(); - module.set_variable("kitty", 42 as INT); + module.set_var("answer", 42 as INT); - assert!(module.contains_variable("kitty")); - assert_eq!(module.get_variable_value::("kitty").unwrap(), 42); + assert!(module.contains_var("answer")); + assert_eq!(module.get_var_value::("answer").unwrap(), 42); } #[test] -fn test_sub_module() { +fn test_sub_module() -> Result<(), Box> { let mut module = Module::new(); let mut sub_module = Module::new(); let mut sub_module2 = Module::new(); - sub_module2.set_variable("kitty", 42 as INT); + sub_module2.set_var("answer", 41 as INT); + let hash = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); - sub_module.set_sub_module("world", sub_module2); - module.set_sub_module("hello", sub_module); + sub_module.set_sub_module("universe", sub_module2); + module.set_sub_module("life", sub_module); - assert!(module.contains_sub_module("hello")); - let m = module.get_sub_module("hello").unwrap(); + assert!(module.contains_sub_module("life")); + let m = module.get_sub_module("life").unwrap(); - assert!(m.contains_sub_module("world")); - let m2 = m.get_sub_module("world").unwrap(); + assert!(m.contains_sub_module("universe")); + let m2 = m.get_sub_module("universe").unwrap(); - assert!(m2.contains_variable("kitty")); - assert_eq!(m2.get_variable_value::("kitty").unwrap(), 42); + assert!(m2.contains_var("answer")); + assert!(m2.contains_fn(hash)); + + assert_eq!(m2.get_var_value::("answer").unwrap(), 41); + + let mut engine = Engine::new(); + let mut scope = Scope::new(); + + scope.push_module("question", module); + + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "question::life::universe::answer + 1" + )?, + 42 + ); + assert_eq!( + engine.eval_expression_with_scope::( + &mut scope, + "question::life::universe::inc(question::life::universe::answer)" + )?, + 42 + ); + + Ok(()) }