Implement index setters.
This commit is contained in:
parent
56eb659d5a
commit
5f40a1376a
59
README.md
59
README.md
@ -472,7 +472,7 @@ The follow packages are available:
|
|||||||
Packages typically contain Rust functions that are callable within a Rhai script.
|
Packages typically contain Rust functions that are callable within a Rhai script.
|
||||||
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
|
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
|
||||||
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
|
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
|
||||||
even across threads (under the [`sync`] feature). Therefore, a package only has to be created _once_.
|
even across threads (under [`sync`]). Therefore, a package only has to be created _once_.
|
||||||
|
|
||||||
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
|
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
|
||||||
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
|
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
|
||||||
@ -572,7 +572,7 @@ if type_of(x) == "string" {
|
|||||||
|
|
||||||
[`Dynamic`]: #dynamic-values
|
[`Dynamic`]: #dynamic-values
|
||||||
|
|
||||||
A `Dynamic` value can be _any_ type. However, under the [`sync`] feature, all types must be `Send + Sync`.
|
A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`.
|
||||||
|
|
||||||
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
|
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
|
||||||
actions based on the actual value's type.
|
actions based on the actual value's type.
|
||||||
@ -975,8 +975,8 @@ let result = engine.eval::<i64>(
|
|||||||
println!("result: {}", result); // prints 1
|
println!("result: {}", result); // prints 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Under the [`no_object`] feature, however, the _method_ style of function calls
|
Under [`no_object`], however, the _method_ style of function calls (i.e. calling a function as an object-method)
|
||||||
(i.e. calling a function as an object-method) is no longer supported.
|
is no longer supported.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
|
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
|
||||||
@ -999,8 +999,7 @@ let x = new_ts();
|
|||||||
print(x.type_of()); // prints "Hello"
|
print(x.type_of()); // prints "Hello"
|
||||||
```
|
```
|
||||||
|
|
||||||
Getters and setters
|
### Getters and setters
|
||||||
-------------------
|
|
||||||
|
|
||||||
Similarly, custom types can expose members by registering a `get` and/or `set` function.
|
Similarly, custom types can expose members by registering a `get` and/or `set` function.
|
||||||
|
|
||||||
@ -1040,12 +1039,10 @@ let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;
|
|||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
```
|
```
|
||||||
|
|
||||||
Indexers
|
### Indexers
|
||||||
--------
|
|
||||||
|
|
||||||
Custom types can also expose an _indexer_ by registering an indexer function.
|
Custom types can also expose an _indexer_ by registering an indexer function.
|
||||||
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
||||||
(but not update it - indexers are read-only).
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -1057,9 +1054,12 @@ impl TestStruct {
|
|||||||
fn get_field(&mut self, index: i64) -> i64 {
|
fn get_field(&mut self, index: i64) -> i64 {
|
||||||
self.fields[index as usize]
|
self.fields[index as usize]
|
||||||
}
|
}
|
||||||
|
fn set_field(&mut self, index: i64, value: i64) {
|
||||||
|
self.fields[index as usize] = value
|
||||||
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
TestStruct { fields: vec![1, 2, 42, 4, 5] }
|
TestStruct { fields: vec![1, 2, 3, 4, 5] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1068,22 +1068,31 @@ let engine = Engine::new();
|
|||||||
engine.register_type::<TestStruct>();
|
engine.register_type::<TestStruct>();
|
||||||
|
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
engine.register_indexer(TestStruct::get_field);
|
|
||||||
|
|
||||||
let result = engine.eval::<i64>("let a = new_ts(); a[2]")?;
|
// Shorthand: engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
|
||||||
|
engine.register_indexer_get(TestStruct::get_field);
|
||||||
|
engine.register_indexer_set(TestStruct::set_field);
|
||||||
|
|
||||||
|
let result = engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?;
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
```
|
```
|
||||||
|
|
||||||
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`
|
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
|
||||||
and `register_indexer` are not available under the [`no_object`] feature.
|
[arrays] and [object maps].
|
||||||
`register_indexer` is also not available under the [`no_index`] feature.
|
|
||||||
|
|
||||||
Printing for custom types
|
### Disabling custom types
|
||||||
-------------------------
|
|
||||||
|
|
||||||
To use custom types for `print` and `debug`, or format its value into a [string], it is necessary that the following
|
The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`,
|
||||||
functions be registered (assuming the custom type is `T` and it is `Display + Debug`):
|
`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`].
|
||||||
|
|
||||||
|
The indexers API `register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are also
|
||||||
|
not available under [`no_index`].
|
||||||
|
|
||||||
|
### Printing for custom types
|
||||||
|
|
||||||
|
To use custom types for `print` and `debug`, or convert its value into a [string], it is necessary that the following
|
||||||
|
functions be registered (assuming the custom type is `T : Display + Debug`):
|
||||||
|
|
||||||
| Function | Signature | Typical implementation | Usage |
|
| Function | Signature | Typical implementation | Usage |
|
||||||
| ----------- | ------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------- |
|
| ----------- | ------------------------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------- |
|
||||||
@ -1103,7 +1112,7 @@ By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting on
|
|||||||
but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
|
but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
|
||||||
from one invocation to the next, such a state must be manually created and passed in.
|
from one invocation to the next, such a state must be manually created and passed in.
|
||||||
|
|
||||||
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. Under the [`sync`] feature, however,
|
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. Under [`sync`], however,
|
||||||
only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`.
|
only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`.
|
||||||
This is extremely useful in multi-threaded applications.
|
This is extremely useful in multi-threaded applications.
|
||||||
|
|
||||||
@ -1205,7 +1214,7 @@ The following are reserved keywords in Rhai:
|
|||||||
| `import`, `export`, `as` | Modules | [`no_module`] |
|
| `import`, `export`, `as` | Modules | [`no_module`] |
|
||||||
|
|
||||||
Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled.
|
Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled.
|
||||||
For example, `fn` is a valid variable name under the [`no_function`] feature.
|
For example, `fn` is a valid variable name under [`no_function`].
|
||||||
|
|
||||||
Statements
|
Statements
|
||||||
----------
|
----------
|
||||||
@ -2375,10 +2384,10 @@ which simply loads a script file based on the path (with `.rhai` extension attac
|
|||||||
|
|
||||||
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
||||||
|
|
||||||
| Module Resolver | Description |
|
| Module Resolver | Description |
|
||||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. 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. |
|
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`]. 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. |
|
||||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under the [`no_std`] feature. |
|
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
|
||||||
|
|
||||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||||
|
|
||||||
|
@ -8,6 +8,13 @@ Breaking changes
|
|||||||
----------------
|
----------------
|
||||||
|
|
||||||
* Callback closure passed to `Engine::on_progress` now takes `&u64` instead of `u64` to be consistent with other callback signatures.
|
* Callback closure passed to `Engine::on_progress` now takes `&u64` instead of `u64` to be consistent with other callback signatures.
|
||||||
|
* `Engine::register_indexer` is renamed to `Engine::register_indexer_get`.
|
||||||
|
* `Module::set_indexer_fn` is renamed to `Module::set_indexer_get_fn`.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Indexers are now split into getters ans setters (which now support updates). The API is split into `Engine::register_indexer_get` and `Engine::register_indexer_set` with `Engine::register_indexer_get_set` being a shorthand. Similarly, `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn` are added.
|
||||||
|
|
||||||
|
|
||||||
Version 0.15.0
|
Version 0.15.0
|
||||||
|
107
src/api.rs
107
src/api.rs
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
use crate::any::{Dynamic, Variant};
|
use crate::any::{Dynamic, Variant};
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
get_script_function_by_signature, make_getter, make_setter, Engine, State, FUNC_INDEXER,
|
get_script_function_by_signature, make_getter, make_setter, Engine, State, FUNC_INDEXER_GET,
|
||||||
|
FUNC_INDEXER_SET,
|
||||||
};
|
};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::fn_call::FuncArgs;
|
use crate::fn_call::FuncArgs;
|
||||||
@ -275,7 +276,7 @@ impl Engine {
|
|||||||
self.register_set(name, set_fn);
|
self.register_set(name, set_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register an indexer function for a registered type with the `Engine`.
|
/// Register an index getter for a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
/// The function signature must start with `&mut self` and not `&self`.
|
/// The function signature must start with `&mut self` and not `&self`.
|
||||||
///
|
///
|
||||||
@ -305,7 +306,7 @@ impl Engine {
|
|||||||
/// engine.register_fn("new_ts", TestStruct::new);
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
///
|
///
|
||||||
/// // Register an indexer.
|
/// // Register an indexer.
|
||||||
/// engine.register_indexer(TestStruct::get_field);
|
/// engine.register_indexer_get(TestStruct::get_field);
|
||||||
///
|
///
|
||||||
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
|
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2]")?, 3);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -313,7 +314,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn register_indexer<T, X, U>(
|
pub fn register_indexer_get<T, X, U>(
|
||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
|
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
|
||||||
) where
|
) where
|
||||||
@ -321,7 +322,103 @@ impl Engine {
|
|||||||
U: Variant + Clone,
|
U: Variant + Clone,
|
||||||
X: Variant + Clone,
|
X: Variant + Clone,
|
||||||
{
|
{
|
||||||
self.register_fn(FUNC_INDEXER, callback);
|
self.register_fn(FUNC_INDEXER_GET, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register an index setter for a registered type with the `Engine`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// fields: Vec<i64>
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl TestStruct {
|
||||||
|
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
|
||||||
|
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// use rhai::{Engine, RegisterFn};
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register the custom type.
|
||||||
|
/// engine.register_type::<TestStruct>();
|
||||||
|
///
|
||||||
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
///
|
||||||
|
/// // Register an indexer.
|
||||||
|
/// engine.register_indexer_set(TestStruct::set_field);
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
|
||||||
|
/// 42
|
||||||
|
/// );
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn register_indexer_set<T, X, U>(
|
||||||
|
&mut self,
|
||||||
|
callback: impl Fn(&mut T, X, U) -> () + SendSync + 'static,
|
||||||
|
) where
|
||||||
|
T: Variant + Clone,
|
||||||
|
U: Variant + Clone,
|
||||||
|
X: Variant + Clone,
|
||||||
|
{
|
||||||
|
self.register_fn(FUNC_INDEXER_SET, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shorthand for register both index getter and setter functions for a registered type with the `Engine`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #[derive(Clone)]
|
||||||
|
/// struct TestStruct {
|
||||||
|
/// fields: Vec<i64>
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl TestStruct {
|
||||||
|
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
|
||||||
|
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
|
||||||
|
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// use rhai::{Engine, RegisterFn};
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register the custom type.
|
||||||
|
/// engine.register_type::<TestStruct>();
|
||||||
|
///
|
||||||
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
///
|
||||||
|
/// // Register an indexer.
|
||||||
|
/// engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?, 42);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub fn register_indexer_get_set<T, X, U>(
|
||||||
|
&mut self,
|
||||||
|
getter: impl Fn(&mut T, X) -> U + SendSync + 'static,
|
||||||
|
setter: impl Fn(&mut T, X, U) -> () + SendSync + 'static,
|
||||||
|
) where
|
||||||
|
T: Variant + Clone,
|
||||||
|
U: Variant + Clone,
|
||||||
|
X: Variant + Clone,
|
||||||
|
{
|
||||||
|
self.register_indexer_get(getter);
|
||||||
|
self.register_indexer_set(setter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile a string into an `AST`, which can be used later for evaluation.
|
/// Compile a string into an `AST`, which can be used later for evaluation.
|
||||||
|
220
src/engine.rs
220
src/engine.rs
@ -74,9 +74,11 @@ pub const KEYWORD_EVAL: &str = "eval";
|
|||||||
pub const FUNC_TO_STRING: &str = "to_string";
|
pub const FUNC_TO_STRING: &str = "to_string";
|
||||||
pub const FUNC_GETTER: &str = "get$";
|
pub const FUNC_GETTER: &str = "get$";
|
||||||
pub const FUNC_SETTER: &str = "set$";
|
pub const FUNC_SETTER: &str = "set$";
|
||||||
pub const FUNC_INDEXER: &str = "$index$";
|
pub const FUNC_INDEXER_GET: &str = "$index$get$";
|
||||||
|
pub const FUNC_INDEXER_SET: &str = "$index$set$";
|
||||||
|
|
||||||
/// A type that encapsulates a mutation target for an expression with side effects.
|
/// A type that encapsulates a mutation target for an expression with side effects.
|
||||||
|
#[derive(Debug)]
|
||||||
enum Target<'a> {
|
enum Target<'a> {
|
||||||
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
||||||
Ref(&'a mut Dynamic),
|
Ref(&'a mut Dynamic),
|
||||||
@ -91,40 +93,45 @@ impl Target<'_> {
|
|||||||
/// Is the `Target` a reference pointing to other data?
|
/// Is the `Target` a reference pointing to other data?
|
||||||
pub fn is_ref(&self) -> bool {
|
pub fn is_ref(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(_) => true,
|
Self::Ref(_) => true,
|
||||||
Target::Value(_) | Target::StringChar(_, _, _) => false,
|
Self::Value(_) | Self::StringChar(_, _, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Is the `Target` an owned value?
|
||||||
|
pub fn is_value(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Ref(_) => false,
|
||||||
|
Self::Value(_) => true,
|
||||||
|
Self::StringChar(_, _, _) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
|
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
|
||||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
pub fn clone_into_dynamic(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(r) => r.clone(), // Referenced value is cloned
|
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
||||||
Target::Value(v) => v, // Owned value is simply taken
|
Self::Value(v) => v, // Owned value is simply taken
|
||||||
Target::StringChar(_, _, ch) => ch, // Character is taken
|
Self::StringChar(_, _, ch) => ch, // Character is taken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference from the `Target`.
|
/// Get a mutable reference from the `Target`.
|
||||||
pub fn as_mut(&mut self) -> &mut Dynamic {
|
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(r) => *r,
|
Self::Ref(r) => *r,
|
||||||
Target::Value(ref mut r) => r,
|
Self::Value(ref mut r) => r,
|
||||||
Target::StringChar(_, _, ref mut r) => r,
|
Self::StringChar(_, _, ref mut r) => r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the value of the `Target`.
|
/// Update the value of the `Target`.
|
||||||
/// Position in `EvalAltResult` is None and must be set afterwards.
|
/// Position in `EvalAltResult` is None and must be set afterwards.
|
||||||
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
|
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(r) => **r = new_val,
|
Self::Ref(r) => **r = new_val,
|
||||||
Target::Value(_) => {
|
Self::Value(_) => {
|
||||||
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
Target::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => {
|
Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => {
|
||||||
// Replace the character at the specified index position
|
// Replace the character at the specified index position
|
||||||
let new_ch = new_val
|
let new_ch = new_val
|
||||||
.as_char()
|
.as_char()
|
||||||
@ -163,20 +170,17 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
|||||||
///
|
///
|
||||||
/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via
|
/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via
|
||||||
/// direct lifetime casting.
|
/// direct lifetime casting.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Default)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
|
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
|
||||||
/// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned.
|
/// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned.
|
||||||
/// When that happens, this flag is turned on to force a scope lookup by name.
|
/// When that happens, this flag is turned on to force a scope lookup by name.
|
||||||
pub always_search: bool,
|
pub always_search: bool,
|
||||||
|
|
||||||
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
||||||
/// is one level higher, and so on.
|
/// is one level higher, and so on.
|
||||||
pub scope_level: usize,
|
pub scope_level: usize,
|
||||||
|
|
||||||
/// Number of operations performed.
|
/// Number of operations performed.
|
||||||
pub operations: u64,
|
pub operations: u64,
|
||||||
|
|
||||||
/// Number of modules loaded.
|
/// Number of modules loaded.
|
||||||
pub modules: u64,
|
pub modules: u64,
|
||||||
}
|
}
|
||||||
@ -361,14 +365,23 @@ fn search_scope<'s, 'a>(
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if it is qualified
|
||||||
if let Some(modules) = modules.as_ref() {
|
if let Some(modules) = modules.as_ref() {
|
||||||
let module = if let Some(index) = modules.index() {
|
// Qualified - check if the root module is directly indexed
|
||||||
|
let index = if state.always_search {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
modules.index()
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = if let Some(index) = index {
|
||||||
scope
|
scope
|
||||||
.get_mut(scope.len() - index.get())
|
.get_mut(scope.len() - index.get())
|
||||||
.0
|
.0
|
||||||
.downcast_mut::<Module>()
|
.downcast_mut::<Module>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
} else {
|
} else {
|
||||||
|
// Find the root module in the scope
|
||||||
let (id, root_pos) = modules.get(0);
|
let (id, root_pos) = modules.get(0);
|
||||||
|
|
||||||
scope
|
scope
|
||||||
@ -376,28 +389,27 @@ fn search_scope<'s, 'a>(
|
|||||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
|
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok((
|
let target = module.get_qualified_var_mut(name, *hash_var, *pos)?;
|
||||||
module.get_qualified_var_mut(name, *hash_var, *pos)?,
|
|
||||||
name,
|
|
||||||
// Module variables are constant
|
|
||||||
ScopeEntryType::Constant,
|
|
||||||
*pos,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = if state.always_search { None } else { *index };
|
// Module variables are constant
|
||||||
|
Ok((target, name, ScopeEntryType::Constant, *pos))
|
||||||
let index = if let Some(index) = index {
|
|
||||||
scope.len() - index.get()
|
|
||||||
} else {
|
} else {
|
||||||
scope
|
// Unqualified - check if it is directly indexed
|
||||||
.get_index(name)
|
let index = if state.always_search { None } else { *index };
|
||||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), *pos)))?
|
|
||||||
.0
|
|
||||||
};
|
|
||||||
|
|
||||||
let (val, typ) = scope.get_mut(index);
|
let index = if let Some(index) = index {
|
||||||
Ok((val, name, typ, *pos))
|
scope.len() - index.get()
|
||||||
|
} else {
|
||||||
|
// Find the variable in the scope
|
||||||
|
scope
|
||||||
|
.get_index(name)
|
||||||
|
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), *pos)))?
|
||||||
|
.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let (val, typ) = scope.get_mut(index);
|
||||||
|
Ok((val, name, typ, *pos))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
@ -585,10 +597,16 @@ impl Engine {
|
|||||||
.or_else(|| self.packages.get_fn(hashes.0));
|
.or_else(|| self.packages.get_fn(hashes.0));
|
||||||
|
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
|
// Calling pure function in method-call?
|
||||||
|
normalize_first_arg(
|
||||||
|
(func.is_pure() || func.is_script()) && is_ref,
|
||||||
|
&mut this_copy,
|
||||||
|
&mut old_this_ptr,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
if func.is_script() {
|
if func.is_script() {
|
||||||
// Run scripted function
|
// Run scripted function
|
||||||
normalize_first_arg(is_ref, &mut this_copy, &mut old_this_ptr, args);
|
|
||||||
|
|
||||||
let fn_def = func.get_fn_def();
|
let fn_def = func.get_fn_def();
|
||||||
let result =
|
let result =
|
||||||
self.call_script_fn(scope, state, lib, fn_name, fn_def, args, level)?;
|
self.call_script_fn(scope, state, lib, fn_name, fn_def, args, level)?;
|
||||||
@ -598,14 +616,6 @@ impl Engine {
|
|||||||
|
|
||||||
return Ok((result, false));
|
return Ok((result, false));
|
||||||
} else {
|
} else {
|
||||||
// Calling pure function in method-call?
|
|
||||||
normalize_first_arg(
|
|
||||||
func.is_pure() && is_ref,
|
|
||||||
&mut this_copy,
|
|
||||||
&mut old_this_ptr,
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Run external function
|
// Run external function
|
||||||
let result = func.get_native_fn()(args)?;
|
let result = func.get_native_fn()(args)?;
|
||||||
|
|
||||||
@ -668,22 +678,40 @@ impl Engine {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let types_list: Vec<_> = args
|
// index getter function not found?
|
||||||
.iter()
|
if fn_name == FUNC_INDEXER_GET && args.len() == 2 {
|
||||||
.map(|name| self.map_type_name(name.type_name()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Getter function not found?
|
|
||||||
if fn_name == FUNC_INDEXER {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||||
format!("[]({})", types_list.join(", ")),
|
format!(
|
||||||
|
"{} [{}]",
|
||||||
|
self.map_type_name(args[0].type_name()),
|
||||||
|
self.map_type_name(args[1].type_name()),
|
||||||
|
),
|
||||||
|
Position::none(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// index setter function not found?
|
||||||
|
if fn_name == FUNC_INDEXER_SET {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||||
|
format!(
|
||||||
|
"{} [{}]=",
|
||||||
|
self.map_type_name(args[0].type_name()),
|
||||||
|
self.map_type_name(args[1].type_name()),
|
||||||
|
),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raise error
|
// Raise error
|
||||||
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||||
format!("{} ({})", fn_name, types_list.join(", ")),
|
format!(
|
||||||
|
"{} ({})",
|
||||||
|
fn_name,
|
||||||
|
args.iter()
|
||||||
|
.map(|name| self.map_type_name(name.type_name()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
@ -787,12 +815,8 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||||
let hash_fn = calc_fn_hash(
|
let arg_types = args.iter().map(|a| a.type_id());
|
||||||
empty(),
|
let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types);
|
||||||
fn_name,
|
|
||||||
args.len(),
|
|
||||||
args.iter().map(|a| a.type_id()),
|
|
||||||
);
|
|
||||||
let hashes = (hash_fn, if native_only { 0 } else { hash_fn_def });
|
let hashes = (hash_fn, if native_only { 0 } else { hash_fn_def });
|
||||||
|
|
||||||
match fn_name {
|
match fn_name {
|
||||||
@ -890,8 +914,8 @@ impl Engine {
|
|||||||
let (idx, expr, pos) = x.as_ref();
|
let (idx, expr, pos) = x.as_ref();
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
let idx_pos = idx.position();
|
let idx_pos = idx.position();
|
||||||
let this_ptr = &mut self
|
let this_ptr =
|
||||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, idx_pos, false)?;
|
&mut self.get_indexed_mut(state, lib, target, idx_val, idx_pos, false)?;
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, expr, idx_values, is_idx, level, new_val,
|
state, lib, this_ptr, expr, idx_values, is_idx, level, new_val,
|
||||||
@ -900,17 +924,52 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
let this_ptr =
|
let mut idx_val2 = idx_val.clone();
|
||||||
&mut self.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, true)?;
|
|
||||||
|
|
||||||
this_ptr
|
match self.get_indexed_mut(state, lib, target, idx_val, pos, true) {
|
||||||
.set_value(new_val.unwrap())
|
// Indexed value is an owned value - the only possibility is an indexer
|
||||||
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?;
|
// Try to call an index setter
|
||||||
Ok((Default::default(), true))
|
Ok(this_ptr) if this_ptr.is_value() => {
|
||||||
|
let fn_name = FUNC_INDEXER_SET;
|
||||||
|
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val.unwrap()];
|
||||||
|
|
||||||
|
self.exec_fn_call(state, lib, fn_name, true, 0, args, is_ref, None, 0)
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
// If there is no index setter, no need to set it back because the indexer is read-only
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(s, _)
|
||||||
|
if s == FUNC_INDEXER_SET =>
|
||||||
|
{
|
||||||
|
Ok(Default::default())
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
// Indexed value is a reference - update directly
|
||||||
|
Ok(ref mut this_ptr) => {
|
||||||
|
this_ptr
|
||||||
|
.set_value(new_val.unwrap())
|
||||||
|
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?;
|
||||||
|
}
|
||||||
|
Err(err) => match *err {
|
||||||
|
// No index getter - try to call an index setter
|
||||||
|
EvalAltResult::ErrorIndexingType(_, _) => {
|
||||||
|
let fn_name = FUNC_INDEXER_SET;
|
||||||
|
let args =
|
||||||
|
&mut [target.as_mut(), &mut idx_val2, &mut new_val.unwrap()];
|
||||||
|
|
||||||
|
self.exec_fn_call(
|
||||||
|
state, lib, fn_name, true, 0, args, is_ref, None, 0,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
// Error
|
||||||
|
err => return Err(Box::new(err)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
// xxx[rhs]
|
// xxx[rhs]
|
||||||
_ => self
|
_ => self
|
||||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, false)
|
.get_indexed_mut(state, lib, target, idx_val, pos, false)
|
||||||
.map(|v| (v.clone_into_dynamic(), false)),
|
.map(|v| (v.clone_into_dynamic(), false)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -940,8 +999,7 @@ impl Engine {
|
|||||||
Expr::Property(x) if obj.is::<Map>() && new_val.is_some() => {
|
Expr::Property(x) if obj.is::<Map>() && new_val.is_some() => {
|
||||||
let ((prop, _, _), pos) = x.as_ref();
|
let ((prop, _, _), pos) = x.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
let mut val =
|
let mut val = self.get_indexed_mut(state, lib, target, index, *pos, true)?;
|
||||||
self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, true)?;
|
|
||||||
|
|
||||||
val.set_value(new_val.unwrap())
|
val.set_value(new_val.unwrap())
|
||||||
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?;
|
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?;
|
||||||
@ -952,7 +1010,7 @@ impl Engine {
|
|||||||
Expr::Property(x) if obj.is::<Map>() => {
|
Expr::Property(x) if obj.is::<Map>() => {
|
||||||
let ((prop, _, _), pos) = x.as_ref();
|
let ((prop, _, _), pos) = x.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
let val = self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, false)?;
|
let val = self.get_indexed_mut(state, lib, target, index, *pos, false)?;
|
||||||
|
|
||||||
Ok((val.clone_into_dynamic(), false))
|
Ok((val.clone_into_dynamic(), false))
|
||||||
}
|
}
|
||||||
@ -981,7 +1039,7 @@ impl Engine {
|
|||||||
let mut val = if let Expr::Property(p) = prop {
|
let mut val = if let Expr::Property(p) = prop {
|
||||||
let ((prop, _, _), _) = p.as_ref();
|
let ((prop, _, _), _) = p.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, false)?
|
self.get_indexed_mut(state, lib, target, index, *pos, false)?
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
@ -1160,14 +1218,16 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &Module,
|
||||||
val: &'a mut Dynamic,
|
target: &'a mut Target,
|
||||||
is_ref: bool,
|
|
||||||
mut idx: Dynamic,
|
mut idx: Dynamic,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
create: bool,
|
create: bool,
|
||||||
) -> Result<Target<'a>, Box<EvalAltResult>> {
|
) -> Result<Target<'a>, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
|
|
||||||
|
let is_ref = target.is_ref();
|
||||||
|
let val = target.as_mut();
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Dynamic(Union::Array(arr)) => {
|
Dynamic(Union::Array(arr)) => {
|
||||||
@ -1233,7 +1293,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let fn_name = FUNC_INDEXER;
|
let fn_name = FUNC_INDEXER_GET;
|
||||||
let type_name = self.map_type_name(val.type_name());
|
let type_name = self.map_type_name(val.type_name());
|
||||||
let args = &mut [val, &mut idx];
|
let args = &mut [val, &mut idx];
|
||||||
self.exec_fn_call(state, lib, fn_name, true, 0, args, is_ref, None, 0)
|
self.exec_fn_call(state, lib, fn_name, true, 0, args, is_ref, None, 0)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::any::{Dynamic, Variant};
|
use crate::any::{Dynamic, Variant};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{make_getter, make_setter, Engine, FUNC_INDEXER};
|
use crate::engine::{make_getter, make_setter, Engine, FUNC_INDEXER_GET, FUNC_INDEXER_SET};
|
||||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
|
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
FnAccess,
|
FnAccess,
|
||||||
@ -518,7 +518,7 @@ impl Module {
|
|||||||
self.set_fn_2_mut(make_setter(&name.into()), func)
|
self.set_fn_2_mut(make_setter(&name.into()), func)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust indexer function taking two parameters (the first one mutable) into the module,
|
/// Set a Rust index getter taking two parameters (the first one mutable) into the module,
|
||||||
/// returning a hash key.
|
/// returning a hash key.
|
||||||
///
|
///
|
||||||
/// If there is a similar existing setter Rust function, it is replaced.
|
/// If there is a similar existing setter Rust function, it is replaced.
|
||||||
@ -529,19 +529,19 @@ impl Module {
|
|||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
///
|
///
|
||||||
/// let mut module = Module::new();
|
/// let mut module = Module::new();
|
||||||
/// let hash = module.set_indexer_fn(|x: &mut i64, y: ImmutableString| {
|
/// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| {
|
||||||
/// Ok(*x + y.len() as i64)
|
/// Ok(*x + y.len() as i64)
|
||||||
/// });
|
/// });
|
||||||
/// assert!(module.get_fn(hash).is_some());
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn set_indexer_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
|
pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
|
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
|
||||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
self.set_fn_2_mut(FUNC_INDEXER, func)
|
self.set_fn_2_mut(FUNC_INDEXER_GET, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
||||||
@ -629,6 +629,44 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a Rust index setter taking three parameters (the first one mutable) into the module,
|
||||||
|
/// returning a hash key.
|
||||||
|
///
|
||||||
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Module, ImmutableString};
|
||||||
|
///
|
||||||
|
/// let mut module = Module::new();
|
||||||
|
/// let hash = module.set_indexer_set_fn(|x: &mut i64, y: ImmutableString, value: i64| {
|
||||||
|
/// *x = y.len() as i64 + value;
|
||||||
|
/// Ok(())
|
||||||
|
/// });
|
||||||
|
/// assert!(module.get_fn(hash).is_some());
|
||||||
|
/// ```
|
||||||
|
pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone>(
|
||||||
|
&mut self,
|
||||||
|
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, A) -> FuncReturn<()> + 'static,
|
||||||
|
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, A) -> FuncReturn<()> + Send + Sync + 'static,
|
||||||
|
) -> u64 {
|
||||||
|
let f = move |args: &mut FnCallArgs| {
|
||||||
|
let b = mem::take(args[1]).cast::<B>();
|
||||||
|
let c = mem::take(args[2]).cast::<A>();
|
||||||
|
let a = args[0].downcast_mut::<A>().unwrap();
|
||||||
|
|
||||||
|
func(a, b, c).map(Dynamic::from)
|
||||||
|
};
|
||||||
|
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<A>()];
|
||||||
|
self.set_fn(
|
||||||
|
FUNC_INDEXER_SET,
|
||||||
|
Public,
|
||||||
|
&args,
|
||||||
|
CallableFunction::from_method(Box::new(f)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a Rust function taking four parameters into the module, returning a hash key.
|
/// Set a Rust function taking four parameters into the module, returning a hash key.
|
||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
|
@ -42,18 +42,25 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.register_fn("add", |value: &mut INT| *value += 41);
|
engine.register_fn("add", |value: &mut INT| *value += 41);
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
engine.register_indexer(|value: &mut TestStruct, index: ImmutableString| {
|
|
||||||
value.array[index.len()]
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4);
|
{
|
||||||
|
engine.register_indexer_get_set(
|
||||||
|
|value: &mut TestStruct, index: ImmutableString| value.array[index.len()],
|
||||||
|
|value: &mut TestStruct, index: ImmutableString, new_val: INT| {
|
||||||
|
value.array[index.len()] = new_val
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r#"let a = new_ts(); a["abc"] = 42; a["abc"]"#)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user