Rename sub-scope/SubScope to module.

This commit is contained in:
Stephen Chung 2020-05-05 10:39:12 +08:00
parent 64036f69ca
commit 143861747d
10 changed files with 162 additions and 201 deletions

View File

@ -28,7 +28,7 @@ no_float = [] # no floating-point
no_function = [] # no script-defined functions no_function = [] # no script-defined functions
no_object = [] # no custom objects no_object = [] # no custom objects
no_optimize = [] # no script optimizer no_optimize = [] # no script optimizer
no_import = [] # no modules no_module = [] # no modules
only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
sync = [] # restrict to only types that implement Send + Sync sync = [] # restrict to only types that implement Send + Sync

View File

@ -70,7 +70,7 @@ Optional features
| `no_object` | Disable support for custom types and objects. | | `no_object` | Disable support for custom types and objects. |
| `no_float` | Disable floating-point numbers and math if not needed. | | `no_float` | Disable floating-point numbers and math if not needed. |
| `no_optimize` | Disable the script optimizer. | | `no_optimize` | Disable the script optimizer. |
| `no_import` | Disable modules. | | `no_module` | Disable modules. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
@ -86,7 +86,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
[`no_function`]: #optional-features [`no_function`]: #optional-features
[`no_object`]: #optional-features [`no_object`]: #optional-features
[`no_optimize`]: #optional-features [`no_optimize`]: #optional-features
[`no_import`]: #optional-features [`no_module`]: #optional-features
[`only_i32`]: #optional-features [`only_i32`]: #optional-features
[`only_i64`]: #optional-features [`only_i64`]: #optional-features
[`no_std`]: #optional-features [`no_std`]: #optional-features

View File

@ -1,6 +1,6 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::engine::{Array, Map, SubScope}; use crate::engine::{Array, Map, Module};
use crate::parser::INT; use crate::parser::INT;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -135,7 +135,7 @@ pub enum Union {
Float(FLOAT), Float(FLOAT),
Array(Box<Array>), Array(Box<Array>),
Map(Box<Map>), Map(Box<Map>),
SubScope(Box<SubScope>), Module(Box<Module>),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>),
} }
@ -166,7 +166,7 @@ impl Dynamic {
Union::Float(_) => TypeId::of::<FLOAT>(), Union::Float(_) => TypeId::of::<FLOAT>(),
Union::Array(_) => TypeId::of::<Array>(), Union::Array(_) => TypeId::of::<Array>(),
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_) => TypeId::of::<Map>(),
Union::SubScope(_) => TypeId::of::<SubScope>(), Union::Module(_) => TypeId::of::<Module>(),
Union::Variant(value) => (***value).type_id(), Union::Variant(value) => (***value).type_id(),
} }
} }
@ -183,7 +183,7 @@ impl Dynamic {
Union::Float(_) => type_name::<FLOAT>(), Union::Float(_) => type_name::<FLOAT>(),
Union::Array(_) => "array", Union::Array(_) => "array",
Union::Map(_) => "map", Union::Map(_) => "map",
Union::SubScope(_) => "sub-scope", Union::Module(_) => "sub-scope",
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp", Union::Variant(value) if value.is::<Instant>() => "timestamp",
@ -204,7 +204,7 @@ impl fmt::Display for Dynamic {
Union::Float(value) => write!(f, "{}", value), Union::Float(value) => write!(f, "{}", value),
Union::Array(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value),
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => write!(f, "#{:?}", value),
Union::SubScope(value) => write!(f, "#{:?}", value), Union::Module(value) => write!(f, "#{:?}", value),
Union::Variant(_) => write!(f, "?"), Union::Variant(_) => write!(f, "?"),
} }
} }
@ -222,7 +222,7 @@ impl fmt::Debug for Dynamic {
Union::Float(value) => write!(f, "{:?}", value), Union::Float(value) => write!(f, "{:?}", value),
Union::Array(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value),
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => write!(f, "#{:?}", value),
Union::SubScope(value) => write!(f, "#{:?}", value), Union::Module(value) => write!(f, "#{:?}", value),
Union::Variant(_) => write!(f, "<dynamic>"), Union::Variant(_) => write!(f, "<dynamic>"),
} }
} }
@ -240,7 +240,7 @@ impl Clone for Dynamic {
Union::Float(value) => Self(Union::Float(value)), Union::Float(value) => Self(Union::Float(value)),
Union::Array(ref value) => Self(Union::Array(value.clone())), Union::Array(ref value) => Self(Union::Array(value.clone())),
Union::Map(ref value) => Self(Union::Map(value.clone())), Union::Map(ref value) => Self(Union::Map(value.clone())),
Union::SubScope(ref value) => Self(Union::SubScope(value.clone())), Union::Module(ref value) => Self(Union::Module(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(), Union::Variant(ref value) => (***value).clone_into_dynamic(),
} }
} }
@ -369,7 +369,7 @@ impl Dynamic {
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(), Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().cloned(),
Union::Array(value) => cast_box::<_, T>(value).ok(), Union::Array(value) => cast_box::<_, T>(value).ok(),
Union::Map(value) => cast_box::<_, T>(value).ok(), Union::Map(value) => cast_box::<_, T>(value).ok(),
Union::SubScope(value) => cast_box::<_, T>(value).ok(), Union::Module(value) => cast_box::<_, T>(value).ok(),
Union::Variant(value) => value.as_any().downcast_ref::<T>().cloned(), Union::Variant(value) => value.as_any().downcast_ref::<T>().cloned(),
} }
} }
@ -407,7 +407,7 @@ impl Dynamic {
Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(), Union::Float(ref value) => (value as &dyn Any).downcast_ref::<T>().unwrap().clone(),
Union::Array(value) => cast_box::<_, T>(value).unwrap(), Union::Array(value) => cast_box::<_, T>(value).unwrap(),
Union::Map(value) => cast_box::<_, T>(value).unwrap(), Union::Map(value) => cast_box::<_, T>(value).unwrap(),
Union::SubScope(value) => cast_box::<_, T>(value).unwrap(), Union::Module(value) => cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => value.as_any().downcast_ref::<T>().unwrap().clone(), Union::Variant(value) => value.as_any().downcast_ref::<T>().unwrap().clone(),
} }
} }
@ -430,7 +430,7 @@ impl Dynamic {
Union::Float(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Float(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::SubScope(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
} }
} }
@ -453,7 +453,7 @@ impl Dynamic {
Union::Float(value) => (value as &mut dyn Any).downcast_mut::<T>(), Union::Float(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::SubScope(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
} }
} }

View File

@ -5,7 +5,7 @@ use crate::calc_fn_hash;
use crate::error::ParseErrorType; use crate::error::ParseErrorType;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage}; use crate::packages::{CorePackage, Package, PackageLibrary, StandardPackage};
use crate::parser::{Expr, FnDef, ReturnType, Stmt}; use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position; use crate::token::Position;
@ -42,27 +42,27 @@ pub type Array = Vec<Dynamic>;
/// Not available under the `no_object` feature. /// Not available under the `no_object` feature.
pub type Map = HashMap<String, Dynamic>; pub type Map = HashMap<String, Dynamic>;
/// A sub-scope - basically an imported module namespace. /// An imported module.
/// ///
/// Not available under the `no_import` feature. /// Not available under the `no_module` feature.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SubScope(HashMap<String, Dynamic>); pub struct Module(HashMap<String, Dynamic>);
impl SubScope { impl Module {
/// Create a new sub-scope. /// Create a new module.
pub fn new() -> Self { pub fn new() -> Self {
Self(HashMap::new()) Self(HashMap::new())
} }
} }
impl Deref for SubScope { impl Deref for Module {
type Target = HashMap<String, Dynamic>; type Target = HashMap<String, Dynamic>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
} }
impl DerefMut for SubScope { impl DerefMut for Module {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
} }
@ -500,12 +500,51 @@ fn default_print(s: &str) {
} }
/// Search for a variable within the scope /// Search for a variable within the scope
fn search_scope_variables<'a>( fn search_scope<'a>(
scope: &'a mut Scope, scope: &'a mut Scope,
name: &str, name: &str,
modules: &Option<Box<StaticVec<(String, Position)>>>,
index: Option<NonZeroUsize>, index: Option<NonZeroUsize>,
pos: Position, pos: Position,
) -> Result<(&'a mut Dynamic, ScopeEntryType), Box<EvalAltResult>> { ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box<EvalAltResult>> {
if let Some(modules) = modules {
let (id, root_pos) = modules.get(0); // First module
let mut module = if let Some(index) = index {
scope
.get_mut(scope.len() - index.get())
.0
.downcast_mut::<Module>()
.unwrap()
} else {
scope
.find_module(id)
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
};
for x in 1..modules.len() {
let (id, id_pos) = modules.get(x);
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)
}
} else {
let index = if let Some(index) = index { let index = if let Some(index) = index {
scope.len() - index.get() scope.len() - index.get()
} else { } else {
@ -517,51 +556,6 @@ fn search_scope_variables<'a>(
Ok(scope.get_mut(index)) Ok(scope.get_mut(index))
} }
/// Search for a sub-scope within the scope
fn search_scope_modules<'a>(
scope: &'a mut Scope,
name: &str,
modules: &Box<StaticVec<(String, Position)>>,
index: Option<NonZeroUsize>,
pos: Position,
) -> Result<(&'a mut Dynamic, ScopeEntryType), Box<EvalAltResult>> {
let (id, root_pos) = modules.get(0); // First module
let mut sub_scope = if let Some(index) = index {
scope
.get_mut(scope.len() - index.get())
.0
.downcast_mut::<SubScope>()
.unwrap()
} else {
scope
.find_sub_scope(id)
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
};
for x in 1..modules.len() {
let (id, id_pos) = modules.get(x);
sub_scope = sub_scope
.get_mut(id)
.and_then(|v| v.downcast_mut::<SubScope>())
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *id_pos)))?;
}
let result = sub_scope
.get_mut(name)
.map(|v| (v, ScopeEntryType::Constant))
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?;
if result.0.is::<SubScope>() {
Err(Box::new(EvalAltResult::ErrorVariableNotFound(
name.into(),
pos,
)))
} else {
Ok(result)
}
} }
impl Engine { impl Engine {
@ -620,11 +614,13 @@ impl Engine {
} }
/// Universal method for calling functions either registered with the `Engine` or written in Rhai /// 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( pub(crate) fn call_fn_raw(
&self, &self,
scope: Option<&mut Scope>, scope: Option<&mut Scope>,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
fn_name: &str, fn_name: &str,
modules: &ModuleRef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
pos: Position, pos: Position,
@ -794,6 +790,7 @@ impl Engine {
&self, &self,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
fn_name: &str, fn_name: &str,
modules: &ModuleRef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
pos: Position, pos: Position,
@ -801,12 +798,20 @@ impl Engine {
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
match fn_name { match fn_name {
// type_of // type_of
KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_TYPE_OF) => { KEYWORD_TYPE_OF
if modules.is_none()
&& args.len() == 1
&& !self.has_override(fn_lib, KEYWORD_TYPE_OF) =>
{
Ok(self.map_type_name(args[0].type_name()).to_string().into()) Ok(self.map_type_name(args[0].type_name()).to_string().into())
} }
// eval - reaching this point it must be a method-style call // eval - reaching this point it must be a method-style call
KEYWORD_EVAL if args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) => { KEYWORD_EVAL
if modules.is_none()
&& args.len() == 1
&& !self.has_override(fn_lib, KEYWORD_EVAL) =>
{
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
"'eval' should not be called in method style. Try eval(...);".into(), "'eval' should not be called in method style. Try eval(...);".into(),
pos, pos,
@ -814,7 +819,7 @@ impl Engine {
} }
// Normal method call // Normal method call
_ => self.call_fn_raw(None, fn_lib, fn_name, args, def_val, pos, level), _ => self.call_fn_raw(None, fn_lib, fn_name, modules, args, def_val, pos, level),
} }
} }
@ -915,7 +920,7 @@ impl Engine {
let def_val = def_val.as_deref(); let def_val = def_val.as_deref();
// A function call is assumed to have side effects, so the value is changed // 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 // TODO - Remove assumption of side effects by checking whether the first parameter is &mut
self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) self.exec_fn_call(fn_lib, fn_name, &None, &mut args, def_val, *pos, 0).map(|v| (v, true))
} }
// xxx.module::fn_name(...) - syntax error // xxx.module::fn_name(...) - syntax error
Expr::FnCall(_,_,_,_,_) => unreachable!(), Expr::FnCall(_,_,_,_,_) => unreachable!(),
@ -936,13 +941,13 @@ impl Engine {
Expr::Property(id, pos) if new_val.is_some() => { Expr::Property(id, pos) if new_val.is_some() => {
let fn_name = make_setter(id); let fn_name = make_setter(id);
let mut args = [obj, new_val.as_mut().unwrap()]; let mut args = [obj, new_val.as_mut().unwrap()];
self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, true)) self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, true))
} }
// xxx.id // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let fn_name = make_getter(id); let fn_name = make_getter(id);
let mut args = [obj]; let mut args = [obj];
self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) self.exec_fn_call(fn_lib, &fn_name, &None, &mut args, None, *pos, 0).map(|v| (v, false))
} }
// {xxx:map}.idx_lhs[idx_expr] // {xxx:map}.idx_lhs[idx_expr]
Expr::Index(dot_lhs, dot_rhs, pos) | Expr::Index(dot_lhs, dot_rhs, pos) |
@ -972,7 +977,7 @@ impl Engine {
let indexed_val = &mut (if let Expr::Property(id, pos) = dot_lhs.as_ref() { let indexed_val = &mut (if let Expr::Property(id, pos) = dot_lhs.as_ref() {
let fn_name = make_getter(id); let fn_name = make_getter(id);
self.exec_fn_call(fn_lib, &fn_name, &mut args[..1], None, *pos, 0)? self.exec_fn_call(fn_lib, &fn_name, &None, &mut args[..1], None, *pos, 0)?
} else { } else {
// Syntax error // Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr( return Err(Box::new(EvalAltResult::ErrorDotExpr(
@ -990,7 +995,7 @@ impl Engine {
let fn_name = make_setter(id); let fn_name = make_setter(id);
// Re-use args because the first &mut parameter will not be consumed // Re-use args because the first &mut parameter will not be consumed
args[1] = indexed_val; args[1] = indexed_val;
self.exec_fn_call(fn_lib, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { self.exec_fn_call(fn_lib, &fn_name, &None, &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 // If there is no setter, no need to feed it back because the property is read-only
EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()),
err => Err(Box::new(err)) err => Err(Box::new(err))
@ -1030,16 +1035,11 @@ impl Engine {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(id, modules, index, pos) => { Expr::Variable(id, modules, index, pos) => {
let index = if state.always_search { None } else { *index }; let index = if state.always_search { None } else { *index };
let (target, typ) = search_scope(scope, id, modules, index, *pos)?;
let (target, typ) = if let Some(modules) = modules {
search_scope_modules(scope, id, modules, index, *pos)?
} else {
search_scope_variables(scope, id, index, *pos)?
};
// Constants cannot be modified // Constants cannot be modified
match typ { match typ {
ScopeEntryType::SubScope => unreachable!(), ScopeEntryType::Module => unreachable!(),
ScopeEntryType::Constant if new_val.is_some() => { ScopeEntryType::Constant if new_val.is_some() => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
id.to_string(), id.to_string(),
@ -1223,9 +1223,10 @@ impl Engine {
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let args = &mut [&mut lhs_value, value]; let args = &mut [&mut lhs_value, value];
let def_value = Some(&def_value); let def_value = Some(&def_value);
let pos = rhs.position();
if self if self
.call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? .call_fn_raw(None, fn_lib, "==", &None, args, def_value, pos, level)?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1268,13 +1269,7 @@ impl Engine {
Expr::CharConstant(c, _) => Ok((*c).into()), Expr::CharConstant(c, _) => Ok((*c).into()),
Expr::Variable(id, modules, index, pos) => { Expr::Variable(id, modules, index, pos) => {
let index = if state.always_search { None } else { *index }; let index = if state.always_search { None } else { *index };
let val = search_scope(scope, id, modules, index, *pos)?;
let val = if let Some(modules) = modules {
search_scope_modules(scope, id, modules, index, *pos)?
} else {
search_scope_variables(scope, id, index, *pos)?
};
Ok(val.0.clone()) Ok(val.0.clone())
} }
Expr::Property(_, _) => unreachable!(), Expr::Property(_, _) => unreachable!(),
@ -1290,13 +1285,7 @@ impl Engine {
// name = rhs // name = rhs
Expr::Variable(name, modules, index, pos) => { Expr::Variable(name, modules, index, pos) => {
let index = if state.always_search { None } else { *index }; let index = if state.always_search { None } else { *index };
let val = if let Some(modules) = modules { match search_scope(scope, name, modules, index, *pos)? {
search_scope_modules(scope, name, modules, index, *pos)?
} else {
search_scope_variables(scope, name, index, *pos)?
};
match val {
(_, ScopeEntryType::Constant) => Err(Box::new( (_, ScopeEntryType::Constant) => Err(Box::new(
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos),
)), )),
@ -1304,8 +1293,8 @@ impl Engine {
*value_ptr = rhs_val; *value_ptr = rhs_val;
Ok(Default::default()) Ok(Default::default())
} }
// End variable cannot be a sub-scope // End variable cannot be a module
(_, ScopeEntryType::SubScope) => unreachable!(), (_, ScopeEntryType::Module) => unreachable!(),
} }
} }
// idx_lhs[idx_expr] = rhs // idx_lhs[idx_expr] = rhs
@ -1369,7 +1358,6 @@ impl Engine {
.collect::<Result<HashMap<_, _>, _>>()?, .collect::<Result<HashMap<_, _>, _>>()?,
)))), )))),
// TODO - handle moduled function call
Expr::FnCall(fn_name, modules, arg_exprs, def_val, pos) => { Expr::FnCall(fn_name, modules, arg_exprs, def_val, pos) => {
let mut arg_values = arg_exprs let mut arg_values = arg_exprs
.iter() .iter()
@ -1380,6 +1368,7 @@ impl Engine {
// eval - only in function call style // eval - only in function call style
if fn_name.as_ref() == KEYWORD_EVAL if fn_name.as_ref() == KEYWORD_EVAL
&& modules.is_none()
&& args.len() == 1 && args.len() == 1
&& !self.has_override(fn_lib, KEYWORD_EVAL) && !self.has_override(fn_lib, KEYWORD_EVAL)
{ {
@ -1399,7 +1388,8 @@ impl Engine {
} }
// Normal function call - except for eval (handled above) // Normal function call - except for eval (handled above)
self.exec_fn_call(fn_lib, fn_name, &mut args, def_val.as_deref(), *pos, level) 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, _) => { Expr::In(lhs, rhs, _) => {
@ -1634,13 +1624,13 @@ impl Engine {
.eval_expr(scope, state, fn_lib, expr, level)? .eval_expr(scope, state, fn_lib, expr, level)?
.try_cast::<String>() .try_cast::<String>()
{ {
let mut module = SubScope::new(); let mut module = Module::new();
module.insert("kitty".to_string(), "foo".to_string().into()); module.insert("kitty".to_string(), "foo".to_string().into());
module.insert("path".to_string(), path.into()); module.insert("path".to_string(), path.into());
// TODO - avoid copying module name in inner block? // TODO - avoid copying module name in inner block?
let mod_name = name.as_ref().clone(); let mod_name = name.as_ref().clone();
scope.push_sub_scope(mod_name, module); scope.push_module(mod_name, module);
Ok(Default::default()) Ok(Default::default())
} else { } else {
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))

View File

@ -103,9 +103,6 @@ pub use engine::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub use engine::Map; pub use engine::Map;
#[cfg(not(feature = "no_import"))]
pub use engine::SubScope;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub use parser::FLOAT; pub use parser::FLOAT;

View File

@ -231,6 +231,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
} }
// let id; // let id;
Stmt::Let(_, None, _) => stmt, Stmt::Let(_, None, _) => stmt,
// import expr as id;
Stmt::Import(expr, id, pos) => Stmt::Import(Box::new(optimize_expr(*expr, state)), id, pos),
// { block } // { block }
Stmt::Block(block, pos) => { Stmt::Block(block, pos) => {
let orig_len = block.len(); // Original number of statements in the block, for change detection let orig_len = block.len(); // Original number of statements in the block, for change detection
@ -260,7 +262,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
result.push(stmt); result.push(stmt);
} }
// Remove all let statements at the end of a block - the new variables will go away anyway. // Remove all let/import statements at the end of a block - the new variables will go away anyway.
// But be careful only remove ones that have no initial values or have values that are pure expressions, // But be careful only remove ones that have no initial values or have values that are pure expressions,
// otherwise there may be side effects. // otherwise there may be side effects.
let mut removed = false; let mut removed = false;
@ -268,7 +270,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
while let Some(expr) = result.pop() { while let Some(expr) = result.pop() {
match expr { match expr {
Stmt::Let(_, None, _) => removed = true, Stmt::Let(_, None, _) => removed = true,
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true, Stmt::Let(_, Some(val_expr), _) => removed = val_expr.is_pure(),
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
_ => { _ => {
result.push(expr); result.push(expr);
break; break;
@ -323,6 +326,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// Only one let/import statement - leave it alone
[Stmt::Let(_, _, _)] | [Stmt::Import(_, _, _)] => Stmt::Block(result, pos),
// Only one statement - promote // Only one statement - promote
[_] => { [_] => {
state.set_dirty(); state.set_dirty();
@ -666,7 +671,10 @@ fn optimize<'a>(
_ => { _ => {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; let keep = match stmt {
Stmt::Let(_, _, _) | Stmt::Import(_, _, _) => true,
_ => i == num_statements - 1,
};
optimize_stmt(stmt, &mut state, keep) optimize_stmt(stmt, &mut state, keep)
} }
} }

View File

@ -42,6 +42,8 @@ pub type FLOAT = f64;
type PERR = ParseErrorType; type PERR = ParseErrorType;
pub type ModuleRef = Option<Box<StaticVec<(String, Position)>>>;
/// Compiled AST (abstract syntax tree) of a Rhai script. /// 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`. /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
@ -204,7 +206,7 @@ impl Stack {
.enumerate() .enumerate()
.find(|(_, (n, typ))| match typ { .find(|(_, (n, typ))| match typ {
ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name, ScopeEntryType::Normal | ScopeEntryType::Constant => *n == name,
ScopeEntryType::SubScope => false, ScopeEntryType::Module => false,
}) })
.and_then(|(i, _)| NonZeroUsize::new(i + 1)) .and_then(|(i, _)| NonZeroUsize::new(i + 1))
} }
@ -218,7 +220,7 @@ impl Stack {
.rev() .rev()
.enumerate() .enumerate()
.find(|(_, (n, typ))| match typ { .find(|(_, (n, typ))| match typ {
ScopeEntryType::SubScope => *n == name, ScopeEntryType::Module => *n == name,
ScopeEntryType::Normal | ScopeEntryType::Constant => false, ScopeEntryType::Normal | ScopeEntryType::Constant => false,
}) })
.and_then(|(i, _)| NonZeroUsize::new(i + 1)) .and_then(|(i, _)| NonZeroUsize::new(i + 1))
@ -344,12 +346,7 @@ pub enum Expr {
/// String constant. /// String constant.
StringConstant(String, Position), StringConstant(String, Position),
/// Variable access - (variable name, optional modules, optional index, position) /// Variable access - (variable name, optional modules, optional index, position)
Variable( Variable(Box<String>, ModuleRef, Option<NonZeroUsize>, Position),
Box<String>,
Option<Box<StaticVec<(String, Position)>>>,
Option<NonZeroUsize>,
Position,
),
/// Property access. /// Property access.
Property(String, Position), Property(String, Position),
/// { stmt } /// { stmt }
@ -359,7 +356,7 @@ pub enum Expr {
/// and the function names are predictable, so no need to allocate a new `String`. /// and the function names are predictable, so no need to allocate a new `String`.
FnCall( FnCall(
Box<Cow<'static, str>>, Box<Cow<'static, str>>,
Option<Box<StaticVec<(String, Position)>>>, ModuleRef,
Box<Vec<Expr>>, Box<Vec<Expr>>,
Option<Box<Dynamic>>, Option<Box<Dynamic>>,
Position, Position,
@ -575,12 +572,12 @@ impl Expr {
Self::Variable(_, None, _, _) => match token { Self::Variable(_, None, _, _) => match token {
Token::LeftBracket | Token::LeftParen => true, Token::LeftBracket | Token::LeftParen => true,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
Token::DoubleColon => true, Token::DoubleColon => true,
_ => false, _ => false,
}, },
Self::Variable(_, _, _, _) => match token { Self::Variable(_, _, _, _) => match token {
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
Token::DoubleColon => true, Token::DoubleColon => true,
_ => false, _ => false,
}, },
@ -659,7 +656,7 @@ fn parse_call_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack, stack: &mut Stack,
id: String, id: String,
modules: Option<Box<StaticVec<(String, Position)>>>, modules: ModuleRef,
begin: Position, begin: Position,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
@ -1071,7 +1068,7 @@ fn parse_primary<'a>(
parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)? parse_call_expr(input, stack, id, None, pos, allow_stmt_expr)?
} }
// module access // module access
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
(Expr::Variable(id, mut modules, mut index, pos), Token::DoubleColon) => { (Expr::Variable(id, mut modules, mut index, pos), Token::DoubleColon) => {
match input.next().unwrap() { match input.next().unwrap() {
(Token::Identifier(id2), pos2) => { (Token::Identifier(id2), pos2) => {
@ -1790,7 +1787,7 @@ fn parse_let<'a>(
Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position()))
} }
// Variable cannot be a sub-scope // Variable cannot be a sub-scope
ScopeEntryType::SubScope => unreachable!(), ScopeEntryType::Module => unreachable!(),
} }
} else { } else {
// let name // let name
@ -1799,7 +1796,7 @@ fn parse_let<'a>(
} }
/// Parse an import statement. /// Parse an import statement.
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
fn parse_import<'a>( fn parse_import<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack, stack: &mut Stack,
@ -1829,7 +1826,7 @@ fn parse_import<'a>(
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
}; };
stack.push((name.clone(), ScopeEntryType::SubScope)); stack.push((name.clone(), ScopeEntryType::Module));
Ok(Stmt::Import(Box::new(expr), Box::new(name), pos)) Ok(Stmt::Import(Box::new(expr), Box::new(name), pos))
} }

View File

@ -1,7 +1,7 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Dynamic, Union, Variant}; use crate::any::{Dynamic, Union, Variant};
use crate::engine::SubScope; use crate::engine::Module;
use crate::parser::{map_dynamic_to_expr, Expr}; use crate::parser::{map_dynamic_to_expr, Expr};
use crate::token::Position; use crate::token::Position;
@ -14,8 +14,9 @@ pub enum EntryType {
Normal, Normal,
/// Immutable constant value. /// Immutable constant value.
Constant, Constant,
/// Name of a sub-scope, allowing member access with the :: operator. /// Name of a module, allowing member access with the :: operator.
SubScope, /// This is for internal use only.
Module,
} }
/// An entry in the Scope. /// An entry in the Scope.
@ -168,30 +169,14 @@ impl<'a> Scope<'a> {
self.push_dynamic_value(name, EntryType::Normal, value, false); self.push_dynamic_value(name, EntryType::Normal, value, false);
} }
/// Add (push) a new sub-scope to the Scope. /// Add (push) a new module to the Scope.
/// ///
/// Sub-scopes are used for accessing members in modules and plugins under a namespace. /// Modules are used for accessing member variables, functions and plugins under a namespace.
/// pub(crate) fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Module) {
/// # Examples
///
/// ```
/// use rhai::{Scope, SubScope};
///
/// let mut my_scope = Scope::new();
///
/// let mut sub_scope = SubScope::new();
/// sub_scope.insert("x".to_string(), 42_i64.into());
///
/// my_scope.push_sub_scope("my_plugin", sub_scope);
///
/// let s = my_scope.find_sub_scope("my_plugin").unwrap();
/// assert_eq!(*s.get("x").unwrap().downcast_ref::<i64>().unwrap(), 42);
/// ```
pub fn push_sub_scope<K: Into<Cow<'a, str>>>(&mut self, name: K, value: SubScope) {
self.push_dynamic_value( self.push_dynamic_value(
name, name,
EntryType::SubScope, EntryType::Module,
Dynamic(Union::SubScope(Box::new(value))), Dynamic(Union::Module(Box::new(value))),
true, true,
); );
} }
@ -295,8 +280,6 @@ impl<'a> Scope<'a> {
/// Does the scope contain the entry? /// Does the scope contain the entry?
/// ///
/// Sub-scopes are ignored.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@ -314,13 +297,13 @@ impl<'a> Scope<'a> {
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.any(|Entry { name: key, typ, .. }| match typ { .any(|Entry { name: key, typ, .. }| match typ {
EntryType::Normal | EntryType::Constant => name == key, EntryType::Normal | EntryType::Constant => name == key,
EntryType::SubScope => false, EntryType::Module => false,
}) })
} }
/// Find an entry in the Scope, starting from the last. /// Find an entry in the Scope, starting from the last.
/// ///
/// Sub-scopes are ignored. /// modules are ignored.
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> {
self.0 self.0
.iter() .iter()
@ -334,18 +317,18 @@ impl<'a> Scope<'a> {
None None
} }
} }
EntryType::SubScope => None, EntryType::Module => None,
}) })
} }
/// Find a sub-scope in the Scope, starting from the last. /// Find a module in the Scope, starting from the last.
pub(crate) fn get_sub_scope_index(&self, name: &str) -> Option<usize> { pub(crate) fn get_module_index(&self, name: &str) -> Option<usize> {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find_map(|(index, Entry { name: key, typ, .. })| match typ { .find_map(|(index, Entry { name: key, typ, .. })| match typ {
EntryType::SubScope => { EntryType::Module => {
if name == key { if name == key {
Some(index) Some(index)
} else { } else {
@ -356,15 +339,15 @@ impl<'a> Scope<'a> {
}) })
} }
/// Find a sub-scope in the Scope, starting from the last entry. /// Find a module in the Scope, starting from the last entry.
pub fn find_sub_scope(&mut self, name: &str) -> Option<&mut SubScope> { pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
let index = self.get_sub_scope_index(name)?; let index = self.get_module_index(name)?;
self.get_mut(index).0.downcast_mut::<SubScope>() self.get_mut(index).0.downcast_mut::<Module>()
} }
/// Get the value of an entry in the Scope, starting from the last. /// Get the value of an entry in the Scope, starting from the last.
/// ///
/// Sub-scopes are ignored. /// modules are ignored.
/// ///
/// # Examples /// # Examples
/// ///
@ -382,7 +365,7 @@ impl<'a> Scope<'a> {
.rev() .rev()
.find(|Entry { name: key, typ, .. }| match typ { .find(|Entry { name: key, typ, .. }| match typ {
EntryType::Normal | EntryType::Constant => name == key, EntryType::Normal | EntryType::Constant => name == key,
EntryType::SubScope => false, EntryType::Module => false,
}) })
.and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned()) .and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned())
} }
@ -415,8 +398,8 @@ impl<'a> Scope<'a> {
Some((index, EntryType::Normal)) => { Some((index, EntryType::Normal)) => {
self.0.get_mut(index).unwrap().value = Dynamic::from(value) self.0.get_mut(index).unwrap().value = Dynamic::from(value)
} }
// Sub-scopes cannot be modified // modules cannot be modified
Some((_, EntryType::SubScope)) => unreachable!(), Some((_, EntryType::Module)) => unreachable!(),
} }
} }

View File

@ -153,7 +153,7 @@ pub enum Token {
RightShift, RightShift,
SemiColon, SemiColon,
Colon, Colon,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
DoubleColon, DoubleColon,
Comma, Comma,
Period, Period,
@ -199,11 +199,11 @@ pub enum Token {
XOrAssign, XOrAssign,
ModuloAssign, ModuloAssign,
PowerOfAssign, PowerOfAssign,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
Import, Import,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
Export, Export,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
As, As,
LexError(Box<LexError>), LexError(Box<LexError>),
EOF, EOF,
@ -238,7 +238,7 @@ impl Token {
Divide => "/", Divide => "/",
SemiColon => ";", SemiColon => ";",
Colon => ":", Colon => ":",
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
DoubleColon => "::", DoubleColon => "::",
Comma => ",", Comma => ",",
Period => ".", Period => ".",
@ -288,11 +288,11 @@ impl Token {
ModuloAssign => "%=", ModuloAssign => "%=",
PowerOf => "~", PowerOf => "~",
PowerOfAssign => "~=", PowerOfAssign => "~=",
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
Import => "import", Import => "import",
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
Export => "export", Export => "export",
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
As => "as", As => "as",
EOF => "{EOF}", EOF => "{EOF}",
_ => panic!("operator should be match in outer scope"), _ => panic!("operator should be match in outer scope"),
@ -763,11 +763,11 @@ impl<'a> TokenIterator<'a> {
"for" => Token::For, "for" => Token::For,
"in" => Token::In, "in" => Token::In,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
"import" => Token::Import, "import" => Token::Import,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
"export" => Token::Export, "export" => Token::Export,
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
"as" => Token::As, "as" => Token::As,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -924,7 +924,7 @@ impl<'a> TokenIterator<'a> {
} }
('=', _) => return Some((Token::Equals, pos)), ('=', _) => return Some((Token::Equals, pos)),
#[cfg(not(feature = "no_import"))] #[cfg(not(feature = "no_module"))]
(':', ':') => { (':', ':') => {
self.eat_next(); self.eat_next();
return Some((Token::DoubleColon, pos)); return Some((Token::DoubleColon, pos));

View File

@ -1,15 +1 @@
use rhai::{EvalAltResult, Scope, SubScope, INT}; use rhai::{EvalAltResult, Scope, INT};
#[test]
#[cfg(not(feature = "no_import"))]
fn test_sub_scope() {
let mut my_scope = Scope::new();
let mut sub_scope = SubScope::new();
sub_scope.insert("x".to_string(), (42 as INT).into());
my_scope.push_sub_scope("my_plugin", sub_scope);
let s = my_scope.find_sub_scope("my_plugin").unwrap();
assert_eq!(*s.get("x").unwrap().downcast_ref::<INT>().unwrap(), 42);
}