Merge pull request #315 from schungx/master

Refine parsing.
This commit is contained in:
Stephen Chung 2020-12-27 17:01:40 +08:00 committed by GitHub
commit 90a337d4c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1607 additions and 1124 deletions

View File

@ -7,12 +7,26 @@ Version 0.19.9
This version removes the confusing differences between _packages_ and _modules_ This version removes the confusing differences between _packages_ and _modules_
by unifying the terminology and API under the global umbrella of _modules_. by unifying the terminology and API under the global umbrella of _modules_.
Bug fixes
---------
* Property access in
Breaking changes Breaking changes
---------------- ----------------
* `Engine::load_package` is renamed `Engine::register_global_module` and now must explicitly pass a shared [`Module`]. * `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`]. * `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`. * `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 Version 0.19.8

View File

@ -3,7 +3,7 @@ use syn::{
spanned::Spanned, spanned::Spanned,
}; };
#[derive(Debug)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ExportScope { pub enum ExportScope {
PubOnly, PubOnly,
Prefix(String), Prefix(String),
@ -22,12 +22,14 @@ pub trait ExportedParams: Sized {
fn from_info(info: ExportInfo) -> syn::Result<Self>; fn from_info(info: ExportInfo) -> syn::Result<Self>;
} }
#[derive(Debug, Clone)]
pub struct AttrItem { pub struct AttrItem {
pub key: proc_macro2::Ident, pub key: proc_macro2::Ident,
pub value: Option<syn::LitStr>, pub value: Option<syn::LitStr>,
pub span: proc_macro2::Span, pub span: proc_macro2::Span,
} }
#[derive(Debug, Clone)]
pub struct ExportInfo { pub struct ExportInfo {
pub item_span: proc_macro2::Span, pub item_span: proc_macro2::Span,
pub items: Vec<AttrItem>, pub items: Vec<AttrItem>,

View File

@ -22,23 +22,30 @@ use crate::attrs::{ExportInfo, ExportScope, ExportedParams};
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)]
pub enum FnNamespaceAccess { pub enum FnNamespaceAccess {
Unset,
Global, Global,
Internal, 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 { pub enum Index {
Get, Get,
Set, Set,
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum Property { pub enum Property {
Get(syn::Ident), Get(syn::Ident),
Set(syn::Ident), Set(syn::Ident),
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum FnSpecialAccess { pub enum FnSpecialAccess {
None, None,
Index(Index), Index(Index),
@ -97,11 +104,11 @@ pub(crate) fn print_type(ty: &syn::Type) -> String {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct ExportedFnParams { pub(crate) struct ExportedFnParams {
pub name: Option<Vec<String>>, pub name: Vec<String>,
pub return_raw: bool, pub return_raw: bool,
pub skip: bool, pub skip: bool,
pub special: FnSpecialAccess, pub special: FnSpecialAccess,
pub namespace: Option<FnNamespaceAccess>, pub namespace: FnNamespaceAccess,
pub span: Option<proc_macro2::Span>, pub span: Option<proc_macro2::Span>,
} }
@ -138,7 +145,7 @@ impl ExportedParams for ExportedFnParams {
let mut name = Vec::new(); let mut name = Vec::new();
let mut return_raw = false; let mut return_raw = false;
let mut skip = false; let mut skip = false;
let mut namespace = None; let mut namespace = FnNamespaceAccess::Unset;
let mut special = FnSpecialAccess::None; let mut special = FnSpecialAccess::None;
for attr in attrs { for attr in attrs {
let crate::attrs::AttrItem { let crate::attrs::AttrItem {
@ -226,22 +233,16 @@ impl ExportedParams for ExportedFnParams {
("global", Some(s)) | ("internal", Some(s)) => { ("global", Some(s)) | ("internal", Some(s)) => {
return Err(syn::Error::new(s.span(), "extraneous value")) return Err(syn::Error::new(s.span(), "extraneous value"))
} }
("global", None) => { ("global", None) => match namespace {
if let Some(ns) = namespace { FnNamespaceAccess::Unset => namespace = FnNamespaceAccess::Global,
if ns != FnNamespaceAccess::Global { FnNamespaceAccess::Global => (),
return Err(syn::Error::new(key.span(), "conflicting namespace")); _ => return Err(syn::Error::new(key.span(), "conflicting namespace")),
} },
} ("internal", None) => match namespace {
namespace = Some(FnNamespaceAccess::Global); FnNamespaceAccess::Unset => namespace = FnNamespaceAccess::Internal,
} FnNamespaceAccess::Internal => (),
("internal", None) => { _ => return Err(syn::Error::new(key.span(), "conflicting namespace")),
if let Some(ns) = namespace { },
if ns != FnNamespaceAccess::Internal {
return Err(syn::Error::new(key.span(), "conflicting namespace"));
}
}
namespace = Some(FnNamespaceAccess::Internal);
}
(attr, _) => { (attr, _) => {
return Err(syn::Error::new( return Err(syn::Error::new(
key.span(), key.span(),
@ -252,7 +253,7 @@ impl ExportedParams for ExportedFnParams {
} }
Ok(ExportedFnParams { Ok(ExportedFnParams {
name: if name.is_empty() { None } else { Some(name) }, name,
return_raw, return_raw,
skip, skip,
special, special,
@ -455,16 +456,12 @@ impl ExportedFn {
} }
pub(crate) fn exported_names(&self) -> Vec<syn::LitStr> { pub(crate) fn exported_names(&self) -> Vec<syn::LitStr> {
let mut literals = self let mut literals: Vec<_> = self
.params .params
.name .name
.as_ref() .iter()
.map(|v| { .map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
v.iter() .collect();
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
.collect()
})
.unwrap_or_else(|| Vec::new());
if let Some((s, _, span)) = self.params.special.get_fn_name() { if let Some((s, _, span)) = self.params.special.get_fn_name() {
literals.push(syn::LitStr::new(&s, span)); literals.push(syn::LitStr::new(&s, span));
@ -481,11 +478,10 @@ impl ExportedFn {
} }
pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> { pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> {
if let Some(ref name) = self.params.name { self.params.name.last().map_or_else(
name.last().unwrap().as_str().into() || self.signature.ident.to_string().into(),
} else { |s| s.as_str().into(),
self.signature.ident.to_string().into() )
}
} }
pub(crate) fn arg_list(&self) -> impl Iterator<Item = &syn::FnArg> { pub(crate) fn arg_list(&self) -> impl Iterator<Item = &syn::FnArg> {
@ -712,10 +708,12 @@ impl ExportedFn {
pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream {
let sig_name = self.name().clone(); let sig_name = self.name().clone();
let name = self.params.name.as_ref().map_or_else( let name = self
|| self.name().to_string(), .params
|names| names.last().unwrap().clone(), .name
); .last()
.cloned()
.unwrap_or_else(|| self.name().to_string());
let arg_count = self.arg_count(); let arg_count = self.arg_count();
let is_method_call = self.mutable_receiver(); let is_method_call = self.mutable_receiver();

View File

@ -21,7 +21,7 @@
//! let m = exported_module!(advanced_math); //! let m = exported_module!(advanced_math);
//! let mut r = StaticModuleResolver::new(); //! let mut r = StaticModuleResolver::new();
//! r.insert("Math::Advanced", m); //! r.insert("Math::Advanced", m);
//! engine.set_module_resolver(Some(r)); //! engine.set_module_resolver(r);
//! //!
//! assert_eq!(engine.eval::<FLOAT>( //! assert_eq!(engine.eval::<FLOAT>(
//! r#" //! r#"
@ -51,7 +51,7 @@
//! set_exported_fn!(m, "euclidean_distance", distance_function); //! set_exported_fn!(m, "euclidean_distance", distance_function);
//! let mut r = StaticModuleResolver::new(); //! let mut r = StaticModuleResolver::new();
//! r.insert("Math::Advanced", m); //! r.insert("Math::Advanced", m);
//! engine.set_module_resolver(Some(r)); //! engine.set_module_resolver(r);
//! //!
//! assert_eq!(engine.eval::<FLOAT>( //! assert_eq!(engine.eval::<FLOAT>(
//! r#" //! r#"

View File

@ -19,9 +19,9 @@ use std::borrow::Cow;
use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams}; use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams};
use crate::function::ExportedFnParams; use crate::function::ExportedFnParams;
#[derive(Debug, Default)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub(crate) struct ExportedModParams { pub(crate) struct ExportedModParams {
pub name: Option<String>, pub name: String,
skip: bool, skip: bool,
pub scope: ExportScope, pub scope: ExportScope,
} }
@ -49,21 +49,32 @@ impl ExportedParams for ExportedModParams {
fn from_info(info: ExportInfo) -> syn::Result<Self> { fn from_info(info: ExportInfo) -> syn::Result<Self> {
let ExportInfo { items: attrs, .. } = info; let ExportInfo { items: attrs, .. } = info;
let mut name = None; let mut name = Default::default();
let mut skip = false; let mut skip = false;
let mut scope = ExportScope::default(); let mut scope = None;
for attr in attrs { for attr in attrs {
let AttrItem { key, value, .. } = attr; let AttrItem { key, value, .. } = attr;
match (key.to_string().as_ref(), value) { 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")), ("name", None) => return Err(syn::Error::new(key.span(), "requires value")),
("skip", None) => skip = true, ("skip", None) => skip = true,
("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), ("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) => { ("export_prefix", None) => {
return Err(syn::Error::new(key.span(), "requires value")) 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)) => { ("export_all", Some(s)) => {
return Err(syn::Error::new(s.span(), "extraneous value")) return Err(syn::Error::new(s.span(), "extraneous value"))
} }
@ -79,7 +90,7 @@ impl ExportedParams for ExportedModParams {
Ok(ExportedModParams { Ok(ExportedModParams {
name, name,
skip, skip,
scope, scope: scope.unwrap_or_default(),
..Default::default() ..Default::default()
}) })
} }
@ -87,7 +98,7 @@ impl ExportedParams for ExportedModParams {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Module { pub(crate) struct Module {
mod_all: Option<syn::ItemMod>, mod_all: syn::ItemMod,
fns: Vec<ExportedFn>, fns: Vec<ExportedFn>,
consts: Vec<ExportedConst>, consts: Vec<ExportedConst>,
submodules: Vec<Module>, submodules: Vec<Module>,
@ -183,7 +194,7 @@ impl Parse for Module {
fns = new_vec![]; fns = new_vec![];
} }
Ok(Module { Ok(Module {
mod_all: Some(mod_all), mod_all,
fns, fns,
consts, consts,
submodules, submodules,
@ -194,39 +205,27 @@ impl Parse for Module {
#[allow(dead_code)] #[allow(dead_code)]
impl Module { impl Module {
pub fn attrs(&self) -> Option<&Vec<syn::Attribute>> { pub fn attrs(&self) -> &Vec<syn::Attribute> {
self.mod_all.as_ref().map(|m| &m.attrs) &self.mod_all.attrs
} }
pub fn module_name(&self) -> Option<&syn::Ident> { pub fn module_name(&self) -> &syn::Ident {
self.mod_all.as_ref().map(|m| &m.ident) &self.mod_all.ident
} }
pub fn exported_name(&self) -> Option<Cow<str>> { pub fn exported_name(&self) -> Cow<str> {
if let Some(ref s) = self.params.name { if !self.params.name.is_empty() {
Some(s.into()) self.params.name.as_str().into()
} else { } 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) { pub fn update_scope(&mut self, parent_scope: &ExportScope) {
let keep = match (self.params.skip, parent_scope) { let keep = match (self.params.skip, parent_scope) {
(true, _) => false, (true, _) => false,
(_, ExportScope::PubOnly) => { (_, ExportScope::PubOnly) => matches!(self.mod_all.vis, syn::Visibility::Public(_)),
if let Some(ref mod_all) = self.mod_all { (_, ExportScope::Prefix(s)) => self.mod_all.ident.to_string().starts_with(s),
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::All) => true, (_, ExportScope::All) => true,
}; };
self.params.skip = !keep; self.params.skip = !keep;
@ -249,14 +248,13 @@ impl Module {
// Extract the current structure of the module. // Extract the current structure of the module.
let Module { let Module {
mod_all, mut mod_all,
mut fns, mut fns,
consts, consts,
mut submodules, mut submodules,
params, params,
.. ..
} = self; } = self;
let mut mod_all = mod_all.unwrap();
let mod_name = mod_all.ident.clone(); let mod_name = mod_all.ident.clone();
let (_, orig_content) = mod_all.content.take().unwrap(); let (_, orig_content) = mod_all.content.take().unwrap();
let mod_attrs = mem::take(&mut mod_all.attrs); let mod_attrs = mem::take(&mut mod_all.attrs);
@ -299,8 +297,8 @@ impl Module {
} }
} }
pub fn name(&self) -> Option<&syn::Ident> { pub fn name(&self) -> &syn::Ident {
self.mod_all.as_ref().map(|m| &m.ident) &self.mod_all.ident
} }
pub fn consts(&self) -> &[ExportedConst] { pub fn consts(&self) -> &[ExportedConst] {
@ -317,10 +315,10 @@ impl Module {
pub fn content(&self) -> Option<&Vec<syn::Item>> { pub fn content(&self) -> Option<&Vec<syn::Item>> {
match self.mod_all { match self.mod_all {
Some(syn::ItemMod { syn::ItemMod {
content: Some((_, ref vec)), content: Some((_, ref vec)),
.. ..
}) => Some(vec), } => Some(vec),
_ => None, _ => None,
} }
} }

View File

@ -18,6 +18,7 @@ pub(crate) fn generated_module_path(
} }
type RegisterMacroInput = (syn::Expr, proc_macro2::TokenStream, syn::Path); type RegisterMacroInput = (syn::Expr, proc_macro2::TokenStream, syn::Path);
pub fn parse_register_macro( pub fn parse_register_macro(
args: proc_macro::TokenStream, args: proc_macro::TokenStream,
) -> Result<RegisterMacroInput, syn::Error> { ) -> Result<RegisterMacroInput, syn::Error> {

View File

@ -40,15 +40,13 @@ pub(crate) fn generate_body(
if itemmod.skipped() { if itemmod.skipped() {
continue; continue;
} }
let module_name = itemmod.module_name().unwrap(); let module_name = itemmod.module_name();
let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { let exported_name: syn::LitStr = syn::LitStr::new(
syn::LitStr::new(&name, proc_macro2::Span::call_site()) itemmod.exported_name().as_ref(),
} else { proc_macro2::Span::call_site(),
syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) );
};
let cfg_attrs: Vec<&syn::Attribute> = itemmod let cfg_attrs: Vec<&syn::Attribute> = itemmod
.attrs() .attrs()
.unwrap()
.iter() .iter()
.filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) .filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
.collect(); .collect();
@ -158,12 +156,14 @@ pub(crate) fn generate_body(
} }
} }
if let Some(ns) = function.params().namespace { match function.params().namespace {
namespace = ns; FnNamespaceAccess::Unset => (),
ns => namespace = ns,
} }
let ns_str = syn::Ident::new( let ns_str = syn::Ident::new(
match namespace { match namespace {
FnNamespaceAccess::Unset => unreachable!(),
FnNamespaceAccess::Global => "Global", FnNamespaceAccess::Global => "Global",
FnNamespaceAccess::Internal => "Internal", FnNamespaceAccess::Internal => "Internal",
}, },
@ -243,24 +243,24 @@ pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::
let mut fn_defs = HashMap::<String, proc_macro2::Span>::new(); let mut fn_defs = HashMap::<String, proc_macro2::Span>::new();
for itemfn in fns.iter() { for itemfn in fns.iter() {
if itemfn.params().name.is_some() || itemfn.params().special != FnSpecialAccess::None { if !itemfn.params().name.is_empty() || itemfn.params().special != FnSpecialAccess::None {
let mut names = itemfn let mut names: Vec<_> = itemfn
.params() .params()
.name .name
.as_ref() .iter()
.map(|v| v.iter().map(|n| (n.clone(), n.clone())).collect()) .map(|n| (n.clone(), n.clone()))
.unwrap_or_else(|| Vec::new()); .collect();
if let Some((s, n, _)) = itemfn.params().special.get_fn_name() { if let Some((s, n, _)) = itemfn.params().special.get_fn_name() {
names.push((s, n)); names.push((s, n));
} }
for (name, fn_name) in names { 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); 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( let mut err = syn::Error::new(
*current_span, current_span,
format!("duplicate Rhai signature for '{}'", &fn_name), format!("duplicate Rhai signature for '{}'", &fn_name),
); );
err.combine(syn::Error::new( err.combine(syn::Error::new(

View File

@ -18,14 +18,11 @@ fn raw_fn_test() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("get_mystic_number", || 42 as FLOAT); engine.register_fn("get_mystic_number", || 42 as FLOAT);
let mut m = Module::new(); let mut m = Module::new();
rhai::set_exported_fn!(m, "euclidean_distance", raw_fn::distance_function); rhai::set_exported_fn!(m, "euclidean_distance", raw_fn::distance_function);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
)?, )?,
41.0 41.0
); );
@ -48,16 +45,13 @@ fn raw_fn_mut_test() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("get_mystic_number", || 42 as FLOAT); engine.register_fn("get_mystic_number", || 42 as FLOAT);
let mut m = Module::new(); let mut m = Module::new();
rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"let x = get_mystic_number();
let x = get_mystic_number(); Math::Advanced::add_in_place(x, 1.0);
math::add_in_place(x, 1.0); x"#
x"#
)?, )?,
43.0 43.0
); );
@ -80,16 +74,10 @@ fn raw_fn_str_test() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("get_mystic_number", || 42 as FLOAT); engine.register_fn("get_mystic_number", || 42 as FLOAT);
let mut m = Module::new(); let mut m = Module::new();
rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Host::IO", m.into());
r.insert("Host::IO", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?,
r#"import "Host::IO" as io;
let x = io::write_out_str("hello world!");
x"#
)?,
true true
); );
Ok(()) Ok(())
@ -138,18 +126,16 @@ fn mut_opaque_ref_test() -> Result<(), Box<EvalAltResult>> {
rhai::set_exported_fn!(m, "new_message", mut_opaque_ref::new_message); 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, "new_os_message", mut_opaque_ref::new_os_message);
rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Host::Msg", m.into());
r.insert("Host::Msg", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(
r#"import "Host::Msg" as msg; r#"
let message1 = msg::new_message(true, "it worked"); let message1 = Host::Msg::new_message(true, "it worked");
let ok1 = msg::write_out_message(message1); let ok1 = Host::Msg::write_out_message(message1);
let message2 = msg::new_os_message(true, 0); let message2 = Host::Msg::new_os_message(true, 0);
let ok2 = msg::write_out_message(message2); let ok2 = Host::Msg::write_out_message(message2);
ok1 && ok2"# ok1 && ok2"#
)?, )?,
true true
); );
@ -179,14 +165,11 @@ fn raw_returning_fn_test() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("get_mystic_number", || 42 as FLOAT); engine.register_fn("get_mystic_number", || 42 as FLOAT);
let mut m = Module::new(); let mut m = Module::new();
rhai::set_exported_fn!(m, "euclidean_distance", raw_returning_fn::distance_function); rhai::set_exported_fn!(m, "euclidean_distance", raw_returning_fn::distance_function);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
)?, )?,
41.0 41.0
); );

View File

@ -12,14 +12,8 @@ pub mod empty_module {
fn empty_module_test() -> Result<(), Box<EvalAltResult>> { fn empty_module_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::empty_module::EmptyModule); let m = rhai::exported_module!(crate::empty_module::EmptyModule);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Module::Empty", m.into());
r.insert("Module::Empty", m);
engine.set_module_resolver(Some(r));
assert_eq!(
engine.eval::<INT>(r#"import "Module::Empty" as m; 42"#)?,
42
);
Ok(()) Ok(())
} }
@ -39,16 +33,10 @@ pub mod one_fn_module {
fn one_fn_module_test() -> Result<(), Box<EvalAltResult>> { fn one_fn_module_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::one_fn_module::advanced_math); let m = rhai::exported_module!(crate::one_fn_module::advanced_math);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(r#"let m = Math::Advanced::get_mystic_number();m"#)?,
r#"import "Math::Advanced" as math;
let m = math::get_mystic_number();
m"#
)?,
42.0 42.0
); );
Ok(()) Ok(())
@ -73,16 +61,14 @@ pub mod one_fn_and_const_module {
fn one_fn_and_const_module_test() -> Result<(), Box<EvalAltResult>> { fn one_fn_and_const_module_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math); let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let m = math::MYSTIC_NUMBER; let m = Math::Advanced::MYSTIC_NUMBER;
let x = math::euclidean_distance(0.0, 1.0, 0.0, m); let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, m);
x"# x"#
)?, )?,
41.0 41.0
); );
@ -105,16 +91,10 @@ pub mod raw_fn_str_module {
fn raw_fn_str_module_test() -> Result<(), Box<EvalAltResult>> { fn raw_fn_str_module_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::raw_fn_str_module::host_io); let m = rhai::exported_module!(crate::raw_fn_str_module::host_io);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Host::IO", m.into());
r.insert("Host::IO", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?,
r#"import "Host::IO" as io;
let x = io::write_out_str("hello world!");
x"#
)?,
true true
); );
Ok(()) Ok(())
@ -162,19 +142,17 @@ pub mod mut_opaque_ref_module {
fn mut_opaque_ref_test() -> Result<(), Box<EvalAltResult>> { fn mut_opaque_ref_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg); let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Host::Msg", m.into());
r.insert("Host::Msg", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<bool>( engine.eval::<bool>(
r#"import "Host::Msg" as msg; r#"
let success = "it worked"; let success = "it worked";
let message1 = msg::new_message(true, success); let message1 = Host::Msg::new_message(true, success);
let ok1 = msg::write_out_message(message1); let ok1 = Host::Msg::write_out_message(message1);
let message2 = msg::new_os_message(true, 0); let message2 = Host::Msg::new_os_message(true, 0);
let ok2 = msg::write_out_message(message2); let ok2 = Host::Msg::write_out_message(message2);
ok1 && ok2"# ok1 && ok2"#
)?, )?,
true true
); );
@ -204,18 +182,16 @@ fn duplicate_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_fn("get_mystic_number", || 42 as FLOAT); engine.register_fn("get_mystic_number", || 42 as FLOAT);
let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds); let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
let output_array = engine.eval::<Array>( let output_array = engine.eval::<Array>(
r#"import "Math::Advanced" as math; r#"
let fx = get_mystic_number(); let fx = get_mystic_number();
let fy = math::add(fx, 1.0); let fy = Math::Advanced::add(fx, 1.0);
let ix = 42; let ix = 42;
let iy = math::add(ix, 1); let iy = Math::Advanced::add(ix, 1);
[fy, iy] [fy, iy]
"#, "#,
)?; )?;
assert_eq!(&output_array[0].as_float().unwrap(), &43.0); assert_eq!(&output_array[0].as_float().unwrap(), &43.0);
assert_eq!(&output_array[1].as_int().unwrap(), &43); assert_eq!(&output_array[1].as_int().unwrap(), &43);
@ -291,6 +267,7 @@ fn multiple_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
mod export_by_prefix { mod export_by_prefix {
use rhai::plugin::*; use rhai::plugin::*;
#[export_module(export_prefix = "foo_")] #[export_module(export_prefix = "foo_")]
pub mod my_adds { pub mod my_adds {
use rhai::{FLOAT, INT}; use rhai::{FLOAT, INT};
@ -328,20 +305,18 @@ mod export_by_prefix {
fn export_by_prefix_test() -> Result<(), Box<EvalAltResult>> { fn export_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::export_by_prefix::my_adds); let m = rhai::exported_module!(crate::export_by_prefix::my_adds);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
let output_array = engine.eval::<Array>( let output_array = engine.eval::<Array>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::foo_add_f(ex, 1.0); let fx = Math::Advanced::foo_add_f(ex, 1.0);
let gx = math::foo_m(41.0, 1.0); let gx = Math::Advanced::foo_m(41.0, 1.0);
let ei = 41; let ei = 41;
let fi = math::bar_add_i(ei, 1); let fi = Math::Advanced::bar_add_i(ei, 1);
let gi = math::foo_n(41, 1); let gi = Math::Advanced::foo_n(41, 1);
[fx, gx, fi, gi] [fx, gx, fi, gi]
"#, "#,
)?; )?;
assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
assert_eq!(&output_array[1].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<EvalAltResult>> {
assert_eq!(&output_array[3].as_int().unwrap(), &42); assert_eq!(&output_array[3].as_int().unwrap(), &42);
assert!(matches!(*engine.eval::<FLOAT>( assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::foo_add_float2(ex, 1.0); let fx = Math::Advanced::foo_add_float2(ex, 1.0);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_add_float2 (f64, f64)" if s == "Math::Advanced::foo_add_float2 (f64, f64)"
&& p == rhai::Position::new(3, 23))); && p == rhai::Position::new(3, 34)));
assert!(matches!(*engine.eval::<FLOAT>( assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::bar_m(ex, 1.0); let fx = Math::Advanced::bar_m(ex, 1.0);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::bar_m (f64, f64)" if s == "Math::Advanced::bar_m (f64, f64)"
&& p == rhai::Position::new(3, 23))); && p == rhai::Position::new(3, 34)));
Ok(()) Ok(())
} }
mod export_all { mod export_all {
use rhai::plugin::*; use rhai::plugin::*;
#[export_module(export_all)] #[export_module(export_all)]
pub mod my_adds { pub mod my_adds {
use rhai::{FLOAT, INT}; use rhai::{FLOAT, INT};
@ -411,20 +387,18 @@ mod export_all {
fn export_all_test() -> Result<(), Box<EvalAltResult>> { fn export_all_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::export_all::my_adds); let m = rhai::exported_module!(crate::export_all::my_adds);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
let output_array = engine.eval::<Array>( let output_array = engine.eval::<Array>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::foo_add_f(ex, 1.0); let fx = Math::Advanced::foo_add_f(ex, 1.0);
let gx = math::foo_m(41.0, 1.0); let gx = Math::Advanced::foo_m(41.0, 1.0);
let ei = 41; let ei = 41;
let fi = math::foo_add_i(ei, 1); let fi = Math::Advanced::foo_add_i(ei, 1);
let gi = math::foo_n(41, 1); let gi = Math::Advanced::foo_n(41, 1);
[fx, gx, fi, gi] [fx, gx, fi, gi]
"#, "#,
)?; )?;
assert_eq!(&output_array[0].as_float().unwrap(), &42.0); assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
assert_eq!(&output_array[1].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<EvalAltResult>> {
assert_eq!(&output_array[3].as_int().unwrap(), &42); assert_eq!(&output_array[3].as_int().unwrap(), &42);
assert!(matches!(*engine.eval::<INT>( assert!(matches!(*engine.eval::<INT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41; let ex = 41;
let fx = math::foo_p(ex, 1); let fx = Math::Advanced::foo_p(ex, 1);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_p (i64, i64)" if s == "Math::Advanced::foo_p (i64, i64)"
&& p == rhai::Position::new(3, 23))); && p == rhai::Position::new(3, 34)));
Ok(()) Ok(())
} }

View File

@ -20,16 +20,10 @@ pub mod one_fn_module_nested_attr {
fn one_fn_module_nested_attr_test() -> Result<(), Box<EvalAltResult>> { fn one_fn_module_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math); let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(r#"let m = Math::Advanced::get_mystic_number(); m"#)?,
r#"import "Math::Advanced" as math;
let m = math::get_mystic_number();
m"#
)?,
42.0 42.0
); );
Ok(()) Ok(())
@ -56,16 +50,10 @@ pub mod one_fn_submodule_nested_attr {
fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> { fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
assert_eq!( assert_eq!(
engine.eval::<FLOAT>( engine.eval::<FLOAT>(r#"let m = Math::Advanced::constants::get_mystic_number(); m"#)?,
r#"import "Math::Advanced" as math;
let m = math::constants::get_mystic_number();
m"#
)?,
42.0 42.0
); );
Ok(()) Ok(())
@ -73,8 +61,8 @@ fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
mod export_nested_by_prefix { mod export_nested_by_prefix {
use rhai::plugin::*; use rhai::plugin::*;
#[export_module(export_prefix = "foo_")]
#[export_module(export_prefix = "foo_")]
pub mod my_adds { pub mod my_adds {
pub mod foo_first_adders { pub mod foo_first_adders {
use rhai::{FLOAT, INT}; use rhai::{FLOAT, INT};
@ -131,26 +119,24 @@ mod export_nested_by_prefix {
fn export_nested_by_prefix_test() -> Result<(), Box<EvalAltResult>> { fn export_nested_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds); let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds);
let mut r = StaticModuleResolver::new(); engine.register_static_module("Math::Advanced", m.into());
r.insert("Math::Advanced", m);
engine.set_module_resolver(Some(r));
let output_array = engine.eval::<Array>( let output_array = engine.eval::<Array>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::foo_first_adders::add_float(ex, 1.0); let fx = Math::Advanced::foo_first_adders::add_float(ex, 1.0);
let ei = 41; let ei = 41;
let fi = math::foo_first_adders::add_int(ei, 1); let fi = Math::Advanced::foo_first_adders::add_int(ei, 1);
let gx = 41.0; let gx = 41.0;
let hx = math::foo_second_adders::add_float(gx, 1.0); let hx = Math::Advanced::foo_second_adders::add_float(gx, 1.0);
let gi = 41; let gi = 41;
let hi = math::foo_second_adders::add_int(gi, 1); 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[0].as_float().unwrap(), &42.0);
assert_eq!(&output_array[1].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<EvalAltResult>> {
assert_eq!(&output_array[3].as_int().unwrap(), &42); assert_eq!(&output_array[3].as_int().unwrap(), &42);
assert!(matches!(*engine.eval::<FLOAT>( assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::foo_third_adders::add_float(ex, 1.0); let fx = Math::Advanced::foo_third_adders::add_float(ex, 1.0);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_third_adders::add_float (f64, f64)" if s == "Math::Advanced::foo_third_adders::add_float (f64, f64)"
&& p == rhai::Position::new(3, 41))); && p == rhai::Position::new(3, 52)));
assert!(matches!(*engine.eval::<FLOAT>( assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41; let ex = 41;
let fx = math::foo_third_adders::add_int(ex, 1); let fx = Math::Advanced::foo_third_adders::add_int(ex, 1);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::foo_third_adders::add_int (i64, i64)" if s == "Math::Advanced::foo_third_adders::add_int (i64, i64)"
&& p == rhai::Position::new(3, 41))); && p == rhai::Position::new(3, 52)));
assert!(matches!(*engine.eval::<FLOAT>( assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41; let ex = 41;
let fx = math::bar_fourth_adders::add_int(ex, 1); let fx = Math::Advanced::bar_fourth_adders::add_int(ex, 1);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::bar_fourth_adders::add_int (i64, i64)" if s == "Math::Advanced::bar_fourth_adders::add_int (i64, i64)"
&& p == rhai::Position::new(3, 42))); && p == rhai::Position::new(3, 53)));
assert!(matches!(*engine.eval::<FLOAT>( assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math; r#"
let ex = 41.0; let ex = 41.0;
let fx = math::bar_fourth_adders::add_float(ex, 1.0); let fx = Math::Advanced::bar_fourth_adders::add_float(ex, 1.0);
fx fx
"#).unwrap_err(), "#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(s, p) EvalAltResult::ErrorFunctionNotFound(s, p)
if s == "math::bar_fourth_adders::add_float (f64, f64)" if s == "Math::Advanced::bar_fourth_adders::add_float (f64, f64)"
&& p == rhai::Position::new(3, 42))); && p == rhai::Position::new(3, 53)));
Ok(()) Ok(())
} }

View File

@ -127,19 +127,22 @@ The Rhai Scripting Language
7. [One Engine Instance Per Call](patterns/parallel.md) 7. [One Engine Instance Per Call](patterns/parallel.md)
8. [Scriptable Event Handler with State](patterns/events.md) 8. [Scriptable Event Handler with State](patterns/events.md)
9. [Dynamic Constants Provider](patterns/dynamic-const.md) 9. [Dynamic Constants Provider](patterns/dynamic-const.md)
9. [Advanced Topics](advanced.md) 9. [Advanced Topics](advanced.md)
10. [Capture Scope for Function Call](language/fn-capture.md) 1. [Capture Scope for Function Call](language/fn-capture.md)
11. [Low-Level API](rust/register-raw.md) 2. [Low-Level API](rust/register-raw.md)
12. [Variable Resolver](engine/var.md) 3. [Variable Resolver](engine/var.md)
13. [Use as DSL](engine/dsl.md) 4. [Use as DSL](engine/dsl.md)
1. [Disable Keywords and/or Operators](engine/disable.md) 1. [Disable Keywords and/or Operators](engine/disable.md)
2. [Custom Operators](engine/custom-op.md) 2. [Custom Operators](engine/custom-op.md)
3. [Extending with Custom Syntax](engine/custom-syntax.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md)
14. [Multiple Instantiation](patterns/multiple.md) 5. [Multiple Instantiation](patterns/multiple.md)
15. [Functions Metadata](engine/metadata/index.md) 6. [Functions Metadata](engine/metadata/index.md)
4. [Generate Function Signatures](engine/metadata/gen_fn_sig.md) 1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md)
5. [Export Metadata to JSON](engine/metadata/export_to_json.md) 2. [Export Metadata to JSON](engine/metadata/export_to_json.md)
10. [Appendix](appendix/index.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) 1. [Keywords](appendix/keywords.md)
2. [Operators and Symbols](appendix/operators.md) 2. [Operators and Symbols](appendix/operators.md)
3. [Literals](appendix/literals.md) 3. [Literals](appendix/literals.md)

View File

@ -15,13 +15,19 @@ Online Resources for Rhai
* [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info * [`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 * [Discord Chat](https://discord.gg/HquqbYFcZ9) - Rhai channel
* [Reddit](https://www.reddit.com/r/Rhai) - Rhai community * [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 Other Cool Projects
------------------- -------------------

View File

@ -41,8 +41,8 @@ let mut scope = Scope::new();
// If arguments of the wrong types are passed, the Engine will not find the function. // 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 ) )?; 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,) )?; let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?;
// ^^^^^^^^^^ tuple of one // ^^^^^^^^^^ tuple of one

View File

@ -6,7 +6,8 @@ Custom Operators
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
customized operators performing specific logic. 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 Example
@ -52,15 +53,23 @@ into a syntax that uses the corresponding function calls.
Using `Engine::register_custom_operator` merely enables a convenient shortcut. 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]. 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 ```rust
engine.register_custom_operator("foo", 20); // 'foo' is a valid custom operator 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
``` ```

View File

@ -7,22 +7,24 @@ Create a Module from an AST
`Module::eval_ast_as_new` `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. 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 See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to
a Rhai script for this purpose as well as to control which functions/variables to export. 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). * Global variables - all variables exported via the `export` statement (those not exported remain hidden).
* Functions not specifically marked `private`. * 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 `Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the
with the global namespace. Therefore, functions defined within the same module script can cross-call each other. module namespace with the global namespace. Therefore, functions defined within the same module
script can cross-call each other.
Examples Examples

View File

@ -25,7 +25,6 @@ Use Case 1 - Make the `Module` Globally Available
`Engine::register_global_module` registers a shared [module] into the _global_ namespace. `Engine::register_global_module` registers a shared [module] into the _global_ namespace.
All [functions] and [type iterators] can be accessed without _namespace qualifiers_. All [functions] and [type iterators] can be accessed without _namespace qualifiers_.
Variables and sub-modules are **ignored**. Variables and sub-modules are **ignored**.
This is by far the easiest way to expose a module's functionalities to Rhai. 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 let mut module = Module::new(); // new module
// Use the 'set_fn_XXX' API to add functions. // Use the 'Module::set_fn_XXX' API to add functions.
let hash = module.set_fn_1("inc", |x: i64| Ok(x+1)); let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1));
// Remember to update the parameter names/types and return type metadata. // 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"]); module.update_fn_metadata(hash, ["x: i64", "i64"]);
// Register the module into the global namespace of the Engine. // Register the module into the global namespace of the Engine.
@ -49,6 +48,19 @@ engine.register_global_module(module.into());
engine.eval::<i64>("inc(41)")? == 42; // no need to import module engine.eval::<i64>("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::<i64>("inc(41)")? == 42; // no need to import module
```
Use Case 2 - Make the `Module` a Static 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 let mut module = Module::new(); // new module
// Use the 'set_fn_XXX' API to add functions. // Use the 'Module::set_fn_XXX' API to add functions.
let hash = module.set_fn_1("inc", |x: i64| Ok(x+1)); let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1));
// Remember to update the parameter names/types and return type metadata. // 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"]); 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(); let mut engine = Engine::new();
engine.register_static_module("calc", module.into()); engine.register_static_module("services::calc", module.into());
engine.eval::<i64>("calc::inc(41)")? == 42; // refer to the 'Calc' module // refer to the 'services::calc' module
engine.eval::<i64>("services::calc::inc(41)")? == 42;
``` ```
### Expose Functions to the Global Namespace ### Expose Functions to the Global Namespace
`Module::set_fn_mut` and `Module::set_fn_XXX_mut` can optionally expose functions (usually _methods_) The `Module::set_fn_XXX_mut` API methods can optionally expose functions in the [module]
in the module to the _global_ namespace, so [getters/setters] and [indexers] for [custom types] to the _global_ namespace by setting the `namespace` parameter to `FnNamespace::Global`,
can work as expected. 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. [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 let mut module = Module::new(); // new module
// Expose method 'inc' to the global namespace (default is 'Internal') // 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)); 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. // 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"]); module.update_fn_metadata(hash, ["x: &mut i64", "i64"]);
// Register the module into the Engine as a static module namespace 'calc' // Register the module into the Engine as a static module namespace 'calc'
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_static_module("calc", module.into()); 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::<i64>("calc::inc(41)")? == 42;
// 'inc' also works without a namespace qualifier
// because it is exposed to the global namespace
engine.eval::<i64>("let x = 41; x.inc()")? == 42; engine.eval::<i64>("let x = 41; x.inc()")? == 42;
engine.eval::<i64>("let x = 41; inc(x)")? == 42;
``` ```
@ -118,7 +137,7 @@ use rhai::module_resolvers::StaticModuleResolver;
let mut module = Module::new(); // new module let mut module = Module::new(); // new module
module.set_var("answer", 41_i64); // variable 'answer' under 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 // Create the module resolver
let mut resolver = StaticModuleResolver::new(); let mut resolver = StaticModuleResolver::new();
@ -129,7 +148,7 @@ resolver.insert("question", module);
// Set the module resolver into the 'Engine' // Set the module resolver into the 'Engine'
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(resolver);
// Use namespace-qualified variables // Use namespace-qualified variables
engine.eval::<i64>(r#"import "question" as q; q::answer + 1"#)? == 42; engine.eval::<i64>(r#"import "question" as q; q::answer + 1"#)? == 42;

View File

@ -62,7 +62,7 @@ impl ModuleResolver for MyModuleResolver {
let mut engine = Engine::new(); let mut engine = Engine::new();
// Set the custom module resolver into the 'Engine'. // Set the custom module resolver into the 'Engine'.
engine.set_module_resolver(Some(MyModuleResolver {})); engine.set_module_resolver(MyModuleResolver {});
engine.consume(r#" engine.consume(r#"
import "hello" as foo; // this 'import' statement will call import "hello" as foo; // this 'import' statement will call

View File

@ -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. 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` Set into `Engine`
----------------- -----------------
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
```rust ```rust
use rhai::module_resolvers::StaticModuleResolver; use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver};
// Create a module resolver // Create a module resolver
let resolver = StaticModuleResolver::new(); let resolver = StaticModuleResolver::new();
@ -167,8 +173,9 @@ let resolver = StaticModuleResolver::new();
// Register functions into 'resolver'... // Register functions into 'resolver'...
// Use the module 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' // Effectively disable 'import' statements by setting module resolver to
engine.set_module_resolver(None); // the 'DummyModuleResolver' which acts as... well... a dummy.
engine.set_module_resolver(DummyModuleResolver::new());
``` ```

View File

@ -3,18 +3,19 @@ Override a Built-in Function
{{#include ../links.md}} {{#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. native Rust function of the same name and number of parameters.
```rust ```rust
// Override the built-in function 'to_int' // Override the built-in function 'to_float' when called as a method
fn to_int(num) { fn to_float() {
print("Ha! Gotcha! " + num); 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 A registered native Rust function, in turn, overrides any built-in function of the

View File

@ -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 | | [`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 | | [`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`] | | [`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).<br/>The [`serde`] feature is required to run | | [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run |
| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function | | [`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 | | [`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). language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).

View File

@ -46,8 +46,8 @@ The following scripts are for benchmarking the speed of Rhai:
Running Example Scripts 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 ```bash
cargo run --example rhai_runner scripts/any_script.rhai cargo run --example rhai-run scripts/any_script.rhai
``` ```

6
doc/src/tools/index.md Normal file
View File

@ -0,0 +1,6 @@
External Tools
==============
{{#include ../links.md}}
External tools available to work with Rhai.

View File

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

View File

@ -0,0 +1,6 @@
Rhai Script Documentation Tool
=============================
{{#include ../links.md}}
<< TODO >>

View File

@ -1,38 +1,40 @@
use rhai::{Engine, RegisterFn, INT}; use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[derive(Clone, Debug)] #[derive(Debug, Clone)]
struct TestStruct { struct TestStruct {
x: INT, x: INT,
} }
impl TestStruct { impl TestStruct {
fn update(&mut self) { pub fn update(&mut self) {
self.x += 1000; self.x += 1000;
} }
fn new() -> Self { pub fn new() -> Self {
Self { x: 1 } Self { x: 1 }
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn main() { fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine engine
.register_type::<TestStruct>() .register_type::<TestStruct>()
.register_fn("update", TestStruct::update) .register_fn("new_ts", TestStruct::new)
.register_fn("new_ts", TestStruct::new); .register_fn("update", TestStruct::update);
println!( println!(
"{:?}", "{:?}",
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x") engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?
); );
println!( println!(
"{:?}", "{:?}",
engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]") engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]")?
); );
Ok(())
} }
#[cfg(any(feature = "no_index", feature = "no_object"))] #[cfg(any(feature = "no_index", feature = "no_object"))]

View File

@ -1,16 +1,16 @@
use rhai::{Engine, EvalAltResult, RegisterFn, INT}; use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[derive(Clone)] #[derive(Debug, Clone)]
struct TestStruct { struct TestStruct {
x: INT, x: INT,
} }
impl TestStruct { impl TestStruct {
fn update(&mut self) { pub fn update(&mut self) {
self.x += 1000; self.x += 1000;
} }
fn new() -> Self { pub fn new() -> Self {
Self { x: 1 } Self { x: 1 }
} }
} }
@ -21,8 +21,8 @@ fn main() -> Result<(), Box<EvalAltResult>> {
engine engine
.register_type::<TestStruct>() .register_type::<TestStruct>()
.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::<TestStruct>("let x = new_ts(); x.update(); x")?; let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;

View File

@ -43,7 +43,7 @@ fn print_help() {
println!("quit, exit => quit"); println!("quit, exit => quit");
println!("scope => print all variables in the scope"); println!("scope => print all variables in the scope");
println!("functions => print all functions defined"); 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!("astu => print the last raw, un-optimized AST");
println!(r"end a line with '\' to continue to the next line."); println!(r"end a line with '\' to continue to the next line.");
println!(); println!();
@ -67,7 +67,7 @@ fn main() {
print_help(); print_help();
'main_loop: loop { 'main_loop: loop {
print!("rhai> "); print!("rhai-repl> ");
stdout().flush().expect("couldn't flush stdout"); stdout().flush().expect("couldn't flush stdout");
input.clear(); input.clear();
@ -126,12 +126,12 @@ fn main() {
} }
"astu" => { "astu" => {
// print the last un-optimized AST // print the last un-optimized AST
println!("{:#?}\n", &ast_u); println!("{:#?}\n", ast_u);
continue; continue;
} }
"ast" => { "ast" => {
// print the last AST // print the last AST
println!("{:#?}\n", &ast); println!("{:#?}\n", ast);
continue; continue;
} }
"functions" => { "functions" => {

View File

@ -17,13 +17,13 @@ mod example {
use rhai::{Dynamic, Engine, Map}; use rhai::{Dynamic, Engine, Map};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Point { struct Point {
x: f64, x: f64,
y: f64, y: f64,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct MyStruct { struct MyStruct {
a: i64, a: i64,
b: Vec<String>, b: Vec<String>,
@ -71,6 +71,18 @@ mod example {
// Convert the 'Dynamic' object map into 'MyStruct' // Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result).unwrap(); 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); println!("Deserialized to struct: {:#?}", x);
} }
} }

View File

@ -69,7 +69,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
display("Trimmed", x); display("Trimmed", x);
display("Trimmed Length", x.len()); display("Trimmed Length", x.len());
display("Index of \"!!!\"", x.index_of("!!!")); display("Index of \"!!!\"", x.index_of("!!!"));
"#, "#,
)?; )?;
println!(); println!();

View File

@ -7,14 +7,14 @@ Testing scripts written in Rhai.
How to Run How to Run
---------- ----------
Compile the `rhai_runner` example: Compile the `rhai-run` example:
```bash ```bash
cargo build --example rhai_runner cargo build --example rhai-run
``` ```
Run it: Run it:
```bash ```bash
./target/debug/examples/rhai_runner ./scripts/test_script_to_run.rhai ./target/debug/examples/rhai-run ./scripts/test_script_to_run.rhai
``` ```

View File

@ -9,7 +9,7 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
fmt, fmt,
hash::Hash, hash::Hash,
num::NonZeroUsize, num::{NonZeroU64, NonZeroUsize},
ops::{Add, AddAssign}, ops::{Add, AddAssign},
string::String, string::String,
vec, vec,
@ -736,7 +736,7 @@ impl Stmt {
_ => false, _ => false,
} }
} }
/// Get the [position][`Position`] of this statement. /// Get the [position][Position] of this statement.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
@ -765,7 +765,7 @@ impl Stmt {
Self::Share(x) => x.pos, 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 { pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self { match self {
Self::Noop(pos) Self::Noop(pos)
@ -914,16 +914,14 @@ pub struct BinaryExpr {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct FnCallExpr { pub struct FnCallExpr {
/// Pre-calculated hash for a script-defined function of the same name and number of parameters. /// Pre-calculated hash for a script-defined function of the same name and number of parameters.
pub hash: u64, /// None if native Rust only.
/// Call native functions only? Set to [`true`] to skip searching for script-defined function overrides pub hash_script: Option<NonZeroU64>,
/// when it is certain that the function must be native (e.g. an operator).
pub native_only: bool,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture: bool, pub capture: bool,
/// Default value when the function is not found, mostly used to provide a default for comparison functions. /// Default value when the function is not found, mostly used to provide a default for comparison functions.
pub def_value: Option<Dynamic>, pub def_value: Option<Dynamic>,
/// Namespace of the function, if any. Boxed because it occurs rarely. /// Namespace of the function, if any. Boxed because it occurs rarely.
pub namespace: Option<Box<NamespaceRef>>, pub namespace: Option<NamespaceRef>,
/// Function name. /// Function name.
/// Use [`Cow<'static, str>`][Cow] because a lot of operators (e.g. `==`, `>=`) are implemented as /// 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`]. /// 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<StaticVec<(Ident, Expr)>>, Position), Map(Box<StaticVec<(Ident, Expr)>>, Position),
/// () /// ()
Unit(Position), Unit(Position),
/// Variable access - (optional index, optional modules, hash, variable name) /// Variable access - (optional index, optional (hash, modules), variable name)
Variable(Box<(Option<NonZeroUsize>, Option<Box<NamespaceRef>>, u64, Ident)>), Variable(
Box<(
Option<NonZeroUsize>,
Option<(NonZeroU64, NamespaceRef)>,
Ident,
)>,
),
/// Property access - (getter, setter), prop /// Property access - (getter, setter), prop
Property(Box<((ImmutableString, ImmutableString), Ident)>), Property(Box<(ImmutableString, ImmutableString, Ident)>),
/// { [statement][Stmt] } /// { [statement][Stmt] }
Stmt(Box<StaticVec<Stmt>>, Position), Stmt(Box<StaticVec<Stmt>>, Position),
/// Wrapped [expression][`Expr`] - should not be optimized away. /// Wrapped [expression][Expr] - should not be optimized away.
Expr(Box<Expr>), Expr(Box<Expr>),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
@ -1044,11 +1048,11 @@ impl Expr {
/// Is the expression a simple variable access? /// Is the expression a simple variable access?
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
match self { 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, _ => None,
} }
} }
/// Get the [position][`Position`] of the expression. /// Get the [position][Position] of the expression.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
Self::Expr(x) => x.position(), Self::Expr(x) => x.position(),
@ -1064,9 +1068,9 @@ impl Expr {
Self::FnPointer(_, pos) => *pos, Self::FnPointer(_, pos) => *pos,
Self::Array(_, pos) => *pos, Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos, Self::Map(_, pos) => *pos,
Self::Property(x) => (x.1).pos, Self::Property(x) => (x.2).pos,
Self::Stmt(_, pos) => *pos, Self::Stmt(_, pos) => *pos,
Self::Variable(x) => (x.3).pos, Self::Variable(x) => (x.2).pos,
Self::FnCall(_, pos) => *pos, Self::FnCall(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(), Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
@ -1078,7 +1082,7 @@ impl Expr {
Self::Custom(_, pos) => *pos, 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 { pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self { match self {
Self::Expr(x) => { Self::Expr(x) => {
@ -1096,8 +1100,8 @@ impl Expr {
Self::FnPointer(_, pos) => *pos = new_pos, Self::FnPointer(_, pos) => *pos = new_pos,
Self::Array(_, pos) => *pos = new_pos, Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos,
Self::Variable(x) => (x.3).pos = new_pos, Self::Variable(x) => (x.2).pos = new_pos,
Self::Property(x) => (x.1).pos = new_pos, Self::Property(x) => (x.2).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, 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? /// Is a particular [token][Token] allowed as a postfix operator to this expression?
pub fn is_valid_postfix(&self, token: &Token) -> bool { pub fn is_valid_postfix(&self, token: &Token) -> bool {
match token {
#[cfg(not(feature = "no_object"))]
Token::Period => return true,
_ => (),
}
match self { match self {
Self::Expr(x) => x.is_valid_postfix(token), Self::Expr(x) => x.is_valid_postfix(token),
@ -1189,24 +1199,20 @@ impl Expr {
| Self::Unit(_) => false, | Self::Unit(_) => false,
Self::StringConstant(_, _) Self::StringConstant(_, _)
| Self::Stmt(_, _)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Stmt(_, _)
| Self::Dot(_, _) | Self::Dot(_, _)
| Self::Index(_, _) | Self::Index(_, _)
| Self::Array(_, _) | Self::Array(_, _)
| Self::Map(_, _) => match token { | Self::Map(_, _) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
_ => false, _ => false,
}, },
Self::Variable(_) => match token { Self::Variable(_) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
Token::LeftParen => true, Token::LeftParen => true,
Token::Bang => true, Token::Bang => true,
Token::DoubleColon => true, Token::DoubleColon => true,
@ -1216,8 +1222,6 @@ impl Expr {
Self::Property(_) => match token { Self::Property(_) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
Token::LeftParen => true, Token::LeftParen => true,
_ => false, _ => false,
}, },

View File

@ -748,13 +748,16 @@ impl Dynamic {
Self(Union::Variant(Box::new(boxed), AccessMode::ReadWrite)) 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`]`>>` /// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an
/// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature. /// [`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 /// Shared [`Dynamic`] values are relatively cheap to clone as they simply increment the
/// reference counts. /// 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. /// 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 not a shared value, it returns itself.
/// ///
/// If the [`Dynamic`] is a shared value, it returns the shared value if there are /// If the [`Dynamic`] is a shared value, it returns the shared value if there are no
/// no outstanding references, or a cloned copy. /// outstanding references, or a cloned copy.
#[inline(always)] #[inline(always)]
pub fn flatten(self) -> Self { pub fn flatten(self) -> Self {
match self.0 { match self.0 {

View File

@ -18,8 +18,8 @@ use crate::stdlib::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt, format, fmt, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::{empty, once}, iter::{empty, once, FromIterator},
num::NonZeroUsize, num::{NonZeroU64, NonZeroU8, NonZeroUsize},
ops::DerefMut, ops::DerefMut,
string::{String, ToString}, string::{String, ToString},
}; };
@ -112,23 +112,15 @@ impl Imports {
} }
/// Does the specified function hash key exist in this stack of imported [modules][Module]? /// Does the specified function hash key exist in this stack of imported [modules][Module]?
#[allow(dead_code)] #[allow(dead_code)]
pub fn contains_fn(&self, hash: u64) -> bool { pub fn contains_fn(&self, hash: NonZeroU64) -> bool {
if hash == 0 { self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
false
} else {
self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
}
} }
/// Get specified function via its hash key. /// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { pub fn get_fn(&self, hash: NonZeroU64) -> Option<&CallableFunction> {
if hash == 0 { self.0
None .iter()
} else { .rev()
self.0 .find_map(|(_, m)| m.get_qualified_fn(hash))
.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]? /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]?
#[allow(dead_code)] #[allow(dead_code)]
@ -144,6 +136,22 @@ impl Imports {
} }
} }
impl<'a, T: IntoIterator<Item = (&'a ImmutableString, &'a Shared<Module>)>> From<T> for Imports {
fn from(value: T) -> Self {
Self(
value
.into_iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
)
}
}
impl FromIterator<(ImmutableString, Shared<Module>)> for Imports {
fn from_iter<T: IntoIterator<Item = (ImmutableString, Shared<Module>)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub const MAX_CALL_STACK_DEPTH: usize = 8; pub const MAX_CALL_STACK_DEPTH: usize = 8;
@ -478,7 +486,7 @@ pub struct State {
/// Number of modules loaded. /// Number of modules loaded.
pub modules: usize, pub modules: usize,
/// Cached lookup values for function hashes. /// Cached lookup values for function hashes.
pub functions_cache: HashMap<u64, Option<CallableFunction>, StraightHasherBuilder>, pub functions_cache: HashMap<NonZeroU64, Option<CallableFunction>, StraightHasherBuilder>,
} }
impl State { impl State {
@ -616,11 +624,11 @@ pub struct Engine {
/// A collection of all modules loaded into the global namespace of the Engine. /// A collection of all modules loaded into the global namespace of the Engine.
pub(crate) global_modules: StaticVec<Shared<Module>>, pub(crate) global_modules: StaticVec<Shared<Module>>,
/// A collection of all sub-modules directly loaded into the Engine. /// A collection of all sub-modules directly loaded into the Engine.
pub(crate) global_sub_modules: Imports, pub(crate) global_sub_modules: HashMap<ImmutableString, Shared<Module>>,
/// A module resolution service. /// A module resolution service.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>, pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
/// A hashmap mapping type names to pretty-print names. /// A hashmap mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: HashMap<String, String>,
@ -628,7 +636,7 @@ pub struct Engine {
/// A hashset containing symbols to disable. /// A hashset containing symbols to disable.
pub(crate) disabled_symbols: HashSet<String>, pub(crate) disabled_symbols: HashSet<String>,
/// A hashmap containing custom keywords and precedence to recognize. /// A hashmap containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: HashMap<String, Option<u8>>, pub(crate) custom_keywords: HashMap<String, Option<NonZeroU8>>,
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>, pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
@ -752,7 +760,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[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(not(feature = "no_module"))]
#[cfg(any(feature = "no_std", target_arch = "wasm32",))] #[cfg(any(feature = "no_std", target_arch = "wasm32",))]
module_resolver: None, module_resolver: None,
@ -815,7 +823,7 @@ impl Engine {
global_sub_modules: Default::default(), global_sub_modules: Default::default(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
module_resolver: None, module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
type_names: Default::default(), type_names: Default::default(),
disabled_symbols: Default::default(), disabled_symbols: Default::default(),
@ -856,19 +864,19 @@ impl Engine {
/// Search for a variable within the scope or within imports, /// Search for a variable within the scope or within imports,
/// depending on whether the variable name is namespace-qualified. /// depending on whether the variable name is namespace-qualified.
pub(crate) fn search_namespace<'s, 'a>( pub(crate) fn search_namespace<'s>(
&self, &self,
scope: &'s mut Scope, scope: &'s mut Scope,
mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr, expr: &Expr,
) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
match expr { match expr {
Expr::Variable(v) => match v.as_ref() { Expr::Variable(v) => match v.as_ref() {
// Qualified variable // Qualified variable
(_, Some(modules), hash_var, Ident { name, pos }) => { (_, Some((hash_var, modules)), Ident { name, pos }) => {
let module = search_imports(mods, state, modules)?; let module = search_imports(mods, state, modules)?;
let target = module.get_qualified_var(*hash_var).map_err(|mut err| { let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
match *err { match *err {
@ -883,7 +891,7 @@ impl Engine {
// Module variables are constant // Module variables are constant
let mut target = target.clone(); let mut target = target.clone();
target.set_access_mode(AccessMode::ReadOnly); target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), name, *pos)) Ok((target.into(), *pos))
} }
// Normal variable access // Normal variable access
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
@ -893,16 +901,16 @@ impl Engine {
} }
/// Search for a variable within the scope /// Search for a variable within the scope
pub(crate) fn search_scope_only<'s, 'a>( pub(crate) fn search_scope_only<'s>(
&self, &self,
scope: &'s mut Scope, scope: &'s mut Scope,
mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr, expr: &Expr,
) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
let (index, _, _, Ident { name, pos }) = match expr { let (index, _, Ident { name, pos }) = match expr {
Expr::Variable(v) => v.as_ref(), Expr::Variable(v) => v.as_ref(),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -910,7 +918,7 @@ impl Engine {
// Check if the variable is `this` // Check if the variable is `this`
if name.as_str() == KEYWORD_THIS { if name.as_str() == KEYWORD_THIS {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, *pos)); return Ok(((*val).into(), *pos));
} else { } else {
return EvalAltResult::ErrorUnboundThis(*pos).into(); return EvalAltResult::ErrorUnboundThis(*pos).into();
} }
@ -938,7 +946,7 @@ impl Engine {
resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))?
{ {
result.set_access_mode(AccessMode::ReadOnly); result.set_access_mode(AccessMode::ReadOnly);
return Ok((result.into(), name, *pos)); return Ok((result.into(), *pos));
} }
} }
@ -952,7 +960,7 @@ impl Engine {
.0 .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 // 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. // when the object variable is also used as a parameter.
@ -960,7 +968,7 @@ impl Engine {
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
// } // }
Ok((val.into(), name, *pos)) Ok((val.into(), *pos))
} }
/// Chain-evaluate a dot/index chain. /// Chain-evaluate a dot/index chain.
@ -980,7 +988,7 @@ impl Engine {
new_val: Option<(Dynamic, Position)>, new_val: Option<(Dynamic, Position)>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
if chain_type == ChainType::None { if chain_type == ChainType::None {
panic!(); unreachable!();
} }
let is_ref = target.is_ref(); let is_ref = target.is_ref();
@ -1047,7 +1055,7 @@ impl Engine {
let args = &mut [target_val, &mut idx_val2, &mut new_val.0]; let args = &mut [target_val, &mut idx_val2, &mut new_val.0];
self.exec_fn_call( 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, new_val.1, None, None, level,
) )
.map_err(|err| match *err { .map_err(|err| match *err {
@ -1083,23 +1091,22 @@ impl Engine {
Expr::FnCall(x, pos) if x.namespace.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { let FnCallExpr {
name, name,
native_only: native, hash_script: hash,
hash,
def_value, def_value,
.. ..
} = x.as_ref(); } = x.as_ref();
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
self.make_method_call( self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, *native, false, mods, state, lib, name, *hash, target, args, def_value, false, *pos,
*pos, level, level,
) )
} }
// xxx.module::fn_name(...) - syntax error // xxx.module::fn_name(...) - syntax error
Expr::FnCall(_, _) => unreachable!(), Expr::FnCall(_, _) => unreachable!(),
// {xxx:map}.id = ??? // {xxx:map}.id = ???
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => { Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
let Ident { name, pos } = &x.1; let Ident { name, pos } = &x.2;
let index = name.clone().into(); let index = name.clone().into();
let mut val = self.get_indexed_mut( let mut val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, true, is_ref, false, level, mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
@ -1112,7 +1119,7 @@ impl Engine {
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target_val.is::<Map>() => { Expr::Property(x) if target_val.is::<Map>() => {
let Ident { name, pos } = &x.1; let Ident { name, pos } = &x.2;
let index = name.clone().into(); let index = name.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, false, is_ref, false, level, mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
@ -1122,11 +1129,11 @@ impl Engine {
} }
// xxx.id = ??? // xxx.id = ???
Expr::Property(x) if new_val.is_some() => { 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 new_val = new_val;
let mut args = [target_val, &mut new_val.as_mut().unwrap().0]; let mut args = [target_val, &mut new_val.as_mut().unwrap().0];
self.exec_fn_call( 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, None, None, level,
) )
.map(|(v, _)| (v, true)) .map(|(v, _)| (v, true))
@ -1134,10 +1141,10 @@ impl Engine {
} }
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
let ((getter, _), Ident { pos, .. }) = x.as_ref(); let (getter, _, Ident { pos, .. }) = x.as_ref();
let mut args = [target_val]; let mut args = [target_val];
self.exec_fn_call( 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, None, None, level,
) )
.map(|(v, _)| (v, false)) .map(|(v, _)| (v, false))
@ -1147,7 +1154,7 @@ impl Engine {
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => {
let mut val = match &x.lhs { let mut val = match &x.lhs {
Expr::Property(p) => { Expr::Property(p) => {
let Ident { name, pos } = &p.1; let Ident { name, pos } = &p.2;
let index = name.clone().into(); let index = name.clone().into();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, false, is_ref, true, 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() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { let FnCallExpr {
name, name,
native_only: native, hash_script: hash,
hash,
def_value, def_value,
.. ..
} = x.as_ref(); } = x.as_ref();
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
let (val, _) = self.make_method_call( let (val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, mods, state, lib, name, *hash, target, args, def_value, false,
*native, false, *pos, level, *pos, level,
)?; )?;
val.into() val.into()
} }
@ -1188,12 +1194,12 @@ impl Engine {
match &x.lhs { match &x.lhs {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => { 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 arg_values = &mut [target_val, &mut Default::default()];
let args = &mut arg_values[..1]; let args = &mut arg_values[..1];
let (mut val, updated) = self let (mut val, updated) = self
.exec_fn_call( .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, *pos, None, None, level,
) )
.map_err(|err| err.fill_position(*pos))?; .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 // Re-use args because the first &mut parameter will not be consumed
arg_values[1] = val; arg_values[1] = val;
self.exec_fn_call( 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, false, *pos, None, None, level,
) )
.or_else( .or_else(
@ -1240,16 +1246,15 @@ impl Engine {
Expr::FnCall(f, pos) if f.namespace.is_none() => { Expr::FnCall(f, pos) if f.namespace.is_none() => {
let FnCallExpr { let FnCallExpr {
name, name,
native_only: native, hash_script: hash,
hash,
def_value, def_value,
.. ..
} = f.as_ref(); } = f.as_ref();
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args(); let args = idx_val.as_fn_call_args();
let (mut val, _) = self.make_method_call( let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, mods, state, lib, name, *hash, target, args, def_value, false,
*native, false, *pos, level, *pos, level,
)?; )?;
let val = &mut val; let val = &mut val;
let target = &mut val.into(); let target = &mut val.into();
@ -1306,11 +1311,11 @@ impl Engine {
let Ident { let Ident {
name: var_name, name: var_name,
pos: var_pos, pos: var_pos,
} = &x.3; } = &x.2;
self.inc_operations(state, *var_pos)?; self.inc_operations(state, *var_pos)?;
let (target, _, pos) = let (target, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
// Constants cannot be modified // Constants cannot be modified
@ -1501,8 +1506,8 @@ impl Engine {
let mut idx = idx; let mut idx = idx;
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, 0, args, _is_ref, true, false, idx_pos, None, _mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, false, idx_pos,
None, _level, None, None, _level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
.map_err(|err| match *err { .map_err(|err| match *err {
@ -1553,7 +1558,8 @@ impl Engine {
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let hash = 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(); let pos = rhs.position();
@ -1603,7 +1609,7 @@ impl Engine {
match expr { match expr {
// var - point directly to the value // var - point directly to the value
Expr::Variable(_) => { Expr::Variable(_) => {
let (mut target, _, pos) = let (mut target, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
// If necessary, constants are cloned // If necessary, constants are cloned
@ -1659,7 +1665,7 @@ impl Engine {
if target.is::<Map>() { if target.is::<Map>() {
// map.prop - point directly to the item // 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(); let idx = name.clone().into();
if target.is_shared() || target.is_value() { if target.is_shared() || target.is_value() {
@ -1677,10 +1683,10 @@ impl Engine {
.map(|v| (v, *pos)) .map(|v| (v, *pos))
} else { } else {
// var.prop - call property getter // 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()]; let mut args = [target.as_mut()];
self.exec_fn_call( 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, None, None, level,
) )
.map(|(v, _)| (v.into(), *pos)) .map(|(v, _)| (v.into(), *pos))
@ -1721,15 +1727,15 @@ impl Engine {
Expr::StringConstant(x, _) => Ok(x.clone().into()), Expr::StringConstant(x, _) => Ok(x.clone().into()),
Expr::CharConstant(x, _) => Ok((*x).into()), Expr::CharConstant(x, _) => Ok((*x).into()),
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).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 { if let Some(val) = this_ptr {
Ok(val.clone()) Ok(val.clone())
} else { } else {
EvalAltResult::ErrorUnboundThis((x.3).pos).into() EvalAltResult::ErrorUnboundThis((x.2).pos).into()
} }
} }
Expr::Variable(_) => { 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()) Ok(val.take_or_clone())
} }
Expr::Property(_) => unreachable!(), Expr::Property(_) => unreachable!(),
@ -1778,17 +1784,16 @@ impl Engine {
Expr::FnCall(x, pos) if x.namespace.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { let FnCallExpr {
name, name,
native_only: native,
capture: cap_scope, capture: cap_scope,
hash, hash_script: hash,
args, args,
def_value, def_value,
.. ..
} = x.as_ref(); } = x.as_ref();
let def_value = def_value.as_ref(); let def_value = def_value.as_ref();
self.make_function_call( self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, def_value, *hash, *native, scope, mods, state, lib, this_ptr, name, args, def_value, *hash, false, *pos,
false, *pos, *cap_scope, level, *cap_scope, level,
) )
} }
@ -1797,15 +1802,16 @@ impl Engine {
let FnCallExpr { let FnCallExpr {
name, name,
namespace, namespace,
hash, hash_script: hash,
args, args,
def_value, def_value,
.. ..
} = x.as_ref(); } = 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(); let def_value = def_value.as_ref();
self.make_qualified_function_call( 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, *pos, level,
) )
} }
@ -1935,11 +1941,15 @@ impl Engine {
let mut rhs_val = self let mut rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten(); .flatten();
let (mut lhs_ptr, name, pos) = let (mut lhs_ptr, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?; self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() { 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)?; self.inc_operations(state, pos)?;
@ -1947,7 +1957,7 @@ impl Engine {
if lhs_ptr.as_ref().is_read_only() { if lhs_ptr.as_ref().is_read_only() {
// Assignment to constant variable // Assignment to constant variable
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
name.to_string(), lhs_expr.get_variable_access(false).unwrap().to_string(),
pos, pos,
))) )))
} else if op.is_empty() { } else if op.is_empty() {
@ -1966,7 +1976,7 @@ impl Engine {
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // 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 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 match self
.global_namespace .global_namespace
@ -2015,8 +2025,8 @@ impl Engine {
// Run function // Run function
let (value, _) = self.exec_fn_call( let (value, _) = 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, level, None, None, level,
)?; )?;
let value = value.flatten(); let value = value.flatten();
@ -2051,7 +2061,7 @@ impl Engine {
let result = self let result = self
.exec_fn_call( .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, None, level,
) )
.map(|(v, _)| v)?; .map(|(v, _)| v)?;
@ -2203,7 +2213,7 @@ impl Engine {
state.scope_level += 1; state.scope_level += 1;
for iter_value in func(iter_obj) { 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(); let value = iter_value.flatten();
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() { 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)? .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
.try_cast::<ImmutableString>() .try_cast::<ImmutableString>()
{ {
if let Some(resolver) = &self.module_resolver { let module = self.module_resolver.resolve(self, &path, expr.position())?;
let module = resolver.resolve(self, &path, expr.position())?;
if let Some(name_def) = alias { if let Some(name_def) = alias {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not 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); let mut module = crate::fn_native::shared_take_or_clone(module);
module.build_index(); module.build_index();
mods.push(name_def.name.clone(), module); mods.push(name_def.name.clone(), module);
} else { } else {
mods.push(name_def.name.clone(), module); mods.push(name_def.name.clone(), module);
}
// When imports list is modified, clear the functions lookup cache
state.functions_cache.clear();
} }
// When imports list is modified, clear the functions lookup cache
state.modules += 1; state.functions_cache.clear();
Ok(Dynamic::UNIT)
} else {
Err(
EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position())
.into(),
)
} }
state.modules += 1;
Ok(Dynamic::UNIT)
} else { } else {
Err(self.make_type_mismatch_err::<ImmutableString>("", expr.position())) Err(self.make_type_mismatch_err::<ImmutableString>("", expr.position()))
} }
@ -2419,7 +2422,7 @@ impl Engine {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => { Stmt::Share(x) => {
if let Some((index, _)) = scope.get_index(&x.name) { 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() { if !val.is_shared() {
// Replace the variable with a shared value. // Replace the variable with a shared value.

View File

@ -209,9 +209,10 @@ impl Engine {
pub fn register_get<T: Variant + Clone, U: Variant + Clone>( pub fn register_get<T: Variant + Clone, U: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
callback: impl Fn(&mut T) -> U + SendSync + 'static, get_fn: impl Fn(&mut T) -> U + SendSync + 'static,
) -> &mut Self { ) -> &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`]. /// 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<T: Variant + Clone>( pub fn register_get_result<T: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
callback: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static, get_fn: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
crate::RegisterResultFn::register_result_fn( use crate::{engine::make_getter, RegisterResultFn};
self, self.register_result_fn(&make_getter(name), get_fn)
&crate::engine::make_getter(name),
callback,
)
} }
/// Register a setter function for a member of a registered type with the [`Engine`]. /// Register a setter function for a member of a registered type with the [`Engine`].
/// ///
@ -304,9 +302,10 @@ impl Engine {
pub fn register_set<T: Variant + Clone, U: Variant + Clone>( pub fn register_set<T: Variant + Clone, U: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
callback: impl Fn(&mut T, U) + SendSync + 'static, set_fn: impl Fn(&mut T, U) + SendSync + 'static,
) -> &mut Self { ) -> &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`]. /// 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<T: Variant + Clone, U: Variant + Clone>( pub fn register_set_result<T: Variant + Clone, U: Variant + Clone>(
&mut self, &mut self,
name: &str, name: &str,
callback: impl Fn(&mut T, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut T, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
crate::RegisterResultFn::register_result_fn( use crate::{engine::make_setter, RegisterResultFn};
self, self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| {
&crate::engine::make_setter(name), set_fn(obj, value).map(Into::into)
move |obj: &mut T, value: U| callback(obj, value).map(Into::into), })
)
} }
/// Short-hand for registering both getter and setter functions /// Short-hand for registering both getter and setter functions
/// of a registered type with the [`Engine`]. /// of a registered type with the [`Engine`].
@ -453,7 +451,7 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>( pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>(
&mut self, &mut self,
callback: impl Fn(&mut T, X) -> U + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -469,7 +467,8 @@ impl Engine {
panic!("Cannot register indexer for strings."); 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`]. /// Register an index getter for a custom type with the [`Engine`].
/// ///
@ -518,7 +517,7 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn register_indexer_get_result<T: Variant + Clone, X: Variant + Clone>( pub fn register_indexer_get_result<T: Variant + Clone, X: Variant + Clone>(
&mut self, &mut self,
callback: impl Fn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static, get_fn: impl Fn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -534,7 +533,8 @@ impl Engine {
panic!("Cannot register indexer for strings."); 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`]. /// Register an index setter for a custom type with the [`Engine`].
/// ///
@ -581,7 +581,7 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>( pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>(
&mut self, &mut self,
callback: impl Fn(&mut T, X, U) + SendSync + 'static, set_fn: impl Fn(&mut T, X, U) + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -597,7 +597,8 @@ impl Engine {
panic!("Cannot register indexer for strings."); 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`]. /// Register an index setter for a custom type with the [`Engine`].
/// ///
@ -651,7 +652,7 @@ impl Engine {
U: Variant + Clone, U: Variant + Clone,
>( >(
&mut self, &mut self,
callback: impl Fn(&mut T, X, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, set_fn: impl Fn(&mut T, X, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
if TypeId::of::<T>() == TypeId::of::<Array>() { if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
@ -667,11 +668,10 @@ impl Engine {
panic!("Cannot register indexer for strings."); panic!("Cannot register indexer for strings.");
} }
crate::RegisterResultFn::register_result_fn( use crate::{engine::FN_IDX_SET, RegisterResultFn};
self, self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| {
crate::engine::FN_IDX_SET, set_fn(obj, index, value).map(Into::into)
move |obj: &mut T, index: X, value: U| callback(obj, index, value).map(Into::into), })
)
} }
/// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. /// 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) self.register_indexer_get(getter)
.register_indexer_set(setter) .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**. /// Sub-modules and variables are **ignored**.
/// ///
/// When searching for functions, modules loaded later are preferred. /// When searching for functions, modules loaded later are preferred. In other words, loaded
/// In other words, loaded modules are searched in reverse order. /// modules are searched in reverse order.
#[inline(always)] #[inline(always)]
pub fn register_global_module(&mut self, module: Shared<Module>) -> &mut Self { pub fn register_global_module(&mut self, module: Shared<Module>) -> &mut Self {
// Insert the module into the front // Insert the module into the front
self.global_modules.insert(0, module); self.global_modules.insert(0, module);
self self
} }
/// Register a shared [`Module`][crate::Module] into the global namespace of [`Engine`]. /// Register a shared [`Module`] into the global namespace of [`Engine`].
/// ///
/// ## Deprecated /// ## Deprecated
/// ///
@ -747,15 +748,17 @@ impl Engine {
pub fn load_package(&mut self, module: impl Into<Shared<Module>>) -> &mut Self { pub fn load_package(&mut self, module: impl Into<Shared<Module>>) -> &mut Self {
self.register_global_module(module.into()) 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 /// # Example
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> { /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Module}; /// use rhai::{Engine, Shared, Module};
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
@ -763,9 +766,18 @@ impl Engine {
/// let mut module = Module::new(); /// let mut module = Module::new();
/// module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// ///
/// // Register the module as a fixed sub-module /// let module: Shared<Module> = module.into();
/// engine.register_static_module("CalcService", 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::<i64>("foo::bar::baz::calc(41)")?, 42);
/// assert_eq!(engine.eval::<i64>("foo::bar::hello::calc(41)")?, 42);
/// assert_eq!(engine.eval::<i64>("CalcService::calc(41)")?, 42); /// assert_eq!(engine.eval::<i64>("CalcService::calc(41)")?, 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -773,20 +785,50 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn register_static_module( pub fn register_static_module(
&mut self, &mut self,
name: impl Into<crate::ImmutableString>, name: impl AsRef<str>,
module: Shared<Module>, module: Shared<Module>,
) -> &mut Self { ) -> &mut Self {
if !module.is_indexed() { fn register_static_module_raw(
// Index the module (making a clone copy if necessary) if it is not indexed root: &mut crate::stdlib::collections::HashMap<crate::ImmutableString, Shared<Module>>,
let mut module = crate::fn_native::shared_take_or_clone(module); name: impl AsRef<str>,
module.build_index(); module: Shared<Module>,
self.global_sub_modules.push(name, module); ) {
} else { let separator = crate::token::Token::DoubleColon.syntax();
self.global_sub_modules.push(name, module);
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 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 /// ## Deprecated
/// ///
@ -796,7 +838,7 @@ impl Engine {
#[deprecated = "use `register_static_module` instead"] #[deprecated = "use `register_static_module` instead"]
pub fn register_module( pub fn register_module(
&mut self, &mut self,
name: impl Into<crate::ImmutableString>, name: impl AsRef<str>,
module: impl Into<Shared<Module>>, module: impl Into<Shared<Module>>,
) -> &mut Self { ) -> &mut Self {
self.register_static_module(name, module.into()) self.register_static_module(name, module.into())
@ -1407,7 +1449,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
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)?; let result = self.eval_ast_with_scope_raw(scope, mods, ast)?;
@ -1493,7 +1535,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mods = &mut self.global_sub_modules.clone(); let mods = &mut (&self.global_sub_modules).into();
let state = &mut State { let state = &mut State {
source: ast.clone_source(), source: ast.clone_source(),
..Default::default() ..Default::default()
@ -1539,12 +1581,12 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub fn call_fn<A: crate::fn_args::FuncArgs, T: Variant + Clone>( pub fn call_fn<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
name: &str, name: &str,
args: A, args: impl crate::fn_args::FuncArgs,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); 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))?; .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?;
let mut state = Default::default(); 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. // Check for data race.
if cfg!(not(feature = "no_closure")) { 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. /// With this method, it is no longer necessary to recompile a large script.
/// The script [`AST`] can be compiled just once. Before evaluation, /// The script [`AST`] can be compiled just once. Before evaluation,
/// constants are passed into the [`Engine`] via an external scope /// 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. /// Then, the [`AST`] is cloned and the copy re-optimized before running.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline] #[inline]

View File

@ -1,7 +1,7 @@
//! Configuration settings for [`Engine`]. //! Configuration settings for [`Engine`].
use crate::stdlib::{format, string::String}; use crate::stdlib::{format, num::NonZeroU8, string::String};
use crate::token::{is_valid_identifier, Token}; use crate::token::Token;
use crate::Engine; use crate::Engine;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -168,9 +168,9 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn set_module_resolver( pub fn set_module_resolver(
&mut self, &mut self,
resolver: Option<impl crate::ModuleResolver + 'static>, resolver: impl crate::ModuleResolver + 'static,
) -> &mut Self { ) -> &mut Self {
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn crate::ModuleResolver>); self.module_resolver = Box::new(resolver);
self self
} }
/// Disable a particular keyword or operator in the language. /// Disable a particular keyword or operator in the language.
@ -214,10 +214,12 @@ impl Engine {
self.disabled_symbols.insert(symbol.into()); self.disabled_symbols.insert(symbol.into());
self 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 operator must be a valid identifier (i.e. it cannot be a symbol).
/// ///
/// The precedence cannot be zero.
///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
@ -245,22 +247,38 @@ impl Engine {
keyword: &str, keyword: &str,
precedence: u8, precedence: u8,
) -> Result<&mut Self, String> { ) -> Result<&mut Self, String> {
if !is_valid_identifier(keyword.chars()) { let precedence = NonZeroU8::new(precedence);
return Err(format!("not a valid identifier: '{}'", keyword).into());
if precedence.is_none() {
return Err("precedence cannot be zero".into());
} }
match Token::lookup_from_syntax(keyword) { match Token::lookup_from_syntax(keyword) {
// Standard identifiers, reserved keywords and custom keywords are OK // Standard identifiers, reserved keywords and custom keywords are OK
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), 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 // 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 // Add to custom keywords
self.custom_keywords self.custom_keywords.insert(keyword.into(), precedence);
.insert(keyword.into(), Some(precedence));
Ok(self) Ok(self)
} }

View File

@ -15,10 +15,12 @@ use crate::stdlib::{
format, format,
iter::{empty, once}, iter::{empty, once},
mem, mem,
num::NonZeroU64,
ops::Deref, ops::Deref,
string::ToString, string::ToString,
vec::Vec, vec::Vec,
}; };
use crate::utils::combine_hashes;
use crate::{ use crate::{
calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr, calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr,
ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, INT, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, INT,
@ -161,7 +163,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_fn: u64, hash_fn: NonZeroU64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
pub_only: bool, pub_only: bool,
@ -453,22 +455,22 @@ impl Engine {
&self, &self,
mods: Option<&Imports>, mods: Option<&Imports>,
lib: &[&Module], lib: &[&Module],
hash_fn: u64, hash_fn: Option<NonZeroU64>,
hash_script: u64, hash_script: Option<NonZeroU64>,
pub_only: bool, pub_only: bool,
) -> bool { ) -> bool {
// First check script-defined functions // First check script-defined functions
(hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only))) hash_script.map(|hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))).unwrap_or(false)
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)) //|| hash_fn.map(|hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))).unwrap_or(false)
// Then check registered functions // Then check registered functions
//|| (hash_script != 0 && self.global_namespace.contains_fn(hash_script, pub_only)) //|| hash_script.map(|hash| self.global_namespace.contains_fn(hash, pub_only)).unwrap_or(false)
|| self.global_namespace.contains_fn(hash_fn, false) || hash_fn.map(|hash| self.global_namespace.contains_fn(hash, false)).unwrap_or(false)
// Then check packages // Then check packages
|| (hash_script != 0 && self.global_modules.iter().any(|m| m.contains_fn(hash_script, false))) || hash_script.map(|hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))).unwrap_or(false)
|| self.global_modules.iter().any(|m| m.contains_fn(hash_fn, false)) || hash_fn.map(|hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))).unwrap_or(false)
// Then check imported modules // Then check imported modules
|| (hash_script != 0 && mods.map(|m| m.contains_fn(hash_script)).unwrap_or(false)) || hash_script.map(|hash| mods.map(|m| m.contains_fn(hash)).unwrap_or(false)).unwrap_or(false)
|| mods.map(|m| m.contains_fn(hash_fn)).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. /// Perform an actual function call, native Rust or scripted, taking care of special functions.
@ -484,7 +486,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_script: u64, hash_script: Option<NonZeroU64>,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
_is_method: bool, _is_method: bool,
@ -534,9 +536,11 @@ impl Engine {
// Script-like function found // Script-like function found
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ if hash_script != 0 _ if hash_script.is_some()
&& self.has_override(Some(mods), lib, 0, hash_script, pub_only) => && self.has_override(Some(mods), lib, None, hash_script, pub_only) =>
{ {
let hash_script = hash_script.unwrap();
// Get function // Get function
let (func, mut source) = lib let (func, mut source) = lib
.iter() .iter()
@ -636,7 +640,16 @@ impl Engine {
// Normal native function call // Normal native function call
_ => self.call_native_fn( _ => 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, state: &mut State,
lib: &[&Module], lib: &[&Module],
fn_name: &str, fn_name: &str,
hash_script: u64, hash_script: Option<NonZeroU64>,
target: &mut crate::engine::Target, target: &mut crate::engine::Target,
mut call_args: StaticVec<Dynamic>, mut call_args: StaticVec<Dynamic>,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
native: bool,
pub_only: bool, pub_only: bool,
pos: Position, pos: Position,
level: usize, level: usize,
@ -745,11 +757,7 @@ impl Engine {
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // Recalculate hash
let hash = if native { let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
0
} else {
calc_script_fn_hash(empty(), fn_name, args_len)
};
// Arguments are passed as-is, adding the curried arguments // Arguments are passed as-is, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>(); let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut arg_values = curry let mut arg_values = curry
@ -773,11 +781,7 @@ impl Engine {
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // Recalculate hash
let hash = if native { let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
0
} else {
calc_script_fn_hash(empty(), fn_name, args_len)
};
// Replace the first argument with the object pointer, adding the curried arguments // Replace the first argument with the object pointer, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>(); let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut arg_values = once(obj) let mut arg_values = once(obj)
@ -837,17 +841,14 @@ impl Engine {
.enumerate() .enumerate()
.for_each(|(i, v)| call_args.insert(i, v)); .for_each(|(i, v)| call_args.insert(i, v));
// Recalculate the hash based on the new function name and new arguments // Recalculate the hash based on the new function name and new arguments
hash = if native { hash = hash_script
0 .and_then(|_| calc_script_fn_hash(empty(), fn_name, call_args.len()));
} else {
calc_script_fn_hash(empty(), fn_name, call_args.len())
};
} }
} }
}; };
if native { if hash_script.is_none() {
hash = 0; hash = None;
} }
// Attached object pointer in front of the arguments // Attached object pointer in front of the arguments
@ -881,8 +882,7 @@ impl Engine {
fn_name: &str, fn_name: &str,
args_expr: impl AsRef<[Expr]>, args_expr: impl AsRef<[Expr]>,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
mut hash_script: u64, mut hash_script: Option<NonZeroU64>,
native: bool,
pub_only: bool, pub_only: bool,
pos: Position, pos: Position,
capture_scope: bool, capture_scope: bool,
@ -951,7 +951,7 @@ impl Engine {
if name == KEYWORD_FN_PTR_CALL if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1 && 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)?; 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)) .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let (mut target, _, pos) = let (mut target, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
if target.as_ref().is_read_only() { 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(); let args = args.as_mut();
self.exec_fn_call( 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, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
@ -1093,7 +1103,7 @@ impl Engine {
fn_name: &str, fn_name: &str,
args_expr: impl AsRef<[Expr]>, args_expr: impl AsRef<[Expr]>,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
hash_script: u64, hash_script: NonZeroU64,
pos: Position, pos: Position,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
@ -1127,7 +1137,7 @@ impl Engine {
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Get target reference to first argument // 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.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
@ -1167,9 +1177,9 @@ impl Engine {
// 2) Calculate a second hash with no qualifiers, empty function name, // 2) Calculate a second hash with no qualifiers, empty function name,
// and the actual list of argument `TypeId`'.s // and the actual list of argument `TypeId`'.s
let hash_fn_args = let hash_fn_args =
calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())).unwrap();
// 3) The final hash is the XOR of the two hashes. // 3) The two hashes are combined.
let hash_qualified_fn = hash_script ^ hash_fn_args; let hash_qualified_fn = combine_hashes(hash_script, hash_fn_args);
module.get_qualified_fn(hash_qualified_fn) module.get_qualified_fn(hash_qualified_fn)
} }

View File

@ -27,15 +27,15 @@ pub trait Func<ARGS, RET> {
/// // Func takes two type parameters: /// // Func takes two type parameters:
/// // 1) a tuple made up of the types of the script function's parameters /// // 1) a tuple made up of the types of the script function's parameters
/// // 2) the return type of the script function /// // 2) the return type of the script function
/// // ///
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable! /// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable!
/// let func = Func::<(i64, String), bool>::create_from_ast( /// let func = Func::<(i64, String), bool>::create_from_ast(
/// // ^^^^^^^^^^^^^ function parameter types in tuple /// // ^^^^^^^^^^^^^ function parameter types in tuple
/// ///
/// engine, // the 'Engine' is consumed into the closure /// engine, // the 'Engine' is consumed into the closure
/// ast, // the 'AST' /// ast, // the 'AST'
/// "calc" // the entry-point function name /// "calc" // the entry-point function name
/// ); /// );
/// ///
/// func(123, "hello".to_string())? == false; // call the anonymous function /// func(123, "hello".to_string())? == false; // call the anonymous function
/// # Ok(()) /// # Ok(())
@ -58,15 +58,15 @@ pub trait Func<ARGS, RET> {
/// // Func takes two type parameters: /// // Func takes two type parameters:
/// // 1) a tuple made up of the types of the script function's parameters /// // 1) a tuple made up of the types of the script function's parameters
/// // 2) the return type of the script function /// // 2) the return type of the script function
/// // ///
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable! /// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable!
/// let func = Func::<(i64, String), bool>::create_from_script( /// let func = Func::<(i64, String), bool>::create_from_script(
/// // ^^^^^^^^^^^^^ function parameter types in tuple /// // ^^^^^^^^^^^^^ function parameter types in tuple
/// ///
/// engine, // the 'Engine' is consumed into the closure /// engine, // the 'Engine' is consumed into the closure
/// script, // the script, notice number of parameters must match /// script, // the script, notice number of parameters must match
/// "calc" // the entry-point function name /// "calc" // the entry-point function name
/// )?; /// )?;
/// ///
/// func(123, "hello".to_string())? == false; // call the anonymous function /// func(123, "hello".to_string())? == false; // call the anonymous function
/// # Ok(()) /// # Ok(())

View File

@ -3,7 +3,14 @@
use crate::ast::{FnAccess, ScriptFnDef}; use crate::ast::{FnAccess, ScriptFnDef};
use crate::engine::Imports; use crate::engine::Imports;
use crate::plugin::PluginFunction; 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::token::is_valid_identifier;
use crate::{ use crate::{
calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, 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], args: &mut [&mut Dynamic],
def_value: Option<&Dynamic>, def_value: Option<&Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
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() self.engine()
.exec_fn_call( .exec_fn_call(
&mut mods, &mut self.mods.cloned().unwrap_or_default(),
&mut Default::default(), &mut Default::default(),
self.lib, self.lib,
fn_name, fn_name,
hash_script, calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }),
args, args,
is_method, is_method,
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 /// A general function pointer, which may carry additional (i.e. curried) argument values
/// to be passed onto a function during a call. /// to be passed onto a function during a call.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct FnPtr(ImmutableString, StaticVec<Dynamic>); pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
impl FnPtr { impl FnPtr {
/// Create a new function pointer. /// Create a new function pointer.
pub fn new(name: impl Into<ImmutableString>) -> Result<Self, Box<EvalAltResult>> {
name.into().try_into()
}
/// Create a new function pointer without checking its parameters.
#[inline(always)] #[inline(always)]
pub(crate) fn new_unchecked( pub(crate) fn new_unchecked(
name: impl Into<ImmutableString>, name: impl Into<ImmutableString>,
@ -257,7 +256,24 @@ impl FnPtr {
pub fn curry(&self) -> &[Dynamic] { pub fn curry(&self) -> &[Dynamic] {
self.1.as_ref() 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<Item = Dynamic>) -> &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"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn is_anonymous(&self) -> bool { pub fn is_anonymous(&self) -> bool {

View File

@ -1,14 +1,18 @@
//! # Rhai - embedded scripting for Rust //! # 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 //! 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. //! 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 //! ```,ignore
//! // Brute force factorial function //! /// Brute force factorial function
//! fn factorial(x) { //! fn factorial(x) {
//! if x == 1 { return 1; } //! if x == 1 { return 1; }
//! x * factorial(x - 1) //! x * factorial(x - 1)
@ -18,7 +22,7 @@
//! compute(factorial(10)) //! compute(factorial(10))
//! ``` //! ```
//! //!
//! And the Rust part: //! ## The Rust part
//! //!
//! ```,no_run //! ```,no_run
//! use rhai::{Engine, EvalAltResult, RegisterFn}; //! use rhai::{Engine, EvalAltResult, RegisterFn};

View File

@ -10,13 +10,14 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
fmt, format, fmt, format,
iter::empty, iter::empty,
num::NonZeroU64,
num::NonZeroUsize, num::NonZeroUsize,
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut},
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
use crate::token::Token; use crate::token::Token;
use crate::utils::StraightHasherBuilder; use crate::utils::{combine_hashes, StraightHasherBuilder};
use crate::{ use crate::{
Dynamic, EvalAltResult, ImmutableString, NativeCallContext, Position, Shared, StaticVec, Dynamic, EvalAltResult, ImmutableString, NativeCallContext, Position, Shared, StaticVec,
}; };
@ -34,9 +35,9 @@ use crate::Map;
/// A type representing the namespace of a function. /// A type representing the namespace of a function.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FnNamespace { pub enum FnNamespace {
/// Global namespace. /// Expose to global namespace.
Global, Global,
/// Internal only. /// Module namespace only.
Internal, Internal,
} }
@ -79,9 +80,9 @@ pub struct FuncInfo {
/// Number of parameters. /// Number of parameters.
pub params: usize, pub params: usize,
/// Parameter types (if applicable). /// Parameter types (if applicable).
pub param_types: Option<StaticVec<TypeId>>, pub param_types: StaticVec<TypeId>,
/// Parameter names (if available). /// Parameter names (if available).
pub param_names: Option<StaticVec<ImmutableString>>, pub param_names: StaticVec<ImmutableString>,
} }
impl FuncInfo { impl FuncInfo {
@ -89,13 +90,19 @@ impl FuncInfo {
pub fn gen_signature(&self) -> String { pub fn gen_signature(&self) -> String {
let mut sig = format!("{}(", self.name); let mut sig = format!("{}(", self.name);
if let Some(ref names) = self.param_names { if !self.param_names.is_empty() {
let mut params: Vec<_> = names.iter().map(ImmutableString::to_string).collect(); let mut params: Vec<_> = self
.param_names
.iter()
.map(ImmutableString::to_string)
.collect();
let return_type = params.pop().unwrap_or_else(|| "()".to_string()); let return_type = params.pop().unwrap_or_else(|| "()".to_string());
sig.push_str(&params.join(", ")); sig.push_str(&params.join(", "));
if return_type != "()" { if return_type != "()" {
sig.push_str(") -> "); sig.push_str(") -> ");
sig.push_str(&return_type); sig.push_str(&return_type);
} else if self.func.is_script() {
sig.push_str(") -> Dynamic");
} else { } else {
sig.push_str(")"); sig.push_str(")");
} }
@ -106,7 +113,12 @@ impl FuncInfo {
sig.push_str(", "); sig.push_str(", ");
} }
} }
sig.push_str(") -> Dynamic");
if self.func.is_script() {
sig.push_str(") -> Dynamic");
} else {
sig.push_str(") -> ?");
}
} }
sig sig
@ -126,12 +138,12 @@ pub struct Module {
/// Module variables. /// Module variables.
variables: HashMap<ImmutableString, Dynamic>, variables: HashMap<ImmutableString, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules. /// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>, all_variables: HashMap<NonZeroU64, Dynamic, StraightHasherBuilder>,
/// External Rust functions. /// External Rust functions.
functions: HashMap<u64, FuncInfo, StraightHasherBuilder>, functions: HashMap<NonZeroU64, FuncInfo, StraightHasherBuilder>,
/// Flattened collection of all external Rust functions, native or scripted. /// Flattened collection of all external Rust functions, native or scripted.
/// including those in sub-modules. /// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>, all_functions: HashMap<NonZeroU64, CallableFunction, StraightHasherBuilder>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of iterator functions, including those in sub-modules. /// 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::<T>) self.get_var(name).and_then(Dynamic::try_cast::<T>)
} }
/// Get a module variable as a [`Dynamic`][crate::Dynamic]. /// Get a module variable as a [`Dynamic`].
/// ///
/// # Example /// # Example
/// ///
@ -387,16 +399,15 @@ impl Module {
/// Get a reference to a namespace-qualified variable. /// Get a reference to a namespace-qualified variable.
/// Name and Position in `EvalAltResult` are None and must be set afterwards. /// 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)] #[inline(always)]
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box<EvalAltResult>> { pub(crate) fn get_qualified_var(
if hash_var == 0 { &self,
Err(EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()) hash_var: NonZeroU64,
} else { ) -> Result<&Dynamic, Box<EvalAltResult>> {
self.all_variables.get(&hash_var).ok_or_else(|| { self.all_variables.get(&hash_var).ok_or_else(|| {
EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into() EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()
}) })
}
} }
/// Set a script-defined function into the module. /// 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. /// If there is an existing function of the same name and number of arguments, it is replaced.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub(crate) fn set_script_fn(&mut self, fn_def: impl Into<Shared<ScriptFnDef>>) -> u64 { pub(crate) fn set_script_fn(&mut self, fn_def: impl Into<Shared<ScriptFnDef>>) -> NonZeroU64 {
let fn_def = fn_def.into(); let fn_def = fn_def.into();
// None + function name + number of arguments. // None + function name + number of arguments.
let num_params = fn_def.params.len(); 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(); let mut param_names: StaticVec<_> = fn_def.params.iter().cloned().collect();
param_names.push("Dynamic".into()); param_names.push("Dynamic".into());
self.functions.insert( self.functions.insert(
@ -419,8 +430,8 @@ impl Module {
namespace: FnNamespace::Internal, namespace: FnNamespace::Internal,
access: fn_def.access, access: fn_def.access,
params: num_params, params: num_params,
param_types: None, param_types: Default::default(),
param_names: Some(param_names), param_names,
func: fn_def.into(), func: fn_def.into(),
}, },
); );
@ -454,6 +465,24 @@ impl Module {
.map(|FuncInfo { func, .. }| func.get_fn_def()) .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<ImmutableString, Shared<Module>> {
// 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? /// Does a sub-module exist in the module?
/// ///
/// # Example /// # Example
@ -515,7 +544,7 @@ impl Module {
/// Does the particular Rust function exist in the 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. /// It is also returned by the `set_fn_XXX` calls.
/// ///
/// # Example /// # Example
@ -528,10 +557,8 @@ impl Module {
/// assert!(module.contains_fn(hash, true)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
#[inline] #[inline]
pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool {
if hash_fn == 0 { if public_only {
false
} else if public_only {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.map(|FuncInfo { access, .. }| access.is_public()) .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. /// 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`]. /// the function [`crate::calc_script_fn_hash`].
/// ///
/// Each parameter name/type pair should be a single string of the format: `var_name: type`. /// 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. /// In other words, the number of entries should be one larger than the number of parameters.
pub fn update_fn_metadata<'a>( pub fn update_fn_metadata<'a>(
&mut self, &mut self,
hash_fn: u64, hash_fn: NonZeroU64,
arg_names: impl AsRef<[&'a str]>, arg_names: impl AsRef<[&'a str]>,
) -> &mut Self { ) -> &mut Self {
if let Some(f) = self.functions.get_mut(&hash_fn) { 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 self
} }
/// Update the namespace of a registered function. /// 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`]. /// 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) { if let Some(f) = self.functions.get_mut(&hash_fn) {
f.namespace = namespace; f.namespace = namespace;
} }
@ -588,12 +619,13 @@ impl Module {
arg_names: Option<&[&str]>, arg_names: Option<&[&str]>,
arg_types: &[TypeId], arg_types: &[TypeId],
func: CallableFunction, func: CallableFunction,
) -> u64 { ) -> NonZeroU64 {
let name = name.into(); 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() .into_iter()
.cloned() .cloned()
.map(|id| { .map(|id| {
@ -611,9 +643,13 @@ impl Module {
name, name,
namespace, namespace,
access, access,
params: params.len(), params: param_types.len(),
param_types: Some(params), param_types,
param_names: arg_names.map(|p| p.iter().map(|&v| v.into()).collect()), param_names: if let Some(p) = arg_names {
p.iter().map(|&v| v.into()).collect()
} else {
Default::default()
},
func: func.into(), func: func.into(),
}, },
); );
@ -624,7 +660,7 @@ impl Module {
} }
/// Set a Rust function taking a reference to the scripting [`Engine`][crate::Engine], /// 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. /// into the module, returning a hash key.
/// ///
/// Use this to register a built-in function which must reference settings on the scripting /// 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. /// 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. /// 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. /// 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<T, Box<EvalAltResult>> func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = let f =
move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from);
@ -733,7 +769,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn() -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn() -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from); let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from);
let arg_types = []; let arg_types = [];
self.set_fn( self.set_fn(
@ -768,7 +804,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
func(cast_arg::<A>(&mut args[0])).map(Dynamic::from) func(cast_arg::<A>(&mut args[0])).map(Dynamic::from)
}; };
@ -808,7 +844,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from) func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
}; };
@ -847,7 +883,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
self.set_fn_1_mut( self.set_fn_1_mut(
crate::engine::make_getter(&name.into()), crate::engine::make_getter(&name.into()),
FnNamespace::Global, FnNamespace::Global,
@ -879,7 +915,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let a = cast_arg::<A>(&mut args[0]); let a = cast_arg::<A>(&mut args[0]);
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
@ -926,7 +962,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
let a = &mut args[0].write_lock::<A>().unwrap(); let a = &mut args[0].write_lock::<A>().unwrap();
@ -972,7 +1008,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
self.set_fn_2_mut( self.set_fn_2_mut(
crate::engine::make_setter(&name.into()), crate::engine::make_setter(&name.into()),
FnNamespace::Global, FnNamespace::Global,
@ -1011,7 +1047,7 @@ impl Module {
pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>( pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
if TypeId::of::<A>() == TypeId::of::<Array>() { if TypeId::of::<A>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -1058,7 +1094,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let a = cast_arg::<A>(&mut args[0]); let a = cast_arg::<A>(&mut args[0]);
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
@ -1111,7 +1147,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[2]); let b = cast_arg::<B>(&mut args[2]);
let c = cast_arg::<C>(&mut args[3]); let c = cast_arg::<C>(&mut args[3]);
@ -1162,7 +1198,7 @@ impl Module {
pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone>( pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone>(
&mut self, &mut self,
func: impl Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
if TypeId::of::<A>() == TypeId::of::<Array>() { if TypeId::of::<A>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays."); panic!("Cannot register indexer for arrays.");
} }
@ -1234,7 +1270,7 @@ impl Module {
&mut self, &mut self,
getter: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, getter: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
setter: impl Fn(&mut A, B, T) -> Result<(), Box<EvalAltResult>> + SendSync + 'static, setter: impl Fn(&mut A, B, T) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> (u64, u64) { ) -> (NonZeroU64, NonZeroU64) {
( (
self.set_indexer_get_fn(getter), self.set_indexer_get_fn(getter),
self.set_indexer_set_fn(setter), self.set_indexer_set_fn(setter),
@ -1271,7 +1307,7 @@ impl Module {
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
func: impl Fn(A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let a = cast_arg::<A>(&mut args[0]); let a = cast_arg::<A>(&mut args[0]);
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
@ -1331,7 +1367,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
namespace: FnNamespace, namespace: FnNamespace,
func: impl Fn(&mut A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static, func: impl Fn(&mut A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
) -> u64 { ) -> NonZeroU64 {
let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[1]); let b = cast_arg::<B>(&mut args[1]);
let c = cast_arg::<C>(&mut args[2]); let c = cast_arg::<C>(&mut args[2]);
@ -1358,48 +1394,43 @@ impl Module {
/// Get a Rust function. /// 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. /// It is also returned by the `set_fn_XXX` calls.
#[inline(always)] #[inline(always)]
pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> { pub(crate) fn get_fn(
if hash_fn == 0 { &self,
None hash_fn: NonZeroU64,
} else { public_only: bool,
self.functions ) -> Option<&CallableFunction> {
.get(&hash_fn) self.functions
.and_then(|FuncInfo { access, func, .. }| match access { .get(&hash_fn)
_ if !public_only => Some(func), .and_then(|FuncInfo { access, func, .. }| match access {
FnAccess::Public => Some(func), _ if !public_only => Some(func),
FnAccess::Private => None, FnAccess::Public => Some(func),
}) FnAccess::Private => None,
} })
} }
/// Does the particular namespace-qualified function exist in the module? /// 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]. /// the hash calculated by [`build_index`][Module::build_index].
#[inline] #[inline(always)]
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool { pub fn contains_qualified_fn(&self, hash_fn: NonZeroU64) -> bool {
if hash_fn == 0 { self.all_functions.contains_key(&hash_fn)
false
} else {
self.all_functions.contains_key(&hash_fn)
}
} }
/// Get a namespace-qualified function. /// Get a namespace-qualified function.
/// Name and Position in `EvalAltResult` are None and must be set afterwards. /// 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]. /// the hash calculated by [`build_index`][Module::build_index].
#[inline(always)] #[inline(always)]
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { pub(crate) fn get_qualified_fn(
if hash_qualified_fn == 0 { &self,
None hash_qualified_fn: NonZeroU64,
} else { ) -> Option<&CallableFunction> {
self.all_functions.get(&hash_qualified_fn) self.all_functions.get(&hash_qualified_fn)
}
} }
/// Combine another module into this module. /// Combine another module into this module.
@ -1686,7 +1717,7 @@ impl Module {
ast: &crate::AST, ast: &crate::AST,
engine: &crate::Engine, engine: &crate::Engine,
) -> Result<Self, Box<EvalAltResult>> { ) -> Result<Self, Box<EvalAltResult>> {
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(); let orig_mods_len = mods.len();
// Run the script // Run the script
@ -1745,8 +1776,8 @@ impl Module {
fn index_module<'a>( fn index_module<'a>(
module: &'a Module, module: &'a Module,
qualifiers: &mut Vec<&'a str>, qualifiers: &mut Vec<&'a str>,
variables: &mut HashMap<u64, Dynamic, StraightHasherBuilder>, variables: &mut HashMap<NonZeroU64, Dynamic, StraightHasherBuilder>,
functions: &mut HashMap<u64, CallableFunction, StraightHasherBuilder>, functions: &mut HashMap<NonZeroU64, CallableFunction, StraightHasherBuilder>,
type_iterators: &mut HashMap<TypeId, IteratorFn>, type_iterators: &mut HashMap<TypeId, IteratorFn>,
) { ) {
module.modules.iter().for_each(|(name, m)| { module.modules.iter().for_each(|(name, m)| {
@ -1760,7 +1791,7 @@ impl Module {
module.variables.iter().for_each(|(var_name, value)| { module.variables.iter().for_each(|(var_name, value)| {
// Qualifiers + variable name // Qualifiers + variable name
let hash_var = 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()); variables.insert(hash_var, value.clone());
}); });
@ -1781,7 +1812,7 @@ impl Module {
name, name,
namespace, namespace,
params, params,
param_types: types, param_types,
func, func,
.. ..
}, },
@ -1793,9 +1824,10 @@ impl Module {
// Qualifiers + function name + number of arguments. // Qualifiers + function name + number of arguments.
let hash_qualified_script = 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()); assert_eq!(*params, param_types.len());
// Namespace-qualified Rust functions are indexed in two steps: // Namespace-qualified Rust functions are indexed in two steps:
@ -1807,9 +1839,11 @@ impl Module {
empty(), empty(),
"", "",
param_types.iter().cloned(), param_types.iter().cloned(),
); )
// 3) The final hash is the XOR of the two hashes. .unwrap();
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; // 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()); functions.insert(hash_qualified_fn, func.clone());
} else if cfg!(not(feature = "no_function")) { } 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. /// _(INTERNALS)_ A chain of [module][Module] names to namespace-qualify a variable or function call.
/// Exported under the `internals` feature only. /// 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, /// 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. /// 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. /// This type is volatile and may change.
#[derive(Clone, Eq, PartialEq, Default, Hash)] #[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct NamespaceRef(StaticVec<Ident>, Option<NonZeroUsize>); pub struct NamespaceRef(Option<NonZeroUsize>, StaticVec<Ident>);
impl fmt::Debug for NamespaceRef { impl fmt::Debug for NamespaceRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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) write!(f, " -> {}", index)
} else { } else {
Ok(()) Ok(())
@ -1924,19 +1958,19 @@ impl Deref for NamespaceRef {
type Target = StaticVec<Ident>; type Target = StaticVec<Ident>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.1
} }
} }
impl DerefMut for NamespaceRef { impl DerefMut for NamespaceRef {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.1
} }
} }
impl fmt::Display for NamespaceRef { impl fmt::Display for NamespaceRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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())?; write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
} }
Ok(()) Ok(())
@ -1945,7 +1979,7 @@ impl fmt::Display for NamespaceRef {
impl From<StaticVec<Ident>> for NamespaceRef { impl From<StaticVec<Ident>> for NamespaceRef {
fn from(modules: StaticVec<Ident>) -> Self { fn from(modules: StaticVec<Ident>) -> Self {
Self(modules, None) Self(None, modules)
} }
} }
@ -1975,12 +2009,14 @@ impl<M: Into<Module>> AddAssign<M> for Module {
} }
impl NamespaceRef { impl NamespaceRef {
/// Get the [`Scope`][crate::Scope] index offset.
pub(crate) fn index(&self) -> Option<NonZeroUsize> { pub(crate) fn index(&self) -> Option<NonZeroUsize> {
self.1 self.0
} }
/// Set the [`Scope`][crate::Scope] index offset.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) { pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.1 = index self.0 = index
} }
} }

View File

@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// collection.push(resolver); /// collection.push(resolver);
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(collection)); /// engine.set_module_resolver(collection);
/// ``` /// ```
#[derive(Default)] #[derive(Default)]
pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>); pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
@ -36,16 +36,41 @@ impl ModuleResolversCollection {
/// collection.push(resolver); /// collection.push(resolver);
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(collection)); /// engine.set_module_resolver(collection);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
/// Add a module keyed by its path. /// Append a module resolver to the end.
#[inline(always)] #[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.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<Box<dyn ModuleResolver>> {
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<dyn ModuleResolver> {
self.0.remove(index)
} }
/// Get an iterator of all the module resolvers. /// Get an iterator of all the module resolvers.
#[inline(always)] #[inline(always)]
@ -59,8 +84,9 @@ impl ModuleResolversCollection {
} }
/// Remove all module resolvers. /// Remove all module resolvers.
#[inline(always)] #[inline(always)]
pub fn clear(&mut self) { pub fn clear(&mut self) -> &mut Self {
self.0.clear(); self.0.clear();
self
} }
/// Is this [`ModuleResolversCollection`] empty? /// Is this [`ModuleResolversCollection`] empty?
#[inline(always)] #[inline(always)]
@ -75,8 +101,9 @@ impl ModuleResolversCollection {
/// Add another [`ModuleResolversCollection`] to the end of this collection. /// Add another [`ModuleResolversCollection`] to the end of this collection.
/// The other [`ModuleResolversCollection`] is consumed. /// The other [`ModuleResolversCollection`] is consumed.
#[inline(always)] #[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.0.extend(other.0.into_iter());
self
} }
} }

View File

@ -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<Shared<Module>, Box<EvalAltResult>> {
EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()
}
}

View File

@ -1,5 +1,9 @@
use crate::stdlib::{ 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}; 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(); /// let mut engine = Engine::new();
/// ///
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct FileModuleResolver { pub struct FileModuleResolver {
@ -65,10 +69,10 @@ impl FileModuleResolver {
/// let resolver = FileModuleResolver::new_with_path("./scripts"); /// let resolver = FileModuleResolver::new_with_path("./scripts");
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self { pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
Self::new_with_path_and_extension(path, "rhai") 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 resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>( pub fn new_with_path_and_extension(
path: P, path: impl Into<PathBuf>,
extension: E, extension: impl Into<String>,
) -> Self { ) -> Self {
Self { Self {
path: path.into(), path: path.into(),
@ -114,12 +118,64 @@ impl FileModuleResolver {
/// let resolver = FileModuleResolver::new(); /// let resolver = FileModuleResolver::new();
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Default::default() 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<PathBuf>) -> &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<String>) -> &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<Path>) -> Option<Shared<Module>> {
#[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 { impl ModuleResolver for FileModuleResolver {

View File

@ -2,6 +2,9 @@ use crate::fn_native::SendSync;
use crate::stdlib::boxed::Box; use crate::stdlib::boxed::Box;
use crate::{Engine, EvalAltResult, Module, Position, Shared}; use crate::{Engine, EvalAltResult, Module, Position, Shared};
mod dummy;
pub use dummy::DummyModuleResolver;
mod collection; mod collection;
pub use collection::ModuleResolversCollection; pub use collection::ModuleResolversCollection;

View File

@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct StaticModuleResolver(HashMap<String, Shared<Module>>); pub struct StaticModuleResolver(HashMap<String, Shared<Module>>);
@ -36,7 +36,7 @@ impl StaticModuleResolver {
/// resolver.insert("hello", module); /// resolver.insert("hello", module);
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver)); /// engine.set_module_resolver(resolver);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
@ -60,8 +60,13 @@ impl StaticModuleResolver {
} }
/// Get an iterator of all the modules. /// Get an iterator of all the modules.
#[inline(always)] #[inline(always)]
pub fn iter(&self) -> impl Iterator<Item = (&str, Shared<Module>)> { pub fn iter(&self) -> impl Iterator<Item = (&str, &Shared<Module>)> {
self.0.iter().map(|(k, v)| (k.as_str(), v.clone())) 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<Item = (&str, &mut Shared<Module>)> {
self.0.iter_mut().map(|(k, v)| (k.as_str(), v))
} }
/// Get a mutable iterator of all the modules. /// Get a mutable iterator of all the modules.
#[inline(always)] #[inline(always)]
@ -75,8 +80,8 @@ impl StaticModuleResolver {
} }
/// Get an iterator of all the modules. /// Get an iterator of all the modules.
#[inline(always)] #[inline(always)]
pub fn values<'a>(&'a self) -> impl Iterator<Item = Shared<Module>> + 'a { pub fn values(&self) -> impl Iterator<Item = &Shared<Module>> {
self.0.values().map(|m| m.clone()) self.0.values().map(|m| m)
} }
/// Remove all modules. /// Remove all modules.
#[inline(always)] #[inline(always)]
@ -95,6 +100,8 @@ impl StaticModuleResolver {
} }
/// Merge another [`StaticModuleResolver`] into this. /// Merge another [`StaticModuleResolver`] into this.
/// The other [`StaticModuleResolver`] is consumed. /// The other [`StaticModuleResolver`] is consumed.
///
/// Existing modules of the same path name are overwritten.
#[inline(always)] #[inline(always)]
pub fn merge(&mut self, other: Self) { pub fn merge(&mut self, other: Self) {
if !other.is_empty() { if !other.is_empty() {

View File

@ -2,7 +2,7 @@
use crate::ast::{Expr, ScriptFnDef, Stmt}; use crate::ast::{Expr, ScriptFnDef, Stmt};
use crate::dynamic::AccessMode; 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::fn_call::run_builtin_binary_op;
use crate::parser::map_dynamic_to_expr; use crate::parser::map_dynamic_to_expr;
use crate::stdlib::{ use crate::stdlib::{
@ -62,6 +62,8 @@ struct State<'a> {
variables: Vec<(String, AccessMode, Expr)>, variables: Vec<(String, AccessMode, Expr)>,
/// An [`Engine`] instance for eager function evaluation. /// An [`Engine`] instance for eager function evaluation.
engine: &'a Engine, engine: &'a Engine,
/// Collection of sub-modules.
mods: Imports,
/// [Module] containing script-defined functions. /// [Module] containing script-defined functions.
lib: &'a [&'a Module], lib: &'a [&'a Module],
/// Optimization level. /// Optimization level.
@ -76,6 +78,7 @@ impl<'a> State<'a> {
changed: false, changed: false,
variables: vec![], variables: vec![],
engine, engine,
mods: (&engine.global_sub_modules).into(),
lib, lib,
optimization_level: level, optimization_level: level,
} }
@ -138,7 +141,7 @@ fn call_fn_with_constant_arguments(
&mut Default::default(), &mut Default::default(),
state.lib, state.lib,
fn_name, fn_name,
hash_fn, hash_fn.unwrap(),
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
true, true,
@ -149,7 +152,7 @@ fn call_fn_with_constant_arguments(
.map(|(v, _)| v) .map(|(v, _)| v)
} }
/// Optimize a block of [statements][crate::ast::Stmt]. /// Optimize a block of [statements][Stmt].
fn optimize_stmt_block( fn optimize_stmt_block(
mut statements: Vec<Stmt>, mut statements: Vec<Stmt>,
pos: Position, 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) { fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
match stmt { match stmt {
// expr op= expr // 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) { fn optimize_expr(expr: &mut Expr, state: &mut State) {
// These keywords are handled specially // These keywords are handled specially
const DONT_EVAL_KEYWORDS: &[&str] = &[ 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) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
// map.string // map.string
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => { (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. // Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); 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(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
// Search for overloaded operators (can override built-in). // 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]) if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1])
.ok().flatten() .ok().flatten()
.and_then(|result| map_dynamic_to_expr(result, *pos)) .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)), Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)),
// constant-name // 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(); state.set_dirty();
// Replace constant with value // Replace constant with value
let mut result = state.find_constant(&x.3.name).unwrap().clone(); let mut result = state.find_constant(&x.2.name).unwrap().clone();
result.set_position(x.3.pos); result.set_position(x.2.pos);
*expr = result; *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( fn optimize_top_level(
mut statements: Vec<Stmt>, mut statements: Vec<Stmt>,
engine: &Engine, engine: &Engine,

View File

@ -32,7 +32,7 @@ mod fn_ptr_functions {
} else { } else {
let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize); let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize);
ctx.engine() ctx.engine()
.has_override(ctx.mods, ctx.lib, 0, hash_script, true) .has_override(ctx.mods, ctx.lib, None, hash_script, true)
} }
} }
} }

View File

@ -91,13 +91,16 @@ pub enum ParseErrorType {
UnknownOperator(String), UnknownOperator(String),
/// Expecting a particular token but not finding one. Wrapped values are the token and description. /// Expecting a particular token but not finding one. Wrapped values are the token and description.
MissingToken(String, String), 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), 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. /// Never appears under the `no_index` feature.
MalformedIndexExpr(String), 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. /// Never appears under the `no_object` and `no_index` features combination.
MalformedInExpr(String), MalformedInExpr(String),
@ -137,7 +140,8 @@ pub enum ParseErrorType {
/// ///
/// Never appears under the `no_function` feature. /// Never appears under the `no_function` feature.
FnMissingParams(String), 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. /// Never appears under the `no_function` feature.
FnDuplicatedParam(String, String), FnDuplicatedParam(String, String),

View File

@ -13,7 +13,7 @@ use crate::stdlib::{
format, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty, iter::empty,
num::NonZeroUsize, num::{NonZeroU64, NonZeroUsize},
string::{String, ToString}, string::{String, ToString},
vec, vec,
vec::Vec, vec::Vec,
@ -34,7 +34,7 @@ use crate::FnAccess;
type PERR = ParseErrorType; type PERR = ParseErrorType;
type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>; type FunctionsLib = HashMap<NonZeroU64, ScriptFnDef, StraightHasherBuilder>;
/// A type that encapsulates the current state of the parser. /// A type that encapsulates the current state of the parser.
#[derive(Debug)] #[derive(Debug)]
@ -238,10 +238,10 @@ impl Expr {
fn into_property(self, state: &mut ParseState) -> Self { fn into_property(self, state: &mut ParseState) -> Self {
match self { match self {
Self::Variable(x) if x.1.is_none() => { 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 getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
let setter = state.get_interned_string(crate::engine::make_setter(&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, _ => self,
} }
@ -278,8 +278,11 @@ fn parse_paren_expr(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
// ( ...
settings.pos = eat_token(input, Token::LeftParen);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -310,7 +313,7 @@ fn parse_fn_call(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
id: ImmutableString, id: ImmutableString,
capture: bool, capture: bool,
mut namespace: Option<Box<NamespaceRef>>, mut namespace: Option<NamespaceRef>,
settings: ParseSettings, settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, token_pos) = input.peek().unwrap(); let (token, token_pos) = input.peek().unwrap();
@ -335,7 +338,7 @@ fn parse_fn_call(
Token::RightParen => { Token::RightParen => {
eat_token(input, 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"))] #[cfg(not(feature = "no_module"))]
modules.set_index(state.find_module(&modules[0].name)); modules.set_index(state.find_module(&modules[0].name));
@ -352,13 +355,17 @@ fn parse_fn_call(
calc_script_fn_hash(empty(), &id, 0) 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( return Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: id.to_string().into(), name: id.to_string().into(),
native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers
capture, capture,
namespace, namespace,
hash: hash_script, hash_script,
args, args,
..Default::default() ..Default::default()
}), }),
@ -383,7 +390,7 @@ fn parse_fn_call(
(Token::RightParen, _) => { (Token::RightParen, _) => {
eat_token(input, 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"))] #[cfg(not(feature = "no_module"))]
modules.set_index(state.find_module(&modules[0].name)); 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()) 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( return Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: id.to_string().into(), name: id.to_string().into(),
native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers
capture, capture,
namespace, namespace,
hash: hash_script, hash_script,
args, args,
..Default::default() ..Default::default()
}), }),
@ -629,8 +640,11 @@ fn parse_array_literal(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
// [ ...
settings.pos = eat_token(input, Token::LeftBracket);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -696,8 +710,11 @@ fn parse_map_literal(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
// #{ ...
settings.pos = eat_token(input, Token::MapStart);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -939,105 +956,196 @@ fn parse_primary(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; 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 // { - block statement as expression
Token::LeftBrace if settings.allow_stmt_expr => { 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), Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
_ => unreachable!(), _ => 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));
} }
} }
// ( - grouped expression
// 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));
}
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?, 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"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?, Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
// Map literal
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, 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), // Identifier
Token::LexError(err) => return Err(err.into_err(settings.pos)), 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( return Err(
@ -1048,26 +1156,26 @@ fn parse_primary(
// Tail processing all possible postfix operators // Tail processing all possible postfix operators
loop { 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; break;
} }
let (token, token_pos) = input.next().unwrap(); let (tail_token, tail_pos) = input.next().unwrap();
settings.pos = token_pos; settings.pos = tail_pos;
root_expr = match (root_expr, token) { root_expr = match (root_expr, tail_token) {
// Qualified function call with ! // Qualified function call with !
(Expr::Variable(x), Token::Bang) if x.1.is_some() => { (Expr::Variable(x), Token::Bang) if x.1.is_some() => {
return Err(if !match_token(input, Token::LeftParen).0 { 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 { } else {
LexError::ImproperSymbol( LexError::ImproperSymbol(
"!".to_string(), "!".to_string(),
"'!' cannot be used to call module functions".to_string(), "'!' cannot be used to call module functions".to_string(),
) )
.into_err(token_pos) .into_err(tail_pos)
}); });
} }
// Function call with ! // Function call with !
@ -1081,35 +1189,38 @@ fn parse_primary(
.into_err(pos)); .into_err(pos));
} }
let (_, modules, _, Ident { name, pos }) = *x; let (_, namespace, Ident { name, pos }) = *x;
settings.pos = pos; 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 // Function call
(Expr::Variable(x), Token::LeftParen) => { (Expr::Variable(x), Token::LeftParen) => {
let (_, modules, _, Ident { name, pos }) = *x; let (_, namespace, Ident { name, pos }) = *x;
settings.pos = pos; 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!(), (Expr::Property(_), _) => unreachable!(),
// module access // module access
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
(Token::Identifier(id2), pos2) => { (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 { if let Some((_, ref mut namespace)) = namespace {
modules.push(var_name_def); namespace.push(var_name_def);
} else { } else {
let mut m: NamespaceRef = Default::default(); let mut ns: NamespaceRef = Default::default();
m.push(var_name_def); ns.push(var_name_def);
modules = Some(Box::new(m)); let index = NonZeroU64::new(42).unwrap(); // Dummy
namespace = Some((index, ns));
} }
let var_name_def = Ident { let var_name_def = Ident {
name: state.get_interned_string(id2), name: state.get_interned_string(id2),
pos: pos2, 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()) => { (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
return Err(PERR::Reserved(id2).into_err(pos2)); return Err(PERR::Reserved(id2).into_err(pos2));
@ -1121,11 +1232,17 @@ fn parse_primary(
(expr, Token::LeftBracket) => { (expr, Token::LeftBracket) => {
parse_index_chain(input, state, lib, expr, settings.level_up())? parse_index_chain(input, state, lib, expr, settings.level_up())?
} }
// Method access // Property access
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
(expr, Token::Period) => { (expr, Token::Period) => {
// prevents capturing of the object properties as vars: xxx.<var>
#[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())?; 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 // Unknown postfix operator
(expr, token) => unreachable!( (expr, token) => unreachable!(
@ -1146,14 +1263,16 @@ fn parse_primary(
_ => None, _ => None,
} }
.map(|x| { .map(|x| {
let (_, modules, hash, Ident { name, .. }) = x.as_mut(); if let (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) = x.as_mut() {
let namespace = modules.as_mut().unwrap(); // Qualifiers + variable name
*hash =
calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap();
// Qualifiers + variable name #[cfg(not(feature = "no_module"))]
*hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); namespace.set_index(state.find_module(&namespace[0].name));
} else {
#[cfg(not(feature = "no_module"))] unreachable!();
namespace.set_index(state.find_module(&namespace[0].name)); }
}); });
// Make sure identifiers are valid // Make sure identifiers are valid
@ -1174,16 +1293,6 @@ fn parse_unary(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
match token { 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 // -expr
Token::UnaryMinus => { Token::UnaryMinus => {
let pos = eat_token(input, Token::UnaryMinus); let pos = eat_token(input, Token::UnaryMinus);
@ -1208,16 +1317,12 @@ fn parse_unary(
// Call negative function // Call negative function
expr => { expr => {
let op = "-"; let op = "-";
let hash = calc_script_fn_hash(empty(), op, 1);
let mut args = StaticVec::new(); let mut args = StaticVec::new();
args.push(expr); args.push(expr);
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
native_only: true,
namespace: None,
hash,
args, args,
..Default::default() ..Default::default()
}), }),
@ -1238,16 +1343,12 @@ fn parse_unary(
// Call plus function // Call plus function
expr => { expr => {
let op = "+"; let op = "+";
let hash = calc_script_fn_hash(empty(), op, 1);
let mut args = StaticVec::new(); let mut args = StaticVec::new();
args.push(expr); args.push(expr);
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
native_only: true,
namespace: None,
hash,
args, args,
..Default::default() ..Default::default()
}), }),
@ -1264,13 +1365,10 @@ fn parse_unary(
args.push(expr); args.push(expr);
let op = "!"; let op = "!";
let hash = calc_script_fn_hash(empty(), op, 1);
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
native_only: true,
hash,
args, args,
def_value: Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false def_value: Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
..Default::default() ..Default::default()
@ -1278,44 +1376,6 @@ fn parse_unary(
pos, 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)
}
// <EOF> // <EOF>
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
// All other tokens // All other tokens
@ -1323,6 +1383,7 @@ fn parse_unary(
} }
} }
/// Make an assignment statement.
fn make_assignment_stmt<'a>( fn make_assignment_stmt<'a>(
fn_name: Cow<'static, str>, fn_name: Cow<'static, str>,
state: &mut ParseState, state: &mut ParseState,
@ -1338,7 +1399,7 @@ fn make_assignment_stmt<'a>(
)), )),
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(x) => { 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 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
AccessMode::ReadWrite => Ok(Stmt::Assignment( AccessMode::ReadWrite => Ok(Stmt::Assignment(
Box::new((lhs, fn_name.into(), rhs)), Box::new((lhs, fn_name.into(), rhs)),
@ -1359,7 +1420,7 @@ fn make_assignment_stmt<'a>(
)), )),
// var[???] (indexed) = rhs, var.??? (indexed) = rhs // var[???] (indexed) = rhs, var.??? (indexed) = rhs
Expr::Variable(x) => { 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 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
AccessMode::ReadWrite => Ok(Stmt::Assignment( AccessMode::ReadWrite => Ok(Stmt::Assignment(
Box::new((lhs, fn_name.into(), rhs)), Box::new((lhs, fn_name.into(), rhs)),
@ -1443,16 +1504,16 @@ fn make_dot_expr(
} }
// lhs.id // lhs.id
(lhs, Expr::Variable(x)) if x.1.is_none() => { (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 getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
let setter = state.get_interned_string(crate::engine::make_setter(&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) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
(_, Expr::Variable(x)) if x.1.is_some() => { (_, 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
(lhs, prop @ Expr::Property(_)) => { (lhs, prop @ Expr::Property(_)) => {
@ -1662,15 +1723,24 @@ fn parse_binary_op(
loop { loop {
let (current_op, current_pos) = input.peek().unwrap(); let (current_op, current_pos) = input.peek().unwrap();
let precedence = if let Token::Custom(c) = current_op { let precedence = match current_op {
// Custom operators Token::Custom(c) => {
if let Some(Some(p)) = state.engine.custom_keywords.get(c) { if state
*p .engine
} else { .custom_keywords
return Err(PERR::Reserved(c.clone()).into_err(*current_pos)); .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 { Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
current_op.precedence() return Err(PERR::UnknownOperator(c.into()).into_err(*current_pos))
}
_ => current_op.precedence(),
}; };
let bind_right = current_op.is_bind_right(); let bind_right = current_op.is_bind_right();
@ -1682,28 +1752,27 @@ fn parse_binary_op(
let (op_token, pos) = input.next().unwrap(); 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.<var>
#[cfg(not(feature = "no_closure"))]
{
state.allow_capture = false;
}
}
}
let rhs = parse_unary(input, state, lib, settings)?; let rhs = parse_unary(input, state, lib, settings)?;
let (next_op, next_pos) = input.peek().unwrap(); let (next_op, next_pos) = input.peek().unwrap();
let next_precedence = if let Token::Custom(c) = next_op { let next_precedence = match next_op {
// Custom operators Token::Custom(c) => {
if let Some(Some(p)) = state.engine.custom_keywords.get(c) { if state
*p .engine
} else { .custom_keywords
return Err(PERR::Reserved(c.clone()).into_err(*next_pos)); .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 { Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
next_op.precedence() return Err(PERR::UnknownOperator(c.into()).into_err(*next_pos))
}
_ => next_op.precedence(),
}; };
// Bind to right if the next operator has higher 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 cmp_def = Some(false.into());
let op = op_token.syntax(); let op = op_token.syntax();
let hash = calc_script_fn_hash(empty(), &op, 2);
let op_base = FnCallExpr { let op_base = FnCallExpr {
name: op, name: op,
native_only: true,
capture: false, capture: false,
..Default::default() ..Default::default()
}; };
@ -1747,19 +1814,11 @@ fn parse_binary_op(
| Token::PowerOf | Token::PowerOf
| Token::Ampersand | Token::Ampersand
| Token::Pipe | Token::Pipe
| Token::XOr => Expr::FnCall( | Token::XOr => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos),
Box::new(FnCallExpr {
hash,
args,
..op_base
}),
pos,
),
// '!=' defaults to true when passed invalid operands // '!=' defaults to true when passed invalid operands
Token::NotEqualsTo => Expr::FnCall( Token::NotEqualsTo => Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
hash,
args, args,
def_value: Some(true.into()), def_value: Some(true.into()),
..op_base ..op_base
@ -1774,7 +1833,6 @@ fn parse_binary_op(
| Token::GreaterThan | Token::GreaterThan
| Token::GreaterThanEqualsTo => Expr::FnCall( | Token::GreaterThanEqualsTo => Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
hash,
args, args,
def_value: cmp_def, def_value: cmp_def,
..op_base ..op_base
@ -1810,20 +1868,31 @@ fn parse_binary_op(
make_in_expr(current_lhs, rhs, pos)? make_in_expr(current_lhs, rhs, pos)?
} }
#[cfg(not(feature = "no_object"))] // #[cfg(not(feature = "no_object"))]
Token::Period => { // Token::Period => {
let rhs = args.pop().unwrap(); // let rhs = args.pop().unwrap();
let current_lhs = args.pop().unwrap(); // let current_lhs = args.pop().unwrap();
make_dot_expr(state, current_lhs, rhs, pos)? // 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( Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
hash, hash_script,
args, args,
native_only: false,
..op_base ..op_base
}), }),
pos, pos,
@ -1892,7 +1961,7 @@ fn parse_custom_syntax(
segments.push(name.clone()); segments.push(name.clone());
tokens.push(state.get_interned_string(MARKER_IDENT)); tokens.push(state.get_interned_string(MARKER_IDENT));
let var_name_def = Ident { name, pos }; 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()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos)); 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)?; let func = parse_fn(input, &mut new_state, lib, access, settings, comments)?;
// Qualifiers (none) + function name + number of arguments. // 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); lib.insert(hash, func);
@ -2565,6 +2634,7 @@ fn parse_stmt(
} }
Token::If => parse_if(input, state, lib, settings.level_up()).map(Some), 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 => { Token::While | Token::Loop => {
parse_while_loop(input, state, lib, settings.level_up()).map(Some) 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<Ident>, pos: Po
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals.iter().for_each(|x| { 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")] #[cfg(feature = "no_closure")]
externals.into_iter().for_each(|x| { 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 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( let expr = Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: curry_func.into(), name: curry_func.into(),
hash, hash_script,
args, args,
..Default::default() ..Default::default()
}), }),

View File

@ -13,8 +13,8 @@ pub use rhai_codegen::*;
pub use rhai_codegen::{export_fn, register_exported_fn}; pub use rhai_codegen::{export_fn, register_exported_fn};
/// Trait implemented by a _plugin function_. /// 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. /// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead.
pub trait PluginFunction { pub trait PluginFunction {
/// Call the plugin function with the arguments provided. /// Call the plugin function with the arguments provided.

View File

@ -55,8 +55,8 @@ pub enum EvalAltResult {
/// String indexing out-of-bounds. /// String indexing out-of-bounds.
/// Wrapped values are the current number of characters in the string and the index number. /// Wrapped values are the current number of characters in the string and the index number.
ErrorStringBounds(usize, INT, Position), 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. /// Trying to index into a type that is not an array, an object map, or a string, and has no
/// Wrapped value is the type name. /// indexer function defined. Wrapped value is the type name.
ErrorIndexingType(String, Position), ErrorIndexingType(String, Position),
/// Invalid arguments for `in` operator. /// Invalid arguments for `in` operator.
ErrorInExpr(Position), ErrorInExpr(Position),

View File

@ -39,17 +39,18 @@ use crate::{Dynamic, ImmutableString, StaticVec};
// # Implementation Notes // # Implementation Notes
// //
// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.) // [`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, // is manually split into two equal-length arrays. That's because variable names take up the most space,
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up // with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to
// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether. // 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. // 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> { pub struct Scope<'a> {
/// Current value of the entry. /// Current value of the entry.
values: Vec<Dynamic>, values: Vec<Dynamic>,
/// (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<StaticVec<ImmutableString>>)>, names: Vec<(Cow<'a, str>, Box<StaticVec<ImmutableString>>)>,
} }
@ -357,8 +358,35 @@ impl<'a> Scope<'a> {
self self
} }
/// Get a mutable reference to an entry in the [`Scope`]. /// 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::<i64>("x").unwrap(), 42);
///
/// let ptr = my_scope.get_mut("x").unwrap();
/// *ptr = 123_i64.into();
///
/// assert_eq!(my_scope.get_value::<i64>("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)] #[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") self.values.get_mut(index).expect("invalid index in Scope")
} }
/// Update the access type of an entry in the [`Scope`]. /// Update the access type of an entry in the [`Scope`].

View File

@ -90,7 +90,7 @@ struct FnMetadata {
pub return_type: Option<String>, pub return_type: Option<String>,
pub signature: String, pub signature: String,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub doc_comments: Option<Vec<String>>, pub doc_comments: Vec<String>,
} }
impl PartialOrd for FnMetadata { impl PartialOrd for FnMetadata {
@ -152,9 +152,9 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
}, },
signature: info.gen_signature(), signature: info.gen_signature(),
doc_comments: if info.func.is_script() { doc_comments: if info.func.is_script() {
Some(info.func.get_fn_def().comments.clone()) info.func.get_fn_def().comments.clone()
} else { } else {
None Default::default()
}, },
} }
} }

View File

@ -121,20 +121,26 @@ impl Engine {
continue; continue;
} }
let token = Token::lookup_from_syntax(s);
let seg = match s { let seg = match s {
// Markers not in first position // Markers not in first position
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK if !segments.is_empty() => s.into(), MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position // Standard or reserved keyword/symbol not in first position
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => { s if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol // Make it a custom keyword/symbol if it is disabled or reserved
if !self.custom_keywords.contains_key(s) { if (self.disabled_symbols.contains(s)
|| matches!(token, Some(Token::Reserved(_))))
&& !self.custom_keywords.contains_key(s)
{
self.custom_keywords.insert(s.into(), None); self.custom_keywords.insert(s.into(), None);
} }
s.into() s.into()
} }
// Standard keyword in first position // Standard keyword in first position
s if segments.is_empty() s if segments.is_empty()
&& Token::lookup_from_syntax(s) && token
.as_ref()
.map(|v| v.is_keyword() || v.is_reserved()) .map(|v| v.is_keyword() || v.is_reserved())
.unwrap_or(false) => .unwrap_or(false) =>
{ {
@ -151,7 +157,11 @@ impl Engine {
} }
// Identifier in first position // Identifier in first position
s if segments.is_empty() && is_valid_identifier(s.chars()) => { 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); self.custom_keywords.insert(s.into(), None);
} }
s.into() s.into()
@ -208,7 +218,7 @@ impl Engine {
/// * `parse` is the parsing function. /// * `parse` is the parsing function.
/// * `func` is the implementation 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. /// Otherwise, custom keywords won't be recognized.
pub fn register_custom_syntax_raw( pub fn register_custom_syntax_raw(
&mut self, &mut self,

View File

@ -532,10 +532,10 @@ impl Token {
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()), "import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" "===" | "!==" | "->" | "<-" | ":=" | "**" | "::<" | "(*" | "*)" | "#" | "public"
| "use" | "module" | "package" | "var" | "static" | "begin" | "end" | "shared" | "new" | "use" | "module" | "package" | "var" | "static" | "begin" | "end"
| "with" | "each" | "then" | "goto" | "unless" | "exit" | "match" | "case" | "shared" | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match"
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
| "async" | "await" | "yield" => Reserved(syntax.into()), | "async" | "await" | "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR 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::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => {
Some((Token::Custom(s), pos)) Some((Token::Custom(s), pos))
} }
// Custom standard keyword - must be disabled // Custom standard keyword/symbol - must be disabled
Some((token, pos)) if token.is_keyword() && self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => { Some((token, pos)) if self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => {
if self.engine.disabled_symbols.contains(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)) Some((Token::Custom(token.syntax().into()), pos))
} else { } else {
// Active standard keyword - should never be a custom keyword! // Active standard keyword - should never be a custom keyword!
unreachable!() unreachable!("{:?}", token)
} }
} }
// Disabled operator // Disabled symbol
Some((token, pos)) if token.is_operator() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => { Some((token, pos)) if 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()) => {
Some((Token::Reserved(token.syntax().into()), pos)) Some((Token::Reserved(token.syntax().into()), pos))
} }
// Normal symbol
r => r, r => r,
}; };

View File

@ -9,30 +9,34 @@ use crate::stdlib::{
fmt, fmt,
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::{empty, FromIterator}, iter::{empty, FromIterator},
num::NonZeroU64,
ops::{Add, AddAssign, Deref}, ops::{Add, AddAssign, Deref},
str::FromStr, str::FromStr,
string::{String, ToString}, string::{String, ToString},
}; };
use crate::Shared; 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
/// ///
/// Panics when hashing any data type other than a [`u64`]. /// Panics when hashing any data type other than a [`NonZeroU64`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StraightHasher(u64); pub struct StraightHasher(NonZeroU64);
impl Hasher for StraightHasher { impl Hasher for StraightHasher {
#[inline(always)] #[inline(always)]
fn finish(&self) -> u64 { fn finish(&self) -> u64 {
self.0 self.0.get()
} }
#[inline(always)] #[inline(always)]
fn write(&mut self, bytes: &[u8]) { fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8]; let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes 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)] #[inline(always)]
fn build_hasher(&self) -> Self::Hasher { 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. /// Exported under the `internals` feature only.
/// ///
/// Module names are passed in via `&str` references from an iterator. /// 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<Item = &'a str>, modules: impl Iterator<Item = &'a str>,
fn_name: &str, fn_name: &str,
params: impl Iterator<Item = TypeId>, params: impl Iterator<Item = TypeId>,
) -> u64 { ) -> Option<NonZeroU64> {
calc_fn_hash(modules, fn_name, None, params) 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. /// and the number of parameters, but no parameter types.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -82,21 +97,11 @@ pub fn calc_script_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>, modules: impl Iterator<Item = &'a str>,
fn_name: &str, fn_name: &str,
num: usize, num: usize,
) -> u64 { ) -> Option<NonZeroU64> {
calc_fn_hash(modules, fn_name, Some(num), empty()) calc_fn_hash(modules, fn_name, Some(num), empty())
} }
/// Create an instance of the default hasher. /// Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types.
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.
/// ///
/// Module names are passed in via `&str` references from an iterator. /// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values 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 /// # Note
/// ///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain. /// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline(always)]
fn calc_fn_hash<'a>( fn calc_fn_hash<'a>(
mut modules: impl Iterator<Item = &'a str>, mut modules: impl Iterator<Item = &'a str>,
fn_name: &str, fn_name: &str,
num: Option<usize>, num: Option<usize>,
params: impl Iterator<Item = TypeId>, params: impl Iterator<Item = TypeId>,
) -> u64 { ) -> Option<NonZeroU64> {
let s = &mut get_hasher(); let s = &mut get_hasher();
// Hash a boolean indicating whether the hash is namespace-qualified. // Hash a boolean indicating whether the hash is namespace-qualified.
@ -122,7 +128,15 @@ fn calc_fn_hash<'a>(
} else { } else {
params.for_each(|t| t.hash(s)); 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. /// The system immutable string type.

View File

@ -32,7 +32,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
x.append(y); x.append(y);
x.len + r x.len + r
" "
)?, )?,
14 14
); );
@ -42,7 +42,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
let x = [1, 2, 3]; let x = [1, 2, 3];
x += [4, 5]; x += [4, 5];
len(x) len(x)
" "
)?, )?,
5 5
); );
@ -53,7 +53,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
let x = [1, 2, 3]; let x = [1, 2, 3];
let y = [4, 5]; let y = [4, 5];
x + y x + y
" "
)? )?
.len(), .len(),
5 5

View File

@ -82,7 +82,7 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("private fn add(x, n, ) { x + n }")?; let ast = engine.compile("private fn add(x, n, ) { x + n }")?;
assert!(matches!( 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<INT, Box<EvalAltResult>>)
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add" EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add"
)); ));

View File

@ -54,6 +54,17 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
ParseErrorType::BadInput(_) ParseErrorType::BadInput(_)
)); ));
assert_eq!(
engine.eval::<INT>(
r"
let foo = #{ x: 42 };
let f = || { this.x };
foo.call(f)
",
)?,
42
);
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r#" r#"
@ -239,7 +250,7 @@ fn test_closures_shared_obj() -> Result<(), Box<EvalAltResult>> {
let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box<EvalAltResult>> { let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box<EvalAltResult>> {
let action_ptr = res["action"].clone().cast::<FnPtr>(); let action_ptr = res["action"].clone().cast::<FnPtr>();
let name = action_ptr.fn_name(); 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 // Test closure

View File

@ -101,7 +101,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
resolver.insert("hello", module); resolver.insert("hello", module);
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(resolver);
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
@ -295,13 +295,13 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
"#, "#,
)?; )?;
engine.set_module_resolver(Some(resolver1)); engine.set_module_resolver(resolver1);
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
let mut resolver2 = StaticModuleResolver::new(); let mut resolver2 = StaticModuleResolver::new();
resolver2.insert("testing", module); resolver2.insert("testing", module);
engine.set_module_resolver(Some(resolver2)); engine.set_module_resolver(resolver2);
assert_eq!( assert_eq!(
engine.eval::<INT>(r#"import "testing" as ttt; ttt::abc"#)?, engine.eval::<INT>(r#"import "testing" as ttt; ttt::abc"#)?,
@ -384,7 +384,7 @@ fn test_module_str() -> Result<(), Box<EvalAltResult>> {
let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new();
static_modules.insert("test", module); static_modules.insert("test", module);
engine.set_module_resolver(Some(static_modules)); engine.set_module_resolver(static_modules);
assert_eq!( assert_eq!(
engine.eval::<INT>(r#"import "test" as test; test::test("test");"#)?, engine.eval::<INT>(r#"import "test" as test; test::test("test");"#)?,
@ -418,7 +418,7 @@ fn test_module_ast_namespace() -> Result<(), Box<EvalAltResult>> {
let mut resolver = StaticModuleResolver::new(); let mut resolver = StaticModuleResolver::new();
resolver.insert("testing", module); resolver.insert("testing", module);
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(resolver);
assert_eq!( assert_eq!(
engine.eval::<INT>(r#"import "testing" as t; t::foo(41)"#)?, engine.eval::<INT>(r#"import "testing" as t; t::foo(41)"#)?,
@ -466,7 +466,7 @@ fn test_module_ast_namespace2() -> Result<(), Box<EvalAltResult>> {
let module = Module::eval_ast_as_new(Scope::new(), &module_ast, &engine)?; let module = Module::eval_ast_as_new(Scope::new(), &module_ast, &engine)?;
let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new();
static_modules.insert("test_module", module); static_modules.insert("test_module", module);
engine.set_module_resolver(Some(static_modules)); engine.set_module_resolver(static_modules);
engine.consume(SCRIPT)?; engine.consume(SCRIPT)?;

View File

@ -21,12 +21,17 @@ fn test_tokens_disabled() {
.compile("let x = 40 + 2; x += 1;") .compile("let x = 40 + 2; x += 1;")
.expect_err("should error") .expect_err("should error")
.0, .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] #[test]
fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> { fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
// Register a custom operator called `foo` and give it // Register a custom operator called `foo` and give it
@ -55,6 +60,29 @@ fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_tokens_custom_operator_symbol() -> Result<(), Box<EvalAltResult>> {
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::<INT>("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::<INT>("1 + 2 * 3 => 4 - 5 / 6")?, 15);
Ok(())
}
#[test] #[test]
fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> { fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();