Merge pull request #238 from schungx/master
Support &str and String parameters in modules.
This commit is contained in:
commit
1d77e3e174
@ -9,6 +9,8 @@ Bug fixes
|
||||
|
||||
* `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`.
|
||||
* Module API's now properly handle `&str` and `String` parameters.
|
||||
* Indexers are available under `no_object`.
|
||||
|
||||
New features
|
||||
------------
|
||||
@ -72,7 +74,7 @@ New features
|
||||
* Currying of function pointers is supported via the new `curry` keyword.
|
||||
* 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.
|
||||
* `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.
|
||||
* `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
|
||||
------------
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
|
@ -84,71 +84,91 @@ impl ExportedParams for ExportedFnParams {
|
||||
let mut skip = false;
|
||||
let mut special = FnSpecialAccess::None;
|
||||
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) {
|
||||
("get", None) | ("set", None) | ("name", None) => {
|
||||
return Err(syn::Error::new(key.span(), "requires value"))
|
||||
},
|
||||
}
|
||||
("name", Some(s)) if &s.value() == FN_IDX_GET => {
|
||||
return Err(syn::Error::new(item_span,
|
||||
"use attribute 'index_get' instead"))
|
||||
},
|
||||
return Err(syn::Error::new(
|
||||
item_span,
|
||||
"use attribute 'index_get' instead",
|
||||
))
|
||||
}
|
||||
("name", Some(s)) if &s.value() == FN_IDX_SET => {
|
||||
return Err(syn::Error::new(item_span,
|
||||
"use attribute 'index_set' instead"))
|
||||
},
|
||||
return Err(syn::Error::new(
|
||||
item_span,
|
||||
"use attribute 'index_set' instead",
|
||||
))
|
||||
}
|
||||
("name", Some(s)) if s.value().starts_with("get$") => {
|
||||
return Err(syn::Error::new(item_span,
|
||||
format!("use attribute 'getter = \"{}\"' instead",
|
||||
&s.value()["get$".len()..])))
|
||||
},
|
||||
return Err(syn::Error::new(
|
||||
item_span,
|
||||
format!(
|
||||
"use attribute 'getter = \"{}\"' instead",
|
||||
&s.value()["get$".len()..]
|
||||
),
|
||||
))
|
||||
}
|
||||
("name", Some(s)) if s.value().starts_with("set$") => {
|
||||
return Err(syn::Error::new(item_span,
|
||||
format!("use attribute 'setter = \"{}\"' instead",
|
||||
&s.value()["set$".len()..])))
|
||||
},
|
||||
return Err(syn::Error::new(
|
||||
item_span,
|
||||
format!(
|
||||
"use attribute 'setter = \"{}\"' instead",
|
||||
&s.value()["set$".len()..]
|
||||
),
|
||||
))
|
||||
}
|
||||
("name", Some(s)) if s.value().contains('$') => {
|
||||
return Err(syn::Error::new(s.span(),
|
||||
"Rhai function names may not contain dollar sign"))
|
||||
},
|
||||
return Err(syn::Error::new(
|
||||
s.span(),
|
||||
"Rhai function names may not contain dollar sign",
|
||||
))
|
||||
}
|
||||
("name", Some(s)) if s.value().contains('.') => {
|
||||
return Err(syn::Error::new(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"))
|
||||
return Err(syn::Error::new(
|
||||
s.span(),
|
||||
"Rhai function names may not contain dot",
|
||||
))
|
||||
}
|
||||
},
|
||||
("get", Some(s)) => special = match special {
|
||||
FnSpecialAccess::None =>
|
||||
FnSpecialAccess::Property(Property::Get(syn::Ident::new(&s.value(),
|
||||
s.span()))),
|
||||
_ => {
|
||||
return Err(syn::Error::new(item_span.span(), "conflicting getter"))
|
||||
("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")),
|
||||
}
|
||||
},
|
||||
("index_get", None) => special = match special {
|
||||
FnSpecialAccess::None =>
|
||||
FnSpecialAccess::Index(Index::Get),
|
||||
}
|
||||
("get", Some(s)) => {
|
||||
special = match special {
|
||||
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"))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
("index_set", None) => special = match special {
|
||||
FnSpecialAccess::None =>
|
||||
FnSpecialAccess::Index(Index::Set),
|
||||
("index_set", None) => {
|
||||
special = match special {
|
||||
FnSpecialAccess::None => FnSpecialAccess::Index(Index::Set),
|
||||
_ => {
|
||||
return Err(syn::Error::new(item_span.span(), "conflicting index_set"))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
("return_raw", None) => return_raw = true,
|
||||
("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => {
|
||||
return Err(syn::Error::new(s.span(), "extraneous value"))
|
||||
@ -327,26 +347,40 @@ impl ExportedFn {
|
||||
}
|
||||
|
||||
pub(crate) fn exported_names(&self) -> Vec<syn::LitStr> {
|
||||
let mut literals = self.params.name.as_ref()
|
||||
.map(|v| v.iter()
|
||||
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site())).collect())
|
||||
let mut literals = self
|
||||
.params
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|v| {
|
||||
v.iter()
|
||||
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
|
||||
match self.params.special {
|
||||
FnSpecialAccess::None => {},
|
||||
FnSpecialAccess::Property(Property::Get(ref g)) =>
|
||||
literals.push(syn::LitStr::new(&format!("get${}", g.to_string()), g.span())),
|
||||
FnSpecialAccess::Property(Property::Set(ref s)) =>
|
||||
literals.push(syn::LitStr::new(&format!("set${}", s.to_string()), s.span())),
|
||||
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())),
|
||||
FnSpecialAccess::None => {}
|
||||
FnSpecialAccess::Property(Property::Get(ref g)) => literals.push(syn::LitStr::new(
|
||||
&format!("get${}", g.to_string()),
|
||||
g.span(),
|
||||
)),
|
||||
FnSpecialAccess::Property(Property::Set(ref s)) => literals.push(syn::LitStr::new(
|
||||
&format!("set${}", s.to_string()),
|
||||
s.span(),
|
||||
)),
|
||||
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() {
|
||||
literals.push(syn::LitStr::new(&self.signature.ident.to_string(),
|
||||
self.signature.ident.span()));
|
||||
literals.push(syn::LitStr::new(
|
||||
&self.signature.ident.to_string(),
|
||||
self.signature.ident.span(),
|
||||
));
|
||||
}
|
||||
|
||||
literals
|
||||
@ -394,53 +428,61 @@ impl ExportedFn {
|
||||
|
||||
match params.special {
|
||||
// 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(
|
||||
self.signature.span(),
|
||||
"property getter requires exactly 1 argument",
|
||||
)),
|
||||
))
|
||||
}
|
||||
// 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(
|
||||
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.
|
||||
FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 =>
|
||||
FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => {
|
||||
return Err(syn::Error::new(
|
||||
self.signature.span(),
|
||||
"property setter requires exactly 2 arguments",
|
||||
)),
|
||||
))
|
||||
}
|
||||
// 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(
|
||||
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.
|
||||
FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 =>
|
||||
FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => {
|
||||
return Err(syn::Error::new(
|
||||
self.signature.span(),
|
||||
"index getter requires exactly 2 arguments",
|
||||
)),
|
||||
))
|
||||
}
|
||||
// 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(
|
||||
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.
|
||||
FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 =>
|
||||
FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => {
|
||||
return Err(syn::Error::new(
|
||||
self.signature.span(),
|
||||
"index setter requires exactly 3 arguments",
|
||||
)),
|
||||
))
|
||||
}
|
||||
// 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(
|
||||
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.
|
||||
//
|
||||
// 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 string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap();
|
||||
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 is_str_ref;
|
||||
let is_string;
|
||||
let is_ref;
|
||||
match arg {
|
||||
syn::FnArg::Typed(pattern) => {
|
||||
let arg_type: &syn::Type = pattern.ty.as_ref();
|
||||
@ -611,15 +655,22 @@ impl ExportedFn {
|
||||
..
|
||||
}) => match elem.as_ref() {
|
||||
&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()=>
|
||||
mem::take(args[#i])
|
||||
.clone().cast::<ImmutableString>())
|
||||
mem::take(args[#i]).take_immutable_string().unwrap())
|
||||
}
|
||||
_ => 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()=>
|
||||
mem::take(args[#i]).clone().cast::<#arg_type>())
|
||||
}
|
||||
@ -631,7 +682,7 @@ impl ExportedFn {
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
if !is_str_ref {
|
||||
if !is_string {
|
||||
input_type_exprs.push(
|
||||
syn::parse2::<syn::Expr>(quote_spanned!(
|
||||
arg_type.span()=> TypeId::of::<#arg_type>()
|
||||
@ -649,7 +700,7 @@ impl ExportedFn {
|
||||
}
|
||||
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());
|
||||
} else {
|
||||
unpack_exprs.push(syn::parse2::<syn::Expr>(quote! { &#var }).unwrap());
|
||||
@ -685,7 +736,7 @@ impl ExportedFn {
|
||||
quote! {
|
||||
impl PluginFunction for #type_name {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(args.len(), #arg_count,
|
||||
"wrong arg count: {} != {}",
|
||||
|
@ -152,6 +152,19 @@ pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::Toke
|
||||
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]
|
||||
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)
|
||||
|
@ -140,12 +140,13 @@ impl Parse for Module {
|
||||
ref expr,
|
||||
ident,
|
||||
attrs,
|
||||
ty,
|
||||
..
|
||||
}) => {
|
||||
// #[cfg] attributes are not allowed on const declarations
|
||||
crate::attrs::deny_cfg_attr(&attrs)?;
|
||||
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 {
|
||||
pub fn attrs(&self) -> Option<&Vec<syn::Attribute>> {
|
||||
self.mod_all.as_ref().map(|m| &m.attrs)
|
||||
|
@ -6,7 +6,7 @@ use crate::attrs::ExportScope;
|
||||
use crate::function::ExportedFn;
|
||||
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(
|
||||
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_const_stmts: Vec<syn::Stmt> = 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 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_ref = syn::Ident::new(&const_name, proc_macro2::Span::call_site());
|
||||
set_const_stmts.push(
|
||||
syn::parse2::<syn::Stmt>(quote! {
|
||||
m.set_var(#const_literal, #const_expr);
|
||||
m.set_var(#const_literal, #const_ref);
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
@ -54,6 +57,14 @@ pub(crate) fn generate_body(
|
||||
})
|
||||
.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 ">>"
|
||||
@ -87,6 +98,11 @@ pub(crate) fn generate_body(
|
||||
}
|
||||
_ => 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 {
|
||||
mutability: Some(_),
|
||||
ref elem,
|
||||
@ -129,13 +145,22 @@ pub(crate) fn generate_body(
|
||||
pub mod generate_info {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
#[allow(unused_mut)]
|
||||
|
||||
pub fn rhai_module_generate() -> Module {
|
||||
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_const_stmts)*
|
||||
|
||||
if flatten {
|
||||
#(#set_flattened_mod_blocks)*
|
||||
} else {
|
||||
#(#add_mod_blocks)*
|
||||
m
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -237,21 +237,27 @@ mod generate_tests {
|
||||
let expected = expected.to_string();
|
||||
if &actual != &expected {
|
||||
let mut counter = 0;
|
||||
let iter = actual
|
||||
.chars()
|
||||
.zip(expected.chars())
|
||||
.inspect(|_| counter += 1)
|
||||
.skip_while(|(a, e)| *a == *e);
|
||||
let (actual_diff, expected_diff) = {
|
||||
let _iter = actual.chars().zip(expected.chars()).skip_while(|(a, e)| {
|
||||
if *a == *e {
|
||||
counter += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let (_actual_diff, _expected_diff) = {
|
||||
let mut actual_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);
|
||||
expected_diff.push(e);
|
||||
}
|
||||
(actual_diff, expected_diff)
|
||||
};
|
||||
eprintln!("actual != expected, diverge at char {}", counter);
|
||||
// eprintln!(" actual: {}", _actual_diff);
|
||||
// eprintln!("expected: {}", _expected_diff);
|
||||
// assert!(false);
|
||||
}
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@ -269,7 +275,7 @@ mod generate_tests {
|
||||
struct Token();
|
||||
impl PluginFunction for Token {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(args.len(), 0usize,
|
||||
"wrong arg count: {} != {}", args.len(), 0usize);
|
||||
@ -313,7 +319,7 @@ mod generate_tests {
|
||||
struct Token();
|
||||
impl PluginFunction for Token {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(args.len(), 1usize,
|
||||
"wrong arg count: {} != {}", args.len(), 1usize);
|
||||
@ -354,7 +360,7 @@ mod generate_tests {
|
||||
let expected_tokens = quote! {
|
||||
impl PluginFunction for MyType {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(args.len(), 1usize,
|
||||
"wrong arg count: {} != {}", args.len(), 1usize);
|
||||
@ -388,7 +394,7 @@ mod generate_tests {
|
||||
struct Token();
|
||||
impl PluginFunction for Token {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(args.len(), 2usize,
|
||||
"wrong arg count: {} != {}", args.len(), 2usize);
|
||||
@ -435,7 +441,7 @@ mod generate_tests {
|
||||
struct Token();
|
||||
impl PluginFunction for Token {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(args.len(), 2usize,
|
||||
"wrong arg count: {} != {}", args.len(), 2usize);
|
||||
@ -483,11 +489,11 @@ mod generate_tests {
|
||||
struct Token();
|
||||
impl PluginFunction for Token {
|
||||
fn call(&self,
|
||||
args: &mut [&mut Dynamic], pos: Position
|
||||
args: &mut [&mut Dynamic]
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
debug_assert_eq!(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)))
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -52,7 +52,7 @@ The Rhai Scripting Language
|
||||
1. [Comments](language/comments.md)
|
||||
2. [Values and Types](language/values-and-types.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)
|
||||
1. [Operators](language/num-op.md)
|
||||
2. [Functions](language/num-fn.md)
|
||||
|
@ -69,12 +69,12 @@ For example, the following is a SQL-like syntax for some obscure DSL operation:
|
||||
```rust
|
||||
let table = [..., ..., ..., ...];
|
||||
|
||||
// Syntax = calculate $ident$ $ident$ from $expr$ -> $ident$ : $expr$
|
||||
let total = calculate sum price from table -> row : row.weight > 50;
|
||||
// Syntax = calculate $ident$ ( $expr$ -> $ident$ ) => $ident$ : $expr$
|
||||
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:
|
||||
// Syntax = SELECT $ident$ ( $ident$ ) FROM $expr$ AS $ident$ WHERE $expr$
|
||||
let total = SELECT sum(price) FROM table AS row WHERE row.weight > 50;
|
||||
// Syntax = SELECT $ident$ ( $ident$ ) AS $ident$ FROM $expr$ WHERE $expr$
|
||||
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
|
||||
@ -84,9 +84,9 @@ For its evaluation, the callback function will receive the following list of inp
|
||||
|
||||
* `inputs[0] = "sum"` - math operator
|
||||
* `inputs[1] = "price"` - field name
|
||||
* `inputs[2] = Expression(table)` - data source
|
||||
* `inputs[3] = "row"` - loop variable name
|
||||
* `inputs[2] = "row"` - loop variable name
|
||||
* `inputs[3] = Expression(table)` - data source
|
||||
* `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.
|
||||
|
@ -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) |
|
||||
|
||||
|
||||
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
|
||||
--------
|
||||
|
||||
@ -123,9 +132,3 @@ y.clear(); // empty the array
|
||||
|
||||
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)) );
|
||||
```
|
||||
|
@ -6,8 +6,8 @@ Dynamic Values
|
||||
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,
|
||||
it is usually used to perform type-specific actions based on the actual value's type.
|
||||
|
@ -65,17 +65,15 @@ disable `eval` by overloading it, probably with something that throws.
|
||||
```rust
|
||||
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:
|
||||
|
||||
```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())
|
||||
}
|
||||
|
||||
engine.register_result_fn("eval", alt_eval);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
@ -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;
|
||||
`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
|
||||
// Iterate through string, yielding characters
|
||||
let s = "hello, world!";
|
||||
|
@ -20,14 +20,15 @@ Global Variables
|
||||
|
||||
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**).
|
||||
|
||||
```rust
|
||||
// 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
|
||||
|
||||
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
|
||||
---------
|
||||
|
@ -10,7 +10,7 @@ Unary Operators
|
||||
|
||||
| Operator | Description |
|
||||
| -------- | ----------- |
|
||||
| `+` | Plus |
|
||||
| `+` | Positive |
|
||||
| `-` | Negative |
|
||||
|
||||
```rust
|
||||
|
@ -1,5 +1,5 @@
|
||||
`timestamp`'s
|
||||
=============
|
||||
`timestamp`
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
`type_of`
|
||||
=========
|
||||
`type_of()`
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
@ -137,7 +137,7 @@ For example, the above configuration example may be expressed by this custom syn
|
||||
id "hello";
|
||||
|
||||
// Add to list
|
||||
list +"foo"
|
||||
list + "foo";
|
||||
|
||||
// Add to map
|
||||
map "bar" => true;
|
||||
|
@ -6,8 +6,8 @@ Object-Oriented Programming (OOP)
|
||||
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).
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
Use Anonymous Functions to Define Methods
|
||||
----------------------------------------
|
||||
Use Closures to Define Methods
|
||||
-----------------------------
|
||||
|
||||
[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.
|
||||
|
@ -5,9 +5,11 @@ Export a Rust Module to Rhai
|
||||
|
||||
|
||||
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
|
||||
is exactly what would need to be written by hand to achieve the same goal, and is custom fit
|
||||
to each exported item.
|
||||
code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions, constants
|
||||
and sub-modules.
|
||||
|
||||
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
|
||||
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!`
|
||||
----------------------------------------
|
||||
|
||||
Apply `#[export_module]` onto a Rust module to convert all `pub` functions into Rhai plugin
|
||||
functions.
|
||||
Apply `#[export_module]` onto a Rust module to register automatically construct a Rhai [module],
|
||||
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
|
||||
use rhai::plugins::*; // a "prelude" import for macros
|
||||
|
||||
#[export_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'.
|
||||
pub fn greet(name: &str) -> String {
|
||||
format!("hello, {}!", name)
|
||||
@ -36,14 +45,23 @@ mod my_module {
|
||||
pub fn increment(num: &mut i64) {
|
||||
*num += 1;
|
||||
}
|
||||
// This function is NOT registered.
|
||||
// This function is not 'pub', so NOT registered.
|
||||
fn mystic_number() -> i64 {
|
||||
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
|
||||
fn main() {
|
||||
@ -52,7 +70,7 @@ fn main() {
|
||||
// The macro call creates the Rhai 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);
|
||||
}
|
||||
```
|
||||
@ -74,9 +92,12 @@ increment(x);
|
||||
x == 43;
|
||||
```
|
||||
|
||||
Registering this as a custom package is almost the same, except that a module resolver must
|
||||
point to the module, rather than being loaded directly. See the [module] section for more
|
||||
information.
|
||||
Notice that, when using a [module] as a [package], only functions registered at the _top level_
|
||||
can be accessed. Variables as well as sub-modules are ignored.
|
||||
|
||||
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
|
||||
@ -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 [`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.
|
||||
|
||||
|
@ -38,12 +38,14 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
println!("result: {}", result.field); // prints 42
|
||||
```
|
||||
|
||||
|
||||
Register a Custom Type
|
||||
---------------------
|
||||
|
||||
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
|
||||
#[derive(Clone)]
|
||||
@ -66,7 +68,8 @@ let mut engine = Engine::new();
|
||||
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
|
||||
@ -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.**
|
||||
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
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()")?;
|
||||
```
|
||||
|
||||
|
||||
`type_of()` a Custom Type
|
||||
-------------------------
|
||||
|
||||
@ -150,3 +156,33 @@ engine
|
||||
let x = new_ts();
|
||||
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 '=='
|
||||
```
|
||||
|
@ -3,8 +3,8 @@ Disable Custom Types
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`,
|
||||
`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`].
|
||||
The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_get_result`,
|
||||
`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
|
||||
not available under [`no_index`].
|
||||
The indexers API `register_indexer_get`, `register_indexer_get_result`, `register_indexer_set`,
|
||||
`register_indexer_set_result`, and `register_indexer_get_set` are also not available under [`no_index`].
|
||||
|
@ -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 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
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
|
@ -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
|
||||
[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
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
@ -40,7 +52,7 @@ let mut engine = Engine::new();
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.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_set(TestStruct::set_field);
|
||||
|
||||
|
@ -7,7 +7,8 @@ Create a Module from Rust
|
||||
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
|
||||
@ -21,7 +22,8 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai
|
||||
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
|
||||
a custom module.
|
||||
|
@ -58,13 +58,16 @@ def_package!(rhai:MyPackage:"My own personal super package", 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
|
||||
`rhai::def_package!` which simply merges in all the functions defined within a [plugin module].
|
||||
By far the easiest way to create a custom module is to call `rhai::plugins::combine_with_exported_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.
|
||||
|
||||
`rhai::plugins::exported_module!` generates a module from the [plugins][plugin module] definition,
|
||||
and `Module::merge_flatten` consumes its, adding all its registered functions into the package itself.
|
||||
Because of the specific requirements of a [package], all sub-modules are _flattened_
|
||||
(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
|
||||
// Import necessary types and traits.
|
||||
@ -78,12 +81,22 @@ use rhai::plugin::*;
|
||||
// Define plugin module.
|
||||
#[export_module]
|
||||
mod my_module {
|
||||
pub const MY_NUMBER: i64 = 42;
|
||||
|
||||
pub fn greet(name: &str) -> String {
|
||||
format!("hello, {}!", name)
|
||||
}
|
||||
pub fn get_num() -> i64 {
|
||||
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'.
|
||||
@ -94,7 +107,20 @@ def_package!(rhai:MyPackage:"My own personal super package", module, {
|
||||
BasicArrayPackage::init(module);
|
||||
BasicMapPackage::init(module);
|
||||
|
||||
// Merge the plugin module into the custom package.
|
||||
module.merge_flatten(exported_module!(my_module));
|
||||
// Merge all registered functions and constants from the plugin module into the custom package.
|
||||
//
|
||||
// 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));
|
||||
});
|
||||
```
|
||||
|
@ -29,7 +29,7 @@ engine.load_package(package.get()); // load the package manually. 'get' returns
|
||||
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
|
||||
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
|
||||
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
|
||||
--------------------------
|
||||
|
@ -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
|
||||
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
|
||||
----------------------
|
||||
|
||||
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
|
||||
requiring more CPU cycles to complete.
|
||||
On 64-bit targets this may not gain much, but on certain 32-bit targets this improves performance
|
||||
due to 64-bit arithmetic requiring more CPU cycles to complete.
|
||||
|
||||
|
||||
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
|
||||
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
|
||||
Turning on [`no_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] data type only 8 bytes long.
|
||||
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`
|
||||
@ -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).
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
or write lock before reading it.
|
||||
This is because every data access must be checked whether it is a shared value and, if so, take a read
|
||||
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.
|
||||
|
@ -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
|
||||
more control over what a script can (or cannot) do.
|
||||
|
||||
| Feature | 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! |
|
||||
| `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`. |
|
||||
| `no_optimize` | Disable [script optimization]. |
|
||||
| `no_float` | 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_i64` | 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_object` | Disable support for [custom types] and [object maps]. |
|
||||
| `no_function` | Disable script-defined [functions]. |
|
||||
| `no_module` | 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_std` | 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. |
|
||||
| `internals` | 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. |
|
||||
| Feature | Additive? | Description |
|
||||
| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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` | 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` | No | Disable [script optimization]. |
|
||||
| `no_float` | No | Disable floating-point numbers and math. |
|
||||
| `only_i32` | No | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | No | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_index` | No | Disable [arrays] and indexing features. |
|
||||
| `no_object` | No | Disable support for [custom types] and [object maps]. |
|
||||
| `no_function` | No | Disable script-defined [functions]. |
|
||||
| `no_module` | No | Disable loading external [modules]. |
|
||||
| `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` | No | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `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` | 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` | No | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. |
|
||||
|
||||
|
||||
Example
|
||||
@ -57,7 +57,7 @@ This configuration is perfect for an expression parser in a 32-bit embedded syst
|
||||
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.
|
||||
|
||||
|
@ -1136,6 +1136,7 @@ impl Dynamic {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[inline(always)]
|
||||
pub fn take_string(self) -> Result<String, &'static str> {
|
||||
@ -1145,7 +1146,8 @@ impl Dynamic {
|
||||
|
||||
/// Convert the `Dynamic` into `ImmutableString` and return it.
|
||||
/// 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 {
|
||||
Union::Str(s) => Ok(s),
|
||||
Union::FnPtr(f) => Ok(f.take_data().0),
|
||||
|
27
src/api.rs
27
src/api.rs
@ -12,17 +12,18 @@ use crate::scope::Scope;
|
||||
use crate::token::{lex, Position};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::{
|
||||
engine::{make_getter, make_setter, Map},
|
||||
error::ParseErrorType,
|
||||
fn_register::{RegisterFn, RegisterResultFn},
|
||||
token::Token,
|
||||
};
|
||||
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::{
|
||||
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)
|
||||
}
|
||||
|
||||
/// Shorthand for registering both getter and setter functions
|
||||
/// Short-hand for registering both getter and setter functions
|
||||
/// of a registered type with the `Engine`.
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
///
|
||||
@ -452,6 +453,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register the custom type.
|
||||
/// # #[cfg(not(feature = "no_object"))]
|
||||
/// engine.register_type::<TestStruct>();
|
||||
///
|
||||
/// engine.register_fn("new_ts", TestStruct::new);
|
||||
@ -463,7 +465,6 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn register_indexer_get<T, X, U>(
|
||||
&mut self,
|
||||
@ -477,7 +478,7 @@ impl Engine {
|
||||
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>>`.
|
||||
///
|
||||
/// The function signature must start with `&mut self` and not `&self`.
|
||||
@ -505,6 +506,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register the custom type.
|
||||
/// # #[cfg(not(feature = "no_object"))]
|
||||
/// engine.register_type::<TestStruct>();
|
||||
///
|
||||
/// engine.register_fn("new_ts", TestStruct::new);
|
||||
@ -516,7 +518,6 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn register_indexer_get_result<T, X>(
|
||||
&mut self,
|
||||
@ -529,7 +530,7 @@ impl Engine {
|
||||
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
|
||||
///
|
||||
@ -550,6 +551,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register the custom type.
|
||||
/// # #[cfg(not(feature = "no_object"))]
|
||||
/// engine.register_type::<TestStruct>();
|
||||
///
|
||||
/// engine.register_fn("new_ts", TestStruct::new);
|
||||
@ -564,7 +566,6 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn register_indexer_set<T, X, U>(
|
||||
&mut self,
|
||||
@ -578,7 +579,7 @@ impl Engine {
|
||||
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>>`.
|
||||
///
|
||||
/// # Example
|
||||
@ -603,6 +604,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register the custom type.
|
||||
/// # #[cfg(not(feature = "no_object"))]
|
||||
/// engine.register_type::<TestStruct>();
|
||||
///
|
||||
/// engine.register_fn("new_ts", TestStruct::new);
|
||||
@ -617,7 +619,6 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn register_indexer_set_result<T, X, U>(
|
||||
&mut self,
|
||||
@ -631,7 +632,7 @@ impl Engine {
|
||||
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
|
||||
///
|
||||
@ -653,6 +654,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register the custom type.
|
||||
/// # #[cfg(not(feature = "no_object"))]
|
||||
/// engine.register_type::<TestStruct>();
|
||||
///
|
||||
/// engine.register_fn("new_ts", TestStruct::new);
|
||||
@ -664,7 +666,6 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn register_indexer_get_set<T, X, U>(
|
||||
&mut self,
|
||||
|
@ -1134,8 +1134,7 @@ impl Engine {
|
||||
) -> Result<Target<'a>, Box<EvalAltResult>> {
|
||||
self.inc_operations(state)?;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
let is_ref = target.is_ref();
|
||||
|
||||
let val = target.as_mut();
|
||||
@ -1200,7 +1199,6 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
_ if _indexers => {
|
||||
let type_name = val.type_name();
|
||||
|
@ -232,7 +232,7 @@ impl Engine {
|
||||
|
||||
// Run external function
|
||||
let result = if func.is_plugin_fn() {
|
||||
func.get_plugin_fn().call(args, Position::none())
|
||||
func.get_plugin_fn().call(args)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
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() => {
|
||||
if !f.is_method() {
|
||||
// Clone first argument
|
||||
|
@ -46,7 +46,7 @@ pub trait RegisterPlugin<PL: crate::plugin::Plugin> {
|
||||
/// fn is_method_call(&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 y1: NUMBER = std::mem::take(args[1]).clone().cast::<NUMBER>();
|
||||
/// let x2: NUMBER = std::mem::take(args[2]).clone().cast::<NUMBER>();
|
||||
|
@ -4,10 +4,11 @@ use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::Engine;
|
||||
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::result::EvalAltResult;
|
||||
use crate::token::{Position, Token};
|
||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||
use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::fn_native::Shared;
|
||||
@ -32,7 +33,6 @@ use crate::stdlib::{
|
||||
collections::HashMap,
|
||||
fmt, format,
|
||||
iter::empty,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
string::{String, ToString},
|
||||
@ -396,9 +396,21 @@ impl Module {
|
||||
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
|
||||
.insert(hash_fn, (name, access, params, func.into()));
|
||||
@ -518,7 +530,7 @@ impl Module {
|
||||
func: impl Fn(A) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
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>()];
|
||||
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,
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let a = cast_arg::<A>(&mut args[0]);
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
@ -623,7 +635,7 @@ impl Module {
|
||||
func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
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();
|
||||
|
||||
func(a, b).map(Dynamic::from)
|
||||
@ -709,9 +721,9 @@ impl Module {
|
||||
func: impl Fn(A, B, C) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let a = cast_arg::<A>(&mut args[0]);
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
let c = cast_arg::<C>(&mut args[2]);
|
||||
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
@ -746,8 +758,8 @@ impl Module {
|
||||
func: impl Fn(&mut A, B, C) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let b = cast_arg::<B>(&mut args[2]);
|
||||
let c = cast_arg::<C>(&mut args[3]);
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
@ -780,8 +792,8 @@ impl Module {
|
||||
func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
let c = cast_arg::<C>(&mut args[2]);
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
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.
|
||||
/// 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.
|
||||
///
|
||||
@ -858,10 +870,10 @@ impl Module {
|
||||
func: impl Fn(A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let d = mem::take(args[3]).cast::<D>();
|
||||
let a = cast_arg::<A>(&mut args[0]);
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
let c = cast_arg::<C>(&mut args[2]);
|
||||
let d = cast_arg::<D>(&mut args[3]);
|
||||
|
||||
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,
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let d = mem::take(args[3]).cast::<D>();
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
let c = cast_arg::<C>(&mut args[2]);
|
||||
let d = cast_arg::<D>(&mut args[3]);
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
func(a, b, c, d).map(Dynamic::from)
|
||||
|
@ -185,7 +185,7 @@ macro_rules! gen_signed_functions {
|
||||
|
||||
macro_rules! reg_functions {
|
||||
($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
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
lib.combine_flatten(exported_module!(f32_functions));
|
||||
lib.combine_flatten(exported_module!(f64_functions));
|
||||
combine_with_exported_module!(lib, "f32", f32_functions);
|
||||
combine_with_exported_module!(lib, "f64", f64_functions);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,7 @@ macro_rules! reg_functions {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -5,7 +5,7 @@ use crate::plugin::*;
|
||||
use crate::result::EvalAltResult;
|
||||
|
||||
def_package!(crate:EvalPackage:"Disable 'eval'.", lib, {
|
||||
lib.combine_flatten(exported_module!(eval_override));
|
||||
combine_with_exported_module!(lib, "eval", eval_override);
|
||||
});
|
||||
|
||||
#[export_module]
|
||||
|
@ -3,7 +3,7 @@ use crate::fn_native::FnPtr;
|
||||
use crate::plugin::*;
|
||||
|
||||
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]
|
||||
|
@ -45,7 +45,7 @@ macro_rules! gen_cmp_functions {
|
||||
|
||||
macro_rules! reg_functions {
|
||||
($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);
|
||||
)* }
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ use crate::plugin::*;
|
||||
use crate::stdlib::vec::Vec;
|
||||
|
||||
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]
|
||||
|
@ -48,10 +48,10 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
// Floating point functions
|
||||
lib.combine_flatten(exported_module!(float_functions));
|
||||
combine_with_exported_module!(lib, "float", float_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));
|
||||
|
||||
|
@ -57,7 +57,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
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(
|
||||
"pad",
|
||||
|
@ -21,7 +21,7 @@ use instant::Instant;
|
||||
|
||||
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
// Register date/time functions
|
||||
lib.combine_flatten(exported_module!(time_functions));
|
||||
combine_with_exported_module!(lib, "time", time_functions);
|
||||
});
|
||||
|
||||
#[export_module]
|
||||
|
@ -3,7 +3,7 @@
|
||||
pub use crate::{
|
||||
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,
|
||||
Engine, EvalAltResult, FnAccess, ImmutableString, Module, Position, RegisterResultFn,
|
||||
Engine, EvalAltResult, FnAccess, ImmutableString, Module, RegisterResultFn,
|
||||
};
|
||||
|
||||
#[cfg(not(features = "no_module"))]
|
||||
@ -34,8 +34,7 @@ pub trait PluginFunction {
|
||||
fn is_method_call(&self) -> bool;
|
||||
fn is_varadic(&self) -> bool;
|
||||
|
||||
fn call(&self, args: &mut [&mut Dynamic], pos: Position)
|
||||
-> Result<Dynamic, Box<EvalAltResult>>;
|
||||
fn call(&self, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn PluginFunction>;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![cfg(not(feature = "no_module"))]
|
||||
use rhai::{
|
||||
module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, Module, ParseError,
|
||||
ParseErrorType, Scope, INT,
|
||||
module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, ImmutableString,
|
||||
Module, ParseError, ParseErrorType, Scope, INT,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -84,7 +84,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
|target: &mut INT, a: INT, b: INT, c: f64| {
|
||||
*target = a + b + c as INT;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
resolver.insert("hello", module);
|
||||
@ -316,3 +316,41 @@ fn test_module_export() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![cfg(not(any(feature = "no_index", feature = "no_module")))]
|
||||
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
use rhai::plugin::*;
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
@ -10,6 +11,8 @@ mod test {
|
||||
pub mod special_array_package {
|
||||
use rhai::{Array, INT};
|
||||
|
||||
pub const MYSTIC_NUMBER: INT = 42;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub mod feature {
|
||||
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")]
|
||||
#[inline(always)]
|
||||
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 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);
|
||||
|
||||
reg_functions!(engine += greet::single(INT, bool, char));
|
||||
@ -74,6 +84,8 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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]; hi(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"
|
||||
);
|
||||
|
||||
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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user