Add comments and fix documentation.

This commit is contained in:
Stephen Chung 2020-05-09 10:00:59 +08:00
parent e50fcc385f
commit d1de84fdd2
6 changed files with 103 additions and 59 deletions

View File

@ -2127,7 +2127,8 @@ engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer
### Creating a module from an `AST` ### 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 ```rust
use rhai::{Engine, Module}; use rhai::{Engine, Module};
@ -2144,26 +2145,33 @@ let ast = engine.compile(r#"
x + y.len() x + y.len()
} }
// Imported modules become sub-modules // Imported modules can become sub-modules
import "another module" as extra; 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; const x = 123;
let foo = 41; let foo = 41;
let hello; let hello;
// Final variable values become constant module variable values // Variable values become constant module variable values
foo = calc(foo); foo = calc(foo);
hello = "hello, " + foo + " worlds!"; 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 // Convert the 'AST' into a module, using the 'Engine' to evaluate it first
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; 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: // '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' // - functions: 'calc', 'add_len'
// - variables: 'x', 'foo', 'hello' // - variables: 'abc' (renamed from 'x'), 'foo', 'hello'
``` ```
### Module resolvers ### Module resolvers

View File

@ -160,21 +160,15 @@ impl<'a> State<'a> {
} }
/// Does a certain script-defined function exist in the `State`? /// Does a certain script-defined function exist in the `State`?
pub fn has_function(&self, name: &str, params: usize) -> bool { pub fn has_function(&self, name: &str, params: usize) -> bool {
self.fn_lib.contains_key(&calc_fn_hash( // Qualifiers (none) + function name + placeholders (one for each parameter).
empty(), let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params));
name, self.fn_lib.contains_key(&hash)
repeat(EMPTY_TYPE_ID()).take(params),
))
} }
/// Get a script-defined function definition from the `State`. /// Get a script-defined function definition from the `State`.
pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> {
self.fn_lib // Qualifiers (none) + function name + placeholders (one for each parameter).
.get(&calc_fn_hash( let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params));
empty(), self.fn_lib.get(&hash).map(|f| f.as_ref())
name,
repeat(EMPTY_TYPE_ID()).take(params),
))
.map(|f| f.as_ref())
} }
} }
@ -207,20 +201,21 @@ impl FunctionsLib {
pub fn from_vec(vec: Vec<FnDef>) -> Self { pub fn from_vec(vec: Vec<FnDef>) -> Self {
FunctionsLib( FunctionsLib(
vec.into_iter() vec.into_iter()
.map(|f| { .map(|fn_def| {
// Qualifiers (none) + function name + placeholders (one for each parameter).
let hash = calc_fn_hash( let hash = calc_fn_hash(
empty(), empty(),
&f.name, &fn_def.name,
repeat(EMPTY_TYPE_ID()).take(f.params.len()), repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
); );
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
{ {
(hash, Arc::new(f)) (hash, Arc::new(fn_def))
} }
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ {
(hash, Rc::new(f)) (hash, Rc::new(fn_def))
} }
}) })
.collect(), .collect(),
@ -240,6 +235,7 @@ impl FunctionsLib {
} }
/// Get a function definition from the `FunctionsLib`. /// Get a function definition from the `FunctionsLib`.
pub fn get_function_by_signature(&self, name: &str, params: usize) -> Option<&FnDef> { 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)); let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params));
self.get_function(hash) self.get_function(hash)
} }
@ -582,6 +578,7 @@ impl Engine {
} }
// Search built-in's and external functions // 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())); let fn_spec = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
if let Some(func) = self if let Some(func) = self
@ -731,6 +728,7 @@ impl Engine {
// Has a system function an override? // Has a system function an override?
fn has_override(&self, state: &State, name: &str) -> bool { 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::<String>())); let hash = calc_fn_hash(empty(), name, once(TypeId::of::<String>()));
// First check registered functions // First check registered functions
@ -1386,10 +1384,18 @@ impl Engine {
self.call_script_fn(None, state, fn_def, &mut args, *pos, level) self.call_script_fn(None, state, fn_def, &mut args, *pos, level)
} else { } else {
// Then search in Rust functions // 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), Ok(func) => func(&mut args, *pos),
Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()),
Err(err) => Err(err), Err(err) => Err(err),

View File

@ -618,41 +618,51 @@ impl Module {
/// Scan through all the sub-modules in the `Module` build an index of all /// Scan through all the sub-modules in the `Module` build an index of all
/// variables and external Rust functions via hashing. /// 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. // Collect a particular module.
fn collect<'a>( fn index_module<'a>(
module: &'a mut Module, module: &'a mut Module,
names: &mut Vec<&'a str>, qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>, variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, NativeFunction)>, functions: &mut Vec<(u64, NativeFunction)>,
fn_lib: &mut Vec<(u64, ScriptedFunction)>, fn_lib: &mut Vec<(u64, ScriptedFunction)>,
) { ) {
for (n, m) in module.modules.iter_mut() { for (name, m) in module.modules.iter_mut() {
// Collect all the sub-modules first. // Index all the sub-modules first.
names.push(n); qualifiers.push(name);
collect(m, names, variables, functions, fn_lib); index_module(m, qualifiers, variables, functions, fn_lib);
names.pop(); qualifiers.pop();
} }
// Collect all variables // Index all variables
for (var_name, value) in module.variables.iter() { 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())); variables.push((hash, value.clone()));
} }
// Collect all Rust functions // Index all Rust functions
for (fn_name, params, func) in module.functions.values() { 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( let hash1 = calc_fn_hash(
names.iter().map(|v| *v), qualifiers.iter().map(|v| *v),
fn_name, fn_name,
repeat(EMPTY_TYPE_ID()).take(params.len()), 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()); 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() { for fn_def in module.fn_lib.values() {
// Qualifiers + function name + placeholders (one for each parameter)
let hash = calc_fn_hash( let hash = calc_fn_hash(
names.iter().map(|v| *v), qualifiers.iter().map(|v| *v),
&fn_def.name, &fn_def.name,
repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()), repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
); );
@ -664,7 +674,7 @@ impl Module {
let mut functions = Vec::new(); let mut functions = Vec::new();
let mut fn_lib = Vec::new(); let mut fn_lib = Vec::new();
collect( index_module(
self, self,
&mut vec!["root"], &mut vec!["root"],
&mut variables, &mut variables,

View File

@ -716,9 +716,16 @@ fn parse_call_expr<'a>(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
if let Some(modules) = modules.as_mut() { if let Some(modules) = modules.as_mut() {
// Calculate hash // Rust functions are indexed in two steps:
let hash = calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty()); // 1) Calculate a hash in a similar manner to script-defined functions,
modules.set_key(hash); // 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)); modules.set_index(stack.find_module(&modules.get(0).0));
} }
} }
@ -745,13 +752,20 @@ fn parse_call_expr<'a>(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
if let Some(modules) = modules.as_mut() { if let Some(modules) = modules.as_mut() {
// Calculate hash // Rust functions are indexed in two steps:
let hash = calc_fn_hash( // 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()), modules.iter().map(|(m, _)| m.as_str()),
&id, &id,
repeat(EMPTY_TYPE_ID()).take(args.len()), 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)); modules.set_index(stack.find_module(&modules.get(0).0));
} }
} }
@ -1176,9 +1190,10 @@ fn parse_primary<'a>(
} }
match &mut root_expr { match &mut root_expr {
// Calculate hash key for module-qualified variables // Cache the hash key for module-qualified variables
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Expr::Variable(id, Some(modules), _, _) => { Expr::Variable(id, Some(modules), _, _) => {
// Qualifiers + variable name
let hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), id, empty()); let hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), id, empty());
modules.set_key(hash); modules.set_key(hash);
modules.set_index(stack.find_module(&modules.get(0).0)); 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() { if let (Token::Fn, _) = input.peek().unwrap() {
let mut stack = Stack::new(); let mut stack = Stack::new();
let f = parse_fn(input, &mut stack, true)?; let func = parse_fn(input, &mut stack, true)?;
functions.insert(
calc_fn_hash( // Qualifiers (none) + function name + argument `TypeId`'s
empty(), let hash = calc_fn_hash(
&f.name, empty(),
repeat(EMPTY_TYPE_ID()).take(f.params.len()), &func.name,
), repeat(EMPTY_TYPE_ID()).take(func.params.len()),
f,
); );
functions.insert(hash, func);
continue; continue;
} }
} }

View File

@ -178,7 +178,7 @@ impl<'a> Scope<'a> {
/// Modules are used for accessing member variables, functions and plugins under a namespace. /// Modules are used for accessing member variables, functions and plugins under a namespace.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) { pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) {
value.collect_all_sub_modules(); value.index_all_sub_modules();
self.push_dynamic_value( self.push_dynamic_value(
name, name,

View File

@ -115,7 +115,11 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
foo = calc(foo); foo = calc(foo);
hello = "hello, " + foo + " worlds!"; hello = "hello, " + foo + " worlds!";
export x as abc, foo, hello, extra as foobar; export
x as abc,
foo,
hello,
extra as foobar;
"#, "#,
)?; )?;