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.
|
||||
* 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).
|
||||
* 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.
|
||||
* 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).
|
||||
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)).
|
||||
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
||||
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.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) and [closures](https://schungx.github.io/rhai/language/fn-closure.html).
|
||||
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
||||
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
|
||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html) and [operator overloading](https://schungx.github.io/rhai/rust/operators.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).
|
||||
* 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).
|
||||
* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
|
||||
|
||||
Protection against attacks
|
||||
--------------------------
|
||||
@ -69,13 +70,11 @@ Scripts can be evaluated directly from the editor.
|
||||
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
|
||||
* [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt)
|
||||
|
||||
at your choice.
|
||||
|
||||
Unless explicitly stated otherwise, any contribution intentionally submitted
|
||||
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.
|
||||
|
15
RELEASES.md
15
RELEASES.md
@ -4,6 +4,9 @@ Rhai Release Notes
|
||||
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
|
||||
---------
|
||||
|
||||
@ -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_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
|
||||
------------
|
||||
|
||||
* Plugins support via procedural macros.
|
||||
* 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.
|
||||
* 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`.
|
||||
* `+` and `-` operators for timestamps to increment/decrement by seconds.
|
||||
|
||||
|
||||
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)]
|
||||
pub(crate) struct ExportedFnParams {
|
||||
pub name: Option<Vec<String>>,
|
||||
@ -363,22 +387,8 @@ impl ExportedFn {
|
||||
})
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
|
||||
match self.params.special {
|
||||
FnSpecialAccess::None => {}
|
||||
FnSpecialAccess::Property(Property::Get(ref g)) => literals.push(syn::LitStr::new(
|
||||
&format!("get${}", g.to_string()),
|
||||
g.span(),
|
||||
)),
|
||||
FnSpecialAccess::Property(Property::Set(ref s)) => literals.push(syn::LitStr::new(
|
||||
&format!("set${}", s.to_string()),
|
||||
s.span(),
|
||||
)),
|
||||
FnSpecialAccess::Index(Index::Get) => {
|
||||
literals.push(syn::LitStr::new(FN_IDX_GET, proc_macro2::Span::call_site()))
|
||||
}
|
||||
FnSpecialAccess::Index(Index::Set) => {
|
||||
literals.push(syn::LitStr::new(FN_IDX_SET, proc_macro2::Span::call_site()))
|
||||
}
|
||||
if let Some((s, _, span)) = self.params.special.get_fn_name() {
|
||||
literals.push(syn::LitStr::new(&s, span));
|
||||
}
|
||||
|
||||
if literals.is_empty() {
|
||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
use crate::attrs::ExportScope;
|
||||
use crate::function::ExportedFn;
|
||||
use crate::function::{ExportedFn, FnSpecialAccess};
|
||||
use crate::module::Module;
|
||||
|
||||
pub(crate) type ExportedConst = (String, Box<syn::Type>, syn::Expr);
|
||||
@ -183,17 +183,12 @@ pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type {
|
||||
}
|
||||
|
||||
pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> {
|
||||
let mut renames = HashMap::<String, proc_macro2::Span>::new();
|
||||
let mut names = HashMap::<String, proc_macro2::Span>::new();
|
||||
for itemfn in fns.iter() {
|
||||
if let Some(ref names) = itemfn.params().name {
|
||||
for name in names {
|
||||
let current_span = itemfn.params().span.as_ref().unwrap();
|
||||
let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| {
|
||||
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::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"),
|
||||
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
|
||||
ty.as_ref().to_token_stream().to_string()
|
||||
}
|
||||
@ -201,22 +196,43 @@ pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::
|
||||
argstr.push('.');
|
||||
argstr.push_str(&type_string);
|
||||
argstr
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
let mut renames = HashMap::<String, proc_macro2::Span>::new();
|
||||
let mut fn_defs = HashMap::<String, proc_macro2::Span>::new();
|
||||
|
||||
for itemfn in fns.iter() {
|
||||
if itemfn.params().name.is_some() || itemfn.params().special != FnSpecialAccess::None {
|
||||
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 key = make_key(&name, itemfn);
|
||||
if let Some(other_span) = renames.insert(key, *current_span) {
|
||||
let mut err = syn::Error::new(
|
||||
*current_span,
|
||||
format!("duplicate Rhai signature for '{}'", &name),
|
||||
format!("duplicate Rhai signature for '{}'", &fn_name),
|
||||
);
|
||||
err.combine(syn::Error::new(
|
||||
other_span,
|
||||
format!("duplicated function renamed '{}'", &name),
|
||||
format!("duplicated function renamed '{}'", &fn_name),
|
||||
));
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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(
|
||||
ident.span(),
|
||||
format!("duplicate function '{}'", ident.to_string()),
|
||||
@ -227,21 +243,20 @@ pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::
|
||||
));
|
||||
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 key = make_key(ident, itemfn);
|
||||
if let Some(fn_span) = renames.get(&key) {
|
||||
let mut err = syn::Error::new(
|
||||
attr_span,
|
||||
format!("duplicate Rhai signature for '{}'", &new_name),
|
||||
ident.span(),
|
||||
format!("duplicate Rhai signature for '{}'", &ident),
|
||||
);
|
||||
err.combine(syn::Error::new(
|
||||
*fn_span,
|
||||
format!("duplicated function '{}'", &new_name),
|
||||
format!("duplicated function '{}'", &ident),
|
||||
));
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
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
|
||||
|
|
||||
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`
|
||||
--> $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
|
||||
|
|
||||
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`
|
||||
--> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:25:8
|
||||
|
|
||||
|
@ -81,15 +81,15 @@ The Rhai Scripting Language
|
||||
2. [Overloading](language/overload.md)
|
||||
3. [Namespaces](language/fn-namespaces.md)
|
||||
4. [Function Pointers](language/fn-ptr.md)
|
||||
5. [Anonymous Functions](language/fn-anon.md)
|
||||
6. [Currying](language/fn-curry.md)
|
||||
5. [Currying](language/fn-curry.md)
|
||||
6. [Anonymous Functions](language/fn-anon.md)
|
||||
7. [Closures](language/fn-closure.md)
|
||||
16. [Print and Debug](language/print-debug.md)
|
||||
17. [Modules](language/modules/index.md)
|
||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||
2. [Import Modules](language/modules/import.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)
|
||||
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
||||
18. [Eval Statement](language/eval.md)
|
||||
|
@ -37,7 +37,7 @@ Dynamic
|
||||
|
||||
* 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].
|
||||
|
||||
|
@ -4,7 +4,7 @@ Keywords List
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Keyword | Description | Inactive under | Overloadable |
|
||||
| :-------------------: | ---------------------------------------- | :-------------: | :----------: |
|
||||
| :-------------------: | ------------------------------------------- | :-------------: | :----------: |
|
||||
| `true` | boolean true literal | | no |
|
||||
| `false` | boolean false literal | | no |
|
||||
| `let` | variable declaration | | no |
|
||||
@ -15,9 +15,9 @@ Keywords List
|
||||
| `while` | while loop | | no |
|
||||
| `loop` | infinite 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 |
|
||||
| `break` | loop breaking | | no |
|
||||
| `break` | break out of loop iteration | | no |
|
||||
| `return` | return value | | no |
|
||||
| `throw` | throw exception | | no |
|
||||
| `import` | import module | [`no_module`] | no |
|
||||
@ -25,7 +25,7 @@ Keywords List
|
||||
| `as` | alias for variable export | [`no_module`] | no |
|
||||
| `private` | mark function private | [`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 |
|
||||
| `curry` | curry a [function pointer] | | no |
|
||||
| `this` | reference to base object for method call | [`no_function`] | no |
|
||||
|
@ -4,11 +4,11 @@ Literals Syntax
|
||||
{{#include ../links.md}}
|
||||
|
||||
| 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` |
|
||||
| [String] | `"... \x?? \u???? \U???????? ..."` |
|
||||
| Character | `"... \x?? \u???? \U???????? ..."` |
|
||||
| Character | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
|
||||
| [`Array`] | `[ ???, ???, ??? ]` |
|
||||
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
|
||||
| Boolean true | `true` |
|
||||
|
@ -40,13 +40,13 @@ Symbols
|
||||
| ------------ | ------------------------ |
|
||||
| `:` | property value 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}}
|
||||
|
||||
|
||||
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_.
|
||||
|
||||
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 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.
|
||||
|
||||
@ -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$).
|
||||
|
||||
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.
|
||||
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
|
||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
Raw `Engine`
|
||||
===========
|
||||
|
||||
|
@ -31,19 +31,20 @@ let mut scope = Scope::new();
|
||||
scope
|
||||
.push("y", 42_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
|
||||
// remember to use 'String', not '&str'
|
||||
|
||||
// First invocation
|
||||
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;
|
||||
")?;
|
||||
|
||||
// Second invocation using the same state
|
||||
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'
|
||||
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);
|
||||
|
@ -31,19 +31,19 @@ Built-in Functions
|
||||
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `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 |
|
||||
| `+=` operator | array, 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 |
|
||||
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | inserts an element at a certain index |
|
||||
| `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
|
||||
| `+=` operator | 1) array<br/>2) array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | 1) first array<br/>2) second array | concatenates the first array with the second |
|
||||
| `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) |
|
||||
| `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 |
|
||||
| `reverse` | _none_ | reverses the array |
|
||||
| `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 |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
|
||||
|
@ -16,8 +16,45 @@ x = 123; // <- syntax error: cannot assign to constant
|
||||
```
|
||||
|
||||
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
|
||||
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}}
|
||||
|
||||
|
||||
Convert Between Integer and Floating-Point
|
||||
-----------------------------------------
|
||||
|
||||
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`]).
|
||||
@ -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"
|
||||
```
|
||||
|
||||
|
||||
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";
|
||||
```
|
||||
|
@ -83,10 +83,14 @@ match item.type_name() { // 'type_name' returns the name
|
||||
"i64" => ...
|
||||
"alloc::string::String" => ...
|
||||
"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
|
||||
----------------
|
||||
@ -100,4 +104,5 @@ The following conversion traits are implemented for `Dynamic`:
|
||||
* `From<String>`
|
||||
* `From<char>`
|
||||
* `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`])
|
||||
|
@ -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
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 1; // a normal variable
|
||||
|
||||
x.is_shared() == false;
|
||||
|
||||
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
|
||||
|
||||
x.is_shared() == true; // 'x' is now a shared value!
|
||||
|
||||
f.call(2) == 3; // 1 + 2 == 3
|
||||
|
||||
x = 40; // changing 'x'...
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
x.is_shared() == true; // now 'x' is shared
|
||||
@ -152,6 +136,26 @@ x.call(f, 2);
|
||||
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?
|
||||
|
||||
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,
|
||||
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.
|
||||
|
||||
In general, there are two types of _namespaces_ where functions are looked up:
|
||||
@ -43,7 +43,9 @@ This aspect is very similar to JavaScript before ES6 modules.
|
||||
// Compile a script into AST
|
||||
let ast1 = engine.compile(
|
||||
r#"
|
||||
fn get_message() { "Hello!" } // greeting message
|
||||
fn get_message() {
|
||||
"Hello!" // greeting message
|
||||
}
|
||||
|
||||
fn say_hello() {
|
||||
print(get_message()); // prints message
|
||||
@ -136,7 +138,7 @@ the subsequent call using the _namespace-qualified_ function name fails to find
|
||||
function named '`message`' in the global namespace.
|
||||
|
||||
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:
|
||||
|
||||
|
@ -61,8 +61,8 @@ hello.call(0); // error: function not found - 'hello_world (i64)'
|
||||
Global Namespace Only
|
||||
--------------------
|
||||
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace]
|
||||
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace].
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
|
||||
They can only refer to functions within the global [namespace][function namespace].
|
||||
See [function namespaces] for more details.
|
||||
|
||||
```rust
|
||||
|
@ -44,10 +44,13 @@ Representation of Numbers
|
||||
------------------------
|
||||
|
||||
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
|
||||
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
|
||||
with Rhai [object maps].
|
||||
the [`no_float`] feature is not used.
|
||||
|
||||
Most common generators of JSON data distinguish between 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 with Rhai [object maps].
|
||||
|
||||
|
||||
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'.
|
||||
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)?;
|
||||
|
||||
map.len() == 2; // 'map' contains two properties: 'a' and 'b'
|
||||
|
@ -48,9 +48,9 @@ an equivalent method coded in script, where the object is accessed via the `this
|
||||
The following table illustrates the differences:
|
||||
|
||||
| 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) {}` |
|
||||
| Rhai script | _n_ | `this` | `fn method(x, y) {}` |
|
||||
| :-----------: | :--------: | :-----------------------: | :---------------------------: |
|
||||
| Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` |
|
||||
| Rhai script | _N_ | `this` (of type `&mut T`) | `Fn(x: U, y: V)` |
|
||||
|
||||
|
||||
`&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}}
|
||||
|
||||
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`],
|
||||
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
|
||||
----------------
|
||||
Export Global Variables
|
||||
----------------------
|
||||
|
||||
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
|
||||
|
||||
Variables not exported are _private_ and hidden. They are merely used to initialize the module,
|
||||
but cannot be accessed from outside.
|
||||
|
||||
Everything exported from a module is **constant** (**read-only**).
|
||||
Everything exported from a module is **constant** (i.e. read-only).
|
||||
|
||||
```rust
|
||||
// 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.
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
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
|
||||
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.
|
||||
* 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.
|
||||
|
||||
```rust
|
||||
let obj = #{
|
||||
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;
|
||||
|
||||
|
@ -58,13 +58,13 @@ The following methods (defined in the [`BasicMapPackage`][packages] but excluded
|
||||
operate on object maps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `has` | property name | does the object map contain a property of a particular name? |
|
||||
| `len` | _none_ | returns the number of properties |
|
||||
| `clear` | _none_ | empties the object map |
|
||||
| `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 | 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 |
|
||||
| `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`] |
|
||||
|
@ -7,17 +7,18 @@ The following standard methods (mostly defined in the [`MoreStringPackage`][pack
|
||||
using a [raw `Engine`]) operate on [strings]:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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 |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `sub_string` | start index, 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) |
|
||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||
| `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` | 1) start index<br/>2) length _(optional)_ | extracts a sub-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`] |
|
||||
| `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) |
|
||||
| `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
|
||||
|
@ -44,16 +44,16 @@ Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicod
|
||||
Standard escape sequences:
|
||||
|
||||
| Escape sequence | Meaning |
|
||||
| --------------- | ------------------------------ |
|
||||
| --------------- | -------------------------------- |
|
||||
| `\\` | back-slash `\` |
|
||||
| `\t` | tab |
|
||||
| `\r` | carriage-return `CR` |
|
||||
| `\n` | line-feed `LF` |
|
||||
| `\"` | double-quote `"` in strings |
|
||||
| `\'` | single-quote `'` in characters |
|
||||
| `\x`_xx_ | Unicode in 2-digit hex |
|
||||
| `\u`_xxxx_ | Unicode in 4-digit hex |
|
||||
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex |
|
||||
| `\"` | double-quote `"` |
|
||||
| `\'` | single-quote `'` |
|
||||
| `\x`_xx_ | ASCII character in 2-digit hex |
|
||||
| `\u`_xxxx_ | Unicode character in 4-digit hex |
|
||||
| `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
|
||||
|
||||
|
||||
Differences from Rust Strings
|
||||
|
@ -19,9 +19,11 @@ Built-in Functions
|
||||
The following methods (defined in the [`BasicTimePackage`][packages] but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- |
|
||||
| ----------------------------- | ------------------------------------------- | -------------------------------------------------------- |
|
||||
| `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
|
||||
|
@ -24,3 +24,24 @@ if type_of(x) == "string" {
|
||||
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
|
||||
[`to_int`]: {{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 types]: {{rootUrl}}/rust/custom.md
|
||||
|
@ -51,6 +51,12 @@ let config: Rc<RefCell<Config>> = Rc::new(RefCell::new(Default::default()));
|
||||
|
||||
### 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
|
||||
// Notice 'move' is used to move the shared configuration object into the closure.
|
||||
let cfg = config.clone();
|
||||
|
@ -64,6 +64,12 @@ let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()
|
||||
|
||||
### 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
|
||||
// Notice 'move' is used to move the shared API object into the closure.
|
||||
let b = bunny.clone();
|
||||
|
@ -58,7 +58,7 @@ impl EnergizerBunny {
|
||||
pub fn new () -> Self { ... }
|
||||
pub fn go (&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 set_speed (&mut self, speed: i64) { ... }
|
||||
pub fn turn (&mut self, left_turn: bool) { ... }
|
||||
@ -77,13 +77,23 @@ let SharedBunnyType = Rc<RefCell<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
|
||||
engine
|
||||
.register_get_set("power",
|
||||
|bunny: &mut SharedBunnyType| bunny.borrow().is_going(),
|
||||
|bunny: &mut SharedBunnyType, on: bool| {
|
||||
use rhai::plugins::*;
|
||||
|
||||
#[export_module]
|
||||
pub mod bunny_api {
|
||||
pub const MAX_SPEED: i64 = 100;
|
||||
|
||||
#[rhai_fn(get = "power")]
|
||||
pub fn get_power(bunny: &mut SharedBunnyType) -> bool {
|
||||
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...");
|
||||
@ -98,16 +108,21 @@ engine
|
||||
}
|
||||
}
|
||||
}
|
||||
).register_get("speed", |bunny: &mut SharedBunnyType| {
|
||||
#[rhai_fn(get = "speed")]
|
||||
pub fn get_speed(bunny: &mut SharedBunnyType) -> i64 {
|
||||
if bunny.borrow().is_going() {
|
||||
bunny.borrow().get_speed()
|
||||
} else {
|
||||
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 {
|
||||
Err("Speed must be positive!".into())
|
||||
} else if speed > 100 {
|
||||
} else if speed > MAX_SPEED {
|
||||
Err("Bunny will be going too fast!".into())
|
||||
} else if !bunny.borrow().is_going() {
|
||||
Err("Bunny is not yet going!".into())
|
||||
@ -115,15 +130,20 @@ engine
|
||||
b.borrow_mut().set_speed(speed);
|
||||
Ok(().into())
|
||||
}
|
||||
}).register_fn("turn_left", |bunny: &mut SharedBunnyType| {
|
||||
}
|
||||
pub fn turn_left(bunny: &mut SharedBunnyType) {
|
||||
if bunny.borrow().is_going() {
|
||||
bunny.borrow_mut().turn(true);
|
||||
}
|
||||
}).register_fn("turn_right", |bunny: &mut SharedBunnyType| {
|
||||
}
|
||||
pub fn turn_right(bunny: &mut SharedBunnyType) {
|
||||
if bunny.borrow().is_going() {
|
||||
bunny.borrow_mut().turn(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
engine.load_package(exported_module!(bunny_api));
|
||||
```
|
||||
|
||||
### Push Constant Command Object into Custom Scope
|
||||
@ -132,7 +152,9 @@ engine
|
||||
let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::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)?;
|
||||
```
|
||||
@ -140,11 +162,11 @@ engine.consume_with_scope(&mut scope, script)?;
|
||||
### Use the Command API in Script
|
||||
|
||||
```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
|
||||
------
|
||||
|
||||
| Macro | Apply to | Description |
|
||||
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `#[export_fn]` | 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 |
|
||||
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name |
|
||||
| Macro | Signature | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
|
||||
| `#[export_fn]` | apply to rust function defined in a Rust module | exports the function |
|
||||
| `register_exported_fn!` | `register_exported_fn!(&mut `_engine_`, "`_name_`", `_function_`)` | registers the function into an [`Engine`] under a 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!`
|
||||
|
@ -29,9 +29,9 @@ use rhai::plugins::*; // a "prelude" import for macros
|
||||
|
||||
#[export_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.
|
||||
pub const SOME_NUMBER: i64 = 42;
|
||||
pub const MY_NUMBER: i64 = 42;
|
||||
|
||||
// This function will be registered as 'greet'.
|
||||
pub fn greet(name: &str) -> String {
|
||||
@ -261,11 +261,11 @@ 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.
|
||||
|
||||
| Attribute Parameter | Use with | Apply to | Description |
|
||||
| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| ------------------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
|
||||
| `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 |
|
||||
| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property |
|
||||
| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property |
|
||||
| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter |
|
||||
| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter |
|
||||
| `return_raw` | `#[rhai_fn]` | function returning `Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |
|
||||
| `get = "..."` | `#[rhai_fn]` | `pub fn (&mut Type) -> Value` | registers a getter for the named property |
|
||||
| `set = "..."` | `#[rhai_fn]` | `pub fn (&mut Type, Value)` | registers a setter for the named property |
|
||||
| `index_get` | `#[rhai_fn]` | `pub fn (&mut Type, INT) -> Value` | registers an index getter |
|
||||
| `index_set` | `#[rhai_fn]` | `pub fn (&mut Type, INT, Value)` | registers an index setter |
|
||||
| `return_raw` | `#[rhai_fn]` | `pub fn (...) -> Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |
|
||||
|
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`
|
||||
------------------------------------------
|
||||
|
||||
In order to _use_ a custom module, there must be a [module resolver], which serves the module when
|
||||
loaded via `import` statements.
|
||||
`Engine::load_package` supports loading a [module] as a [package].
|
||||
|
||||
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
|
||||
a custom module.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, Module, i64};
|
||||
use rhai::{Engine, Scope, Module};
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mut module = Module::new(); // new 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
|
||||
@ -47,11 +66,12 @@ let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("question", module);
|
||||
|
||||
// Set the module resolver into the 'Engine'
|
||||
let mut engine = Engine::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
// 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
|
||||
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`.
|
||||
|
||||
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
|
||||
return a [`Module`]; if the module cannot be load, return `EvalAltResult::ErrorModuleNotFound`.
|
||||
of the _module path_ (i.e. the path specified in the [`import`] statement).
|
||||
|
||||
* 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
|
||||
-------
|
||||
@ -35,9 +41,12 @@ impl ModuleResolver for MyModuleResolver {
|
||||
// Check module path.
|
||||
if is_valid_module_path(path) {
|
||||
// Load the custom module.
|
||||
let module: Module = load_secret_module(path);
|
||||
Ok(module)
|
||||
load_secret_module(path).map_err(|err|
|
||||
// Return EvalAltResult::ErrorInModule upon loading error
|
||||
EvalAltResult::ErrorInModule(err.to_string(), pos).into()
|
||||
)
|
||||
} else {
|
||||
// Return EvalAltResult::ErrorModuleNotFound if the path is invalid
|
||||
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
|
||||
}
|
||||
}
|
||||
@ -50,7 +59,7 @@ engine.set_module_resolver(Some(MyModuleResolver {}));
|
||||
|
||||
engine.consume(r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
// 'MyModuleResolver::resolve' with "hello" as path
|
||||
// 'MyModuleResolver::resolve' with "hello" as `path`
|
||||
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`
|
||||
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.
|
||||
|
||||
| Module Resolver | Description | Namespace |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
`FileModuleResolver` (default)
|
||||
-----------------------------
|
||||
|
||||
The _default_ module resolution service, not available for [`no_std`] or [WASM] builds.
|
||||
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`
|
||||
@ -30,8 +134,14 @@ Set into `Engine`
|
||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||
|
||||
```rust
|
||||
// Use the 'StaticModuleResolver'
|
||||
let resolver = rhai::module_resolvers::StaticModuleResolver::new();
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
// Create a module resolver
|
||||
let resolver = StaticModuleResolver::new();
|
||||
|
||||
// Register functions into 'resolver'...
|
||||
|
||||
// Use the module resolver
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to 'None'
|
||||
|
@ -34,9 +34,11 @@ Macro Parameters
|
||||
```rust
|
||||
// Import necessary types and traits.
|
||||
use rhai::{
|
||||
def_package,
|
||||
packages::Package,
|
||||
packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage}
|
||||
def_package, // 'def_package!' macro
|
||||
packages::Package, // 'Package' trait
|
||||
packages::{ // pre-defined packages
|
||||
ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage
|
||||
}
|
||||
};
|
||||
|
||||
// Define the package 'MyPackage'.
|
||||
|
@ -9,10 +9,12 @@ Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package
|
||||
packages to be used.
|
||||
|
||||
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`],
|
||||
even across threads (under [`sync`]). Therefore, a package only has to be created _once_.
|
||||
Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`)
|
||||
among multiple instances of [`Engine`], even across threads (under [`sync`]).
|
||||
Therefore, a package only has to be created _once_.
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
@ -85,10 +85,10 @@ Extract Arguments
|
||||
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
|
||||
|
||||
| Argument type | Access (`n` = argument position) | Result |
|
||||
| ------------------------------ | ------------------------------------- | --------------------------------------------------------- |
|
||||
| ------------------------------ | ------------------------------------- | ----------------------------------------------------- |
|
||||
| [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 (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value.<br/>The original value becomes `()` |
|
||||
| [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; the original value becomes `()` |
|
||||
| `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),
|
||||
|
@ -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
|
||||
-------------------
|
||||
|
||||
|
@ -11,12 +11,16 @@ 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.
|
||||
|
||||
```rust
|
||||
// Use the fluent API to configure an 'Engine' and then keep an immutable instance.
|
||||
let engine = Engine::new()
|
||||
.register_get("field", get_field)
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Use the fluent API to configure an 'Engine'
|
||||
engine.register_get("field", get_field)
|
||||
.register_set("field", set_field)
|
||||
.register_fn("do_work", action);
|
||||
|
||||
// Then turn it into an immutable instance
|
||||
let engine = engine;
|
||||
|
||||
// 'engine' is immutable...
|
||||
```
|
||||
|
||||
|
80
src/any.rs
80
src/any.rs
@ -159,6 +159,8 @@ pub enum Union {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Box<Map>),
|
||||
FnPtr(Box<FnPtr>),
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
TimeStamp(Box<Instant>),
|
||||
|
||||
Variant(Box<Box<dyn Variant>>),
|
||||
|
||||
@ -313,6 +315,8 @@ impl Dynamic {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => TypeId::of::<Map>(),
|
||||
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::TimeStamp(_) => TypeId::of::<Instant>(),
|
||||
|
||||
Union::Variant(value) => (***value).type_id(),
|
||||
|
||||
@ -345,9 +349,9 @@ impl Dynamic {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => "map",
|
||||
Union::FnPtr(_) => "Fn",
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::Variant(value) if value.is::<Instant>() => "timestamp",
|
||||
Union::TimeStamp(_) => "timestamp",
|
||||
|
||||
Union::Variant(value) => (***value).type_name(),
|
||||
|
||||
#[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>() {
|
||||
"Fn"
|
||||
} else {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
if name == type_name::<Instant>() {
|
||||
return "timestamp";
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if name == type_name::<Array>() {
|
||||
return "array";
|
||||
@ -387,6 +387,10 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
|
||||
if name == type_name::<Map>() {
|
||||
return "map";
|
||||
}
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
if name == type_name::<Instant>() {
|
||||
return "timestamp";
|
||||
}
|
||||
|
||||
name
|
||||
}
|
||||
@ -410,9 +414,9 @@ impl fmt::Display for Dynamic {
|
||||
fmt::Debug::fmt(value, f)
|
||||
}
|
||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||
|
||||
#[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()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -449,9 +453,9 @@ impl fmt::Debug for Dynamic {
|
||||
fmt::Debug::fmt(value, f)
|
||||
}
|
||||
Union::FnPtr(value) => fmt::Debug::fmt(value, f),
|
||||
|
||||
#[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()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -485,6 +489,8 @@ impl Clone for Dynamic {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value) => Self(Union::Map(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(),
|
||||
|
||||
@ -601,6 +607,14 @@ impl Dynamic {
|
||||
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)))
|
||||
}
|
||||
|
||||
@ -629,7 +643,7 @@ impl Dynamic {
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_closure")]
|
||||
unimplemented!()
|
||||
panic!("converting into a shared value is not supported under 'no_closure'");
|
||||
}
|
||||
|
||||
/// 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::<()>() {
|
||||
return match self.0 {
|
||||
Union::Unit(value) => unsafe_try_cast(value),
|
||||
@ -781,7 +803,20 @@ impl Dynamic {
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
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.
|
||||
@ -980,6 +1015,13 @@ impl Dynamic {
|
||||
_ => 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::<()>() {
|
||||
return match &self.0 {
|
||||
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
@ -1055,6 +1097,13 @@ impl Dynamic {
|
||||
_ => 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::<()>() {
|
||||
return match &mut self.0 {
|
||||
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
@ -1256,3 +1305,10 @@ impl From<Box<FnPtr>> for Dynamic {
|
||||
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];
|
||||
|
||||
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,
|
||||
)
|
||||
.map_err(|err| match *err {
|
||||
@ -798,8 +798,9 @@ impl Engine {
|
||||
// xxx.fn_name(arg_expr_list)
|
||||
Expr::FnCall(x) if x.1.is_none() => {
|
||||
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
|
||||
let def_val = def_val.map(Into::<Dynamic>::into);
|
||||
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,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
@ -833,7 +834,7 @@ impl Engine {
|
||||
let mut new_val = new_val;
|
||||
let mut args = [target.as_mut(), new_val.as_mut().unwrap()];
|
||||
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,
|
||||
)
|
||||
.map(|(v, _)| (v, true))
|
||||
@ -844,7 +845,7 @@ impl Engine {
|
||||
let ((_, getter, _), pos) = x.as_ref();
|
||||
let mut args = [target.as_mut()];
|
||||
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,
|
||||
)
|
||||
.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
|
||||
Expr::FnCall(x) if x.1.is_none() => {
|
||||
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
|
||||
let def_val = def_val.map(Into::<Dynamic>::into);
|
||||
let (val, _) = self
|
||||
.make_method_call(
|
||||
state, lib, name, *hash, target, idx_val, *def_val,
|
||||
state, lib, name, *hash, target, idx_val, &def_val,
|
||||
*native, false, level,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))?;
|
||||
@ -898,7 +900,7 @@ impl Engine {
|
||||
let (mut val, updated) = self
|
||||
.exec_fn_call(
|
||||
state, lib, getter, 0, args, is_ref, true, false, None,
|
||||
None, level,
|
||||
&None, level,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))?;
|
||||
|
||||
@ -924,7 +926,7 @@ impl Engine {
|
||||
arg_values[1] = val;
|
||||
self.exec_fn_call(
|
||||
state, lib, setter, 0, arg_values, is_ref, true, false,
|
||||
None, None, level,
|
||||
None, &None, level,
|
||||
)
|
||||
.or_else(
|
||||
|err| match *err {
|
||||
@ -942,9 +944,10 @@ impl Engine {
|
||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||
Expr::FnCall(x) if x.1.is_none() => {
|
||||
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
|
||||
let def_val = def_val.map(Into::<Dynamic>::into);
|
||||
let (mut val, _) = self
|
||||
.make_method_call(
|
||||
state, lib, name, *hash, target, idx_val, *def_val,
|
||||
state, lib, name, *hash, target, idx_val, &def_val,
|
||||
*native, false, level,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))?;
|
||||
@ -1202,7 +1205,7 @@ impl Engine {
|
||||
let mut idx = idx;
|
||||
let args = &mut [val, &mut idx];
|
||||
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_err(|err| match *err {
|
||||
@ -1245,8 +1248,9 @@ impl Engine {
|
||||
let op = "==";
|
||||
|
||||
// Call the `==` operator to compare each value
|
||||
let def_value = Some(false.into());
|
||||
|
||||
for value in rhs_value.iter_mut() {
|
||||
let def_value = Some(false);
|
||||
let args = &mut [&mut lhs_value.clone(), value];
|
||||
|
||||
// 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()));
|
||||
|
||||
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()))?
|
||||
.0
|
||||
.as_bool()
|
||||
@ -1264,7 +1268,7 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false.into())
|
||||
Ok(def_value.unwrap())
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(rhs_value)) => match lhs_value {
|
||||
@ -1395,7 +1399,7 @@ impl Engine {
|
||||
// Run function
|
||||
let (value, _) = self
|
||||
.exec_fn_call(
|
||||
state, lib, op, 0, args, false, false, false, None, None,
|
||||
state, lib, op, 0, args, false, false, false, None, &None,
|
||||
level,
|
||||
)
|
||||
.map_err(|err| err.new_position(*op_pos))?;
|
||||
@ -1431,7 +1435,7 @@ impl Engine {
|
||||
&mut rhs_val,
|
||||
];
|
||||
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_err(|err| err.new_position(*op_pos))?
|
||||
@ -1499,8 +1503,9 @@ impl Engine {
|
||||
// Normal function call
|
||||
Expr::FnCall(x) if x.1.is_none() => {
|
||||
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(
|
||||
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,
|
||||
)
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
|
@ -211,7 +211,7 @@ impl Engine {
|
||||
args: &mut FnCallArgs,
|
||||
is_ref: bool,
|
||||
pub_only: bool,
|
||||
def_val: Option<bool>,
|
||||
def_val: &Option<Dynamic>,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
self.inc_operations(state)?;
|
||||
|
||||
@ -221,6 +221,7 @@ impl Engine {
|
||||
let func = self
|
||||
.global_module
|
||||
.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));
|
||||
|
||||
if let Some(func) = func {
|
||||
@ -280,14 +281,14 @@ impl Engine {
|
||||
|
||||
// Return default value (if any)
|
||||
if let Some(val) = def_val {
|
||||
return Ok((val.into(), false));
|
||||
return Ok((val.clone(), false));
|
||||
}
|
||||
|
||||
// Getter function not found?
|
||||
if let Some(prop) = extract_prop_from_getter(fn_name) {
|
||||
return EvalAltResult::ErrorDotExpr(
|
||||
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,
|
||||
self.map_type_name(args[0].type_name())
|
||||
),
|
||||
@ -300,9 +301,10 @@ impl Engine {
|
||||
if let Some(prop) = extract_prop_from_setter(fn_name) {
|
||||
return EvalAltResult::ErrorDotExpr(
|
||||
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,
|
||||
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(),
|
||||
)
|
||||
@ -439,9 +441,9 @@ impl Engine {
|
||||
|
||||
// First check script-defined functions
|
||||
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
|
||||
//|| 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)
|
||||
// Then check packages
|
||||
|| self.packages.contains_fn(hash_script, pub_only)
|
||||
@ -467,7 +469,7 @@ impl Engine {
|
||||
_is_method: bool,
|
||||
pub_only: bool,
|
||||
_capture: Option<Scope>,
|
||||
def_val: Option<bool>,
|
||||
def_val: &Option<Dynamic>,
|
||||
_level: usize,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
// Check for data race.
|
||||
@ -522,17 +524,22 @@ impl Engine {
|
||||
.into()
|
||||
}
|
||||
|
||||
// Normal script function call
|
||||
// Script-like function found
|
||||
#[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) =>
|
||||
{
|
||||
// Get scripted function
|
||||
let func = lib
|
||||
// Get function
|
||||
let func = self
|
||||
.global_module
|
||||
.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))
|
||||
.unwrap()
|
||||
.get_fn_def();
|
||||
.unwrap();
|
||||
|
||||
if func.is_script() {
|
||||
let func = func.get_fn_def();
|
||||
|
||||
let scope = &mut Scope::new();
|
||||
let mods = &mut Imports::new();
|
||||
@ -574,6 +581,19 @@ impl Engine {
|
||||
};
|
||||
|
||||
Ok((result, false))
|
||||
} else {
|
||||
// If it is a native function, redirect it
|
||||
self.call_native_fn(
|
||||
state,
|
||||
lib,
|
||||
fn_name,
|
||||
hash_script,
|
||||
args,
|
||||
is_ref,
|
||||
pub_only,
|
||||
def_val,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Normal native function call
|
||||
@ -649,7 +669,7 @@ impl Engine {
|
||||
hash_script: u64,
|
||||
target: &mut Target,
|
||||
idx_val: Dynamic,
|
||||
def_val: Option<bool>,
|
||||
def_val: &Option<Dynamic>,
|
||||
native: bool,
|
||||
pub_only: bool,
|
||||
level: usize,
|
||||
@ -795,7 +815,7 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
name: &str,
|
||||
args_expr: &[Expr],
|
||||
def_val: Option<bool>,
|
||||
def_val: &Option<Dynamic>,
|
||||
mut hash_script: u64,
|
||||
native: bool,
|
||||
pub_only: bool,
|
||||
|
@ -145,7 +145,7 @@ impl FnPtr {
|
||||
has_this,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
&None,
|
||||
0,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
|
@ -93,8 +93,13 @@ pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
pub use syntax::{EvalContext, Expression};
|
||||
pub use token::Position;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
pub use utils::calc_fn_hash;
|
||||
|
||||
#[cfg(not(feature = "internals"))]
|
||||
pub(crate) use utils::calc_fn_hash;
|
||||
|
||||
pub use rhai_codegen::*;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -118,8 +123,6 @@ pub use parser::FLOAT;
|
||||
pub use module::ModuleResolver;
|
||||
|
||||
/// Module containing all built-in _module resolvers_ available to Rhai.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mod module_resolvers {
|
||||
pub use crate::module::resolvers::*;
|
||||
|
@ -3,7 +3,7 @@
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::Engine;
|
||||
use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync};
|
||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
|
||||
use crate::fn_register::by_value as cast_arg;
|
||||
use crate::parser::{FnAccess, FnAccess::Public};
|
||||
use crate::result::EvalAltResult;
|
||||
@ -38,19 +38,17 @@ use crate::stdlib::{
|
||||
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.
|
||||
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,
|
||||
/// external Rust functions, and script-defined functions.
|
||||
///
|
||||
@ -67,18 +65,14 @@ pub struct Module {
|
||||
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
|
||||
|
||||
/// External Rust functions.
|
||||
functions: HashMap<
|
||||
u64,
|
||||
(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func),
|
||||
StraightHasherBuilder,
|
||||
>,
|
||||
functions: HashMap<u64, FuncInfo, StraightHasherBuilder>,
|
||||
|
||||
/// Iterator functions, keyed by the type producing the iterator.
|
||||
type_iterators: HashMap<TypeId, IteratorFn>,
|
||||
|
||||
/// Flattened collection of all external Rust functions, native or scripted,
|
||||
/// including those in sub-modules.
|
||||
all_functions: HashMap<u64, Func, StraightHasherBuilder>,
|
||||
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
|
||||
|
||||
/// Is the module indexed?
|
||||
indexed: bool,
|
||||
@ -391,7 +385,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
access: FnAccess,
|
||||
arg_types: &[TypeId],
|
||||
func: Func,
|
||||
func: CallableFunction,
|
||||
) -> u64 {
|
||||
let name = name.into();
|
||||
|
||||
@ -491,11 +485,18 @@ impl Module {
|
||||
let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| {
|
||||
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_module"))]
|
||||
pub(crate) fn set_raw_fn_as_scripted(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
@ -513,7 +514,7 @@ impl Module {
|
||||
FnAccess::Public,
|
||||
num_args,
|
||||
None,
|
||||
Func::from_pure(Box::new(f)),
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
),
|
||||
);
|
||||
self.indexed = false;
|
||||
@ -540,7 +541,12 @@ impl Module {
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, _: &mut FnCallArgs| func().map(Dynamic::from);
|
||||
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.
|
||||
@ -565,7 +571,12 @@ impl Module {
|
||||
func(cast_arg::<A>(&mut args[0])).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>()];
|
||||
self.set_fn(name, Public, &arg_types, Func::from_pure(Box::new(f)))
|
||||
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.
|
||||
@ -590,7 +601,12 @@ impl Module {
|
||||
func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
|
||||
};
|
||||
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.
|
||||
@ -642,7 +658,12 @@ impl Module {
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
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,
|
||||
@ -673,7 +694,12 @@ impl Module {
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
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,
|
||||
@ -778,7 +804,12 @@ impl Module {
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
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,
|
||||
@ -815,7 +846,12 @@ impl Module {
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
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,
|
||||
@ -871,7 +907,7 @@ impl Module {
|
||||
FN_IDX_SET,
|
||||
Public,
|
||||
&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::<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,
|
||||
@ -999,14 +1040,19 @@ impl Module {
|
||||
TypeId::of::<C>(),
|
||||
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.
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
||||
/// 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 {
|
||||
None
|
||||
} else {
|
||||
@ -1025,7 +1071,7 @@ impl Module {
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
|
||||
/// 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)
|
||||
}
|
||||
|
||||
@ -1090,7 +1136,9 @@ impl Module {
|
||||
.iter()
|
||||
.filter(|(_, (_, _, _, _, v))| match v {
|
||||
#[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,
|
||||
})
|
||||
.map(|(&k, v)| (k, v.clone())),
|
||||
@ -1112,7 +1160,7 @@ impl Module {
|
||||
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
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,
|
||||
});
|
||||
|
||||
@ -1141,9 +1189,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Get an iterator to the functions in the module.
|
||||
pub(crate) fn iter_fn(
|
||||
&self,
|
||||
) -> impl Iterator<Item = &(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func)> {
|
||||
pub(crate) fn iter_fn(&self) -> impl Iterator<Item = &FuncInfo> {
|
||||
self.functions.values()
|
||||
}
|
||||
|
||||
@ -1162,13 +1208,23 @@ impl Module {
|
||||
self.functions
|
||||
.iter()
|
||||
.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`.
|
||||
///
|
||||
/// ### `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
|
||||
///
|
||||
/// ```
|
||||
@ -1177,14 +1233,19 @@ impl Module {
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
/// 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_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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();
|
||||
|
||||
// Run the script
|
||||
@ -1207,7 +1268,44 @@ impl Module {
|
||||
module.modules.insert(alias.to_string(), m);
|
||||
});
|
||||
|
||||
#[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)
|
||||
}
|
||||
@ -1221,7 +1319,7 @@ impl Module {
|
||||
module: &'a Module,
|
||||
qualifiers: &mut Vec<&'a str>,
|
||||
variables: &mut Vec<(u64, Dynamic)>,
|
||||
functions: &mut Vec<(u64, Func)>,
|
||||
functions: &mut Vec<(u64, CallableFunction)>,
|
||||
) {
|
||||
for (name, m) in &module.modules {
|
||||
// Index all the sub-modules first.
|
||||
@ -1372,573 +1470,10 @@ impl ModuleRef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that encapsulates a module resolution service.
|
||||
pub trait ModuleResolver: SendSync {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(&self, _: &Engine, path: &str, pos: Position) -> Result<Module, Box<EvalAltResult>>;
|
||||
}
|
||||
/// Re-export module resolver trait.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub use resolvers::ModuleResolver;
|
||||
|
||||
/// Re-export module resolvers.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod resolvers;
|
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(),
|
||||
false,
|
||||
true,
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.ok()
|
||||
.map(|(v, _)| v)
|
||||
|
@ -187,9 +187,8 @@ mod string_functions {
|
||||
pub fn contains_char(s: &str, ch: char) -> bool {
|
||||
s.contains(ch)
|
||||
}
|
||||
#[rhai_fn(name = "contains")]
|
||||
#[inline(always)]
|
||||
pub fn contains_string(s: &str, find: ImmutableString) -> bool {
|
||||
pub fn contains(s: &str, find: ImmutableString) -> bool {
|
||||
s.contains(find.as_str())
|
||||
}
|
||||
|
||||
@ -230,13 +229,12 @@ mod string_functions {
|
||||
.unwrap_or(-1 as INT)
|
||||
}
|
||||
#[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())
|
||||
.map(|index| s[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "sub_string")]
|
||||
pub fn sub_string(s: &str, start: INT, len: INT) -> ImmutableString {
|
||||
let offset = if s.is_empty() || len <= 0 {
|
||||
return "".to_string().into();
|
||||
@ -272,7 +270,7 @@ mod string_functions {
|
||||
}
|
||||
|
||||
#[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 {
|
||||
s.make_mut().clear();
|
||||
return;
|
||||
@ -300,12 +298,12 @@ mod string_functions {
|
||||
#[rhai_fn(name = "crop")]
|
||||
#[inline(always)]
|
||||
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")]
|
||||
#[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();
|
||||
}
|
||||
#[rhai_fn(name = "replace")]
|
||||
@ -338,6 +336,18 @@ mod string_functions {
|
||||
pub fn prepend(x: &mut Array, y: &str) -> String {
|
||||
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"))]
|
||||
|
@ -1,23 +1,23 @@
|
||||
#![cfg(not(feature = "no_std"))]
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
use super::{arithmetic::make_err, math_basic::MAX_INT};
|
||||
use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT};
|
||||
|
||||
use crate::any::Dynamic;
|
||||
use crate::def_package;
|
||||
use crate::parser::INT;
|
||||
use crate::plugin::*;
|
||||
use crate::result::EvalAltResult;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
use crate::parser::INT;
|
||||
use crate::stdlib::boxed::Box;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::stdlib::time::Instant;
|
||||
use crate::stdlib::time::{Duration, Instant};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use instant::Instant;
|
||||
use instant::{Duration, Instant};
|
||||
|
||||
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
// Register date/time functions
|
||||
@ -43,7 +43,7 @@ mod time_functions {
|
||||
let seconds = timestamp.elapsed().as_secs();
|
||||
|
||||
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
|
||||
Err(make_err(format!(
|
||||
Err(make_arithmetic_err(format!(
|
||||
"Integer overflow for timestamp.elapsed: {}",
|
||||
seconds
|
||||
)))
|
||||
@ -70,18 +70,18 @@ mod time_functions {
|
||||
let seconds = (ts2 - ts1).as_secs();
|
||||
|
||||
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
|
||||
Err(make_err(format!(
|
||||
Err(make_arithmetic_err(format!(
|
||||
"Integer overflow for timestamp duration: -{}",
|
||||
seconds
|
||||
)))
|
||||
} else {
|
||||
Ok(Dynamic::from(-(seconds as INT)))
|
||||
Ok((-(seconds as INT)).into())
|
||||
}
|
||||
} else {
|
||||
let seconds = (ts1 - ts2).as_secs();
|
||||
|
||||
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
|
||||
Err(make_err(format!(
|
||||
Err(make_arithmetic_err(format!(
|
||||
"Integer overflow for timestamp duration: {}",
|
||||
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 = "==")]
|
||||
#[inline(always)]
|
||||
pub fn eq(x: Instant, y: Instant) -> bool {
|
||||
|
@ -790,7 +790,7 @@ pub enum Expr {
|
||||
Option<Box<ModuleRef>>,
|
||||
u64,
|
||||
StaticVec<Expr>,
|
||||
Option<bool>,
|
||||
Option<bool>, // Default value is `bool` in order for `Expr` to be `Hash`.
|
||||
)>,
|
||||
),
|
||||
/// expr op= expr
|
||||
|
@ -39,6 +39,9 @@ pub enum EvalAltResult {
|
||||
/// An error has occurred inside a called function.
|
||||
/// Wrapped values are the name of the function and the interior error.
|
||||
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.
|
||||
ErrorUnboundThis(Position),
|
||||
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
|
||||
@ -113,6 +116,7 @@ impl EvalAltResult {
|
||||
|
||||
Self::ErrorParsing(p, _) => p.desc(),
|
||||
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
|
||||
Self::ErrorInModule(_, _, _) => "Error in module",
|
||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||
Self::ErrorUnboundThis(_) => "'this' is not bound",
|
||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||
@ -180,6 +184,10 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorInFunctionCall(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::ErrorVariableNotFound(s, _)
|
||||
@ -280,6 +288,7 @@ impl EvalAltResult {
|
||||
Self::ErrorParsing(_, pos)
|
||||
| Self::ErrorFunctionNotFound(_, pos)
|
||||
| Self::ErrorInFunctionCall(_, _, pos)
|
||||
| Self::ErrorInModule(_, _, pos)
|
||||
| Self::ErrorUnboundThis(pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
@ -321,6 +330,7 @@ impl EvalAltResult {
|
||||
Self::ErrorParsing(_, pos)
|
||||
| Self::ErrorFunctionNotFound(_, pos)
|
||||
| Self::ErrorInFunctionCall(_, _, pos)
|
||||
| Self::ErrorInModule(_, _, pos)
|
||||
| Self::ErrorUnboundThis(pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
|
@ -160,9 +160,8 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => self.deserialize_map(visitor),
|
||||
Union::FnPtr(_) => self.type_error(),
|
||||
|
||||
#[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::<i16>() => self.deserialize_i16(visitor),
|
||||
|
@ -262,7 +262,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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();
|
||||
resolver2.insert("testing", module);
|
||||
|
@ -34,12 +34,15 @@ fn test_packages() -> Result<(), Box<EvalAltResult>> {
|
||||
#[test]
|
||||
fn test_packages_with_script() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let ast = engine.compile("fn foo(x) { x + 1 }")?;
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
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, false, &engine)?;
|
||||
engine.load_package(module);
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -186,3 +186,25 @@ fn test_string_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
#[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