diff --git a/RELEASES.md b/RELEASES.md index 2e9a14e5..ae226bae 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,12 +7,26 @@ Version 0.19.9 This version removes the confusing differences between _packages_ and _modules_ by unifying the terminology and API under the global umbrella of _modules_. +Bug fixes +--------- + +* Property access in + Breaking changes ---------------- * `Engine::load_package` is renamed `Engine::register_global_module` and now must explicitly pass a shared [`Module`]. * `Engine::register_module` is renamed `Engine::register_static_module` and now must explicitly pass a shared [`Module`]. * `Package::get` is renamed `Package::as_shared_module`. +* `Engine::set_module_resolver` now takes a straight module resolver instead of an `Option`. To disable module resolving, use the new `DummyModuleResolver`. + +Enhancements +------------ + +* `Scope` is now `Clone + Hash`. +* `Engine::register_static_module` now supports sub-module paths (e.g. `foo::bar::baz`). +* `Engine::register_custom_operator` now accepts reserved symbols. +* `Engine::register_custom_operator` now returns an error if given a precedence of zero. Version 0.19.8 diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index a7986b6b..bd2272a4 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -3,7 +3,7 @@ use syn::{ spanned::Spanned, }; -#[derive(Debug)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum ExportScope { PubOnly, Prefix(String), @@ -22,12 +22,14 @@ pub trait ExportedParams: Sized { fn from_info(info: ExportInfo) -> syn::Result; } +#[derive(Debug, Clone)] pub struct AttrItem { pub key: proc_macro2::Ident, pub value: Option, pub span: proc_macro2::Span, } +#[derive(Debug, Clone)] pub struct ExportInfo { pub item_span: proc_macro2::Span, pub items: Vec, diff --git a/codegen/src/function.rs b/codegen/src/function.rs index e84232c3..426f4edc 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -22,23 +22,30 @@ use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; #[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)] pub enum FnNamespaceAccess { + Unset, Global, Internal, } -#[derive(Clone, Debug, Eq, PartialEq)] +impl Default for FnNamespaceAccess { + fn default() -> Self { + Self::Unset + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)] pub enum Index { Get, Set, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum Property { Get(syn::Ident), Set(syn::Ident), } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum FnSpecialAccess { None, Index(Index), @@ -97,11 +104,11 @@ pub(crate) fn print_type(ty: &syn::Type) -> String { #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { - pub name: Option>, + pub name: Vec, pub return_raw: bool, pub skip: bool, pub special: FnSpecialAccess, - pub namespace: Option, + pub namespace: FnNamespaceAccess, pub span: Option, } @@ -138,7 +145,7 @@ impl ExportedParams for ExportedFnParams { let mut name = Vec::new(); let mut return_raw = false; let mut skip = false; - let mut namespace = None; + let mut namespace = FnNamespaceAccess::Unset; let mut special = FnSpecialAccess::None; for attr in attrs { let crate::attrs::AttrItem { @@ -226,22 +233,16 @@ impl ExportedParams for ExportedFnParams { ("global", Some(s)) | ("internal", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) } - ("global", None) => { - if let Some(ns) = namespace { - if ns != FnNamespaceAccess::Global { - return Err(syn::Error::new(key.span(), "conflicting namespace")); - } - } - namespace = Some(FnNamespaceAccess::Global); - } - ("internal", None) => { - if let Some(ns) = namespace { - if ns != FnNamespaceAccess::Internal { - return Err(syn::Error::new(key.span(), "conflicting namespace")); - } - } - namespace = Some(FnNamespaceAccess::Internal); - } + ("global", None) => match namespace { + FnNamespaceAccess::Unset => namespace = FnNamespaceAccess::Global, + FnNamespaceAccess::Global => (), + _ => return Err(syn::Error::new(key.span(), "conflicting namespace")), + }, + ("internal", None) => match namespace { + FnNamespaceAccess::Unset => namespace = FnNamespaceAccess::Internal, + FnNamespaceAccess::Internal => (), + _ => return Err(syn::Error::new(key.span(), "conflicting namespace")), + }, (attr, _) => { return Err(syn::Error::new( key.span(), @@ -252,7 +253,7 @@ impl ExportedParams for ExportedFnParams { } Ok(ExportedFnParams { - name: if name.is_empty() { None } else { Some(name) }, + name, return_raw, skip, special, @@ -455,16 +456,12 @@ impl ExportedFn { } pub(crate) fn exported_names(&self) -> Vec { - let mut literals = self + let mut literals: Vec<_> = self .params .name - .as_ref() - .map(|v| { - v.iter() - .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site())) - .collect() - }) - .unwrap_or_else(|| Vec::new()); + .iter() + .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site())) + .collect(); if let Some((s, _, span)) = self.params.special.get_fn_name() { literals.push(syn::LitStr::new(&s, span)); @@ -481,11 +478,10 @@ impl ExportedFn { } pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> { - if let Some(ref name) = self.params.name { - name.last().unwrap().as_str().into() - } else { - self.signature.ident.to_string().into() - } + self.params.name.last().map_or_else( + || self.signature.ident.to_string().into(), + |s| s.as_str().into(), + ) } pub(crate) fn arg_list(&self) -> impl Iterator { @@ -712,10 +708,12 @@ impl ExportedFn { pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { let sig_name = self.name().clone(); - let name = self.params.name.as_ref().map_or_else( - || self.name().to_string(), - |names| names.last().unwrap().clone(), - ); + let name = self + .params + .name + .last() + .cloned() + .unwrap_or_else(|| self.name().to_string()); let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index ea341e1b..d71054a0 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -21,7 +21,7 @@ //! let m = exported_module!(advanced_math); //! let mut r = StaticModuleResolver::new(); //! r.insert("Math::Advanced", m); -//! engine.set_module_resolver(Some(r)); +//! engine.set_module_resolver(r); //! //! assert_eq!(engine.eval::( //! r#" @@ -51,7 +51,7 @@ //! set_exported_fn!(m, "euclidean_distance", distance_function); //! let mut r = StaticModuleResolver::new(); //! r.insert("Math::Advanced", m); -//! engine.set_module_resolver(Some(r)); +//! engine.set_module_resolver(r); //! //! assert_eq!(engine.eval::( //! r#" diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 283436ab..de6fbd28 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -19,9 +19,9 @@ use std::borrow::Cow; use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams}; use crate::function::ExportedFnParams; -#[derive(Debug, Default)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub(crate) struct ExportedModParams { - pub name: Option, + pub name: String, skip: bool, pub scope: ExportScope, } @@ -49,21 +49,32 @@ impl ExportedParams for ExportedModParams { fn from_info(info: ExportInfo) -> syn::Result { let ExportInfo { items: attrs, .. } = info; - let mut name = None; + let mut name = Default::default(); let mut skip = false; - let mut scope = ExportScope::default(); + let mut scope = None; for attr in attrs { let AttrItem { key, value, .. } = attr; match (key.to_string().as_ref(), value) { - ("name", Some(s)) => name = Some(s.value()), + ("name", Some(s)) => { + let new_name = s.value(); + if name == new_name { + return Err(syn::Error::new(key.span(), "conflicting name")); + } + name = new_name; + } ("name", None) => return Err(syn::Error::new(key.span(), "requires value")), + ("skip", None) => skip = true, ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), - ("export_prefix", Some(s)) => scope = ExportScope::Prefix(s.value()), + + ("export_prefix", Some(_)) | ("export_all", None) if scope.is_some() => { + return Err(syn::Error::new(key.span(), "duplicate export scope")); + } + ("export_prefix", Some(s)) => scope = Some(ExportScope::Prefix(s.value())), ("export_prefix", None) => { return Err(syn::Error::new(key.span(), "requires value")) } - ("export_all", None) => scope = ExportScope::All, + ("export_all", None) => scope = Some(ExportScope::All), ("export_all", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) } @@ -79,7 +90,7 @@ impl ExportedParams for ExportedModParams { Ok(ExportedModParams { name, skip, - scope, + scope: scope.unwrap_or_default(), ..Default::default() }) } @@ -87,7 +98,7 @@ impl ExportedParams for ExportedModParams { #[derive(Debug)] pub(crate) struct Module { - mod_all: Option, + mod_all: syn::ItemMod, fns: Vec, consts: Vec, submodules: Vec, @@ -183,7 +194,7 @@ impl Parse for Module { fns = new_vec![]; } Ok(Module { - mod_all: Some(mod_all), + mod_all, fns, consts, submodules, @@ -194,39 +205,27 @@ impl Parse for Module { #[allow(dead_code)] impl Module { - pub fn attrs(&self) -> Option<&Vec> { - self.mod_all.as_ref().map(|m| &m.attrs) + pub fn attrs(&self) -> &Vec { + &self.mod_all.attrs } - pub fn module_name(&self) -> Option<&syn::Ident> { - self.mod_all.as_ref().map(|m| &m.ident) + pub fn module_name(&self) -> &syn::Ident { + &self.mod_all.ident } - pub fn exported_name(&self) -> Option> { - if let Some(ref s) = self.params.name { - Some(s.into()) + pub fn exported_name(&self) -> Cow { + if !self.params.name.is_empty() { + self.params.name.as_str().into() } else { - self.module_name().map(|m| m.to_string().into()) + self.module_name().to_string().into() } } pub fn update_scope(&mut self, parent_scope: &ExportScope) { let keep = match (self.params.skip, parent_scope) { (true, _) => false, - (_, ExportScope::PubOnly) => { - if let Some(ref mod_all) = self.mod_all { - matches!(mod_all.vis, syn::Visibility::Public(_)) - } else { - false - } - } - (_, ExportScope::Prefix(s)) => { - if let Some(ref mod_all) = self.mod_all { - mod_all.ident.to_string().starts_with(s) - } else { - false - } - } + (_, ExportScope::PubOnly) => matches!(self.mod_all.vis, syn::Visibility::Public(_)), + (_, ExportScope::Prefix(s)) => self.mod_all.ident.to_string().starts_with(s), (_, ExportScope::All) => true, }; self.params.skip = !keep; @@ -249,14 +248,13 @@ impl Module { // Extract the current structure of the module. let Module { - mod_all, + mut mod_all, mut fns, consts, mut submodules, params, .. } = self; - let mut mod_all = mod_all.unwrap(); let mod_name = mod_all.ident.clone(); let (_, orig_content) = mod_all.content.take().unwrap(); let mod_attrs = mem::take(&mut mod_all.attrs); @@ -299,8 +297,8 @@ impl Module { } } - pub fn name(&self) -> Option<&syn::Ident> { - self.mod_all.as_ref().map(|m| &m.ident) + pub fn name(&self) -> &syn::Ident { + &self.mod_all.ident } pub fn consts(&self) -> &[ExportedConst] { @@ -317,10 +315,10 @@ impl Module { pub fn content(&self) -> Option<&Vec> { match self.mod_all { - Some(syn::ItemMod { + syn::ItemMod { content: Some((_, ref vec)), .. - }) => Some(vec), + } => Some(vec), _ => None, } } diff --git a/codegen/src/register.rs b/codegen/src/register.rs index 321769b7..515f13d1 100644 --- a/codegen/src/register.rs +++ b/codegen/src/register.rs @@ -18,6 +18,7 @@ pub(crate) fn generated_module_path( } type RegisterMacroInput = (syn::Expr, proc_macro2::TokenStream, syn::Path); + pub fn parse_register_macro( args: proc_macro::TokenStream, ) -> Result { diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index b1e26691..5d9c148f 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -40,15 +40,13 @@ pub(crate) fn generate_body( if itemmod.skipped() { continue; } - let module_name = itemmod.module_name().unwrap(); - let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { - syn::LitStr::new(&name, proc_macro2::Span::call_site()) - } else { - syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) - }; + let module_name = itemmod.module_name(); + let exported_name: syn::LitStr = syn::LitStr::new( + itemmod.exported_name().as_ref(), + proc_macro2::Span::call_site(), + ); let cfg_attrs: Vec<&syn::Attribute> = itemmod .attrs() - .unwrap() .iter() .filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) .collect(); @@ -158,12 +156,14 @@ pub(crate) fn generate_body( } } - if let Some(ns) = function.params().namespace { - namespace = ns; + match function.params().namespace { + FnNamespaceAccess::Unset => (), + ns => namespace = ns, } let ns_str = syn::Ident::new( match namespace { + FnNamespaceAccess::Unset => unreachable!(), FnNamespaceAccess::Global => "Global", FnNamespaceAccess::Internal => "Internal", }, @@ -243,24 +243,24 @@ pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn:: let mut fn_defs = HashMap::::new(); for itemfn in fns.iter() { - if itemfn.params().name.is_some() || itemfn.params().special != FnSpecialAccess::None { - let mut names = itemfn + if !itemfn.params().name.is_empty() || itemfn.params().special != FnSpecialAccess::None { + let mut names: Vec<_> = itemfn .params() .name - .as_ref() - .map(|v| v.iter().map(|n| (n.clone(), n.clone())).collect()) - .unwrap_or_else(|| Vec::new()); + .iter() + .map(|n| (n.clone(), n.clone())) + .collect(); if let Some((s, n, _)) = itemfn.params().special.get_fn_name() { names.push((s, n)); } for (name, fn_name) in names { - let current_span = itemfn.params().span.as_ref().unwrap(); + let current_span = itemfn.params().span.unwrap(); let key = make_key(&name, itemfn); - if let Some(other_span) = renames.insert(key, *current_span) { + if let Some(other_span) = renames.insert(key, current_span) { let mut err = syn::Error::new( - *current_span, + current_span, format!("duplicate Rhai signature for '{}'", &fn_name), ); err.combine(syn::Error::new( diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 590ac6e9..303076e2 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -18,14 +18,11 @@ fn raw_fn_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "euclidean_distance", raw_fn::distance_function); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# )?, 41.0 ); @@ -48,16 +45,13 @@ fn raw_fn_mut_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let x = get_mystic_number(); - math::add_in_place(x, 1.0); - x"# + r#"let x = get_mystic_number(); + Math::Advanced::add_in_place(x, 1.0); + x"# )?, 43.0 ); @@ -80,16 +74,10 @@ fn raw_fn_str_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); - let mut r = StaticModuleResolver::new(); - r.insert("Host::IO", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::IO", m.into()); assert_eq!( - engine.eval::( - r#"import "Host::IO" as io; - let x = io::write_out_str("hello world!"); - x"# - )?, + engine.eval::(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?, true ); Ok(()) @@ -138,18 +126,16 @@ fn mut_opaque_ref_test() -> Result<(), Box> { rhai::set_exported_fn!(m, "new_message", mut_opaque_ref::new_message); rhai::set_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); - let mut r = StaticModuleResolver::new(); - r.insert("Host::Msg", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::Msg", m.into()); assert_eq!( engine.eval::( - r#"import "Host::Msg" as msg; - let message1 = msg::new_message(true, "it worked"); - let ok1 = msg::write_out_message(message1); - let message2 = msg::new_os_message(true, 0); - let ok2 = msg::write_out_message(message2); - ok1 && ok2"# + r#" + let message1 = Host::Msg::new_message(true, "it worked"); + let ok1 = Host::Msg::write_out_message(message1); + let message2 = Host::Msg::new_os_message(true, 0); + let ok2 = Host::Msg::write_out_message(message2); + ok1 && ok2"# )?, true ); @@ -179,14 +165,11 @@ fn raw_returning_fn_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); rhai::set_exported_fn!(m, "euclidean_distance", raw_returning_fn::distance_function); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# )?, 41.0 ); diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index 878f4fef..4993c5c3 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -12,14 +12,8 @@ pub mod empty_module { fn empty_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::empty_module::EmptyModule); - let mut r = StaticModuleResolver::new(); - r.insert("Module::Empty", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Module::Empty", m.into()); - assert_eq!( - engine.eval::(r#"import "Module::Empty" as m; 42"#)?, - 42 - ); Ok(()) } @@ -39,16 +33,10 @@ pub mod one_fn_module { fn one_fn_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_module::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::get_mystic_number(); - m"# - )?, + engine.eval::(r#"let m = Math::Advanced::get_mystic_number();m"#)?, 42.0 ); Ok(()) @@ -73,16 +61,14 @@ pub mod one_fn_and_const_module { fn one_fn_and_const_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::MYSTIC_NUMBER; - let x = math::euclidean_distance(0.0, 1.0, 0.0, m); - x"# + r#" + let m = Math::Advanced::MYSTIC_NUMBER; + let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, m); + x"# )?, 41.0 ); @@ -105,16 +91,10 @@ pub mod raw_fn_str_module { fn raw_fn_str_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::raw_fn_str_module::host_io); - let mut r = StaticModuleResolver::new(); - r.insert("Host::IO", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::IO", m.into()); assert_eq!( - engine.eval::( - r#"import "Host::IO" as io; - let x = io::write_out_str("hello world!"); - x"# - )?, + engine.eval::(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?, true ); Ok(()) @@ -162,19 +142,17 @@ pub mod mut_opaque_ref_module { fn mut_opaque_ref_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg); - let mut r = StaticModuleResolver::new(); - r.insert("Host::Msg", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Host::Msg", m.into()); assert_eq!( engine.eval::( - r#"import "Host::Msg" as msg; - let success = "it worked"; - let message1 = msg::new_message(true, success); - let ok1 = msg::write_out_message(message1); - let message2 = msg::new_os_message(true, 0); - let ok2 = msg::write_out_message(message2); - ok1 && ok2"# + r#" + let success = "it worked"; + let message1 = Host::Msg::new_message(true, success); + let ok1 = Host::Msg::write_out_message(message1); + let message2 = Host::Msg::new_os_message(true, 0); + let ok2 = Host::Msg::write_out_message(message2); + ok1 && ok2"# )?, true ); @@ -204,18 +182,16 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let fx = get_mystic_number(); - let fy = math::add(fx, 1.0); - let ix = 42; - let iy = math::add(ix, 1); - [fy, iy] - "#, + r#" + let fx = get_mystic_number(); + let fy = Math::Advanced::add(fx, 1.0); + let ix = 42; + let iy = Math::Advanced::add(ix, 1); + [fy, iy] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &43.0); assert_eq!(&output_array[1].as_int().unwrap(), &43); @@ -291,6 +267,7 @@ fn multiple_fn_rename_test() -> Result<(), Box> { mod export_by_prefix { use rhai::plugin::*; + #[export_module(export_prefix = "foo_")] pub mod my_adds { use rhai::{FLOAT, INT}; @@ -328,20 +305,18 @@ mod export_by_prefix { fn export_by_prefix_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_by_prefix::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_add_f(ex, 1.0); - let gx = math::foo_m(41.0, 1.0); - let ei = 41; - let fi = math::bar_add_i(ei, 1); - let gi = math::foo_n(41, 1); - [fx, gx, fi, gi] - "#, + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_add_f(ex, 1.0); + let gx = Math::Advanced::foo_m(41.0, 1.0); + let ei = 41; + let fi = Math::Advanced::bar_add_i(ei, 1); + let gi = Math::Advanced::foo_n(41, 1); + [fx, gx, fi, gi] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[1].as_float().unwrap(), &42.0); @@ -349,30 +324,31 @@ fn export_by_prefix_test() -> Result<(), Box> { assert_eq!(&output_array[3].as_int().unwrap(), &42); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_add_float2(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_add_float2 (f64, f64)" - && p == rhai::Position::new(3, 23))); + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_add_float2(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_add_float2 (f64, f64)" + && p == rhai::Position::new(3, 34))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::bar_m(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_m (f64, f64)" - && p == rhai::Position::new(3, 23))); + r#" + let ex = 41.0; + let fx = Math::Advanced::bar_m(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::bar_m (f64, f64)" + && p == rhai::Position::new(3, 34))); Ok(()) } mod export_all { use rhai::plugin::*; + #[export_module(export_all)] pub mod my_adds { use rhai::{FLOAT, INT}; @@ -411,20 +387,18 @@ mod export_all { fn export_all_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_all::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_add_f(ex, 1.0); - let gx = math::foo_m(41.0, 1.0); - let ei = 41; - let fi = math::foo_add_i(ei, 1); - let gi = math::foo_n(41, 1); - [fx, gx, fi, gi] - "#, + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_add_f(ex, 1.0); + let gx = Math::Advanced::foo_m(41.0, 1.0); + let ei = 41; + let fi = Math::Advanced::foo_add_i(ei, 1); + let gi = Math::Advanced::foo_n(41, 1); + [fx, gx, fi, gi] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[1].as_float().unwrap(), &42.0); @@ -432,14 +406,14 @@ fn export_all_test() -> Result<(), Box> { assert_eq!(&output_array[3].as_int().unwrap(), &42); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41; - let fx = math::foo_p(ex, 1); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_p (i64, i64)" - && p == rhai::Position::new(3, 23))); + r#" + let ex = 41; + let fx = Math::Advanced::foo_p(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_p (i64, i64)" + && p == rhai::Position::new(3, 34))); Ok(()) } diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index cf2c1a2e..a7f898b5 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -20,16 +20,10 @@ pub mod one_fn_module_nested_attr { fn one_fn_module_nested_attr_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::get_mystic_number(); - m"# - )?, + engine.eval::(r#"let m = Math::Advanced::get_mystic_number(); m"#)?, 42.0 ); Ok(()) @@ -56,16 +50,10 @@ pub mod one_fn_submodule_nested_attr { fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); assert_eq!( - engine.eval::( - r#"import "Math::Advanced" as math; - let m = math::constants::get_mystic_number(); - m"# - )?, + engine.eval::(r#"let m = Math::Advanced::constants::get_mystic_number(); m"#)?, 42.0 ); Ok(()) @@ -73,8 +61,8 @@ fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { mod export_nested_by_prefix { use rhai::plugin::*; - #[export_module(export_prefix = "foo_")] + #[export_module(export_prefix = "foo_")] pub mod my_adds { pub mod foo_first_adders { use rhai::{FLOAT, INT}; @@ -131,26 +119,24 @@ mod export_nested_by_prefix { fn export_nested_by_prefix_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds); - let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced", m); - engine.set_module_resolver(Some(r)); + engine.register_static_module("Math::Advanced", m.into()); let output_array = engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_first_adders::add_float(ex, 1.0); + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_first_adders::add_float(ex, 1.0); - let ei = 41; - let fi = math::foo_first_adders::add_int(ei, 1); + let ei = 41; + let fi = Math::Advanced::foo_first_adders::add_int(ei, 1); - let gx = 41.0; - let hx = math::foo_second_adders::add_float(gx, 1.0); + let gx = 41.0; + let hx = Math::Advanced::foo_second_adders::add_float(gx, 1.0); - let gi = 41; - let hi = math::foo_second_adders::add_int(gi, 1); + let gi = 41; + let hi = Math::Advanced::foo_second_adders::add_int(gi, 1); - [fx, hx, fi, hi] - "#, + [fx, hx, fi, hi] + "#, )?; assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[1].as_float().unwrap(), &42.0); @@ -158,44 +144,44 @@ fn export_nested_by_prefix_test() -> Result<(), Box> { assert_eq!(&output_array[3].as_int().unwrap(), &42); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::foo_third_adders::add_float(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_third_adders::add_float (f64, f64)" - && p == rhai::Position::new(3, 41))); + r#" + let ex = 41.0; + let fx = Math::Advanced::foo_third_adders::add_float(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_third_adders::add_float (f64, f64)" + && p == rhai::Position::new(3, 52))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41; - let fx = math::foo_third_adders::add_int(ex, 1); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::foo_third_adders::add_int (i64, i64)" - && p == rhai::Position::new(3, 41))); + r#" + let ex = 41; + let fx = Math::Advanced::foo_third_adders::add_int(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::foo_third_adders::add_int (i64, i64)" + && p == rhai::Position::new(3, 52))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41; - let fx = math::bar_fourth_adders::add_int(ex, 1); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_fourth_adders::add_int (i64, i64)" - && p == rhai::Position::new(3, 42))); + r#" + let ex = 41; + let fx = Math::Advanced::bar_fourth_adders::add_int(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::bar_fourth_adders::add_int (i64, i64)" + && p == rhai::Position::new(3, 53))); assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::bar_fourth_adders::add_float(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_fourth_adders::add_float (f64, f64)" - && p == rhai::Position::new(3, 42))); + r#" + let ex = 41.0; + let fx = Math::Advanced::bar_fourth_adders::add_float(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "Math::Advanced::bar_fourth_adders::add_float (f64, f64)" + && p == rhai::Position::new(3, 53))); Ok(()) } diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 45c199df..4b3e7e30 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -127,19 +127,22 @@ The Rhai Scripting Language 7. [One Engine Instance Per Call](patterns/parallel.md) 8. [Scriptable Event Handler with State](patterns/events.md) 9. [Dynamic Constants Provider](patterns/dynamic-const.md) -9. [Advanced Topics](advanced.md) - 10. [Capture Scope for Function Call](language/fn-capture.md) - 11. [Low-Level API](rust/register-raw.md) - 12. [Variable Resolver](engine/var.md) - 13. [Use as DSL](engine/dsl.md) +9. [Advanced Topics](advanced.md) + 1. [Capture Scope for Function Call](language/fn-capture.md) + 2. [Low-Level API](rust/register-raw.md) + 3. [Variable Resolver](engine/var.md) + 4. [Use as DSL](engine/dsl.md) 1. [Disable Keywords and/or Operators](engine/disable.md) 2. [Custom Operators](engine/custom-op.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md) - 14. [Multiple Instantiation](patterns/multiple.md) - 15. [Functions Metadata](engine/metadata/index.md) - 4. [Generate Function Signatures](engine/metadata/gen_fn_sig.md) - 5. [Export Metadata to JSON](engine/metadata/export_to_json.md) -10. [Appendix](appendix/index.md) + 5. [Multiple Instantiation](patterns/multiple.md) + 6. [Functions Metadata](engine/metadata/index.md) + 1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md) + 2. [Export Metadata to JSON](engine/metadata/export_to_json.md) +10. [External Tools](tools/index.md) + 1. [Online Playground](tools/playground.md) + 2. [`rhai-doc`](tools/rhai-doc.md) +11. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators and Symbols](appendix/operators.md) 3. [Literals](appendix/literals.md) diff --git a/doc/src/about/related.md b/doc/src/about/related.md index 9b55003b..0f29cb16 100644 --- a/doc/src/about/related.md +++ b/doc/src/about/related.md @@ -15,13 +15,19 @@ Online Resources for Rhai * [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info -* [Online Playground][playground] - Run scripts directly from editor - * [Discord Chat](https://discord.gg/HquqbYFcZ9) - Rhai channel * [Reddit](https://www.reddit.com/r/Rhai) - Rhai community +External Tools +-------------- + +* [Online Playground][playground] - Run Rhai scripts directly from an editor in the browser + +* [`rhai-doc`] - Rhai script documentation tool + + Other Cool Projects ------------------- diff --git a/doc/src/engine/call-fn.md b/doc/src/engine/call-fn.md index a68925ac..466ec1ee 100644 --- a/doc/src/engine/call-fn.md +++ b/doc/src/engine/call-fn.md @@ -41,8 +41,8 @@ let mut scope = Scope::new(); // If arguments of the wrong types are passed, the Engine will not find the function. let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?; -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -// put arguments in a tuple +// ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// return type must be specified put arguments in a tuple let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?; // ^^^^^^^^^^ tuple of one diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md index e2a118e9..3dacfa9d 100644 --- a/doc/src/engine/custom-op.md +++ b/doc/src/engine/custom-op.md @@ -6,7 +6,8 @@ Custom Operators For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with customized operators performing specific logic. -`Engine::register_custom_operator` registers a keyword as a custom operator. +`Engine::register_custom_operator` registers a keyword as a custom operator, giving it a particular +_precedence_ (which cannot be zero). Example @@ -52,15 +53,23 @@ into a syntax that uses the corresponding function calls. Using `Engine::register_custom_operator` merely enables a convenient shortcut. -Must Follow Variable Naming --------------------------- +Must be a Valid Identifier or Reserved Symbol +-------------------------------------------- All custom operators must be _identifiers_ that follow the same naming rules as [variables]. +Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols), +[disabled operators or keywords][disable keywords and operators]. + ```rust engine.register_custom_operator("foo", 20); // 'foo' is a valid custom operator -engine.register_custom_operator("=>", 30); // <- error: '=>' is not a valid custom operator +engine.register_custom_operator("#", 20); // the reserved symbol '#' is also + // a valid custom operator + +engine.register_custom_operator("+", 30); // <- error: '+' is an active operator + +engine.register_custom_operator("=>", 30); // <- error: '=>' is an active symbol ``` diff --git a/doc/src/rust/modules/ast.md b/doc/src/rust/modules/ast.md index 1410fa20..80dd7c5a 100644 --- a/doc/src/rust/modules/ast.md +++ b/doc/src/rust/modules/ast.md @@ -7,22 +7,24 @@ Create a Module from an AST `Module::eval_ast_as_new` ------------------------ -A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables, +A [module] can be created from a single script (or pre-compiled [`AST`]) containing global variables, functions and sub-modules via the `Module::eval_ast_as_new` method. -See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to prepare -a Rhai script for this purpose as well as to control which functions/variables to export. +See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to +prepare a Rhai script for this purpose as well as to control which functions/variables to export. -When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module: +When given an [`AST`], it is first evaluated, then the following items are exposed as members of the +new [module]: * Global variables - all variables exported via the `export` statement (those not exported remain hidden). * Functions not specifically marked `private`. -* Global modules that remain in the [`Scope`] at the end of a script run. +* Global modules that remain in the [`Scope`] at the end of a script run (become sub-modules). -`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the module namespace -with the global namespace. Therefore, functions defined within the same module script can cross-call each other. +`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the +module namespace with the global namespace. Therefore, functions defined within the same module +script can cross-call each other. Examples diff --git a/doc/src/rust/modules/create.md b/doc/src/rust/modules/create.md index d31ccf42..d6d3496a 100644 --- a/doc/src/rust/modules/create.md +++ b/doc/src/rust/modules/create.md @@ -25,7 +25,6 @@ Use Case 1 - Make the `Module` Globally Available `Engine::register_global_module` registers a shared [module] into the _global_ namespace. All [functions] and [type iterators] can be accessed without _namespace qualifiers_. - Variables and sub-modules are **ignored**. This is by far the easiest way to expose a module's functionalities to Rhai. @@ -35,11 +34,11 @@ use rhai::{Engine, Module}; let mut module = Module::new(); // new module -// Use the 'set_fn_XXX' API to add functions. -let hash = module.set_fn_1("inc", |x: i64| Ok(x+1)); +// Use the 'Module::set_fn_XXX' API to add functions. +let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1)); // Remember to update the parameter names/types and return type metadata. -// 'set_fn_XXX' by default does not set function metadata. +// 'Module::set_fn_XXX' by default does not set function metadata. module.update_fn_metadata(hash, ["x: i64", "i64"]); // Register the module into the global namespace of the Engine. @@ -49,6 +48,19 @@ engine.register_global_module(module.into()); engine.eval::("inc(41)")? == 42; // no need to import module ``` +Registering a [module] via `Engine::register_global_module` is essentially the _same_ +as calling `Engine::register_fn` (or any of the `Engine::register_XXX` API) individually +on each top-level function within that [module]. In fact, the actual implementation of +`Engine::register_fn` etc. simply adds the function to an internal [module]! + +```rust +// The above is essentially the same as: +let mut engine = Engine::new(); + +engine.register_fn("inc", |x: i64| x + 1); + +engine.eval::("inc(41)")? == 42; // no need to import module +``` Use Case 2 - Make the `Module` a Static Module --------------------------------------------- @@ -60,25 +72,27 @@ use rhai::{Engine, Module}; let mut module = Module::new(); // new module -// Use the 'set_fn_XXX' API to add functions. -let hash = module.set_fn_1("inc", |x: i64| Ok(x+1)); +// Use the 'Module::set_fn_XXX' API to add functions. +let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1)); // Remember to update the parameter names/types and return type metadata. -// 'set_fn_XXX' by default does not set function metadata. +// 'Module::set_fn_XXX' by default does not set function metadata. module.update_fn_metadata(hash, ["x: i64", "i64"]); -// Register the module into the Engine as a static module namespace 'calc' +// Register the module into the Engine as the static module namespace path +// 'services::calc' let mut engine = Engine::new(); -engine.register_static_module("calc", module.into()); +engine.register_static_module("services::calc", module.into()); -engine.eval::("calc::inc(41)")? == 42; // refer to the 'Calc' module +// refer to the 'services::calc' module +engine.eval::("services::calc::inc(41)")? == 42; ``` ### Expose Functions to the Global Namespace -`Module::set_fn_mut` and `Module::set_fn_XXX_mut` can optionally expose functions (usually _methods_) -in the module to the _global_ namespace, so [getters/setters] and [indexers] for [custom types] -can work as expected. +The `Module::set_fn_XXX_mut` API methods can optionally expose functions in the [module] +to the _global_ namespace by setting the `namespace` parameter to `FnNamespace::Global`, +so [getters/setters] and [indexers] for [custom types] can work as expected. [Type iterators], because of their special nature, are _always_ exposed to the _global_ namespace. @@ -87,19 +101,24 @@ use rhai::{Engine, Module, FnNamespace}; let mut module = Module::new(); // new module -// Expose method 'inc' to the global namespace (default is 'Internal') -let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x+1)); +// Expose method 'inc' to the global namespace (default is 'FnNamespace::Internal') +let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x + 1)); // Remember to update the parameter names/types and return type metadata. -// 'set_fn_XXX' by default does not set function metadata. +// 'Module::set_fn_XXX_mut' by default does not set function metadata. module.update_fn_metadata(hash, ["x: &mut i64", "i64"]); // Register the module into the Engine as a static module namespace 'calc' let mut engine = Engine::new(); engine.register_static_module("calc", module.into()); -// The method 'inc' works as expected because it is exposed to the global namespace +// 'inc' works when qualified by the namespace +engine.eval::("calc::inc(41)")? == 42; + +// 'inc' also works without a namespace qualifier +// because it is exposed to the global namespace engine.eval::("let x = 41; x.inc()")? == 42; +engine.eval::("let x = 41; inc(x)")? == 42; ``` @@ -118,7 +137,7 @@ use rhai::module_resolvers::StaticModuleResolver; let mut module = Module::new(); // new module module.set_var("answer", 41_i64); // variable 'answer' under module -module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions +module.set_fn_1("inc", |x: i64| Ok(x + 1)); // use the 'set_fn_XXX' API to add functions // Create the module resolver let mut resolver = StaticModuleResolver::new(); @@ -129,7 +148,7 @@ resolver.insert("question", module); // Set the module resolver into the 'Engine' let mut engine = Engine::new(); -engine.set_module_resolver(Some(resolver)); +engine.set_module_resolver(resolver); // Use namespace-qualified variables engine.eval::(r#"import "question" as q; q::answer + 1"#)? == 42; diff --git a/doc/src/rust/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md index 0b8f789c..1094e208 100644 --- a/doc/src/rust/modules/imp-resolver.md +++ b/doc/src/rust/modules/imp-resolver.md @@ -62,7 +62,7 @@ impl ModuleResolver for MyModuleResolver { let mut engine = Engine::new(); // Set the custom module resolver into the 'Engine'. -engine.set_module_resolver(Some(MyModuleResolver {})); +engine.set_module_resolver(MyModuleResolver {}); engine.consume(r#" import "hello" as foo; // this 'import' statement will call diff --git a/doc/src/rust/modules/resolvers.md b/doc/src/rust/modules/resolvers.md index a56b9b14..c7ba3196 100644 --- a/doc/src/rust/modules/resolvers.md +++ b/doc/src/rust/modules/resolvers.md @@ -153,13 +153,19 @@ A collection of module resolvers. Modules will be resolved from each resolver in This is useful when multiple types of modules are needed simultaneously. +`DummyResolversCollection` +------------------------- + +This module resolver acts as a _dummy_ and always fails all module resolution calls. + + Set into `Engine` ----------------- An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: ```rust -use rhai::module_resolvers::StaticModuleResolver; +use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver}; // Create a module resolver let resolver = StaticModuleResolver::new(); @@ -167,8 +173,9 @@ let resolver = StaticModuleResolver::new(); // Register functions into 'resolver'... // Use the module resolver -engine.set_module_resolver(Some(resolver)); +engine.set_module_resolver(resolver); -// Effectively disable 'import' statements by setting module resolver to 'None' -engine.set_module_resolver(None); +// Effectively disable 'import' statements by setting module resolver to +// the 'DummyModuleResolver' which acts as... well... a dummy. +engine.set_module_resolver(DummyModuleResolver::new()); ``` diff --git a/doc/src/rust/override.md b/doc/src/rust/override.md index f6d4d656..7620bfd5 100644 --- a/doc/src/rust/override.md +++ b/doc/src/rust/override.md @@ -3,18 +3,19 @@ Override a Built-in Function {{#include ../links.md}} -Any similarly-named function defined in a script overrides any built-in or registered +Any similarly-named function defined in a script _overrides_ any built-in or registered native Rust function of the same name and number of parameters. ```rust -// Override the built-in function 'to_int' -fn to_int(num) { - print("Ha! Gotcha! " + num); +// Override the built-in function 'to_float' when called as a method +fn to_float() { + print("Ha! Gotcha! " + this); + 42.0 } -let x = (123).to_int(); +let x = 123.to_float(); -print(x); // what happens? +print(x); // what happens? ``` A registered native Rust function, in turn, overrides any built-in function of the diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md index bd3064c8..0b343a52 100644 --- a/doc/src/start/examples/rust.md +++ b/doc/src/start/examples/rust.md @@ -11,13 +11,13 @@ A number of examples can be found in the `examples` directory: | [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it | | [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result | | [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] | -| [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script | +| [`rhai-repl`]({{repoTree}}/examples/rhai-repl.rs) | a simple REPL, interactively evaluate statements from stdin | +| [`rhai-run`]({{repoTree}}/examples/rhai-run.rs) | runs each filename passed to it as a Rhai script | | [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).
The [`serde`] feature is required to run | | [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function | | [`strings`]({{repoTree}}/examples/strings.rs) | shows different ways to register functions taking string arguments | -| [`repl`]({{repoTree}}/examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin | -The `repl` example is a particularly good one as it allows one to interactively try out Rhai's +The `rhai-repl` example is a particularly good one as it allows one to interactively try out Rhai's language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop). diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md index f3167a93..8de664a5 100644 --- a/doc/src/start/examples/scripts.md +++ b/doc/src/start/examples/scripts.md @@ -46,8 +46,8 @@ The following scripts are for benchmarking the speed of Rhai: Running Example Scripts ---------------------- -The [`rhai_runner`](../examples/rust.md) example can be used to run the scripts: +The [`rhai-run`](../examples/rust.md) example can be used to run the scripts: ```bash -cargo run --example rhai_runner scripts/any_script.rhai +cargo run --example rhai-run scripts/any_script.rhai ``` diff --git a/doc/src/tools/index.md b/doc/src/tools/index.md new file mode 100644 index 00000000..9b9eff01 --- /dev/null +++ b/doc/src/tools/index.md @@ -0,0 +1,6 @@ +External Tools +============== + +{{#include ../links.md}} + +External tools available to work with Rhai. diff --git a/doc/src/tools/playground.md b/doc/src/tools/playground.md new file mode 100644 index 00000000..91507a3d --- /dev/null +++ b/doc/src/tools/playground.md @@ -0,0 +1,15 @@ +Online Playground +================= + +{{#include ../links.md}} + + +The Online Playground runs off a [WASM] build of Rhai and allows evaluating +Rhai scripts directly within a browser editor window. + + +Author : [`@alvinhochun`](https://github.com/alvinhochun) + +Repo : [On GitHub](https://github.com/alvinhochun/rhai-playground) + +URL : [Link to Online Playground][playground] diff --git a/doc/src/tools/rhai-doc.md b/doc/src/tools/rhai-doc.md new file mode 100644 index 00000000..fd32be6a --- /dev/null +++ b/doc/src/tools/rhai-doc.md @@ -0,0 +1,6 @@ +Rhai Script Documentation Tool +============================= + +{{#include ../links.md}} + +<< TODO >> \ No newline at end of file diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index 0ad34051..094a7d08 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -1,38 +1,40 @@ -use rhai::{Engine, RegisterFn, INT}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] struct TestStruct { x: INT, } impl TestStruct { - fn update(&mut self) { + pub fn update(&mut self) { self.x += 1000; } - fn new() -> Self { + pub fn new() -> Self { Self { x: 1 } } } #[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_object"))] -fn main() { +fn main() -> Result<(), Box> { let mut engine = Engine::new(); engine .register_type::() - .register_fn("update", TestStruct::update) - .register_fn("new_ts", TestStruct::new); + .register_fn("new_ts", TestStruct::new) + .register_fn("update", TestStruct::update); println!( "{:?}", - engine.eval::("let x = new_ts(); x.update(); x") + engine.eval::("let x = new_ts(); x.update(); x")? ); println!( "{:?}", - engine.eval::("let x = [new_ts()]; x[0].update(); x[0]") + engine.eval::("let x = [new_ts()]; x[0].update(); x[0]")? ); + + Ok(()) } #[cfg(any(feature = "no_index", feature = "no_object"))] diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index 4b0118fa..c48a7fde 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -1,16 +1,16 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT}; -#[derive(Clone)] +#[derive(Debug, Clone)] struct TestStruct { x: INT, } impl TestStruct { - fn update(&mut self) { + pub fn update(&mut self) { self.x += 1000; } - fn new() -> Self { + pub fn new() -> Self { Self { x: 1 } } } @@ -21,8 +21,8 @@ fn main() -> Result<(), Box> { engine .register_type::() - .register_fn("update", TestStruct::update) - .register_fn("new_ts", TestStruct::new); + .register_fn("new_ts", TestStruct::new) + .register_fn("update", TestStruct::update); let result = engine.eval::("let x = new_ts(); x.update(); x")?; diff --git a/examples/repl.rs b/examples/rhai-repl.rs similarity index 96% rename from examples/repl.rs rename to examples/rhai-repl.rs index 92e04a71..f60a0a81 100644 --- a/examples/repl.rs +++ b/examples/rhai-repl.rs @@ -43,7 +43,7 @@ fn print_help() { println!("quit, exit => quit"); println!("scope => print all variables in the scope"); println!("functions => print all functions defined"); - println!("ast => print the last AST"); + println!("ast => print the last AST (optimized)"); println!("astu => print the last raw, un-optimized AST"); println!(r"end a line with '\' to continue to the next line."); println!(); @@ -67,7 +67,7 @@ fn main() { print_help(); 'main_loop: loop { - print!("rhai> "); + print!("rhai-repl> "); stdout().flush().expect("couldn't flush stdout"); input.clear(); @@ -126,12 +126,12 @@ fn main() { } "astu" => { // print the last un-optimized AST - println!("{:#?}\n", &ast_u); + println!("{:#?}\n", ast_u); continue; } "ast" => { // print the last AST - println!("{:#?}\n", &ast); + println!("{:#?}\n", ast); continue; } "functions" => { diff --git a/examples/rhai_runner.rs b/examples/rhai-run.rs similarity index 100% rename from examples/rhai_runner.rs rename to examples/rhai-run.rs diff --git a/examples/serde.rs b/examples/serde.rs index 1868f44b..eca31453 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -17,13 +17,13 @@ mod example { use rhai::{Dynamic, Engine, Map}; use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize)] + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] struct Point { x: f64, y: f64, } - #[derive(Debug, Serialize, Deserialize)] + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] struct MyStruct { a: i64, b: Vec, @@ -71,6 +71,18 @@ mod example { // Convert the 'Dynamic' object map into 'MyStruct' let x: MyStruct = from_dynamic(&result).unwrap(); + assert_eq!( + x, + MyStruct { + a: 42, + b: vec!["hello".into(), "world".into()], + c: true, + d: Point { + x: 123.456, + y: 999.0, + }, + } + ); println!("Deserialized to struct: {:#?}", x); } } diff --git a/examples/strings.rs b/examples/strings.rs index f24e9545..dc5f76b3 100644 --- a/examples/strings.rs +++ b/examples/strings.rs @@ -69,7 +69,7 @@ fn main() -> Result<(), Box> { display("Trimmed", x); display("Trimmed Length", x.len()); display("Index of \"!!!\"", x.index_of("!!!")); - "#, + "#, )?; println!(); diff --git a/scripts/README.md b/scripts/README.md index ca204dd9..f3bf5b4a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -7,14 +7,14 @@ Testing scripts written in Rhai. How to Run ---------- -Compile the `rhai_runner` example: +Compile the `rhai-run` example: ```bash -cargo build --example rhai_runner +cargo build --example rhai-run ``` Run it: ```bash -./target/debug/examples/rhai_runner ./scripts/test_script_to_run.rhai +./target/debug/examples/rhai-run ./scripts/test_script_to_run.rhai ``` diff --git a/src/ast.rs b/src/ast.rs index bc801376..b6c211c6 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -9,7 +9,7 @@ use crate::stdlib::{ collections::HashMap, fmt, hash::Hash, - num::NonZeroUsize, + num::{NonZeroU64, NonZeroUsize}, ops::{Add, AddAssign}, string::String, vec, @@ -736,7 +736,7 @@ impl Stmt { _ => false, } } - /// Get the [position][`Position`] of this statement. + /// Get the [position][Position] of this statement. pub fn position(&self) -> Position { match self { Self::Noop(pos) @@ -765,7 +765,7 @@ impl Stmt { Self::Share(x) => x.pos, } } - /// Override the [position][`Position`] of this statement. + /// Override the [position][Position] of this statement. pub fn set_position(&mut self, new_pos: Position) -> &mut Self { match self { Self::Noop(pos) @@ -914,16 +914,14 @@ pub struct BinaryExpr { #[derive(Debug, Clone, Default)] pub struct FnCallExpr { /// Pre-calculated hash for a script-defined function of the same name and number of parameters. - pub hash: u64, - /// Call native functions only? Set to [`true`] to skip searching for script-defined function overrides - /// when it is certain that the function must be native (e.g. an operator). - pub native_only: bool, + /// None if native Rust only. + pub hash_script: Option, /// Does this function call capture the parent scope? pub capture: bool, /// Default value when the function is not found, mostly used to provide a default for comparison functions. pub def_value: Option, /// Namespace of the function, if any. Boxed because it occurs rarely. - pub namespace: Option>, + pub namespace: Option, /// Function name. /// Use [`Cow<'static, str>`][Cow] because a lot of operators (e.g. `==`, `>=`) are implemented as /// function calls and the function names are predictable, so no need to allocate a new [`String`]. @@ -963,13 +961,19 @@ pub enum Expr { Map(Box>, Position), /// () Unit(Position), - /// Variable access - (optional index, optional modules, hash, variable name) - Variable(Box<(Option, Option>, u64, Ident)>), + /// Variable access - (optional index, optional (hash, modules), variable name) + Variable( + Box<( + Option, + Option<(NonZeroU64, NamespaceRef)>, + Ident, + )>, + ), /// Property access - (getter, setter), prop - Property(Box<((ImmutableString, ImmutableString), Ident)>), + Property(Box<(ImmutableString, ImmutableString, Ident)>), /// { [statement][Stmt] } Stmt(Box>, Position), - /// Wrapped [expression][`Expr`] - should not be optimized away. + /// Wrapped [expression][Expr] - should not be optimized away. Expr(Box), /// func `(` expr `,` ... `)` FnCall(Box, Position), @@ -1044,11 +1048,11 @@ impl Expr { /// Is the expression a simple variable access? pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { match self { - Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.3).name.as_str()), + Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()), _ => None, } } - /// Get the [position][`Position`] of the expression. + /// Get the [position][Position] of the expression. pub fn position(&self) -> Position { match self { Self::Expr(x) => x.position(), @@ -1064,9 +1068,9 @@ impl Expr { Self::FnPointer(_, pos) => *pos, Self::Array(_, pos) => *pos, Self::Map(_, pos) => *pos, - Self::Property(x) => (x.1).pos, + Self::Property(x) => (x.2).pos, Self::Stmt(_, pos) => *pos, - Self::Variable(x) => (x.3).pos, + Self::Variable(x) => (x.2).pos, Self::FnCall(_, pos) => *pos, Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(), @@ -1078,7 +1082,7 @@ impl Expr { Self::Custom(_, pos) => *pos, } } - /// Override the [position][`Position`] of the expression. + /// Override the [position][Position] of the expression. pub fn set_position(&mut self, new_pos: Position) -> &mut Self { match self { Self::Expr(x) => { @@ -1096,8 +1100,8 @@ impl Expr { Self::FnPointer(_, pos) => *pos = new_pos, Self::Array(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos, - Self::Variable(x) => (x.3).pos = new_pos, - Self::Property(x) => (x.1).pos = new_pos, + Self::Variable(x) => (x.2).pos = new_pos, + Self::Property(x) => (x.2).pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos, Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, @@ -1172,6 +1176,12 @@ impl Expr { } /// Is a particular [token][Token] allowed as a postfix operator to this expression? pub fn is_valid_postfix(&self, token: &Token) -> bool { + match token { + #[cfg(not(feature = "no_object"))] + Token::Period => return true, + _ => (), + } + match self { Self::Expr(x) => x.is_valid_postfix(token), @@ -1189,24 +1199,20 @@ impl Expr { | Self::Unit(_) => false, Self::StringConstant(_, _) - | Self::Stmt(_, _) | Self::FnCall(_, _) + | Self::Stmt(_, _) | Self::Dot(_, _) | Self::Index(_, _) | Self::Array(_, _) | Self::Map(_, _) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, - #[cfg(not(feature = "no_object"))] - Token::Period => true, _ => false, }, Self::Variable(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, - #[cfg(not(feature = "no_object"))] - Token::Period => true, Token::LeftParen => true, Token::Bang => true, Token::DoubleColon => true, @@ -1216,8 +1222,6 @@ impl Expr { Self::Property(_) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, - #[cfg(not(feature = "no_object"))] - Token::Period => true, Token::LeftParen => true, _ => false, }, diff --git a/src/dynamic.rs b/src/dynamic.rs index c6efb8b8..ba4abb5d 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -748,13 +748,16 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed), AccessMode::ReadWrite)) } - /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` - /// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature. + /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an + /// [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` or + /// [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` + /// depending on the `sync` feature. /// /// Shared [`Dynamic`] values are relatively cheap to clone as they simply increment the /// reference counts. /// - /// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] values. + /// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] + /// values. /// /// If the [`Dynamic`] value is already shared, this method returns itself. /// @@ -970,8 +973,8 @@ impl Dynamic { /// /// If the [`Dynamic`] is not a shared value, it returns itself. /// - /// If the [`Dynamic`] is a shared value, it returns the shared value if there are - /// no outstanding references, or a cloned copy. + /// If the [`Dynamic`] is a shared value, it returns the shared value if there are no + /// outstanding references, or a cloned copy. #[inline(always)] pub fn flatten(self) -> Self { match self.0 { diff --git a/src/engine.rs b/src/engine.rs index bb6cb872..012578c9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,8 +18,8 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, - iter::{empty, once}, - num::NonZeroUsize, + iter::{empty, once, FromIterator}, + num::{NonZeroU64, NonZeroU8, NonZeroUsize}, ops::DerefMut, string::{String, ToString}, }; @@ -112,23 +112,15 @@ impl Imports { } /// Does the specified function hash key exist in this stack of imported [modules][Module]? #[allow(dead_code)] - pub fn contains_fn(&self, hash: u64) -> bool { - if hash == 0 { - false - } else { - self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash)) - } + pub fn contains_fn(&self, hash: NonZeroU64) -> bool { + self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash)) } /// Get specified function via its hash key. - pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { - if hash == 0 { - None - } else { - self.0 - .iter() - .rev() - .find_map(|(_, m)| m.get_qualified_fn(hash)) - } + pub fn get_fn(&self, hash: NonZeroU64) -> Option<&CallableFunction> { + self.0 + .iter() + .rev() + .find_map(|(_, m)| m.get_qualified_fn(hash)) } /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]? #[allow(dead_code)] @@ -144,6 +136,22 @@ impl Imports { } } +impl<'a, T: IntoIterator)>> From for Imports { + fn from(value: T) -> Self { + Self( + value + .into_iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(), + ) + } +} +impl FromIterator<(ImmutableString, Shared)> for Imports { + fn from_iter)>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] pub const MAX_CALL_STACK_DEPTH: usize = 8; @@ -478,7 +486,7 @@ pub struct State { /// Number of modules loaded. pub modules: usize, /// Cached lookup values for function hashes. - pub functions_cache: HashMap, StraightHasherBuilder>, + pub functions_cache: HashMap, StraightHasherBuilder>, } impl State { @@ -616,11 +624,11 @@ pub struct Engine { /// A collection of all modules loaded into the global namespace of the Engine. pub(crate) global_modules: StaticVec>, /// A collection of all sub-modules directly loaded into the Engine. - pub(crate) global_sub_modules: Imports, + pub(crate) global_sub_modules: HashMap>, /// A module resolution service. #[cfg(not(feature = "no_module"))] - pub(crate) module_resolver: Option>, + pub(crate) module_resolver: Box, /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, @@ -628,7 +636,7 @@ pub struct Engine { /// A hashset containing symbols to disable. pub(crate) disabled_symbols: HashSet, /// A hashmap containing custom keywords and precedence to recognize. - pub(crate) custom_keywords: HashMap>, + pub(crate) custom_keywords: HashMap>, /// Custom syntax. pub(crate) custom_syntax: HashMap, /// Callback closure for resolving variable access. @@ -752,7 +760,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] #[cfg(not(target_arch = "wasm32"))] - module_resolver: Some(Box::new(crate::module::resolvers::FileModuleResolver::new())), + module_resolver: Box::new(crate::module::resolvers::FileModuleResolver::new()), #[cfg(not(feature = "no_module"))] #[cfg(any(feature = "no_std", target_arch = "wasm32",))] module_resolver: None, @@ -815,7 +823,7 @@ impl Engine { global_sub_modules: Default::default(), #[cfg(not(feature = "no_module"))] - module_resolver: None, + module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()), type_names: Default::default(), disabled_symbols: Default::default(), @@ -856,19 +864,19 @@ impl Engine { /// Search for a variable within the scope or within imports, /// depending on whether the variable name is namespace-qualified. - pub(crate) fn search_namespace<'s, 'a>( + pub(crate) fn search_namespace<'s>( &self, scope: &'s mut Scope, mods: &mut Imports, state: &mut State, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, - expr: &'a Expr, - ) -> Result<(Target<'s>, &'a str, Position), Box> { + expr: &Expr, + ) -> Result<(Target<'s>, Position), Box> { match expr { Expr::Variable(v) => match v.as_ref() { // Qualified variable - (_, Some(modules), hash_var, Ident { name, pos }) => { + (_, Some((hash_var, modules)), Ident { name, pos }) => { let module = search_imports(mods, state, modules)?; let target = module.get_qualified_var(*hash_var).map_err(|mut err| { match *err { @@ -883,7 +891,7 @@ impl Engine { // Module variables are constant let mut target = target.clone(); target.set_access_mode(AccessMode::ReadOnly); - Ok((target.into(), name, *pos)) + Ok((target.into(), *pos)) } // Normal variable access _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), @@ -893,16 +901,16 @@ impl Engine { } /// Search for a variable within the scope - pub(crate) fn search_scope_only<'s, 'a>( + pub(crate) fn search_scope_only<'s>( &self, scope: &'s mut Scope, mods: &mut Imports, state: &mut State, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, - expr: &'a Expr, - ) -> Result<(Target<'s>, &'a str, Position), Box> { - let (index, _, _, Ident { name, pos }) = match expr { + expr: &Expr, + ) -> Result<(Target<'s>, Position), Box> { + let (index, _, Ident { name, pos }) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), }; @@ -910,7 +918,7 @@ impl Engine { // Check if the variable is `this` if name.as_str() == KEYWORD_THIS { if let Some(val) = this_ptr { - return Ok(((*val).into(), KEYWORD_THIS, *pos)); + return Ok(((*val).into(), *pos)); } else { return EvalAltResult::ErrorUnboundThis(*pos).into(); } @@ -938,7 +946,7 @@ impl Engine { resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? { result.set_access_mode(AccessMode::ReadOnly); - return Ok((result.into(), name, *pos)); + return Ok((result.into(), *pos)); } } @@ -952,7 +960,7 @@ impl Engine { .0 }; - let val = scope.get_mut(index); + let val = scope.get_mut_by_index(index); // Check for data race - probably not necessary because the only place it should conflict is in a method call // when the object variable is also used as a parameter. @@ -960,7 +968,7 @@ impl Engine { // return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // } - Ok((val.into(), name, *pos)) + Ok((val.into(), *pos)) } /// Chain-evaluate a dot/index chain. @@ -980,7 +988,7 @@ impl Engine { new_val: Option<(Dynamic, Position)>, ) -> Result<(Dynamic, bool), Box> { if chain_type == ChainType::None { - panic!(); + unreachable!(); } let is_ref = target.is_ref(); @@ -1047,7 +1055,7 @@ impl Engine { let args = &mut [target_val, &mut idx_val2, &mut new_val.0]; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, 0, args, is_ref, true, false, + mods, state, lib, FN_IDX_SET, None, args, is_ref, true, false, new_val.1, None, None, level, ) .map_err(|err| match *err { @@ -1083,23 +1091,22 @@ impl Engine { Expr::FnCall(x, pos) if x.namespace.is_none() => { let FnCallExpr { name, - native_only: native, - hash, + hash_script: hash, def_value, .. } = x.as_ref(); let def_value = def_value.as_ref(); let args = idx_val.as_fn_call_args(); self.make_method_call( - mods, state, lib, name, *hash, target, args, def_value, *native, false, - *pos, level, + mods, state, lib, name, *hash, target, args, def_value, false, *pos, + level, ) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_, _) => unreachable!(), // {xxx:map}.id = ??? Expr::Property(x) if target_val.is::() && new_val.is_some() => { - let Ident { name, pos } = &x.1; + let Ident { name, pos } = &x.2; let index = name.clone().into(); let mut val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, true, is_ref, false, level, @@ -1112,7 +1119,7 @@ impl Engine { } // {xxx:map}.id Expr::Property(x) if target_val.is::() => { - let Ident { name, pos } = &x.1; + let Ident { name, pos } = &x.2; let index = name.clone().into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, false, level, @@ -1122,11 +1129,11 @@ impl Engine { } // xxx.id = ??? Expr::Property(x) if new_val.is_some() => { - let ((_, setter), Ident { pos, .. }) = x.as_ref(); + let (_, setter, Ident { pos, .. }) = x.as_ref(); let mut new_val = new_val; let mut args = [target_val, &mut new_val.as_mut().unwrap().0]; self.exec_fn_call( - mods, state, lib, setter, 0, &mut args, is_ref, true, false, *pos, + mods, state, lib, setter, None, &mut args, is_ref, true, false, *pos, None, None, level, ) .map(|(v, _)| (v, true)) @@ -1134,10 +1141,10 @@ impl Engine { } // xxx.id Expr::Property(x) => { - let ((getter, _), Ident { pos, .. }) = x.as_ref(); + let (getter, _, Ident { pos, .. }) = x.as_ref(); let mut args = [target_val]; self.exec_fn_call( - mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos, + mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos, None, None, level, ) .map(|(v, _)| (v, false)) @@ -1147,7 +1154,7 @@ impl Engine { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::() => { let mut val = match &x.lhs { Expr::Property(p) => { - let Ident { name, pos } = &p.1; + let Ident { name, pos } = &p.2; let index = name.clone().into(); self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, true, @@ -1158,16 +1165,15 @@ impl Engine { Expr::FnCall(x, pos) if x.namespace.is_none() => { let FnCallExpr { name, - native_only: native, - hash, + hash_script: hash, def_value, .. } = x.as_ref(); let def_value = def_value.as_ref(); let args = idx_val.as_fn_call_args(); let (val, _) = self.make_method_call( - mods, state, lib, name, *hash, target, args, def_value, - *native, false, *pos, level, + mods, state, lib, name, *hash, target, args, def_value, false, + *pos, level, )?; val.into() } @@ -1188,12 +1194,12 @@ impl Engine { match &x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(p) => { - let ((getter, setter), Ident { pos, .. }) = p.as_ref(); + let (getter, setter, Ident { pos, .. }) = p.as_ref(); let arg_values = &mut [target_val, &mut Default::default()]; let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - mods, state, lib, getter, 0, args, is_ref, true, false, + mods, state, lib, getter, None, args, is_ref, true, false, *pos, None, None, level, ) .map_err(|err| err.fill_position(*pos))?; @@ -1220,7 +1226,7 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed arg_values[1] = val; self.exec_fn_call( - mods, state, lib, setter, 0, arg_values, is_ref, true, + mods, state, lib, setter, None, arg_values, is_ref, true, false, *pos, None, None, level, ) .or_else( @@ -1240,16 +1246,15 @@ impl Engine { Expr::FnCall(f, pos) if f.namespace.is_none() => { let FnCallExpr { name, - native_only: native, - hash, + hash_script: hash, def_value, .. } = f.as_ref(); let def_value = def_value.as_ref(); let args = idx_val.as_fn_call_args(); let (mut val, _) = self.make_method_call( - mods, state, lib, name, *hash, target, args, def_value, - *native, false, *pos, level, + mods, state, lib, name, *hash, target, args, def_value, false, + *pos, level, )?; let val = &mut val; let target = &mut val.into(); @@ -1306,11 +1311,11 @@ impl Engine { let Ident { name: var_name, pos: var_pos, - } = &x.3; + } = &x.2; self.inc_operations(state, *var_pos)?; - let (target, _, pos) = + let (target, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; // Constants cannot be modified @@ -1501,8 +1506,8 @@ impl Engine { let mut idx = idx; let args = &mut [target, &mut idx]; self.exec_fn_call( - _mods, state, _lib, FN_IDX_GET, 0, args, _is_ref, true, false, idx_pos, None, - None, _level, + _mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, false, idx_pos, + None, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1553,7 +1558,8 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let hash = - calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id())); + calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id())) + .unwrap(); let pos = rhs.position(); @@ -1603,7 +1609,7 @@ impl Engine { match expr { // var - point directly to the value Expr::Variable(_) => { - let (mut target, _, pos) = + let (mut target, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; // If necessary, constants are cloned @@ -1659,7 +1665,7 @@ impl Engine { if target.is::() { // map.prop - point directly to the item - let (_, Ident { name, pos }) = p.as_ref(); + let (_, _, Ident { name, pos }) = p.as_ref(); let idx = name.clone().into(); if target.is_shared() || target.is_value() { @@ -1677,10 +1683,10 @@ impl Engine { .map(|v| (v, *pos)) } else { // var.prop - call property getter - let ((getter, _), Ident { pos, .. }) = p.as_ref(); + let (getter, _, Ident { pos, .. }) = p.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( - mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos, + mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos, None, None, level, ) .map(|(v, _)| (v.into(), *pos)) @@ -1721,15 +1727,15 @@ impl Engine { Expr::StringConstant(x, _) => Ok(x.clone().into()), Expr::CharConstant(x, _) => Ok((*x).into()), Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()), - Expr::Variable(x) if (x.3).name == KEYWORD_THIS => { + Expr::Variable(x) if (x.2).name == KEYWORD_THIS => { if let Some(val) = this_ptr { Ok(val.clone()) } else { - EvalAltResult::ErrorUnboundThis((x.3).pos).into() + EvalAltResult::ErrorUnboundThis((x.2).pos).into() } } Expr::Variable(_) => { - let (val, _, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; + let (val, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; Ok(val.take_or_clone()) } Expr::Property(_) => unreachable!(), @@ -1778,17 +1784,16 @@ impl Engine { Expr::FnCall(x, pos) if x.namespace.is_none() => { let FnCallExpr { name, - native_only: native, capture: cap_scope, - hash, + hash_script: hash, args, def_value, .. } = x.as_ref(); let def_value = def_value.as_ref(); self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, def_value, *hash, *native, - false, *pos, *cap_scope, level, + scope, mods, state, lib, this_ptr, name, args, def_value, *hash, false, *pos, + *cap_scope, level, ) } @@ -1797,15 +1802,16 @@ impl Engine { let FnCallExpr { name, namespace, - hash, + hash_script: hash, args, def_value, .. } = x.as_ref(); - let namespace = namespace.as_ref().map(|v| v.as_ref()); + let namespace = namespace.as_ref(); + let hash = hash.unwrap(); let def_value = def_value.as_ref(); self.make_qualified_function_call( - scope, mods, state, lib, this_ptr, namespace, name, args, def_value, *hash, + scope, mods, state, lib, this_ptr, namespace, name, args, def_value, hash, *pos, level, ) } @@ -1935,11 +1941,15 @@ impl Engine { let mut rhs_val = self .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .flatten(); - let (mut lhs_ptr, name, pos) = + let (mut lhs_ptr, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; if !lhs_ptr.is_ref() { - return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into(); + return EvalAltResult::ErrorAssignmentToConstant( + lhs_expr.get_variable_access(false).unwrap().to_string(), + pos, + ) + .into(); } self.inc_operations(state, pos)?; @@ -1947,7 +1957,7 @@ impl Engine { if lhs_ptr.as_ref().is_read_only() { // Assignment to constant variable Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( - name.to_string(), + lhs_expr.get_variable_access(false).unwrap().to_string(), pos, ))) } else if op.is_empty() { @@ -1966,7 +1976,7 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); - let hash_fn = calc_native_fn_hash(empty(), op, arg_types); + let hash_fn = calc_native_fn_hash(empty(), op, arg_types).unwrap(); match self .global_namespace @@ -2015,8 +2025,8 @@ impl Engine { // Run function let (value, _) = self.exec_fn_call( - mods, state, lib, op, 0, args, false, false, false, *op_pos, None, - None, level, + mods, state, lib, op, None, args, false, false, false, *op_pos, + None, None, level, )?; let value = value.flatten(); @@ -2051,7 +2061,7 @@ impl Engine { let result = self .exec_fn_call( - mods, state, lib, op, 0, args, false, false, false, *op_pos, None, + mods, state, lib, op, None, args, false, false, false, *op_pos, None, None, level, ) .map(|(v, _)| v)?; @@ -2203,7 +2213,7 @@ impl Engine { state.scope_level += 1; for iter_value in func(iter_obj) { - let loop_var = scope.get_mut(index); + let loop_var = scope.get_mut_by_index(index); let value = iter_value.flatten(); if cfg!(not(feature = "no_closure")) && loop_var.is_shared() { @@ -2369,31 +2379,24 @@ impl Engine { .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? .try_cast::() { - if let Some(resolver) = &self.module_resolver { - let module = resolver.resolve(self, &path, expr.position())?; + let module = self.module_resolver.resolve(self, &path, expr.position())?; - if let Some(name_def) = alias { - if !module.is_indexed() { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::fn_native::shared_take_or_clone(module); - module.build_index(); - mods.push(name_def.name.clone(), module); - } else { - mods.push(name_def.name.clone(), module); - } - // When imports list is modified, clear the functions lookup cache - state.functions_cache.clear(); + if let Some(name_def) = alias { + if !module.is_indexed() { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut module = crate::fn_native::shared_take_or_clone(module); + module.build_index(); + mods.push(name_def.name.clone(), module); + } else { + mods.push(name_def.name.clone(), module); } - - state.modules += 1; - - Ok(Dynamic::UNIT) - } else { - Err( - EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position()) - .into(), - ) + // When imports list is modified, clear the functions lookup cache + state.functions_cache.clear(); } + + state.modules += 1; + + Ok(Dynamic::UNIT) } else { Err(self.make_type_mismatch_err::("", expr.position())) } @@ -2419,7 +2422,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { if let Some((index, _)) = scope.get_index(&x.name) { - let val = scope.get_mut(index); + let val = scope.get_mut_by_index(index); if !val.is_shared() { // Replace the variable with a shared value. diff --git a/src/engine_api.rs b/src/engine_api.rs index 0a38b820..026e0df8 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -209,9 +209,10 @@ impl Engine { pub fn register_get( &mut self, name: &str, - callback: impl Fn(&mut T) -> U + SendSync + 'static, + get_fn: impl Fn(&mut T) -> U + SendSync + 'static, ) -> &mut Self { - crate::RegisterFn::register_fn(self, &crate::engine::make_getter(name), callback) + use crate::{engine::make_getter, RegisterFn}; + self.register_fn(&make_getter(name), get_fn) } /// Register a getter function for a member of a registered type with the [`Engine`]. /// @@ -255,13 +256,10 @@ impl Engine { pub fn register_get_result( &mut self, name: &str, - callback: impl Fn(&mut T) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, ) -> &mut Self { - crate::RegisterResultFn::register_result_fn( - self, - &crate::engine::make_getter(name), - callback, - ) + use crate::{engine::make_getter, RegisterResultFn}; + self.register_result_fn(&make_getter(name), get_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -304,9 +302,10 @@ impl Engine { pub fn register_set( &mut self, name: &str, - callback: impl Fn(&mut T, U) + SendSync + 'static, + set_fn: impl Fn(&mut T, U) + SendSync + 'static, ) -> &mut Self { - crate::RegisterFn::register_fn(self, &crate::engine::make_setter(name), callback) + use crate::{engine::make_setter, RegisterFn}; + self.register_fn(&make_setter(name), set_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -352,13 +351,12 @@ impl Engine { pub fn register_set_result( &mut self, name: &str, - callback: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { - crate::RegisterResultFn::register_result_fn( - self, - &crate::engine::make_setter(name), - move |obj: &mut T, value: U| callback(obj, value).map(Into::into), - ) + use crate::{engine::make_setter, RegisterResultFn}; + self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { + set_fn(obj, value).map(Into::into) + }) } /// Short-hand for registering both getter and setter functions /// of a registered type with the [`Engine`]. @@ -453,7 +451,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_get( &mut self, - callback: impl Fn(&mut T, X) -> U + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -469,7 +467,8 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_GET, callback) + use crate::{engine::FN_IDX_GET, RegisterFn}; + self.register_fn(FN_IDX_GET, get_fn) } /// Register an index getter for a custom type with the [`Engine`]. /// @@ -518,7 +517,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_get_result( &mut self, - callback: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -534,7 +533,8 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterResultFn::register_result_fn(self, crate::engine::FN_IDX_GET, callback) + use crate::{engine::FN_IDX_GET, RegisterResultFn}; + self.register_result_fn(FN_IDX_GET, get_fn) } /// Register an index setter for a custom type with the [`Engine`]. /// @@ -581,7 +581,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_set( &mut self, - callback: impl Fn(&mut T, X, U) + SendSync + 'static, + set_fn: impl Fn(&mut T, X, U) + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -597,7 +597,8 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_SET, callback) + use crate::{engine::FN_IDX_SET, RegisterFn}; + self.register_fn(FN_IDX_SET, set_fn) } /// Register an index setter for a custom type with the [`Engine`]. /// @@ -651,7 +652,7 @@ impl Engine { U: Variant + Clone, >( &mut self, - callback: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -667,11 +668,10 @@ impl Engine { panic!("Cannot register indexer for strings."); } - crate::RegisterResultFn::register_result_fn( - self, - crate::engine::FN_IDX_SET, - move |obj: &mut T, index: X, value: U| callback(obj, index, value).map(Into::into), - ) + use crate::{engine::FN_IDX_SET, RegisterResultFn}; + self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| { + set_fn(obj, index, value).map(Into::into) + }) } /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. /// @@ -723,21 +723,22 @@ impl Engine { self.register_indexer_get(getter) .register_indexer_set(setter) } - /// Register a shared [`Module`][crate::Module] into the global namespace of [`Engine`]. + /// Register a shared [`Module`] into the global namespace of [`Engine`]. /// - /// All functions and type iterators are automatically available to scripts without namespace qualifications. + /// All functions and type iterators are automatically available to scripts without namespace + /// qualifications. /// /// Sub-modules and variables are **ignored**. /// - /// When searching for functions, modules loaded later are preferred. - /// In other words, loaded modules are searched in reverse order. + /// When searching for functions, modules loaded later are preferred. In other words, loaded + /// modules are searched in reverse order. #[inline(always)] pub fn register_global_module(&mut self, module: Shared) -> &mut Self { // Insert the module into the front self.global_modules.insert(0, module); self } - /// Register a shared [`Module`][crate::Module] into the global namespace of [`Engine`]. + /// Register a shared [`Module`] into the global namespace of [`Engine`]. /// /// ## Deprecated /// @@ -747,15 +748,17 @@ impl Engine { pub fn load_package(&mut self, module: impl Into>) -> &mut Self { self.register_global_module(module.into()) } - /// Register a shared [`Module`][crate::Module] as a static module namespace with the [`Engine`]. + /// Register a shared [`Module`] as a static module namespace with the + /// [`Engine`]. /// - /// Functions marked `FnNamespace::Global` and type iterators are exposed to scripts without namespace qualifications. + /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without + /// namespace qualifications. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Module}; + /// use rhai::{Engine, Shared, Module}; /// /// let mut engine = Engine::new(); /// @@ -763,9 +766,18 @@ impl Engine { /// let mut module = Module::new(); /// module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// - /// // Register the module as a fixed sub-module - /// engine.register_static_module("CalcService", module.into()); + /// let module: Shared = module.into(); /// + /// // Register the module as a fixed sub-module + /// engine.register_static_module("foo::bar::baz", module.clone()); + /// + /// // Multiple registrations to the same partial path is also OK! + /// engine.register_static_module("foo::bar::hello", module.clone()); + /// + /// engine.register_static_module("CalcService", module); + /// + /// assert_eq!(engine.eval::("foo::bar::baz::calc(41)")?, 42); + /// assert_eq!(engine.eval::("foo::bar::hello::calc(41)")?, 42); /// assert_eq!(engine.eval::("CalcService::calc(41)")?, 42); /// # Ok(()) /// # } @@ -773,20 +785,50 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub fn register_static_module( &mut self, - name: impl Into, + name: impl AsRef, module: Shared, ) -> &mut Self { - if !module.is_indexed() { - // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::fn_native::shared_take_or_clone(module); - module.build_index(); - self.global_sub_modules.push(name, module); - } else { - self.global_sub_modules.push(name, module); + fn register_static_module_raw( + root: &mut crate::stdlib::collections::HashMap>, + name: impl AsRef, + module: Shared, + ) { + let separator = crate::token::Token::DoubleColon.syntax(); + + if !name.as_ref().contains(separator.as_ref()) { + if !module.is_indexed() { + // Index the module (making a clone copy if necessary) if it is not indexed + let mut module = crate::fn_native::shared_take_or_clone(module); + module.build_index(); + root.insert(name.as_ref().trim().into(), module.into()); + } else { + root.insert(name.as_ref().trim().into(), module); + } + } else { + let mut iter = name.as_ref().splitn(2, separator.as_ref()); + let sub_module = iter.next().unwrap().trim(); + let remainder = iter.next().unwrap().trim(); + + if !root.contains_key(sub_module) { + let mut m: Module = Default::default(); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } else { + let m = root.remove(sub_module).unwrap(); + let mut m = crate::fn_native::shared_take_or_clone(m); + register_static_module_raw(m.sub_modules_mut(), remainder, module); + m.build_index(); + root.insert(sub_module.into(), m.into()); + } + } } + + register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module); self } - /// Register a shared [`Module`][crate::Module] as a static module namespace with the [`Engine`]. + + /// Register a shared [`Module`] as a static module namespace with the [`Engine`]. /// /// ## Deprecated /// @@ -796,7 +838,7 @@ impl Engine { #[deprecated = "use `register_static_module` instead"] pub fn register_module( &mut self, - name: impl Into, + name: impl AsRef, module: impl Into>, ) -> &mut Self { self.register_static_module(name, module.into()) @@ -1407,7 +1449,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result> { - let mods = &mut self.global_sub_modules.clone(); + let mods = &mut (&self.global_sub_modules).into(); let result = self.eval_ast_with_scope_raw(scope, mods, ast)?; @@ -1493,7 +1535,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result<(), Box> { - let mods = &mut self.global_sub_modules.clone(); + let mods = &mut (&self.global_sub_modules).into(); let state = &mut State { source: ast.clone_source(), ..Default::default() @@ -1539,12 +1581,12 @@ impl Engine { /// ``` #[cfg(not(feature = "no_function"))] #[inline] - pub fn call_fn( + pub fn call_fn( &self, scope: &mut Scope, ast: &AST, name: &str, - args: A, + args: impl crate::fn_args::FuncArgs, ) -> Result> { let mut arg_values = args.into_vec(); let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); @@ -1650,7 +1692,7 @@ impl Engine { .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; let mut state = Default::default(); - let mut mods = self.global_sub_modules.clone(); + let mut mods = (&self.global_sub_modules).into(); // Check for data race. if cfg!(not(feature = "no_closure")) { @@ -1680,7 +1722,7 @@ impl Engine { /// With this method, it is no longer necessary to recompile a large script. /// The script [`AST`] can be compiled just once. Before evaluation, /// constants are passed into the [`Engine`] via an external scope - /// (i.e. with [`scope.push_constant(...)`][crate::Scope::push_constant]). + /// (i.e. with [`Scope::push_constant`]). /// Then, the [`AST`] is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] #[inline] diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 7d5d17f4..1a291ccc 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -1,7 +1,7 @@ //! Configuration settings for [`Engine`]. -use crate::stdlib::{format, string::String}; -use crate::token::{is_valid_identifier, Token}; +use crate::stdlib::{format, num::NonZeroU8, string::String}; +use crate::token::Token; use crate::Engine; #[cfg(not(feature = "no_module"))] @@ -168,9 +168,9 @@ impl Engine { #[inline(always)] pub fn set_module_resolver( &mut self, - resolver: Option, + resolver: impl crate::ModuleResolver + 'static, ) -> &mut Self { - self.module_resolver = resolver.map(|f| Box::new(f) as Box); + self.module_resolver = Box::new(resolver); self } /// Disable a particular keyword or operator in the language. @@ -214,10 +214,12 @@ impl Engine { self.disabled_symbols.insert(symbol.into()); self } - /// Register a custom operator into the language. + /// Register a custom operator with a precedence into the language. /// /// The operator must be a valid identifier (i.e. it cannot be a symbol). /// + /// The precedence cannot be zero. + /// /// # Example /// /// ```rust @@ -245,22 +247,38 @@ impl Engine { keyword: &str, precedence: u8, ) -> Result<&mut Self, String> { - if !is_valid_identifier(keyword.chars()) { - return Err(format!("not a valid identifier: '{}'", keyword).into()); + let precedence = NonZeroU8::new(precedence); + + if precedence.is_none() { + return Err("precedence cannot be zero".into()); } match Token::lookup_from_syntax(keyword) { // Standard identifiers, reserved keywords and custom keywords are OK None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), - // Disabled keywords are also OK - Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => (), // Active standard keywords cannot be made custom - Some(_) => return Err(format!("'{}' is a reserved keyword", keyword).into()), + // Disabled keywords are OK + Some(token) if token.is_keyword() => { + if !self.disabled_symbols.contains(token.syntax().as_ref()) { + return Err(format!("'{}' is a reserved keyword", keyword).into()); + } + } + // Active standard operators cannot be made custom + Some(token) if token.is_operator() => { + if !self.disabled_symbols.contains(token.syntax().as_ref()) { + return Err(format!("'{}' is a reserved operator", keyword).into()); + } + } + // Active standard symbols cannot be made custom + Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => { + return Err(format!("'{}' is a reserved symbol", keyword).into()) + } + // Disabled symbols are OK + Some(_) => (), } // Add to custom keywords - self.custom_keywords - .insert(keyword.into(), Some(precedence)); + self.custom_keywords.insert(keyword.into(), precedence); Ok(self) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 6d19919d..8b42985f 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -15,10 +15,12 @@ use crate::stdlib::{ format, iter::{empty, once}, mem, + num::NonZeroU64, ops::Deref, string::ToString, vec::Vec, }; +use crate::utils::combine_hashes; use crate::{ calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, INT, @@ -161,7 +163,7 @@ impl Engine { state: &mut State, lib: &[&Module], fn_name: &str, - hash_fn: u64, + hash_fn: NonZeroU64, args: &mut FnCallArgs, is_ref: bool, pub_only: bool, @@ -453,22 +455,22 @@ impl Engine { &self, mods: Option<&Imports>, lib: &[&Module], - hash_fn: u64, - hash_script: u64, + hash_fn: Option, + hash_script: Option, pub_only: bool, ) -> bool { // First check script-defined functions - (hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only))) - //|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)) + hash_script.map(|hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))).unwrap_or(false) + //|| hash_fn.map(|hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))).unwrap_or(false) // Then check registered functions - //|| (hash_script != 0 && self.global_namespace.contains_fn(hash_script, pub_only)) - || self.global_namespace.contains_fn(hash_fn, false) + //|| hash_script.map(|hash| self.global_namespace.contains_fn(hash, pub_only)).unwrap_or(false) + || hash_fn.map(|hash| self.global_namespace.contains_fn(hash, false)).unwrap_or(false) // Then check packages - || (hash_script != 0 && self.global_modules.iter().any(|m| m.contains_fn(hash_script, false))) - || self.global_modules.iter().any(|m| m.contains_fn(hash_fn, false)) + || hash_script.map(|hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))).unwrap_or(false) + || hash_fn.map(|hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))).unwrap_or(false) // Then check imported modules - || (hash_script != 0 && mods.map(|m| m.contains_fn(hash_script)).unwrap_or(false)) - || mods.map(|m| m.contains_fn(hash_fn)).unwrap_or(false) + || hash_script.map(|hash| mods.map(|m| m.contains_fn(hash)).unwrap_or(false)).unwrap_or(false) + || hash_fn.map(|hash| mods.map(|m| m.contains_fn(hash)).unwrap_or(false)).unwrap_or(false) } /// Perform an actual function call, native Rust or scripted, taking care of special functions. @@ -484,7 +486,7 @@ impl Engine { state: &mut State, lib: &[&Module], fn_name: &str, - hash_script: u64, + hash_script: Option, args: &mut FnCallArgs, is_ref: bool, _is_method: bool, @@ -534,9 +536,11 @@ impl Engine { // Script-like function found #[cfg(not(feature = "no_function"))] - _ if hash_script != 0 - && self.has_override(Some(mods), lib, 0, hash_script, pub_only) => + _ if hash_script.is_some() + && self.has_override(Some(mods), lib, None, hash_script, pub_only) => { + let hash_script = hash_script.unwrap(); + // Get function let (func, mut source) = lib .iter() @@ -636,7 +640,16 @@ impl Engine { // Normal native function call _ => self.call_native_fn( - mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val, + mods, + state, + lib, + fn_name, + hash_fn.unwrap(), + args, + is_ref, + pub_only, + pos, + def_val, ), } } @@ -723,11 +736,10 @@ impl Engine { state: &mut State, lib: &[&Module], fn_name: &str, - hash_script: u64, + hash_script: Option, target: &mut crate::engine::Target, mut call_args: StaticVec, def_val: Option<&Dynamic>, - native: bool, pub_only: bool, pos: Position, level: usize, @@ -745,11 +757,7 @@ impl Engine { let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash - let hash = if native { - 0 - } else { - calc_script_fn_hash(empty(), fn_name, args_len) - }; + let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); // Arguments are passed as-is, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut arg_values = curry @@ -773,11 +781,7 @@ impl Engine { let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash - let hash = if native { - 0 - } else { - calc_script_fn_hash(empty(), fn_name, args_len) - }; + let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); // Replace the first argument with the object pointer, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut arg_values = once(obj) @@ -837,17 +841,14 @@ impl Engine { .enumerate() .for_each(|(i, v)| call_args.insert(i, v)); // Recalculate the hash based on the new function name and new arguments - hash = if native { - 0 - } else { - calc_script_fn_hash(empty(), fn_name, call_args.len()) - }; + hash = hash_script + .and_then(|_| calc_script_fn_hash(empty(), fn_name, call_args.len())); } } }; - if native { - hash = 0; + if hash_script.is_none() { + hash = None; } // Attached object pointer in front of the arguments @@ -881,8 +882,7 @@ impl Engine { fn_name: &str, args_expr: impl AsRef<[Expr]>, def_val: Option<&Dynamic>, - mut hash_script: u64, - native: bool, + mut hash_script: Option, pub_only: bool, pos: Position, capture_scope: bool, @@ -951,7 +951,7 @@ impl Engine { if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 - && !self.has_override(Some(mods), lib, 0, hash_script, pub_only) + && !self.has_override(Some(mods), lib, None, hash_script, pub_only) { let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; @@ -1041,7 +1041,7 @@ impl Engine { .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .collect::>()?; - let (mut target, _, pos) = + let (mut target, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; if target.as_ref().is_read_only() { @@ -1071,11 +1071,21 @@ impl Engine { } } - let hash = if native { 0 } else { hash_script }; let args = args.as_mut(); self.exec_fn_call( - mods, state, lib, name, hash, args, is_ref, false, pub_only, pos, capture, def_val, + mods, + state, + lib, + name, + hash_script, + args, + is_ref, + false, + pub_only, + pos, + capture, + def_val, level, ) .map(|(v, _)| v) @@ -1093,7 +1103,7 @@ impl Engine { fn_name: &str, args_expr: impl AsRef<[Expr]>, def_val: Option<&Dynamic>, - hash_script: u64, + hash_script: NonZeroU64, pos: Position, level: usize, ) -> Result> { @@ -1127,7 +1137,7 @@ impl Engine { .collect::>()?; // Get target reference to first argument - let (target, _, pos) = + let (target, pos) = self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.inc_operations(state, pos)?; @@ -1167,9 +1177,9 @@ impl Engine { // 2) Calculate a second hash with no qualifiers, empty function name, // and the actual list of argument `TypeId`'.s let hash_fn_args = - calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = hash_script ^ hash_fn_args; + calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())).unwrap(); + // 3) The two hashes are combined. + let hash_qualified_fn = combine_hashes(hash_script, hash_fn_args); module.get_qualified_fn(hash_qualified_fn) } diff --git a/src/fn_func.rs b/src/fn_func.rs index 94b3cba5..1791c0ba 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -27,15 +27,15 @@ pub trait Func { /// // Func takes two type parameters: /// // 1) a tuple made up of the types of the script function's parameters /// // 2) the return type of the script function - /// // + /// /// // 'func' will have type Box Result>> and is callable! /// let func = Func::<(i64, String), bool>::create_from_ast( /// // ^^^^^^^^^^^^^ function parameter types in tuple /// - /// engine, // the 'Engine' is consumed into the closure - /// ast, // the 'AST' - /// "calc" // the entry-point function name - /// ); + /// engine, // the 'Engine' is consumed into the closure + /// ast, // the 'AST' + /// "calc" // the entry-point function name + /// ); /// /// func(123, "hello".to_string())? == false; // call the anonymous function /// # Ok(()) @@ -58,15 +58,15 @@ pub trait Func { /// // Func takes two type parameters: /// // 1) a tuple made up of the types of the script function's parameters /// // 2) the return type of the script function - /// // + /// /// // 'func' will have type Box Result>> and is callable! /// let func = Func::<(i64, String), bool>::create_from_script( /// // ^^^^^^^^^^^^^ function parameter types in tuple /// - /// engine, // the 'Engine' is consumed into the closure - /// script, // the script, notice number of parameters must match - /// "calc" // the entry-point function name - /// )?; + /// engine, // the 'Engine' is consumed into the closure + /// script, // the script, notice number of parameters must match + /// "calc" // the entry-point function name + /// )?; /// /// func(123, "hello".to_string())? == false; // call the anonymous function /// # Ok(()) diff --git a/src/fn_native.rs b/src/fn_native.rs index 71a3a775..68b5b0ce 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -3,7 +3,14 @@ use crate::ast::{FnAccess, ScriptFnDef}; use crate::engine::Imports; use crate::plugin::PluginFunction; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String}; +use crate::stdlib::{ + boxed::Box, + convert::{TryFrom, TryInto}, + fmt, + iter::empty, + mem, + string::String, +}; use crate::token::is_valid_identifier; use crate::{ calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, @@ -153,25 +160,13 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> { args: &mut [&mut Dynamic], def_value: Option<&Dynamic>, ) -> Result> { - let mut mods = self.mods.cloned().unwrap_or_default(); - - let hash_script = calc_script_fn_hash( - empty(), - fn_name, - if is_method { - args.len() - 1 - } else { - args.len() - }, - ); - self.engine() .exec_fn_call( - &mut mods, + &mut self.mods.cloned().unwrap_or_default(), &mut Default::default(), self.lib, fn_name, - hash_script, + calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }), args, is_method, is_method, @@ -225,11 +220,15 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic]; /// A general function pointer, which may carry additional (i.e. curried) argument values /// to be passed onto a function during a call. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct FnPtr(ImmutableString, StaticVec); impl FnPtr { /// Create a new function pointer. + pub fn new(name: impl Into) -> Result> { + name.into().try_into() + } + /// Create a new function pointer without checking its parameters. #[inline(always)] pub(crate) fn new_unchecked( name: impl Into, @@ -257,7 +256,24 @@ impl FnPtr { pub fn curry(&self) -> &[Dynamic] { self.1.as_ref() } - /// Does this function pointer refer to an anonymous function? + /// Add a new curried argument. + #[inline(always)] + pub fn add_curry(&mut self, value: Dynamic) -> &mut Self { + self.1.push(value); + self + } + /// Set curried arguments to the function pointer. + #[inline(always)] + pub fn set_curry(&mut self, values: impl IntoIterator) -> &mut Self { + self.1 = values.into_iter().collect(); + self + } + /// Is the function pointer curried? + #[inline(always)] + pub fn is_curried(&self) -> bool { + !self.1.is_empty() + } + /// Does the function pointer refer to an anonymous function? #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn is_anonymous(&self) -> bool { diff --git a/src/lib.rs b/src/lib.rs index 00f60db2..9f7a8773 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,18 @@ //! # Rhai - embedded scripting for Rust //! +//! ![Rhai logo](https://schungx.github.io/rhai/images/logo/rhai-banner-transparent-colour.svg) +//! //! Rhai is a tiny, simple and fast embedded scripting language for Rust //! that gives you a safe and easy way to add scripting to your applications. -//! It provides a familiar syntax based on JavaScript and Rust and a simple Rust interface. -//! Here is a quick example. //! -//! First, the contents of `my_script.rhai`: +//! It provides a familiar syntax based on JavaScript+Rust and a simple Rust interface. +//! +//! # A Quick Example +//! +//! ## Contents of `my_script.rhai` //! //! ```,ignore -//! // Brute force factorial function +//! /// Brute force factorial function //! fn factorial(x) { //! if x == 1 { return 1; } //! x * factorial(x - 1) @@ -18,7 +22,7 @@ //! compute(factorial(10)) //! ``` //! -//! And the Rust part: +//! ## The Rust part //! //! ```,no_run //! use rhai::{Engine, EvalAltResult, RegisterFn}; diff --git a/src/module/mod.rs b/src/module/mod.rs index 84862afe..92c7b8fb 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -10,13 +10,14 @@ use crate::stdlib::{ collections::HashMap, fmt, format, iter::empty, + num::NonZeroU64, num::NonZeroUsize, ops::{Add, AddAssign, Deref, DerefMut}, string::{String, ToString}, vec::Vec, }; use crate::token::Token; -use crate::utils::StraightHasherBuilder; +use crate::utils::{combine_hashes, StraightHasherBuilder}; use crate::{ Dynamic, EvalAltResult, ImmutableString, NativeCallContext, Position, Shared, StaticVec, }; @@ -34,9 +35,9 @@ use crate::Map; /// A type representing the namespace of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum FnNamespace { - /// Global namespace. + /// Expose to global namespace. Global, - /// Internal only. + /// Module namespace only. Internal, } @@ -79,9 +80,9 @@ pub struct FuncInfo { /// Number of parameters. pub params: usize, /// Parameter types (if applicable). - pub param_types: Option>, + pub param_types: StaticVec, /// Parameter names (if available). - pub param_names: Option>, + pub param_names: StaticVec, } impl FuncInfo { @@ -89,13 +90,19 @@ impl FuncInfo { pub fn gen_signature(&self) -> String { let mut sig = format!("{}(", self.name); - if let Some(ref names) = self.param_names { - let mut params: Vec<_> = names.iter().map(ImmutableString::to_string).collect(); + if !self.param_names.is_empty() { + let mut params: Vec<_> = self + .param_names + .iter() + .map(ImmutableString::to_string) + .collect(); let return_type = params.pop().unwrap_or_else(|| "()".to_string()); sig.push_str(¶ms.join(", ")); if return_type != "()" { sig.push_str(") -> "); sig.push_str(&return_type); + } else if self.func.is_script() { + sig.push_str(") -> Dynamic"); } else { sig.push_str(")"); } @@ -106,7 +113,12 @@ impl FuncInfo { sig.push_str(", "); } } - sig.push_str(") -> Dynamic"); + + if self.func.is_script() { + sig.push_str(") -> Dynamic"); + } else { + sig.push_str(") -> ?"); + } } sig @@ -126,12 +138,12 @@ pub struct Module { /// Module variables. variables: HashMap, /// Flattened collection of all module variables, including those in sub-modules. - all_variables: HashMap, + all_variables: HashMap, /// External Rust functions. - functions: HashMap, + functions: HashMap, /// Flattened collection of all external Rust functions, native or scripted. /// including those in sub-modules. - all_functions: HashMap, + all_functions: HashMap, /// Iterator functions, keyed by the type producing the iterator. type_iterators: HashMap, /// Flattened collection of iterator functions, including those in sub-modules. @@ -344,7 +356,7 @@ impl Module { self.get_var(name).and_then(Dynamic::try_cast::) } - /// Get a module variable as a [`Dynamic`][crate::Dynamic]. + /// Get a module variable as a [`Dynamic`]. /// /// # Example /// @@ -387,16 +399,15 @@ impl Module { /// Get a reference to a namespace-qualified variable. /// Name and Position in `EvalAltResult` are None and must be set afterwards. /// - /// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`]. + /// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`]. #[inline(always)] - pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box> { - if hash_var == 0 { - Err(EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()) - } else { - self.all_variables.get(&hash_var).ok_or_else(|| { - EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into() - }) - } + pub(crate) fn get_qualified_var( + &self, + hash_var: NonZeroU64, + ) -> Result<&Dynamic, Box> { + self.all_variables.get(&hash_var).ok_or_else(|| { + EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into() + }) } /// Set a script-defined function into the module. @@ -404,12 +415,12 @@ impl Module { /// If there is an existing function of the same name and number of arguments, it is replaced. #[cfg(not(feature = "no_function"))] #[inline] - pub(crate) fn set_script_fn(&mut self, fn_def: impl Into>) -> u64 { + pub(crate) fn set_script_fn(&mut self, fn_def: impl Into>) -> NonZeroU64 { let fn_def = fn_def.into(); // None + function name + number of arguments. let num_params = fn_def.params.len(); - let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params); + let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params).unwrap(); let mut param_names: StaticVec<_> = fn_def.params.iter().cloned().collect(); param_names.push("Dynamic".into()); self.functions.insert( @@ -419,8 +430,8 @@ impl Module { namespace: FnNamespace::Internal, access: fn_def.access, params: num_params, - param_types: None, - param_names: Some(param_names), + param_types: Default::default(), + param_names, func: fn_def.into(), }, ); @@ -454,6 +465,24 @@ impl Module { .map(|FuncInfo { func, .. }| func.get_fn_def()) } + /// Get a mutable reference to the underlying [`HashMap`] of sub-modules. + /// + /// ## Warning + /// + /// By taking a mutable reference, it is assumed that some sub-modules will be modified. + /// Thus the module is automatically set to be non-indexed. + #[inline(always)] + pub(crate) fn sub_modules_mut(&mut self) -> &mut HashMap> { + // We must assume that the user has changed the sub-modules + // (otherwise why take a mutable reference?) + self.all_functions.clear(); + self.all_variables.clear(); + self.all_type_iterators.clear(); + self.indexed = false; + + &mut self.modules + } + /// Does a sub-module exist in the module? /// /// # Example @@ -515,7 +544,7 @@ impl Module { /// Does the particular Rust function exist in the module? /// - /// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`]. + /// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`]. /// It is also returned by the `set_fn_XXX` calls. /// /// # Example @@ -528,10 +557,8 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` #[inline] - pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { - if hash_fn == 0 { - false - } else if public_only { + pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool { + if public_only { self.functions .get(&hash_fn) .map(|FuncInfo { access, .. }| access.is_public()) @@ -543,7 +570,7 @@ impl Module { /// Update the metadata (parameter names/types and return type) of a registered function. /// - /// The [`u64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or + /// The [`NonZeroU64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or /// the function [`crate::calc_script_fn_hash`]. /// /// Each parameter name/type pair should be a single string of the format: `var_name: type`. @@ -552,20 +579,24 @@ impl Module { /// In other words, the number of entries should be one larger than the number of parameters. pub fn update_fn_metadata<'a>( &mut self, - hash_fn: u64, + hash_fn: NonZeroU64, arg_names: impl AsRef<[&'a str]>, ) -> &mut Self { if let Some(f) = self.functions.get_mut(&hash_fn) { - f.param_names = Some(arg_names.as_ref().iter().map(|&n| n.into()).collect()); + f.param_names = arg_names.as_ref().iter().map(|&n| n.into()).collect(); } self } /// Update the namespace of a registered function. /// - /// The [`u64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or + /// The [`NonZeroU64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or /// the function [`crate::calc_script_fn_hash`]. - pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { + pub fn update_fn_namespace( + &mut self, + hash_fn: NonZeroU64, + namespace: FnNamespace, + ) -> &mut Self { if let Some(f) = self.functions.get_mut(&hash_fn) { f.namespace = namespace; } @@ -588,12 +619,13 @@ impl Module { arg_names: Option<&[&str]>, arg_types: &[TypeId], func: CallableFunction, - ) -> u64 { + ) -> NonZeroU64 { let name = name.into(); - let hash_fn = crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()); + let hash_fn = + crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()).unwrap(); - let params = arg_types + let param_types = arg_types .into_iter() .cloned() .map(|id| { @@ -611,9 +643,13 @@ impl Module { name, namespace, access, - params: params.len(), - param_types: Some(params), - param_names: arg_names.map(|p| p.iter().map(|&v| v.into()).collect()), + params: param_types.len(), + param_types, + param_names: if let Some(p) = arg_names { + p.iter().map(|&v| v.into()).collect() + } else { + Default::default() + }, func: func.into(), }, ); @@ -624,7 +660,7 @@ impl Module { } /// Set a Rust function taking a reference to the scripting [`Engine`][crate::Engine], - /// the current set of functions, plus a list of mutable [`Dynamic`][crate::Dynamic] references + /// the current set of functions, plus a list of mutable [`Dynamic`] references /// into the module, returning a hash key. /// /// Use this to register a built-in function which must reference settings on the scripting @@ -639,7 +675,7 @@ impl Module { /// /// A list of [`TypeId`]'s is taken as the argument types. /// - /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][crate::Dynamic], + /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`], /// which is guaranteed to contain enough arguments of the correct types. /// /// The function is assumed to be a _method_, meaning that the first argument should not be consumed. @@ -697,7 +733,7 @@ impl Module { func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); @@ -733,7 +769,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn() -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from); let arg_types = []; self.set_fn( @@ -768,7 +804,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { func(cast_arg::(&mut args[0])).map(Dynamic::from) }; @@ -808,7 +844,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { func(&mut args[0].write_lock::().unwrap()).map(Dynamic::from) }; @@ -847,7 +883,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(&mut A) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { self.set_fn_1_mut( crate::engine::make_getter(&name.into()), FnNamespace::Global, @@ -879,7 +915,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A, B) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -926,7 +962,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A, B) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let a = &mut args[0].write_lock::().unwrap(); @@ -972,7 +1008,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(&mut A, B) -> Result<(), Box> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { self.set_fn_2_mut( crate::engine::make_setter(&name.into()), FnNamespace::Global, @@ -1011,7 +1047,7 @@ impl Module { pub fn set_indexer_get_fn( &mut self, func: impl Fn(&mut A, B) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -1058,7 +1094,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A, B, C) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -1111,7 +1147,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A, B, C) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[2]); let c = cast_arg::(&mut args[3]); @@ -1162,7 +1198,7 @@ impl Module { pub fn set_indexer_set_fn( &mut self, func: impl Fn(&mut A, B, C) -> Result<(), Box> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -1234,7 +1270,7 @@ impl Module { &mut self, getter: impl Fn(&mut A, B) -> Result> + SendSync + 'static, setter: impl Fn(&mut A, B, T) -> Result<(), Box> + SendSync + 'static, - ) -> (u64, u64) { + ) -> (NonZeroU64, NonZeroU64) { ( self.set_indexer_get_fn(getter), self.set_indexer_set_fn(setter), @@ -1271,7 +1307,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A, B, C, D) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -1331,7 +1367,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A, B, C, D) -> Result> + SendSync + 'static, - ) -> u64 { + ) -> NonZeroU64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); @@ -1358,48 +1394,43 @@ impl Module { /// Get a Rust function. /// - /// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`]. + /// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`]. /// It is also returned by the `set_fn_XXX` calls. #[inline(always)] - pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> { - if hash_fn == 0 { - None - } else { - self.functions - .get(&hash_fn) - .and_then(|FuncInfo { access, func, .. }| match access { - _ if !public_only => Some(func), - FnAccess::Public => Some(func), - FnAccess::Private => None, - }) - } + pub(crate) fn get_fn( + &self, + hash_fn: NonZeroU64, + public_only: bool, + ) -> Option<&CallableFunction> { + self.functions + .get(&hash_fn) + .and_then(|FuncInfo { access, func, .. }| match access { + _ if !public_only => Some(func), + FnAccess::Public => Some(func), + FnAccess::Private => None, + }) } /// Does the particular namespace-qualified function exist in the module? /// - /// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match + /// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match /// the hash calculated by [`build_index`][Module::build_index]. - #[inline] - pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool { - if hash_fn == 0 { - false - } else { - self.all_functions.contains_key(&hash_fn) - } + #[inline(always)] + pub fn contains_qualified_fn(&self, hash_fn: NonZeroU64) -> bool { + self.all_functions.contains_key(&hash_fn) } /// Get a namespace-qualified function. /// Name and Position in `EvalAltResult` are None and must be set afterwards. /// - /// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match + /// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match /// the hash calculated by [`build_index`][Module::build_index]. #[inline(always)] - pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { - if hash_qualified_fn == 0 { - None - } else { - self.all_functions.get(&hash_qualified_fn) - } + pub(crate) fn get_qualified_fn( + &self, + hash_qualified_fn: NonZeroU64, + ) -> Option<&CallableFunction> { + self.all_functions.get(&hash_qualified_fn) } /// Combine another module into this module. @@ -1686,7 +1717,7 @@ impl Module { ast: &crate::AST, engine: &crate::Engine, ) -> Result> { - let mut mods = engine.global_sub_modules.clone(); + let mut mods: crate::engine::Imports = (&engine.global_sub_modules).into(); let orig_mods_len = mods.len(); // Run the script @@ -1745,8 +1776,8 @@ impl Module { fn index_module<'a>( module: &'a Module, qualifiers: &mut Vec<&'a str>, - variables: &mut HashMap, - functions: &mut HashMap, + variables: &mut HashMap, + functions: &mut HashMap, type_iterators: &mut HashMap, ) { module.modules.iter().for_each(|(name, m)| { @@ -1760,7 +1791,7 @@ impl Module { module.variables.iter().for_each(|(var_name, value)| { // Qualifiers + variable name let hash_var = - crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0); + crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap(); variables.insert(hash_var, value.clone()); }); @@ -1781,7 +1812,7 @@ impl Module { name, namespace, params, - param_types: types, + param_types, func, .. }, @@ -1793,9 +1824,10 @@ impl Module { // Qualifiers + function name + number of arguments. let hash_qualified_script = - crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params); + crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params) + .unwrap(); - if let Some(param_types) = types { + if !func.is_script() { assert_eq!(*params, param_types.len()); // Namespace-qualified Rust functions are indexed in two steps: @@ -1807,9 +1839,11 @@ impl Module { empty(), "", param_types.iter().cloned(), - ); - // 3) The final hash is the XOR of the two hashes. - let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; + ) + .unwrap(); + // 3) The two hashes are combined. + let hash_qualified_fn = + combine_hashes(hash_qualified_script, hash_fn_args); functions.insert(hash_qualified_fn, func.clone()); } else if cfg!(not(feature = "no_function")) { @@ -1897,7 +1931,7 @@ impl Module { /// _(INTERNALS)_ A chain of [module][Module] names to namespace-qualify a variable or function call. /// Exported under the `internals` feature only. /// -/// A [`u64`] hash key is cached for quick search purposes. +/// A [`NonZeroU64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes. /// /// A [`StaticVec`] is used because most namespace-qualified access contains only one level, /// and it is wasteful to always allocate a [`Vec`] with one element. @@ -1906,13 +1940,13 @@ impl Module { /// /// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Default, Hash)] -pub struct NamespaceRef(StaticVec, Option); +pub struct NamespaceRef(Option, StaticVec); impl fmt::Debug for NamespaceRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f)?; + fmt::Debug::fmt(&self.1, f)?; - if let Some(index) = self.1 { + if let Some(index) = self.0 { write!(f, " -> {}", index) } else { Ok(()) @@ -1924,19 +1958,19 @@ impl Deref for NamespaceRef { type Target = StaticVec; fn deref(&self) -> &Self::Target { - &self.0 + &self.1 } } impl DerefMut for NamespaceRef { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + &mut self.1 } } impl fmt::Display for NamespaceRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for Ident { name, .. } in self.0.iter() { + for Ident { name, .. } in self.1.iter() { write!(f, "{}{}", name, Token::DoubleColon.syntax())?; } Ok(()) @@ -1945,7 +1979,7 @@ impl fmt::Display for NamespaceRef { impl From> for NamespaceRef { fn from(modules: StaticVec) -> Self { - Self(modules, None) + Self(None, modules) } } @@ -1975,12 +2009,14 @@ impl> AddAssign for Module { } impl NamespaceRef { + /// Get the [`Scope`][crate::Scope] index offset. pub(crate) fn index(&self) -> Option { - self.1 + self.0 } + /// Set the [`Scope`][crate::Scope] index offset. #[cfg(not(feature = "no_module"))] pub(crate) fn set_index(&mut self, index: Option) { - self.1 = index + self.0 = index } } diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 71c3fe27..30039234 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// collection.push(resolver); /// /// let mut engine = Engine::new(); -/// engine.set_module_resolver(Some(collection)); +/// engine.set_module_resolver(collection); /// ``` #[derive(Default)] pub struct ModuleResolversCollection(Vec>); @@ -36,16 +36,41 @@ impl ModuleResolversCollection { /// collection.push(resolver); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(collection)); + /// engine.set_module_resolver(collection); /// ``` #[inline(always)] pub fn new() -> Self { Default::default() } - /// Add a module keyed by its path. + /// Append a module resolver to the end. #[inline(always)] - pub fn push(&mut self, resolver: impl ModuleResolver + 'static) { + pub fn push(&mut self, resolver: impl ModuleResolver + 'static) -> &mut Self { self.0.push(Box::new(resolver)); + self + } + /// Insert a module resolver to an offset index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + #[inline(always)] + pub fn insert(&mut self, index: usize, resolver: impl ModuleResolver + 'static) -> &mut Self { + self.0.insert(index, Box::new(resolver)); + self + } + /// Remove the last module resolver from the end, if any. + #[inline(always)] + pub fn pop(&mut self) -> Option> { + self.0.pop() + } + /// Remove a module resolver at an offset index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + #[inline(always)] + pub fn remove(&mut self, index: usize) -> Box { + self.0.remove(index) } /// Get an iterator of all the module resolvers. #[inline(always)] @@ -59,8 +84,9 @@ impl ModuleResolversCollection { } /// Remove all module resolvers. #[inline(always)] - pub fn clear(&mut self) { + pub fn clear(&mut self) -> &mut Self { self.0.clear(); + self } /// Is this [`ModuleResolversCollection`] empty? #[inline(always)] @@ -75,8 +101,9 @@ impl ModuleResolversCollection { /// Add another [`ModuleResolversCollection`] to the end of this collection. /// The other [`ModuleResolversCollection`] is consumed. #[inline(always)] - pub fn append(&mut self, other: Self) { + pub fn append(&mut self, other: Self) -> &mut Self { self.0.extend(other.0.into_iter()); + self } } diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs new file mode 100644 index 00000000..8ed3b3f0 --- /dev/null +++ b/src/module/resolvers/dummy.rs @@ -0,0 +1,48 @@ +use crate::stdlib::boxed::Box; +use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; + +/// Empty/disabled module resolution service that acts as a dummy. +/// +/// # Example +/// +/// ``` +/// use rhai::{Engine, Module}; +/// use rhai::module_resolvers::DummyModuleResolver; +/// +/// let resolver = DummyModuleResolver::new(); +/// let mut engine = Engine::new(); +/// engine.set_module_resolver(resolver); +/// ``` +#[derive(Debug, Copy, Eq, PartialEq, Clone, Default, Hash)] +pub struct DummyModuleResolver; + +impl DummyModuleResolver { + /// Create a new [`DummyModuleResolver`]. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Module}; + /// use rhai::module_resolvers::DummyModuleResolver; + /// + /// let resolver = DummyModuleResolver::new(); + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(resolver); + /// ``` + #[inline(always)] + pub fn new() -> Self { + Default::default() + } +} + +impl ModuleResolver for DummyModuleResolver { + #[inline(always)] + fn resolve( + &self, + _: &Engine, + path: &str, + pos: Position, + ) -> Result, Box> { + EvalAltResult::ErrorModuleNotFound(path.into(), pos).into() + } +} diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 0831ff25..b3be9bce 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,5 +1,9 @@ use crate::stdlib::{ - boxed::Box, collections::HashMap, io::Error as IoError, path::PathBuf, string::String, + boxed::Box, + collections::HashMap, + io::Error as IoError, + path::{Path, PathBuf}, + string::String, }; use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; @@ -31,7 +35,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// /// let mut engine = Engine::new(); /// -/// engine.set_module_resolver(Some(resolver)); +/// engine.set_module_resolver(resolver); /// ``` #[derive(Debug)] pub struct FileModuleResolver { @@ -65,10 +69,10 @@ impl FileModuleResolver { /// let resolver = FileModuleResolver::new_with_path("./scripts"); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] - pub fn new_with_path>(path: P) -> Self { + pub fn new_with_path(path: impl Into) -> Self { Self::new_with_path_and_extension(path, "rhai") } @@ -87,12 +91,12 @@ impl FileModuleResolver { /// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x"); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] - pub fn new_with_path_and_extension, E: Into>( - path: P, - extension: E, + pub fn new_with_path_and_extension( + path: impl Into, + extension: impl Into, ) -> Self { Self { path: path.into(), @@ -114,12 +118,64 @@ impl FileModuleResolver { /// let resolver = FileModuleResolver::new(); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] pub fn new() -> Self { Default::default() } + + /// Get the base path for script files. + #[inline(always)] + pub fn path(&self) -> &Path { + self.path.as_ref() + } + /// Set the base path for script files. + #[inline(always)] + pub fn set_path(&mut self, path: impl Into) -> &mut Self { + self.path = path.into(); + self + } + + /// Get the script file extension. + #[inline(always)] + pub fn extension(&self) -> &str { + &self.extension + } + + /// Set the script file extension. + #[inline(always)] + pub fn set_extension(&mut self, extension: impl Into) -> &mut Self { + self.extension = extension.into(); + self + } + + /// Empty the internal cache. + #[inline(always)] + pub fn clear_cache(&mut self) { + #[cfg(not(feature = "sync"))] + self.cache.borrow_mut().clear(); + #[cfg(feature = "sync")] + self.cache.write().unwrap().clear(); + } + + /// Empty the internal cache. + #[inline(always)] + pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option> { + #[cfg(not(feature = "sync"))] + return self + .cache + .borrow_mut() + .remove_entry(path.as_ref()) + .map(|(_, v)| v); + #[cfg(feature = "sync")] + return self + .cache + .write() + .unwrap() + .remove_entry(path.as_ref()) + .map(|(_, v)| v); + } } impl ModuleResolver for FileModuleResolver { diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index d131a1d0..f257f0e8 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -2,6 +2,9 @@ use crate::fn_native::SendSync; use crate::stdlib::boxed::Box; use crate::{Engine, EvalAltResult, Module, Position, Shared}; +mod dummy; +pub use dummy::DummyModuleResolver; + mod collection; pub use collection::ModuleResolversCollection; diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 553a462f..311d92d0 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// /// let mut engine = Engine::new(); /// -/// engine.set_module_resolver(Some(resolver)); +/// engine.set_module_resolver(resolver); /// ``` #[derive(Debug, Clone, Default)] pub struct StaticModuleResolver(HashMap>); @@ -36,7 +36,7 @@ impl StaticModuleResolver { /// resolver.insert("hello", module); /// /// let mut engine = Engine::new(); - /// engine.set_module_resolver(Some(resolver)); + /// engine.set_module_resolver(resolver); /// ``` #[inline(always)] pub fn new() -> Self { @@ -60,8 +60,13 @@ impl StaticModuleResolver { } /// Get an iterator of all the modules. #[inline(always)] - pub fn iter(&self) -> impl Iterator)> { - self.0.iter().map(|(k, v)| (k.as_str(), v.clone())) + pub fn iter(&self) -> impl Iterator)> { + self.0.iter().map(|(k, v)| (k.as_str(), v)) + } + /// Get a mutable iterator of all the modules. + #[inline(always)] + pub fn iter_mut(&mut self) -> impl Iterator)> { + self.0.iter_mut().map(|(k, v)| (k.as_str(), v)) } /// Get a mutable iterator of all the modules. #[inline(always)] @@ -75,8 +80,8 @@ impl StaticModuleResolver { } /// Get an iterator of all the modules. #[inline(always)] - pub fn values<'a>(&'a self) -> impl Iterator> + 'a { - self.0.values().map(|m| m.clone()) + pub fn values(&self) -> impl Iterator> { + self.0.values().map(|m| m) } /// Remove all modules. #[inline(always)] @@ -95,6 +100,8 @@ impl StaticModuleResolver { } /// Merge another [`StaticModuleResolver`] into this. /// The other [`StaticModuleResolver`] is consumed. + /// + /// Existing modules of the same path name are overwritten. #[inline(always)] pub fn merge(&mut self, other: Self) { if !other.is_empty() { diff --git a/src/optimize.rs b/src/optimize.rs index 53be762e..07d16642 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,7 +2,7 @@ use crate::ast::{Expr, ScriptFnDef, Stmt}; use crate::dynamic::AccessMode; -use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; +use crate::engine::{Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_call::run_builtin_binary_op; use crate::parser::map_dynamic_to_expr; use crate::stdlib::{ @@ -62,6 +62,8 @@ struct State<'a> { variables: Vec<(String, AccessMode, Expr)>, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, + /// Collection of sub-modules. + mods: Imports, /// [Module] containing script-defined functions. lib: &'a [&'a Module], /// Optimization level. @@ -76,6 +78,7 @@ impl<'a> State<'a> { changed: false, variables: vec![], engine, + mods: (&engine.global_sub_modules).into(), lib, optimization_level: level, } @@ -138,7 +141,7 @@ fn call_fn_with_constant_arguments( &mut Default::default(), state.lib, fn_name, - hash_fn, + hash_fn.unwrap(), arg_values.iter_mut().collect::>().as_mut(), false, true, @@ -149,7 +152,7 @@ fn call_fn_with_constant_arguments( .map(|(v, _)| v) } -/// Optimize a block of [statements][crate::ast::Stmt]. +/// Optimize a block of [statements][Stmt]. fn optimize_stmt_block( mut statements: Vec, pos: Position, @@ -274,7 +277,7 @@ fn optimize_stmt_block( } } -/// Optimize a [statement][crate::ast::Stmt]. +/// Optimize a [statement][Stmt]. fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { match stmt { // expr op= expr @@ -470,7 +473,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } } -/// Optimize an [expression][crate::ast::Expr]. +/// Optimize an [expression][Expr]. fn optimize_expr(expr: &mut Expr, state: &mut State) { // These keywords are handled specially const DONT_EVAL_KEYWORDS: &[&str] = &[ @@ -501,7 +504,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { // map.string (Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => { - let prop = &p.1.name; + let prop = &p.2.name; // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); @@ -655,7 +658,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); // Search for overloaded operators (can override built-in). - if !state.engine.has_override_by_name_and_arguments(Some(&state.engine.global_sub_modules), state.lib, x.name.as_ref(), arg_types.as_ref(), false) { + if !state.engine.has_override_by_name_and_arguments(Some(&state.mods), state.lib, x.name.as_ref(), arg_types.as_ref(), false) { if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1]) .ok().flatten() .and_then(|result| map_dynamic_to_expr(result, *pos)) @@ -717,12 +720,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)), // constant-name - Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.3.name).is_some() => { + Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => { state.set_dirty(); // Replace constant with value - let mut result = state.find_constant(&x.3.name).unwrap().clone(); - result.set_position(x.3.pos); + let mut result = state.find_constant(&x.2.name).unwrap().clone(); + result.set_position(x.2.pos); *expr = result; } @@ -734,7 +737,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { } } -/// Optimize a block of [statements][crate::ast::Stmt] at top level. +/// Optimize a block of [statements][Stmt] at top level. fn optimize_top_level( mut statements: Vec, engine: &Engine, diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 4b241f55..ad1586ab 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -32,7 +32,7 @@ mod fn_ptr_functions { } else { let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize); ctx.engine() - .has_override(ctx.mods, ctx.lib, 0, hash_script, true) + .has_override(ctx.mods, ctx.lib, None, hash_script, true) } } } diff --git a/src/parse_error.rs b/src/parse_error.rs index 7419fb98..a9ce4e7f 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -91,13 +91,16 @@ pub enum ParseErrorType { UnknownOperator(String), /// Expecting a particular token but not finding one. Wrapped values are the token and description. MissingToken(String, String), - /// An expression in function call arguments `()` has syntax error. Wrapped value is the error description (if any). + /// An expression in function call arguments `()` has syntax error. Wrapped value is the error + /// description (if any). MalformedCallExpr(String), - /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any). + /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error + /// description (if any). /// /// Never appears under the `no_index` feature. MalformedIndexExpr(String), - /// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any). + /// An expression in an `in` expression has syntax error. Wrapped value is the error description + /// (if any). /// /// Never appears under the `no_object` and `no_index` features combination. MalformedInExpr(String), @@ -137,7 +140,8 @@ pub enum ParseErrorType { /// /// Never appears under the `no_function` feature. FnMissingParams(String), - /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. + /// A function definition has duplicated parameters. Wrapped values are the function name and + /// parameter name. /// /// Never appears under the `no_function` feature. FnDuplicatedParam(String, String), diff --git a/src/parser.rs b/src/parser.rs index 9a879083..5d22c9dd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,7 +13,7 @@ use crate::stdlib::{ format, hash::{Hash, Hasher}, iter::empty, - num::NonZeroUsize, + num::{NonZeroU64, NonZeroUsize}, string::{String, ToString}, vec, vec::Vec, @@ -34,7 +34,7 @@ use crate::FnAccess; type PERR = ParseErrorType; -type FunctionsLib = HashMap; +type FunctionsLib = HashMap; /// A type that encapsulates the current state of the parser. #[derive(Debug)] @@ -238,10 +238,10 @@ impl Expr { fn into_property(self, state: &mut ParseState) -> Self { match self { Self::Variable(x) if x.1.is_none() => { - let ident = x.3; + let ident = x.2; let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); - Self::Property(Box::new(((getter, setter), ident.into()))) + Self::Property(Box::new((getter, setter, ident.into()))) } _ => self, } @@ -278,8 +278,11 @@ fn parse_paren_expr( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - settings: ParseSettings, + mut settings: ParseSettings, ) -> Result { + // ( ... + settings.pos = eat_token(input, Token::LeftParen); + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -310,7 +313,7 @@ fn parse_fn_call( lib: &mut FunctionsLib, id: ImmutableString, capture: bool, - mut namespace: Option>, + mut namespace: Option, settings: ParseSettings, ) -> Result { let (token, token_pos) = input.peek().unwrap(); @@ -335,7 +338,7 @@ fn parse_fn_call( Token::RightParen => { eat_token(input, Token::RightParen); - let hash_script = if let Some(modules) = namespace.as_mut() { + let mut hash_script = if let Some(ref mut modules) = namespace { #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].name)); @@ -352,13 +355,17 @@ fn parse_fn_call( calc_script_fn_hash(empty(), &id, 0) }; + // script functions can only be valid identifiers + if !is_valid_identifier(id.chars()) { + hash_script = None; + } + return Ok(Expr::FnCall( Box::new(FnCallExpr { name: id.to_string().into(), - native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers capture, namespace, - hash: hash_script, + hash_script, args, ..Default::default() }), @@ -383,7 +390,7 @@ fn parse_fn_call( (Token::RightParen, _) => { eat_token(input, Token::RightParen); - let hash_script = if let Some(modules) = namespace.as_mut() { + let mut hash_script = if let Some(modules) = namespace.as_mut() { #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].name)); @@ -400,13 +407,17 @@ fn parse_fn_call( calc_script_fn_hash(empty(), &id, args.len()) }; + // script functions can only be valid identifiers + if !is_valid_identifier(id.chars()) { + hash_script = None; + } + return Ok(Expr::FnCall( Box::new(FnCallExpr { name: id.to_string().into(), - native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers capture, namespace, - hash: hash_script, + hash_script, args, ..Default::default() }), @@ -629,8 +640,11 @@ fn parse_array_literal( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - settings: ParseSettings, + mut settings: ParseSettings, ) -> Result { + // [ ... + settings.pos = eat_token(input, Token::LeftBracket); + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -696,8 +710,11 @@ fn parse_map_literal( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - settings: ParseSettings, + mut settings: ParseSettings, ) -> Result { + // #{ ... + settings.pos = eat_token(input, Token::MapStart); + #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -939,105 +956,196 @@ fn parse_primary( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, _) = match token { + let mut root_expr = match token { + Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), + + Token::IntegerConstant(_) + | Token::CharConstant(_) + | Token::StringConstant(_) + | Token::True + | Token::False => match input.next().unwrap().0 { + Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), + Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), + Token::StringConstant(s) => { + Expr::StringConstant(state.get_interned_string(s), settings.pos) + } + Token::True => Expr::BoolConstant(true, settings.pos), + Token::False => Expr::BoolConstant(false, settings.pos), + _ => unreachable!(), + }, + #[cfg(not(feature = "no_float"))] + Token::FloatConstant(x) => { + let x = *x; + input.next().unwrap(); + Expr::FloatConstant(x, settings.pos) + } + // { - block statement as expression Token::LeftBrace if settings.allow_stmt_expr => { - return parse_block(input, state, lib, settings.level_up()).map(|block| match block { + match parse_block(input, state, lib, settings.level_up())? { Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos), _ => unreachable!(), - }) - } - Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), - _ => input.next().unwrap(), - }; - - let (next_token, _) = input.peek().unwrap(); - - let mut root_expr = match token { - Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), - #[cfg(not(feature = "no_float"))] - Token::FloatConstant(x) => Expr::FloatConstant(x, settings.pos), - Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), - Token::StringConstant(s) => { - Expr::StringConstant(state.get_interned_string(s), settings.pos) - } - - // Function call - Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { - // Once the identifier consumed we must enable next variables capturing - #[cfg(not(feature = "no_closure"))] - { - state.allow_capture = true; - } - let var_name_def = Ident { - name: state.get_interned_string(s), - pos: settings.pos, - }; - Expr::Variable(Box::new((None, None, 0, var_name_def))) - } - // Namespace qualification - #[cfg(not(feature = "no_module"))] - Token::Identifier(s) if *next_token == Token::DoubleColon => { - // Once the identifier consumed we must enable next variables capturing - #[cfg(not(feature = "no_closure"))] - { - state.allow_capture = true; - } - let var_name_def = Ident { - name: state.get_interned_string(s), - pos: settings.pos, - }; - Expr::Variable(Box::new((None, None, 0, var_name_def))) - } - // Normal variable access - Token::Identifier(s) => { - let index = state.access_var(&s, settings.pos); - let var_name_def = Ident { - name: state.get_interned_string(s), - pos: settings.pos, - }; - Expr::Variable(Box::new((index, None, 0, var_name_def))) - } - - // Function call is allowed to have reserved keyword - Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { - if is_keyword_function(&s) { - let var_name_def = Ident { - name: state.get_interned_string(s), - pos: settings.pos, - }; - Expr::Variable(Box::new((None, None, 0, var_name_def))) - } else { - return Err(PERR::Reserved(s).into_err(settings.pos)); } } - - // Access to `this` as a variable is OK - Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => { - if !settings.is_function_scope { - let msg = format!("'{}' can only be used in functions", s); - return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos)); - } else { - let var_name_def = Ident { - name: state.get_interned_string(s), - pos: settings.pos, - }; - Expr::Variable(Box::new((None, None, 0, var_name_def))) - } - } - - Token::Reserved(s) if is_valid_identifier(s.chars()) => { - return Err(PERR::Reserved(s).into_err(settings.pos)); - } - + // ( - grouped expression Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?, + + // If statement is allowed to act as expressions + Token::If if settings.allow_if_expr => Expr::Stmt( + Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()), + settings.pos, + ), + // Switch statement is allowed to act as expressions + Token::Switch if settings.allow_switch_expr => Expr::Stmt( + Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()), + settings.pos, + ), + // | ... + #[cfg(not(feature = "no_function"))] + Token::Pipe | Token::Or if settings.allow_anonymous_fn => { + let mut new_state = ParseState::new( + state.engine, + state.script_hash, + #[cfg(not(feature = "unchecked"))] + state.max_function_expr_depth, + #[cfg(not(feature = "unchecked"))] + state.max_function_expr_depth, + ); + + let settings = ParseSettings { + allow_if_expr: true, + allow_switch_expr: true, + allow_stmt_expr: true, + allow_anonymous_fn: true, + is_global: false, + is_function_scope: true, + is_breakable: false, + level: 0, + pos: settings.pos, + }; + + let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; + + #[cfg(not(feature = "no_closure"))] + new_state.externals.iter().for_each(|(closure, pos)| { + state.access_var(closure, *pos); + }); + + // Qualifiers (none) + function name + number of arguments. + let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(); + + lib.insert(hash, func); + + expr + } + + // Array literal #[cfg(not(feature = "no_index"))] Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?, + + // Map literal #[cfg(not(feature = "no_object"))] Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, - Token::True => Expr::BoolConstant(true, settings.pos), - Token::False => Expr::BoolConstant(false, settings.pos), - Token::LexError(err) => return Err(err.into_err(settings.pos)), + + // Identifier + Token::Identifier(_) => { + let s = match input.next().unwrap().0 { + Token::Identifier(s) => s, + _ => unreachable!(), + }; + + match input.peek().unwrap().0 { + // Function call + Token::LeftParen | Token::Bang => { + // Once the identifier consumed we must enable next variables capturing + #[cfg(not(feature = "no_closure"))] + { + state.allow_capture = true; + } + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; + Expr::Variable(Box::new((None, None, var_name_def))) + } + // Namespace qualification + #[cfg(not(feature = "no_module"))] + Token::DoubleColon => { + // Once the identifier consumed we must enable next variables capturing + #[cfg(not(feature = "no_closure"))] + { + state.allow_capture = true; + } + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; + Expr::Variable(Box::new((None, None, var_name_def))) + } + // Normal variable access + _ => { + let index = state.access_var(&s, settings.pos); + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; + Expr::Variable(Box::new((index, None, var_name_def))) + } + } + } + + // Reserved keyword or symbol + Token::Reserved(_) => { + let s = match input.next().unwrap().0 { + Token::Reserved(s) => s, + _ => unreachable!(), + }; + + match input.peek().unwrap().0 { + // Function call is allowed to have reserved keyword + Token::LeftParen | Token::Bang => { + if s == KEYWORD_THIS { + return Err(PERR::Reserved(s).into_err(settings.pos)); + } else if is_keyword_function(&s) { + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; + Expr::Variable(Box::new((None, None, var_name_def))) + } else { + return Err(PERR::Reserved(s).into_err(settings.pos)); + } + } + // Access to `this` as a variable is OK + _ if s == KEYWORD_THIS => { + if !settings.is_function_scope { + let msg = format!("'{}' can only be used in functions", s); + return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos)); + } else { + let var_name_def = Ident { + name: state.get_interned_string(s), + pos: settings.pos, + }; + Expr::Variable(Box::new((None, None, var_name_def))) + } + } + _ if is_valid_identifier(s.chars()) => { + return Err(PERR::Reserved(s).into_err(settings.pos)); + } + _ => { + return Err(LexError::UnexpectedInput(s).into_err(settings.pos)); + } + } + } + + Token::LexError(_) => { + let err = match input.next().unwrap().0 { + Token::LexError(err) => err, + _ => unreachable!(), + }; + + return Err(err.into_err(settings.pos)); + } _ => { return Err( @@ -1048,26 +1156,26 @@ fn parse_primary( // Tail processing all possible postfix operators loop { - let (token, _) = input.peek().unwrap(); + let (tail_token, _) = input.peek().unwrap(); - if !root_expr.is_valid_postfix(token) { + if !root_expr.is_valid_postfix(tail_token) { break; } - let (token, token_pos) = input.next().unwrap(); - settings.pos = token_pos; + let (tail_token, tail_pos) = input.next().unwrap(); + settings.pos = tail_pos; - root_expr = match (root_expr, token) { + root_expr = match (root_expr, tail_token) { // Qualified function call with ! (Expr::Variable(x), Token::Bang) if x.1.is_some() => { return Err(if !match_token(input, Token::LeftParen).0 { - LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos) + LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos) } else { LexError::ImproperSymbol( "!".to_string(), "'!' cannot be used to call module functions".to_string(), ) - .into_err(token_pos) + .into_err(tail_pos) }); } // Function call with ! @@ -1081,35 +1189,38 @@ fn parse_primary( .into_err(pos)); } - let (_, modules, _, Ident { name, pos }) = *x; + let (_, namespace, Ident { name, pos }) = *x; settings.pos = pos; - parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? + let ns = namespace.map(|(_, ns)| ns); + parse_fn_call(input, state, lib, name, true, ns, settings.level_up())? } // Function call (Expr::Variable(x), Token::LeftParen) => { - let (_, modules, _, Ident { name, pos }) = *x; + let (_, namespace, Ident { name, pos }) = *x; settings.pos = pos; - parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? + let ns = namespace.map(|(_, ns)| ns); + parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - let (index, mut modules, _, var_name_def) = *x; + let (index, mut namespace, var_name_def) = *x; - if let Some(ref mut modules) = modules { - modules.push(var_name_def); + if let Some((_, ref mut namespace)) = namespace { + namespace.push(var_name_def); } else { - let mut m: NamespaceRef = Default::default(); - m.push(var_name_def); - modules = Some(Box::new(m)); + let mut ns: NamespaceRef = Default::default(); + ns.push(var_name_def); + let index = NonZeroU64::new(42).unwrap(); // Dummy + namespace = Some((index, ns)); } let var_name_def = Ident { name: state.get_interned_string(id2), pos: pos2, }; - Expr::Variable(Box::new((index, modules, 0, var_name_def))) + Expr::Variable(Box::new((index, namespace, var_name_def))) } (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { return Err(PERR::Reserved(id2).into_err(pos2)); @@ -1121,11 +1232,17 @@ fn parse_primary( (expr, Token::LeftBracket) => { parse_index_chain(input, state, lib, expr, settings.level_up())? } - // Method access + // Property access #[cfg(not(feature = "no_object"))] (expr, Token::Period) => { + // prevents capturing of the object properties as vars: xxx. + #[cfg(not(feature = "no_closure"))] + if let (Token::Identifier(_), _) = input.peek().unwrap() { + state.allow_capture = false; + } + let rhs = parse_unary(input, state, lib, settings.level_up())?; - make_dot_expr(state, expr, rhs, token_pos)? + make_dot_expr(state, expr, rhs, tail_pos)? } // Unknown postfix operator (expr, token) => unreachable!( @@ -1146,14 +1263,16 @@ fn parse_primary( _ => None, } .map(|x| { - let (_, modules, hash, Ident { name, .. }) = x.as_mut(); - let namespace = modules.as_mut().unwrap(); + if let (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) = x.as_mut() { + // Qualifiers + variable name + *hash = + calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap(); - // Qualifiers + variable name - *hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); - - #[cfg(not(feature = "no_module"))] - namespace.set_index(state.find_module(&namespace[0].name)); + #[cfg(not(feature = "no_module"))] + namespace.set_index(state.find_module(&namespace[0].name)); + } else { + unreachable!(); + } }); // Make sure identifiers are valid @@ -1174,16 +1293,6 @@ fn parse_unary( settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { - // If statement is allowed to act as expressions - Token::If if settings.allow_if_expr => Ok(Expr::Stmt( - Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()), - settings.pos, - )), - // Switch statement is allowed to act as expressions - Token::Switch if settings.allow_switch_expr => Ok(Expr::Stmt( - Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()), - settings.pos, - )), // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); @@ -1208,16 +1317,12 @@ fn parse_unary( // Call negative function expr => { let op = "-"; - let hash = calc_script_fn_hash(empty(), op, 1); let mut args = StaticVec::new(); args.push(expr); Ok(Expr::FnCall( Box::new(FnCallExpr { name: op.into(), - native_only: true, - namespace: None, - hash, args, ..Default::default() }), @@ -1238,16 +1343,12 @@ fn parse_unary( // Call plus function expr => { let op = "+"; - let hash = calc_script_fn_hash(empty(), op, 1); let mut args = StaticVec::new(); args.push(expr); Ok(Expr::FnCall( Box::new(FnCallExpr { name: op.into(), - native_only: true, - namespace: None, - hash, args, ..Default::default() }), @@ -1264,13 +1365,10 @@ fn parse_unary( args.push(expr); let op = "!"; - let hash = calc_script_fn_hash(empty(), op, 1); Ok(Expr::FnCall( Box::new(FnCallExpr { name: op.into(), - native_only: true, - hash, args, def_value: Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false ..Default::default() @@ -1278,44 +1376,6 @@ fn parse_unary( pos, )) } - // | ... - #[cfg(not(feature = "no_function"))] - Token::Pipe | Token::Or if settings.allow_anonymous_fn => { - let mut new_state = ParseState::new( - state.engine, - state.script_hash, - #[cfg(not(feature = "unchecked"))] - state.max_function_expr_depth, - #[cfg(not(feature = "unchecked"))] - state.max_function_expr_depth, - ); - - let settings = ParseSettings { - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - allow_anonymous_fn: true, - is_global: false, - is_function_scope: true, - is_breakable: false, - level: 0, - pos: *token_pos, - }; - - let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; - - #[cfg(not(feature = "no_closure"))] - new_state.externals.iter().for_each(|(closure, pos)| { - state.access_var(closure, *pos); - }); - - // Qualifiers (none) + function name + number of arguments. - let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()); - - lib.insert(hash, func); - - Ok(expr) - } // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens @@ -1323,6 +1383,7 @@ fn parse_unary( } } +/// Make an assignment statement. fn make_assignment_stmt<'a>( fn_name: Cow<'static, str>, state: &mut ParseState, @@ -1338,7 +1399,7 @@ fn make_assignment_stmt<'a>( )), // var (indexed) = rhs Expr::Variable(x) => { - let (index, _, _, Ident { name, pos }) = x.as_ref(); + let (index, _, Ident { name, pos }) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { AccessMode::ReadWrite => Ok(Stmt::Assignment( Box::new((lhs, fn_name.into(), rhs)), @@ -1359,7 +1420,7 @@ fn make_assignment_stmt<'a>( )), // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { - let (index, _, _, Ident { name, pos }) = x.as_ref(); + let (index, _, Ident { name, pos }) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { AccessMode::ReadWrite => Ok(Stmt::Assignment( Box::new((lhs, fn_name.into(), rhs)), @@ -1443,16 +1504,16 @@ fn make_dot_expr( } // lhs.id (lhs, Expr::Variable(x)) if x.1.is_none() => { - let ident = x.3; + let ident = x.2; let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); - let rhs = Expr::Property(Box::new(((getter, setter), ident))); + let rhs = Expr::Property(Box::new((getter, setter, ident))); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } // lhs.module::id - syntax error (_, Expr::Variable(x)) if x.1.is_some() => { - return Err(PERR::PropertyExpected.into_err(x.1.unwrap()[0].pos)); + return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos)); } // lhs.prop (lhs, prop @ Expr::Property(_)) => { @@ -1662,15 +1723,24 @@ fn parse_binary_op( loop { let (current_op, current_pos) = input.peek().unwrap(); - let precedence = if let Token::Custom(c) = current_op { - // Custom operators - if let Some(Some(p)) = state.engine.custom_keywords.get(c) { - *p - } else { - return Err(PERR::Reserved(c.clone()).into_err(*current_pos)); + let precedence = match current_op { + Token::Custom(c) => { + if state + .engine + .custom_keywords + .get(c) + .map(Option::is_some) + .unwrap_or(false) + { + state.engine.custom_keywords.get(c).unwrap().unwrap().get() + } else { + return Err(PERR::Reserved(c.clone()).into_err(*current_pos)); + } } - } else { - current_op.precedence() + Token::Reserved(c) if !is_valid_identifier(c.chars()) => { + return Err(PERR::UnknownOperator(c.into()).into_err(*current_pos)) + } + _ => current_op.precedence(), }; let bind_right = current_op.is_bind_right(); @@ -1682,28 +1752,27 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); - if cfg!(not(feature = "no_object")) && op_token == Token::Period { - if let (Token::Identifier(_), _) = input.peek().unwrap() { - // prevents capturing of the object properties as vars: xxx. - #[cfg(not(feature = "no_closure"))] - { - state.allow_capture = false; - } - } - } - let rhs = parse_unary(input, state, lib, settings)?; let (next_op, next_pos) = input.peek().unwrap(); - let next_precedence = if let Token::Custom(c) = next_op { - // Custom operators - if let Some(Some(p)) = state.engine.custom_keywords.get(c) { - *p - } else { - return Err(PERR::Reserved(c.clone()).into_err(*next_pos)); + let next_precedence = match next_op { + Token::Custom(c) => { + if state + .engine + .custom_keywords + .get(c) + .map(Option::is_some) + .unwrap_or(false) + { + state.engine.custom_keywords.get(c).unwrap().unwrap().get() + } else { + return Err(PERR::Reserved(c.clone()).into_err(*next_pos)); + } } - } else { - next_op.precedence() + Token::Reserved(c) if !is_valid_identifier(c.chars()) => { + return Err(PERR::UnknownOperator(c.into()).into_err(*next_pos)) + } + _ => next_op.precedence(), }; // Bind to right if the next operator has higher precedence @@ -1723,11 +1792,9 @@ fn parse_binary_op( let cmp_def = Some(false.into()); let op = op_token.syntax(); - let hash = calc_script_fn_hash(empty(), &op, 2); let op_base = FnCallExpr { name: op, - native_only: true, capture: false, ..Default::default() }; @@ -1747,19 +1814,11 @@ fn parse_binary_op( | Token::PowerOf | Token::Ampersand | Token::Pipe - | Token::XOr => Expr::FnCall( - Box::new(FnCallExpr { - hash, - args, - ..op_base - }), - pos, - ), + | Token::XOr => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos), // '!=' defaults to true when passed invalid operands Token::NotEqualsTo => Expr::FnCall( Box::new(FnCallExpr { - hash, args, def_value: Some(true.into()), ..op_base @@ -1774,7 +1833,6 @@ fn parse_binary_op( | Token::GreaterThan | Token::GreaterThanEqualsTo => Expr::FnCall( Box::new(FnCallExpr { - hash, args, def_value: cmp_def, ..op_base @@ -1810,20 +1868,31 @@ fn parse_binary_op( make_in_expr(current_lhs, rhs, pos)? } - #[cfg(not(feature = "no_object"))] - Token::Period => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - make_dot_expr(state, current_lhs, rhs, pos)? - } + // #[cfg(not(feature = "no_object"))] + // Token::Period => { + // let rhs = args.pop().unwrap(); + // let current_lhs = args.pop().unwrap(); + // make_dot_expr(state, current_lhs, rhs, pos)? + // } + Token::Custom(s) + if state + .engine + .custom_keywords + .get(&s) + .map(Option::is_some) + .unwrap_or(false) => + { + let hash_script = if is_valid_identifier(s.chars()) { + // Accept non-native functions for custom operators + calc_script_fn_hash(empty(), &s, 2) + } else { + None + }; - Token::Custom(s) if state.engine.custom_keywords.contains_key(&s) => { - // Accept non-native functions for custom operators Expr::FnCall( Box::new(FnCallExpr { - hash, + hash_script, args, - native_only: false, ..op_base }), pos, @@ -1892,7 +1961,7 @@ fn parse_custom_syntax( segments.push(name.clone()); tokens.push(state.get_interned_string(MARKER_IDENT)); let var_name_def = Ident { name, pos }; - keywords.push(Expr::Variable(Box::new((None, None, 0, var_name_def)))); + keywords.push(Expr::Variable(Box::new((None, None, var_name_def)))); } (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2549,7 +2618,7 @@ fn parse_stmt( let func = parse_fn(input, &mut new_state, lib, access, settings, comments)?; // Qualifiers (none) + function name + number of arguments. - let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()); + let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(); lib.insert(hash, func); @@ -2565,6 +2634,7 @@ fn parse_stmt( } Token::If => parse_if(input, state, lib, settings.level_up()).map(Some), + Token::Switch => parse_switch(input, state, lib, settings.level_up()).map(Some), Token::While | Token::Loop => { parse_while_loop(input, state, lib, settings.level_up()).map(Some) } @@ -2812,22 +2882,22 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po #[cfg(not(feature = "no_closure"))] externals.iter().for_each(|x| { - args.push(Expr::Variable(Box::new((None, None, 0, x.clone().into())))); + args.push(Expr::Variable(Box::new((None, None, x.clone().into())))); }); #[cfg(feature = "no_closure")] externals.into_iter().for_each(|x| { - args.push(Expr::Variable(Box::new((None, None, 0, x.clone().into())))); + args.push(Expr::Variable(Box::new((None, None, x.clone().into())))); }); let curry_func = crate::engine::KEYWORD_FN_PTR_CURRY; - let hash = calc_script_fn_hash(empty(), curry_func, num_externals + 1); + let hash_script = calc_script_fn_hash(empty(), curry_func, num_externals + 1); let expr = Expr::FnCall( Box::new(FnCallExpr { name: curry_func.into(), - hash, + hash_script, args, ..Default::default() }), diff --git a/src/plugin.rs b/src/plugin.rs index d07cc188..9882951f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -13,8 +13,8 @@ pub use rhai_codegen::*; pub use rhai_codegen::{export_fn, register_exported_fn}; /// Trait implemented by a _plugin function_. -/// This trait should not be used directly. /// +/// This trait should not be used directly. /// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead. pub trait PluginFunction { /// Call the plugin function with the arguments provided. diff --git a/src/result.rs b/src/result.rs index 7719f225..02a0f5c8 100644 --- a/src/result.rs +++ b/src/result.rs @@ -55,8 +55,8 @@ pub enum EvalAltResult { /// String indexing out-of-bounds. /// Wrapped values are the current number of characters in the string and the index number. ErrorStringBounds(usize, INT, Position), - /// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined. - /// Wrapped value is the type name. + /// Trying to index into a type that is not an array, an object map, or a string, and has no + /// indexer function defined. Wrapped value is the type name. ErrorIndexingType(String, Position), /// Invalid arguments for `in` operator. ErrorInExpr(Position), diff --git a/src/scope.rs b/src/scope.rs index 4e5cc08a..3498dce3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -39,17 +39,18 @@ use crate::{Dynamic, ImmutableString, StaticVec}; // # Implementation Notes // // [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) -// is manually split into three equal-length arrays. That's because variable names take up the most space, -// with [`Cow`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up -// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether. +// is manually split into two equal-length arrays. That's because variable names take up the most space, +// with [`Cow`][Cow] being four words long, but in the vast majority of cases the name is NOT used to +// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether. // // Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed. -// The variable type is packed separately into another array because it is even smaller. -#[derive(Debug)] +// +// The alias is `Box`'ed because it occurs infrequently. +#[derive(Debug, Clone, Hash)] pub struct Scope<'a> { /// Current value of the entry. values: Vec, - /// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely. + /// (Name, aliases) of the entry. names: Vec<(Cow<'a, str>, Box>)>, } @@ -357,8 +358,35 @@ impl<'a> Scope<'a> { self } /// Get a mutable reference to an entry in the [`Scope`]. + /// + /// If the entry by the specified name is not found, of if it is read-only, + /// [`None`] is returned. + /// + /// # Example + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// + /// let ptr = my_scope.get_mut("x").unwrap(); + /// *ptr = 123_i64.into(); + /// + /// assert_eq!(my_scope.get_value::("x").unwrap(), 123); + /// ``` + pub fn get_mut(&mut self, name: &str) -> Option<&mut Dynamic> { + self.get_index(name) + .and_then(move |(index, access)| match access { + AccessMode::ReadWrite => Some(self.get_mut_by_index(index)), + AccessMode::ReadOnly => None, + }) + } + /// Get a mutable reference to an entry in the [`Scope`] based on the index. #[inline(always)] - pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic { + pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { self.values.get_mut(index).expect("invalid index in Scope") } /// Update the access type of an entry in the [`Scope`]. diff --git a/src/serde_impl/metadata.rs b/src/serde_impl/metadata.rs index b8bb010b..80a6cd01 100644 --- a/src/serde_impl/metadata.rs +++ b/src/serde_impl/metadata.rs @@ -90,7 +90,7 @@ struct FnMetadata { pub return_type: Option, pub signature: String, #[serde(default, skip_serializing_if = "Option::is_none")] - pub doc_comments: Option>, + pub doc_comments: Vec, } impl PartialOrd for FnMetadata { @@ -152,9 +152,9 @@ impl From<&crate::module::FuncInfo> for FnMetadata { }, signature: info.gen_signature(), doc_comments: if info.func.is_script() { - Some(info.func.get_fn_def().comments.clone()) + info.func.get_fn_def().comments.clone() } else { - None + Default::default() }, } } diff --git a/src/syntax.rs b/src/syntax.rs index 4ceec8f9..8ce578fa 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -121,20 +121,26 @@ impl Engine { continue; } + let token = Token::lookup_from_syntax(s); + let seg = match s { // Markers not in first position MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK if !segments.is_empty() => s.into(), // Standard or reserved keyword/symbol not in first position - s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => { - // Make it a custom keyword/symbol - if !self.custom_keywords.contains_key(s) { + s if !segments.is_empty() && token.is_some() => { + // Make it a custom keyword/symbol if it is disabled or reserved + if (self.disabled_symbols.contains(s) + || matches!(token, Some(Token::Reserved(_)))) + && !self.custom_keywords.contains_key(s) + { self.custom_keywords.insert(s.into(), None); } s.into() } // Standard keyword in first position s if segments.is_empty() - && Token::lookup_from_syntax(s) + && token + .as_ref() .map(|v| v.is_keyword() || v.is_reserved()) .unwrap_or(false) => { @@ -151,7 +157,11 @@ impl Engine { } // Identifier in first position s if segments.is_empty() && is_valid_identifier(s.chars()) => { - if !self.custom_keywords.contains_key(s) { + // Make it a custom keyword/symbol if it is disabled or reserved + if (self.disabled_symbols.contains(s) + || matches!(token, Some(Token::Reserved(_)))) + && !self.custom_keywords.contains_key(s) + { self.custom_keywords.insert(s.into(), None); } s.into() @@ -208,7 +218,7 @@ impl Engine { /// * `parse` is the parsing function. /// * `func` is the implementation function. /// - /// All custom keywords must be manually registered via [`register_custom_operator`][Engine::register_custom_operator]. + /// All custom keywords must be manually registered via [`Engine::register_custom_operator`]. /// Otherwise, custom keywords won't be recognized. pub fn register_custom_syntax_raw( &mut self, diff --git a/src/token.rs b/src/token.rs index ee3febb0..1d3fbead 100644 --- a/src/token.rs +++ b/src/token.rs @@ -532,10 +532,10 @@ impl Token { #[cfg(feature = "no_module")] "import" | "export" | "as" => Reserved(syntax.into()), - "===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" - | "use" | "module" | "package" | "var" | "static" | "begin" | "end" | "shared" - | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match" | "case" - | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" + "===" | "!==" | "->" | "<-" | ":=" | "**" | "::<" | "(*" | "*)" | "#" | "public" + | "new" | "use" | "module" | "package" | "var" | "static" | "begin" | "end" + | "shared" | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match" + | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR @@ -1742,27 +1742,21 @@ impl<'a> Iterator for TokenIterator<'a, '_> { Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => { Some((Token::Custom(s), pos)) } - // Custom standard keyword - must be disabled - Some((token, pos)) if token.is_keyword() && self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => { + // Custom standard keyword/symbol - must be disabled + Some((token, pos)) if self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => { if self.engine.disabled_symbols.contains(token.syntax().as_ref()) { - // Disabled standard keyword + // Disabled standard keyword/symbol Some((Token::Custom(token.syntax().into()), pos)) } else { // Active standard keyword - should never be a custom keyword! - unreachable!() + unreachable!("{:?}", token) } } - // Disabled operator - Some((token, pos)) if token.is_operator() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => { - Some(( - Token::LexError(LexError::UnexpectedInput(token.syntax().into())), - pos, - )) - } - // Disabled standard keyword - Some((token, pos)) if token.is_keyword() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => { + // Disabled symbol + Some((token, pos)) if self.engine.disabled_symbols.contains(token.syntax().as_ref()) => { Some((Token::Reserved(token.syntax().into()), pos)) } + // Normal symbol r => r, }; diff --git a/src/utils.rs b/src/utils.rs index 8513dd6d..52dfda99 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,30 +9,34 @@ use crate::stdlib::{ fmt, hash::{BuildHasher, Hash, Hasher}, iter::{empty, FromIterator}, + num::NonZeroU64, ops::{Add, AddAssign, Deref}, str::FromStr, string::{String, ToString}, }; use crate::Shared; -/// A hasher that only takes one single [`u64`] and returns it as a hash key. +/// A hasher that only takes one single [`NonZeroU64`] and returns it as a hash key. /// /// # Panics /// -/// Panics when hashing any data type other than a [`u64`]. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] -pub struct StraightHasher(u64); +/// Panics when hashing any data type other than a [`NonZeroU64`]. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct StraightHasher(NonZeroU64); impl Hasher for StraightHasher { #[inline(always)] fn finish(&self) -> u64 { - self.0 + self.0.get() } #[inline(always)] fn write(&mut self, bytes: &[u8]) { let mut key = [0_u8; 8]; key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes - self.0 = u64::from_le_bytes(key); + + // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... + self.0 = NonZeroU64::new(u64::from_le_bytes(key)) + .unwrap_or_else(|| NonZeroU64::new(42).unwrap()); } } @@ -45,11 +49,22 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] fn build_hasher(&self) -> Self::Hasher { - Default::default() + StraightHasher(NonZeroU64::new(42).unwrap()) } } -/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types. +/// Create an instance of the default hasher. +pub fn get_hasher() -> impl Hasher { + #[cfg(feature = "no_std")] + let s: ahash::AHasher = Default::default(); + #[cfg(not(feature = "no_std"))] + let s = crate::stdlib::collections::hash_map::DefaultHasher::new(); + + s +} + +/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and +/// parameter types. /// Exported under the `internals` feature only. /// /// Module names are passed in via `&str` references from an iterator. @@ -63,11 +78,11 @@ pub fn calc_native_fn_hash<'a>( modules: impl Iterator, fn_name: &str, params: impl Iterator, -) -> u64 { +) -> Option { calc_fn_hash(modules, fn_name, None, params) } -/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name +/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name /// and the number of parameters, but no parameter types. /// Exported under the `internals` feature only. /// @@ -82,21 +97,11 @@ pub fn calc_script_fn_hash<'a>( modules: impl Iterator, fn_name: &str, num: usize, -) -> u64 { +) -> Option { calc_fn_hash(modules, fn_name, Some(num), empty()) } -/// Create an instance of the default hasher. -pub fn get_hasher() -> impl Hasher { - #[cfg(feature = "no_std")] - let s: ahash::AHasher = Default::default(); - #[cfg(not(feature = "no_std"))] - let s = crate::stdlib::collections::hash_map::DefaultHasher::new(); - - s -} - -/// Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types. +/// Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types. /// /// Module names are passed in via `&str` references from an iterator. /// Parameter types are passed in via [`TypeId`] values from an iterator. @@ -104,12 +109,13 @@ pub fn get_hasher() -> impl Hasher { /// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. +#[inline(always)] fn calc_fn_hash<'a>( mut modules: impl Iterator, fn_name: &str, num: Option, params: impl Iterator, -) -> u64 { +) -> Option { let s = &mut get_hasher(); // Hash a boolean indicating whether the hash is namespace-qualified. @@ -122,7 +128,15 @@ fn calc_fn_hash<'a>( } else { params.for_each(|t| t.hash(s)); } - s.finish() + // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... + NonZeroU64::new(s.finish()).or_else(|| NonZeroU64::new(42)) +} + +/// Combine two [`NonZeroU64`] hashes by taking the XOR of them. +#[inline(always)] +pub(crate) fn combine_hashes(a: NonZeroU64, b: NonZeroU64) -> NonZeroU64 { + // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... + NonZeroU64::new(a.get() ^ b.get()).unwrap_or_else(|| NonZeroU64::new(42).unwrap()) } /// The system immutable string type. diff --git a/tests/arrays.rs b/tests/arrays.rs index 3c1dbff2..78cf30b6 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -32,7 +32,7 @@ fn test_arrays() -> Result<(), Box> { x.append(y); x.len + r - " + " )?, 14 ); @@ -42,7 +42,7 @@ fn test_arrays() -> Result<(), Box> { let x = [1, 2, 3]; x += [4, 5]; len(x) - " + " )?, 5 ); @@ -53,7 +53,7 @@ fn test_arrays() -> Result<(), Box> { let x = [1, 2, 3]; let y = [4, 5]; x + y - " + " )? .len(), 5 diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 7b959472..c95e2853 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -82,7 +82,7 @@ fn test_call_fn_private() -> Result<(), Box> { let ast = engine.compile("private fn add(x, n, ) { x + n }")?; assert!(matches!( - *engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT)) + *(engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT)) as Result>) .expect_err("should error"), EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add" )); diff --git a/tests/closures.rs b/tests/closures.rs index 7c74e9bd..4a0cd74b 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -54,6 +54,17 @@ fn test_closures() -> Result<(), Box> { ParseErrorType::BadInput(_) )); + assert_eq!( + engine.eval::( + r" + let foo = #{ x: 42 }; + let f = || { this.x }; + foo.call(f) + ", + )?, + 42 + ); + assert_eq!( engine.eval::( r#" @@ -239,7 +250,7 @@ fn test_closures_shared_obj() -> Result<(), Box> { let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box> { let action_ptr = res["action"].clone().cast::(); let name = action_ptr.fn_name(); - engine.call_fn::<_, ()>(&mut Scope::new(), &ast, name, (p1, p2)) + engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2)) }; // Test closure diff --git a/tests/modules.rs b/tests/modules.rs index b07d4d5d..26c4af8c 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -101,7 +101,7 @@ fn test_module_resolver() -> Result<(), Box> { resolver.insert("hello", module); let mut engine = Engine::new(); - engine.set_module_resolver(Some(resolver)); + engine.set_module_resolver(resolver); assert_eq!( engine.eval::( @@ -295,13 +295,13 @@ fn test_module_from_ast() -> Result<(), Box> { "#, )?; - engine.set_module_resolver(Some(resolver1)); + engine.set_module_resolver(resolver1); let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; let mut resolver2 = StaticModuleResolver::new(); resolver2.insert("testing", module); - engine.set_module_resolver(Some(resolver2)); + engine.set_module_resolver(resolver2); assert_eq!( engine.eval::(r#"import "testing" as ttt; ttt::abc"#)?, @@ -384,7 +384,7 @@ fn test_module_str() -> Result<(), Box> { let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); static_modules.insert("test", module); - engine.set_module_resolver(Some(static_modules)); + engine.set_module_resolver(static_modules); assert_eq!( engine.eval::(r#"import "test" as test; test::test("test");"#)?, @@ -418,7 +418,7 @@ fn test_module_ast_namespace() -> Result<(), Box> { let mut resolver = StaticModuleResolver::new(); resolver.insert("testing", module); - engine.set_module_resolver(Some(resolver)); + engine.set_module_resolver(resolver); assert_eq!( engine.eval::(r#"import "testing" as t; t::foo(41)"#)?, @@ -466,7 +466,7 @@ fn test_module_ast_namespace2() -> Result<(), Box> { let module = Module::eval_ast_as_new(Scope::new(), &module_ast, &engine)?; let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); static_modules.insert("test_module", module); - engine.set_module_resolver(Some(static_modules)); + engine.set_module_resolver(static_modules); engine.consume(SCRIPT)?; diff --git a/tests/tokens.rs b/tests/tokens.rs index 0070c23d..71158313 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -21,12 +21,17 @@ fn test_tokens_disabled() { .compile("let x = 40 + 2; x += 1;") .expect_err("should error") .0, - ParseErrorType::BadInput(LexError::UnexpectedInput("+=".to_string())) + ParseErrorType::UnknownOperator("+=".to_string()) ); + + assert!(matches!( + *engine.compile("let x = += 0;").expect_err("should error").0, + ParseErrorType::BadInput(LexError::UnexpectedInput(err)) if err == "+=" + )); } #[test] -fn test_tokens_custom_operator() -> Result<(), Box> { +fn test_tokens_custom_operator_identifiers() -> Result<(), Box> { let mut engine = Engine::new(); // Register a custom operator called `foo` and give it @@ -55,6 +60,29 @@ fn test_tokens_custom_operator() -> Result<(), Box> { Ok(()) } +#[test] +fn test_tokens_custom_operator_symbol() -> Result<(), Box> { + let mut engine = Engine::new(); + + // Register a custom operator `#` and give it + // a precedence of 160 (i.e. between +|- and *|/). + engine.register_custom_operator("#", 160).unwrap(); + + // Register a binary function named `#` + engine.register_fn("#", |x: INT, y: INT| (x * y) - (x + y)); + + assert_eq!(engine.eval_expression::("1 + 2 * 3 # 4 - 5 / 6")?, 15); + + // Register a custom operator named `=>` + assert!(engine.register_custom_operator("=>", 160).is_err()); + engine.disable_symbol("=>"); + engine.register_custom_operator("=>", 160).unwrap(); + engine.register_fn("=>", |x: INT, y: INT| (x * y) - (x + y)); + assert_eq!(engine.eval_expression::("1 + 2 * 3 => 4 - 5 / 6")?, 15); + + Ok(()) +} + #[test] fn test_tokens_unicode_xid_ident() -> Result<(), Box> { let engine = Engine::new();