Merge pull request #238 from schungx/master

Support &str and String parameters in modules.
This commit is contained in:
Stephen Chung 2020-09-20 18:41:47 +08:00 committed by GitHub
commit 1d77e3e174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1046 additions and 412 deletions

View File

@ -9,6 +9,8 @@ Bug fixes
* `if` statement with an empty `true` block would not evaluate the `false` block. This is now fixed. * `if` statement with an empty `true` block would not evaluate the `false` block. This is now fixed.
* Fixes a bug in `Module::set_fn_4_mut`. * Fixes a bug in `Module::set_fn_4_mut`.
* Module API's now properly handle `&str` and `String` parameters.
* Indexers are available under `no_object`.
New features New features
------------ ------------
@ -72,7 +74,7 @@ New features
* Currying of function pointers is supported via the new `curry` keyword. * Currying of function pointers is supported via the new `curry` keyword.
* Automatic currying of anonymous functions to capture shared variables from the external scope. * Automatic currying of anonymous functions to capture shared variables from the external scope.
* Capturing of the calling scope for function call via the `func!(...)` syntax. * Capturing of the calling scope for function call via the `func!(...)` syntax.
* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * `Module::set_indexer_get_set_fn` is added as a short-hand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`.
* New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. * New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers.
* `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared). * `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared).
@ -198,7 +200,7 @@ Breaking changes
New features New features
------------ ------------
* Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added. * Indexers are now split into getters and setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a short-hand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added.
* `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters. * `Engine:register_fn` and `Engine:register_result_fn` accepts functions that take parameters of type `&str` (immutable string slice), which maps directly to `ImmutableString`. This is to avoid needing wrappers for functions taking string parameters.
* Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`. * Set maximum limit on data sizes: `Engine::set_max_string_size`, `Engine::set_max_array_size` and `Engine::set_max_map_size`.
* Supports trailing commas on array literals, object map literals, function definitions and function calls. * Supports trailing commas on array literals, object map literals, function definitions and function calls.

View File

@ -84,71 +84,91 @@ impl ExportedParams for ExportedFnParams {
let mut skip = false; let mut skip = false;
let mut special = FnSpecialAccess::None; let mut special = FnSpecialAccess::None;
for attr in attrs { for attr in attrs {
let crate::attrs::AttrItem { key, value, span: item_span } = attr; let crate::attrs::AttrItem {
key,
value,
span: item_span,
} = attr;
match (key.to_string().as_ref(), value) { match (key.to_string().as_ref(), value) {
("get", None) | ("set", None) | ("name", None) => { ("get", None) | ("set", None) | ("name", None) => {
return Err(syn::Error::new(key.span(), "requires value")) return Err(syn::Error::new(key.span(), "requires value"))
}, }
("name", Some(s)) if &s.value() == FN_IDX_GET => { ("name", Some(s)) if &s.value() == FN_IDX_GET => {
return Err(syn::Error::new(item_span, return Err(syn::Error::new(
"use attribute 'index_get' instead")) item_span,
}, "use attribute 'index_get' instead",
))
}
("name", Some(s)) if &s.value() == FN_IDX_SET => { ("name", Some(s)) if &s.value() == FN_IDX_SET => {
return Err(syn::Error::new(item_span, return Err(syn::Error::new(
"use attribute 'index_set' instead")) item_span,
}, "use attribute 'index_set' instead",
))
}
("name", Some(s)) if s.value().starts_with("get$") => { ("name", Some(s)) if s.value().starts_with("get$") => {
return Err(syn::Error::new(item_span, return Err(syn::Error::new(
format!("use attribute 'getter = \"{}\"' instead", item_span,
&s.value()["get$".len()..]))) format!(
}, "use attribute 'getter = \"{}\"' instead",
&s.value()["get$".len()..]
),
))
}
("name", Some(s)) if s.value().starts_with("set$") => { ("name", Some(s)) if s.value().starts_with("set$") => {
return Err(syn::Error::new(item_span, return Err(syn::Error::new(
format!("use attribute 'setter = \"{}\"' instead", item_span,
&s.value()["set$".len()..]))) format!(
}, "use attribute 'setter = \"{}\"' instead",
&s.value()["set$".len()..]
),
))
}
("name", Some(s)) if s.value().contains('$') => { ("name", Some(s)) if s.value().contains('$') => {
return Err(syn::Error::new(s.span(), return Err(syn::Error::new(
"Rhai function names may not contain dollar sign")) s.span(),
}, "Rhai function names may not contain dollar sign",
))
}
("name", Some(s)) if s.value().contains('.') => { ("name", Some(s)) if s.value().contains('.') => {
return Err(syn::Error::new(s.span(), return Err(syn::Error::new(
"Rhai function names may not contain dot")) s.span(),
}, "Rhai function names may not contain dot",
("name", Some(s)) => { ))
name.push(s.value())
},
("set", Some(s)) => special = match special {
FnSpecialAccess::None =>
FnSpecialAccess::Property(Property::Set(syn::Ident::new(&s.value(),
s.span()))),
_ => {
return Err(syn::Error::new(item_span.span(), "conflicting setter"))
} }
}, ("name", Some(s)) => name.push(s.value()),
("get", Some(s)) => special = match special { ("set", Some(s)) => {
FnSpecialAccess::None => special = match special {
FnSpecialAccess::Property(Property::Get(syn::Ident::new(&s.value(), FnSpecialAccess::None => FnSpecialAccess::Property(Property::Set(
s.span()))), syn::Ident::new(&s.value(), s.span()),
_ => { )),
return Err(syn::Error::new(item_span.span(), "conflicting getter")) _ => return Err(syn::Error::new(item_span.span(), "conflicting setter")),
} }
}, }
("index_get", None) => special = match special { ("get", Some(s)) => {
FnSpecialAccess::None => special = match special {
FnSpecialAccess::Index(Index::Get), FnSpecialAccess::None => FnSpecialAccess::Property(Property::Get(
syn::Ident::new(&s.value(), s.span()),
)),
_ => return Err(syn::Error::new(item_span.span(), "conflicting getter")),
}
}
("index_get", None) => {
special = match special {
FnSpecialAccess::None => FnSpecialAccess::Index(Index::Get),
_ => { _ => {
return Err(syn::Error::new(item_span.span(), "conflicting index_get")) return Err(syn::Error::new(item_span.span(), "conflicting index_get"))
} }
}, }
}
("index_set", None) => special = match special { ("index_set", None) => {
FnSpecialAccess::None => special = match special {
FnSpecialAccess::Index(Index::Set), FnSpecialAccess::None => FnSpecialAccess::Index(Index::Set),
_ => { _ => {
return Err(syn::Error::new(item_span.span(), "conflicting index_set")) return Err(syn::Error::new(item_span.span(), "conflicting index_set"))
} }
}, }
}
("return_raw", None) => return_raw = true, ("return_raw", None) => return_raw = true,
("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => { ("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => {
return Err(syn::Error::new(s.span(), "extraneous value")) return Err(syn::Error::new(s.span(), "extraneous value"))
@ -327,26 +347,40 @@ impl ExportedFn {
} }
pub(crate) fn exported_names(&self) -> Vec<syn::LitStr> { pub(crate) fn exported_names(&self) -> Vec<syn::LitStr> {
let mut literals = self.params.name.as_ref() let mut literals = self
.map(|v| v.iter() .params
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site())).collect()) .name
.as_ref()
.map(|v| {
v.iter()
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
.collect()
})
.unwrap_or_else(|| Vec::new()); .unwrap_or_else(|| Vec::new());
match self.params.special { match self.params.special {
FnSpecialAccess::None => {}, FnSpecialAccess::None => {}
FnSpecialAccess::Property(Property::Get(ref g)) => FnSpecialAccess::Property(Property::Get(ref g)) => literals.push(syn::LitStr::new(
literals.push(syn::LitStr::new(&format!("get${}", g.to_string()), g.span())), &format!("get${}", g.to_string()),
FnSpecialAccess::Property(Property::Set(ref s)) => g.span(),
literals.push(syn::LitStr::new(&format!("set${}", s.to_string()), s.span())), )),
FnSpecialAccess::Index(Index::Get) => FnSpecialAccess::Property(Property::Set(ref s)) => literals.push(syn::LitStr::new(
literals.push(syn::LitStr::new(FN_IDX_GET, proc_macro2::Span::call_site())), &format!("set${}", s.to_string()),
FnSpecialAccess::Index(Index::Set) => s.span(),
literals.push(syn::LitStr::new(FN_IDX_SET, proc_macro2::Span::call_site())), )),
FnSpecialAccess::Index(Index::Get) => {
literals.push(syn::LitStr::new(FN_IDX_GET, proc_macro2::Span::call_site()))
}
FnSpecialAccess::Index(Index::Set) => {
literals.push(syn::LitStr::new(FN_IDX_SET, proc_macro2::Span::call_site()))
}
} }
if literals.is_empty() { if literals.is_empty() {
literals.push(syn::LitStr::new(&self.signature.ident.to_string(), literals.push(syn::LitStr::new(
self.signature.ident.span())); &self.signature.ident.to_string(),
self.signature.ident.span(),
));
} }
literals literals
@ -394,53 +428,61 @@ impl ExportedFn {
match params.special { match params.special {
// 2a. Property getters must take only the subject as an argument. // 2a. Property getters must take only the subject as an argument.
FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"property getter requires exactly 1 argument", "property getter requires exactly 1 argument",
)), ))
}
// 2b. Property getters must return a value. // 2b. Property getters must return a value.
FnSpecialAccess::Property(Property::Get(_)) if self.return_type().is_none() => FnSpecialAccess::Property(Property::Get(_)) if self.return_type().is_none() => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"property getter must return a value" "property getter must return a value",
)), ))
}
// 3a. Property setters must take the subject and a new value as arguments. // 3a. Property setters must take the subject and a new value as arguments.
FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"property setter requires exactly 2 arguments", "property setter requires exactly 2 arguments",
)), ))
}
// 3b. Property setters must return nothing. // 3b. Property setters must return nothing.
FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"property setter must return no value" "property setter must return no value",
)), ))
}
// 4a. Index getters must take the subject and the accessed "index" as arguments. // 4a. Index getters must take the subject and the accessed "index" as arguments.
FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"index getter requires exactly 2 arguments", "index getter requires exactly 2 arguments",
)), ))
}
// 4b. Index getters must return a value. // 4b. Index getters must return a value.
FnSpecialAccess::Index(Index::Get) if self.return_type().is_none() => FnSpecialAccess::Index(Index::Get) if self.return_type().is_none() => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"index getter must return a value" "index getter must return a value",
)), ))
}
// 5a. Index setters must take the subject, "index", and new value as arguments. // 5a. Index setters must take the subject, "index", and new value as arguments.
FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"index setter requires exactly 3 arguments", "index setter requires exactly 3 arguments",
)), ))
}
// 5b. Index setters must return nothing. // 5b. Index setters must return nothing.
FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => {
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.span(), self.signature.span(),
"index setter must return no value" "index setter must return no value",
)), ))
}
_ => {} _ => {}
} }
@ -596,11 +638,13 @@ impl ExportedFn {
// Handle the rest of the arguments, which all are passed by value. // Handle the rest of the arguments, which all are passed by value.
// //
// The only exception is strings, which need to be downcast to ImmutableString to enable a // The only exception is strings, which need to be downcast to ImmutableString to enable a
// zero-copy conversion to &str by reference. // zero-copy conversion to &str by reference, or a cloned String.
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap(); let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap();
for (i, arg) in self.arg_list().enumerate().skip(skip_first_arg as usize) { for (i, arg) in self.arg_list().enumerate().skip(skip_first_arg as usize) {
let var = syn::Ident::new(&format!("arg{}", i), proc_macro2::Span::call_site()); let var = syn::Ident::new(&format!("arg{}", i), proc_macro2::Span::call_site());
let is_str_ref; let is_string;
let is_ref;
match arg { match arg {
syn::FnArg::Typed(pattern) => { syn::FnArg::Typed(pattern) => {
let arg_type: &syn::Type = pattern.ty.as_ref(); let arg_type: &syn::Type = pattern.ty.as_ref();
@ -611,15 +655,22 @@ impl ExportedFn {
.. ..
}) => match elem.as_ref() { }) => match elem.as_ref() {
&syn::Type::Path(ref p) if p.path == str_type_path => { &syn::Type::Path(ref p) if p.path == str_type_path => {
is_str_ref = true; is_string = true;
is_ref = true;
quote_spanned!(arg_type.span()=> quote_spanned!(arg_type.span()=>
mem::take(args[#i]) mem::take(args[#i]).take_immutable_string().unwrap())
.clone().cast::<ImmutableString>())
} }
_ => panic!("internal error: why wasn't this found earlier!?"), _ => panic!("internal error: why wasn't this found earlier!?"),
}, },
&syn::Type::Path(ref p) if p.path == string_type_path => {
is_string = true;
is_ref = false;
quote_spanned!(arg_type.span()=>
mem::take(args[#i]).take_string().unwrap())
}
_ => { _ => {
is_str_ref = false; is_string = false;
is_ref = false;
quote_spanned!(arg_type.span()=> quote_spanned!(arg_type.span()=>
mem::take(args[#i]).clone().cast::<#arg_type>()) mem::take(args[#i]).clone().cast::<#arg_type>())
} }
@ -631,7 +682,7 @@ impl ExportedFn {
}) })
.unwrap(), .unwrap(),
); );
if !is_str_ref { if !is_string {
input_type_exprs.push( input_type_exprs.push(
syn::parse2::<syn::Expr>(quote_spanned!( syn::parse2::<syn::Expr>(quote_spanned!(
arg_type.span()=> TypeId::of::<#arg_type>() arg_type.span()=> TypeId::of::<#arg_type>()
@ -649,7 +700,7 @@ impl ExportedFn {
} }
syn::FnArg::Receiver(_) => panic!("internal error: how did this happen!?"), syn::FnArg::Receiver(_) => panic!("internal error: how did this happen!?"),
} }
if !is_str_ref { if !is_ref {
unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { #var }).unwrap()); unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { #var }).unwrap());
} else { } else {
unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { &#var }).unwrap()); unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { &#var }).unwrap());
@ -685,7 +736,7 @@ impl ExportedFn {
quote! { quote! {
impl PluginFunction for #type_name { impl PluginFunction for #type_name {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), #arg_count, debug_assert_eq!(args.len(), #arg_count,
"wrong arg count: {} != {}", "wrong arg count: {} != {}",

View File

@ -152,6 +152,19 @@ pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::Toke
proc_macro::TokenStream::from(tokens) proc_macro::TokenStream::from(tokens)
} }
#[proc_macro]
pub fn combine_with_exported_module(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
let (module_expr, _export_name, module_path) = match crate::register::parse_register_macro(args)
{
Ok(triple) => triple,
Err(e) => return e.to_compile_error().into(),
};
let tokens = quote! {
#module_path::rhai_generate_into_module(#module_expr, true);
};
proc_macro::TokenStream::from(tokens)
}
#[proc_macro] #[proc_macro]
pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
let (engine_expr, export_name, rust_modpath) = match crate::register::parse_register_macro(args) let (engine_expr, export_name, rust_modpath) = match crate::register::parse_register_macro(args)

View File

@ -140,12 +140,13 @@ impl Parse for Module {
ref expr, ref expr,
ident, ident,
attrs, attrs,
ty,
.. ..
}) => { }) => {
// #[cfg] attributes are not allowed on const declarations // #[cfg] attributes are not allowed on const declarations
crate::attrs::deny_cfg_attr(&attrs)?; crate::attrs::deny_cfg_attr(&attrs)?;
if let syn::Visibility::Public(_) = vis { if let syn::Visibility::Public(_) = vis {
consts.push((ident.to_string(), expr.as_ref().clone())); consts.push((ident.to_string(), ty.clone(), expr.as_ref().clone()));
} }
} }
_ => {} _ => {}
@ -191,6 +192,7 @@ impl Parse for Module {
} }
} }
#[allow(dead_code)]
impl Module { impl Module {
pub fn attrs(&self) -> Option<&Vec<syn::Attribute>> { pub fn attrs(&self) -> Option<&Vec<syn::Attribute>> {
self.mod_all.as_ref().map(|m| &m.attrs) self.mod_all.as_ref().map(|m| &m.attrs)

View File

@ -6,7 +6,7 @@ use crate::attrs::ExportScope;
use crate::function::ExportedFn; use crate::function::ExportedFn;
use crate::module::Module; use crate::module::Module;
pub(crate) type ExportedConst = (String, syn::Expr); pub(crate) type ExportedConst = (String, Box<syn::Type>, syn::Expr);
pub(crate) fn generate_body( pub(crate) fn generate_body(
fns: &mut [ExportedFn], fns: &mut [ExportedFn],
@ -17,13 +17,16 @@ pub(crate) fn generate_body(
let mut set_fn_stmts: Vec<syn::Stmt> = Vec::new(); let mut set_fn_stmts: Vec<syn::Stmt> = Vec::new();
let mut set_const_stmts: Vec<syn::Stmt> = Vec::new(); let mut set_const_stmts: Vec<syn::Stmt> = Vec::new();
let mut add_mod_blocks: Vec<syn::ExprBlock> = Vec::new(); let mut add_mod_blocks: Vec<syn::ExprBlock> = Vec::new();
let mut set_flattened_mod_blocks: Vec<syn::ExprBlock> = Vec::new();
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap(); let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap();
for (const_name, const_expr) in consts { for (const_name, _, _) in consts {
let const_literal = syn::LitStr::new(&const_name, proc_macro2::Span::call_site()); let const_literal = syn::LitStr::new(&const_name, proc_macro2::Span::call_site());
let const_ref = syn::Ident::new(&const_name, proc_macro2::Span::call_site());
set_const_stmts.push( set_const_stmts.push(
syn::parse2::<syn::Stmt>(quote! { syn::parse2::<syn::Stmt>(quote! {
m.set_var(#const_literal, #const_expr); m.set_var(#const_literal, #const_ref);
}) })
.unwrap(), .unwrap(),
); );
@ -54,6 +57,14 @@ pub(crate) fn generate_body(
}) })
.unwrap(), .unwrap(),
); );
set_flattened_mod_blocks.push(
syn::parse2::<syn::ExprBlock>(quote! {
#(#cfg_attrs)* {
self::#module_name::rhai_generate_into_module(m, flatten);
}
})
.unwrap(),
);
} }
// NB: these are token streams, because reparsing messes up "> >" vs ">>" // NB: these are token streams, because reparsing messes up "> >" vs ">>"
@ -87,6 +98,11 @@ pub(crate) fn generate_body(
} }
_ => panic!("internal error: non-string shared reference!?"), _ => panic!("internal error: non-string shared reference!?"),
}, },
syn::Type::Path(ref p) if p.path == string_type_path => {
syn::parse2::<syn::Type>(quote! {
ImmutableString })
.unwrap()
}
syn::Type::Reference(syn::TypeReference { syn::Type::Reference(syn::TypeReference {
mutability: Some(_), mutability: Some(_),
ref elem, ref elem,
@ -129,13 +145,22 @@ pub(crate) fn generate_body(
pub mod generate_info { pub mod generate_info {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
#[allow(unused_mut)]
pub fn rhai_module_generate() -> Module { pub fn rhai_module_generate() -> Module {
let mut m = Module::new(); let mut m = Module::new();
rhai_generate_into_module(&mut m, false);
m
}
#[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
#(#set_fn_stmts)* #(#set_fn_stmts)*
#(#set_const_stmts)* #(#set_const_stmts)*
if flatten {
#(#set_flattened_mod_blocks)*
} else {
#(#add_mod_blocks)* #(#add_mod_blocks)*
m }
} }
} }
}) })

View File

@ -237,21 +237,27 @@ mod generate_tests {
let expected = expected.to_string(); let expected = expected.to_string();
if &actual != &expected { if &actual != &expected {
let mut counter = 0; let mut counter = 0;
let iter = actual let _iter = actual.chars().zip(expected.chars()).skip_while(|(a, e)| {
.chars() if *a == *e {
.zip(expected.chars()) counter += 1;
.inspect(|_| counter += 1) true
.skip_while(|(a, e)| *a == *e); } else {
let (actual_diff, expected_diff) = { false
}
});
let (_actual_diff, _expected_diff) = {
let mut actual_diff = String::new(); let mut actual_diff = String::new();
let mut expected_diff = String::new(); let mut expected_diff = String::new();
for (a, e) in iter.take(50) { for (a, e) in _iter.take(50) {
actual_diff.push(a); actual_diff.push(a);
expected_diff.push(e); expected_diff.push(e);
} }
(actual_diff, expected_diff) (actual_diff, expected_diff)
}; };
eprintln!("actual != expected, diverge at char {}", counter); eprintln!("actual != expected, diverge at char {}", counter);
// eprintln!(" actual: {}", _actual_diff);
// eprintln!("expected: {}", _expected_diff);
// assert!(false);
} }
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -269,7 +275,7 @@ mod generate_tests {
struct Token(); struct Token();
impl PluginFunction for Token { impl PluginFunction for Token {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 0usize, debug_assert_eq!(args.len(), 0usize,
"wrong arg count: {} != {}", args.len(), 0usize); "wrong arg count: {} != {}", args.len(), 0usize);
@ -313,7 +319,7 @@ mod generate_tests {
struct Token(); struct Token();
impl PluginFunction for Token { impl PluginFunction for Token {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize, debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize); "wrong arg count: {} != {}", args.len(), 1usize);
@ -354,7 +360,7 @@ mod generate_tests {
let expected_tokens = quote! { let expected_tokens = quote! {
impl PluginFunction for MyType { impl PluginFunction for MyType {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize, debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize); "wrong arg count: {} != {}", args.len(), 1usize);
@ -388,7 +394,7 @@ mod generate_tests {
struct Token(); struct Token();
impl PluginFunction for Token { impl PluginFunction for Token {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize, debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize); "wrong arg count: {} != {}", args.len(), 2usize);
@ -435,7 +441,7 @@ mod generate_tests {
struct Token(); struct Token();
impl PluginFunction for Token { impl PluginFunction for Token {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize, debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize); "wrong arg count: {} != {}", args.len(), 2usize);
@ -483,11 +489,11 @@ mod generate_tests {
struct Token(); struct Token();
impl PluginFunction for Token { impl PluginFunction for Token {
fn call(&self, fn call(&self,
args: &mut [&mut Dynamic], pos: Position args: &mut [&mut Dynamic]
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize, debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize); "wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<ImmutableString>(); let arg0 = mem::take(args[0usize]).take_immutable_string().unwrap();
Ok(Dynamic::from(special_print(&arg0))) Ok(Dynamic::from(special_print(&arg0)))
} }

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,7 @@ The Rhai Scripting Language
1. [Comments](language/comments.md) 1. [Comments](language/comments.md)
2. [Values and Types](language/values-and-types.md) 2. [Values and Types](language/values-and-types.md)
1. [Dynamic Values](language/dynamic.md) 1. [Dynamic Values](language/dynamic.md)
2. [type-of()](language/type-of.md) 2. [type_of()](language/type-of.md)
3. [Numbers](language/numbers.md) 3. [Numbers](language/numbers.md)
1. [Operators](language/num-op.md) 1. [Operators](language/num-op.md)
2. [Functions](language/num-fn.md) 2. [Functions](language/num-fn.md)

View File

@ -69,12 +69,12 @@ For example, the following is a SQL-like syntax for some obscure DSL operation:
```rust ```rust
let table = [..., ..., ..., ...]; let table = [..., ..., ..., ...];
// Syntax = calculate $ident$ $ident$ from $expr$ -> $ident$ : $expr$ // Syntax = calculate $ident$ ( $expr$ -> $ident$ ) => $ident$ : $expr$
let total = calculate sum price from table -> row : row.weight > 50; let total = calculate sum(table->price) => row : row.weight > 50;
// Note: There is nothing special about those symbols; to make it look exactly like SQL: // Note: There is nothing special about those symbols; to make it look exactly like SQL:
// Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$ // Syntax = SELECT $ident$ ( $ident$ ) AS $ident$ FROM $expr$ WHERE $expr$
let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50; let total = SELECT sum(price) AS row FROM table WHERE row.weight > 50;
``` ```
After registering this custom syntax with Rhai, it can be used anywhere inside a script as After registering this custom syntax with Rhai, it can be used anywhere inside a script as
@ -84,9 +84,9 @@ For its evaluation, the callback function will receive the following list of inp
* `inputs[0] = "sum"` - math operator * `inputs[0] = "sum"` - math operator
* `inputs[1] = "price"` - field name * `inputs[1] = "price"` - field name
* `inputs[2] = Expression(table)` - data source * `inputs[2] = "row"` - loop variable name
* `inputs[3] = "row"` - loop variable name * `inputs[3] = Expression(table)` - data source
* `inputs[4] = Expression(row.wright > 50)` - filter predicate * `inputs[4] = Expression(row.wright > 50)` - filter predicate
Other identifiers, such as `"calculate"`, `"from"`, as well as symbols such as `->` and `:`, Other identifiers, such as `"calculate"`, `"FROM"`, as well as symbols such as `->` and `:` etc.,
are parsed in the order defined within the custom syntax. are parsed in the order defined within the custom syntax.

View File

@ -45,6 +45,15 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
Use Custom Types With Arrays
---------------------------
To use a [custom type] with arrays, a number of array functions need to be manually implemented,
in particular `push`, `pad` and the `==` operator (in order to support the `in` operator).
See the section on [custom types] for more details.
Examples Examples
-------- --------
@ -123,9 +132,3 @@ y.clear(); // empty the array
y.len == 0; y.len == 0;
``` ```
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
```rust
engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) );
```

View File

@ -6,8 +6,8 @@ Dynamic Values
A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`. A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`.
Use [`type_of()`] to Get Value Type Use `type_of()` to Get Value Type
---------------------------------- --------------------------------
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
it is usually used to perform type-specific actions based on the actual value's type. it is usually used to perform type-specific actions based on the actual value's type.

View File

@ -65,17 +65,15 @@ disable `eval` by overloading it, probably with something that throws.
```rust ```rust
fn eval(script) { throw "eval is evil! I refuse to run " + script } fn eval(script) { throw "eval is evil! I refuse to run " + script }
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2" let x = eval("40 + 2"); // throws "eval is evil! I refuse to run 40 + 2"
``` ```
Or overload it from Rust: Or overload it from Rust:
```rust ```rust
fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> { engine.register_result_fn("eval", |script: String| -> Result<(), Box<EvalAltResult>> {
Err(format!("eval is evil! I refuse to run {}", script).into()) Err(format!("eval is evil! I refuse to run {}", script).into())
} });
engine.register_result_fn("eval", alt_eval);
``` ```

View File

@ -9,6 +9,9 @@ is provided by the `for` ... `in` loop.
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; Like C, `continue` can be used to skip to the next iteration, by-passing all following statements;
`break` can be used to break out of the loop unconditionally. `break` can be used to break out of the loop unconditionally.
To loop through a number sequence (with or without steps), use the `range` function to
return a numeric iterator.
```rust ```rust
// Iterate through string, yielding characters // Iterate through string, yielding characters
let s = "hello, world!"; let s = "hello, world!";

View File

@ -20,14 +20,15 @@ Global Variables
The `export` statement, which can only be at global level, exposes selected variables as members of a module. The `export` statement, which can only be at global level, exposes selected variables as members of a module.
Variables not exported are _private_ and hidden to the outside. Variables not exported are _private_ and hidden. They are merely used to initialize the module,
but cannot be accessed from outside.
Everything exported from a module is **constant** (**read-only**). Everything exported from a module is **constant** (**read-only**).
```rust ```rust
// This is a module script. // This is a module script.
let private = 123; // variable not exported - default hidden let hidden = 123; // variable not exported - default hidden
let x = 42; // this will be exported below let x = 42; // this will be exported below
export x; // the variable 'x' is exported under its own name export x; // the variable 'x' is exported under its own name
@ -43,9 +44,6 @@ export x as answer; // the variable 'x' is exported under the alias 'answer'
} }
``` ```
[`private`] variables are used to initialize the module.
They cannot be used apart from this.
Functions Functions
--------- ---------

View File

@ -10,7 +10,7 @@ Unary Operators
| Operator | Description | | Operator | Description |
| -------- | ----------- | | -------- | ----------- |
| `+` | Plus | | `+` | Positive |
| `-` | Negative | | `-` | Negative |
```rust ```rust

View File

@ -1,5 +1,5 @@
`timestamp`'s `timestamp`
============= ===========
{{#include ../links.md}} {{#include ../links.md}}

View File

@ -1,5 +1,5 @@
`type_of` `type_of()`
========= ===========
{{#include ../links.md}} {{#include ../links.md}}

View File

@ -137,7 +137,7 @@ For example, the above configuration example may be expressed by this custom syn
id "hello"; id "hello";
// Add to list // Add to list
list +"foo" list + "foo";
// Add to map // Add to map
map "bar" => true; map "bar" => true;

View File

@ -6,8 +6,8 @@ Object-Oriented Programming (OOP)
Rhai does not have _objects_ per se, but it is possible to _simulate_ object-oriented programming. Rhai does not have _objects_ per se, but it is possible to _simulate_ object-oriented programming.
Use [Object Maps] to Simulate OOP Use Object Maps to Simulate OOP
-------------------------------- ------------------------------
Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md). Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md).
@ -22,8 +22,8 @@ a valid [function pointer] (perhaps defined via an [anonymous function] or more
then the call will be dispatched to the actual function with `this` binding to the [object map] itself. then the call will be dispatched to the actual function with `this` binding to the [object map] itself.
Use Anonymous Functions to Define Methods Use Closures to Define Methods
---------------------------------------- -----------------------------
[Anonymous functions] or [closures] defined as values for [object map] properties take on [Anonymous functions] or [closures] defined as values for [object map] properties take on
a syntactic shape that resembles very closely that of class methods in an OOP language. a syntactic shape that resembles very closely that of class methods in an OOP language.

View File

@ -5,9 +5,11 @@ Export a Rust Module to Rhai
When applied to a Rust module, the `#[export_module]` attribute generates the necessary When applied to a Rust module, the `#[export_module]` attribute generates the necessary
code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions. This code code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions, constants
is exactly what would need to be written by hand to achieve the same goal, and is custom fit and sub-modules.
to each exported item.
This code is exactly what would need to be written by hand to achieve the same goal,
and is custom fit to each exported item.
This Rust module can then either be loaded into an [`Engine`] as a normal [module] or This Rust module can then either be loaded into an [`Engine`] as a normal [module] or
registered as a [custom package]. This is done by using the `exported_module!` macro. registered as a [custom package]. This is done by using the `exported_module!` macro.
@ -16,14 +18,21 @@ registered as a [custom package]. This is done by using the `exported_module!` m
`#[export_module]` and `exported_module!` `#[export_module]` and `exported_module!`
---------------------------------------- ----------------------------------------
Apply `#[export_module]` onto a Rust module to convert all `pub` functions into Rhai plugin Apply `#[export_module]` onto a Rust module to register automatically construct a Rhai [module],
functions. which can then be loaded into an [`Engine`].
All `pub` functions become registered functions, all `pub` constants become [module] constant variables,
and all sub-modules become Rhai sub-modules.
```rust ```rust
use rhai::plugins::*; // a "prelude" import for macros use rhai::plugins::*; // a "prelude" import for macros
#[export_module] #[export_module]
mod my_module { mod my_module {
// This constant will be registered as the constant variable 'SOME_NUMBER'.
// Ignored when loaded as a package.
pub const SOME_NUMBER: i64 = 42;
// This function will be registered as 'greet'. // This function will be registered as 'greet'.
pub fn greet(name: &str) -> String { pub fn greet(name: &str) -> String {
format!("hello, {}!", name) format!("hello, {}!", name)
@ -36,14 +45,23 @@ mod my_module {
pub fn increment(num: &mut i64) { pub fn increment(num: &mut i64) {
*num += 1; *num += 1;
} }
// This function is NOT registered. // This function is not 'pub', so NOT registered.
fn mystic_number() -> i64 { fn mystic_number() -> i64 {
42 42
} }
// This sub-module is ignored when loaded as a package.
pub mod my_sub_module {
// This function is ignored when loaded as a package.
// Otherwise it is a valid registered function under a sub-module.
pub fn get_info() -> String {
"hello".to_string()
}
}
} }
``` ```
In order to load this into an [`Engine`], use the `load_package` method on the exported module: The simplest way to load this into an [`Engine`] is to use the `load_package` method on the exported module:
```rust ```rust
fn main() { fn main() {
@ -52,7 +70,7 @@ fn main() {
// The macro call creates the Rhai module. // The macro call creates the Rhai module.
let module = exported_module!(my_module); let module = exported_module!(my_module);
// A module can simply be loaded, registering all public its contents. // A module can simply be loaded, registering all public functions.
engine.load_package(module); engine.load_package(module);
} }
``` ```
@ -74,9 +92,12 @@ increment(x);
x == 43; x == 43;
``` ```
Registering this as a custom package is almost the same, except that a module resolver must Notice that, when using a [module] as a [package], only functions registered at the _top level_
point to the module, rather than being loaded directly. See the [module] section for more can be accessed. Variables as well as sub-modules are ignored.
information.
Using this directly as a Rhai module is almost the same, except that a [module resolver] must
be used to serve the module, and the module is loaded via `import` statements.
See the [module] section for more information.
Function Overloading and Operators Function Overloading and Operators
@ -88,7 +109,8 @@ attribute to individual functions.
The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with
the [`Engine`], disregarding the actual name of the function. the [`Engine`], disregarding the actual name of the function.
With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai, so long as they have different parameters. With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai,
so long as they have different parameters.
Operators (which require function names that are not valid for Rust) can also be registered this way. Operators (which require function names that are not valid for Rust) can also be registered this way.

View File

@ -38,12 +38,14 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42 println!("result: {}", result.field); // prints 42
``` ```
Register a Custom Type Register a Custom Type
--------------------- ---------------------
A custom type must implement `Clone` as this allows the [`Engine`] to pass by value. A custom type must implement `Clone` as this allows the [`Engine`] to pass by value.
Notice that the custom type needs to be _registered_ using `Engine::register_type`. Notice that the custom type needs to be _registered_ using `Engine::register_type`
or `Engine::register_type_with_name`.
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
@ -66,7 +68,8 @@ let mut engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
``` ```
Methods on The Custom Type
Methods on the Custom Type
------------------------- -------------------------
To use native custom types, methods and functions in Rhai scripts, simply register them To use native custom types, methods and functions in Rhai scripts, simply register them
@ -85,6 +88,7 @@ so that invoking methods can update the types. All other parameters in Rhai are
**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** **IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.**
Use the Custom Type in Scripts Use the Custom Type in Scripts
----------------------------- -----------------------------
@ -97,6 +101,7 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42 println!("result: {}", result.field); // prints 42
``` ```
Method-Call Style vs. Function-Call Style Method-Call Style vs. Function-Call Style
---------------------------------------- ----------------------------------------
@ -127,6 +132,7 @@ Under [`no_object`], however, the _method_ style of function calls
let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?; let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?;
``` ```
`type_of()` a Custom Type `type_of()` a Custom Type
------------------------- -------------------------
@ -150,3 +156,33 @@ engine
let x = new_ts(); let x = new_ts();
x.type_of() == "Hello"; x.type_of() == "Hello";
``` ```
Use the Custom Type With Arrays
------------------------------
The `push` and `pad` functions for [arrays] are only defined for standard built-in types.
For custom types, type-specific versions must be registered:
```rust
engine
.register_fn("push", |list: &mut Array, item: TestStruct| {
list.push(Dynamic::from(item));
}).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| {
if len as usize > list.len() {
list.resize(len as usize, item);
}
});
```
In particular, in order to use the `in` operator with a custom type for an [array],
the `==` operator must be registered for that custom type:
```rust
// Assume 'TestStruct' implements `PartialEq`
engine.register_fn("==", |item1: &mut TestStruct, item2: TestStruct| item1 == item2);
// Then this works in Rhai:
let item = new_ts(); // construct a new 'TestStruct'
item in array; // 'in' operator uses '=='
```

View File

@ -3,8 +3,8 @@ Disable Custom Types
{{#include ../links.md}} {{#include ../links.md}}
The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`, The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_get_result`,
`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`]. `register_set`, `register_set_result` and `register_get_set` are not available under [`no_object`].
The indexers API `register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are also The indexers API `register_indexer_get`, `register_indexer_get_result`, `register_indexer_set`,
not available under [`no_index`]. `register_indexer_set_result`, and `register_indexer_get_set` are also not available under [`no_index`].

View File

@ -7,6 +7,20 @@ A custom type can also expose members by registering `get` and/or `set` function
Getters and setters each take a `&mut` reference to the first parameter. Getters and setters each take a `&mut` reference to the first parameter.
Getters and setters are disabled when the [`no_object`] feature is used.
| `Engine` API | Description | Return Value of Function |
| --------------------- | ------------------------------------------------- | :-----------------------------------: |
| `register_get` | Register a getter | _Any_ |
| `register_set` | Register a setter | _Any_ |
| `register_get_set` | Short-hand to register both a getter and a setter | _None_ |
| `register_get_result` | Register a getter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_set_result` | Register a setter | `Result<Dynamic, Box<EvalAltResult>>` |
Examples
--------
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {

View File

@ -16,6 +16,18 @@ Indexers are disabled when the [`no_index`] feature is used.
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
[arrays] and [object maps]. [arrays] and [object maps].
| `Engine` API | Description | Return Value of Function |
| ----------------------------- | -------------------------------------------------------- | :-----------------------------------: |
| `register_indexer_get` | Register an index getter | _Any_ |
| `register_indexer_set` | Register an index setter | _Any_ |
| `register_indexer_get_set` | Short-hand to register both an index getter and a setter | _None_ |
| `register_indexer_get_result` | Register an index getter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_indexer_set_result` | Register an index setter | `Result<Dynamic, Box<EvalAltResult>>` |
Examples
--------
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
@ -40,7 +52,7 @@ let mut engine = Engine::new();
engine engine
.register_type::<TestStruct>() .register_type::<TestStruct>()
.register_fn("new_ts", TestStruct::new) .register_fn("new_ts", TestStruct::new)
// Shorthand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); // Short-hand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
.register_indexer_get(TestStruct::get_field) .register_indexer_get(TestStruct::get_field)
.register_indexer_set(TestStruct::set_field); .register_indexer_set(TestStruct::set_field);

View File

@ -7,7 +7,8 @@ Create a Module from Rust
Create via Plugin Create via Plugin
----------------- -----------------
By far the simplest way to create a [module] is via a [plugin module]. By far the simplest way to create a [module] is via a [plugin module]
which converts a normal Rust module into a Rhai [module] via procedural macros.
Create via `Module` API Create via `Module` API
@ -21,7 +22,8 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai
Make the `Module` Available to the `Engine` Make the `Module` Available to the `Engine`
------------------------------------------ ------------------------------------------
In order to _use_ a custom module, there must be a [module resolver]. In order to _use_ a custom module, there must be a [module resolver], which serves the module when
loaded via `import` statements.
The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such
a custom module. a custom module.

View File

@ -58,13 +58,16 @@ def_package!(rhai:MyPackage:"My own personal super package", module, {
Create a Custom Package from a Plugin Module Create a Custom Package from a Plugin Module
------------------------------------------- -------------------------------------------
By far the easiest way to create a custom module is to call `Module::merge_flatten` from within By far the easiest way to create a custom module is to call `rhai::plugins::combine_with_exported_module!`
`rhai::def_package!` which simply merges in all the functions defined within a [plugin module]. from within `rhai::def_package!` which simply merges in all the functions defined within a [plugin module].
In fact, this exactly is how Rhai's built-in packages, such as `BasicMathPackage`, are implemented. In fact, this exactly is how Rhai's built-in packages, such as `BasicMathPackage`, are implemented.
`rhai::plugins::exported_module!` generates a module from the [plugins][plugin module] definition, Because of the specific requirements of a [package], all sub-modules are _flattened_
and `Module::merge_flatten` consumes its, adding all its registered functions into the package itself. (i.e. all functions defined within sub-modules are pulled up and registered at the top level instead)
and so there will not be any sub-modules added to the package.
Variables in the [plugin module] are ignored.
```rust ```rust
// Import necessary types and traits. // Import necessary types and traits.
@ -78,12 +81,22 @@ use rhai::plugin::*;
// Define plugin module. // Define plugin module.
#[export_module] #[export_module]
mod my_module { mod my_module {
pub const MY_NUMBER: i64 = 42;
pub fn greet(name: &str) -> String { pub fn greet(name: &str) -> String {
format!("hello, {}!", name) format!("hello, {}!", name)
} }
pub fn get_num() -> i64 { pub fn get_num() -> i64 {
42 42
} }
// This is a sub-module, but if using combine_with_exported_module!, it will
// be flattened and all functions registered at the top level.
pub mod my_sub_module {
pub fn get_sub_num() -> i64 {
0
}
}
} }
// Define the package 'MyPackage'. // Define the package 'MyPackage'.
@ -94,7 +107,20 @@ def_package!(rhai:MyPackage:"My own personal super package", module, {
BasicArrayPackage::init(module); BasicArrayPackage::init(module);
BasicMapPackage::init(module); BasicMapPackage::init(module);
// Merge the plugin module into the custom package. // Merge all registered functions and constants from the plugin module into the custom package.
module.merge_flatten(exported_module!(my_module)); //
// The sub-module 'my_sub_module' is flattened and its functions registered at the top level.
//
// The text string name in the middle parameter can be anything and is reserved for future use;
// it is recommended to be an ID string that uniquely identifies the module.
//
// The constant variable, 'MY_NUMBER', is ignored.
//
// This call ends up registering three functions at the top level of the package:
// 1) greet
// 2) get_num
// 3) get_sub_num (pulled up from 'my_sub_module')
//
combine_with_exported_module!(module, "my-functions", my_module));
}); });
``` ```

View File

@ -29,7 +29,7 @@ engine.load_package(package.get()); // load the package manually. 'get' returns
Difference Between a Package and a Module Difference Between a Package and a Module
---------------------------------------- ----------------------------------------
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics. Packages are internally implemented as [modules], so they share a lot of behavior and characteristics.
The main difference is that a package loads under the _global_ namespace, while a module loads under its own The main difference is that a package loads under the _global_ namespace, while a module loads under its own
namespace alias specified in an [`import`] statement (see also [modules]). namespace alias specified in an [`import`] statement (see also [modules]).
@ -37,6 +37,11 @@ namespace alias specified in an [`import`] statement (see also [modules]).
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
the `import` statement). the `import` statement).
Sub-modules in a package are _flattened_, meaning that functions from them must be pulled up to the root level.
In other words, there can be no sub-modules in a package, and everything should reside in one, flat namespace.
Only functions matter for a package. Constant variables registered in a package are ignored.
Load a Module as a Package Load a Module as a Package
-------------------------- --------------------------

View File

@ -13,23 +13,26 @@ If only a single integer type is needed in scripts - most of the time this is th
lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster
because fewer functions need to be loaded. because fewer functions need to be loaded.
The [`only_i32`] and [`only_i64`] features disable all integer types except `i32` or `i64` respectively.
Use Only 32-Bit Numbers Use Only 32-Bit Numbers
---------------------- ----------------------
If only 32-bit integers are needed - again, most of the time this is the case - using [`only_i32`] disables also `i64`. If only 32-bit integers are needed - again, most of the time this is the case - turn on [`only_i32`].
Under this feature, only `i32` is supported as a built-in integer type and no others.
On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic On 64-bit targets this may not gain much, but on certain 32-bit targets this improves performance
requiring more CPU cycles to complete. due to 64-bit arithmetic requiring more CPU cycles to complete.
Minimize Size of `Dynamic` Minimize Size of `Dynamic`
------------------------- -------------------------
Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets Turning on [`no_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] data type only 8 bytes long.
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. Normally [`Dynamic`] can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
Making [`Dynamic`] small helps performance due to better cache efficiency. A small [`Dynamic`] helps performance due to better cache efficiency.
Use `ImmutableString` Use `ImmutableString`
@ -41,17 +44,17 @@ cloning when passing function arguments.
Rhai's internal string type is `ImmutableString` (basically `Rc<String>` or `Arc<String>` depending on the [`sync`] feature). Rhai's internal string type is `ImmutableString` (basically `Rc<String>` or `Arc<String>` depending on the [`sync`] feature).
It is cheap to clone, but expensive to modify (a new copy of the string must be made in order to change it). It is cheap to clone, but expensive to modify (a new copy of the string must be made in order to change it).
Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`) Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (both map to `ImmutableString`)
for the best performance with Rhai. for the best performance with Rhai.
Disable Closures Disable Closures
---------------- ----------------
Support for [closures], including capturing shared values, adds material overhead to script evaluation. Support for [closures] that capture shared variables adds material overhead to script evaluation.
This is because every data access must be checked whether it is a shared value, and if so, take a read This is because every data access must be checked whether it is a shared value and, if so, take a read
or write lock before reading it. lock before reading it.
Use [`no_closure`] to disable closure and capturing support and optimize the hot path Use [`no_closure`] to disable closure and capturing support to optimize the hot path
because there is no need to take locks for shared data. because there is no need to take locks for shared data.

View File

@ -11,23 +11,23 @@ Notice that this deviates from Rust norm where features are _additive_.
Excluding unneeded functionalities can result in smaller, faster builds as well as Excluding unneeded functionalities can result in smaller, faster builds as well as
more control over what a script can (or cannot) do. more control over what a script can (or cannot) do.
| Feature | Description | | Feature | Additive? | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! | | `unchecked` | No | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | | `sync` | No | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. |
| `no_optimize` | Disable [script optimization]. | | `no_optimize` | No | Disable [script optimization]. |
| `no_float` | Disable floating-point numbers and math. | | `no_float` | No | Disable floating-point numbers and math. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i32` | No | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `only_i64` | No | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_index` | Disable [arrays] and indexing features. | | `no_index` | No | Disable [arrays] and indexing features. |
| `no_object` | Disable support for [custom types] and [object maps]. | | `no_object` | No | Disable support for [custom types] and [object maps]. |
| `no_function` | Disable script-defined [functions]. | | `no_function` | No | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. | | `no_module` | No | Disable loading external [modules]. |
| `no_closure` | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | | `no_closure` | No | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. |
| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | | `no_std` | No | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. |
| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `serde` | Yes | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `internals` | Yes | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
| `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | | `unicode-xid-ident` | No | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. |
Example Example
@ -57,7 +57,7 @@ This configuration is perfect for an expression parser in a 32-bit embedded syst
Caveat - Features Are Not Additive Caveat - Features Are Not Additive
--------------------------------- ---------------------------------
Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities. Most Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities.
In fact, most features are _subtractive_ - i.e. they _remove_ functionalities. In fact, most features are _subtractive_ - i.e. they _remove_ functionalities.

View File

@ -1136,6 +1136,7 @@ impl Dynamic {
} }
/// Convert the `Dynamic` into `String` and return it. /// Convert the `Dynamic` into `String` and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
#[inline(always)] #[inline(always)]
pub fn take_string(self) -> Result<String, &'static str> { pub fn take_string(self) -> Result<String, &'static str> {
@ -1145,7 +1146,8 @@ impl Dynamic {
/// Convert the `Dynamic` into `ImmutableString` and return it. /// Convert the `Dynamic` into `ImmutableString` and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> { #[inline]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), Union::Str(s) => Ok(s),
Union::FnPtr(f) => Ok(f.take_data().0), Union::FnPtr(f) => Ok(f.take_data().0),

View File

@ -12,17 +12,18 @@ use crate::scope::Scope;
use crate::token::{lex, Position}; use crate::token::{lex, Position};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::{ use crate::{
engine::{make_getter, make_setter, Map}, engine::{make_getter, make_setter, Map},
error::ParseErrorType, error::ParseErrorType,
fn_register::{RegisterFn, RegisterResultFn},
token::Token, token::Token,
}; };
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
use crate::fn_register::{RegisterFn, RegisterResultFn};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::{ use crate::{
engine::get_script_function_by_signature, fn_args::FuncArgs, fn_call::ensure_no_data_race, engine::get_script_function_by_signature, fn_args::FuncArgs, fn_call::ensure_no_data_race,
@ -375,7 +376,7 @@ impl Engine {
self.register_result_fn(&make_setter(name), callback) self.register_result_fn(&make_setter(name), callback)
} }
/// Shorthand for registering both getter and setter functions /// Short-hand for registering both getter and setter functions
/// of a registered type with the `Engine`. /// of a registered type with the `Engine`.
/// ///
/// All function signatures must start with `&mut self` and not `&self`. /// All function signatures must start with `&mut self` and not `&self`.
@ -427,7 +428,7 @@ impl Engine {
self.register_get(name, get_fn).register_set(name, set_fn) self.register_get(name, get_fn).register_set(name, set_fn)
} }
/// Register an index getter for a registered type with the `Engine`. /// Register an index getter for a custom type with the `Engine`.
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
@ -452,6 +453,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register the custom type. /// // Register the custom type.
/// # #[cfg(not(feature = "no_object"))]
/// engine.register_type::<TestStruct>(); /// engine.register_type::<TestStruct>();
/// ///
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
@ -463,7 +465,6 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer_get<T, X, U>( pub fn register_indexer_get<T, X, U>(
&mut self, &mut self,
@ -477,7 +478,7 @@ impl Engine {
self.register_fn(FN_IDX_GET, callback) self.register_fn(FN_IDX_GET, callback)
} }
/// Register an index getter for a registered type with the `Engine`. /// Register an index getter for a custom type with the `Engine`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`. /// Returns `Result<Dynamic, Box<EvalAltResult>>`.
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
@ -505,6 +506,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register the custom type. /// // Register the custom type.
/// # #[cfg(not(feature = "no_object"))]
/// engine.register_type::<TestStruct>(); /// engine.register_type::<TestStruct>();
/// ///
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
@ -516,7 +518,6 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer_get_result<T, X>( pub fn register_indexer_get_result<T, X>(
&mut self, &mut self,
@ -529,7 +530,7 @@ impl Engine {
self.register_result_fn(FN_IDX_GET, callback) self.register_result_fn(FN_IDX_GET, callback)
} }
/// Register an index setter for a registered type with the `Engine`. /// Register an index setter for a custom type with the `Engine`.
/// ///
/// # Example /// # Example
/// ///
@ -550,6 +551,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register the custom type. /// // Register the custom type.
/// # #[cfg(not(feature = "no_object"))]
/// engine.register_type::<TestStruct>(); /// engine.register_type::<TestStruct>();
/// ///
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
@ -564,7 +566,6 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer_set<T, X, U>( pub fn register_indexer_set<T, X, U>(
&mut self, &mut self,
@ -578,7 +579,7 @@ impl Engine {
self.register_fn(FN_IDX_SET, callback) self.register_fn(FN_IDX_SET, callback)
} }
/// Register an index setter for a registered type with the `Engine`. /// Register an index setter for a custom type with the `Engine`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`. /// Returns `Result<Dynamic, Box<EvalAltResult>>`.
/// ///
/// # Example /// # Example
@ -603,6 +604,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register the custom type. /// // Register the custom type.
/// # #[cfg(not(feature = "no_object"))]
/// engine.register_type::<TestStruct>(); /// engine.register_type::<TestStruct>();
/// ///
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
@ -617,7 +619,6 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer_set_result<T, X, U>( pub fn register_indexer_set_result<T, X, U>(
&mut self, &mut self,
@ -631,7 +632,7 @@ impl Engine {
self.register_result_fn(FN_IDX_SET, callback) self.register_result_fn(FN_IDX_SET, callback)
} }
/// Shorthand for register both index getter and setter functions for a registered type with the `Engine`. /// Short-hand for register both index getter and setter functions for a custom type with the `Engine`.
/// ///
/// # Example /// # Example
/// ///
@ -653,6 +654,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Register the custom type. /// // Register the custom type.
/// # #[cfg(not(feature = "no_object"))]
/// engine.register_type::<TestStruct>(); /// engine.register_type::<TestStruct>();
/// ///
/// engine.register_fn("new_ts", TestStruct::new); /// engine.register_fn("new_ts", TestStruct::new);
@ -664,7 +666,6 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer_get_set<T, X, U>( pub fn register_indexer_get_set<T, X, U>(
&mut self, &mut self,

View File

@ -1134,8 +1134,7 @@ impl Engine {
) -> Result<Target<'a>, Box<EvalAltResult>> { ) -> Result<Target<'a>, Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
#[cfg(not(feature = "no_index"))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[cfg(not(feature = "no_object"))]
let is_ref = target.is_ref(); let is_ref = target.is_ref();
let val = target.as_mut(); let val = target.as_mut();
@ -1200,7 +1199,6 @@ impl Engine {
} }
} }
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if _indexers => { _ if _indexers => {
let type_name = val.type_name(); let type_name = val.type_name();

View File

@ -232,7 +232,7 @@ impl Engine {
// Run external function // Run external function
let result = if func.is_plugin_fn() { let result = if func.is_plugin_fn() {
func.get_plugin_fn().call(args, Position::none()) func.get_plugin_fn().call(args)
} else { } else {
func.get_native_fn()(self, lib, args) func.get_native_fn()(self, lib, args)
}; };
@ -1099,7 +1099,7 @@ impl Engine {
self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level)
} }
Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()), Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut()),
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
if !f.is_method() { if !f.is_method() {
// Clone first argument // Clone first argument

View File

@ -46,7 +46,7 @@ pub trait RegisterPlugin<PL: crate::plugin::Plugin> {
/// fn is_method_call(&self) -> bool { false } /// fn is_method_call(&self) -> bool { false }
/// fn is_varadic(&self) -> bool { false } /// fn is_varadic(&self) -> bool { false }
/// ///
/// fn call(&self, args: &mut[&mut Dynamic], pos: Position) -> Result<Dynamic, Box<EvalAltResult>> { /// fn call(&self, args: &mut[&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
/// let x1: NUMBER = std::mem::take(args[0]).clone().cast::<NUMBER>(); /// let x1: NUMBER = std::mem::take(args[0]).clone().cast::<NUMBER>();
/// let y1: NUMBER = std::mem::take(args[1]).clone().cast::<NUMBER>(); /// let y1: NUMBER = std::mem::take(args[1]).clone().cast::<NUMBER>();
/// let x2: NUMBER = std::mem::take(args[2]).clone().cast::<NUMBER>(); /// let x2: NUMBER = std::mem::take(args[2]).clone().cast::<NUMBER>();

View File

@ -4,10 +4,11 @@ use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync};
use crate::fn_register::by_value as cast_arg;
use crate::parser::{FnAccess, FnAccess::Public, ScriptFnDef}; use crate::parser::{FnAccess, FnAccess::Public, ScriptFnDef};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{Position, Token}; use crate::token::{Position, Token};
use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::fn_native::Shared; use crate::fn_native::Shared;
@ -32,7 +33,6 @@ use crate::stdlib::{
collections::HashMap, collections::HashMap,
fmt, format, fmt, format,
iter::empty, iter::empty,
mem,
num::NonZeroUsize, num::NonZeroUsize,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
string::{String, ToString}, string::{String, ToString},
@ -396,9 +396,21 @@ impl Module {
arg_types.len() arg_types.len()
}; };
let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); let params = arg_types
.into_iter()
.cloned()
.map(|id| {
if id == TypeId::of::<&str>() {
TypeId::of::<ImmutableString>()
} else if id == TypeId::of::<String>() {
TypeId::of::<ImmutableString>()
} else {
id
}
})
.collect();
let params = arg_types.into_iter().cloned().collect(); let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned());
self.functions self.functions
.insert(hash_fn, (name, access, params, func.into())); .insert(hash_fn, (name, access, params, func.into()));
@ -518,7 +530,7 @@ impl Module {
func: impl Fn(A) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(A) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
func(mem::take(args[0]).cast::<A>()).map(Dynamic::from) func(cast_arg::<A>(&mut args[0])).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f)))
@ -592,8 +604,8 @@ impl Module {
func: impl Fn(A, B) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(A, B) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = cast_arg::<A>(&mut args[0]);
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[1]);
func(a, b).map(Dynamic::from) func(a, b).map(Dynamic::from)
}; };
@ -623,7 +635,7 @@ impl Module {
func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[1]);
let a = &mut args[0].write_lock::<A>().unwrap(); let a = &mut args[0].write_lock::<A>().unwrap();
func(a, b).map(Dynamic::from) func(a, b).map(Dynamic::from)
@ -709,9 +721,9 @@ impl Module {
func: impl Fn(A, B, C) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(A, B, C) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = cast_arg::<A>(&mut args[0]);
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[1]);
let c = mem::take(args[2]).cast::<C>(); let c = cast_arg::<C>(&mut args[2]);
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
}; };
@ -746,8 +758,8 @@ impl Module {
func: impl Fn(&mut A, B, C) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(&mut A, B, C) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[2]);
let c = mem::take(args[2]).cast::<C>(); let c = cast_arg::<C>(&mut args[3]);
let a = &mut args[0].write_lock::<A>().unwrap(); let a = &mut args[0].write_lock::<A>().unwrap();
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
@ -780,8 +792,8 @@ impl Module {
func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[1]);
let c = mem::take(args[2]).cast::<C>(); let c = cast_arg::<C>(&mut args[2]);
let a = &mut args[0].write_lock::<A>().unwrap(); let a = &mut args[0].write_lock::<A>().unwrap();
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
@ -796,7 +808,7 @@ impl Module {
} }
/// Set a pair of Rust index getter and setter functions, returning both hash keys. /// Set a pair of Rust index getter and setter functions, returning both hash keys.
/// This is a shorthand for `set_indexer_get_fn` and `set_indexer_set_fn`. /// This is a short-hand for `set_indexer_get_fn` and `set_indexer_set_fn`.
/// ///
/// If there are similar existing Rust functions, they are replaced. /// If there are similar existing Rust functions, they are replaced.
/// ///
@ -858,10 +870,10 @@ impl Module {
func: impl Fn(A, B, C, D) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>(); let a = cast_arg::<A>(&mut args[0]);
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[1]);
let c = mem::take(args[2]).cast::<C>(); let c = cast_arg::<C>(&mut args[2]);
let d = mem::take(args[3]).cast::<D>(); let d = cast_arg::<D>(&mut args[3]);
func(a, b, c, d).map(Dynamic::from) func(a, b, c, d).map(Dynamic::from)
}; };
@ -902,9 +914,9 @@ impl Module {
func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = cast_arg::<B>(&mut args[1]);
let c = mem::take(args[2]).cast::<C>(); let c = cast_arg::<C>(&mut args[2]);
let d = mem::take(args[3]).cast::<D>(); let d = cast_arg::<D>(&mut args[3]);
let a = &mut args[0].write_lock::<A>().unwrap(); let a = &mut args[0].write_lock::<A>().unwrap();
func(a, b, c, d).map(Dynamic::from) func(a, b, c, d).map(Dynamic::from)

View File

@ -185,7 +185,7 @@ macro_rules! gen_signed_functions {
macro_rules! reg_functions { macro_rules! reg_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $( ($mod_name:ident += $root:ident ; $($arg_type:ident),+ ) => { $(
$mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); combine_with_exported_module!($mod_name, "arithmetic", $root::$arg_type::functions);
)* } )* }
} }
@ -208,8 +208,8 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Basic arithmetic for floating-point // Basic arithmetic for floating-point
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
lib.combine_flatten(exported_module!(f32_functions)); combine_with_exported_module!(lib, "f32", f32_functions);
lib.combine_flatten(exported_module!(f64_functions)); combine_with_exported_module!(lib, "f64", f64_functions);
} }
}); });

View File

@ -58,7 +58,7 @@ macro_rules! reg_functions {
} }
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
lib.combine_flatten(exported_module!(array_functions)); combine_with_exported_module!(lib, "array", array_functions);
reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit);

View File

@ -5,7 +5,7 @@ use crate::plugin::*;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { def_package!(crate:EvalPackage:"Disable 'eval'.", lib, {
lib.combine_flatten(exported_module!(eval_override)); combine_with_exported_module!(lib, "eval", eval_override);
}); });
#[export_module] #[export_module]

View File

@ -3,7 +3,7 @@ use crate::fn_native::FnPtr;
use crate::plugin::*; use crate::plugin::*;
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
lib.combine_flatten(exported_module!(fn_ptr_functions)); combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
}); });
#[export_module] #[export_module]

View File

@ -45,7 +45,7 @@ macro_rules! gen_cmp_functions {
macro_rules! reg_functions { macro_rules! reg_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
$mod_name.combine_flatten(exported_module!($root::$arg_type::functions)); combine_with_exported_module!($mod_name, "logic", $root::$arg_type::functions);
)* } )* }
} }

View File

@ -9,7 +9,7 @@ use crate::plugin::*;
use crate::stdlib::vec::Vec; use crate::stdlib::vec::Vec;
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
lib.combine_flatten(exported_module!(map_functions)); combine_with_exported_module!(lib, "map", map_functions);
}); });
#[export_module] #[export_module]

View File

@ -48,10 +48,10 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
// Floating point functions // Floating point functions
lib.combine_flatten(exported_module!(float_functions)); combine_with_exported_module!(lib, "float", float_functions);
// Trig functions // Trig functions
lib.combine_flatten(exported_module!(trig_functions)); combine_with_exported_module!(lib, "trig", trig_functions);
reg_functions!(lib += basic_to_float::to_float(INT)); reg_functions!(lib += basic_to_float::to_float(INT));

View File

@ -57,7 +57,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
reg_functions!(lib += float; f32, f64); reg_functions!(lib += float; f32, f64);
lib.combine_flatten(exported_module!(string_functions)); combine_with_exported_module!(lib, "string", string_functions);
lib.set_raw_fn( lib.set_raw_fn(
"pad", "pad",

View File

@ -21,7 +21,7 @@ use instant::Instant;
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions // Register date/time functions
lib.combine_flatten(exported_module!(time_functions)); combine_with_exported_module!(lib, "time", time_functions);
}); });
#[export_module] #[export_module]

View File

@ -3,7 +3,7 @@
pub use crate::{ pub use crate::{
fn_native::CallableFunction, stdlib::any::TypeId, stdlib::boxed::Box, stdlib::format, fn_native::CallableFunction, stdlib::any::TypeId, stdlib::boxed::Box, stdlib::format,
stdlib::mem, stdlib::string::ToString, stdlib::vec as new_vec, stdlib::vec::Vec, Dynamic, stdlib::mem, stdlib::string::ToString, stdlib::vec as new_vec, stdlib::vec::Vec, Dynamic,
Engine, EvalAltResult, FnAccess, ImmutableString, Module, Position, RegisterResultFn, Engine, EvalAltResult, FnAccess, ImmutableString, Module, RegisterResultFn,
}; };
#[cfg(not(features = "no_module"))] #[cfg(not(features = "no_module"))]
@ -34,8 +34,7 @@ pub trait PluginFunction {
fn is_method_call(&self) -> bool; fn is_method_call(&self) -> bool;
fn is_varadic(&self) -> bool; fn is_varadic(&self) -> bool;
fn call(&self, args: &mut [&mut Dynamic], pos: Position) fn call(&self, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>>;
-> Result<Dynamic, Box<EvalAltResult>>;
fn clone_boxed(&self) -> Box<dyn PluginFunction>; fn clone_boxed(&self) -> Box<dyn PluginFunction>;

View File

@ -1,7 +1,7 @@
#![cfg(not(feature = "no_module"))] #![cfg(not(feature = "no_module"))]
use rhai::{ use rhai::{
module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, Module, ParseError, module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, ImmutableString,
ParseErrorType, Scope, INT, Module, ParseError, ParseErrorType, Scope, INT,
}; };
#[test] #[test]
@ -84,7 +84,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|target: &mut INT, a: INT, b: INT, c: f64| { |target: &mut INT, a: INT, b: INT, c: f64| {
*target = a + b + c as INT; *target = a + b + c as INT;
Ok(()) Ok(())
} },
); );
resolver.insert("hello", module); resolver.insert("hello", module);
@ -316,3 +316,41 @@ fn test_module_export() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_module_str() -> Result<(), Box<EvalAltResult>> {
fn test_fn(_input: ImmutableString) -> Result<INT, Box<EvalAltResult>> {
Ok(42)
}
fn test_fn2(_input: &str) -> Result<INT, Box<EvalAltResult>> {
Ok(42)
}
fn test_fn3(_input: String) -> Result<INT, Box<EvalAltResult>> {
Ok(42)
}
let mut engine = rhai::Engine::new();
let mut module = Module::new();
module.set_fn_1("test", test_fn);
module.set_fn_1("test2", test_fn2);
module.set_fn_1("test3", test_fn3);
let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new();
static_modules.insert("test", module);
engine.set_module_resolver(Some(static_modules));
assert_eq!(
engine.eval::<INT>(r#"import "test" as test; test::test("test");"#)?,
42
);
assert_eq!(
engine.eval::<INT>(r#"import "test" as test; test::test2("test");"#)?,
42
);
assert_eq!(
engine.eval::<INT>(r#"import "test" as test; test::test3("test");"#)?,
42
);
Ok(())
}

View File

@ -1,5 +1,6 @@
#![cfg(not(any(feature = "no_index", feature = "no_module")))] #![cfg(not(any(feature = "no_index", feature = "no_module")))]
use rhai::module_resolvers::StaticModuleResolver;
use rhai::plugin::*; use rhai::plugin::*;
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};
@ -10,6 +11,8 @@ mod test {
pub mod special_array_package { pub mod special_array_package {
use rhai::{Array, INT}; use rhai::{Array, INT};
pub const MYSTIC_NUMBER: INT = 42;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub mod feature { pub mod feature {
use rhai::{Array, Dynamic, EvalAltResult}; use rhai::{Array, Dynamic, EvalAltResult};
@ -21,6 +24,13 @@ mod test {
} }
} }
pub fn hash(_text: String) -> INT {
42
}
pub fn hash2(_text: &str) -> INT {
42
}
#[rhai_fn(name = "test", name = "hi")] #[rhai_fn(name = "test", name = "hi")]
#[inline(always)] #[inline(always)]
pub fn len(array: &mut Array, mul: INT) -> INT { pub fn len(array: &mut Array, mul: INT) -> INT {
@ -66,7 +76,7 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut m = Module::new(); let mut m = Module::new();
m.combine_flatten(exported_module!(test::special_array_package)); combine_with_exported_module!(&mut m, "test", test::special_array_package);
engine.load_package(m); engine.load_package(m);
reg_functions!(engine += greet::single(INT, bool, char)); reg_functions!(engine += greet::single(INT, bool, char));
@ -74,6 +84,8 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1);
assert_eq!(engine.eval::<INT>(r#"hash("hello")"#)?, 42);
assert_eq!(engine.eval::<INT>(r#"hash2("hello")"#)?, 42);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
@ -83,5 +95,14 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
"6 kitties" "6 kitties"
); );
let mut resolver = StaticModuleResolver::new();
resolver.insert("test", exported_module!(test::special_array_package));
engine.set_module_resolver(Some(resolver));
assert_eq!(
engine.eval::<INT>(r#"import "test" as test; test::MYSTIC_NUMBER"#)?,
42
);
Ok(()) Ok(())
} }