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.
* 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.

View File

@ -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: {} != {}",

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

View File

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

View File

@ -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
}
}
}
})

View File

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

View File

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

View File

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

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) |
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)) );
```

View File

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

View File

@ -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);
});
```

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;
`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!";

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.
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
---------

View File

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

View File

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

View File

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

View File

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

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.
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.

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
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.

View File

@ -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 '=='
```

View File

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

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 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 {

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
[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);

View File

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

View File

@ -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));
});
```

View File

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

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
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.

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
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.

View File

@ -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),

View File

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

View File

@ -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();

View File

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

View File

@ -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>();

View File

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

View File

@ -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);
}
});

View File

@ -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);

View File

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

View File

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

View File

@ -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);
)* }
}

View File

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

View File

@ -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));

View File

@ -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",

View File

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

View File

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

View File

@ -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(())
}

View File

@ -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(())
}