From 38e717a838635169db511e2851baeb1cc289cbb5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 5 May 2020 15:00:10 +0800 Subject: [PATCH] Build Module type plus engine hooks. --- examples/repl.rs | 6 +- src/any.rs | 4 +- src/api.rs | 19 +++--- src/engine.rs | 70 ++++++++-------------- src/fn_register.rs | 2 +- src/lib.rs | 3 + src/module.rs | 137 ++++++++++++++++++++++++++++++++++++++------ src/optimize.rs | 9 +-- src/packages/mod.rs | 6 +- src/parser.rs | 55 +++++++++++------- src/scope.rs | 12 +--- src/token.rs | 2 +- src/utils.rs | 24 ++++---- tests/modules.rs | 34 ++++++++++- 14 files changed, 249 insertions(+), 134 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index d2281b6a..af26d593 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -68,9 +68,9 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); - let mut main_ast = AST::new(); - let mut ast_u = AST::new(); - let mut ast = AST::new(); + let mut main_ast: AST = Default::default(); + let mut ast_u: AST = Default::default(); + let mut ast: AST = Default::default(); println!("Rhai REPL tool"); println!("=============="); diff --git a/src/any.rs b/src/any.rs index b87f2d50..4ae08f38 100644 --- a/src/any.rs +++ b/src/any.rs @@ -205,7 +205,7 @@ impl fmt::Display for Dynamic { Union::Float(value) => write!(f, "{}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), - Union::Module(value) => write!(f, "#{:?}", value), + Union::Module(value) => write!(f, "{:?}", value), Union::Variant(_) => write!(f, "?"), } } @@ -223,7 +223,7 @@ impl fmt::Debug for Dynamic { Union::Float(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "#{:?}", value), - Union::Module(value) => write!(f, "#{:?}", value), + Union::Module(value) => write!(f, "{:?}", value), Union::Variant(_) => write!(f, ""), } } diff --git a/src/api.rs b/src/api.rs index 9377bc68..03da48cb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,6 +15,7 @@ use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, collections::HashMap, + mem, string::{String, ToString}, vec::Vec, }; @@ -797,10 +798,10 @@ impl Engine { ) -> Result> { let mut state = State::new(); - ast.0 + ast.statements() .iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0) + self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) }) .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), @@ -862,10 +863,10 @@ impl Engine { ) -> Result<(), Box> { let mut state = State::new(); - ast.0 + ast.statements() .iter() .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0) + self.eval_stmt(scope, &mut state, ast.fn_lib(), stmt, 0) }) .map_or_else( |err| match *err { @@ -921,7 +922,7 @@ impl Engine { ) -> Result> { let mut arg_values = args.into_vec(); let mut args: Vec<_> = arg_values.iter_mut().collect(); - let fn_lib = ast.1.as_ref(); + let fn_lib = ast.fn_lib(); let pos = Position::none(); let fn_def = fn_lib @@ -955,15 +956,17 @@ impl Engine { pub fn optimize_ast( &self, scope: &Scope, - ast: AST, + mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { let fn_lib = ast - .1 + .fn_lib() .iter() .map(|(_, fn_def)| fn_def.as_ref().clone()) .collect(); - optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) + + let stmt = mem::take(ast.statements_mut()); + optimize_into_ast(self, scope, stmt, fn_lib, optimization_level) } /// Override default action of `print` (print to stdout using `println!`) diff --git a/src/engine.rs b/src/engine.rs index 6a1853d2..5be305ac 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,7 +6,7 @@ use crate::error::ParseErrorType; use crate::module::Module; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; -use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt}; +use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -159,7 +159,7 @@ impl State { /// and number of parameters are considered equivalent. /// /// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_def`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct FunctionsLib( #[cfg(feature = "sync")] HashMap>, #[cfg(not(feature = "sync"))] HashMap>, @@ -168,7 +168,7 @@ pub struct FunctionsLib( impl FunctionsLib { /// Create a new `FunctionsLib`. pub fn new() -> Self { - FunctionsLib(HashMap::new()) + Default::default() } /// Create a new `FunctionsLib` from a collection of `FnDef`. pub fn from_vec(vec: Vec) -> Self { @@ -289,10 +289,10 @@ impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Self { - packages: Vec::new(), + packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT), - type_iterators: HashMap::new(), - type_names: HashMap::new(), + type_iterators: Default::default(), + type_names: Default::default(), // default print/debug implementations print: Box::new(default_print), @@ -380,10 +380,9 @@ fn search_scope<'a>( pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { if let Some(modules) = modules { - let mut drain = modules.iter(); - let (id, root_pos) = drain.next().unwrap(); // First module + let (id, root_pos) = modules.get(0); // First module - let mut module = if let Some(index) = index { + let module = if let Some(index) = index { scope .get_mut(scope.len() - index.get()) .0 @@ -395,26 +394,11 @@ fn search_scope<'a>( .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))? }; - for (id, id_pos) in drain { - module = module - .get_mut(id) - .and_then(|v| v.downcast_mut::()) - .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; - } - - let result = module - .get_mut(name) - .map(|v| (v, ScopeEntryType::Constant)) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?; - - if result.0.is::() { - Err(Box::new(EvalAltResult::ErrorVariableNotFound( - name.into(), - pos, - ))) - } else { - Ok(result) - } + Ok(( + module.get_qualified_variable_mut(name, modules.as_ref(), pos)?, + // Module variables are constant + ScopeEntryType::Constant, + )) } else { let index = if let Some(index) = index { scope.len() - index.get() @@ -439,10 +423,10 @@ impl Engine { /// Use the `load_package` method to load packages of functions. pub fn new_raw() -> Self { Self { - packages: Vec::new(), + packages: Default::default(), functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), - type_iterators: HashMap::new(), - type_names: HashMap::new(), + type_iterators: Default::default(), + type_names: Default::default(), print: Box::new(|_| {}), debug: Box::new(|_| {}), @@ -595,8 +579,7 @@ impl Engine { .iter() .zip( // Actually consume the arguments instead of cloning them - args.into_iter() - .map(|v| mem::replace(*v, Default::default())), + args.into_iter().map(|v| mem::take(*v)), ) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); @@ -626,8 +609,7 @@ impl Engine { .iter() .zip( // Actually consume the arguments instead of cloning them - args.into_iter() - .map(|v| mem::replace(*v, Default::default())), + args.into_iter().map(|v| mem::take(*v)), ) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); @@ -715,20 +697,14 @@ impl Engine { )?; // If new functions are defined within the eval string, it is an error - if ast.1.len() > 0 { + if ast.fn_lib().len() > 0 { return Err(Box::new(EvalAltResult::ErrorParsing( ParseErrorType::WrongFnDefinition.into_err(pos), ))); } - #[cfg(feature = "sync")] - { - ast.1 = Arc::new(fn_lib.clone()); - } - #[cfg(not(feature = "sync"))] - { - ast.1 = Rc::new(fn_lib.clone()); - } + let statements = mem::take(ast.statements_mut()); + ast = AST::new(statements, fn_lib.clone()); // Evaluate the AST self.eval_ast_with_scope_raw(scope, &ast) @@ -1496,8 +1472,8 @@ impl Engine { .try_cast::() { let mut module = Module::new(); - module.insert("kitty".to_string(), "foo".to_string().into()); - module.insert("path".to_string(), path.into()); + module.set_variable("kitty", "foo".to_string()); + module.set_variable("path", path); // TODO - avoid copying module name in inner block? let mod_name = name.as_ref().clone(); diff --git a/src/fn_register.rs b/src/fn_register.rs index 197cf6cc..9f798af1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -128,7 +128,7 @@ pub fn by_ref(data: &mut Dynamic) -> &mut T { pub fn by_value(data: &mut Dynamic) -> T { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. - mem::replace(data, Default::default()).cast::() + mem::take(data).cast::() } /// This macro counts the number of arguments via recursion. diff --git a/src/lib.rs b/src/lib.rs index 04e4ebb5..ddcdee28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,5 +109,8 @@ pub use engine::Map; #[cfg(not(feature = "no_float"))] pub use parser::FLOAT; +#[cfg(not(feature = "no_module"))] +pub use module::Module; + #[cfg(not(feature = "no_optimize"))] pub use optimize::OptimizationLevel; diff --git a/src/module.rs b/src/module.rs index 4b62097b..d7c48d3c 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,35 +1,134 @@ //! Module defining external-loaded modules for Rhai. -use crate::any::Dynamic; +use crate::any::{Dynamic, Variant}; +use crate::engine::{FnAny, FunctionsLib}; +use crate::result::EvalAltResult; +use crate::token::Position; +use crate::utils::StaticVec; -use crate::stdlib::{ - collections::HashMap, - ops::{Deref, DerefMut}, - string::String, -}; +use crate::stdlib::{collections::HashMap, fmt, string::String}; -/// An imported module. +/// An imported module, which may contain variables, sub-modules, +/// external Rust functions, and script-defined functions. /// /// Not available under the `no_module` feature. -#[derive(Debug, Clone)] -pub struct Module(HashMap); +#[derive(Default)] +pub struct Module { + /// Sub-modules. + modules: HashMap, + /// Module variables, including sub-modules. + variables: HashMap, + /// External Rust functions. + functions: HashMap>, + /// Script-defined functions. + lib: FunctionsLib, +} + +impl fmt::Debug for Module { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "", + self.variables, + self.functions.len(), + self.lib.len() + ) + } +} + +impl Clone for Module { + fn clone(&self) -> Self { + // `Module` implements `Clone` so it can fit inside a `Dynamic` + // but we should never actually clone it. + unimplemented!() + } +} impl Module { /// Create a new module. pub fn new() -> Self { - Self(HashMap::new()) + Default::default() } -} -impl Deref for Module { - type Target = HashMap; - fn deref(&self) -> &Self::Target { - &self.0 + /// Does a variable exist in the module? + pub fn contains_variable(&self, name: &str) -> bool { + self.variables.contains_key(name) } -} -impl DerefMut for Module { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + /// 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::()) + } + + /// Get a module variable. + pub fn get_variable(&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> { + 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) { + self.variables.insert(name.into(), value.into()); + } + + /// Get a mutable reference to a modules-qualified variable. + pub(crate) fn get_qualified_variable_mut( + &mut self, + name: &str, + modules: &StaticVec<(String, Position)>, + pos: Position, + ) -> Result<&mut Dynamic, Box> { + Ok(self + .get_qualified_module_mut(modules)? + .get_variable_mut(name) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?) + } + + /// Does a sub-module exist in the module? + pub fn contains_sub_module(&self, name: &str) -> bool { + self.modules.contains_key(name) + } + + /// Get a sub-module. + pub fn get_sub_module(&self, name: &str) -> Option<&Module> { + self.modules.get(name) + } + + /// Get a mutable reference to a sub-module. + pub fn get_sub_module_mut(&mut self, name: &str) -> Option<&mut Module> { + self.modules.get_mut(name) + } + + /// Set a sub-module into the module. + /// + /// If there is an existing sub-module of the same name, it is replaced. + pub fn set_sub_module>(&mut self, name: K, sub_module: Module) { + self.modules.insert(name.into(), sub_module.into()); + } + + /// Get a mutable reference to a modules chain. + /// The first module is always skipped and assumed to be the same as `self`. + pub(crate) fn get_qualified_module_mut( + &mut self, + modules: &StaticVec<(String, Position)>, + ) -> Result<&mut Module, Box> { + let mut drain = modules.iter(); + drain.next().unwrap(); // Skip first module + + let mut module = self; + + for (id, id_pos) in drain { + module = module + .get_sub_module_mut(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?; + } + + Ok(module) } } diff --git a/src/optimize.rs b/src/optimize.rs index 29d2f7fe..a2dc0048 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -13,9 +13,7 @@ use crate::token::Position; use crate::stdlib::{ boxed::Box, collections::HashMap, - rc::Rc, string::{String, ToString}, - sync::Arc, vec, vec::Vec, }; @@ -747,16 +745,13 @@ pub fn optimize_into_ast( .collect(), ); - AST( + AST::new( match level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { optimize(statements, engine, &scope, &fn_lib, level) } }, - #[cfg(feature = "sync")] - Arc::new(lib), - #[cfg(not(feature = "sync"))] - Rc::new(lib), + lib, ) } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 54c3f177..3734bd25 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -47,6 +47,7 @@ pub trait Package { } /// Type to store all functions in the package. +#[derive(Default)] pub struct PackageStore { /// All functions, keyed by a hash created from the function name and parameter types. pub functions: HashMap>, @@ -58,10 +59,7 @@ pub struct PackageStore { impl PackageStore { /// Create a new `PackageStore`. pub fn new() -> Self { - Self { - functions: HashMap::new(), - type_iterators: HashMap::new(), - } + Default::default() } } diff --git a/src/parser.rs b/src/parser.rs index 707b9809..9cac3bef 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -51,17 +51,44 @@ pub type ModuleRef = Option>>; /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct AST( - pub(crate) Vec, - #[cfg(feature = "sync")] pub(crate) Arc, - #[cfg(not(feature = "sync"))] pub(crate) Rc, + /// Global statements. + Vec, + /// Script-defined functions, wrapped in an `Arc` for shared access. + #[cfg(feature = "sync")] + Arc, + /// Script-defined functions, wrapped in an `Rc` for shared access. + #[cfg(not(feature = "sync"))] + Rc, ); impl AST { /// Create a new `AST`. - pub fn new() -> Self { - Default::default() + pub fn new(statements: Vec, fn_lib: FunctionsLib) -> Self { + #[cfg(feature = "sync")] + { + Self(statements, Arc::new(fn_lib)) + } + #[cfg(not(feature = "sync"))] + { + Self(statements, Rc::new(fn_lib)) + } + } + + /// Get the statements. + pub(crate) fn statements(&self) -> &Vec { + &self.0 + } + + /// Get a mutable reference to the statements. + pub(crate) fn statements_mut(&mut self) -> &mut Vec { + &mut self.0 + } + + /// Get the script-defined functions. + pub(crate) fn fn_lib(&self) -> &FunctionsLib { + self.1.as_ref() } /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version @@ -148,18 +175,6 @@ impl AST { } } -impl Default for AST { - fn default() -> Self { - #[cfg(feature = "sync")] - { - Self(vec![], Arc::new(FunctionsLib::new())) - } - #[cfg(not(feature = "sync"))] - { - Self(vec![], Rc::new(FunctionsLib::new())) - } - } -} impl Add for &AST { type Output = AST; @@ -191,13 +206,13 @@ pub enum ReturnType { } /// A type that encapsulates a local stack with variable names to simulate an actual runtime scope. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct Stack(Vec<(String, ScopeEntryType)>); impl Stack { /// Create a new `Stack`. pub fn new() -> Self { - Self(Vec::new()) + Default::default() } /// Find a variable by name in the `Stack`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, diff --git a/src/scope.rs b/src/scope.rs index da90f72a..56a0e6fb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -60,7 +60,7 @@ pub struct Entry<'a> { /// allowing for automatic _shadowing_. /// /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { @@ -77,7 +77,7 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn new() -> Self { - Self(Vec::new()) + Default::default() } /// Empty the Scope. @@ -177,7 +177,7 @@ impl<'a> Scope<'a> { name, EntryType::Module, Dynamic(Union::Module(Box::new(value))), - true, + false, ); } @@ -422,12 +422,6 @@ impl<'a> Scope<'a> { } } -impl Default for Scope<'_> { - fn default() -> Self { - Scope::new() - } -} - impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { fn extend>(&mut self, iter: T) { self.0 diff --git a/src/token.rs b/src/token.rs index 1a303810..29e7ce87 100644 --- a/src/token.rs +++ b/src/token.rs @@ -122,7 +122,7 @@ impl fmt::Display for Position { impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({}:{})", self.line, self.pos) + write!(f, "{}:{}", self.line, self.pos) } } diff --git a/src/utils.rs b/src/utils.rs index 4f51c7aa..4a9479f1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ use crate::stdlib::{ any::TypeId, + fmt, hash::{Hash, Hasher}, mem, vec::Vec, @@ -44,7 +45,7 @@ pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 { /// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This simplified implementation here is to avoid pulling in another crate. -#[derive(Debug, Clone)] +#[derive(Clone, Default)] pub struct StaticVec { /// Total number of values held. len: usize, @@ -57,16 +58,7 @@ pub struct StaticVec { impl StaticVec { /// Create a new `StaticVec`. pub fn new() -> Self { - Self { - len: 0, - list: [ - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ], - more: Vec::new(), - } + Default::default() } /// Push a new value to the end of this `StaticVec`. pub fn push>(&mut self, value: X) { @@ -86,7 +78,7 @@ impl StaticVec { let result = if self.len <= 0 { panic!("nothing to pop!") } else if self.len <= self.list.len() { - mem::replace(self.list.get_mut(self.len - 1).unwrap(), Default::default()) + mem::take(self.list.get_mut(self.len - 1).unwrap()) } else { self.more.pop().unwrap() }; @@ -126,3 +118,11 @@ impl StaticVec { self.list[..num].iter().chain(self.more.iter()) } } + +impl fmt::Debug for StaticVec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ ")?; + self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; + write!(f, "]") + } +} diff --git a/tests/modules.rs b/tests/modules.rs index 5046791d..4f17d635 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1 +1,33 @@ -use rhai::{EvalAltResult, Scope, INT}; +#![cfg(not(feature = "no_module"))] +use rhai::{EvalAltResult, Module, Scope, INT}; + +#[test] +fn test_module() { + let mut module = Module::new(); + module.set_variable("kitty", 42 as INT); + + assert!(module.contains_variable("kitty")); + assert_eq!(module.get_variable_value::("kitty").unwrap(), 42); +} + +#[test] +fn test_sub_module() { + 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_module.set_sub_module("world", sub_module2); + module.set_sub_module("hello", sub_module); + + assert!(module.contains_sub_module("hello")); + let m = module.get_sub_module("hello").unwrap(); + + assert!(m.contains_sub_module("world")); + let m2 = m.get_sub_module("world").unwrap(); + + assert!(m2.contains_variable("kitty")); + assert_eq!(m2.get_variable_value::("kitty").unwrap(), 42); +}