Merge pull request #248 from schungx/master

New FileModuleResolver.
This commit is contained in:
Stephen Chung 2020-09-28 22:32:11 +08:00 committed by GitHub
commit b1cbf0ec0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 1935 additions and 1146 deletions

View File

@ -23,22 +23,23 @@ Standard features
----------------- -----------------
* Easy-to-use language similar to JavaScript+Rust with dynamic typing. * Easy-to-use language similar to JavaScript+Rust with dynamic typing.
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html). * Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html).
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html). * Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* Relatively little `unsafe` code (yes there are some for performance reasons). * Relatively little `unsafe` code (yes there are some for performance reasons).
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* [Function overloading](https://schungx.github.io/rhai/language/overload.html). * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). * Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html) and [closures](https://schungx.github.io/rhai/language/fn-closure.html). * [Function overloading](https://schungx.github.io/rhai/language/overload.html) and [operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html).
* [Closures](https://schungx.github.io/rhai/language/fn-closure.html) (anonymous functions) that can capture shared values.
* Some syntactic support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
Protection against attacks Protection against attacks
-------------------------- --------------------------
@ -69,13 +70,11 @@ Scripts can be evaluated directly from the editor.
License License
------- -------
Licensed under either: Licensed under either of the following, at your choice:
* [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or * [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or
* [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt) * [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt)
at your choice.
Unless explicitly stated otherwise, any contribution intentionally submitted Unless explicitly stated otherwise, any contribution intentionally submitted
for inclusion in this crate, as defined in the Apache-2.0 license, shall for inclusion in this crate, as defined in the Apache-2.0 license, shall
be dual-licensed as above, without any additional terms or conditions. be dual-licensed as above, without any additional terms or conditions.

View File

@ -4,6 +4,9 @@ Rhai Release Notes
Version 0.19.0 Version 0.19.0
============== ==============
The major new feature for this version is _Plugins_ support, powered by procedural macros.
Plugins make it extremely easy to develop and register Rust functions with an `Engine`.
Bug fixes Bug fixes
--------- ---------
@ -17,17 +20,23 @@ Breaking changes
---------------- ----------------
* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box<EvalAltResult>>`. * `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box<EvalAltResult>>`.
* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Arrary`, `Map` or `String`. * `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Array`, `Map` or `String`.
* `EvalAltResult` has a new variant `ErrorInModule` which holds errors when loading an external module.
* `Module::eval_ast_as_new` now takes an extra boolean parameter, indicating whether to encapsulate the entire module into a separate namespace.
* Functions in `FileModuleResolver` loaded modules now can cross-call each other in addition to functions in the global namespace. For the old behavior, use `MergingFileModuleResolver` instead.
* New `EvalAltResult::ErrorInModule` variant capturing errors when loading a module from a script file.
New features New features
------------ ------------
* Plugins support via procedural macros. * Plugins support via procedural macros.
* Scripted functions are allowed in packages. * Scripted functions are allowed in packages.
* `parse_int` and `parse_float` functions. * `parse_int` and `parse_float` functions for parsing numbers; `split` function for splitting strings.
* `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions. * `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions.
* Functions iteration functions now take `FnMut` instead of `Fn`. * Functions iteration functions for `AST` and `Module` now take `FnMut` instead of `Fn`.
* New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`. * New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`.
* `+` and `-` operators for timestamps to increment/decrement by seconds.
Version 0.18.3 Version 0.18.3
============== ==============

View File

@ -43,6 +43,30 @@ impl Default for FnSpecialAccess {
} }
} }
impl FnSpecialAccess {
pub fn get_fn_name(&self) -> Option<(String, String, proc_macro2::Span)> {
match self {
FnSpecialAccess::None => None,
FnSpecialAccess::Property(Property::Get(ref g)) => {
Some((format!("get${}", g.to_string()), g.to_string(), g.span()))
}
FnSpecialAccess::Property(Property::Set(ref s)) => {
Some((format!("set${}", s.to_string()), s.to_string(), s.span()))
}
FnSpecialAccess::Index(Index::Get) => Some((
FN_IDX_GET.to_string(),
"index_get".to_string(),
proc_macro2::Span::call_site(),
)),
FnSpecialAccess::Index(Index::Set) => Some((
FN_IDX_SET.to_string(),
"index_set".to_string(),
proc_macro2::Span::call_site(),
)),
}
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct ExportedFnParams { pub(crate) struct ExportedFnParams {
pub name: Option<Vec<String>>, pub name: Option<Vec<String>>,
@ -363,22 +387,8 @@ impl ExportedFn {
}) })
.unwrap_or_else(|| Vec::new()); .unwrap_or_else(|| Vec::new());
match self.params.special { if let Some((s, _, span)) = self.params.special.get_fn_name() {
FnSpecialAccess::None => {} literals.push(syn::LitStr::new(&s, span));
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() { if literals.is_empty() {

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use crate::attrs::ExportScope; use crate::attrs::ExportScope;
use crate::function::ExportedFn; use crate::function::{ExportedFn, FnSpecialAccess};
use crate::module::Module; use crate::module::Module;
pub(crate) type ExportedConst = (String, Box<syn::Type>, syn::Expr); pub(crate) type ExportedConst = (String, Box<syn::Type>, syn::Expr);
@ -183,40 +183,56 @@ pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type {
} }
pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> { pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> {
fn make_key(name: impl ToString, itemfn: &ExportedFn) -> String {
itemfn
.arg_list()
.fold(name.to_string(), |mut argstr, fnarg| {
let type_string: String = match fnarg {
syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"),
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
ty.as_ref().to_token_stream().to_string()
}
};
argstr.push('.');
argstr.push_str(&type_string);
argstr
})
}
let mut renames = HashMap::<String, proc_macro2::Span>::new(); let mut renames = HashMap::<String, proc_macro2::Span>::new();
let mut names = HashMap::<String, proc_macro2::Span>::new(); let mut fn_defs = HashMap::<String, proc_macro2::Span>::new();
for itemfn in fns.iter() { for itemfn in fns.iter() {
if let Some(ref names) = itemfn.params().name { if itemfn.params().name.is_some() || itemfn.params().special != FnSpecialAccess::None {
for name in names { let mut names = itemfn
.params()
.name
.as_ref()
.map(|v| v.iter().map(|n| (n.clone(), n.clone())).collect())
.unwrap_or_else(|| Vec::new());
if let Some((s, n, _)) = itemfn.params().special.get_fn_name() {
names.push((s, n));
}
for (name, fn_name) in names {
let current_span = itemfn.params().span.as_ref().unwrap(); let current_span = itemfn.params().span.as_ref().unwrap();
let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { let key = make_key(&name, itemfn);
let type_string: String = match fnarg {
syn::FnArg::Receiver(_) => {
unimplemented!("receiver rhai_fns not implemented")
}
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
ty.as_ref().to_token_stream().to_string()
}
};
argstr.push('.');
argstr.push_str(&type_string);
argstr
});
if let Some(other_span) = renames.insert(key, *current_span) { if let Some(other_span) = renames.insert(key, *current_span) {
let mut err = syn::Error::new( let mut err = syn::Error::new(
*current_span, *current_span,
format!("duplicate Rhai signature for '{}'", &name), format!("duplicate Rhai signature for '{}'", &fn_name),
); );
err.combine(syn::Error::new( err.combine(syn::Error::new(
other_span, other_span,
format!("duplicated function renamed '{}'", &name), format!("duplicated function renamed '{}'", &fn_name),
)); ));
return Err(err); return Err(err);
} }
} }
} else { } else {
let ident = itemfn.name(); let ident = itemfn.name();
if let Some(other_span) = names.insert(ident.to_string(), ident.span()) { if let Some(other_span) = fn_defs.insert(ident.to_string(), ident.span()) {
let mut err = syn::Error::new( let mut err = syn::Error::new(
ident.span(), ident.span(),
format!("duplicate function '{}'", ident.to_string()), format!("duplicate function '{}'", ident.to_string()),
@ -227,21 +243,20 @@ pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::
)); ));
return Err(err); return Err(err);
} }
let key = make_key(ident, itemfn);
if let Some(fn_span) = renames.get(&key) {
let mut err = syn::Error::new(
ident.span(),
format!("duplicate Rhai signature for '{}'", &ident),
);
err.combine(syn::Error::new(
*fn_span,
format!("duplicated function '{}'", &ident),
));
return Err(err);
}
} }
} }
for (new_name, attr_span) in renames.drain() {
let new_name = new_name.split('.').next().unwrap();
if let Some(fn_span) = names.get(new_name) {
let mut err = syn::Error::new(
attr_span,
format!("duplicate Rhai signature for '{}'", &new_name),
);
err.combine(syn::Error::new(
*fn_span,
format!("duplicated function '{}'", &new_name),
));
return Err(err);
}
}
Ok(()) Ok(())
} }

View File

@ -1,15 +1,15 @@
error: duplicate Rhai signature for 'foo' error: duplicate Rhai signature for 'foo'
--> $DIR/rhai_fn_rename_collision_oneattr.rs:12:15
|
12 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
error: duplicated function 'foo'
--> $DIR/rhai_fn_rename_collision_oneattr.rs:17:12 --> $DIR/rhai_fn_rename_collision_oneattr.rs:17:12
| |
17 | pub fn foo(input: Point) -> bool { 17 | pub fn foo(input: Point) -> bool {
| ^^^ | ^^^
error: duplicated function 'foo'
--> $DIR/rhai_fn_rename_collision_oneattr.rs:12:15
|
12 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module` error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> $DIR/rhai_fn_rename_collision_oneattr.rs:27:8 --> $DIR/rhai_fn_rename_collision_oneattr.rs:27:8
| |

View File

@ -1,15 +1,15 @@
error: duplicate Rhai signature for 'foo' error: duplicate Rhai signature for 'bar'
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:17:15
|
17 | #[rhai_fn(get = "bar")]
| ^^^^^^^^^^^
error: duplicated function renamed 'bar'
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:12:15 --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:12:15
| |
12 | #[rhai_fn(name = "foo", get = "bar")] 12 | #[rhai_fn(name = "foo", get = "bar")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^
error: duplicated function 'foo'
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:18:12
|
18 | pub fn foo(input: Point) -> bool {
| ^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module` error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:25:8 --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:25:8
| |

View File

@ -81,15 +81,15 @@ The Rhai Scripting Language
2. [Overloading](language/overload.md) 2. [Overloading](language/overload.md)
3. [Namespaces](language/fn-namespaces.md) 3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md) 4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md) 5. [Currying](language/fn-curry.md)
6. [Currying](language/fn-curry.md) 6. [Anonymous Functions](language/fn-anon.md)
7. [Closures](language/fn-closure.md) 7. [Closures](language/fn-closure.md)
16. [Print and Debug](language/print-debug.md) 16. [Print and Debug](language/print-debug.md)
17. [Modules](language/modules/index.md) 17. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
2. [Import Modules](language/modules/import.md) 2. [Import Modules](language/modules/import.md)
3. [Create from Rust](rust/modules/create.md) 3. [Create from Rust](rust/modules/create.md)
4. [Create from AST](language/modules/ast.md) 4. [Create from AST](rust/modules/ast.md)
5. [Module Resolvers](rust/modules/resolvers.md) 5. [Module Resolvers](rust/modules/resolvers.md)
1. [Custom Implementation](rust/modules/imp-resolver.md) 1. [Custom Implementation](rust/modules/imp-resolver.md)
18. [Eval Statement](language/eval.md) 18. [Eval Statement](language/eval.md)

View File

@ -37,7 +37,7 @@ Dynamic
* Dynamic dispatch via [function pointers] with additional support for [currying]. * Dynamic dispatch via [function pointers] with additional support for [currying].
* Closures via [automatic currying] with capturing shared variables from the external scope. * [Closures] that can capture shared variables.
* Some support for [object-oriented programming (OOP)][OOP]. * Some support for [object-oriented programming (OOP)][OOP].

View File

@ -3,36 +3,36 @@ Keywords List
{{#include ../links.md}} {{#include ../links.md}}
| Keyword | Description | Inactive under | Overloadable | | Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-------------: | :----------: | | :-------------------: | ------------------------------------------- | :-------------: | :----------: |
| `true` | boolean true literal | | no | | `true` | boolean true literal | | no |
| `false` | boolean false literal | | no | | `false` | boolean false literal | | no |
| `let` | variable declaration | | no | | `let` | variable declaration | | no |
| `const` | constant declaration | | no | | `const` | constant declaration | | no |
| `is_shared` | is a value shared? | | no | | `is_shared` | is a value shared? | | no |
| `if` | if statement | | no | | `if` | if statement | | no |
| `else` | else block of if statement | | no | | `else` | else block of if statement | | no |
| `while` | while loop | | no | | `while` | while loop | | no |
| `loop` | infinite loop | | no | | `loop` | infinite loop | | no |
| `for` | for loop | | no | | `for` | for loop | | no |
| `in` | containment test, part of for loop | | no | | `in` | 1) containment test<br/>2) part of for loop | | no |
| `continue` | continue a loop at the next iteration | | no | | `continue` | continue a loop at the next iteration | | no |
| `break` | loop breaking | | no | | `break` | break out of loop iteration | | no |
| `return` | return value | | no | | `return` | return value | | no |
| `throw` | throw exception | | no | | `throw` | throw exception | | no |
| `import` | import module | [`no_module`] | no | | `import` | import module | [`no_module`] | no |
| `export` | export variable | [`no_module`] | no | | `export` | export variable | [`no_module`] | no |
| `as` | alias for variable export | [`no_module`] | no | | `as` | alias for variable export | [`no_module`] | no |
| `private` | mark function private | [`no_function`] | no | | `private` | mark function private | [`no_function`] | no |
| `fn` (lower-case `f`) | function definition | [`no_function`] | no | | `fn` (lower-case `f`) | function definition | [`no_function`] | no |
| `Fn` (capital `F`) | function to create a [function pointer] | | yes | | `Fn` (capital `F`) | create a [function pointer] | | yes |
| `call` | call a [function pointer] | | no | | `call` | call a [function pointer] | | no |
| `curry` | curry a [function pointer] | | no | | `curry` | curry a [function pointer] | | no |
| `this` | reference to base object for method call | [`no_function`] | no | | `this` | reference to base object for method call | [`no_function`] | no |
| `type_of` | get type name of value | | yes | | `type_of` | get type name of value | | yes |
| `print` | print value | | yes | | `print` | print value | | yes |
| `debug` | print value in debug format | | yes | | `debug` | print value in debug format | | yes |
| `eval` | evaluate script | | yes | | `eval` | evaluate script | | yes |
Reserved Keywords Reserved Keywords

View File

@ -3,14 +3,14 @@ Literals Syntax
{{#include ../links.md}} {{#include ../links.md}}
| Type | Literal syntax | | Type | Literal syntax |
| :--------------------------------: | :------------------------------------------------------------------------------: | | :--------------------------------: | :-----------------------------------------------------------------------------------------: |
| `INT` | `42`, `-123`, `0`,<br/>`0x????..` (hex), `0b????..` (binary), `0o????..` (octal) | | `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` |
| `FLOAT` | `42.0`, `-123.456`, `0.0` | | `FLOAT` | `42.0`, `-123.456`, `0.0` |
| [String] | `"... \x?? \u???? \U???????? ..."` | | [String] | `"... \x?? \u???? \U???????? ..."` |
| Character | `"... \x?? \u???? \U???????? ..."` | | Character | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
| [`Array`] | `[ ???, ???, ??? ]` | | [`Array`] | `[ ???, ???, ??? ]` |
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` | | [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
| Boolean true | `true` | | Boolean true | `true` |
| Boolean false | `false` | | Boolean false | `false` |
| `Nothing`/`null`/`nil`/`void`/Unit | `()` | | `Nothing`/`null`/`nil`/`void`/Unit | `()` |

View File

@ -40,13 +40,13 @@ Symbols
| ------------ | ------------------------ | | ------------ | ------------------------ |
| `:` | property value separator | | `:` | property value separator |
| `::` | module path separator | | `::` | module path separator |
| `#` | _Reserved_ | | `#` | _reserved_ |
| `=>` | _Reserved_ | | `=>` | _reserved_ |
| `->` | _Reserved_ | | `->` | _reserved_ |
| `<-` | _Reserved_ | | `<-` | _reserved_ |
| `===` | _Reserved_ | | `===` | _reserved_ |
| `!==` | _Reserved_ | | `!==` | _reserved_ |
| `:=` | _Reserved_ | | `:=` | _reserved_ |
| `::<` .. `>` | _Reserved_ | | `::<` .. `>` | _reserved_ |
| `@` | _Reserved_ | | `@` | _reserved_ |
| `(*` .. `*)` | _Reserved_ | | `(*` .. `*)` | _reserved_ |

View File

@ -4,7 +4,7 @@ Extend Rhai with Custom Syntax
{{#include ../links.md}} {{#include ../links.md}}
For the ultimate advantageous, there is a built-in facility to _extend_ the Rhai language For the ultimate adventurous, there is a built-in facility to _extend_ the Rhai language
with custom-defined _syntax_. with custom-defined _syntax_.
But before going off to define the next weird statement type, heed this warning: But before going off to define the next weird statement type, heed this warning:
@ -28,7 +28,7 @@ Where This Might Be Useful
* Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances understanding of the code's intent. * Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances understanding of the code's intent.
* Where certain logic cannot be easily encapsulated inside a function. This is usually the case where _closures_ are required, because Rhai does not have closures. * Where certain logic cannot be easily encapsulated inside a function.
* Where you just want to confuse your user and make their lives miserable, because you can. * Where you just want to confuse your user and make their lives miserable, because you can.
@ -154,10 +154,10 @@ let result = engine.eval_expression_tree(context, scope, expr)?;
New variables maybe declared (usually with a variable name that is passed in via `$ident$). New variables maybe declared (usually with a variable name that is passed in via `$ident$).
It can simply be pushed into the [`scope`]. It can simply be pushed into the [`Scope`].
However, beware that all new variables must be declared _prior_ to evaluating any expression tree. However, beware that all new variables must be declared _prior_ to evaluating any expression tree.
In other words, any `scope.push(...)` calls must come _before_ any `engine::eval_expression_tree(...)` calls. In other words, any `Scope::push` calls must come _before_ any `Engine::eval_expression_tree` calls.
```rust ```rust
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();

View File

@ -11,9 +11,9 @@ Creating them is accomplished via the `Func` trait which contains `create_from_s
(as well as its companion method `create_from_ast`): (as well as its companion method `create_from_ast`):
```rust ```rust
use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
let engine = Engine::new(); // create a new 'Engine' just for this let engine = Engine::new(); // create a new 'Engine' just for this
let script = "fn calc(x, y) { x + y.len < 42 }"; let script = "fn calc(x, y) { x + y.len < 42 }";
@ -25,14 +25,14 @@ let script = "fn calc(x, y) { x + y.len < 42 }";
let func = Func::<(i64, String), bool>::create_from_script( let func = Func::<(i64, String), bool>::create_from_script(
// ^^^^^^^^^^^^^ function parameter types in tuple // ^^^^^^^^^^^^^ function parameter types in tuple
engine, // the 'Engine' is consumed into the closure engine, // the 'Engine' is consumed into the closure
script, // the script, notice number of parameters must match script, // the script, notice number of parameters must match
"calc" // the entry-point function name "calc" // the entry-point function name
)?; )?;
func(123, "hello".to_string())? == false; // call the closure func(123, "hello".to_string())? == false; // call the closure
schedule_callback(func); // pass it as a callback to another function schedule_callback(func); // pass it as a callback to another function
// Although there is nothing you can't do by manually writing out the closure yourself... // Although there is nothing you can't do by manually writing out the closure yourself...
let engine = Engine::new(); let engine = Engine::new();

View File

@ -8,13 +8,13 @@ Some optimizations can alter subtle semantics of the script.
For example: For example:
```rust ```rust
if true { // condition always true if true { // condition always true
123.456; // eliminated 123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist! hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level foo(42) // promoted up-level
} }
foo(42) // <- the above optimizes to this foo(42) // <- the above optimizes to this
``` ```
If the original script were evaluated instead, it would have been an error - the variable `hello` does not exist, If the original script were evaluated instead, it would have been an error - the variable `hello` does not exist,

View File

@ -1,4 +1,3 @@
Raw `Engine` Raw `Engine`
=========== ===========

View File

@ -31,19 +31,20 @@ let mut scope = Scope::new();
scope scope
.push("y", 42_i64) .push("y", 42_i64)
.push("z", 999_i64) .push("z", 999_i64)
.push_constant("MY_NUMBER", 123_i64) // constants can also be added
.set_value("s", "hello, world!".to_string()); //'set_value' adds a variable when one doesn't exist .set_value("s", "hello, world!".to_string()); //'set_value' adds a variable when one doesn't exist
// remember to use 'String', not '&str' // remember to use 'String', not '&str'
// First invocation // First invocation
engine.eval_with_scope::<()>(&mut scope, r" engine.eval_with_scope::<()>(&mut scope, r"
let x = 4 + 5 - y + z + s.len; let x = 4 + 5 - y + z + MY_NUMBER + s.len;
y = 1; y = 1;
")?; ")?;
// Second invocation using the same state // Second invocation using the same state
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?; let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
println!("result: {}", result); // prints 979 println!("result: {}", result); // prints 1102
// Variable y is changed in the script - read it with 'get_value' // Variable y is changed in the script - read it with 'get_value'
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1); assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);

View File

@ -30,22 +30,22 @@ Built-in Functions
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays: The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end | | `push` | element to insert | inserts an element at the end |
| `+=` operator | array, element to insert (not another array) | inserts an element at the end | | `append` | array to append | concatenates the second array to the end of the first |
| `append` | array to append | concatenates the second array to the end of the first | | `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
| `+=` operator | array, array to append | concatenates the second array to the end of the first | | `+=` operator | 1) array<br/>2) array to append | concatenates the second array to the end of the first |
| `+` operator | first array, second array | concatenates the first array with the second | | `+` operator | 1) first array<br/>2) second array | concatenates the first array with the second |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | inserts an element at a certain index | | `insert` | 1) element to insert<br/>2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | | `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | | `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `reverse` | _none_ | reverses the array | | `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements | | `len` method and property | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length | | `pad` | 1) target length<br/>2) element to pad | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array | | `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
Use Custom Types With Arrays Use Custom Types With Arrays

View File

@ -16,8 +16,45 @@ x = 123; // <- syntax error: cannot assign to constant
``` ```
Unlike variables which need not have initial values (default to [`()`]), Unlike variables which need not have initial values (default to [`()`]),
constants must be assigned one, and it must be a constant _value_, not an expression. constants must be assigned one, and it must be a [_literal value_](../appendix/literals.md),
not an expression.
```rust ```rust
const x = 40 + 2; // <- syntax error: cannot assign expression to constant const x = 40 + 2; // <- syntax error: cannot assign expression to constant
``` ```
Manually Add Constant into Custom Scope
--------------------------------------
It is possible to add a constant into a custom [`Scope`] so it'll be available to scripts
running with that [`Scope`].
When added to a custom [`Scope`], a constant can hold any value, not just a literal value.
It is very useful to have a constant value hold a [custom type], which essentially acts
as a [_singleton_](../patterns/singleton.md). The singleton object can be modified via its
registered API - being a constant only prevents it from being re-assigned or operated upon by Rhai;
mutating it via a Rust function is still allowed.
```rust
use rhai::{Engine, Scope};
struct TestStruct(i64); // custom type
let engine = Engine::new()
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
.register_get_set("value",
|obj: &mut TestStruct| obj.0, // property getter
|obj: &mut TestStruct, value: i64| obj.0 = value // property setter
);
let mut scope = Scope::new(); // create custom scope
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
engine.consume_with_scope(&mut scope, r"
MY_NUMBER.value = 42; // constant objects can be modified
print(MY_NUMBER.value); // prints 42
")?;
```

View File

@ -3,6 +3,10 @@ Value Conversions
{{#include ../links.md}} {{#include ../links.md}}
Convert Between Integer and Floating-Point
-----------------------------------------
The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`). The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`).
The `to_int` function converts a supported number to `INT` (`i32` or `i64` depending on [`only_i32`]). The `to_int` function converts a supported number to `INT` (`i32` or `i64` depending on [`only_i32`]).
@ -22,3 +26,31 @@ let c = 'X'; // character
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
``` ```
Parse String into Number
------------------------
The `parse_float` function converts a [string] into a `FLOAT` (defaults to `f64`).
The `parse_int` function converts a [string] into an `INT` (`i32` or `i64` depending on [`only_i32`]).
An optional radix (2-36) can be provided to parse the [string] into a number of the specified radix.
```rust
let x = parse_float("123.4"); // parse as floating-point
x == 123.4;
type_of(x) == "f64";
let dec = parse_int("42"); // parse as decimal
let dec = parse_int("42", 10); // radix = 10 is the default
dec == 42;
type_of(dec) == "i64";
let bin = parse_int("110", 2); // parse as binary (radix = 2)
bin == 0b110;
type_of(bin) == "i64";
let hex = parse_int("ab", 16); // parse as hex (radix = 16)
hex == 0xab;
type_of(hex) == "i64";
```

View File

@ -58,15 +58,15 @@ The `cast` method then converts the value into a specific, known type.
Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails. Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
```rust ```rust
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' let item = list[0]; // an element in an 'Array' is 'Dynamic'
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None' let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
``` ```
Type Name Type Name
@ -76,17 +76,21 @@ The `type_name` method gets the name of the actual type as a static string slice
which can be `match`-ed against. which can be `match`-ed against.
```rust ```rust
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' let item = list[0]; // an element in an 'Array' is 'Dynamic'
match item.type_name() { // 'type_name' returns the name of the actual Rust type match item.type_name() { // 'type_name' returns the name of the actual Rust type
"i64" => ... "i64" => ...
"alloc::string::String" => ... "alloc::string::String" => ...
"bool" => ... "bool" => ...
"path::to::module::TestStruct" => ... "crate::path::to::module::TestStruct" => ...
} }
``` ```
**Note:** `type_name` always returns the _full_ Rust path name of the type, even when the type
has been registered with a friendly name via `Engine::register_type_with_name`. This behavior
is different from that of the [`type_of`][`type_of()`] function in Rhai.
Conversion Traits Conversion Traits
---------------- ----------------
@ -100,4 +104,5 @@ The following conversion traits are implemented for `Dynamic`:
* `From<String>` * `From<String>`
* `From<char>` * `From<char>`
* `From<Vec<T>>` (into an [array]) * `From<Vec<T>>` (into an [array])
* `From<HashMap<String, T>>` (into an [object map]). * `From<HashMap<String, T>>` (into an [object map])
* `From<Instant>` (into a [timestamp] if not [`no_std`])

View File

@ -10,8 +10,8 @@ This scenario is especially common when simulating object-oriented programming (
// Define object // Define object
let obj = #{ let obj = #{
data: 42, data: 42,
increment: Fn("inc_obj"), // use function pointers to increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions decrement: Fn("dec_obj"), // refer to method functions
print: Fn("print_obj") print: Fn("print_obj")
}; };

View File

@ -23,43 +23,25 @@ Therefore, similar to closures in many languages, these captured shared values p
reference counting, and may be read or modified even after the variables that hold them reference counting, and may be read or modified even after the variables that hold them
go out of scope and no longer exist. go out of scope and no longer exist.
Use the `is_shared` function to check whether a particular value is a shared value. Use the `Dynamic::is_shared` function to check whether a particular value is a shared value.
Automatic currying can be turned off via the [`no_closure`] feature. Automatic currying can be turned off via the [`no_closure`] feature.
Actual Implementation
---------------------
The actual implementation de-sugars to:
1. Keeping track of what variables are accessed inside the anonymous function,
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
in the current execution scope - where the anonymous function is created.
3. The variable is added to the parameters list of the anonymous function, at the front.
4. The variable is then converted into a **reference-counted shared value**.
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
and inserting it into future calls of the function.
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
Examples Examples
-------- --------
```rust ```rust
let x = 1; // a normal variable let x = 1; // a normal variable
x.is_shared() == false;
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
x.is_shared() == true; // 'x' is now a shared value! x.is_shared() == true; // 'x' is now a shared value!
f.call(2) == 3; // 1 + 2 == 3
x = 40; // changing 'x'... x = 40; // changing 'x'...
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
@ -117,6 +99,8 @@ will occur and the script will terminate with an error.
```rust ```rust
let x = 20; let x = 20;
x.is_shared() == false; // 'x' is not shared, so no data race is possible
let f = |a| this += x + a; // 'x' is captured in this closure let f = |a| this += x + a; // 'x' is captured in this closure
x.is_shared() == true; // now 'x' is shared x.is_shared() == true; // now 'x' is shared
@ -152,6 +136,26 @@ x.call(f, 2);
TL;DR TL;DR
----- -----
### Q: How is it actually implemented?
The actual implementation of closures de-sugars to:
1. Keeping track of what variables are accessed inside the anonymous function,
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
in the current execution scope - where the anonymous function is created.
3. The variable is added to the parameters list of the anonymous function, at the front.
4. The variable is then converted into a **reference-counted shared value**.
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
and inserting it into future calls of the function.
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
### Q: Why are closures implemented as automatic currying? ### Q: Why are closures implemented as automatic currying?
In concept, a closure _closes_ over captured variables from the outer scope - that's why In concept, a closure _closes_ over captured variables from the outer scope - that's why

View File

@ -10,7 +10,7 @@ Each Function is a Separate Compilation Unit
This means that individual functions can be separated, exported, re-grouped, imported, This means that individual functions can be separated, exported, re-grouped, imported,
and generally mix-'n-match-ed with other completely unrelated scripts. and generally mix-'n-match-ed with other completely unrelated scripts.
For example, the `AST::merge` method allows Global all functions in one [`AST`] into another, For example, the `AST::merge` method allows merging all functions in one [`AST`] into another,
forming a new, combined, group of functions. forming a new, combined, group of functions.
In general, there are two types of _namespaces_ where functions are looked up: In general, there are two types of _namespaces_ where functions are looked up:
@ -43,10 +43,12 @@ This aspect is very similar to JavaScript before ES6 modules.
// Compile a script into AST // Compile a script into AST
let ast1 = engine.compile( let ast1 = engine.compile(
r#" r#"
fn get_message() { "Hello!" } // greeting message fn get_message() {
"Hello!" // greeting message
}
fn say_hello() { fn say_hello() {
print(get_message()); // prints message print(get_message()); // prints message
} }
say_hello(); say_hello();
@ -136,7 +138,7 @@ the subsequent call using the _namespace-qualified_ function name fails to find
function named '`message`' in the global namespace. function named '`message`' in the global namespace.
Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver], Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver],
make sure that those functions are as _pure_ as possible and avoid cross-calling them from each other. make sure that those functions are as independent as possible and avoid cross-calling them from each other.
A [function pointer] is a valid technique to call another function in an environment-independent manner: A [function pointer] is a valid technique to call another function in an environment-independent manner:

View File

@ -61,8 +61,8 @@ hello.call(0); // error: function not found - 'hello_world (i64)'
Global Namespace Only Global Namespace Only
-------------------- --------------------
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace] Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace]. They can only refer to functions within the global [namespace][function namespace].
See [function namespaces] for more details. See [function namespaces] for more details.
```rust ```rust

View File

@ -44,10 +44,13 @@ Representation of Numbers
------------------------ ------------------------
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if
the [`no_float`] feature is not used. Most common generators of JSON data distinguish between the [`no_float`] feature is not used.
integer and floating-point values by always serializing a floating-point number with a decimal point
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully Most common generators of JSON data distinguish between integer and floating-point values by always
with Rhai [object maps]. serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is
assumed to be an integer).
This style can be used successfully with Rhai [object maps].
Parse JSON with Sub-Objects Parse JSON with Sub-Objects
@ -68,9 +71,10 @@ A JSON object hash starting with `#{` is handled transparently by `Engine::parse
// JSON with sub-object 'b'. // JSON with sub-object 'b'.
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#; let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
let new_json = json.replace("{" "#{"); // Our JSON text does not contain the '{' character, so off we go!
let new_json = json.replace("{", "#{");
// The leading '{' will also be replaced to '#{', but parse_json can handle this. // The leading '{' will also be replaced to '#{', but 'parse_json' handles this just fine.
let map = engine.parse_json(&new_json, false)?; let map = engine.parse_json(&new_json, false)?;
map.len() == 2; // 'map' contains two properties: 'a' and 'b' map.len() == 2; // 'map' contains two properties: 'a' and 'b'

View File

@ -47,10 +47,10 @@ an equivalent method coded in script, where the object is accessed via the `this
The following table illustrates the differences: The following table illustrates the differences:
| Function type | Parameters | Object reference | Function signature | | Function type | Parameters | Object reference | Function signature |
| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: | | :-----------: | :--------: | :-----------------------: | :---------------------------: |
| Native Rust | _n_ + 1 | first `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` | | Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` |
| Rhai script | _n_ | `this` | `fn method(x, y) {}` | | Rhai script | _N_ | `this` (of type `&mut T`) | `Fn(x: U, y: V)` |
`&mut` is Efficient, Except for `ImmutableString` `&mut` is Efficient, Except for `ImmutableString`

View File

@ -1,53 +0,0 @@
Create a Module from an AST
==========================
{{#include ../../links.md}}
It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`.
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
other than non-[`private`] functions (unless that's intentional).
```rust
use rhai::{Engine, Module};
let engine = Engine::new();
// Compile a script into an 'AST'
let ast = engine.compile(r#"
// Functions become module functions
fn calc(x) {
x + 1
}
fn add_len(x, y) {
x + y.len
}
// Imported modules can become sub-modules
import "another module" as extra;
// Variables defined at global level can become module variables
const x = 123;
let foo = 41;
let hello;
// Variable values become constant module variable values
foo = calc(foo);
hello = "hello, " + foo + " worlds!";
// Finally, export the variables and modules
export
x as abc, // aliased variable name
foo,
hello,
extra as foobar; // export sub-module
"#)?;
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
// 'module' now can be loaded into a custom 'Scope' for future use. It contains:
// - sub-module: 'foobar' (renamed from 'extra')
// - functions: 'calc', 'add_len'
// - variables: 'abc' (renamed from 'x'), 'foo', 'hello'
```

View File

@ -3,27 +3,16 @@ Export Variables, Functions and Sub-Modules in Module
{{#include ../../links.md}} {{#include ../../links.md}}
A _module_ is a single script (or pre-compiled [`AST`]) containing global variables, functions and sub-modules.
A module can be created from a script via the `Module::eval_ast_as_new` method. When given an [`AST`], Export Global Variables
it is first evaluated, then the following items are exposed as members of the new module: ----------------------
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
* Functions not specifically marked `private`.
* Global modules that remain in the [`Scope`] at the end of a script run.
Global Variables
----------------
The `export` statement, which can only be at global level, exposes selected variables as members of a module. The `export` statement, which can only be at global level, exposes selected variables as members of a module.
Variables not exported are _private_ and hidden. They are merely used to initialize the module, Variables not exported are _private_ and hidden. They are merely used to initialize the module,
but cannot be accessed from outside. but cannot be accessed from outside.
Everything exported from a module is **constant** (**read-only**). Everything exported from a module is **constant** (i.e. read-only).
```rust ```rust
// This is a module script. // This is a module script.
@ -45,8 +34,8 @@ export x as answer; // the variable 'x' is exported under the alias 'answer'
``` ```
Functions Export Functions
--------- ----------------
All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix. All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.

View File

@ -10,25 +10,23 @@ If an [object map]'s property holds a [function pointer], the property can simpl
a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax
of using the `call` function keyword. of using the `call` function keyword.
When a property holding a [function pointer] (which incudes [closures]) is called like a method, When a property holding a [function pointer] or a [closure] is called like a method,
what happens next depends on whether the target function is a native Rust function or what happens next depends on whether the target function is a native Rust function or
a script-defined function. a script-defined function.
If it is a registered native Rust method function, it is called directly. * If it is a registered native Rust function, it is called directly in _method-call_ style with the [object map] inserted as the first argument.
If it is a script-defined function, the `this` variable within the function body is bound * If it is a script-defined function, the `this` variable within the function body is bound to the [object map] before the function is called.
to the [object map] before the function is called. There is no way to simulate this behavior
via a normal function-call syntax because all scripted function arguments are passed by value.
```rust ```rust
let obj = #{ let obj = #{
data: 40, data: 40,
action: || this.data += x // 'action' holds a function pointer which is a closure action: || this.data += x // 'action' holds a closure
}; };
obj.action(2); // Calls the function pointer with `this` bound to 'obj' obj.action(2); // calls the function pointer with `this` bound to 'obj'
obj.call(obj.action, 2); // The above de-sugars to this obj.call(obj.action, 2); // <- the above de-sugars to this
obj.data == 42; obj.data == 42;

View File

@ -57,17 +57,17 @@ Built-in Functions
The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`]) The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`])
operate on object maps: operate on object maps:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? | | `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties | | `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map | | `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | | `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | | `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
| `+` operator | first object map, second object map | merges the first object map with the second | | `+` operator | 1) first object map<br/>2) second object map | merges the first object map with the second |
| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map | | `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] | | `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] | | `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
Examples Examples

View File

@ -6,19 +6,20 @@ Built-in String Functions
The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if
using a [raw `Engine`]) operate on [strings]: using a [raw `Engine`]) operate on [strings]:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | | `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | character to pad, target length | pads the string with an character to at least a specified length | | `pad` | 1) character to pad<br/>2) target length | pads the string with an character to at least a specified length |
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | | `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string | | `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters | | `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | | `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | | `index_of` | 1) character/sub-string to search for<br/>2) start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | | `sub_string` | 1) start index<br/>2) length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | | `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] |
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another | | `crop` | 1) start index<br/>2) length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `trim` | _none_ | trims the string of whitespace at the beginning and end | | `replace` | 1) target character/sub-string<br/>2) replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
Examples Examples
-------- --------

View File

@ -43,17 +43,17 @@ Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicod
Standard escape sequences: Standard escape sequences:
| Escape sequence | Meaning | | Escape sequence | Meaning |
| --------------- | ------------------------------ | | --------------- | -------------------------------- |
| `\\` | back-slash `\` | | `\\` | back-slash `\` |
| `\t` | tab | | `\t` | tab |
| `\r` | carriage-return `CR` | | `\r` | carriage-return `CR` |
| `\n` | line-feed `LF` | | `\n` | line-feed `LF` |
| `\"` | double-quote `"` in strings | | `\"` | double-quote `"` |
| `\'` | single-quote `'` in characters | | `\'` | single-quote `'` |
| `\x`_xx_ | Unicode in 2-digit hex | | `\x`_xx_ | ASCII character in 2-digit hex |
| `\u`_xxxx_ | Unicode in 4-digit hex | | `\u`_xxxx_ | Unicode character in 4-digit hex |
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex | | `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
Differences from Rust Strings Differences from Rust Strings

View File

@ -18,10 +18,12 @@ Built-in Functions
The following methods (defined in the [`BasicTimePackage`][packages] but excluded if using a [raw `Engine`]) operate on timestamps: The following methods (defined in the [`BasicTimePackage`][packages] but excluded if using a [raw `Engine`]) operate on timestamps:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- | | ----------------------------- | ------------------------------------------- | -------------------------------------------------------- |
| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp | | `elapsed` method and property | _none_ | returns the number of seconds since the timestamp |
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps | | `-` operator | 1) later timestamp<br/>2) earlier timestamp | returns the number of seconds between the two timestamps |
| `+` operator | number of seconds to add | returns a new timestamp |
| `-` operator | number of seconds to subtract | returns a new timestamp |
Examples Examples

View File

@ -24,3 +24,24 @@ if type_of(x) == "string" {
do_something_with_string(x); do_something_with_string(x);
} }
``` ```
Custom Types
------------
`type_of()` a [custom type] returns:
* if registered via `Engine::register_type_with_name` - the registered name
* if registered via `Engine::register_type` - the full Rust path name
```rust
struct TestStruct1;
struct TestStruct2;
engine
// type_of(struct1) == "crate::path::to::module::TestStruct1"
.register_type::<TestStruct1>()
// type_of(struct2) == "MyStruct"
.register_type_with_name::<TestStruct2>("MyStruct");
```

View File

@ -49,6 +49,8 @@
[`Dynamic`]: {{rootUrl}}/language/dynamic.md [`Dynamic`]: {{rootUrl}}/language/dynamic.md
[`to_int`]: {{rootUrl}}/language/convert.md [`to_int`]: {{rootUrl}}/language/convert.md
[`to_float`]: {{rootUrl}}/language/convert.md [`to_float`]: {{rootUrl}}/language/convert.md
[`parse_int`]: {{rootUrl}}/language/convert.md
[`parse_float`]: {{rootUrl}}/language/convert.md
[custom type]: {{rootUrl}}/rust/custom.md [custom type]: {{rootUrl}}/rust/custom.md
[custom types]: {{rootUrl}}/rust/custom.md [custom types]: {{rootUrl}}/rust/custom.md

View File

@ -51,6 +51,12 @@ let config: Rc<RefCell<Config>> = Rc::new(RefCell::new(Default::default()));
### Register Config API ### Register Config API
The trick to building a Config API is to clone the shared configuration object and
move it into each function registration as a closure.
It is not possible to use a [plugin module] to achieve this, so each function must
be registered one after another.
```rust ```rust
// Notice 'move' is used to move the shared configuration object into the closure. // Notice 'move' is used to move the shared configuration object into the closure.
let cfg = config.clone(); let cfg = config.clone();
@ -66,27 +72,27 @@ engine.register_fn("config_set", move |value: i64| *cfg.borrow_mut().some_field
let cfg = config.clone(); let cfg = config.clone();
engine.register_fn("config_add", move |value: String| engine.register_fn("config_add", move |value: String|
cfg.borrow_mut().some_list.push(value) cfg.borrow_mut().some_list.push(value)
); );
let cfg = config.clone(); let cfg = config.clone();
engine.register_fn("config_add", move |values: &mut Array| engine.register_fn("config_add", move |values: &mut Array|
cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string())) cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
); );
let cfg = config.clone(); let cfg = config.clone();
engine.register_fn("config_add", move |key: String, value: bool| engine.register_fn("config_add", move |key: String, value: bool|
cfg.borrow_mut().some_map.insert(key, value) cfg.borrow_mut().some_map.insert(key, value)
); );
let cfg = config.clone(); let cfg = config.clone();
engine.register_fn("config_contains", move |value: String| engine.register_fn("config_contains", move |value: String|
cfg.borrow().some_list.contains(&value) cfg.borrow().some_list.contains(&value)
); );
let cfg = config.clone(); let cfg = config.clone();
engine.register_fn("config_is_set", move |value: String| engine.register_fn("config_is_set", move |value: String|
cfg.borrow().some_map.get(&value).cloned().unwrap_or(false) cfg.borrow().some_map.get(&value).cloned().unwrap_or(false)
); );
``` ```

View File

@ -64,6 +64,12 @@ let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()
### Register Control API ### Register Control API
The trick to building a Control API is to clone the shared API object and
move it into each function registration as a closure.
It is not possible to use a [plugin module] to achieve this, so each function must
be registered one after another.
```rust ```rust
// Notice 'move' is used to move the shared API object into the closure. // Notice 'move' is used to move the shared API object into the closure.
let b = bunny.clone(); let b = bunny.clone();

View File

@ -58,7 +58,7 @@ impl EnergizerBunny {
pub fn new () -> Self { ... } pub fn new () -> Self { ... }
pub fn go (&mut self) { ... } pub fn go (&mut self) { ... }
pub fn stop (&mut self) { ... } pub fn stop (&mut self) { ... }
pub fn is_going (&self) { ... } pub fn is_going (&self) -> bol { ... }
pub fn get_speed (&self) -> i64 { ... } pub fn get_speed (&self) -> i64 { ... }
pub fn set_speed (&mut self, speed: i64) { ... } pub fn set_speed (&mut self, speed: i64) { ... }
pub fn turn (&mut self, left_turn: bool) { ... } pub fn turn (&mut self, left_turn: bool) { ... }
@ -77,37 +77,52 @@ let SharedBunnyType = Rc<RefCell<EnergizerBunny>>;
engine.register_type_with_name::<SharedBunnyType>("EnergizerBunny"); engine.register_type_with_name::<SharedBunnyType>("EnergizerBunny");
``` ```
### Register Methods and Getters/Setters ### Develop a Plugin with Methods and Getters/Setters
The easiest way to develop a complete set of API for a [custom type] is via a [plugin module].
```rust ```rust
engine use rhai::plugins::*;
.register_get_set("power",
|bunny: &mut SharedBunnyType| bunny.borrow().is_going(), #[export_module]
|bunny: &mut SharedBunnyType, on: bool| { pub mod bunny_api {
if on { pub const MAX_SPEED: i64 = 100;
if bunny.borrow().is_going() {
println!("Still going..."); #[rhai_fn(get = "power")]
} else { pub fn get_power(bunny: &mut SharedBunnyType) -> bool {
bunny.borrow_mut().go(); bunny.borrow().is_going()
} }
#[rhai_fn(set = "power")]
pub fn set_power(bunny: &mut SharedBunnyType, on: bool) {
if on {
if bunny.borrow().is_going() {
println!("Still going...");
} else { } else {
if bunny.borrow().is_going() { bunny.borrow_mut().go();
bunny.borrow_mut().stop(); }
} else { } else {
println!("Already out of battery!"); if bunny.borrow().is_going() {
} bunny.borrow_mut().stop();
} else {
println!("Already out of battery!");
} }
} }
).register_get("speed", |bunny: &mut SharedBunnyType| { }
#[rhai_fn(get = "speed")]
pub fn get_speed(bunny: &mut SharedBunnyType) -> i64 {
if bunny.borrow().is_going() { if bunny.borrow().is_going() {
bunny.borrow().get_speed() bunny.borrow().get_speed()
} else { } else {
0 0
} }
}).register_set_result("speed", |bunny: &mut SharedBunnyType, speed: i64| { }
#[rhai_fn(set = "speed", return_raw)]
pub fn set_speed(bunny: &mut SharedBunnyType, speed: i64)
-> Result<Dynamic, Box<EvalAltResult>>
{
if speed <= 0 { if speed <= 0 {
Err("Speed must be positive!".into()) Err("Speed must be positive!".into())
} else if speed > 100 { } else if speed > MAX_SPEED {
Err("Bunny will be going too fast!".into()) Err("Bunny will be going too fast!".into())
} else if !bunny.borrow().is_going() { } else if !bunny.borrow().is_going() {
Err("Bunny is not yet going!".into()) Err("Bunny is not yet going!".into())
@ -115,15 +130,20 @@ engine
b.borrow_mut().set_speed(speed); b.borrow_mut().set_speed(speed);
Ok(().into()) Ok(().into())
} }
}).register_fn("turn_left", |bunny: &mut SharedBunnyType| { }
pub fn turn_left(bunny: &mut SharedBunnyType) {
if bunny.borrow().is_going() { if bunny.borrow().is_going() {
bunny.borrow_mut().turn(true); bunny.borrow_mut().turn(true);
} }
}).register_fn("turn_right", |bunny: &mut SharedBunnyType| { }
pub fn turn_right(bunny: &mut SharedBunnyType) {
if bunny.borrow().is_going() { if bunny.borrow().is_going() {
bunny.borrow_mut().turn(false); bunny.borrow_mut().turn(false);
} }
}); }
}
engine.load_package(exported_module!(bunny_api));
``` ```
### Push Constant Command Object into Custom Scope ### Push Constant Command Object into Custom Scope
@ -132,7 +152,9 @@ engine
let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new())); let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new()));
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push_constant("BUNNY", bunny.clone());
// Add the command object into a custom Scope.
scope.push_constant("Bunny", bunny.clone());
engine.consume_with_scope(&mut scope, script)?; engine.consume_with_scope(&mut scope, script)?;
``` ```
@ -140,11 +162,11 @@ engine.consume_with_scope(&mut scope, script)?;
### Use the Command API in Script ### Use the Command API in Script
```rust ```rust
// Access the command object via constant variable 'BUNNY'. // Access the command object via constant variable 'Bunny'.
if !BUNNY.power { BUNNY.power = true; } if !Bunny.power { Bunny.power = true; }
if BUNNY.speed > 50 { BUNNY.speed = 50; } if Bunny.speed > 50 { Bunny.speed = 50; }
BUNNY.turn_left(); Bunny.turn_left();
``` ```

View File

@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module].
Macros Macros
------ ------
| Macro | Apply to | Description | | Macro | Signature | Description |
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- | | ----------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
| `#[export_fn]` | rust function defined in a Rust module | exports the function | | `#[export_fn]` | apply to rust function defined in a Rust module | exports the function |
| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | registers the function into an [`Engine`] under specific name | | `register_exported_fn!` | `register_exported_fn!(&mut `_engine_`, "`_name_`", `_function_`)` | registers the function into an [`Engine`] under a specific name |
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name | | `set_exported_fn!` | `set_exported_fn!(&mut `_module_`, "`_name_`", `_function_`)` | registers the function into a [`Module`] under a specific name |
`#[export_fn]` and `register_exported_fn!` `#[export_fn]` and `register_exported_fn!`

View File

@ -29,9 +29,9 @@ use rhai::plugins::*; // a "prelude" import for macros
#[export_module] #[export_module]
mod my_module { mod my_module {
// This constant will be registered as the constant variable 'SOME_NUMBER'. // This constant will be registered as the constant variable 'MY_NUMBER'.
// Ignored when loaded as a package. // Ignored when loaded as a package.
pub const SOME_NUMBER: i64 = 42; pub const MY_NUMBER: i64 = 42;
// This function will be registered as 'greet'. // This function will be registered as 'greet'.
pub fn greet(name: &str) -> String { pub fn greet(name: &str) -> String {
@ -260,12 +260,12 @@ Inner attributes can be applied to the inner items of a module to tweak the expo
Parameters should be set on inner attributes to specify the desired behavior. Parameters should be set on inner attributes to specify the desired behavior.
| Attribute Parameter | Use with | Apply to | Description | | Attribute Parameter | Use with | Apply to | Description |
| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | | ------------------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module | | `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module |
| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name | | `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name |
| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property | | `get = "..."` | `#[rhai_fn]` | `pub fn (&mut Type) -> Value` | registers a getter for the named property |
| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property | | `set = "..."` | `#[rhai_fn]` | `pub fn (&mut Type, Value)` | registers a setter for the named property |
| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter | | `index_get` | `#[rhai_fn]` | `pub fn (&mut Type, INT) -> Value` | registers an index getter |
| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter | | `index_set` | `#[rhai_fn]` | `pub fn (&mut Type, INT, Value)` | registers an index setter |
| `return_raw` | `#[rhai_fn]` | function returning `Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] | | `return_raw` | `#[rhai_fn]` | `pub fn (...) -> Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |

View File

@ -53,10 +53,10 @@ impl TestStruct {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine engine
.register_type::<TestStruct>() .register_type::<TestStruct>()
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field) .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
.register_fn("new_ts", TestStruct::new); .register_fn("new_ts", TestStruct::new);
let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?; let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;

View File

@ -0,0 +1,88 @@
Create a Module from an AST
==========================
{{#include ../../links.md}}
`Module::eval_ast_as_new`
------------------------
A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables,
functions and sub-modules via the `Module::eval_ast_as_new` method.
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module:
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
* Functions not specifically marked `private`.
* Global modules that remain in the [`Scope`] at the end of a script run.
`merge_namespaces` Parameter
---------------------------
The parameter `merge_namespaces` in `Module::eval_ast_as_new` determines the exact behavior of
functions exposed by the module and the namespace that they can access:
| `merge_namespaces` value | Description | Namespace | Performance | Call global functions | Call functions in same module |
| :----------------------: | ------------------------------------------------ | :-----------------: | :---------: | :-------------------: | :---------------------------: |
| `true` | encapsulate entire `AST` into each function call | module, then global | slower | yes | yes |
| `false` | register each function independently | global only | fast | yes | no |
Examples
--------
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
other than non-[`private`] functions (unless that's intentional).
```rust
use rhai::{Engine, Module};
let engine = Engine::new();
// Compile a script into an 'AST'
let ast = engine.compile(r#"
// Functions become module functions
fn calc(x) {
x + 1
}
fn add_len(x, y) {
x + y.len
}
// Imported modules can become sub-modules
import "another module" as extra;
// Variables defined at global level can become module variables
const x = 123;
let foo = 41;
let hello;
// Variable values become constant module variable values
foo = calc(foo);
hello = "hello, " + foo + " worlds!";
// Finally, export the variables and modules
export
x as abc, // aliased variable name
foo,
hello,
extra as foobar; // export sub-module
"#)?;
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
//
// The second parameter ('merge_namespaces'), when set to true, will encapsulate
// a copy of the entire 'AST' into each function, allowing functions in the module script
// to cross-call each other.
//
// This incurs additional overhead, avoidable by setting 'merge_namespaces' to false.
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
// 'module' now contains:
// - sub-module: 'foobar' (renamed from 'extra')
// - functions: 'calc', 'add_len'
// - constants: 'abc' (renamed from 'x'), 'foo', 'hello'
```

View File

@ -22,19 +22,38 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai
Make the `Module` Available to the `Engine` Make the `Module` Available to the `Engine`
------------------------------------------ ------------------------------------------
In order to _use_ a custom module, there must be a [module resolver], which serves the module when `Engine::load_package` supports loading a [module] as a [package].
loaded via `import` statements.
Since it acts as a [package], all functions will be registered into the _global_ namespace
and can be accessed without _module qualifiers_.
```rust
use rhai::{Engine, Module};
let mut module = Module::new(); // new module
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
// Load the module into the Engine as a new package.
let mut engine = Engine::new();
engine.load_package(module);
engine.eval::<i64>("inc(41)")? == 42; // no need to import module
```
Make the `Module` Dynamically Loadable
-------------------------------------
In order to dynamically load a custom module, there must be a [module resolver] which serves
the module when loaded via `import` statements.
The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such
a custom module. a custom module.
```rust ```rust
use rhai::{Engine, Scope, Module, i64}; use rhai::{Engine, Scope, Module};
use rhai::module_resolvers::StaticModuleResolver; use rhai::module_resolvers::StaticModuleResolver;
let mut engine = Engine::new();
let mut scope = Scope::new();
let mut module = Module::new(); // new module let mut module = Module::new(); // new module
module.set_var("answer", 41_i64); // variable 'answer' under module module.set_var("answer", 41_i64); // variable 'answer' under module
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
@ -47,11 +66,12 @@ let mut resolver = StaticModuleResolver::new();
resolver.insert("question", module); resolver.insert("question", module);
// Set the module resolver into the 'Engine' // Set the module resolver into the 'Engine'
let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(Some(resolver));
// Use module-qualified variables // Use module-qualified variables
engine.eval::<i64>(&scope, r#"import "question" as q; q::answer + 1"#)? == 42; engine.eval::<i64>(r#"import "question" as q; q::answer + 1"#)? == 42;
// Call module-qualified functions // Call module-qualified functions
engine.eval::<i64>(&scope, r#"import "question" as q; q::inc(q::answer)"#)? == 42; engine.eval::<i64>(r#"import "question" as q; q::inc(q::answer)"#)? == 42;
``` ```

View File

@ -11,8 +11,14 @@ A module resolver must implement the trait [`rhai::ModuleResolver`][traits],
which contains only one function: `resolve`. which contains only one function: `resolve`.
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name
of the _module path_ (i.e. the path specified in the [`import`] statement). Upon success, it should of the _module path_ (i.e. the path specified in the [`import`] statement).
return a [`Module`]; if the module cannot be load, return `EvalAltResult::ErrorModuleNotFound`.
* Upon success, it should return a [`Module`].
* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`.
* If the module failed to load, return `EvalAltResult::ErrorInModule`.
Example Example
------- -------
@ -35,9 +41,12 @@ impl ModuleResolver for MyModuleResolver {
// Check module path. // Check module path.
if is_valid_module_path(path) { if is_valid_module_path(path) {
// Load the custom module. // Load the custom module.
let module: Module = load_secret_module(path); load_secret_module(path).map_err(|err|
Ok(module) // Return EvalAltResult::ErrorInModule upon loading error
EvalAltResult::ErrorInModule(err.to_string(), pos).into()
)
} else { } else {
// Return EvalAltResult::ErrorModuleNotFound if the path is invalid
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
} }
} }
@ -50,7 +59,7 @@ engine.set_module_resolver(Some(MyModuleResolver {}));
engine.consume(r#" engine.consume(r#"
import "hello" as foo; // this 'import' statement will call import "hello" as foo; // this 'import' statement will call
// 'MyModuleResolver::resolve' with "hello" as path // 'MyModuleResolver::resolve' with "hello" as `path`
foo:bar(); foo:bar();
"#)?; "#)?;
``` ```

View File

@ -12,16 +12,120 @@ Built-In Module Resolvers
------------------------ ------------------------
There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver`
which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. which simply loads a script file based on the path (with `.rhai` extension attached)
and execute it to form a module.
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | Namespace |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: | `FileModuleResolver` (default)
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | Module (cannot access global namespace) | -----------------------------
| `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>**Note:** All functions are assumed absolutely _pure_ and cannot cross-call each other.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | Global |
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | Global | The _default_ module resolution service, not available for [`no_std`] or [WASM] builds.
| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.<br/>This is useful when multiple types of modules are needed simultaneously. | Global | Loads a script file (based off the current directory) with `.rhai` extension.
All functions in the _global_ namespace, plus all those defined in the same module,
are _merged_ into a _unified_ namespace.
```rust
------------------
| my_module.rhai |
------------------
private fn inner_message() { "hello! from module!" }
fn greet(callback) { print(callback.call()); }
-------------
| main.rhai |
-------------
fn main_message() { "hi! from main!" }
import "my_module" as m;
m::greet(|| "hello, " + "world!"); // works - anonymous function in global
m::greet(|| inner_message()); // works - function in module
m::greet(|| main_message()); // works - function in global
```
The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function.
`FileModuleResolver::create_module` loads a script file and returns a module.
`GlobalFileModuleResolver`
-------------------------
A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules.
Not available for [`no_std`] or [WASM] builds.
Loads a script file (based off the current directory) with `.rhai` extension.
All functions are assumed **independent** and _cannot_ cross-call each other.
Functions are searched _only_ in the _global_ namespace.
```rust
------------------
| my_module.rhai |
------------------
private fn inner_message() { "hello! from module!" }
fn greet_inner() {
print(inner_message()); // cross-calling a module function!
// there will be trouble because each function
// in the module is supposed to be independent
// of each other
}
fn greet() {
print(main_message()); // function is searched in global namespace
}
-------------
| main.rhai |
-------------
fn main_message() { "hi! from main!" }
import "my_module" as m;
m::greet_inner(); // <- function not found: 'inner_message'
m::greet(); // works because 'main_message' exists in
// the global namespace
```
The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function.
`GlobalFileModuleResolver::create_module` loads a script file and returns a module.
`StaticModuleResolver`
---------------------
Loads modules that are statically added. This can be used under [`no_std`].
Functions are searched in the _global_ namespace by default.
```rust
use rhai::{Module, module_resolvers::StaticModuleResolver};
let module: Module = create_a_module();
let mut resolver = StaticModuleResolver::new();
resolver.insert("my_module", module);
```
`ModuleResolversCollection`
--------------------------
A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously.
Set into `Engine` Set into `Engine`
@ -30,8 +134,14 @@ Set into `Engine`
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
```rust ```rust
// Use the 'StaticModuleResolver' use rhai::module_resolvers::StaticModuleResolver;
let resolver = rhai::module_resolvers::StaticModuleResolver::new();
// Create a module resolver
let resolver = StaticModuleResolver::new();
// Register functions into 'resolver'...
// Use the module resolver
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(Some(resolver));
// Effectively disable 'import' statements by setting module resolver to 'None' // Effectively disable 'import' statements by setting module resolver to 'None'

View File

@ -34,9 +34,11 @@ Macro Parameters
```rust ```rust
// Import necessary types and traits. // Import necessary types and traits.
use rhai::{ use rhai::{
def_package, def_package, // 'def_package!' macro
packages::Package, packages::Package, // 'Package' trait
packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage} packages::{ // pre-defined packages
ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage
}
}; };
// Define the package 'MyPackage'. // Define the package 'MyPackage'.

View File

@ -9,10 +9,12 @@ Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package
packages to be used. packages to be used.
Packages typically contain Rust functions that are callable within a Rhai script. Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). All functions registered in a package is loaded under the _global namespace_
(i.e. they're available without module qualifiers).
Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) among multiple instances of [`Engine`], Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`)
even across threads (under [`sync`]). Therefore, a package only has to be created _once_. among multiple instances of [`Engine`], even across threads (under [`sync`]).
Therefore, a package only has to be created _once_.
```rust ```rust
use rhai::Engine; use rhai::Engine;

View File

@ -84,12 +84,12 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result | | Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | --------------------------------------------------------- | | ------------------------------ | ------------------------------------- | ----------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value | | [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
| Custom type | `args[n].read_lock::<T>().unwrap()` | immutable reference to value | | [Custom type] | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value.<br/>The original value becomes `()` | | [Custom type] (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value; the original value becomes `()` |
| `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value | | `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value |
When there is a mutable reference to the `this` object (i.e. the first argument), When there is a mutable reference to the `this` object (i.e. the first argument),
there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain.

View File

@ -105,6 +105,17 @@ let x: MyStruct = from_dynamic(&result)?;
``` ```
Cannot Deserialize Shared Values
-------------------------------
A [`Dynamic`] containing a _shared_ value cannot be deserialized - i.e. it will give a type error.
Use `Dynamic::flatten` to obtain a cloned copy before deserialization
(if the value is not shared, it is simply returned and not cloned).
Shared values are turned off via the [`no_closure`] feature.
Lighter Alternative Lighter Alternative
------------------- -------------------

View File

@ -11,11 +11,15 @@ Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including
It is highly recommended that [`Engine`]'s be created immutable as much as possible. It is highly recommended that [`Engine`]'s be created immutable as much as possible.
```rust ```rust
// Use the fluent API to configure an 'Engine' and then keep an immutable instance. let mut engine = Engine::new();
let engine = Engine::new()
.register_get("field", get_field) // Use the fluent API to configure an 'Engine'
.register_set("field", set_field) engine.register_get("field", get_field)
.register_fn("do_work", action); .register_set("field", set_field)
.register_fn("do_work", action);
// Then turn it into an immutable instance
let engine = engine;
// 'engine' is immutable... // 'engine' is immutable...
``` ```

View File

@ -159,6 +159,8 @@ pub enum Union {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Map(Box<Map>), Map(Box<Map>),
FnPtr(Box<FnPtr>), FnPtr(Box<FnPtr>),
#[cfg(not(feature = "no_std"))]
TimeStamp(Box<Instant>),
Variant(Box<Box<dyn Variant>>), Variant(Box<Box<dyn Variant>>),
@ -313,6 +315,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_) => TypeId::of::<Map>(),
Union::FnPtr(_) => TypeId::of::<FnPtr>(), Union::FnPtr(_) => TypeId::of::<FnPtr>(),
#[cfg(not(feature = "no_std"))]
Union::TimeStamp(_) => TypeId::of::<Instant>(),
Union::Variant(value) => (***value).type_id(), Union::Variant(value) => (***value).type_id(),
@ -345,9 +349,9 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => "map", Union::Map(_) => "map",
Union::FnPtr(_) => "Fn", Union::FnPtr(_) => "Fn",
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp", Union::TimeStamp(_) => "timestamp",
Union::Variant(value) => (***value).type_name(), Union::Variant(value) => (***value).type_name(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -375,10 +379,6 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
} else if name == type_name::<FnPtr>() { } else if name == type_name::<FnPtr>() {
"Fn" "Fn"
} else { } else {
#[cfg(not(feature = "no_std"))]
if name == type_name::<Instant>() {
return "timestamp";
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if name == type_name::<Array>() { if name == type_name::<Array>() {
return "array"; return "array";
@ -387,6 +387,10 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
if name == type_name::<Map>() { if name == type_name::<Map>() {
return "map"; return "map";
} }
#[cfg(not(feature = "no_std"))]
if name == type_name::<Instant>() {
return "timestamp";
}
name name
} }
@ -410,9 +414,9 @@ impl fmt::Display for Dynamic {
fmt::Debug::fmt(value, f) fmt::Debug::fmt(value, f)
} }
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => f.write_str("<timestamp>"), Union::TimeStamp(_) => f.write_str("<timestamp>"),
Union::Variant(value) => f.write_str((*value).type_name()), Union::Variant(value) => f.write_str((*value).type_name()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -449,9 +453,9 @@ impl fmt::Debug for Dynamic {
fmt::Debug::fmt(value, f) fmt::Debug::fmt(value, f)
} }
Union::FnPtr(value) => fmt::Debug::fmt(value, f), Union::FnPtr(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::TimeStamp(_) => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -485,6 +489,8 @@ impl Clone for Dynamic {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())), Union::Map(ref value) => Self(Union::Map(value.clone())),
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
#[cfg(not(feature = "no_std"))]
Union::TimeStamp(ref value) => Self(Union::TimeStamp(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(), Union::Variant(ref value) => (***value).clone_into_dynamic(),
@ -601,6 +607,14 @@ impl Dynamic {
Err(val) => val, Err(val) => val,
}; };
#[cfg(not(feature = "no_std"))]
{
boxed = match unsafe_cast_box::<_, Instant>(boxed) {
Ok(timestamp) => return (*timestamp).into(),
Err(val) => val,
}
}
Self(Union::Variant(Box::new(boxed))) Self(Union::Variant(Box::new(boxed)))
} }
@ -629,7 +643,7 @@ impl Dynamic {
}; };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
unimplemented!() panic!("converting into a shared value is not supported under 'no_closure'");
} }
/// Convert the `Dynamic` value into specific type. /// Convert the `Dynamic` value into specific type.
@ -738,6 +752,14 @@ impl Dynamic {
}; };
} }
#[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match self.0 {
Union::TimeStamp(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 { return match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value) => unsafe_try_cast(value),
@ -781,7 +803,20 @@ impl Dynamic {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn cast<T: Variant + Clone>(self) -> T { pub fn cast<T: Variant + Clone>(self) -> T {
self.try_cast::<T>().unwrap() let self_type_name = if self.is_shared() {
// Avoid panics/deadlocks with shared values
"<shared>"
} else {
self.type_name()
};
self.try_cast::<T>().unwrap_or_else(|| {
panic!(
"cannot cast {} value and to {}",
self_type_name,
type_name::<T>()
)
})
} }
/// Flatten the `Dynamic` and clone it. /// Flatten the `Dynamic` and clone it.
@ -980,6 +1015,13 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match &self.0 {
Union::TimeStamp(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match &self.0 { return match &self.0 {
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value), Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value),
@ -1055,6 +1097,13 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_std"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match &mut self.0 {
Union::TimeStamp(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match &mut self.0 { return match &mut self.0 {
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value), Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value),
@ -1256,3 +1305,10 @@ impl From<Box<FnPtr>> for Dynamic {
Self(Union::FnPtr(value)) Self(Union::FnPtr(value))
} }
} }
#[cfg(not(feature = "no_std"))]
impl From<Instant> for Dynamic {
#[inline(always)]
fn from(value: Instant) -> Self {
Self(Union::TimeStamp(Box::new(value)))
}
}

View File

@ -769,7 +769,7 @@ impl Engine {
let args = &mut [val, &mut idx_val2, &mut new_val]; let args = &mut [val, &mut idx_val2, &mut new_val];
self.exec_fn_call( self.exec_fn_call(
state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, &None,
level, level,
) )
.map_err(|err| match *err { .map_err(|err| match *err {
@ -798,8 +798,9 @@ impl Engine {
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
self.make_method_call( self.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, *native, false, state, lib, name, *hash, target, idx_val, &def_val, *native, false,
level, level,
) )
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
@ -833,7 +834,7 @@ impl Engine {
let mut new_val = new_val; let mut new_val = new_val;
let mut args = [target.as_mut(), new_val.as_mut().unwrap()]; let mut args = [target.as_mut(), new_val.as_mut().unwrap()];
self.exec_fn_call( self.exec_fn_call(
state, lib, setter, 0, &mut args, is_ref, true, false, None, None, state, lib, setter, 0, &mut args, is_ref, true, false, None, &None,
level, level,
) )
.map(|(v, _)| (v, true)) .map(|(v, _)| (v, true))
@ -844,7 +845,7 @@ impl Engine {
let ((_, getter, _), pos) = x.as_ref(); let ((_, getter, _), pos) = x.as_ref();
let mut args = [target.as_mut()]; let mut args = [target.as_mut()];
self.exec_fn_call( self.exec_fn_call(
state, lib, getter, 0, &mut args, is_ref, true, false, None, None, state, lib, getter, 0, &mut args, is_ref, true, false, None, &None,
level, level,
) )
.map(|(v, _)| (v, false)) .map(|(v, _)| (v, false))
@ -865,9 +866,10 @@ impl Engine {
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
let (val, _) = self let (val, _) = self
.make_method_call( .make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, state, lib, name, *hash, target, idx_val, &def_val,
*native, false, level, *native, false, level,
) )
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
@ -898,7 +900,7 @@ impl Engine {
let (mut val, updated) = self let (mut val, updated) = self
.exec_fn_call( .exec_fn_call(
state, lib, getter, 0, args, is_ref, true, false, None, state, lib, getter, 0, args, is_ref, true, false, None,
None, level, &None, level,
) )
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
@ -924,7 +926,7 @@ impl Engine {
arg_values[1] = val; arg_values[1] = val;
self.exec_fn_call( self.exec_fn_call(
state, lib, setter, 0, arg_values, is_ref, true, false, state, lib, setter, 0, arg_values, is_ref, true, false,
None, None, level, None, &None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -942,9 +944,10 @@ impl Engine {
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
let (mut val, _) = self let (mut val, _) = self
.make_method_call( .make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, state, lib, name, *hash, target, idx_val, &def_val,
*native, false, level, *native, false, level,
) )
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
@ -1202,7 +1205,7 @@ impl Engine {
let mut idx = idx; let mut idx = idx;
let args = &mut [val, &mut idx]; let args = &mut [val, &mut idx];
self.exec_fn_call( self.exec_fn_call(
state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level, state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, &None, _level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
.map_err(|err| match *err { .map_err(|err| match *err {
@ -1245,8 +1248,9 @@ impl Engine {
let op = "=="; let op = "==";
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
let def_value = Some(false.into());
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value]; let args = &mut [&mut lhs_value.clone(), value];
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
@ -1254,7 +1258,7 @@ impl Engine {
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
if self if self
.call_native_fn(state, lib, op, hash, args, false, false, def_value) .call_native_fn(state, lib, op, hash, args, false, false, &def_value)
.map_err(|err| err.new_position(rhs.position()))? .map_err(|err| err.new_position(rhs.position()))?
.0 .0
.as_bool() .as_bool()
@ -1264,7 +1268,7 @@ impl Engine {
} }
} }
Ok(false.into()) Ok(def_value.unwrap())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value)) => match lhs_value { Dynamic(Union::Map(rhs_value)) => match lhs_value {
@ -1395,7 +1399,7 @@ impl Engine {
// Run function // Run function
let (value, _) = self let (value, _) = self
.exec_fn_call( .exec_fn_call(
state, lib, op, 0, args, false, false, false, None, None, state, lib, op, 0, args, false, false, false, None, &None,
level, level,
) )
.map_err(|err| err.new_position(*op_pos))?; .map_err(|err| err.new_position(*op_pos))?;
@ -1431,7 +1435,7 @@ impl Engine {
&mut rhs_val, &mut rhs_val,
]; ];
self.exec_fn_call( self.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, None, level, state, lib, op, 0, args, false, false, false, None, &None, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))? .map_err(|err| err.new_position(*op_pos))?
@ -1499,8 +1503,9 @@ impl Engine {
// Normal function call // Normal function call
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref(); let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
self.make_function_call( self.make_function_call(
scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
false, *capture, level, false, *capture, level,
) )
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))

View File

@ -211,7 +211,7 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
pub_only: bool, pub_only: bool,
def_val: Option<bool>, def_val: &Option<Dynamic>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -221,6 +221,7 @@ impl Engine {
let func = self let func = self
.global_module .global_module
.get_fn(hash_fn, pub_only) .get_fn(hash_fn, pub_only)
.or_else(|| lib.get_fn(hash_fn, pub_only))
.or_else(|| self.packages.get_fn(hash_fn, pub_only)); .or_else(|| self.packages.get_fn(hash_fn, pub_only));
if let Some(func) = func { if let Some(func) = func {
@ -280,14 +281,14 @@ impl Engine {
// Return default value (if any) // Return default value (if any)
if let Some(val) = def_val { if let Some(val) = def_val {
return Ok((val.into(), false)); return Ok((val.clone(), false));
} }
// Getter function not found? // Getter function not found?
if let Some(prop) = extract_prop_from_getter(fn_name) { if let Some(prop) = extract_prop_from_getter(fn_name) {
return EvalAltResult::ErrorDotExpr( return EvalAltResult::ErrorDotExpr(
format!( format!(
"Unknown property '{}' for {}, or it is write-only", "Failed to get property '{}' of '{}' - the property may not exist, or it may be write-only",
prop, prop,
self.map_type_name(args[0].type_name()) self.map_type_name(args[0].type_name())
), ),
@ -300,9 +301,10 @@ impl Engine {
if let Some(prop) = extract_prop_from_setter(fn_name) { if let Some(prop) = extract_prop_from_setter(fn_name) {
return EvalAltResult::ErrorDotExpr( return EvalAltResult::ErrorDotExpr(
format!( format!(
"Unknown property '{}' for {}, or it is read-only", "Failed to set property '{}' of '{}' - the property may not exist, may be read-only, or '{}' is the wrong type",
prop, prop,
self.map_type_name(args[0].type_name()) self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()),
), ),
Position::none(), Position::none(),
) )
@ -439,9 +441,9 @@ impl Engine {
// First check script-defined functions // First check script-defined functions
lib.contains_fn(hash_script, pub_only) lib.contains_fn(hash_script, pub_only)
//|| lib.contains_fn(hash_fn, pub_only) || lib.contains_fn(hash_fn, pub_only)
// Then check registered functions // Then check registered functions
//|| self.global_module.contains_fn(hash_script, pub_only) || self.global_module.contains_fn(hash_script, pub_only)
|| self.global_module.contains_fn(hash_fn, pub_only) || self.global_module.contains_fn(hash_fn, pub_only)
// Then check packages // Then check packages
|| self.packages.contains_fn(hash_script, pub_only) || self.packages.contains_fn(hash_script, pub_only)
@ -467,7 +469,7 @@ impl Engine {
_is_method: bool, _is_method: bool,
pub_only: bool, pub_only: bool,
_capture: Option<Scope>, _capture: Option<Scope>,
def_val: Option<bool>, def_val: &Option<Dynamic>,
_level: usize, _level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Check for data race. // Check for data race.
@ -522,58 +524,76 @@ impl Engine {
.into() .into()
} }
// Normal script function call // Script-like function found
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ if lib.contains_fn(hash_script, pub_only) _ if self.global_module.contains_fn(hash_script, pub_only)
|| lib.contains_fn(hash_script, pub_only)
|| self.packages.contains_fn(hash_script, pub_only) => || self.packages.contains_fn(hash_script, pub_only) =>
{ {
// Get scripted function // Get function
let func = lib let func = self
.global_module
.get_fn(hash_script, pub_only) .get_fn(hash_script, pub_only)
.or_else(|| lib.get_fn(hash_script, pub_only))
.or_else(|| self.packages.get_fn(hash_script, pub_only)) .or_else(|| self.packages.get_fn(hash_script, pub_only))
.unwrap() .unwrap();
.get_fn_def();
let scope = &mut Scope::new(); if func.is_script() {
let mods = &mut Imports::new(); let func = func.get_fn_def();
// Add captured variables into scope let scope = &mut Scope::new();
#[cfg(not(feature = "no_closure"))] let mods = &mut Imports::new();
if let Some(captured) = _capture {
add_captured_variables_into_scope(&func.externals, captured, scope);
}
let result = if _is_method { // Add captured variables into scope
// Method call of script function - map first argument to `this` #[cfg(not(feature = "no_closure"))]
let (first, rest) = args.split_first_mut().unwrap(); if let Some(captured) = _capture {
self.call_script_fn( add_captured_variables_into_scope(&func.externals, captured, scope);
scope, }
mods,
let result = if _is_method {
// Method call of script function - map first argument to `this`
let (first, rest) = args.split_first_mut().unwrap();
self.call_script_fn(
scope,
mods,
state,
lib,
&mut Some(*first),
fn_name,
func,
rest,
_level,
)?
} else {
// Normal call of script function - map first argument to `this`
// The first argument is a reference?
let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args);
let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, func, args, _level,
);
// Restore the original reference
backup.restore_first_arg(args);
result?
};
Ok((result, false))
} else {
// If it is a native function, redirect it
self.call_native_fn(
state, state,
lib, lib,
&mut Some(*first),
fn_name, fn_name,
func, hash_script,
rest, args,
_level, is_ref,
)? pub_only,
} else { def_val,
// Normal call of script function - map first argument to `this` )
// The first argument is a reference? }
let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args);
let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, func, args, _level,
);
// Restore the original reference
backup.restore_first_arg(args);
result?
};
Ok((result, false))
} }
// Normal native function call // Normal native function call
@ -649,7 +669,7 @@ impl Engine {
hash_script: u64, hash_script: u64,
target: &mut Target, target: &mut Target,
idx_val: Dynamic, idx_val: Dynamic,
def_val: Option<bool>, def_val: &Option<Dynamic>,
native: bool, native: bool,
pub_only: bool, pub_only: bool,
level: usize, level: usize,
@ -795,7 +815,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
name: &str, name: &str,
args_expr: &[Expr], args_expr: &[Expr],
def_val: Option<bool>, def_val: &Option<Dynamic>,
mut hash_script: u64, mut hash_script: u64,
native: bool, native: bool,
pub_only: bool, pub_only: bool,

View File

@ -145,7 +145,7 @@ impl FnPtr {
has_this, has_this,
true, true,
None, None,
None, &None,
0, 0,
) )
.map(|(v, _)| v) .map(|(v, _)| v)

View File

@ -93,8 +93,13 @@ pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
pub use syntax::{EvalContext, Expression}; pub use syntax::{EvalContext, Expression};
pub use token::Position; pub use token::Position;
#[cfg(feature = "internals")]
pub use utils::calc_fn_hash; pub use utils::calc_fn_hash;
#[cfg(not(feature = "internals"))]
pub(crate) use utils::calc_fn_hash;
pub use rhai_codegen::*; pub use rhai_codegen::*;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -118,8 +123,6 @@ pub use parser::FLOAT;
pub use module::ModuleResolver; pub use module::ModuleResolver;
/// Module containing all built-in _module resolvers_ available to Rhai. /// Module containing all built-in _module resolvers_ available to Rhai.
///
/// Not available under the `no_module` feature.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod module_resolvers { pub mod module_resolvers {
pub use crate::module::resolvers::*; pub use crate::module::resolvers::*;

View File

@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
use crate::fn_register::by_value as cast_arg; use crate::fn_register::by_value as cast_arg;
use crate::parser::{FnAccess, FnAccess::Public}; use crate::parser::{FnAccess, FnAccess::Public};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
@ -38,19 +38,17 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_std"))]
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell;
#[cfg(not(feature = "no_std"))]
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// Return type of module-level Rust function. /// Return type of module-level Rust function.
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>; pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
pub type FuncInfo = (
String,
FnAccess,
usize,
Option<StaticVec<TypeId>>,
CallableFunction,
);
/// An imported module, which may contain variables, sub-modules, /// An imported module, which may contain variables, sub-modules,
/// external Rust functions, and script-defined functions. /// external Rust functions, and script-defined functions.
/// ///
@ -67,18 +65,14 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>, all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions. /// External Rust functions.
functions: HashMap< functions: HashMap<u64, FuncInfo, StraightHasherBuilder>,
u64,
(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func),
StraightHasherBuilder,
>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted, /// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules. /// including those in sub-modules.
all_functions: HashMap<u64, Func, StraightHasherBuilder>, all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
/// Is the module indexed? /// Is the module indexed?
indexed: bool, indexed: bool,
@ -391,7 +385,7 @@ impl Module {
name: impl Into<String>, name: impl Into<String>,
access: FnAccess, access: FnAccess,
arg_types: &[TypeId], arg_types: &[TypeId],
func: Func, func: CallableFunction,
) -> u64 { ) -> u64 {
let name = name.into(); let name = name.into();
@ -491,11 +485,18 @@ impl Module {
let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| { let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| {
func(engine, lib, args).map(Dynamic::from) func(engine, lib, args).map(Dynamic::from)
}; };
self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f))) self.set_fn(
name,
Public,
arg_types,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Set a raw function but with a signature that is a scripted function, but the implementation is in Rust. /// Set a raw function but with a signature that is a scripted function (meaning that the types
/// are not determined), but the implementation is in Rust.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
pub(crate) fn set_raw_fn_as_scripted( pub(crate) fn set_raw_fn_as_scripted(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
@ -513,7 +514,7 @@ impl Module {
FnAccess::Public, FnAccess::Public,
num_args, num_args,
None, None,
Func::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
), ),
); );
self.indexed = false; self.indexed = false;
@ -540,7 +541,12 @@ impl Module {
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, _: &mut FnCallArgs| func().map(Dynamic::from); let f = move |_: &Engine, _: &Module, _: &mut FnCallArgs| func().map(Dynamic::from);
let arg_types = []; let arg_types = [];
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking one parameter into the module, returning a hash key. /// Set a Rust function taking one parameter into the module, returning a hash key.
@ -565,7 +571,12 @@ impl Module {
func(cast_arg::<A>(&mut args[0])).map(Dynamic::from) func(cast_arg::<A>(&mut args[0])).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking one mutable parameter into the module, returning a hash key. /// Set a Rust function taking one mutable parameter into the module, returning a hash key.
@ -590,7 +601,12 @@ impl Module {
func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from) func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Set a Rust getter function taking one mutable parameter, returning a hash key. /// Set a Rust getter function taking one mutable parameter, returning a hash key.
@ -642,7 +658,12 @@ impl Module {
func(a, b).map(Dynamic::from) func(a, b).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking two parameters (the first one mutable) into the module, /// Set a Rust function taking two parameters (the first one mutable) into the module,
@ -673,7 +694,12 @@ impl Module {
func(a, b).map(Dynamic::from) func(a, b).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Set a Rust setter function taking two parameters (the first one mutable) into the module, /// Set a Rust setter function taking two parameters (the first one mutable) into the module,
@ -778,7 +804,12 @@ impl Module {
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking three parameters (the first one mutable) into the module, /// Set a Rust function taking three parameters (the first one mutable) into the module,
@ -815,7 +846,12 @@ impl Module {
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Set a Rust index setter taking three parameters (the first one mutable) into the module, /// Set a Rust index setter taking three parameters (the first one mutable) into the module,
@ -871,7 +907,7 @@ impl Module {
FN_IDX_SET, FN_IDX_SET,
Public, Public,
&arg_types, &arg_types,
Func::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
} }
@ -955,7 +991,12 @@ impl Module {
TypeId::of::<C>(), TypeId::of::<C>(),
TypeId::of::<D>(), TypeId::of::<D>(),
]; ];
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking four parameters (the first one mutable) into the module, /// Set a Rust function taking four parameters (the first one mutable) into the module,
@ -999,14 +1040,19 @@ impl Module {
TypeId::of::<C>(), TypeId::of::<C>(),
TypeId::of::<D>(), TypeId::of::<D>(),
]; ];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(
name,
Public,
&arg_types,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Get a Rust function. /// Get a Rust function.
/// ///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls. /// It is also returned by the `set_fn_XXX` calls.
pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> { pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> {
if hash_fn == 0 { if hash_fn == 0 {
None None
} else { } else {
@ -1025,7 +1071,7 @@ impl Module {
/// ///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// the hash calculated by `index_all_sub_modules`. /// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> { pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
self.all_functions.get(&hash_qualified_fn) self.all_functions.get(&hash_qualified_fn)
} }
@ -1090,7 +1136,9 @@ impl Module {
.iter() .iter()
.filter(|(_, (_, _, _, _, v))| match v { .filter(|(_, (_, _, _, _, v))| match v {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()), CallableFunction::Script(f) => {
_filter(f.access, f.name.as_str(), f.params.len())
}
_ => true, _ => true,
}) })
.map(|(&k, v)| (k, v.clone())), .map(|(&k, v)| (k, v.clone())),
@ -1112,7 +1160,7 @@ impl Module {
mut filter: impl FnMut(FnAccess, &str, usize) -> bool, mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
self.functions.retain(|_, (_, _, _, _, v)| match v { self.functions.retain(|_, (_, _, _, _, v)| match v {
Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), CallableFunction::Script(f) => filter(f.access, f.name.as_str(), f.params.len()),
_ => true, _ => true,
}); });
@ -1141,9 +1189,7 @@ impl Module {
} }
/// Get an iterator to the functions in the module. /// Get an iterator to the functions in the module.
pub(crate) fn iter_fn( pub(crate) fn iter_fn(&self) -> impl Iterator<Item = &FuncInfo> {
&self,
) -> impl Iterator<Item = &(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func)> {
self.functions.values() self.functions.values()
} }
@ -1162,13 +1208,23 @@ impl Module {
self.functions self.functions
.iter() .iter()
.for_each(|(_, (_, _, _, _, v))| match v { .for_each(|(_, (_, _, _, _, v))| match v {
Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()), CallableFunction::Script(f) => action(f.access, f.name.as_str(), f.params.len()),
_ => (), _ => (),
}); });
} }
/// Create a new `Module` by evaluating an `AST`. /// Create a new `Module` by evaluating an `AST`.
/// ///
/// ### `merge_namespaces` parameter
///
/// * If `true`, the entire `AST` is encapsulated into each function, allowing functions
/// to cross-call each other. Functions in the global namespace, plus all functions
/// defined in the module, are _merged_ into a _unified_ namespace before each call.
/// Therefore, all functions will be found, at the expense of some performance.
///
/// * If `false`, each function is registered independently and cannot cross-call
/// each other. Functions are searched in the global namespace.
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@ -1177,14 +1233,19 @@ impl Module {
/// ///
/// let engine = Engine::new(); /// let engine = Engine::new();
/// let ast = engine.compile("let answer = 42; export answer;")?; /// let ast = engine.compile("let answer = 42; export answer;")?;
/// let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; /// let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
/// assert!(module.contains_var("answer")); /// assert!(module.contains_var("answer"));
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> { pub fn eval_ast_as_new(
mut scope: Scope,
ast: &AST,
merge_namespaces: bool,
engine: &Engine,
) -> FuncReturn<Self> {
let mut mods = Imports::new(); let mut mods = Imports::new();
// Run the script // Run the script
@ -1207,7 +1268,44 @@ impl Module {
module.modules.insert(alias.to_string(), m); module.modules.insert(alias.to_string(), m);
}); });
module.merge(ast.lib()); #[cfg(not(feature = "no_function"))]
if merge_namespaces {
ast.iter_functions(|access, name, num_args| match access {
FnAccess::Private => (),
FnAccess::Public => {
let fn_name = name.to_string();
let ast_lib = ast.lib().clone();
module.set_raw_fn_as_scripted(
name,
num_args,
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let mut lib_merged = lib.clone();
lib_merged.merge(&ast_lib);
engine
.call_fn_dynamic_raw(
&mut Scope::new(),
&lib_merged,
&fn_name,
&mut None,
args,
)
.map_err(|err| {
// Wrap the error in a module-error
Box::new(EvalAltResult::ErrorInModule(
"".to_string(),
err,
Position::none(),
))
})
},
);
}
});
} else {
module.merge(ast.lib());
}
Ok(module) Ok(module)
} }
@ -1221,7 +1319,7 @@ impl Module {
module: &'a Module, module: &'a Module,
qualifiers: &mut Vec<&'a str>, qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>, variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, Func)>, functions: &mut Vec<(u64, CallableFunction)>,
) { ) {
for (name, m) in &module.modules { for (name, m) in &module.modules {
// Index all the sub-modules first. // Index all the sub-modules first.
@ -1372,573 +1470,10 @@ impl ModuleRef {
} }
} }
/// Trait that encapsulates a module resolution service. /// Re-export module resolver trait.
pub trait ModuleResolver: SendSync { #[cfg(not(feature = "no_module"))]
/// Resolve a module based on a path string. pub use resolvers::ModuleResolver;
fn resolve(&self, _: &Engine, path: &str, pos: Position) -> Result<Module, Box<EvalAltResult>>;
}
/// Re-export module resolvers. /// Re-export module resolvers.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod resolvers { pub mod resolvers;
pub use super::collection::ModuleResolversCollection;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub use super::file::{FileModuleResolver, GlobalFileModuleResolver};
pub use super::stat::StaticModuleResolver;
}
#[cfg(feature = "no_module")]
pub mod resolvers {}
/// Script file-based module resolver.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
mod file {
use super::*;
use crate::stdlib::path::PathBuf;
/// Module resolution service that loads module script files from the file system.
///
/// All functions in each module are treated as strictly _pure_ and cannot refer to
/// other functions within the same module. Functions are searched in the _global_ namespace.
///
/// For simple utility libraries, this usually performs better than the full `FileModuleResolver`.
///
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
///
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
/// allow specification of a base directory with module path used as a relative path offset
/// to the base directory. The script file is then forced to be in a specified extension
/// (default `.rhai`).
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug)]
pub struct GlobalFileModuleResolver {
path: PathBuf,
extension: String,
#[cfg(not(feature = "sync"))]
cache: RefCell<HashMap<PathBuf, AST>>,
#[cfg(feature = "sync")]
cache: RwLock<HashMap<PathBuf, AST>>,
}
impl Default for GlobalFileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl GlobalFileModuleResolver {
/// Create a new `GlobalFileModuleResolver` with a specific base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = GlobalFileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
/// Create a new `GlobalFileModuleResolver` with a specific base path and file extension.
///
/// The default extension is `.rhai`.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
) -> Self {
Self {
path: path.into(),
extension: extension.into(),
cache: Default::default(),
}
}
/// Create a new `GlobalFileModuleResolver` with the current directory as base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = GlobalFileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
/// Create a `Module` from a file path.
pub fn create_module<P: Into<PathBuf>>(
&self,
engine: &Engine,
path: &str,
) -> Result<Module, Box<EvalAltResult>> {
self.resolve(engine, path, Default::default())
}
}
impl ModuleResolver for GlobalFileModuleResolver {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
// Construct the script file path
let mut file_path = self.path.clone();
file_path.push(path);
file_path.set_extension(&self.extension); // Force extension
let scope = Default::default();
// See if it is cached
let (module, ast) = {
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
if let Some(ast) = c.get(&file_path) {
(
Module::eval_ast_as_new(scope, ast, engine)
.map_err(|err| err.new_position(pos))?,
None,
)
} else {
// Load the file and compile it if not found
let ast = engine
.compile_file(file_path.clone())
.map_err(|err| err.new_position(pos))?;
(
Module::eval_ast_as_new(scope, &ast, engine)
.map_err(|err| err.new_position(pos))?,
Some(ast),
)
}
};
if let Some(ast) = ast {
// Put it into the cache
#[cfg(not(feature = "sync"))]
self.cache.borrow_mut().insert(file_path, ast);
#[cfg(feature = "sync")]
self.cache.write().unwrap().insert(file_path, ast);
}
Ok(module)
}
}
/// Module resolution service that loads module script files from the file system.
///
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
///
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
/// allow specification of a base directory with module path used as a relative path offset
/// to the base directory. The script file is then forced to be in a specified extension
/// (default `.rhai`).
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug)]
pub struct FileModuleResolver {
path: PathBuf,
extension: String,
#[cfg(not(feature = "sync"))]
cache: RefCell<HashMap<PathBuf, AST>>,
#[cfg(feature = "sync")]
cache: RwLock<HashMap<PathBuf, AST>>,
}
impl Default for FileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl FileModuleResolver {
/// Create a new `FileModuleResolver` with a specific base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
/// Create a new `FileModuleResolver` with a specific base path and file extension.
///
/// The default extension is `.rhai`.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
) -> Self {
Self {
path: path.into(),
extension: extension.into(),
cache: Default::default(),
}
}
/// Create a new `FileModuleResolver` with the current directory as base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
/// Create a `Module` from a file path.
pub fn create_module<P: Into<PathBuf>>(
&self,
engine: &Engine,
path: &str,
) -> Result<Module, Box<EvalAltResult>> {
self.resolve(engine, path, Default::default())
}
}
impl ModuleResolver for FileModuleResolver {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
// Construct the script file path
let mut file_path = self.path.clone();
file_path.push(path);
file_path.set_extension(&self.extension); // Force extension
// See if it is cached
let exists = {
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
c.contains_key(&file_path)
};
if !exists {
// Load the file and compile it if not found
let ast = engine
.compile_file(file_path.clone())
.map_err(|err| err.new_position(pos))?;
// Put it into the cache
#[cfg(not(feature = "sync"))]
self.cache.borrow_mut().insert(file_path.clone(), ast);
#[cfg(feature = "sync")]
self.cache.write().unwrap().insert(file_path.clone(), ast);
}
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
let ast = c.get(&file_path).unwrap();
let mut _module = Module::eval_ast_as_new(Scope::new(), ast, engine)?;
#[cfg(not(feature = "no_function"))]
ast.iter_functions(|access, name, num_args| match access {
FnAccess::Private => (),
FnAccess::Public => {
let fn_name = name.to_string();
let ast_lib = ast.lib().clone();
_module.set_raw_fn_as_scripted(
name,
num_args,
move |engine: &Engine, _, args: &mut [&mut Dynamic]| {
engine.call_fn_dynamic_raw(
&mut Scope::new(),
&ast_lib,
&fn_name,
&mut None,
args,
)
},
);
}
});
Ok(_module)
}
}
}
/// Static module resolver.
#[cfg(not(feature = "no_module"))]
mod stat {
use super::*;
/// Module resolution service that serves modules added into it.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::StaticModuleResolver;
///
/// let mut resolver = StaticModuleResolver::new();
///
/// let module = Module::new();
/// resolver.insert("hello".to_string(), module);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug, Clone, Default)]
pub struct StaticModuleResolver(HashMap<String, Module>);
impl StaticModuleResolver {
/// Create a new `StaticModuleResolver`.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::StaticModuleResolver;
///
/// let mut resolver = StaticModuleResolver::new();
///
/// let module = Module::new();
/// resolver.insert("hello", module);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
}
impl StaticModuleResolver {
/// Add a module keyed by its path.
pub fn insert<S: Into<String>>(&mut self, path: S, mut module: Module) {
module.index_all_sub_modules();
self.0.insert(path.into(), module);
}
/// Remove a module given its path.
pub fn remove(&mut self, path: &str) -> Option<Module> {
self.0.remove(path)
}
/// Does the path exist?
pub fn contains_path(&self, path: &str) -> bool {
self.0.contains_key(path)
}
/// Get an iterator of all the modules.
pub fn iter(&self) -> impl Iterator<Item = (&str, &Module)> {
self.0.iter().map(|(k, v)| (k.as_str(), v))
}
/// Get a mutable iterator of all the modules.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Module)> {
self.0.iter_mut().map(|(k, v)| (k.as_str(), v))
}
/// Get an iterator of all the module paths.
pub fn paths(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str)
}
/// Get an iterator of all the modules.
pub fn values(&self) -> impl Iterator<Item = &Module> {
self.0.values()
}
/// Get a mutable iterator of all the modules.
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Module> {
self.0.values_mut()
}
/// Remove all modules.
pub fn clear(&mut self) {
self.0.clear();
}
}
impl ModuleResolver for StaticModuleResolver {
fn resolve(
&self,
_: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
self.0
.get(path)
.cloned()
.ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
}
}
}
/// Module resolver collection.
#[cfg(not(feature = "no_module"))]
mod collection {
use super::*;
/// Module resolution service that holds a collection of module resolves,
/// to be searched in sequential order.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection};
///
/// let mut collection = ModuleResolversCollection::new();
///
/// let resolver = StaticModuleResolver::new();
/// collection.push(resolver);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(collection));
/// ```
#[derive(Default)]
pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
impl ModuleResolversCollection {
/// Create a new `ModuleResolversCollection`.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection};
///
/// let mut collection = ModuleResolversCollection::new();
///
/// let resolver = StaticModuleResolver::new();
/// collection.push(resolver);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(collection));
/// ```
pub fn new() -> Self {
Default::default()
}
}
impl ModuleResolversCollection {
/// Add a module keyed by its path.
pub fn push(&mut self, resolver: impl ModuleResolver + 'static) {
self.0.push(Box::new(resolver));
}
/// Get an iterator of all the module resolvers.
pub fn iter(&self) -> impl Iterator<Item = &dyn ModuleResolver> {
self.0.iter().map(|v| v.as_ref())
}
/// Remove all module resolvers.
pub fn clear(&mut self) {
self.0.clear();
}
}
impl ModuleResolver for ModuleResolversCollection {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
for resolver in self.0.iter() {
if let Ok(module) = resolver.resolve(engine, path, pos) {
return Ok(module);
}
}
EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()
}
}
}

View File

@ -0,0 +1,85 @@
use crate::engine::Engine;
use crate::module::{Module, ModuleResolver};
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{boxed::Box, vec::Vec};
/// Module resolution service that holds a collection of module resolves,
/// to be searched in sequential order.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection};
///
/// let mut collection = ModuleResolversCollection::new();
///
/// let resolver = StaticModuleResolver::new();
/// collection.push(resolver);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(collection));
/// ```
#[derive(Default)]
pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
impl ModuleResolversCollection {
/// Create a new `ModuleResolversCollection`.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::{StaticModuleResolver, ModuleResolversCollection};
///
/// let mut collection = ModuleResolversCollection::new();
///
/// let resolver = StaticModuleResolver::new();
/// collection.push(resolver);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(collection));
/// ```
pub fn new() -> Self {
Default::default()
}
}
impl ModuleResolversCollection {
/// Add a module keyed by its path.
pub fn push(&mut self, resolver: impl ModuleResolver + 'static) {
self.0.push(Box::new(resolver));
}
/// Get an iterator of all the module resolvers.
pub fn iter(&self) -> impl Iterator<Item = &dyn ModuleResolver> {
self.0.iter().map(|v| v.as_ref())
}
/// Remove all module resolvers.
pub fn clear(&mut self) {
self.0.clear();
}
}
impl ModuleResolver for ModuleResolversCollection {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
for resolver in self.0.iter() {
match resolver.resolve(engine, path, pos) {
Ok(module) => return Ok(module),
Err(err) => match *err {
EvalAltResult::ErrorModuleNotFound(_, _) => continue,
EvalAltResult::ErrorInModule(_, err, _) => return Err(err),
_ => panic!("ModuleResolver::resolve returns error that is not ErrorModuleNotFound or ErrorInModule"),
},
}
}
EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()
}
}

View File

@ -0,0 +1,183 @@
use crate::engine::Engine;
use crate::module::{Module, ModuleResolver};
use crate::parser::AST;
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::String};
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell;
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// Module resolution service that loads module script files from the file system.
///
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
///
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
/// allow specification of a base directory with module path used as a relative path offset
/// to the base directory. The script file is then forced to be in a specified extension
/// (default `.rhai`).
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
///
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug)]
pub struct FileModuleResolver {
path: PathBuf,
extension: String,
#[cfg(not(feature = "sync"))]
cache: RefCell<HashMap<PathBuf, AST>>,
#[cfg(feature = "sync")]
cache: RwLock<HashMap<PathBuf, AST>>,
}
impl Default for FileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl FileModuleResolver {
/// Create a new `FileModuleResolver` with a specific base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
/// Create a new `FileModuleResolver` with a specific base path and file extension.
///
/// The default extension is `.rhai`.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
) -> Self {
Self {
path: path.into(),
extension: extension.into(),
cache: Default::default(),
}
}
/// Create a new `FileModuleResolver` with the current directory as base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
/// Create a `Module` from a file path.
pub fn create_module<P: Into<PathBuf>>(
&self,
engine: &Engine,
path: &str,
) -> Result<Module, Box<EvalAltResult>> {
self.resolve(engine, path, Default::default())
}
}
impl ModuleResolver for FileModuleResolver {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
// Construct the script file path
let mut file_path = self.path.clone();
file_path.push(path);
file_path.set_extension(&self.extension); // Force extension
let scope = Default::default();
let module;
// See if it is cached
let ast = {
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
if let Some(ast) = c.get(&file_path) {
module = Module::eval_ast_as_new(scope, ast, true, engine).map_err(|err| {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
None
} else {
// Load the file and compile it if not found
let ast = engine.compile_file(file_path.clone()).map_err(|err| {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
module = Module::eval_ast_as_new(scope, &ast, true, engine).map_err(|err| {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
Some(ast)
}
};
if let Some(ast) = ast {
// Put it into the cache
#[cfg(not(feature = "sync"))]
self.cache.borrow_mut().insert(file_path, ast);
#[cfg(feature = "sync")]
self.cache.write().unwrap().insert(file_path, ast);
}
Ok(module)
}
}

View File

@ -0,0 +1,188 @@
use crate::engine::Engine;
use crate::module::{Module, ModuleResolver};
use crate::parser::AST;
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::String};
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell;
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// Module resolution service that loads module script files from the file system.
///
/// All functions in each module are treated as strictly independent and cannot refer to
/// other functions within the same module. Functions are searched in the _global_ namespace.
///
/// For simple utility libraries, this usually performs better than the full `FileModuleResolver`.
///
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
///
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
/// allow specification of a base directory with module path used as a relative path offset
/// to the base directory. The script file is then forced to be in a specified extension
/// (default `.rhai`).
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
///
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug)]
pub struct GlobalFileModuleResolver {
path: PathBuf,
extension: String,
#[cfg(not(feature = "sync"))]
cache: RefCell<HashMap<PathBuf, AST>>,
#[cfg(feature = "sync")]
cache: RwLock<HashMap<PathBuf, AST>>,
}
impl Default for GlobalFileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl GlobalFileModuleResolver {
/// Create a new `GlobalFileModuleResolver` with a specific base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = GlobalFileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
/// Create a new `GlobalFileModuleResolver` with a specific base path and file extension.
///
/// The default extension is `.rhai`.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
) -> Self {
Self {
path: path.into(),
extension: extension.into(),
cache: Default::default(),
}
}
/// Create a new `GlobalFileModuleResolver` with the current directory as base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = GlobalFileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
/// Create a `Module` from a file path.
pub fn create_module<P: Into<PathBuf>>(
&self,
engine: &Engine,
path: &str,
) -> Result<Module, Box<EvalAltResult>> {
self.resolve(engine, path, Default::default())
}
}
impl ModuleResolver for GlobalFileModuleResolver {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
// Construct the script file path
let mut file_path = self.path.clone();
file_path.push(path);
file_path.set_extension(&self.extension); // Force extension
let scope = Default::default();
let module;
// See if it is cached
let ast = {
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
if let Some(ast) = c.get(&file_path) {
module = Module::eval_ast_as_new(scope, ast, false, engine).map_err(|err| {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
None
} else {
// Load the file and compile it if not found
let ast = engine.compile_file(file_path.clone()).map_err(|err| {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
module = Module::eval_ast_as_new(scope, &ast, false, engine).map_err(|err| {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
Some(ast)
}
};
if let Some(ast) = ast {
// Put it into the cache
#[cfg(not(feature = "sync"))]
self.cache.borrow_mut().insert(file_path, ast);
#[cfg(feature = "sync")]
self.cache.write().unwrap().insert(file_path, ast);
}
Ok(module)
}
}

View File

@ -0,0 +1,40 @@
use crate::engine::Engine;
use crate::fn_native::SendSync;
use crate::module::Module;
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::boxed::Box;
mod collection;
pub use collection::ModuleResolversCollection;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
mod file;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub use file::FileModuleResolver;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
mod global_file;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
pub use global_file::GlobalFileModuleResolver;
mod stat;
pub use stat::StaticModuleResolver;
/// Trait that encapsulates a module resolution service.
pub trait ModuleResolver: SendSync {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
}

View File

@ -0,0 +1,96 @@
use crate::engine::Engine;
use crate::module::{Module, ModuleResolver};
use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{boxed::Box, collections::HashMap, string::String};
/// Module resolution service that serves modules added into it.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::StaticModuleResolver;
///
/// let mut resolver = StaticModuleResolver::new();
///
/// let module = Module::new();
/// resolver.insert("hello".to_string(), module);
///
/// let mut engine = Engine::new();
///
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug, Clone, Default)]
pub struct StaticModuleResolver(HashMap<String, Module>);
impl StaticModuleResolver {
/// Create a new `StaticModuleResolver`.
///
/// # Examples
///
/// ```
/// use rhai::{Engine, Module};
/// use rhai::module_resolvers::StaticModuleResolver;
///
/// let mut resolver = StaticModuleResolver::new();
///
/// let module = Module::new();
/// resolver.insert("hello", module);
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
}
impl StaticModuleResolver {
/// Add a module keyed by its path.
pub fn insert<S: Into<String>>(&mut self, path: S, module: Module) {
self.0.insert(path.into(), module);
}
/// Remove a module given its path.
pub fn remove(&mut self, path: &str) -> Option<Module> {
self.0.remove(path)
}
/// Does the path exist?
pub fn contains_path(&self, path: &str) -> bool {
self.0.contains_key(path)
}
/// Get an iterator of all the modules.
pub fn iter(&self) -> impl Iterator<Item = (&str, &Module)> {
self.0.iter().map(|(k, v)| (k.as_str(), v))
}
/// Get a mutable iterator of all the modules.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Module)> {
self.0.iter_mut().map(|(k, v)| (k.as_str(), v))
}
/// Get an iterator of all the module paths.
pub fn paths(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str)
}
/// Get an iterator of all the modules.
pub fn values(&self) -> impl Iterator<Item = &Module> {
self.0.values()
}
/// Get a mutable iterator of all the modules.
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Module> {
self.0.values_mut()
}
/// Remove all modules.
pub fn clear(&mut self) {
self.0.clear();
}
}
impl ModuleResolver for StaticModuleResolver {
fn resolve(&self, _: &Engine, path: &str, pos: Position) -> Result<Module, Box<EvalAltResult>> {
self.0
.get(path)
.cloned()
.ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
}
}

View File

@ -142,7 +142,7 @@ fn call_fn_with_constant_arguments(
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
true, true,
None, &None,
) )
.ok() .ok()
.map(|(v, _)| v) .map(|(v, _)| v)

View File

@ -187,9 +187,8 @@ mod string_functions {
pub fn contains_char(s: &str, ch: char) -> bool { pub fn contains_char(s: &str, ch: char) -> bool {
s.contains(ch) s.contains(ch)
} }
#[rhai_fn(name = "contains")]
#[inline(always)] #[inline(always)]
pub fn contains_string(s: &str, find: ImmutableString) -> bool { pub fn contains(s: &str, find: ImmutableString) -> bool {
s.contains(find.as_str()) s.contains(find.as_str())
} }
@ -230,13 +229,12 @@ mod string_functions {
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_string(s: &str, find: ImmutableString) -> INT { pub fn index_of(s: &str, find: ImmutableString) -> INT {
s.find(find.as_str()) s.find(find.as_str())
.map(|index| s[0..index].chars().count() as INT) .map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
#[rhai_fn(name = "sub_string")]
pub fn sub_string(s: &str, start: INT, len: INT) -> ImmutableString { pub fn sub_string(s: &str, start: INT, len: INT) -> ImmutableString {
let offset = if s.is_empty() || len <= 0 { let offset = if s.is_empty() || len <= 0 {
return "".to_string().into(); return "".to_string().into();
@ -272,7 +270,7 @@ mod string_functions {
} }
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
pub fn crop_string(s: &mut ImmutableString, start: INT, len: INT) { pub fn crop(s: &mut ImmutableString, start: INT, len: INT) {
let offset = if s.is_empty() || len <= 0 { let offset = if s.is_empty() || len <= 0 {
s.make_mut().clear(); s.make_mut().clear();
return; return;
@ -300,12 +298,12 @@ mod string_functions {
#[rhai_fn(name = "crop")] #[rhai_fn(name = "crop")]
#[inline(always)] #[inline(always)]
pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) { pub fn crop_string_starting_from(s: &mut ImmutableString, start: INT) {
crop_string(s, start, s.len() as INT); crop(s, start, s.len() as INT);
} }
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
#[inline(always)] #[inline(always)]
pub fn replace_string(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) { pub fn replace(s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString) {
*s = s.replace(find.as_str(), sub.as_str()).into(); *s = s.replace(find.as_str(), sub.as_str()).into();
} }
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
@ -338,6 +336,18 @@ mod string_functions {
pub fn prepend(x: &mut Array, y: &str) -> String { pub fn prepend(x: &mut Array, y: &str) -> String {
format!("{:?}{}", x, y) format!("{:?}{}", x, y)
} }
#[inline(always)]
pub fn split(s: &str, delimiter: ImmutableString) -> Array {
s.split(delimiter.as_str())
.map(Into::<Dynamic>::into)
.collect()
}
#[rhai_fn(name = "split")]
#[inline(always)]
pub fn split_char(s: &str, delimiter: char) -> Array {
s.split(delimiter).map(Into::<Dynamic>::into).collect()
}
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]

View File

@ -1,23 +1,23 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#[cfg(feature = "no_float")] use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT};
use super::{arithmetic::make_err, math_basic::MAX_INT};
use crate::any::Dynamic;
use crate::def_package; use crate::def_package;
use crate::parser::INT;
use crate::plugin::*; use crate::plugin::*;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(feature = "no_float")] use crate::stdlib::boxed::Box;
use crate::parser::INT;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::{Duration, Instant};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use instant::Instant; use instant::{Duration, Instant};
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions // Register date/time functions
@ -43,7 +43,7 @@ mod time_functions {
let seconds = timestamp.elapsed().as_secs(); let seconds = timestamp.elapsed().as_secs();
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
Err(make_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp.elapsed: {}", "Integer overflow for timestamp.elapsed: {}",
seconds seconds
))) )))
@ -70,18 +70,18 @@ mod time_functions {
let seconds = (ts2 - ts1).as_secs(); let seconds = (ts2 - ts1).as_secs();
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
Err(make_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp duration: -{}", "Integer overflow for timestamp duration: -{}",
seconds seconds
))) )))
} else { } else {
Ok(Dynamic::from(-(seconds as INT))) Ok((-(seconds as INT)).into())
} }
} else { } else {
let seconds = (ts1 - ts2).as_secs(); let seconds = (ts1 - ts2).as_secs();
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) { if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
Err(make_err(format!( Err(make_arithmetic_err(format!(
"Integer overflow for timestamp duration: {}", "Integer overflow for timestamp duration: {}",
seconds seconds
))) )))
@ -91,6 +91,126 @@ mod time_functions {
} }
} }
#[cfg(not(feature = "no_float"))]
pub mod float_functions {
fn add_impl(x: Instant, seconds: FLOAT) -> Result<Instant, Box<EvalAltResult>> {
if seconds < 0.0 {
subtract_impl(x, -seconds)
} else if cfg!(not(feature = "unchecked")) {
if seconds > (MAX_INT as FLOAT) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {}",
seconds
)))
} else {
x.checked_add(Duration::from_millis((seconds * 1000.0) as u64))
.ok_or_else(|| {
make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)",
seconds
))
})
}
} else {
Ok(x + Duration::from_millis((seconds * 1000.0) as u64))
}
}
fn subtract_impl(x: Instant, seconds: FLOAT) -> Result<Instant, Box<EvalAltResult>> {
if seconds < 0.0 {
add_impl(x, -seconds)
} else if cfg!(not(feature = "unchecked")) {
if seconds > (MAX_INT as FLOAT) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {}",
seconds
)))
} else {
x.checked_sub(Duration::from_millis((seconds * 1000.0) as u64))
.ok_or_else(|| {
make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)",
seconds
))
})
}
} else {
Ok(x - Duration::from_millis((seconds * 1000.0) as u64))
}
}
#[rhai_fn(return_raw, name = "+")]
pub fn add(x: Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> {
add_impl(x, seconds).map(Into::<Dynamic>::into)
}
#[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(x: &mut Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> {
*x = add_impl(*x, seconds)?;
Ok(().into())
}
#[rhai_fn(return_raw, name = "-")]
pub fn subtract(x: Instant, seconds: FLOAT) -> Result<Dynamic, Box<EvalAltResult>> {
subtract_impl(x, seconds).map(Into::<Dynamic>::into)
}
#[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(
x: &mut Instant,
seconds: FLOAT,
) -> Result<Dynamic, Box<EvalAltResult>> {
*x = subtract_impl(*x, seconds)?;
Ok(().into())
}
}
fn add_impl(x: Instant, seconds: INT) -> Result<Instant, Box<EvalAltResult>> {
if seconds < 0 {
subtract_impl(x, -seconds)
} else if cfg!(not(feature = "unchecked")) {
x.checked_add(Duration::from_secs(seconds as u64))
.ok_or_else(|| {
make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)",
seconds
))
})
} else {
Ok(x + Duration::from_secs(seconds as u64))
}
}
fn subtract_impl(x: Instant, seconds: INT) -> Result<Instant, Box<EvalAltResult>> {
if seconds < 0 {
add_impl(x, -seconds)
} else if cfg!(not(feature = "unchecked")) {
x.checked_sub(Duration::from_secs(seconds as u64))
.ok_or_else(|| {
make_arithmetic_err(format!(
"Timestamp overflow when adding {} second(s)",
seconds
))
})
} else {
Ok(x - Duration::from_secs(seconds as u64))
}
}
#[rhai_fn(return_raw, name = "+")]
pub fn add(x: Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
add_impl(x, seconds).map(Into::<Dynamic>::into)
}
#[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(x: &mut Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
*x = add_impl(*x, seconds)?;
Ok(().into())
}
#[rhai_fn(return_raw, name = "-")]
pub fn subtract(x: Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
subtract_impl(x, seconds).map(Into::<Dynamic>::into)
}
#[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(x: &mut Instant, seconds: INT) -> Result<Dynamic, Box<EvalAltResult>> {
*x = subtract_impl(*x, seconds)?;
Ok(().into())
}
#[rhai_fn(name = "==")] #[rhai_fn(name = "==")]
#[inline(always)] #[inline(always)]
pub fn eq(x: Instant, y: Instant) -> bool { pub fn eq(x: Instant, y: Instant) -> bool {

View File

@ -790,7 +790,7 @@ pub enum Expr {
Option<Box<ModuleRef>>, Option<Box<ModuleRef>>,
u64, u64,
StaticVec<Expr>, StaticVec<Expr>,
Option<bool>, Option<bool>, // Default value is `bool` in order for `Expr` to be `Hash`.
)>, )>,
), ),
/// expr op= expr /// expr op= expr

View File

@ -39,6 +39,9 @@ pub enum EvalAltResult {
/// An error has occurred inside a called function. /// An error has occurred inside a called function.
/// Wrapped values are the name of the function and the interior error. /// Wrapped values are the name of the function and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position), ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
/// An error has occurred while loading a module.
/// Wrapped value are the name of the module and the interior error.
ErrorInModule(String, Box<EvalAltResult>, Position),
/// Access to `this` that is not bound. /// Access to `this` that is not bound.
ErrorUnboundThis(Position), ErrorUnboundThis(Position),
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
@ -113,6 +116,7 @@ impl EvalAltResult {
Self::ErrorParsing(p, _) => p.desc(), Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorInModule(_, _, _) => "Error in module",
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorUnboundThis(_) => "'this' is not bound",
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
@ -178,8 +182,12 @@ impl fmt::Display for EvalAltResult {
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
Self::ErrorInFunctionCall(s, err, _) => { Self::ErrorInFunctionCall(s, err, _) => {
write!(f, "Error in call to function '{}' : {}", s, err)? write!(f, "Error in call to function '{}': {}", s, err)?
} }
Self::ErrorInModule(s, err, _) if s.is_empty() => {
write!(f, "Error in module: {}", err)?
}
Self::ErrorInModule(s, err, _) => write!(f, "Error in module '{}': {}", s, err)?,
Self::ErrorFunctionNotFound(s, _) Self::ErrorFunctionNotFound(s, _)
| Self::ErrorVariableNotFound(s, _) | Self::ErrorVariableNotFound(s, _)
@ -280,6 +288,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorInModule(_, _, pos)
| Self::ErrorUnboundThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
@ -321,6 +330,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorInModule(_, _, pos)
| Self::ErrorUnboundThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)

View File

@ -160,9 +160,8 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_) => self.deserialize_map(visitor), Union::Map(_) => self.deserialize_map(visitor),
Union::FnPtr(_) => self.type_error(), Union::FnPtr(_) => self.type_error(),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => self.type_error(), Union::TimeStamp(_) => self.type_error(),
Union::Variant(value) if value.is::<i8>() => self.deserialize_i8(visitor), Union::Variant(value) if value.is::<i8>() => self.deserialize_i8(visitor),
Union::Variant(value) if value.is::<i16>() => self.deserialize_i16(visitor), Union::Variant(value) if value.is::<i16>() => self.deserialize_i16(visitor),

View File

@ -262,7 +262,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
engine.set_module_resolver(Some(resolver1)); engine.set_module_resolver(Some(resolver1));
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?; let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
let mut resolver2 = StaticModuleResolver::new(); let mut resolver2 = StaticModuleResolver::new();
resolver2.insert("testing", module); resolver2.insert("testing", module);

View File

@ -34,12 +34,15 @@ fn test_packages() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
fn test_packages_with_script() -> Result<(), Box<EvalAltResult>> { fn test_packages_with_script() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let ast = engine.compile("fn foo(x) { x + 1 }")?; let ast = engine.compile("fn foo(x) { x + 1 } fn bar(x) { foo(x) + 1 }")?;
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
let module = Module::eval_ast_as_new(Scope::new(), &ast, false, &engine)?;
engine.load_package(module); engine.load_package(module);
assert_eq!(engine.eval::<INT>("foo(41)")?, 42); assert_eq!(engine.eval::<INT>("foo(41)")?, 42);
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
engine.load_package(module);
assert_eq!(engine.eval::<INT>("bar(40)")?, 42);
Ok(()) Ok(())
} }

View File

@ -186,3 +186,25 @@ fn test_string_fn() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
#[test]
fn test_string_split() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.split(' ').len"#
)?,
3
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.split("hello").len"#
)?,
2
);
Ok(())
}

View File

@ -50,3 +50,60 @@ fn test_timestamp() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_timestamp_op() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
#[cfg(not(feature = "no_float"))]
assert!(
(engine.eval::<FLOAT>(
r#"
let time1 = timestamp();
let time2 = time1 + 123.45;
time2 - time1
"#
)? - 123.45)
.abs()
< 0.001
);
#[cfg(not(feature = "no_float"))]
assert!(
(engine.eval::<FLOAT>(
r#"
let time1 = timestamp();
let time2 = time1 - 123.45;
time1 - time2
"#
)? - 123.45)
.abs()
< 0.001
);
#[cfg(feature = "no_float")]
assert_eq!(
engine.eval::<INT>(
r#"
let time1 = timestamp();
let time2 = time1 + 42;
time2 - time1
"#
)?,
42
);
#[cfg(feature = "no_float")]
assert_eq!(
engine.eval::<INT>(
r#"
let time1 = timestamp();
let time2 = time1 - 42;
time1 - time2
"#
)?,
42
);
Ok(())
}