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_
by unifying the terminology and API under the global umbrella of _modules_.
Bug fixes
---------
* Property access in
Breaking changes
----------------
* `Engine::load_package` is renamed `Engine::register_global_module` and now must explicitly pass a shared [`Module`].
* `Engine::register_module` is renamed `Engine::register_static_module` and now must explicitly pass a shared [`Module`].
* `Package::get` is renamed `Package::as_shared_module`.
* `Engine::set_module_resolver` now takes a straight module resolver instead of an `Option`. To disable module resolving, use the new `DummyModuleResolver`.
Enhancements
------------
* `Scope` is now `Clone + Hash`.
* `Engine::register_static_module` now supports sub-module paths (e.g. `foo::bar::baz`).
* `Engine::register_custom_operator` now accepts reserved symbols.
* `Engine::register_custom_operator` now returns an error if given a precedence of zero.
Version 0.19.8

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -128,18 +128,21 @@ The Rhai Scripting Language
8. [Scriptable Event Handler with State](patterns/events.md)
9. [Dynamic Constants Provider](patterns/dynamic-const.md)
9. [Advanced Topics](advanced.md)
10. [Capture Scope for Function Call](language/fn-capture.md)
11. [Low-Level API](rust/register-raw.md)
12. [Variable Resolver](engine/var.md)
13. [Use as DSL](engine/dsl.md)
1. [Capture Scope for Function Call](language/fn-capture.md)
2. [Low-Level API](rust/register-raw.md)
3. [Variable Resolver](engine/var.md)
4. [Use as DSL](engine/dsl.md)
1. [Disable Keywords and/or Operators](engine/disable.md)
2. [Custom Operators](engine/custom-op.md)
3. [Extending with Custom Syntax](engine/custom-syntax.md)
14. [Multiple Instantiation](patterns/multiple.md)
15. [Functions Metadata](engine/metadata/index.md)
4. [Generate Function Signatures](engine/metadata/gen_fn_sig.md)
5. [Export Metadata to JSON](engine/metadata/export_to_json.md)
10. [Appendix](appendix/index.md)
5. [Multiple Instantiation](patterns/multiple.md)
6. [Functions Metadata](engine/metadata/index.md)
1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md)
2. [Export Metadata to JSON](engine/metadata/export_to_json.md)
10. [External Tools](tools/index.md)
1. [Online Playground](tools/playground.md)
2. [`rhai-doc`](tools/rhai-doc.md)
11. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md)
2. [Operators and Symbols](appendix/operators.md)
3. [Literals](appendix/literals.md)

View File

@ -15,13 +15,19 @@ Online Resources for Rhai
* [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info
* [Online Playground][playground] - Run scripts directly from editor
* [Discord Chat](https://discord.gg/HquqbYFcZ9) - Rhai channel
* [Reddit](https://www.reddit.com/r/Rhai) - Rhai community
External Tools
--------------
* [Online Playground][playground] - Run Rhai scripts directly from an editor in the browser
* [`rhai-doc`] - Rhai script documentation tool
Other Cool Projects
-------------------

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.
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// put arguments in a tuple
// ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// return type must be specified put arguments in a tuple
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?;
// ^^^^^^^^^^ tuple of one

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
customized operators performing specific logic.
`Engine::register_custom_operator` registers a keyword as a custom operator.
`Engine::register_custom_operator` registers a keyword as a custom operator, giving it a particular
_precedence_ (which cannot be zero).
Example
@ -52,15 +53,23 @@ into a syntax that uses the corresponding function calls.
Using `Engine::register_custom_operator` merely enables a convenient shortcut.
Must Follow Variable Naming
--------------------------
Must be a Valid Identifier or Reserved Symbol
--------------------------------------------
All custom operators must be _identifiers_ that follow the same naming rules as [variables].
Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols),
[disabled operators or keywords][disable keywords and operators].
```rust
engine.register_custom_operator("foo", 20); // 'foo' is a valid custom operator
engine.register_custom_operator("=>", 30); // <- error: '=>' is not a valid custom operator
engine.register_custom_operator("#", 20); // the reserved symbol '#' is also
// a valid custom operator
engine.register_custom_operator("+", 30); // <- error: '+' is an active operator
engine.register_custom_operator("=>", 30); // <- error: '=>' is an active symbol
```

View File

@ -7,22 +7,24 @@ Create a Module from an AST
`Module::eval_ast_as_new`
------------------------
A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables,
A [module] can be created from a single script (or pre-compiled [`AST`]) containing global variables,
functions and sub-modules via the `Module::eval_ast_as_new` method.
See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to prepare
a Rhai script for this purpose as well as to control which functions/variables to export.
See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to
prepare a Rhai script for this purpose as well as to control which functions/variables to export.
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module:
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the
new [module]:
* Global variables - all variables exported via the `export` statement (those not exported remain hidden).
* Functions not specifically marked `private`.
* Global modules that remain in the [`Scope`] at the end of a script run.
* Global modules that remain in the [`Scope`] at the end of a script run (become sub-modules).
`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the module namespace
with the global namespace. Therefore, functions defined within the same module script can cross-call each other.
`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the
module namespace with the global namespace. Therefore, functions defined within the same module
script can cross-call each other.
Examples

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.
All [functions] and [type iterators] can be accessed without _namespace qualifiers_.
Variables and sub-modules are **ignored**.
This is by far the easiest way to expose a module's functionalities to Rhai.
@ -35,11 +34,11 @@ use rhai::{Engine, Module};
let mut module = Module::new(); // new module
// Use the 'set_fn_XXX' API to add functions.
// Use the 'Module::set_fn_XXX' API to add functions.
let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1));
// Remember to update the parameter names/types and return type metadata.
// 'set_fn_XXX' by default does not set function metadata.
// 'Module::set_fn_XXX' by default does not set function metadata.
module.update_fn_metadata(hash, ["x: i64", "i64"]);
// Register the module into the global namespace of the Engine.
@ -49,6 +48,19 @@ engine.register_global_module(module.into());
engine.eval::<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
---------------------------------------------
@ -60,25 +72,27 @@ use rhai::{Engine, 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));
// Remember to update the parameter names/types and return type metadata.
// 'set_fn_XXX' by default does not set function metadata.
// 'Module::set_fn_XXX' by default does not set function metadata.
module.update_fn_metadata(hash, ["x: i64", "i64"]);
// Register the module into the Engine as a static module namespace 'calc'
// Register the module into the Engine as the static module namespace path
// 'services::calc'
let mut engine = Engine::new();
engine.register_static_module("calc", module.into());
engine.register_static_module("services::calc", module.into());
engine.eval::<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
`Module::set_fn_mut` and `Module::set_fn_XXX_mut` can optionally expose functions (usually _methods_)
in the module to the _global_ namespace, so [getters/setters] and [indexers] for [custom types]
can work as expected.
The `Module::set_fn_XXX_mut` API methods can optionally expose functions in the [module]
to the _global_ namespace by setting the `namespace` parameter to `FnNamespace::Global`,
so [getters/setters] and [indexers] for [custom types] can work as expected.
[Type iterators], because of their special nature, are _always_ exposed to the _global_ namespace.
@ -87,19 +101,24 @@ use rhai::{Engine, Module, FnNamespace};
let mut module = Module::new(); // new module
// Expose method 'inc' to the global namespace (default is 'Internal')
// Expose method 'inc' to the global namespace (default is 'FnNamespace::Internal')
let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x + 1));
// Remember to update the parameter names/types and return type metadata.
// 'set_fn_XXX' by default does not set function metadata.
// 'Module::set_fn_XXX_mut' by default does not set function metadata.
module.update_fn_metadata(hash, ["x: &mut i64", "i64"]);
// Register the module into the Engine as a static module namespace 'calc'
let mut engine = Engine::new();
engine.register_static_module("calc", module.into());
// The method 'inc' works as expected because it is exposed to the global namespace
// 'inc' works when qualified by the namespace
engine.eval::<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; inc(x)")? == 42;
```
@ -129,7 +148,7 @@ resolver.insert("question", module);
// Set the module resolver into the 'Engine'
let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver));
engine.set_module_resolver(resolver);
// Use namespace-qualified variables
engine.eval::<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();
// Set the custom module resolver into the 'Engine'.
engine.set_module_resolver(Some(MyModuleResolver {}));
engine.set_module_resolver(MyModuleResolver {});
engine.consume(r#"
import "hello" as foo; // this 'import' statement will call

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

View File

@ -3,16 +3,17 @@ Override a Built-in Function
{{#include ../links.md}}
Any similarly-named function defined in a script overrides any built-in or registered
Any similarly-named function defined in a script _overrides_ any built-in or registered
native Rust function of the same name and number of parameters.
```rust
// Override the built-in function 'to_int'
fn to_int(num) {
print("Ha! Gotcha! " + num);
// Override the built-in function 'to_float' when called as a method
fn to_float() {
print("Ha! Gotcha! " + this);
42.0
}
let x = (123).to_int();
let x = 123.to_float();
print(x); // what happens?
```

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 |
| [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result |
| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
| [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script |
| [`rhai-repl`]({{repoTree}}/examples/rhai-repl.rs) | a simple REPL, interactively evaluate statements from stdin |
| [`rhai-run`]({{repoTree}}/examples/rhai-run.rs) | runs each filename passed to it as a Rhai script |
| [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run |
| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function |
| [`strings`]({{repoTree}}/examples/strings.rs) | shows different ways to register functions taking string arguments |
| [`repl`]({{repoTree}}/examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin |
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
The `rhai-repl` example is a particularly good one as it allows one to interactively try out Rhai's
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).

View File

@ -46,8 +46,8 @@ The following scripts are for benchmarking the speed of Rhai:
Running Example Scripts
----------------------
The [`rhai_runner`](../examples/rust.md) example can be used to run the scripts:
The [`rhai-run`](../examples/rust.md) example can be used to run the scripts:
```bash
cargo run --example rhai_runner scripts/any_script.rhai
cargo run --example rhai-run scripts/any_script.rhai
```

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 {
x: INT,
}
impl TestStruct {
fn update(&mut self) {
pub fn update(&mut self) {
self.x += 1000;
}
fn new() -> Self {
pub fn new() -> Self {
Self { x: 1 }
}
}
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn main() {
fn main() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine
.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!(
"{:?}",
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?
);
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"))]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -748,13 +748,16 @@ impl Dynamic {
Self(Union::Variant(Box::new(boxed), AccessMode::ReadWrite))
}
/// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>`
/// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature.
/// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an
/// [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` or
/// [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>`
/// depending on the `sync` feature.
///
/// Shared [`Dynamic`] values are relatively cheap to clone as they simply increment the
/// reference counts.
///
/// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] values.
/// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`]
/// values.
///
/// If the [`Dynamic`] value is already shared, this method returns itself.
///
@ -970,8 +973,8 @@ impl Dynamic {
///
/// If the [`Dynamic`] is not a shared value, it returns itself.
///
/// If the [`Dynamic`] is a shared value, it returns the shared value if there are
/// no outstanding references, or a cloned copy.
/// If the [`Dynamic`] is a shared value, it returns the shared value if there are no
/// outstanding references, or a cloned copy.
#[inline(always)]
pub fn flatten(self) -> Self {
match self.0 {

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ pub trait Func<ARGS, RET> {
/// // Func takes two type parameters:
/// // 1) a tuple made up of the types of the script function's parameters
/// // 2) the return type of the script function
/// //
///
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable!
/// let func = Func::<(i64, String), bool>::create_from_ast(
/// // ^^^^^^^^^^^^^ function parameter types in tuple
@ -58,7 +58,7 @@ pub trait Func<ARGS, RET> {
/// // Func takes two type parameters:
/// // 1) a tuple made up of the types of the script function's parameters
/// // 2) the return type of the script function
/// //
///
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable!
/// let func = Func::<(i64, String), bool>::create_from_script(
/// // ^^^^^^^^^^^^^ function parameter types in tuple

View File

@ -3,7 +3,14 @@
use crate::ast::{FnAccess, ScriptFnDef};
use crate::engine::Imports;
use crate::plugin::PluginFunction;
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String};
use crate::stdlib::{
boxed::Box,
convert::{TryFrom, TryInto},
fmt,
iter::empty,
mem,
string::String,
};
use crate::token::is_valid_identifier;
use crate::{
calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module,
@ -153,25 +160,13 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> {
args: &mut [&mut Dynamic],
def_value: Option<&Dynamic>,
) -> Result<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()
.exec_fn_call(
&mut mods,
&mut self.mods.cloned().unwrap_or_default(),
&mut Default::default(),
self.lib,
fn_name,
hash_script,
calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }),
args,
is_method,
is_method,
@ -225,11 +220,15 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer, which may carry additional (i.e. curried) argument values
/// to be passed onto a function during a call.
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
impl FnPtr {
/// 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)]
pub(crate) fn new_unchecked(
name: impl Into<ImmutableString>,
@ -257,7 +256,24 @@ impl FnPtr {
pub fn curry(&self) -> &[Dynamic] {
self.1.as_ref()
}
/// Does this function pointer refer to an anonymous function?
/// Add a new curried argument.
#[inline(always)]
pub fn add_curry(&mut self, value: Dynamic) -> &mut Self {
self.1.push(value);
self
}
/// Set curried arguments to the function pointer.
#[inline(always)]
pub fn set_curry(&mut self, values: impl IntoIterator<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"))]
#[inline(always)]
pub fn is_anonymous(&self) -> bool {

View File

@ -1,14 +1,18 @@
//! # Rhai - embedded scripting for Rust
//!
//! ![Rhai logo](https://schungx.github.io/rhai/images/logo/rhai-banner-transparent-colour.svg)
//!
//! Rhai is a tiny, simple and fast embedded scripting language for Rust
//! that gives you a safe and easy way to add scripting to your applications.
//! It provides a familiar syntax based on JavaScript and Rust and a simple Rust interface.
//! Here is a quick example.
//!
//! First, the contents of `my_script.rhai`:
//! It provides a familiar syntax based on JavaScript+Rust and a simple Rust interface.
//!
//! # A Quick Example
//!
//! ## Contents of `my_script.rhai`
//!
//! ```,ignore
//! // Brute force factorial function
//! /// Brute force factorial function
//! fn factorial(x) {
//! if x == 1 { return 1; }
//! x * factorial(x - 1)
@ -18,7 +22,7 @@
//! compute(factorial(10))
//! ```
//!
//! And the Rust part:
//! ## The Rust part
//!
//! ```,no_run
//! use rhai::{Engine, EvalAltResult, RegisterFn};

View File

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

View File

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

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::{
boxed::Box, collections::HashMap, io::Error as IoError, path::PathBuf, string::String,
boxed::Box,
collections::HashMap,
io::Error as IoError,
path::{Path, PathBuf},
string::String,
};
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
@ -31,7 +35,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
///
/// let mut engine = Engine::new();
///
/// engine.set_module_resolver(Some(resolver));
/// engine.set_module_resolver(resolver);
/// ```
#[derive(Debug)]
pub struct FileModuleResolver {
@ -65,10 +69,10 @@ impl FileModuleResolver {
/// let resolver = FileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// engine.set_module_resolver(resolver);
/// ```
#[inline(always)]
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
@ -87,12 +91,12 @@ impl FileModuleResolver {
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// engine.set_module_resolver(resolver);
/// ```
#[inline(always)]
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
pub fn new_with_path_and_extension(
path: impl Into<PathBuf>,
extension: impl Into<String>,
) -> Self {
Self {
path: path.into(),
@ -114,12 +118,64 @@ impl FileModuleResolver {
/// let resolver = FileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// engine.set_module_resolver(resolver);
/// ```
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
/// Get the base path for script files.
#[inline(always)]
pub fn path(&self) -> &Path {
self.path.as_ref()
}
/// Set the base path for script files.
#[inline(always)]
pub fn set_path(&mut self, path: impl Into<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 {

View File

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

View File

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

View File

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

View File

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

View File

@ -91,13 +91,16 @@ pub enum ParseErrorType {
UnknownOperator(String),
/// Expecting a particular token but not finding one. Wrapped values are the token and description.
MissingToken(String, String),
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error description (if any).
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error
/// description (if any).
MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any).
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error
/// description (if any).
///
/// Never appears under the `no_index` feature.
MalformedIndexExpr(String),
/// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any).
/// An expression in an `in` expression has syntax error. Wrapped value is the error description
/// (if any).
///
/// Never appears under the `no_object` and `no_index` features combination.
MalformedInExpr(String),
@ -137,7 +140,8 @@ pub enum ParseErrorType {
///
/// Never appears under the `no_function` feature.
FnMissingParams(String),
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
/// A function definition has duplicated parameters. Wrapped values are the function name and
/// parameter name.
///
/// Never appears under the `no_function` feature.
FnDuplicatedParam(String, String),

View File

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

View File

@ -13,8 +13,8 @@ pub use rhai_codegen::*;
pub use rhai_codegen::{export_fn, register_exported_fn};
/// Trait implemented by a _plugin function_.
/// This trait should not be used directly.
///
/// This trait should not be used directly.
/// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead.
pub trait PluginFunction {
/// Call the plugin function with the arguments provided.

View File

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

View File

@ -39,17 +39,18 @@ use crate::{Dynamic, ImmutableString, StaticVec};
// # Implementation Notes
//
// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.)
// is manually split into three equal-length arrays. That's because variable names take up the most space,
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up
// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether.
// is manually split into two equal-length arrays. That's because variable names take up the most space,
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to
// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether.
//
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed.
// The variable type is packed separately into another array because it is even smaller.
#[derive(Debug)]
//
// The alias is `Box`'ed because it occurs infrequently.
#[derive(Debug, Clone, Hash)]
pub struct Scope<'a> {
/// Current value of the entry.
values: Vec<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>>)>,
}
@ -357,8 +358,35 @@ impl<'a> Scope<'a> {
self
}
/// Get a mutable reference to an entry in the [`Scope`].
///
/// If the entry by the specified name is not found, of if it is read-only,
/// [`None`] is returned.
///
/// # Example
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<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)]
pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic {
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
self.values.get_mut(index).expect("invalid index in Scope")
}
/// Update the access type of an entry in the [`Scope`].

View File

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

View File

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

View File

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

View File

@ -9,30 +9,34 @@ use crate::stdlib::{
fmt,
hash::{BuildHasher, Hash, Hasher},
iter::{empty, FromIterator},
num::NonZeroU64,
ops::{Add, AddAssign, Deref},
str::FromStr,
string::{String, ToString},
};
use crate::Shared;
/// A hasher that only takes one single [`u64`] and returns it as a hash key.
/// A hasher that only takes one single [`NonZeroU64`] and returns it as a hash key.
///
/// # Panics
///
/// Panics when hashing any data type other than a [`u64`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasher(u64);
/// Panics when hashing any data type other than a [`NonZeroU64`].
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StraightHasher(NonZeroU64);
impl Hasher for StraightHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
self.0.get()
}
#[inline(always)]
fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
self.0 = u64::from_le_bytes(key);
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
self.0 = NonZeroU64::new(u64::from_le_bytes(key))
.unwrap_or_else(|| NonZeroU64::new(42).unwrap());
}
}
@ -45,11 +49,22 @@ impl BuildHasher for StraightHasherBuilder {
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher {
Default::default()
StraightHasher(NonZeroU64::new(42).unwrap())
}
}
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types.
/// Create an instance of the default hasher.
pub fn get_hasher() -> impl Hasher {
#[cfg(feature = "no_std")]
let s: ahash::AHasher = Default::default();
#[cfg(not(feature = "no_std"))]
let s = crate::stdlib::collections::hash_map::DefaultHasher::new();
s
}
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and
/// parameter types.
/// Exported under the `internals` feature only.
///
/// Module names are passed in via `&str` references from an iterator.
@ -63,11 +78,11 @@ pub fn calc_native_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
params: impl Iterator<Item = TypeId>,
) -> u64 {
) -> Option<NonZeroU64> {
calc_fn_hash(modules, fn_name, None, params)
}
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name
/// and the number of parameters, but no parameter types.
/// Exported under the `internals` feature only.
///
@ -82,21 +97,11 @@ pub fn calc_script_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
num: usize,
) -> u64 {
) -> Option<NonZeroU64> {
calc_fn_hash(modules, fn_name, Some(num), empty())
}
/// Create an instance of the default hasher.
pub fn get_hasher() -> impl Hasher {
#[cfg(feature = "no_std")]
let s: ahash::AHasher = Default::default();
#[cfg(not(feature = "no_std"))]
let s = crate::stdlib::collections::hash_map::DefaultHasher::new();
s
}
/// Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types.
/// Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types.
///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator.
@ -104,12 +109,13 @@ pub fn get_hasher() -> impl Hasher {
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline(always)]
fn calc_fn_hash<'a>(
mut modules: impl Iterator<Item = &'a str>,
fn_name: &str,
num: Option<usize>,
params: impl Iterator<Item = TypeId>,
) -> u64 {
) -> Option<NonZeroU64> {
let s = &mut get_hasher();
// Hash a boolean indicating whether the hash is namespace-qualified.
@ -122,7 +128,15 @@ fn calc_fn_hash<'a>(
} else {
params.for_each(|t| t.hash(s));
}
s.finish()
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
NonZeroU64::new(s.finish()).or_else(|| NonZeroU64::new(42))
}
/// Combine two [`NonZeroU64`] hashes by taking the XOR of them.
#[inline(always)]
pub(crate) fn combine_hashes(a: NonZeroU64, b: NonZeroU64) -> NonZeroU64 {
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
NonZeroU64::new(a.get() ^ b.get()).unwrap_or_else(|| NonZeroU64::new(42).unwrap())
}
/// The system immutable string type.

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 }")?;
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"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add"
));

View File

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

View File

@ -101,7 +101,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
resolver.insert("hello", module);
let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver));
engine.set_module_resolver(resolver);
assert_eq!(
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 mut resolver2 = StaticModuleResolver::new();
resolver2.insert("testing", module);
engine.set_module_resolver(Some(resolver2));
engine.set_module_resolver(resolver2);
assert_eq!(
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();
static_modules.insert("test", module);
engine.set_module_resolver(Some(static_modules));
engine.set_module_resolver(static_modules);
assert_eq!(
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();
resolver.insert("testing", module);
engine.set_module_resolver(Some(resolver));
engine.set_module_resolver(resolver);
assert_eq!(
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 mut static_modules = rhai::module_resolvers::StaticModuleResolver::new();
static_modules.insert("test_module", module);
engine.set_module_resolver(Some(static_modules));
engine.set_module_resolver(static_modules);
engine.consume(SCRIPT)?;

View File

@ -21,12 +21,17 @@ fn test_tokens_disabled() {
.compile("let x = 40 + 2; x += 1;")
.expect_err("should error")
.0,
ParseErrorType::BadInput(LexError::UnexpectedInput("+=".to_string()))
ParseErrorType::UnknownOperator("+=".to_string())
);
assert!(matches!(
*engine.compile("let x = += 0;").expect_err("should error").0,
ParseErrorType::BadInput(LexError::UnexpectedInput(err)) if err == "+="
));
}
#[test]
fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
// Register a custom operator called `foo` and give it
@ -55,6 +60,29 @@ fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
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]
fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();