commit
b1cbf0ec0d
21
README.md
21
README.md
@ -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.
|
||||||
|
15
RELEASES.md
15
RELEASES.md
@ -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
|
||||||
==============
|
==============
|
||||||
|
@ -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() {
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
|
|
||||||
|
@ -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
|
||||||
|
|
|
|
||||||
|
@ -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)
|
||||||
|
@ -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].
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 | `()` |
|
||||||
|
@ -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_ |
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
Raw `Engine`
|
Raw `Engine`
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
")?;
|
||||||
|
```
|
||||||
|
@ -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";
|
||||||
|
```
|
||||||
|
@ -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`])
|
||||||
|
@ -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")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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`
|
||||||
|
@ -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'
|
|
||||||
```
|
|
@ -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.
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
--------
|
--------
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
|
```
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
```
|
```
|
||||||
|
@ -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!`
|
||||||
|
@ -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] |
|
||||||
|
@ -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"#)?;
|
||||||
|
|
||||||
|
88
doc/src/rust/modules/ast.md
Normal file
88
doc/src/rust/modules/ast.md
Normal 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'
|
||||||
|
```
|
@ -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;
|
||||||
```
|
```
|
||||||
|
@ -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();
|
||||||
"#)?;
|
"#)?;
|
||||||
```
|
```
|
||||||
|
@ -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'
|
||||||
|
@ -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'.
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -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...
|
||||||
```
|
```
|
||||||
|
80
src/any.rs
80
src/any.rs
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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))
|
||||||
|
122
src/fn_call.rs
122
src/fn_call.rs
@ -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,
|
||||||
|
@ -145,7 +145,7 @@ impl FnPtr {
|
|||||||
has_this,
|
has_this,
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
None,
|
&None,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
|
@ -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::*;
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
85
src/module/resolvers/collection.rs
Normal file
85
src/module/resolvers/collection.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
183
src/module/resolvers/file.rs
Normal file
183
src/module/resolvers/file.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
188
src/module/resolvers/global_file.rs
Normal file
188
src/module/resolvers/global_file.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
40
src/module/resolvers/mod.rs
Normal file
40
src/module/resolvers/mod.rs
Normal 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>>;
|
||||||
|
}
|
96
src/module/resolvers/stat.rs
Normal file
96
src/module/resolvers/stat.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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"))]
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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),
|
||||||
|
@ -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);
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user