diff --git a/README.md b/README.md index 5a4ebd16..4ed22d10 100644 --- a/README.md +++ b/README.md @@ -2127,7 +2127,8 @@ engine.eval_expression_with_scope::(&scope, "question::inc(question::answer ### Creating a module from an `AST` -It is easy to convert a pre-compiled `AST` into a module, just use `Module::eval_ast_as_new`: +It is easy to convert a pre-compiled `AST` into a module, just use `Module::eval_ast_as_new`. +Don't forget the `export` statement, otherwise there will be nothing inside the module! ```rust use rhai::{Engine, Module}; @@ -2144,26 +2145,33 @@ let ast = engine.compile(r#" x + y.len() } - // Imported modules become sub-modules + // Imported modules can become sub-modules import "another module" as extra; - // Variables defined at global level become module variables + // Variables defined at global level can become module variables const x = 123; let foo = 41; let hello; - // Final variable values become constant module variable values + // Variable values become constant module variable values foo = calc(foo); hello = "hello, " + foo + " worlds!"; + + // Finally, export the variables and modules + export + x as abc, // aliased variable name + foo, + hello, + extra as foobar; // export sub-module "#)?; // Convert the 'AST' into a module, using the 'Engine' to evaluate it first let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; // 'module' now can be loaded into a custom 'Scope' for future use. It contains: -// - sub-module: 'extra' +// - sub-module: 'foobar' (renamed from 'extra') // - functions: 'calc', 'add_len' -// - variables: 'x', 'foo', 'hello' +// - variables: 'abc' (renamed from 'x'), 'foo', 'hello' ``` ### Module resolvers diff --git a/src/engine.rs b/src/engine.rs index 0832152b..6a52dcf4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -160,21 +160,15 @@ impl<'a> State<'a> { } /// Does a certain script-defined function exist in the `State`? pub fn has_function(&self, name: &str, params: usize) -> bool { - self.fn_lib.contains_key(&calc_fn_hash( - empty(), - name, - repeat(EMPTY_TYPE_ID()).take(params), - )) + // Qualifiers (none) + function name + placeholders (one for each parameter). + let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params)); + self.fn_lib.contains_key(&hash) } /// Get a script-defined function definition from the `State`. pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { - self.fn_lib - .get(&calc_fn_hash( - empty(), - name, - repeat(EMPTY_TYPE_ID()).take(params), - )) - .map(|f| f.as_ref()) + // Qualifiers (none) + function name + placeholders (one for each parameter). + let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params)); + self.fn_lib.get(&hash).map(|f| f.as_ref()) } } @@ -207,20 +201,21 @@ impl FunctionsLib { pub fn from_vec(vec: Vec) -> Self { FunctionsLib( vec.into_iter() - .map(|f| { + .map(|fn_def| { + // Qualifiers (none) + function name + placeholders (one for each parameter). let hash = calc_fn_hash( empty(), - &f.name, - repeat(EMPTY_TYPE_ID()).take(f.params.len()), + &fn_def.name, + repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()), ); #[cfg(feature = "sync")] { - (hash, Arc::new(f)) + (hash, Arc::new(fn_def)) } #[cfg(not(feature = "sync"))] { - (hash, Rc::new(f)) + (hash, Rc::new(fn_def)) } }) .collect(), @@ -240,6 +235,7 @@ impl FunctionsLib { } /// Get a function definition from the `FunctionsLib`. pub fn get_function_by_signature(&self, name: &str, params: usize) -> Option<&FnDef> { + // Qualifiers (none) + function name + placeholders (one for each parameter). let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params)); self.get_function(hash) } @@ -582,6 +578,7 @@ impl Engine { } // Search built-in's and external functions + // Qualifiers (none) + function name + argument `TypeId`'s. let fn_spec = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())); if let Some(func) = self @@ -731,6 +728,7 @@ impl Engine { // Has a system function an override? fn has_override(&self, state: &State, name: &str) -> bool { + // Qualifiers (none) + function name + argument `TypeId`'s. let hash = calc_fn_hash(empty(), name, once(TypeId::of::())); // First check registered functions @@ -1386,10 +1384,18 @@ impl Engine { self.call_script_fn(None, state, fn_def, &mut args, *pos, level) } else { // Then search in Rust functions - let hash1 = modules.key(); - let hash2 = calc_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); - match module.get_qualified_fn(fn_name, hash1 ^ hash2, *pos) { + // Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + dummy parameter types (one for each parameter). + let hash1 = modules.key(); + // 2) Calculate a second hash with no qualifiers, empty function name, and + // the actual list of parameter `TypeId`'.s + let hash2 = calc_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); + // 3) The final hash is the XOR of the two hashes. + let hash = hash1 ^ hash2; + + match module.get_qualified_fn(fn_name, hash, *pos) { Ok(func) => func(&mut args, *pos), Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), Err(err) => Err(err), diff --git a/src/module.rs b/src/module.rs index 10636ee8..ef74ec6a 100644 --- a/src/module.rs +++ b/src/module.rs @@ -618,41 +618,51 @@ impl Module { /// Scan through all the sub-modules in the `Module` build an index of all /// variables and external Rust functions via hashing. - pub(crate) fn collect_all_sub_modules(&mut self) { + pub(crate) fn index_all_sub_modules(&mut self) { // Collect a particular module. - fn collect<'a>( + fn index_module<'a>( module: &'a mut Module, - names: &mut Vec<&'a str>, + qualifiers: &mut Vec<&'a str>, variables: &mut Vec<(u64, Dynamic)>, functions: &mut Vec<(u64, NativeFunction)>, fn_lib: &mut Vec<(u64, ScriptedFunction)>, ) { - for (n, m) in module.modules.iter_mut() { - // Collect all the sub-modules first. - names.push(n); - collect(m, names, variables, functions, fn_lib); - names.pop(); + for (name, m) in module.modules.iter_mut() { + // Index all the sub-modules first. + qualifiers.push(name); + index_module(m, qualifiers, variables, functions, fn_lib); + qualifiers.pop(); } - // Collect all variables + // Index all variables for (var_name, value) in module.variables.iter() { - let hash = calc_fn_hash(names.iter().map(|v| *v), var_name, empty()); + // Qualifiers + variable name + let hash = calc_fn_hash(qualifiers.iter().map(|v| *v), var_name, empty()); variables.push((hash, value.clone())); } - // Collect all Rust functions + // Index all Rust functions for (fn_name, params, func) in module.functions.values() { + // Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + dummy parameter types (one for each parameter). let hash1 = calc_fn_hash( - names.iter().map(|v| *v), + qualifiers.iter().map(|v| *v), fn_name, repeat(EMPTY_TYPE_ID()).take(params.len()), ); + // 2) Calculate a second hash with no qualifiers, empty function name, and + // the actual list of parameter `TypeId`'.s let hash2 = calc_fn_hash(empty(), "", params.iter().cloned()); - functions.push((hash1 ^ hash2, func.clone())); + // 3) The final hash is the XOR of the two hashes. + let hash = hash1 ^ hash2; + + functions.push((hash, func.clone())); } - // Collect all script-defined functions + // Index all script-defined functions for fn_def in module.fn_lib.values() { + // Qualifiers + function name + placeholders (one for each parameter) let hash = calc_fn_hash( - names.iter().map(|v| *v), + qualifiers.iter().map(|v| *v), &fn_def.name, repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()), ); @@ -664,7 +674,7 @@ impl Module { let mut functions = Vec::new(); let mut fn_lib = Vec::new(); - collect( + index_module( self, &mut vec!["root"], &mut variables, diff --git a/src/parser.rs b/src/parser.rs index 274854b4..b3b05e1b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -716,9 +716,16 @@ fn parse_call_expr<'a>( #[cfg(not(feature = "no_module"))] { if let Some(modules) = modules.as_mut() { - // Calculate hash - let hash = calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty()); - modules.set_key(hash); + // Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + dummy parameter types (one for each parameter). + let hash1 = calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty()); + // 2) Calculate a second hash with no qualifiers, empty function name, and + // the actual list of parameter `TypeId`'.s + // 3) The final hash is the XOR of the two hashes. + + // Cache the first hash + modules.set_key(hash1); modules.set_index(stack.find_module(&modules.get(0).0)); } } @@ -745,13 +752,20 @@ fn parse_call_expr<'a>( #[cfg(not(feature = "no_module"))] { if let Some(modules) = modules.as_mut() { - // Calculate hash - let hash = calc_fn_hash( + // Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + dummy parameter types (one for each parameter). + let hash1 = calc_fn_hash( modules.iter().map(|(m, _)| m.as_str()), &id, repeat(EMPTY_TYPE_ID()).take(args.len()), ); - modules.set_key(hash); + // 2) Calculate a second hash with no qualifiers, empty function name, and + // the actual list of parameter `TypeId`'.s + // 3) The final hash is the XOR of the two hashes. + + // Cache the first hash + modules.set_key(hash1); modules.set_index(stack.find_module(&modules.get(0).0)); } } @@ -1176,9 +1190,10 @@ fn parse_primary<'a>( } match &mut root_expr { - // Calculate hash key for module-qualified variables + // Cache the hash key for module-qualified variables #[cfg(not(feature = "no_module"))] Expr::Variable(id, Some(modules), _, _) => { + // Qualifiers + variable name let hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), id, empty()); modules.set_key(hash); modules.set_index(stack.find_module(&modules.get(0).0)); @@ -2317,15 +2332,16 @@ fn parse_global_level<'a>( { if let (Token::Fn, _) = input.peek().unwrap() { let mut stack = Stack::new(); - let f = parse_fn(input, &mut stack, true)?; - functions.insert( - calc_fn_hash( - empty(), - &f.name, - repeat(EMPTY_TYPE_ID()).take(f.params.len()), - ), - f, + let func = parse_fn(input, &mut stack, true)?; + + // Qualifiers (none) + function name + argument `TypeId`'s + let hash = calc_fn_hash( + empty(), + &func.name, + repeat(EMPTY_TYPE_ID()).take(func.params.len()), ); + + functions.insert(hash, func); continue; } } diff --git a/src/scope.rs b/src/scope.rs index c15c0581..04e11fe6 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -178,7 +178,7 @@ impl<'a> Scope<'a> { /// Modules are used for accessing member variables, functions and plugins under a namespace. #[cfg(not(feature = "no_module"))] pub fn push_module>>(&mut self, name: K, mut value: Module) { - value.collect_all_sub_modules(); + value.index_all_sub_modules(); self.push_dynamic_value( name, diff --git a/tests/modules.rs b/tests/modules.rs index 4876e9fb..7ec17483 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -115,7 +115,11 @@ fn test_module_from_ast() -> Result<(), Box> { foo = calc(foo); hello = "hello, " + foo + " worlds!"; - export x as abc, foo, hello, extra as foobar; + export + x as abc, + foo, + hello, + extra as foobar; "#, )?;