commit
87685f653b
@ -4,10 +4,18 @@ Rhai Release Notes
|
||||
Version 0.19.0
|
||||
==============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Fixes bug that prevents calling functions in closures.
|
||||
* Fixes bug that erroneously consumes the first argument to a module-qualified function call.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* Adds `Engine::register_get_result`, `Engine::register_set_result`, `Engine::register_indexer_get_result`, `Engine::register_indexer_set_result` API.
|
||||
* Adds `Module::combine` to combine two modules.
|
||||
* `Engine::parse_json` now also accepts a JSON object starting with `#{`.
|
||||
|
||||
|
||||
Version 0.18.1
|
||||
|
@ -88,6 +88,7 @@ The Rhai Scripting Language
|
||||
4. [Create from AST](language/modules/ast.md)
|
||||
5. [Module Resolvers](rust/modules/resolvers.md)
|
||||
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
||||
18. [Eval Statement](language/eval.md)
|
||||
6. [Safety and Protection](safety/index.md)
|
||||
1. [Checked Arithmetic](safety/checked.md)
|
||||
2. [Sand-Boxing](safety/sandbox.md)
|
||||
@ -119,7 +120,7 @@ The Rhai Scripting Language
|
||||
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||
2. [Custom Operators](engine/custom-op.md)
|
||||
3. [Extending with Custom Syntax](engine/custom-syntax.md)
|
||||
7. [Eval Statement](language/eval.md)
|
||||
7. [Multiple Instantiation](patterns/multiple.md)
|
||||
8. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators and Symbols](appendix/operators.md)
|
||||
|
@ -3,11 +3,14 @@ Parse an Object Map from JSON
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The syntax for an [object map] is extremely similar to JSON, with the exception of `null` values which can
|
||||
technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a
|
||||
Rhai [object map] does - that's the major difference!
|
||||
The syntax for an [object map] is extremely similar to the JSON representation of a object hash,
|
||||
with the exception of `null` values which can technically be mapped to [`()`].
|
||||
|
||||
Use the `Engine::parse_json` method to parse a piece of JSON into an object map:
|
||||
A valid JSON string does not start with a hash character `#` while a Rhai [object map] does - that's the major difference!
|
||||
|
||||
Use the `Engine::parse_json` method to parse a piece of JSON into an object map.
|
||||
The JSON text must represent a single object hash (i.e. must be wrapped within "`{ .. }`")
|
||||
otherwise it returns a syntax error.
|
||||
|
||||
```rust
|
||||
// JSON string - notice that JSON property names are always quoted
|
||||
@ -26,7 +29,7 @@ let json = r#"{
|
||||
// Set the second boolean parameter to true in order to map 'null' to '()'
|
||||
let map = engine.parse_json(json, true)?;
|
||||
|
||||
map.len() == 6; // 'map' contains all properties in the JSON string
|
||||
map.len() == 6; // 'map' contains all properties in the JSON string
|
||||
|
||||
// Put the object map into a 'Scope'
|
||||
let mut scope = Scope::new();
|
||||
@ -34,7 +37,7 @@ scope.push("map", map);
|
||||
|
||||
let result = engine.eval_with_scope::<INT>(r#"map["^^^!!!"].len()"#)?;
|
||||
|
||||
result == 3; // the object map is successfully used in the script
|
||||
result == 3; // the object map is successfully used in the script
|
||||
```
|
||||
|
||||
Representation of Numbers
|
||||
@ -45,3 +48,30 @@ the [`no_float`] feature is not used. Most common generators of JSON data disti
|
||||
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
|
||||
--------------------------
|
||||
|
||||
`Engine::parse_json` depends on the fact that the [object map] literal syntax in Rhai is _almost_
|
||||
the same as a JSON object. However, it is _almost_ because the syntax for a sub-object in JSON
|
||||
(i.e. "`{ ... }`") is different from a Rhai [object map] literal (i.e. "`#{ ... }`").
|
||||
|
||||
When `Engine::parse_json` encounters JSON with sub-objects, it fails with a syntax error.
|
||||
|
||||
If it is certain that no text string in the JSON will ever contain the character '`{`',
|
||||
then it is possible to parse it by first replacing all occupance of '`{`' with "`#{`".
|
||||
|
||||
A JSON object hash starting with `#{` is handled transparently by `Engine::parse_json`.
|
||||
|
||||
```rust
|
||||
// JSON with sub-object 'b'.
|
||||
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
|
||||
|
||||
let new_json = json.replace("{" "#{");
|
||||
|
||||
// The leading '{' will also be replaced to '#{', but parse_json can handle this.
|
||||
let map = engine.parse_json(&new_json, false)?;
|
||||
|
||||
map.len() == 2; // 'map' contains two properties: 'a' and 'b'
|
||||
```
|
||||
|
@ -63,16 +63,5 @@ For example, the `len` method of an [array] has the signature: `Fn(&mut Array) -
|
||||
The array itself is not modified in any way, but using a `&mut` parameter avoids a cloning that would
|
||||
otherwise have happened if the signature were `Fn(Array) -> INT`.
|
||||
|
||||
For primary types that are cheap to clone (e.g. those that implement `Copy`),
|
||||
including `ImmutableString`, this is not necessary.
|
||||
|
||||
|
||||
Avoid `&mut ImmutableString`
|
||||
---------------------------
|
||||
|
||||
`ImmutableString`, Rhai's internal [string] type, is an exception.
|
||||
|
||||
`ImmutableString` is cheap to clone, but expensive to take a mutable reference (because the underlying
|
||||
string must be cloned to make a private copy).
|
||||
|
||||
Therefore, avoid using `&mut ImmutableString` unless the intention is to mutate it.
|
||||
For primary types that are cheap to clone (e.g. those that implement `Copy`), including `ImmutableString`,
|
||||
this is not necessary.
|
||||
|
@ -46,7 +46,7 @@ struct Config {
|
||||
### Make Shared Object
|
||||
|
||||
```rust
|
||||
let config: Rc<RefCell<Config>> = Rc::new(RefCell::(Default::default()));
|
||||
let config: Rc<RefCell<Config>> = Rc::new(RefCell::new(Default::default()));
|
||||
```
|
||||
|
||||
### Register Config API
|
||||
@ -76,7 +76,17 @@ engine.register_fn("config_add", move |values: &mut Array|
|
||||
|
||||
let cfg = config.clone();
|
||||
engine.register_fn("config_add", move |key: String, value: bool|
|
||||
cfg.borrow_mut().som_map.insert(key, value)
|
||||
cfg.borrow_mut().some_map.insert(key, value)
|
||||
);
|
||||
|
||||
let cfg = config.clone();
|
||||
engine.register_fn("config_contains", move |value: String|
|
||||
cfg.borrow().some_list.contains(&value)
|
||||
);
|
||||
|
||||
let cfg = config.clone();
|
||||
engine.register_fn("config_is_set", move |value: String|
|
||||
cfg.borrow().some_map.get(&value).cloned().unwrap_or(false)
|
||||
);
|
||||
```
|
||||
|
||||
@ -91,7 +101,10 @@ config_set_id("hello");
|
||||
|
||||
config_add("foo"); // add to list
|
||||
config_add("bar", true); // add to map
|
||||
config_add("baz", false); // add to map
|
||||
|
||||
if config_contains("hey") || config_is_set("hey") {
|
||||
config_add("baz", false); // add to map
|
||||
}
|
||||
```
|
||||
|
||||
### Load the Configuration
|
||||
@ -103,3 +116,35 @@ let id = config_get_id();
|
||||
|
||||
id == "hello";
|
||||
```
|
||||
|
||||
|
||||
Consider a Custom Syntax
|
||||
------------------------
|
||||
|
||||
This is probably one of the few scenarios where a [custom syntax] can be recommended.
|
||||
|
||||
A properly-designed [custom syntax] can make the configuration file clean, simple to write,
|
||||
easy to understand and quick to modify.
|
||||
|
||||
For example, the above configuration example may be expressed by this custom syntax:
|
||||
|
||||
```rust
|
||||
------------------
|
||||
| my_config.rhai |
|
||||
------------------
|
||||
|
||||
// Configure ID
|
||||
id "hello";
|
||||
|
||||
// Add to list
|
||||
list +"foo"
|
||||
|
||||
// Add to map
|
||||
map "bar" => true;
|
||||
|
||||
if config contains "hey" || config is_set "hey" {
|
||||
map "baz" => false;
|
||||
}
|
||||
```
|
||||
|
||||
Notice that `contains` and `is_set` may be implemented as a [custom operator].
|
||||
|
89
doc/src/patterns/multiple.md
Normal file
89
doc/src/patterns/multiple.md
Normal file
@ -0,0 +1,89 @@
|
||||
Multiple Instantiation
|
||||
======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Background
|
||||
----------
|
||||
|
||||
Rhai's [features] are not strictly additive. This is easily deduced from the [`no_std`] feature
|
||||
which prepares the crate for `no-std` builds. Obviously, turning on this feature has a material
|
||||
impact on how Rhai behaves.
|
||||
|
||||
Many crates resolve this by going the opposite direction: build for `no-std` in default,
|
||||
but add a `std` feature, included by default, which builds for the `stdlib`.
|
||||
|
||||
|
||||
Rhai Language Features Are Not Additive
|
||||
--------------------------------------
|
||||
|
||||
Rhai, however, is more complex. Language features cannot be easily made _additive_.
|
||||
|
||||
That is because the _lack_ of a language feature is a feature by itself.
|
||||
|
||||
For example, by including [`no_float`], a project sets the Rhai language to ignore floating-point math.
|
||||
Floating-point numbers do not even parse under this case and will generate syntax errors.
|
||||
Assume that the project expects this behavior (why? perhaps integers are all that make sense
|
||||
within the project domain).
|
||||
|
||||
Now, assume that a dependent crate also depends on Rhai. Under such circumstances,
|
||||
unless _exact_ versioning is used and the dependent crate depends on a _different_ version
|
||||
of Rhai, Cargo automatically _merges_ both dependencies, with the [`no_float`] feature turned on
|
||||
because Cargo features are _additive_.
|
||||
|
||||
This will break the dependent crate, which does not by itself specify [`no_float`]
|
||||
and expects floating-point numbers and math to work normally.
|
||||
|
||||
There is no way out of this dilemma. Reversing the [features] set with a `float` feature
|
||||
causes the project to break because floating-point numbers are not rejected as expected.
|
||||
|
||||
|
||||
Multiple Instantiations of Rhai Within The Same Project
|
||||
------------------------------------------------------
|
||||
|
||||
The trick is to differentiate between multiple identical copies of Rhai, each having
|
||||
a different [features] set, by their _sources_:
|
||||
|
||||
* Different versions from [`crates.io`](https://crates.io/crates/rhai/) - The official crate.
|
||||
|
||||
* Different releases from [`GitHub`](https://github.com/jonathandturner/rhai) - Crate source on GitHub.
|
||||
|
||||
* Forked copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) on GitHub.
|
||||
|
||||
* Local copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) downloaded form GitHub.
|
||||
|
||||
Use the following configuration in `Cargo.toml` to pull in multiple copies of Rhai within the same project:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = { version = "{{version}}", features = [ "no_float" ] }
|
||||
rhai_github = { git = "https://github.com/jonathandturner/rhai", features = [ "unchecked" ] }
|
||||
rhai_my_github = { git = "https://github.com/my_github/rhai", branch = "variation1", features = [ "serde", "no_closure" ] }
|
||||
rhai_local = { path = "../rhai_copy" }
|
||||
```
|
||||
|
||||
The example above creates four different modules: `rhai`, `rhai_github`, `rhai_my_github` and
|
||||
`rhai_local`, each referring to a different Rhai copy with the appropriate [features] set.
|
||||
|
||||
Only one crate of any particular version can be used from each source, because Cargo merges
|
||||
all candidate cases within the same source, adding all [features] together.
|
||||
|
||||
If more than four different instantiations of Rhai is necessary (why?), create more local repositories
|
||||
or GitHub forks or branches.
|
||||
|
||||
|
||||
Caveat - No Way To Avoid Dependency Conflicts
|
||||
--------------------------------------------
|
||||
|
||||
Unfortunately, pulling in Rhai from different sources do not resolve the problem of
|
||||
[features] conflict between dependencies. Even overriding `crates.io` via the `[patch]` manifest
|
||||
section doesn't work - all dependencies will eventually find the only one copy.
|
||||
|
||||
What is necessary - multiple copies of Rhai, one for each dependent crate that requires it,
|
||||
together with their _unique_ [features] set intact. In other words, turning off Cargo's
|
||||
crate merging feature _just for Rhai_.
|
||||
|
||||
Unfortunately, as of this writing, there is no known method to achieve it.
|
||||
|
||||
Therefore, moral of the story: avoid pulling in multiple crates that depend on Rhai.
|
@ -33,7 +33,7 @@ engine.register_raw_fn(
|
||||
|
||||
// But remember this is Rust, so you can keep only one mutable reference at any one time!
|
||||
// Therefore, get a '&mut' reference to the first argument _last_.
|
||||
// Alternatively, use `args.split_at_mut(1)` etc. to split the slice first.
|
||||
// Alternatively, use `args.split_first_mut()` etc. to split the slice first.
|
||||
|
||||
let y: i64 = *args[1].read_lock::<i64>() // get a reference to the second argument
|
||||
.unwrap(); // then copying it because it is a primary type
|
||||
@ -163,17 +163,17 @@ Hold Multiple References
|
||||
------------------------
|
||||
|
||||
In order to access a value argument that is expensive to clone _while_ holding a mutable reference
|
||||
to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_at`
|
||||
to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_first`
|
||||
to partition the slice:
|
||||
|
||||
```rust
|
||||
// Partition the slice
|
||||
let (first, rest) = args.split_at_mut(1);
|
||||
let (first, rest) = args.split_first_mut().unwrap();
|
||||
|
||||
// Mutable reference to the first parameter
|
||||
let this_ptr: &mut Dynamic = &mut *first[0].write_lock::<A>().unwrap();
|
||||
let this_ptr: &mut A = &mut *first.write_lock::<A>().unwrap();
|
||||
|
||||
// Immutable reference to the second value parameter
|
||||
// This can be mutable but there is no point because the parameter is passed by value
|
||||
let value_ref: &Dynamic = &*rest[0].read_lock::<B>().unwrap();
|
||||
let value_ref: &B = &*rest[0].read_lock::<B>().unwrap();
|
||||
```
|
||||
|
@ -41,25 +41,3 @@ let len = engine.eval::<i64>("x.len1()")?; // 'x' is cloned, ve
|
||||
let len = engine.eval::<i64>("x.len2()")?; // 'x' is shared
|
||||
let len = engine.eval::<i64>("x.len3()")?; // 'x' is shared
|
||||
```
|
||||
|
||||
|
||||
Avoid `&mut ImmutableString`
|
||||
---------------------------
|
||||
|
||||
Rhai functions can take a first `&mut` parameter. Usually this is a good idea because it avoids
|
||||
cloning of the argument (except for primary types where cloning is cheap), so its use is encouraged
|
||||
even though there is no intention to ever mutate that argument.
|
||||
|
||||
[`ImmutableString`][string] is an exception to this rule.
|
||||
|
||||
While `ImmutableString` is cheap to clone (only incrementing a reference count), taking a mutable
|
||||
reference to it involves making a private clone of the underlying string because Rhai has no way
|
||||
to find out whether that parameter will be mutated.
|
||||
|
||||
If the `ImmutableString` is not shared by any other variables, then Rhai just returns a mutable
|
||||
reference to it since nobody else is watching! Otherwise a private copy is made first,
|
||||
because other reference holders will not expect the `ImmutableString` to ever change
|
||||
(it is supposed to be _immutable_).
|
||||
|
||||
Therefore, avoid using `&mut ImmutableString` as the first parameter of a function unless you really
|
||||
intend to mutate that string. Use `ImmutableString` instead.
|
||||
|
@ -52,3 +52,15 @@ no floating-point, is `Send + Sync` (so it can be safely used across threads), a
|
||||
nor loading external [modules].
|
||||
|
||||
This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware.
|
||||
|
||||
|
||||
Caveat - Features Are Not Additive
|
||||
---------------------------------
|
||||
|
||||
Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities.
|
||||
|
||||
In fact, most features are _subtractive_ - i.e. they _remove_ functionalities.
|
||||
|
||||
There is a reason for this design, because the _lack_ of a language feature by itself is a feature.
|
||||
|
||||
See [here]({{rootUrl}}/patterns/multiple.md) for more details.
|
||||
|
45
src/api.rs
45
src/api.rs
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{Engine, Imports, State};
|
||||
use crate::error::ParseError;
|
||||
use crate::error::{ParseError, ParseErrorType};
|
||||
use crate::fn_native::{IteratorFn, SendSync};
|
||||
use crate::module::{FuncReturn, Module};
|
||||
use crate::optimize::OptimizationLevel;
|
||||
@ -34,6 +34,7 @@ use crate::optimize::optimize_into_ast;
|
||||
use crate::stdlib::{
|
||||
any::{type_name, TypeId},
|
||||
boxed::Box,
|
||||
string::ToString,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -895,24 +896,39 @@ impl Engine {
|
||||
|
||||
/// Parse a JSON string into a map.
|
||||
///
|
||||
/// The JSON string must be an object hash. It cannot be a simple JavaScript primitive.
|
||||
///
|
||||
/// Set `has_null` to `true` in order to map `null` values to `()`.
|
||||
/// Setting it to `false` will cause a _variable not found_ error during parsing.
|
||||
///
|
||||
/// # JSON With Sub-Objects
|
||||
///
|
||||
/// This method assumes no sub-objects in the JSON string. That is because the syntax
|
||||
/// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`.
|
||||
/// Parsing a JSON string with sub-objects will cause a syntax error.
|
||||
///
|
||||
/// If it is certain that the character `{` never appears in any text string within the JSON object,
|
||||
/// then globally replace `{` with `#{` before calling this method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::Engine;
|
||||
/// use rhai::{Engine, Map};
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
/// let map = engine.parse_json(r#"{"a":123, "b":42, "c":false, "d":null}"#, true)?;
|
||||
/// let map = engine.parse_json(
|
||||
/// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"#
|
||||
/// .replace("{", "#{").as_str(), true)?;
|
||||
///
|
||||
/// assert_eq!(map.len(), 4);
|
||||
/// assert_eq!(map.get("a").cloned().unwrap().cast::<i64>(), 123);
|
||||
/// assert_eq!(map.get("b").cloned().unwrap().cast::<i64>(), 42);
|
||||
/// assert_eq!(map.get("c").cloned().unwrap().cast::<bool>(), false);
|
||||
/// assert_eq!(map.get("d").cloned().unwrap().cast::<()>(), ());
|
||||
/// assert_eq!(map["a"].as_int().unwrap(), 123);
|
||||
/// assert_eq!(map["b"].as_int().unwrap(), 42);
|
||||
/// assert!(map["d"].is::<()>());
|
||||
///
|
||||
/// let c = map["c"].read_lock::<Map>().unwrap();
|
||||
/// assert_eq!(c["x"].as_bool().unwrap(), false);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@ -921,7 +937,20 @@ impl Engine {
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Trims the JSON string and add a '#' in front
|
||||
let scripts = ["#", json.trim()];
|
||||
let json_text = json.trim_start();
|
||||
let scripts = if json_text.starts_with(Token::MapStart.syntax().as_ref()) {
|
||||
[json_text, ""]
|
||||
} else if json_text.starts_with(Token::LeftBrace.syntax().as_ref()) {
|
||||
["#", json_text]
|
||||
} else {
|
||||
return Err(ParseErrorType::MissingToken(
|
||||
Token::LeftBrace.syntax().to_string(),
|
||||
"to start a JSON object hash".to_string(),
|
||||
)
|
||||
.into_err(Position::new(1, (json.len() - json_text.len() + 1) as u16))
|
||||
.into());
|
||||
};
|
||||
|
||||
let stream = lex(
|
||||
&scripts,
|
||||
if has_null {
|
||||
|
@ -521,13 +521,13 @@ impl Engine {
|
||||
|
||||
let result = if _is_method {
|
||||
// Method call of script function - map first argument to `this`
|
||||
let (first, rest) = args.split_at_mut(1);
|
||||
let (first, rest) = args.split_first_mut().unwrap();
|
||||
self.call_script_fn(
|
||||
scope,
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
&mut Some(first[0]),
|
||||
&mut Some(*first),
|
||||
fn_name,
|
||||
func,
|
||||
rest,
|
||||
@ -974,6 +974,7 @@ impl Engine {
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let modules = modules.as_ref().unwrap();
|
||||
let mut arg_values: StaticVec<_>;
|
||||
let mut first_arg_value = None;
|
||||
let mut args: StaticVec<_>;
|
||||
|
||||
if args_expr.is_empty() {
|
||||
@ -988,17 +989,28 @@ impl Engine {
|
||||
Expr::Variable(x) if x.1.is_none() => {
|
||||
arg_values = args_expr
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
|
||||
.enumerate()
|
||||
.map(|(i, expr)| {
|
||||
// Skip the first argument
|
||||
if i == 0 {
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Get target reference to first argument
|
||||
let (target, _, _, pos) =
|
||||
search_scope_only(scope, state, this_ptr, args_expr.get(0).unwrap())?;
|
||||
|
||||
self.inc_operations(state)
|
||||
.map_err(|err| err.new_position(pos))?;
|
||||
|
||||
args = once(target).chain(arg_values.iter_mut()).collect();
|
||||
let (first, rest) = arg_values.split_first_mut().unwrap();
|
||||
first_arg_value = Some(first);
|
||||
|
||||
args = once(target).chain(rest.iter_mut()).collect();
|
||||
}
|
||||
// func(..., ...) or func(mod::x, ...)
|
||||
_ => {
|
||||
@ -1037,6 +1049,13 @@ impl Engine {
|
||||
match func {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Some(f) if f.is_script() => {
|
||||
// Clone first argument
|
||||
if let Some(first) = first_arg_value {
|
||||
let first_val = args[0].clone();
|
||||
args[0] = first;
|
||||
*args[0] = first_val;
|
||||
}
|
||||
|
||||
let args = args.as_mut();
|
||||
let func = f.get_fn_def();
|
||||
|
||||
@ -1055,7 +1074,19 @@ impl Engine {
|
||||
|
||||
self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level)
|
||||
}
|
||||
Some(f) => f.get_native_fn()(self, lib, args.as_mut()),
|
||||
Some(f) if f.is_native() => {
|
||||
if !f.is_method() {
|
||||
// Clone first argument
|
||||
if let Some(first) = first_arg_value {
|
||||
let first_val = args[0].clone();
|
||||
args[0] = first;
|
||||
*args[0] = first_val;
|
||||
}
|
||||
}
|
||||
|
||||
f.get_native_fn()(self, lib, args.as_mut())
|
||||
}
|
||||
Some(_) => unreachable!(),
|
||||
None if def_val.is_some() => Ok(def_val.unwrap().into()),
|
||||
None => EvalAltResult::ErrorFunctionNotFound(
|
||||
format!(
|
||||
|
@ -611,9 +611,9 @@ impl Module {
|
||||
) -> u64 {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let mut a = args[0].write_lock::<A>().unwrap();
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
func(&mut a, b).map(Dynamic::from)
|
||||
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)))
|
||||
@ -735,9 +735,9 @@ impl Module {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let mut a = args[0].write_lock::<A>().unwrap();
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
func(&mut a, b, c).map(Dynamic::from)
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
|
||||
@ -769,9 +769,9 @@ impl Module {
|
||||
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let mut a = args[0].write_lock::<A>().unwrap();
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
func(&mut a, b, c).map(Dynamic::from)
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(
|
||||
@ -892,9 +892,9 @@ impl Module {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let d = mem::take(args[3]).cast::<D>();
|
||||
let mut a = args[0].write_lock::<A>().unwrap();
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
|
||||
func(&mut a, b, c, d).map(Dynamic::from)
|
||||
func(a, b, c, d).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [
|
||||
TypeId::of::<A>(),
|
||||
@ -928,6 +928,18 @@ impl Module {
|
||||
self.all_functions.get(&hash_qualified_fn)
|
||||
}
|
||||
|
||||
/// Combine another module into this module.
|
||||
/// The other module is consumed to merge into this module.
|
||||
pub fn combine(&mut self, other: Self) -> &mut Self {
|
||||
self.variables.extend(other.variables.into_iter());
|
||||
self.functions.extend(other.functions.into_iter());
|
||||
self.type_iterators.extend(other.type_iterators.into_iter());
|
||||
self.all_functions.clear();
|
||||
self.all_variables.clear();
|
||||
self.indexed = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Merge another module into this module.
|
||||
pub fn merge(&mut self, other: &Self) -> &mut Self {
|
||||
self.merge_filtered(other, |_, _, _| true)
|
||||
|
@ -414,7 +414,8 @@ struct ParseState<'e> {
|
||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: HashMap<String, Position>,
|
||||
/// An indicator that disables variable capturing into externals one single time.
|
||||
/// An indicator that disables variable capturing into externals one single time
|
||||
/// up until the nearest consumed Identifier token.
|
||||
/// If set to false the next call to `access_var` will not capture the variable.
|
||||
/// All consequent calls to `access_var` will not be affected
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -1637,6 +1638,11 @@ fn parse_primary(
|
||||
|
||||
// Function call
|
||||
Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => {
|
||||
// Once the identifier consumed we must enable next variables capturing
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.allow_capture = true;
|
||||
}
|
||||
Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
|
||||
}
|
||||
// Normal variable access
|
||||
|
@ -1,6 +1,7 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, RegisterFn, INT};
|
||||
use std::any::TypeId;
|
||||
use std::mem::take;
|
||||
|
||||
#[test]
|
||||
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -88,6 +89,59 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let a = 40;
|
||||
let f = |x| {
|
||||
let f = |x| {
|
||||
let f = |x| plus_one(a) + x;
|
||||
f.call(x)
|
||||
};
|
||||
f.call(x)
|
||||
};
|
||||
f.call(1)
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let a = 21;
|
||||
let f = |x| a += x;
|
||||
f.call(a);
|
||||
a
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
#[allow(deprecated)]
|
||||
engine.register_raw_fn(
|
||||
"custom_call",
|
||||
&[TypeId::of::<INT>(), TypeId::of::<FnPtr>()],
|
||||
|engine: &Engine, module: &Module, args: &mut [&mut Dynamic]| {
|
||||
let func = take(args[1]).cast::<FnPtr>();
|
||||
|
||||
func.call_dynamic(engine, module, None, [])
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let a = 41;
|
||||
let b = 0;
|
||||
let f = || b.custom_call(|| a + 1);
|
||||
|
||||
f.call()
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -101,12 +155,12 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
let a = 1;
|
||||
let b = 40;
|
||||
let foo = |x| { this += a + x };
|
||||
b.call(foo, 1);
|
||||
b
|
||||
"#
|
||||
let a = 1;
|
||||
let b = 40;
|
||||
let foo = |x| { this += a + x };
|
||||
b.call(foo, 1);
|
||||
b
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
@ -115,11 +169,11 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
|
||||
*engine
|
||||
.eval::<INT>(
|
||||
r#"
|
||||
let a = 20;
|
||||
let foo = |x| { this += a + x };
|
||||
a.call(foo, 1);
|
||||
a
|
||||
"#
|
||||
let a = 20;
|
||||
let foo = |x| { this += a + x };
|
||||
a.call(foo, 1);
|
||||
a
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataRace(_, _)
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, Map, Scope, INT};
|
||||
use rhai::{Engine, EvalAltResult, Map, ParseErrorType, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -182,6 +182,14 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
|
||||
);
|
||||
}
|
||||
|
||||
engine.parse_json(&format!("#{}", json), true)?;
|
||||
|
||||
assert!(matches!(
|
||||
*engine.parse_json(" 123", true).expect_err("should error"),
|
||||
EvalAltResult::ErrorParsing(ParseErrorType::MissingToken(token, _), pos)
|
||||
if token == "{" && pos.position() == Some(4)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,31 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
import "hello" as h1;
|
||||
import "hello" as h2;
|
||||
let x = 42;
|
||||
h1::sum(x, -10, 3, 7)
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
import "hello" as h1;
|
||||
import "hello" as h2;
|
||||
let x = 42;
|
||||
h1::sum(x, 0, 0, 0);
|
||||
x
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
|
Loading…
Reference in New Issue
Block a user