Implement variable resolver.
This commit is contained in:
parent
9d93dac8e7
commit
fd5a932611
@ -16,10 +16,12 @@ Breaking changes
|
|||||||
* `Scope::iter_raw` returns an iterator with an additional field indicating whether the variable is constant or not.
|
* `Scope::iter_raw` returns an iterator with an additional field indicating whether the variable is constant or not.
|
||||||
* `rhai::ser` and `rhai::de` namespaces are merged into `rhai::serde`.
|
* `rhai::ser` and `rhai::de` namespaces are merged into `rhai::serde`.
|
||||||
* New reserved symbols: `++`, `--`, `..`, `...`.
|
* New reserved symbols: `++`, `--`, `..`, `...`.
|
||||||
|
* Callback signature for custom syntax implementation function is changed to allow for more flexibility.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* New `Engine::on_var` to register a _variable resolver_.
|
||||||
* `const` statements can now take any expression (or none at all) instead of only constant values.
|
* `const` statements can now take any expression (or none at all) instead of only constant values.
|
||||||
* `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded).
|
* `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded).
|
||||||
* Added `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined.
|
* Added `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined.
|
||||||
|
@ -124,11 +124,12 @@ The Rhai Scripting Language
|
|||||||
9. [Advanced Topics](advanced.md)
|
9. [Advanced Topics](advanced.md)
|
||||||
1. [Capture Scope for Function Call](language/fn-capture.md)
|
1. [Capture Scope for Function Call](language/fn-capture.md)
|
||||||
2. [Low-Level API](rust/register-raw.md)
|
2. [Low-Level API](rust/register-raw.md)
|
||||||
3. [Use as DSL](engine/dsl.md)
|
3. [Variable Resolver](engine/var.md)
|
||||||
|
4. [Use as DSL](engine/dsl.md)
|
||||||
1. [Disable Keywords and/or Operators](engine/disable.md)
|
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||||
2. [Custom Operators](engine/custom-op.md)
|
2. [Custom Operators](engine/custom-op.md)
|
||||||
3. [Extending with Custom Syntax](engine/custom-syntax.md)
|
3. [Extending with Custom Syntax](engine/custom-syntax.md)
|
||||||
4. [Multiple Instantiation](patterns/multiple.md)
|
5. [Multiple Instantiation](patterns/multiple.md)
|
||||||
10. [Appendix](appendix/index.md)
|
10. [Appendix](appendix/index.md)
|
||||||
1. [Keywords](appendix/keywords.md)
|
1. [Keywords](appendix/keywords.md)
|
||||||
2. [Operators and Symbols](appendix/operators.md)
|
2. [Operators and Symbols](appendix/operators.md)
|
||||||
|
@ -114,13 +114,17 @@ Any custom syntax must include an _implementation_ of it.
|
|||||||
|
|
||||||
The function signature of an implementation is:
|
The function signature of an implementation is:
|
||||||
|
|
||||||
> `Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>`
|
> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>`
|
||||||
|
|
||||||
where:
|
where:
|
||||||
|
|
||||||
* `engine: &Engine` - reference to the current [`Engine`].
|
|
||||||
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_; **do not touch**.
|
|
||||||
* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it.
|
* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it.
|
||||||
|
|
||||||
|
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_ (**do not touch**) which exposes the following fields:
|
||||||
|
* `context.engine(): &Engine` - reference to the current [`Engine`].
|
||||||
|
* `context.namespace(): &Module` - reference to the current _global namespace_ containing all script-defined functions.
|
||||||
|
* `context.call_level(): usize` - the current nesting level of function calls.
|
||||||
|
|
||||||
* `inputs: &[Expression]` - a list of input expression trees.
|
* `inputs: &[Expression]` - a list of input expression trees.
|
||||||
|
|
||||||
#### WARNING - Lark's Vomit
|
#### WARNING - Lark's Vomit
|
||||||
@ -143,11 +147,12 @@ To access a particular argument, use the following patterns:
|
|||||||
|
|
||||||
### Evaluate an Expression Tree
|
### Evaluate an Expression Tree
|
||||||
|
|
||||||
Use the `engine::eval_expression_tree` method to evaluate an expression tree.
|
Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expression tree
|
||||||
|
within the current evaluation context.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let expr = inputs.get(0).unwrap();
|
let expr = inputs.get(0).unwrap();
|
||||||
let result = engine.eval_expression_tree(context, scope, expr)?;
|
let result = context.eval_expression_tree(scope, expr)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Declare Variables
|
### Declare Variables
|
||||||
@ -157,15 +162,15 @@ New variables maybe declared (usually with a variable name that is passed in via
|
|||||||
It can simply be pushed into the [`Scope`].
|
It can simply be pushed into the [`Scope`].
|
||||||
|
|
||||||
However, beware that all new variables must be declared _prior_ to evaluating any expression tree.
|
However, beware that all new variables must be declared _prior_ to evaluating any expression tree.
|
||||||
In other words, any `Scope::push` calls must come _before_ any `Engine::eval_expression_tree` calls.
|
In other words, any `Scope::push` calls must come _before_ any `EvalContext::eval_expression_tree` calls.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
let expr = inputs.get(1).unwrap();
|
let expr = inputs.get(1).unwrap();
|
||||||
|
|
||||||
scope.push(var_name, 0 as INT); // do this BEFORE 'engine.eval_expression_tree'!
|
scope.push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'!
|
||||||
|
|
||||||
let result = engine.eval_expression_tree(context, scope, expr)?;
|
let result = context.eval_expression_tree(context, scope, expr)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -182,27 +187,29 @@ The syntax is passed simply as a slice of `&str`.
|
|||||||
```rust
|
```rust
|
||||||
// Custom syntax implementation
|
// Custom syntax implementation
|
||||||
fn implementation_func(
|
fn implementation_func(
|
||||||
engine: &Engine,
|
|
||||||
context: &mut EvalContext,
|
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
|
context: &mut EvalContext,
|
||||||
inputs: &[Expression]
|
inputs: &[Expression]
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
let stmt = inputs.get(1).unwrap();
|
let stmt = inputs.get(1).unwrap();
|
||||||
let condition = inputs.get(2).unwrap();
|
let condition = inputs.get(2).unwrap();
|
||||||
|
|
||||||
// Push one new variable into the 'scope' BEFORE 'eval_expression_tree'
|
// Push one new variable into the 'scope' BEFORE 'context.eval_expression_tree'
|
||||||
scope.push(var_name, 0 as INT);
|
scope.push(var_name, 0 as INT);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Evaluate the statement block
|
// Evaluate the statement block
|
||||||
engine.eval_expression_tree(context, scope, stmt)?;
|
context.eval_expression_tree(scope, stmt)?;
|
||||||
|
|
||||||
// Evaluate the condition expression
|
// Evaluate the condition expression
|
||||||
let stop = !engine.eval_expression_tree(context, scope, condition)?
|
let stop = !context.eval_expression_tree(scope, condition)?
|
||||||
.as_bool()
|
.as_bool().map_err(|err| Box::new(
|
||||||
.map_err(|_| EvalAltResult::ErrorBooleanArgMismatch(
|
EvalAltResult::ErrorMismatchDataType(
|
||||||
"do-while".into(), expr.position()
|
"bool".to_string(),
|
||||||
|
err.to_string(),
|
||||||
|
condition.position(),
|
||||||
|
)
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
if stop {
|
if stop {
|
||||||
|
75
doc/src/engine/var.md
Normal file
75
doc/src/engine/var.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
Variable Resolver
|
||||||
|
=================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
By default, Rhai looks up access to variables from the enclosing block scope,
|
||||||
|
working its way outwards until it reaches the top (global) level, then it
|
||||||
|
searches the [`Scope`] that is passed into the `Engine::eval` call.
|
||||||
|
|
||||||
|
There is a built-in facility for advanced users to _hook_ into the variable
|
||||||
|
resolution service and to override its default behavior.
|
||||||
|
|
||||||
|
To do so, provide a closure to the [`Engine`] via the [`Engine::on_var`] method:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Register a variable resolver.
|
||||||
|
engine.on_var(|name, index, engine, scope, lib| {
|
||||||
|
match name {
|
||||||
|
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
|
||||||
|
// Override a variable - make it not found even if it exists!
|
||||||
|
"DO_NOT_USE" => Err(Box::new(
|
||||||
|
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
|
||||||
|
)),
|
||||||
|
// Silently maps 'chameleon' into 'innocent'.
|
||||||
|
"chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
|
||||||
|
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
|
||||||
|
)),
|
||||||
|
// Return Ok(None) to continue with the normal variable resolution process.
|
||||||
|
_ => Ok(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Returned Values are Constants
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Variable values, if any returned, are treated as _constants_ by the script and cannot be assigned to.
|
||||||
|
This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain.
|
||||||
|
|
||||||
|
In order to change these variables, it is best to push them into a custom [`Scope`] instead of using
|
||||||
|
a variable resolver. Then these variables can be assigned to and their updated values read back after
|
||||||
|
the script is evaluated.
|
||||||
|
|
||||||
|
|
||||||
|
Function Signature
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The function signature passed to `Engine::on_var` takes the following form:
|
||||||
|
|
||||||
|
> `Fn(name: &str, index: Option<usize>, scope: &Scope, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
* `name: &str` - variable name.
|
||||||
|
|
||||||
|
* `index: Option<usize>` - an offset from the bottom of the current [`Scope`] that the variable is supposed to reside.
|
||||||
|
Offsets start from 1, with 1 meaning the last variable in the current [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.
|
||||||
|
Notice that, if there was an [`eval`] statement before the current statement, new variables may have been introduced and this index may be incorrect.
|
||||||
|
Therefore, this index is for reference only. It should not be relied upon.
|
||||||
|
|
||||||
|
If `index` is `None`, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed.
|
||||||
|
|
||||||
|
* `scope : &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position.
|
||||||
|
|
||||||
|
* `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields:
|
||||||
|
* `context.engine(): &Engine` - reference to the current [`Engine`].
|
||||||
|
* `context.namespace(): &Module` - reference to the current _global namespace_ containing all script-defined functions.
|
||||||
|
* `context.call_level(): usize` - the current nesting level of function calls.
|
||||||
|
|
||||||
|
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where `Ok(None)` indicates that the normal
|
||||||
|
variable resolution process should continue.
|
||||||
|
|
@ -106,6 +106,7 @@
|
|||||||
[module]: {{rootUrl}}/rust/modules/index.md
|
[module]: {{rootUrl}}/rust/modules/index.md
|
||||||
[modules]: {{rootUrl}}/rust/modules/index.md
|
[modules]: {{rootUrl}}/rust/modules/index.md
|
||||||
[module resolver]: {{rootUrl}}/rust/modules/resolvers.md
|
[module resolver]: {{rootUrl}}/rust/modules/resolvers.md
|
||||||
|
[variable resolver]: {{rootUrl}}/engine/var.md
|
||||||
[`export`]: {{rootUrl}}/language/modules/export.md
|
[`export`]: {{rootUrl}}/language/modules/export.md
|
||||||
[`import`]: {{rootUrl}}/language/modules/import.md
|
[`import`]: {{rootUrl}}/language/modules/import.md
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ Maximum Number of Operations
|
|||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
Limit How Long a Script Can Run
|
Limit How Long a Script Can Run
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ It is impossible to know when, or even whether, a script run will end
|
|||||||
(a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)).
|
(a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)).
|
||||||
|
|
||||||
When dealing with third-party untrusted scripts that may be malicious, to track evaluation progress and
|
When dealing with third-party untrusted scripts that may be malicious, to track evaluation progress and
|
||||||
to force-terminate a script prematurely (for any reason), provide a closure to the `Engine::on_progress` method:
|
to force-terminate a script prematurely (for any reason), provide a closure to the [`Engine`] via
|
||||||
|
the `Engine::on_progress` method:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
50
src/api.rs
50
src/api.rs
@ -1,7 +1,7 @@
|
|||||||
//! Module that defines the extern API of `Engine`.
|
//! Module that defines the extern API of `Engine`.
|
||||||
|
|
||||||
use crate::any::{Dynamic, Variant};
|
use crate::any::{Dynamic, Variant};
|
||||||
use crate::engine::{Engine, Imports, State};
|
use crate::engine::{Engine, EvalContext, Imports, State};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::fn_native::{IteratorFn, SendSync};
|
use crate::fn_native::{IteratorFn, SendSync};
|
||||||
use crate::module::{FuncReturn, Module};
|
use crate::module::{FuncReturn, Module};
|
||||||
@ -1686,6 +1686,54 @@ impl Engine {
|
|||||||
optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Provide a callback that will be invoked before each variable access.
|
||||||
|
///
|
||||||
|
/// ## Return Value of Callback
|
||||||
|
///
|
||||||
|
/// Return `Ok(None)` to continue with normal variable access.
|
||||||
|
/// Return `Ok(Some(Dynamic))` as the variable's value.
|
||||||
|
///
|
||||||
|
/// ## Errors in Callback
|
||||||
|
///
|
||||||
|
/// Return `Err(...)` if there is an error.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Register a variable resolver.
|
||||||
|
/// engine.on_var(|name, _, _, _| {
|
||||||
|
/// match name {
|
||||||
|
/// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
|
||||||
|
/// _ => Ok(None)
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// engine.eval::<i64>("MYSTIC_NUMBER")?;
|
||||||
|
///
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn on_var(
|
||||||
|
&mut self,
|
||||||
|
callback: impl Fn(
|
||||||
|
&str,
|
||||||
|
Option<usize>,
|
||||||
|
&Scope,
|
||||||
|
&EvalContext,
|
||||||
|
) -> Result<Option<Dynamic>, Box<EvalAltResult>>
|
||||||
|
+ SendSync
|
||||||
|
+ 'static,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.resolve_var = Some(Box::new(callback));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a callback for script evaluation progress.
|
/// Register a callback for script evaluation progress.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
346
src/engine.rs
346
src/engine.rs
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::any::{map_std_type_name, Dynamic, Union};
|
use crate::any::{map_std_type_name, Dynamic, Union};
|
||||||
use crate::fn_call::run_builtin_op_assignment;
|
use crate::fn_call::run_builtin_op_assignment;
|
||||||
use crate::fn_native::{Callback, FnPtr};
|
use crate::fn_native::{Callback, FnPtr, OnVarCallback};
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
||||||
@ -10,7 +10,7 @@ use crate::parser::{Expr, ReturnType, Stmt, INT};
|
|||||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::syntax::{CustomSyntax, EvalContext};
|
use crate::syntax::CustomSyntax;
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
use crate::{calc_fn_hash, StaticVec};
|
use crate::{calc_fn_hash, StaticVec};
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ pub enum Target<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl Target<'_> {
|
impl<'a> Target<'a> {
|
||||||
/// Is the `Target` a reference pointing to other data?
|
/// Is the `Target` a reference pointing to other data?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -207,7 +207,7 @@ impl Target<'_> {
|
|||||||
}
|
}
|
||||||
/// 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.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
pub fn take_or_clone(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
Self::Ref(r) => r.clone(), // Referenced value is cloned
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -218,6 +218,14 @@ impl Target<'_> {
|
|||||||
Self::StringChar(_, _, ch) => ch, // Character is taken
|
Self::StringChar(_, _, ch) => ch, // Character is taken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Take a `&mut Dynamic` reference from the `Target`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn take_ref(self) -> Option<&'a mut Dynamic> {
|
||||||
|
match self {
|
||||||
|
Self::Ref(r) => Some(r),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Get a mutable reference from the `Target`.
|
/// Get a mutable reference from the `Target`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn as_mut(&mut self) -> &mut Dynamic {
|
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||||
@ -374,6 +382,39 @@ pub struct Limits {
|
|||||||
pub max_map_size: usize,
|
pub max_map_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Context of a script evaluation process.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EvalContext<'e, 'a, 's, 'm, 't, 'd: 't> {
|
||||||
|
pub(crate) engine: &'e Engine,
|
||||||
|
pub(crate) mods: &'a mut Imports,
|
||||||
|
pub(crate) state: &'s mut State,
|
||||||
|
pub(crate) lib: &'m Module,
|
||||||
|
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,
|
||||||
|
pub(crate) level: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> {
|
||||||
|
/// The current `Engine`.
|
||||||
|
pub fn engine(&self) -> &'e Engine {
|
||||||
|
self.engine
|
||||||
|
}
|
||||||
|
/// _[INTERNALS]_ The current set of modules imported via `import` statements.
|
||||||
|
/// Available under the `internals` feature only.
|
||||||
|
#[cfg(feature = "internals")]
|
||||||
|
#[cfg(not(feature = "no_modules"))]
|
||||||
|
pub fn imports(&self) -> &'a Imports {
|
||||||
|
self.mods
|
||||||
|
}
|
||||||
|
/// The global namespace containing definition of all script-defined functions.
|
||||||
|
pub fn namespace(&self) -> &'m Module {
|
||||||
|
self.lib
|
||||||
|
}
|
||||||
|
/// The current nesting level of function calls.
|
||||||
|
pub fn call_level(&self) -> usize {
|
||||||
|
self.level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Rhai main scripting engine.
|
/// Rhai main scripting engine.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -412,6 +453,8 @@ pub struct Engine {
|
|||||||
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
|
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
|
||||||
/// Custom syntax.
|
/// Custom syntax.
|
||||||
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
|
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
|
||||||
|
/// Callback closure for resolving variable access.
|
||||||
|
pub(crate) resolve_var: Option<OnVarCallback>,
|
||||||
|
|
||||||
/// Callback closure for implementing the `print` command.
|
/// Callback closure for implementing the `print` command.
|
||||||
pub(crate) print: Callback<str, ()>,
|
pub(crate) print: Callback<str, ()>,
|
||||||
@ -522,88 +565,6 @@ pub fn search_imports_mut<'s>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope or within imports,
|
|
||||||
/// depending on whether the variable name is qualified.
|
|
||||||
pub fn search_namespace<'s, 'a>(
|
|
||||||
scope: &'s mut Scope,
|
|
||||||
mods: &'s mut Imports,
|
|
||||||
state: &mut State,
|
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
|
||||||
expr: &'a Expr,
|
|
||||||
) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
|
||||||
match expr {
|
|
||||||
Expr::Variable(v) => match v.as_ref() {
|
|
||||||
// Qualified variable
|
|
||||||
((name, pos), Some(modules), hash_var, _) => {
|
|
||||||
let module = search_imports_mut(mods, state, modules)?;
|
|
||||||
let target = module
|
|
||||||
.get_qualified_var_mut(*hash_var)
|
|
||||||
.map_err(|err| match *err {
|
|
||||||
EvalAltResult::ErrorVariableNotFound(_, _) => {
|
|
||||||
EvalAltResult::ErrorVariableNotFound(
|
|
||||||
format!("{}{}", modules, name),
|
|
||||||
*pos,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
_ => err.new_position(*pos),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Module variables are constant
|
|
||||||
Ok((target, name, ScopeEntryType::Constant, *pos))
|
|
||||||
}
|
|
||||||
// Normal variable access
|
|
||||||
_ => search_scope_only(scope, state, this_ptr, expr),
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search for a variable within the scope
|
|
||||||
pub fn search_scope_only<'s, 'a>(
|
|
||||||
scope: &'s mut Scope,
|
|
||||||
state: &mut State,
|
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
|
||||||
expr: &'a Expr,
|
|
||||||
) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
|
||||||
let ((name, pos), _, _, index) = match expr {
|
|
||||||
Expr::Variable(v) => v.as_ref(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the variable is `this`
|
|
||||||
if name == KEYWORD_THIS {
|
|
||||||
if let Some(val) = this_ptr {
|
|
||||||
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
|
|
||||||
} else {
|
|
||||||
return EvalAltResult::ErrorUnboundThis(*pos).into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it is directly indexed
|
|
||||||
let index = if state.always_search { None } else { *index };
|
|
||||||
|
|
||||||
let index = if let Some(index) = index {
|
|
||||||
scope.len() - index.get()
|
|
||||||
} else {
|
|
||||||
// Find the variable in the scope
|
|
||||||
scope
|
|
||||||
.get_index(name)
|
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
|
|
||||||
.0
|
|
||||||
};
|
|
||||||
|
|
||||||
let (val, typ) = scope.get_mut(index);
|
|
||||||
|
|
||||||
// Check for data race - probably not necessary because the only place it should conflict is in a method call
|
|
||||||
// when the object variable is also used as a parameter.
|
|
||||||
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
|
|
||||||
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
|
|
||||||
// }
|
|
||||||
|
|
||||||
Ok((val, name, typ, *pos))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Create a new `Engine`
|
/// Create a new `Engine`
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -628,6 +589,9 @@ impl Engine {
|
|||||||
custom_keywords: None,
|
custom_keywords: None,
|
||||||
custom_syntax: None,
|
custom_syntax: None,
|
||||||
|
|
||||||
|
// variable resolver
|
||||||
|
resolve_var: None,
|
||||||
|
|
||||||
// default print/debug implementations
|
// default print/debug implementations
|
||||||
print: Box::new(default_print),
|
print: Box::new(default_print),
|
||||||
debug: Box::new(default_print),
|
debug: Box::new(default_print),
|
||||||
@ -678,6 +642,8 @@ impl Engine {
|
|||||||
custom_keywords: None,
|
custom_keywords: None,
|
||||||
custom_syntax: None,
|
custom_syntax: None,
|
||||||
|
|
||||||
|
resolve_var: None,
|
||||||
|
|
||||||
print: Box::new(|_| {}),
|
print: Box::new(|_| {}),
|
||||||
debug: Box::new(|_| {}),
|
debug: Box::new(|_| {}),
|
||||||
progress: None,
|
progress: None,
|
||||||
@ -702,6 +668,111 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Search for a variable within the scope or within imports,
|
||||||
|
/// depending on whether the variable name is qualified.
|
||||||
|
pub(crate) fn search_namespace<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
scope: &'s mut Scope,
|
||||||
|
mods: &'s mut Imports,
|
||||||
|
state: &mut State,
|
||||||
|
lib: &Module,
|
||||||
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
|
expr: &'a Expr,
|
||||||
|
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
|
match expr {
|
||||||
|
Expr::Variable(v) => match v.as_ref() {
|
||||||
|
// Qualified variable
|
||||||
|
((name, pos), Some(modules), hash_var, _) => {
|
||||||
|
let module = search_imports_mut(mods, state, modules)?;
|
||||||
|
let target =
|
||||||
|
module
|
||||||
|
.get_qualified_var_mut(*hash_var)
|
||||||
|
.map_err(|err| match *err {
|
||||||
|
EvalAltResult::ErrorVariableNotFound(_, _) => {
|
||||||
|
EvalAltResult::ErrorVariableNotFound(
|
||||||
|
format!("{}{}", modules, name),
|
||||||
|
*pos,
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
_ => err.fill_position(*pos),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Module variables are constant
|
||||||
|
Ok((target.into(), name, ScopeEntryType::Constant, *pos))
|
||||||
|
}
|
||||||
|
// Normal variable access
|
||||||
|
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for a variable within the scope
|
||||||
|
pub(crate) fn search_scope_only<'s, 'a>(
|
||||||
|
&self,
|
||||||
|
scope: &'s mut Scope,
|
||||||
|
mods: &mut Imports,
|
||||||
|
state: &mut State,
|
||||||
|
lib: &Module,
|
||||||
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
|
expr: &'a Expr,
|
||||||
|
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
|
let ((name, pos), _, _, index) = match expr {
|
||||||
|
Expr::Variable(v) => v.as_ref(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the variable is `this`
|
||||||
|
if name == KEYWORD_THIS {
|
||||||
|
if let Some(val) = this_ptr {
|
||||||
|
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
|
||||||
|
} else {
|
||||||
|
return EvalAltResult::ErrorUnboundThis(*pos).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it is directly indexed
|
||||||
|
let index = if state.always_search { None } else { *index };
|
||||||
|
|
||||||
|
// Check the variable resolver, if any
|
||||||
|
if let Some(ref resolve_var) = self.resolve_var {
|
||||||
|
let context = EvalContext {
|
||||||
|
engine: self,
|
||||||
|
mods,
|
||||||
|
state,
|
||||||
|
lib,
|
||||||
|
this_ptr,
|
||||||
|
level: 0,
|
||||||
|
};
|
||||||
|
if let Some(result) = resolve_var(name, index.map(|v| v.get()), scope, &context)
|
||||||
|
.map_err(|err| err.fill_position(*pos))?
|
||||||
|
{
|
||||||
|
return Ok((result.into(), name, ScopeEntryType::Constant, *pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = if let Some(index) = index {
|
||||||
|
scope.len() - index.get()
|
||||||
|
} else {
|
||||||
|
// Find the variable in the scope
|
||||||
|
scope
|
||||||
|
.get_index(name)
|
||||||
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
|
||||||
|
.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let (val, typ) = scope.get_mut(index);
|
||||||
|
|
||||||
|
// Check for data race - probably not necessary because the only place it should conflict is in a method call
|
||||||
|
// when the object variable is also used as a parameter.
|
||||||
|
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
|
||||||
|
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok((val.into(), name, typ, *pos))
|
||||||
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot/index chain.
|
/// Chain-evaluate a dot/index chain.
|
||||||
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
@ -750,7 +821,7 @@ impl Engine {
|
|||||||
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
@ -800,7 +871,7 @@ impl Engine {
|
|||||||
// xxx[rhs]
|
// xxx[rhs]
|
||||||
_ => self
|
_ => self
|
||||||
.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
|
.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
|
||||||
.map(|v| (v.clone_into_dynamic(), false)),
|
.map(|v| (v.take_or_clone(), false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -815,7 +886,7 @@ impl Engine {
|
|||||||
state, lib, name, *hash, target, idx_val, &def_val, *native, false,
|
state, lib, name, *hash, target, idx_val, &def_val, *native, false,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx.module::fn_name(...) - syntax error
|
// xxx.module::fn_name(...) - syntax error
|
||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
@ -837,7 +908,7 @@ impl Engine {
|
|||||||
state, lib, target, index, *pos, false, false, level,
|
state, lib, target, index, *pos, false, false, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok((val.clone_into_dynamic(), false))
|
Ok((val.take_or_clone(), false))
|
||||||
}
|
}
|
||||||
// xxx.id = ???
|
// xxx.id = ???
|
||||||
Expr::Property(x) if new_val.is_some() => {
|
Expr::Property(x) if new_val.is_some() => {
|
||||||
@ -849,7 +920,7 @@ impl Engine {
|
|||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| (v, true))
|
.map(|(v, _)| (v, true))
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Property(x) => {
|
Expr::Property(x) => {
|
||||||
@ -860,7 +931,7 @@ impl Engine {
|
|||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| (v, false))
|
.map(|(v, _)| (v, false))
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
|
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
|
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
|
||||||
@ -883,7 +954,7 @@ impl Engine {
|
|||||||
state, lib, name, *hash, target, idx_val, &def_val,
|
state, lib, name, *hash, target, idx_val, &def_val,
|
||||||
*native, false, level,
|
*native, false, level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
.map_err(|err| err.fill_position(*pos))?;
|
||||||
val.into()
|
val.into()
|
||||||
}
|
}
|
||||||
// {xxx:map}.module::fn_name(...) - syntax error
|
// {xxx:map}.module::fn_name(...) - syntax error
|
||||||
@ -896,7 +967,7 @@ impl Engine {
|
|||||||
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
|
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
@ -913,7 +984,7 @@ impl Engine {
|
|||||||
state, lib, getter, 0, args, is_ref, true, false, None,
|
state, lib, getter, 0, args, is_ref, true, false, None,
|
||||||
&None, level,
|
&None, level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
.map_err(|err| err.fill_position(*pos))?;
|
||||||
|
|
||||||
let val = &mut val;
|
let val = &mut val;
|
||||||
|
|
||||||
@ -929,7 +1000,7 @@ impl Engine {
|
|||||||
level,
|
level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
.map_err(|err| err.fill_position(*pos))?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
if updated || may_be_changed {
|
if updated || may_be_changed {
|
||||||
@ -945,7 +1016,7 @@ impl Engine {
|
|||||||
EvalAltResult::ErrorDotExpr(_, _) => {
|
EvalAltResult::ErrorDotExpr(_, _) => {
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
_ => Err(err.new_position(*pos)),
|
_ => Err(err.fill_position(*pos)),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@ -961,7 +1032,7 @@ impl Engine {
|
|||||||
state, lib, name, *hash, target, idx_val, &def_val,
|
state, lib, name, *hash, target, idx_val, &def_val,
|
||||||
*native, false, level,
|
*native, false, level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))?;
|
.map_err(|err| err.fill_position(*pos))?;
|
||||||
let val = &mut val;
|
let val = &mut val;
|
||||||
let target = &mut val.into();
|
let target = &mut val.into();
|
||||||
|
|
||||||
@ -969,7 +1040,7 @@ impl Engine {
|
|||||||
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
||||||
level, new_val,
|
level, new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
// xxx.module::fn_name(...) - syntax error
|
// xxx.module::fn_name(...) - syntax error
|
||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
@ -1017,10 +1088,10 @@ impl Engine {
|
|||||||
let (var_name, var_pos) = &x.0;
|
let (var_name, var_pos) = &x.0;
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(*var_pos))?;
|
.map_err(|err| err.fill_position(*var_pos))?;
|
||||||
|
|
||||||
let (target, _, typ, pos) =
|
let (target, _, typ, pos) =
|
||||||
search_namespace(scope, mods, state, this_ptr, dot_lhs)?;
|
self.search_namespace(scope, mods, state, lib, this_ptr, dot_lhs)?;
|
||||||
|
|
||||||
// Constants cannot be modified
|
// Constants cannot be modified
|
||||||
match typ {
|
match typ {
|
||||||
@ -1036,7 +1107,7 @@ impl Engine {
|
|||||||
state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
|
state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
.map_err(|err| err.new_position(*op_pos))
|
.map_err(|err| err.fill_position(*op_pos))
|
||||||
}
|
}
|
||||||
// {expr}.??? = ??? or {expr}[???] = ???
|
// {expr}.??? = ??? or {expr}[???] = ???
|
||||||
expr if new_val.is_some() => {
|
expr if new_val.is_some() => {
|
||||||
@ -1050,7 +1121,7 @@ impl Engine {
|
|||||||
state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
|
state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
.map_err(|err| err.new_position(*op_pos))
|
.map_err(|err| err.fill_position(*op_pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1075,7 +1146,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(expr.position()))?;
|
.map_err(|err| err.fill_position(expr.position()))?;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::FnCall(x) if x.1.is_none() => {
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
@ -1248,7 +1319,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(rhs.position()))?;
|
.map_err(|err| err.fill_position(rhs.position()))?;
|
||||||
|
|
||||||
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
|
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
|
||||||
let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?;
|
let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?;
|
||||||
@ -1270,7 +1341,7 @@ impl Engine {
|
|||||||
|
|
||||||
if self
|
if self
|
||||||
.call_native_fn(state, lib, op, hash, args, false, false, &def_value)
|
.call_native_fn(state, lib, op, hash, args, false, false, &def_value)
|
||||||
.map_err(|err| err.new_position(rhs.position()))?
|
.map_err(|err| err.fill_position(rhs.position()))?
|
||||||
.0
|
.0
|
||||||
.as_bool()
|
.as_bool()
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
@ -1310,7 +1381,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(expr.position()))?;
|
.map_err(|err| err.fill_position(expr.position()))?;
|
||||||
|
|
||||||
let result = match expr {
|
let result = match expr {
|
||||||
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x.as_ref(), level),
|
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x.as_ref(), level),
|
||||||
@ -1329,8 +1400,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Variable(_) => {
|
Expr::Variable(_) => {
|
||||||
let (val, _, _, _) = search_namespace(scope, mods, state, this_ptr, expr)?;
|
let (val, _, _, _) =
|
||||||
Ok(val.clone())
|
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
|
||||||
|
Ok(val.take_or_clone())
|
||||||
}
|
}
|
||||||
Expr::Property(_) => unreachable!(),
|
Expr::Property(_) => unreachable!(),
|
||||||
|
|
||||||
@ -1340,12 +1412,18 @@ impl Engine {
|
|||||||
// var op= rhs
|
// var op= rhs
|
||||||
Expr::Assignment(x) if matches!(x.0, Expr::Variable(_)) => {
|
Expr::Assignment(x) if matches!(x.0, Expr::Variable(_)) => {
|
||||||
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
|
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
|
||||||
let mut rhs_val =
|
let mut rhs_val = self
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
|
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
||||||
let (lhs_ptr, name, typ, pos) =
|
.flatten();
|
||||||
search_namespace(scope, mods, state, this_ptr, lhs_expr)?;
|
let (mut lhs_ptr, name, typ, pos) =
|
||||||
|
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
|
||||||
|
|
||||||
|
if !lhs_ptr.is_ref() {
|
||||||
|
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
|
||||||
|
}
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(pos))?;
|
.map_err(|err| err.fill_position(pos))?;
|
||||||
|
|
||||||
match typ {
|
match typ {
|
||||||
// Assignment to constant variable
|
// Assignment to constant variable
|
||||||
@ -1354,11 +1432,10 @@ impl Engine {
|
|||||||
)),
|
)),
|
||||||
// Normal assignment
|
// Normal assignment
|
||||||
ScopeEntryType::Normal if op.is_empty() => {
|
ScopeEntryType::Normal if op.is_empty() => {
|
||||||
let value = rhs_val.flatten();
|
|
||||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
|
||||||
} else {
|
} else {
|
||||||
*lhs_ptr = value;
|
*lhs_ptr.as_mut() = rhs_val;
|
||||||
}
|
}
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
@ -1369,7 +1446,8 @@ impl Engine {
|
|||||||
// 3) Map to `var = var op rhs`
|
// 3) Map to `var = var op rhs`
|
||||||
|
|
||||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||||
let arg_types = once(lhs_ptr.type_id()).chain(once(rhs_val.type_id()));
|
let arg_types =
|
||||||
|
once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
|
||||||
let hash_fn = calc_fn_hash(empty(), op, 2, arg_types);
|
let hash_fn = calc_fn_hash(empty(), op, 2, arg_types);
|
||||||
|
|
||||||
match self
|
match self
|
||||||
@ -1383,10 +1461,10 @@ impl Engine {
|
|||||||
let lhs_ptr_inner;
|
let lhs_ptr_inner;
|
||||||
|
|
||||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||||
lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
|
lock_guard = lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap();
|
||||||
lhs_ptr_inner = lock_guard.deref_mut();
|
lhs_ptr_inner = lock_guard.deref_mut();
|
||||||
} else {
|
} else {
|
||||||
lhs_ptr_inner = lhs_ptr;
|
lhs_ptr_inner = lhs_ptr.as_mut();
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = &mut [lhs_ptr_inner, &mut rhs_val];
|
let args = &mut [lhs_ptr_inner, &mut rhs_val];
|
||||||
@ -1399,13 +1477,14 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Built-in op-assignment function
|
// Built-in op-assignment function
|
||||||
_ if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_some() => {}
|
_ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)?
|
||||||
|
.is_some() => {}
|
||||||
// Not built-in: expand to `var = var op rhs`
|
// Not built-in: expand to `var = var op rhs`
|
||||||
_ => {
|
_ => {
|
||||||
let op = &op[..op.len() - 1]; // extract operator without =
|
let op = &op[..op.len() - 1]; // extract operator without =
|
||||||
|
|
||||||
// Clone the LHS value
|
// Clone the LHS value
|
||||||
let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val];
|
let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val];
|
||||||
|
|
||||||
// Run function
|
// Run function
|
||||||
let (value, _) = self
|
let (value, _) = self
|
||||||
@ -1413,14 +1492,14 @@ impl Engine {
|
|||||||
state, lib, op, 0, args, false, false, false, None, &None,
|
state, lib, op, 0, args, false, false, false, None, &None,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*op_pos))?;
|
.map_err(|err| err.fill_position(*op_pos))?;
|
||||||
|
|
||||||
let value = value.flatten();
|
let value = value.flatten();
|
||||||
|
|
||||||
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
|
||||||
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
|
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = value;
|
||||||
} else {
|
} else {
|
||||||
*lhs_ptr = value;
|
*lhs_ptr.as_mut() = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1451,7 +1530,7 @@ impl Engine {
|
|||||||
state, lib, op, 0, args, false, false, false, None, &None, level,
|
state, lib, op, 0, args, false, false, false, None, &None, level,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
.map_err(|err| err.new_position(*op_pos))?;
|
.map_err(|err| err.fill_position(*op_pos))?;
|
||||||
|
|
||||||
Some((result, rhs_expr.position()))
|
Some((result, rhs_expr.position()))
|
||||||
};
|
};
|
||||||
@ -1520,7 +1599,7 @@ impl Engine {
|
|||||||
scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
|
scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
|
||||||
false, *capture, level,
|
false, *capture, level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module-qualified function call
|
// Module-qualified function call
|
||||||
@ -1530,7 +1609,7 @@ impl Engine {
|
|||||||
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
|
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
|
||||||
*capture, level,
|
*capture, level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.new_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level),
|
Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level),
|
||||||
@ -1571,20 +1650,21 @@ impl Engine {
|
|||||||
let func = (x.0).1.as_ref();
|
let func = (x.0).1.as_ref();
|
||||||
let ep = (x.0).0.iter().map(|e| e.into()).collect::<StaticVec<_>>();
|
let ep = (x.0).0.iter().map(|e| e.into()).collect::<StaticVec<_>>();
|
||||||
let mut context = EvalContext {
|
let mut context = EvalContext {
|
||||||
|
engine: self,
|
||||||
mods,
|
mods,
|
||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
level,
|
level,
|
||||||
};
|
};
|
||||||
func(self, &mut context, scope, ep.as_ref())
|
func(scope, &mut context, ep.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.check_data_size(result)
|
self.check_data_size(result)
|
||||||
.map_err(|err| err.new_position(expr.position()))
|
.map_err(|err| err.fill_position(expr.position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statement
|
/// Evaluate a statement
|
||||||
@ -1605,7 +1685,7 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(stmt.position()))?;
|
.map_err(|err| err.fill_position(stmt.position()))?;
|
||||||
|
|
||||||
let result = match stmt {
|
let result = match stmt {
|
||||||
// No-op
|
// No-op
|
||||||
@ -1720,7 +1800,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(stmt.position()))?;
|
.map_err(|err| err.fill_position(stmt.position()))?;
|
||||||
|
|
||||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) {
|
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
@ -1872,7 +1952,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.check_data_size(result)
|
self.check_data_size(result)
|
||||||
.map_err(|err| err.new_position(stmt.position()))
|
.map_err(|err| err.fill_position(stmt.position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
/// Check a result to ensure that the data size is within allowable limit.
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG,
|
search_imports, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR,
|
||||||
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN,
|
KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR,
|
||||||
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
use crate::error::ParseErrorType;
|
use crate::error::ParseErrorType;
|
||||||
use crate::fn_native::{FnCallArgs, FnPtr};
|
use crate::fn_native::{FnCallArgs, FnPtr};
|
||||||
@ -861,7 +861,7 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
.and_then(|s| FnPtr::try_from(s))
|
.and_then(|s| FnPtr::try_from(s))
|
||||||
.map(Into::<Dynamic>::into)
|
.map(Into::<Dynamic>::into)
|
||||||
.map_err(|err| err.new_position(expr.position()));
|
.map_err(|err| err.fill_position(expr.position()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1000,7 +1000,7 @@ impl Engine {
|
|||||||
})?;
|
})?;
|
||||||
let result = if !script.is_empty() {
|
let result = if !script.is_empty() {
|
||||||
self.eval_script_expr(scope, mods, state, lib, script, level + 1)
|
self.eval_script_expr(scope, mods, state, lib, script, level + 1)
|
||||||
.map_err(|err| err.new_position(expr.position()))
|
.map_err(|err| err.fill_position(expr.position()))
|
||||||
} else {
|
} else {
|
||||||
Ok(().into())
|
Ok(().into())
|
||||||
};
|
};
|
||||||
@ -1040,18 +1040,21 @@ impl Engine {
|
|||||||
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
|
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
let (target, _, _, pos) = search_namespace(scope, mods, state, this_ptr, lhs)?;
|
let (target, _, _, pos) =
|
||||||
|
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(pos))?;
|
.map_err(|err| err.fill_position(pos))?;
|
||||||
|
|
||||||
args = if target.is_shared() {
|
args = if target.is_shared() || target.is_value() {
|
||||||
arg_values.insert(0, target.flatten_clone());
|
arg_values.insert(0, target.take_or_clone().flatten());
|
||||||
arg_values.iter_mut().collect()
|
arg_values.iter_mut().collect()
|
||||||
} else {
|
} else {
|
||||||
// Turn it into a method call only if the object is not shared
|
// Turn it into a method call only if the object is not shared and not a simple value
|
||||||
is_ref = true;
|
is_ref = true;
|
||||||
once(target).chain(arg_values.iter_mut()).collect()
|
once(target.take_ref().unwrap())
|
||||||
|
.chain(arg_values.iter_mut())
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// func(..., ...)
|
// func(..., ...)
|
||||||
@ -1121,16 +1124,23 @@ impl Engine {
|
|||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
// Get target reference to first argument
|
// Get target reference to first argument
|
||||||
|
let var_expr = args_expr.get(0).unwrap();
|
||||||
let (target, _, _, pos) =
|
let (target, _, _, pos) =
|
||||||
search_scope_only(scope, state, this_ptr, args_expr.get(0).unwrap())?;
|
self.search_scope_only(scope, mods, state, lib, this_ptr, var_expr)?;
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state)
|
||||||
.map_err(|err| err.new_position(pos))?;
|
.map_err(|err| err.fill_position(pos))?;
|
||||||
|
|
||||||
|
if target.is_shared() || target.is_value() {
|
||||||
|
arg_values[0] = target.take_or_clone().flatten();
|
||||||
|
args = arg_values.iter_mut().collect();
|
||||||
|
} else {
|
||||||
let (first, rest) = arg_values.split_first_mut().unwrap();
|
let (first, rest) = arg_values.split_first_mut().unwrap();
|
||||||
first_arg_value = Some(first);
|
first_arg_value = Some(first);
|
||||||
|
args = once(target.take_ref().unwrap())
|
||||||
args = once(target).chain(rest.iter_mut()).collect();
|
.chain(rest.iter_mut())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// func(..., ...) or func(mod::x, ...)
|
// func(..., ...) or func(mod::x, ...)
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
//! Module defining interfaces to native-Rust functions.
|
//! Module defining interfaces to native-Rust functions.
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::Engine;
|
use crate::engine::{Engine, EvalContext};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::parser::{FnAccess, ScriptFnDef};
|
use crate::parser::{FnAccess, ScriptFnDef};
|
||||||
use crate::plugin::PluginFunction;
|
use crate::plugin::PluginFunction;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
|
use crate::scope::Scope;
|
||||||
use crate::token::{is_valid_identifier, Position};
|
use crate::token::{is_valid_identifier, Position};
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::ImmutableString;
|
||||||
|
|
||||||
@ -220,6 +221,21 @@ pub type Callback<T, R> = Box<dyn Fn(&T) -> R + 'static>;
|
|||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + Send + Sync + 'static>;
|
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
/// A standard callback function.
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub type OnVarCallback = Box<
|
||||||
|
dyn Fn(&str, Option<usize>, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
|
||||||
|
+ 'static,
|
||||||
|
>;
|
||||||
|
/// A standard callback function.
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub type OnVarCallback = Box<
|
||||||
|
dyn Fn(&str, Option<usize>, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
>;
|
||||||
|
|
||||||
/// A type encapsulating a function callable by Rhai.
|
/// A type encapsulating a function callable by Rhai.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum CallableFunction {
|
pub enum CallableFunction {
|
||||||
|
@ -83,7 +83,7 @@ mod r#unsafe;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use any::Dynamic;
|
pub use any::Dynamic;
|
||||||
pub use engine::Engine;
|
pub use engine::{Engine, EvalContext};
|
||||||
pub use error::{ParseError, ParseErrorType};
|
pub use error::{ParseError, ParseErrorType};
|
||||||
pub use fn_native::{FnPtr, IteratorFn};
|
pub use fn_native::{FnPtr, IteratorFn};
|
||||||
pub use fn_register::{RegisterFn, RegisterResultFn};
|
pub use fn_register::{RegisterFn, RegisterResultFn};
|
||||||
@ -91,7 +91,7 @@ pub use module::Module;
|
|||||||
pub use parser::{ImmutableString, AST, INT};
|
pub use parser::{ImmutableString, AST, INT};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
pub use syntax::{EvalContext, Expression};
|
pub use syntax::Expression;
|
||||||
pub use token::Position;
|
pub use token::Position;
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
|
@ -1430,11 +1430,12 @@ impl Module {
|
|||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
// Wrap the error in a module-error
|
// Wrap the error in a module-error
|
||||||
Box::new(EvalAltResult::ErrorInModule(
|
EvalAltResult::ErrorInModule(
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
err,
|
err,
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
)
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -339,7 +339,7 @@ impl EvalAltResult {
|
|||||||
/// Consume the current `EvalAltResult` and return a new one with the specified `Position`
|
/// Consume the current `EvalAltResult` and return a new one with the specified `Position`
|
||||||
/// if the current position is `Position::None`.
|
/// if the current position is `Position::None`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
|
pub(crate) fn fill_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
|
||||||
if self.position().is_none() {
|
if self.position().is_none() {
|
||||||
self.set_position(new_position);
|
self.set_position(new_position);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
//! Module implementing custom syntax for `Engine`.
|
//! Module implementing custom syntax for `Engine`.
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
use crate::engine::{Engine, EvalContext, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
||||||
use crate::error::{LexError, ParseError};
|
use crate::error::{LexError, ParseError};
|
||||||
use crate::fn_native::{SendSync, Shared};
|
use crate::fn_native::{SendSync, Shared};
|
||||||
use crate::module::Module;
|
|
||||||
use crate::parser::Expr;
|
use crate::parser::Expr;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
@ -19,15 +18,11 @@ use crate::stdlib::{
|
|||||||
|
|
||||||
/// A general expression evaluation trait object.
|
/// A general expression evaluation trait object.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type FnCustomSyntaxEval = dyn Fn(
|
pub type FnCustomSyntaxEval =
|
||||||
&Engine,
|
dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||||
&mut EvalContext,
|
|
||||||
&mut Scope,
|
|
||||||
&[Expression],
|
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>>;
|
|
||||||
/// A general expression evaluation trait object.
|
/// A general expression evaluation trait object.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
pub type FnCustomSyntaxEval = dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync;
|
+ Sync;
|
||||||
|
|
||||||
@ -63,6 +58,30 @@ impl Expression<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EvalContext<'_, '_, '_, '_, '_, '_> {
|
||||||
|
/// Evaluate an expression tree.
|
||||||
|
///
|
||||||
|
/// ## WARNING - Low Level API
|
||||||
|
///
|
||||||
|
/// This function is very low level. It evaluates an expression from an AST.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn eval_expression_tree(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
expr: &Expression,
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
self.engine.eval_expr(
|
||||||
|
scope,
|
||||||
|
self.mods,
|
||||||
|
self.state,
|
||||||
|
self.lib,
|
||||||
|
self.this_ptr,
|
||||||
|
expr.expr(),
|
||||||
|
self.level,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CustomSyntax {
|
pub struct CustomSyntax {
|
||||||
pub segments: StaticVec<String>,
|
pub segments: StaticVec<String>,
|
||||||
@ -77,27 +96,17 @@ impl fmt::Debug for CustomSyntax {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Context of a script evaluation process.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EvalContext<'a, 's, 'm, 't, 'd: 't> {
|
|
||||||
pub(crate) mods: &'a mut Imports,
|
|
||||||
pub(crate) state: &'s mut State,
|
|
||||||
pub(crate) lib: &'m Module,
|
|
||||||
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,
|
|
||||||
pub(crate) level: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
/// Register a custom syntax with the `Engine`.
|
||||||
|
///
|
||||||
|
/// * `keywords` holds a slice of strings that define the custom syntax.
|
||||||
|
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
|
||||||
|
/// * `func` is the implementation function.
|
||||||
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
|
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keywords: &[S],
|
keywords: &[S],
|
||||||
scope_delta: isize,
|
new_vars: isize,
|
||||||
func: impl Fn(
|
func: impl Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
&Engine,
|
|
||||||
&mut EvalContext,
|
|
||||||
&mut Scope,
|
|
||||||
&[Expression],
|
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>>
|
|
||||||
+ SendSync
|
+ SendSync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
) -> Result<&mut Self, ParseError> {
|
) -> Result<&mut Self, ParseError> {
|
||||||
@ -176,7 +185,7 @@ impl Engine {
|
|||||||
let syntax = CustomSyntax {
|
let syntax = CustomSyntax {
|
||||||
segments,
|
segments,
|
||||||
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
||||||
scope_delta,
|
scope_delta: new_vars,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.custom_syntax.is_none() {
|
if self.custom_syntax.is_none() {
|
||||||
@ -190,27 +199,4 @@ impl Engine {
|
|||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an expression tree.
|
|
||||||
///
|
|
||||||
/// ## WARNING - Low Level API
|
|
||||||
///
|
|
||||||
/// This function is very low level. It evaluates an expression from an AST.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn eval_expression_tree(
|
|
||||||
&self,
|
|
||||||
context: &mut EvalContext,
|
|
||||||
scope: &mut Scope,
|
|
||||||
expr: &Expression,
|
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
|
||||||
self.eval_expr(
|
|
||||||
scope,
|
|
||||||
context.mods,
|
|
||||||
context.state,
|
|
||||||
context.lib,
|
|
||||||
context.this_ptr,
|
|
||||||
expr.expr(),
|
|
||||||
context.level,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,28 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
|
"do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
|
||||||
],
|
],
|
||||||
1,
|
1,
|
||||||
|engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]| {
|
|scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression]| {
|
||||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
let stmt = inputs.get(1).unwrap();
|
let stmt = inputs.get(1).unwrap();
|
||||||
let expr = inputs.get(2).unwrap();
|
let condition = inputs.get(2).unwrap();
|
||||||
|
|
||||||
scope.push(var_name, 0 as INT);
|
scope.push(var_name, 0 as INT);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
engine.eval_expression_tree(context, scope, stmt)?;
|
context.eval_expression_tree(scope, stmt)?;
|
||||||
|
|
||||||
if !engine
|
let stop = !context
|
||||||
.eval_expression_tree(context, scope, expr)?
|
.eval_expression_tree(scope, condition)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
.map_err(|err| engine.make_type_mismatch_err::<bool>(err, expr.position()))?
|
.map_err(|err| {
|
||||||
{
|
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||||
|
"bool".to_string(),
|
||||||
|
err.to_string(),
|
||||||
|
condition.position(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if stop {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +68,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
// The first symbol must be an identifier
|
// The first symbol must be an identifier
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*engine
|
*engine
|
||||||
.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into()))
|
.register_custom_syntax(&["!"], 0, |_, _, _| Ok(().into()))
|
||||||
.expect_err("should error")
|
.expect_err("should error")
|
||||||
.0,
|
.0,
|
||||||
ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
|
ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
use rhai::{Engine, EvalAltResult, Position, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
|
fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -52,3 +52,41 @@ fn test_scope_eval() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("innocent", 1 as INT);
|
||||||
|
scope.push("chameleon", 123 as INT);
|
||||||
|
scope.push("DO_NOT_USE", 999 as INT);
|
||||||
|
|
||||||
|
engine.on_var(|name, _, scope, _| {
|
||||||
|
match name {
|
||||||
|
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
|
||||||
|
// Override a variable - make it not found even if it exists!
|
||||||
|
"DO_NOT_USE" => {
|
||||||
|
Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into())
|
||||||
|
}
|
||||||
|
// Silently maps 'chameleon' into 'innocent'.
|
||||||
|
"chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| {
|
||||||
|
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into()
|
||||||
|
}),
|
||||||
|
// Return Ok(None) to continue with the normal variable resolution process.
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(&mut scope, "MYSTIC_NUMBER")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "chameleon")?, 1);
|
||||||
|
assert!(
|
||||||
|
matches!(*engine.eval_with_scope::<INT>(&mut scope, "DO_NOT_USE").expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorVariableNotFound(n, _) if n == "DO_NOT_USE")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user