Build Module type plus engine hooks.

This commit is contained in:
Stephen Chung 2020-05-05 15:00:10 +08:00
parent c03b162b7e
commit 38e717a838
14 changed files with 249 additions and 134 deletions

View File

@ -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!("==============");

View File

@ -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, "<dynamic>"),
}
}

View File

@ -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<Dynamic, Box<EvalAltResult>> {
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<EvalAltResult>> {
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<T, Box<EvalAltResult>> {
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!`)

View File

@ -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<u64, Arc<FnDef>>,
#[cfg(not(feature = "sync"))] HashMap<u64, Rc<FnDef>>,
@ -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<FnDef>) -> 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<EvalAltResult>> {
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::<Module>())
.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::<Module>() {
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::<String>()
{
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();

View File

@ -128,7 +128,7 @@ pub fn by_ref<T: Clone + 'static>(data: &mut Dynamic) -> &mut T {
pub fn by_value<T: Clone + 'static>(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::<T>()
mem::take(data).cast::<T>()
}
/// This macro counts the number of arguments via recursion.

View File

@ -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;

View File

@ -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<String, Dynamic>);
#[derive(Default)]
pub struct Module {
/// Sub-modules.
modules: HashMap<String, Module>,
/// Module variables, including sub-modules.
variables: HashMap<String, Dynamic>,
/// External Rust functions.
functions: HashMap<u64, Box<FnAny>>,
/// Script-defined functions.
lib: FunctionsLib,
}
impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"<module {:?}, functions={}, lib={}>",
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<String, Dynamic>;
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<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.get_variable(name).and_then(|v| v.try_cast::<T>())
}
/// Get a module variable.
pub fn get_variable(&self, name: &str) -> Option<Dynamic> {
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<K: Into<String>, T: Into<Dynamic>>(&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<EvalAltResult>> {
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<K: Into<String>>(&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<EvalAltResult>> {
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)
}
}

View File

@ -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,
)
}

View File

@ -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<u64, Box<FnAny>>,
@ -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()
}
}

View File

@ -51,17 +51,44 @@ pub type ModuleRef = Option<Box<StaticVec<(String, Position)>>>;
/// 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<Stmt>,
#[cfg(feature = "sync")] pub(crate) Arc<FunctionsLib>,
#[cfg(not(feature = "sync"))] pub(crate) Rc<FunctionsLib>,
/// Global statements.
Vec<Stmt>,
/// Script-defined functions, wrapped in an `Arc` for shared access.
#[cfg(feature = "sync")]
Arc<FunctionsLib>,
/// Script-defined functions, wrapped in an `Rc` for shared access.
#[cfg(not(feature = "sync"))]
Rc<FunctionsLib>,
);
impl AST {
/// Create a new `AST`.
pub fn new() -> Self {
Default::default()
pub fn new(statements: Vec<Stmt>, 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<Stmt> {
&self.0
}
/// Get a mutable reference to the statements.
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> {
&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<Self> 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`,

View File

@ -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<Entry<'a>>);
impl<'a> Scope<'a> {
@ -77,7 +77,7 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("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<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> {
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) {
self.0

View File

@ -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)
}
}

View File

@ -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<T: Default + Clone> {
/// Total number of values held.
len: usize,
@ -57,16 +58,7 @@ pub struct StaticVec<T: Default + Clone> {
impl<T: Default + Clone> StaticVec<T> {
/// 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<X: Into<T>>(&mut self, value: X) {
@ -86,7 +78,7 @@ impl<T: Default + Clone> StaticVec<T> {
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<T: Default + Clone> StaticVec<T> {
self.list[..num].iter().chain(self.more.iter())
}
}
impl<T: Default + Clone + fmt::Debug> fmt::Debug for StaticVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ ")?;
self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?;
write!(f, "]")
}
}

View File

@ -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::<INT>("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::<INT>("kitty").unwrap(), 42);
}