Merge pull request #682 from schungx/master

New optimizations and features.
This commit is contained in:
Stephen Chung 2022-12-21 16:42:36 +08:00 committed by GitHub
commit b87243bfe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1689 additions and 37694 deletions

View File

@ -178,8 +178,8 @@ jobs:
strategy:
matrix:
include:
- {toolchain: nightly, os: ubuntu-latest, experimental: false, flags: "--features metadata"}
- {toolchain: nightly, os: windows-latest, experimental: false, flags: "--features metadata"}
- {toolchain: stable, os: ubuntu-latest, experimental: false, flags: "--features metadata"}
- {toolchain: stable, os: windows-latest, experimental: false, flags: "--features metadata"}
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@ -22,12 +22,13 @@ Deprecated API's
* `Module::with_capacity` is deprecated.
* The internal method `Engine::eval_statements_raw` is deprecated.
* Array overloaded methods that take function names (as string) are deprecated in favor of using the `Fn("...")` call.
Speed improvements
------------------
* The functions registration mechanism is revamped to take advantage of constant generics, among others.
* This yields a 10-20% speed improvements on certain real-life, function-call-heavy workloads.
* The function registration mechanism is revamped to take advantage of constant generics, among others, to omit checking code where possible. This yields a 10-20% speed improvements on certain real-life, function-call-heavy workloads.
* Functions taking function pointers as parameters, usually called with closures, now run faster because a link to the anonymous function (generated by the closure) is stored together with the function pointer itself. This allows short-circuiting the function lookup step.
Net features
------------
@ -36,7 +37,7 @@ Net features
* A function pointer created via a closure definition now links to the particular anonymous function itself.
* This avoids a potentially expensive function lookup when the function pointer is called, speeding up closures.
* It does _not_, however, allow the function pointer to be `export`ed as a constant from a script module because the closure may cross-call other functions defined in the module and the function pointer won't keep the fully encapsulated environment.
* Closures now also encapsulate their defining environment, so function pointers can now be freely `export`ed from modules!
### `!in`
@ -48,6 +49,12 @@ Net features
* The options are for future-proofing the API.
* In this version, it gains the ability to set the value of the _custom state_ (accessible via `NativeCallContext::tag`) for a function evaluation, overriding `Engine::set_default_tag`.
### Compact a script for compression
* `Engine::compact_script` is added which takes a valid script (it still returns parsing errors) and returns a _compacted_ version of the script with all insignificant whitespaces and all comments removed.
* A compact script compresses better than one with liberal whitespaces and comments.
* Unlike some uglifiers or minifiers, `Engine::compact_script` does not optimize the script in any way, nor does it rename variables.
Enhancements
------------
@ -61,6 +68,11 @@ Enhancements
* Block-style doc-comments are now "un-indented" for better formatting.
* Doc-comments on plugin modules are now captured in the module's `doc` field.
* Expression nesting levels is refined such that it grows less excessively for common patterns.
* The traits `Index` and `IndexMut` are added to `FnPtr`.
* `FnPtr::iter_curry` and `FnPtr::iter_curry_mut` are added.
* `Dynamic::deep_scan` is added to recursively scan for `Dynamic` values.
* `>>` and `<<` operators on integers no longer throw errors when the number of bits to shift is out of bounds. Shifting by a negative number of bits simply reverses the shift direction.
* `find` and `find_map` are added for arrays.
Version 1.11.0

View File

@ -2,7 +2,7 @@ error: `cfg` attributes are not allowed for `export_fn`
--> ui_tests/export_fn_cfg.rs:9:1
|
9 | #[cfg(not(feature = "foo"))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/export_fn_cfg.rs:20:8

View File

@ -2,7 +2,7 @@ error: expecting attribute name
--> ui_tests/export_fn_path_attr.rs:9:13
|
9 | #[export_fn(rhai::name = "thing")]
| ^^^^^^^^^^
| ^^^^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/export_fn_path_attr.rs:19:8

View File

@ -2,7 +2,7 @@ error: expecting attribute name
--> ui_tests/export_mod_path_attr.rs:11:11
|
11 | #[rhai_fn(rhai::name = "thing")]
| ^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_mod`
--> ui_tests/export_mod_path_attr.rs:22:8

View File

@ -2,7 +2,7 @@ error: references from Rhai in this position must be mutable
--> ui_tests/first_shared_ref.rs:11:23
|
11 | pub fn test_fn(input: &NonClonable) -> bool {
| ^^^^^^^^^^^^
| ^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/first_shared_ref.rs:22:8

View File

@ -2,7 +2,7 @@ error: Rhai functions cannot return references
--> ui_tests/return_mut_ref.rs:12:38
|
12 | pub fn test_fn(input: &mut Clonable) -> &mut bool {
| ^^^^^^^^^^^^
| ^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/return_mut_ref.rs:23:8

View File

@ -2,7 +2,7 @@ error: Rhai functions cannot return pointers
--> ui_tests/return_pointer.rs:12:33
|
12 | pub fn test_fn(input: Clonable) -> *const str {
| ^^^^^^^^^^^^^
| ^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/return_pointer.rs:24:19

View File

@ -2,7 +2,7 @@ error: Rhai functions cannot return references
--> ui_tests/return_shared_ref.rs:12:33
|
12 | pub fn test_fn(input: Clonable) -> &'static str {
| ^^^^^^^^^^^^^^^
| ^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/return_shared_ref.rs:23:20

View File

@ -2,16 +2,16 @@ error: duplicated attribute 'rhai_fn'
--> ui_tests/rhai_fn_duplicate_attr.rs:6:5
|
6 | #[rhai_fn(pure)]
| ^^^^^^^^^^^^^^^^
error[E0425]: cannot find value `n` in this scope
--> ui_tests/rhai_fn_duplicate_attr.rs:13:29
|
13 | if test_module::test_fn(n) {
| ^ not found in this scope
| ^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_duplicate_attr.rs:13:8
|
13 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared crate or module `test_module`
error[E0425]: cannot find value `n` in this scope
--> ui_tests/rhai_fn_duplicate_attr.rs:13:29
|
13 | if test_module::test_fn(n) {
| ^ not found in this scope

View File

@ -2,7 +2,7 @@ error: conflicting setter
--> ui_tests/rhai_fn_getter_conflict.rs:12:42
|
12 | #[rhai_fn(name = "foo", get = "foo", set = "bar")]
| ^^^^^^^^^^^
| ^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_getter_conflict.rs:23:8

View File

@ -2,7 +2,7 @@ error: conflicting getter
--> ui_tests/rhai_fn_getter_multiple.rs:12:42
|
12 | #[rhai_fn(name = "foo", get = "foo", get = "bar")]
| ^^^^^^^^^^^
| ^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_getter_multiple.rs:23:8

View File

@ -2,7 +2,7 @@ error: property getter must return a value
--> ui_tests/rhai_fn_getter_return.rs:13:9
|
13 | pub fn test_fn(input: &mut Point) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_getter_return.rs:23:5

View File

@ -2,7 +2,7 @@ error: property getter requires exactly 1 parameter
--> ui_tests/rhai_fn_getter_signature.rs:13:20
|
13 | pub fn test_fn(input: Point, value: bool) -> bool {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_getter_signature.rs:23:8

View File

@ -2,7 +2,7 @@ error: index getter must return a value
--> ui_tests/rhai_fn_index_getter_return.rs:13:9
|
13 | pub fn test_fn(input: &mut Point, i: f32) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_index_getter_return.rs:23:8

View File

@ -2,7 +2,7 @@ error: index getter requires exactly 2 parameters
--> ui_tests/rhai_fn_index_getter_signature.rs:13:20
|
13 | pub fn test_fn(input: Point) -> bool {
| ^^^^^^^^^^^^
| ^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_index_getter_signature.rs:23:8

View File

@ -2,7 +2,7 @@ error: expecting attribute name
--> ui_tests/rhai_fn_path_attr.rs:11:11
|
11 | #[rhai_fn(rhai::name = "thing")]
| ^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_path_attr.rs:22:8

View File

@ -2,13 +2,13 @@ error: duplicate Rhai signature for 'foo'
--> ui_tests/rhai_fn_rename_collision.rs:17:15
|
17 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
| ^^^^
error: duplicated function renamed 'foo'
--> ui_tests/rhai_fn_rename_collision.rs:12:15
|
12 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_collision.rs:28:8

View File

@ -8,7 +8,7 @@ error: duplicated function 'foo'
--> ui_tests/rhai_fn_rename_collision_oneattr.rs:12:15
|
12 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_collision_oneattr.rs:27:8

View File

@ -2,13 +2,13 @@ error: duplicate Rhai signature for 'bar'
--> ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs:17:15
|
17 | #[rhai_fn(get = "bar")]
| ^^^^^^^^^^^
| ^^^
error: duplicated function renamed 'bar'
--> ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs:12:15
|
12 | #[rhai_fn(name = "foo", get = "bar")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs:25:8

View File

@ -2,13 +2,13 @@ error: duplicate Rhai signature for 'foo'
--> ui_tests/rhai_fn_rename_collision_with_itself.rs:12:15
|
12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^
error: duplicated function renamed 'foo'
--> ui_tests/rhai_fn_rename_collision_with_itself.rs:12:15
|
12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_collision_with_itself.rs:20:8

View File

@ -2,7 +2,7 @@ error: use attribute 'index_get' instead
--> ui_tests/rhai_fn_rename_to_index_getter.rs:12:15
|
12 | #[rhai_fn(name = "index$get$")]
| ^^^^^^^^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_to_index_getter.rs:23:8

View File

@ -2,7 +2,7 @@ error: use attribute 'index_set' instead
--> ui_tests/rhai_fn_rename_to_index_setter.rs:12:15
|
12 | #[rhai_fn(name = "index$set$")]
| ^^^^^^^^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_to_index_setter.rs:23:8

View File

@ -2,7 +2,7 @@ error: use attribute 'getter = "foo"' instead
--> ui_tests/rhai_fn_rename_to_prop_getter.rs:12:15
|
12 | #[rhai_fn(name = "get$foo")]
| ^^^^^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_to_prop_getter.rs:23:8

View File

@ -2,7 +2,7 @@ error: use attribute 'getter = "foo"' instead
--> ui_tests/rhai_fn_rename_to_prop_setter.rs:12:15
|
12 | #[rhai_fn(name = "get$foo")]
| ^^^^^^^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_rename_to_prop_setter.rs:23:8

View File

@ -2,7 +2,7 @@ error: index setter requires exactly 3 parameters
--> ui_tests/rhai_fn_setter_index_signature.rs:13:20
|
13 | pub fn test_fn(input: Point) -> bool {
| ^^^^^^^^^^^^
| ^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_setter_index_signature.rs:23:8

View File

@ -2,7 +2,7 @@ error: conflicting setter
--> ui_tests/rhai_fn_setter_multiple.rs:12:42
|
12 | #[rhai_fn(name = "foo", set = "foo", set = "bar")]
| ^^^^^^^^^^^
| ^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_setter_multiple.rs:23:8

View File

@ -2,7 +2,7 @@ error: property setter cannot return any value
--> ui_tests/rhai_fn_setter_return.rs:13:51
|
13 | pub fn test_fn(input: &mut Point, value: f32) -> bool {
| ^^^^^^^
| ^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_setter_return.rs:24:8

View File

@ -2,7 +2,7 @@ error: property setter requires exactly 2 parameters
--> ui_tests/rhai_fn_setter_signature.rs:13:20
|
13 | pub fn test_fn(input: Point) -> bool {
| ^^^^^^^^^^^^
| ^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_setter_signature.rs:23:8

View File

@ -2,7 +2,7 @@ error: expecting attribute name
--> ui_tests/rhai_mod_path_attr.rs:11:12
|
11 | #[rhai_mod(rhai::name = "thing")]
| ^^^^^^^^^^
| ^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_mod_path_attr.rs:24:8

View File

@ -2,7 +2,7 @@ error: function parameters other than the first one cannot be passed by referenc
--> ui_tests/second_shared_ref.rs:12:41
|
12 | pub fn test_fn(input: Clonable, factor: &bool) -> bool {
| ^^^^^
| ^
error[E0425]: cannot find function `test_fn` in this scope
--> ui_tests/second_shared_ref.rs:23:8

View File

@ -219,17 +219,11 @@ impl Engine {
let orig_lib_len = global.lib.len();
let mut orig_tag = None;
if let Some(value) = options.tag {
orig_tag = Some(mem::replace(&mut global.tag, value));
}
let orig_tag = options.tag.map(|v| mem::replace(&mut global.tag, v));
let mut this_ptr = options.this_ptr;
global.lib.push(ast.shared_lib().clone());
let mut no_this_ptr = Dynamic::NULL;
let this_ptr = options.this_ptr.unwrap_or(&mut no_this_ptr);
#[cfg(not(feature = "no_module"))]
let orig_embedded_module_resolver = std::mem::replace(
&mut global.embedded_module_resolver,
@ -264,7 +258,8 @@ impl Engine {
global,
caches,
scope,
this_ptr,
this_ptr.as_deref_mut(),
None,
fn_def,
args,
rewind_scope,
@ -278,7 +273,7 @@ impl Engine {
if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let node = &crate::ast::Stmt::Noop(Position::NONE);
self.run_debugger(global, caches, scope, this_ptr, node)?;
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), node)?;
}
#[cfg(not(feature = "no_module"))]

View File

@ -1,6 +1,7 @@
//! Module containing all deprecated API that will be removed in the next major version.
use crate::func::RegisterNativeFunction;
use crate::plugin::*;
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, NativeCallContext,
@ -631,3 +632,593 @@ impl Module {
Self::new()
}
}
#[cfg(not(feature = "no_index"))]
#[export_module]
pub mod deprecated_array_functions {
use crate::packages::array_basic::array_functions::*;
use crate::{Array, INT};
/// Iterate through all the elements in the array, applying a function named by `mapper` to each
/// element in turn, and return the results as a new array.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.map(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `mapper` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn square(x) { x * x }
///
/// fn multiply(x, i) { x * i }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.map("square");
///
/// print(y); // prints "[1, 4, 9, 16, 25]"
///
/// let y = x.map("multiply");
///
/// print(y); // prints "[0, 2, 6, 12, 20]"
/// ```
#[rhai_fn(name = "map", return_raw)]
pub fn map_by_fn_name(
ctx: NativeCallContext,
array: Array,
mapper: &str,
) -> RhaiResultOf<Array> {
map(ctx, array, FnPtr::new(mapper)?)
}
/// Iterate through all the elements in the array, applying a function named by `filter` to each
/// element in turn, and return a copy of all elements (in order) that return `true` as a new array.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.filter(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn screen(x, i) { x * i >= 10 }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.filter("is_odd");
///
/// print(y); // prints "[1, 3, 5]"
///
/// let y = x.filter("screen");
///
/// print(y); // prints "[12, 20]"
/// ```
#[rhai_fn(name = "filter", return_raw)]
pub fn filter_by_fn_name(
ctx: NativeCallContext,
array: Array,
filter_func: &str,
) -> RhaiResultOf<Array> {
filter(ctx, array, FnPtr::new(filter_func)?)
}
/// Iterate through all the elements in the array, applying a function named by `filter` to each
/// element in turn, and return the index of the first element that returns `true`.
/// If no element returns `true`, `-1` is returned.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.index_of(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn is_special(x) { x > 3 }
///
/// fn is_dumb(x) { x > 8 }
///
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of("is_special")); // prints 3
///
/// print(x.index_of("is_dumb")); // prints -1
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<INT> {
index_of_filter(ctx, array, FnPtr::new(filter)?)
}
/// Iterate through all the elements in the array, starting from a particular `start` position,
/// applying a function named by `filter` to each element in turn, and return the index of the
/// first element that returns `true`. If no element returns `true`, `-1` is returned.
///
/// * If `start` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `start` < -length of array, position counts from the beginning of the array.
/// * If `start` ≥ length of array, `-1` is returned.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.index_of(Fn("fn_name"), start)` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn plural(x) { x > 1 }
///
/// fn singular(x) { x < 2 }
///
/// fn screen(x, i) { x * i > 20 }
///
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.index_of("plural", 3)); // prints 5: 2 > 1
///
/// print(x.index_of("singular", 9)); // prints -1: nothing < 2 past index 9
///
/// print(x.index_of("plural", 15)); // prints -1: nothing found past end of array
///
/// print(x.index_of("plural", -5)); // prints 9: -5 = start from index 8
///
/// print(x.index_of("plural", -99)); // prints 1: -99 = start from beginning
///
/// print(x.index_of("screen", 8)); // prints 10: 3 * 10 > 20
/// ```
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_by_fn_name_starting_from(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
start: INT,
) -> RhaiResultOf<INT> {
index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start)
}
/// Return `true` if any element in the array that returns `true` when applied a function named
/// by `filter`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.some(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn large(x) { x > 3 }
///
/// fn huge(x) { x > 10 }
///
/// fn screen(x, i) { i > x }
///
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.some("large")); // prints true
///
/// print(x.some("huge")); // prints false
///
/// print(x.some("screen")); // prints true
/// ```
#[rhai_fn(name = "some", return_raw, pure)]
pub fn some_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<bool> {
some(ctx, array, FnPtr::new(filter)?)
}
/// Return `true` if all elements in the array return `true` when applied a function named by `filter`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.all(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5];
///
/// print(x.all(|v| v > 3)); // prints false
///
/// print(x.all(|v| v > 1)); // prints true
///
/// print(x.all(|v, i| i > v)); // prints false
/// ```
#[rhai_fn(name = "all", return_raw, pure)]
pub fn all_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<bool> {
all(ctx, array, FnPtr::new(filter)?)
}
/// Remove duplicated _consecutive_ elements from the array that return `true` when applied a
/// function named by `comparer`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.dedup(Fn("fn_name"))` instead.
///
/// No element is removed if the correct `comparer` function does not exist.
///
/// # Function Parameters
///
/// * `element1`: copy of the current array element to compare
/// * `element2`: copy of the next array element to compare
///
/// ## Return Value
///
/// `true` if `element1 == element2`, otherwise `false`.
///
/// # Example
///
/// ```rhai
/// fn declining(a, b) { a >= b }
///
/// let x = [1, 2, 2, 2, 3, 1, 2, 3, 4, 3, 3, 2, 1];
///
/// x.dedup("declining");
///
/// print(x); // prints "[1, 2, 3, 4]"
/// ```
#[rhai_fn(name = "dedup", return_raw)]
pub fn dedup_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
comparer: &str,
) -> RhaiResultOf<()> {
Ok(dedup_by_comparer(ctx, array, FnPtr::new(comparer)?))
}
/// Reduce an array by iterating through all elements while applying a function named by `reducer`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.reduce(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) {
/// x + (r ?? 0)
/// }
/// fn process_extra(r, x, i) {
/// x + i + (r ?? 0)
/// }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce("process");
///
/// print(y); // prints 15
///
/// let y = x.reduce("process_extra");
///
/// print(y); // prints 25
/// ```
#[rhai_fn(name = "reduce", return_raw, pure)]
pub fn reduce_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
) -> RhaiResult {
reduce(ctx, array, FnPtr::new(reducer)?)
}
/// Reduce an array by iterating through all elements while applying a function named by `reducer`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.reduce(Fn("fn_name"), initial)` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) { x + r }
///
/// fn process_extra(r, x, i) { x + i + r }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce("process", 5);
///
/// print(y); // prints 20
///
/// let y = x.reduce("process_extra", 5);
///
/// print(y); // prints 30
/// ```
#[rhai_fn(name = "reduce", return_raw, pure)]
pub fn reduce_by_fn_name_with_initial(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
initial: Dynamic,
) -> RhaiResult {
reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
}
/// Reduce an array by iterating through all elements, in _reverse_ order,
/// while applying a function named by `reducer`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.reduce_rev(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, initially `()`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) {
/// x + (r ?? 0)
/// }
/// fn process_extra(r, x, i) {
/// x + i + (r ?? 0)
/// }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev("process");
///
/// print(y); // prints 15
///
/// let y = x.reduce_rev("process_extra");
///
/// print(y); // prints 25
/// ```
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
pub fn reduce_rev_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
) -> RhaiResult {
reduce_rev(ctx, array, FnPtr::new(reducer)?)
}
/// Reduce an array by iterating through all elements, in _reverse_ order,
/// while applying a function named by `reducer`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.reduce_rev(Fn("fn_name"), initial)` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `reducer` must exist taking these parameters:
///
/// * `result`: accumulated result, starting with the value of `initial`
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn process(r, x) { x + r }
///
/// fn process_extra(r, x, i) { x + i + r }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.reduce_rev("process", 5);
///
/// print(y); // prints 20
///
/// let y = x.reduce_rev("process_extra", 5);
///
/// print(y); // prints 30
/// ```
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
pub fn reduce_rev_by_fn_name_with_initial(
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
initial: Dynamic,
) -> RhaiResult {
reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
}
/// Sort the array based on applying a function named by `comparer`.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.sort(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `comparer` must exist taking these parameters:
///
/// * `element1`: copy of the current array element to compare
/// * `element2`: copy of the next array element to compare
///
/// ## Return Value
///
/// * Any integer > 0 if `element1 > element2`
/// * Zero if `element1 == element2`
/// * Any integer < 0 if `element1 < element2`
///
/// # Example
///
/// ```rhai
/// fn reverse(a, b) {
/// if a > b {
/// -1
/// } else if a < b {
/// 1
/// } else {
/// 0
/// }
/// }
/// let x = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10];
///
/// x.sort("reverse");
///
/// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
/// ```
#[rhai_fn(name = "sort", return_raw)]
pub fn sort_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
comparer: &str,
) -> RhaiResultOf<()> {
sort(ctx, array, FnPtr::new(comparer)?)
}
/// Remove all elements in the array that returns `true` when applied a function named by `filter`
/// and return them as a new array.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.drain(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn small(x) { x < 3 }
///
/// fn screen(x, i) { x + i > 5 }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.drain("small");
///
/// print(x); // prints "[3, 4, 5]"
///
/// print(y); // prints "[1, 2]"
///
/// let z = x.drain("screen");
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "drain", return_raw)]
pub fn drain_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<Array> {
drain(ctx, array, FnPtr::new(filter)?)
}
/// Remove all elements in the array that do not return `true` when applied a function named by
/// `filter` and return them as a new array.
///
/// # Deprecated API
///
/// This method is deprecated and will be removed from the next major version.
/// Use `array.retain(Fn("fn_name"))` instead.
///
/// # Function Parameters
///
/// A function with the same name as the value of `filter` must exist taking these parameters:
///
/// * `element`: copy of array element
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// fn large(x) { x >= 3 }
///
/// fn screen(x, i) { x + i <= 5 }
///
/// let x = [1, 2, 3, 4, 5];
///
/// let y = x.retain("large");
///
/// print(x); // prints "[3, 4, 5]"
///
/// print(y); // prints "[1, 2]"
///
/// let z = x.retain("screen");
///
/// print(x); // prints "[3, 4]"
///
/// print(z); // prints "[5]"
/// ```
#[rhai_fn(name = "retain", return_raw)]
pub fn retain_by_fn_name(
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> RhaiResultOf<Array> {
retain(ctx, array, FnPtr::new(filter)?)
}
}

View File

@ -250,9 +250,8 @@ impl Engine {
#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let mut this_ptr = Dynamic::NULL;
let node = &crate::ast::Stmt::Noop(Position::NONE);
self.run_debugger(global, caches, scope, &mut this_ptr, node)?;
self.run_debugger(global, caches, scope, None, node)?;
}
Ok(r)
})

View File

@ -1,6 +1,10 @@
//! Module that provide formatting services to the [`Engine`].
use crate::packages::iter_basic::{BitRange, CharsStream, StepRange};
use crate::parser::{ParseResult, ParseState};
use crate::types::StringsInterner;
use crate::{
Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, Position, RhaiError, ERR,
Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, OptimizationLevel, Position,
RhaiError, Scope, SmartString, ERR,
};
use std::any::type_name;
#[cfg(feature = "no_std")]
@ -263,4 +267,33 @@ impl Engine {
let t = self.map_type_name(type_name::<T>()).into();
ERR::ErrorMismatchDataType(t, typ.into(), pos).into()
}
/// Compact a script to eliminate insignificant whitespaces and comments.
///
/// This is useful to prepare a script for further compressing.
///
/// The output script is semantically identical to the input script, except smaller in size.
///
/// Unlike other uglifiers and minifiers, this method does not rename variables nor perform any
/// optimization on the input script.
#[inline]
pub fn compact_script(&self, script: impl AsRef<str>) -> ParseResult<String> {
let scripts = [script];
let (mut stream, tc) = self.lex_raw(&scripts, self.token_mapper.as_deref());
tc.borrow_mut().compressed = Some(String::new());
stream.state.last_token = Some(SmartString::new_const());
let scope = Scope::new();
let mut interner = StringsInterner::new();
let mut state = ParseState::new(&scope, &mut interner, tc);
let mut _ast = self.parse(
stream.peekable(),
&mut state,
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
#[cfg(feature = "no_optimize")]
(),
)?;
let tc = state.tokenizer_control.borrow();
Ok(tc.compressed.as_ref().unwrap().into())
}
}

View File

@ -23,7 +23,7 @@ pub mod limits_unchecked;
pub mod events;
pub mod type_names;
pub mod formatting;
pub mod custom_syntax;

View File

@ -131,9 +131,8 @@ impl Engine {
#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
let mut this_ptr = crate::Dynamic::NULL;
let node = &crate::ast::Stmt::Noop(crate::Position::NONE);
self.run_debugger(global, caches, scope, &mut this_ptr, node)?;
self.run_debugger(global, caches, scope, None, node)?;
}
Ok(())
})

View File

@ -750,10 +750,8 @@ impl AST {
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
self.lib
.iter_script_fn()
.map(|(.., fn_def)| fn_def.as_ref())
pub fn iter_fn_def(&self) -> impl Iterator<Item = &crate::Shared<super::ScriptFnDef>> {
self.lib.iter_script_fn().map(|(.., fn_def)| fn_def)
}
/// Iterate through all function definitions.
///
@ -762,10 +760,8 @@ impl AST {
#[cfg(not(feature = "no_function"))]
#[allow(dead_code)]
#[inline]
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
self.lib
.iter_script_fn()
.map(|(.., fn_def)| fn_def.as_ref())
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &crate::Shared<super::ScriptFnDef>> {
self.lib.iter_script_fn().map(|(.., fn_def)| fn_def)
}
/// Iterate through all function definitions.
///

View File

@ -19,9 +19,6 @@ pub use ident::Ident;
pub use namespace::Namespace;
#[cfg(feature = "no_module")]
pub use namespace_none::Namespace;
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use script_fn::EncapsulatedEnviron;
#[cfg(not(feature = "no_function"))]
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
pub use stmt::{

View File

@ -7,34 +7,12 @@ use crate::{FnArgsVec, ImmutableString};
use std::prelude::v1::*;
use std::{fmt, hash::Hash};
/// _(internals)_ Encapsulated AST environment.
/// Exported under the `internals` feature only.
///
/// 1) other functions defined within the same AST
/// 2) the stack of imported [modules][crate::Module]
/// 3) global constants
///
/// Not available under `no_module` or `no_function`.
#[cfg(not(feature = "no_module"))]
#[derive(Debug, Clone)]
pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST].
pub lib: crate::SharedModule,
/// Imported [modules][crate::Module].
pub imports: Box<[(ImmutableString, crate::SharedModule)]>,
/// Globally-defined constants.
pub constants: Option<crate::eval::SharedGlobalConstants>,
}
/// _(internals)_ A type containing information on a script-defined function.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone)]
pub struct ScriptFnDef {
/// Function body.
pub body: StmtBlock,
/// Encapsulated AST environment, if any.
#[cfg(not(feature = "no_module"))]
pub environ: Option<crate::Shared<EncapsulatedEnviron>>,
/// Function name.
pub name: ImmutableString,
/// Function access mode.

View File

@ -573,7 +573,7 @@ pub enum Stmt {
Assignment(Box<(OpAssignment, BinaryExpr)>),
/// func `(` expr `,` ... `)`
///
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
/// This is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
/// function call forming one statement.
FnCall(Box<FnCallExpr>, Position),
/// `{` stmt`;` ... `}`

View File

@ -340,7 +340,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
expr: &Expr,
new_val: Option<(Dynamic, &OpAssignment)>,
) -> RhaiResult {
@ -379,7 +379,13 @@ impl Engine {
}
// All other patterns - evaluate the arguments chain
_ => self.eval_dot_index_chain_arguments(
global, caches, scope, this_ptr, expr, rhs, idx_values,
global,
caches,
scope,
this_ptr.as_deref_mut(),
expr,
rhs,
idx_values,
)?,
}
@ -387,22 +393,13 @@ impl Engine {
// id.??? or id[???]
Expr::Variable(.., var_pos) => {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, lhs)?;
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), lhs)?;
self.track_operation(global, *var_pos)?;
let target = &mut self.search_namespace(global, caches, scope, this_ptr, lhs)?;
let mut this_ptr = Dynamic::NULL;
self.eval_dot_index_chain_raw(
global,
caches,
&mut this_ptr,
lhs,
expr,
target,
rhs,
idx_values,
new_val,
global, caches, None, lhs, expr, target, rhs, idx_values, new_val,
)
}
// {expr}.??? = ??? or {expr}[???] = ???
@ -410,7 +407,7 @@ impl Engine {
// {expr}.??? or {expr}[???]
lhs_expr => {
let value = self
.eval_expr(global, caches, scope, this_ptr, lhs_expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs_expr)?
.flatten();
let obj_ptr = &mut value.into();
@ -428,7 +425,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
parent: &Expr,
expr: &Expr,
idx_values: &mut FnArgsVec<Dynamic>,
@ -440,12 +437,10 @@ impl Engine {
match expr {
#[cfg(not(feature = "no_object"))]
Expr::MethodCall(x, ..) if chain_type == ChainType::Dotting && !x.is_qualified() => {
for arg_expr in &x.args {
idx_values.push(
self.get_arg_value(global, caches, scope, this_ptr, arg_expr)?
.0
.flatten(),
);
for expr in &x.args {
let arg_value =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
idx_values.push(arg_value.0.flatten());
}
}
#[cfg(not(feature = "no_object"))]
@ -474,12 +469,15 @@ impl Engine {
Expr::MethodCall(x, ..)
if chain_type == ChainType::Dotting && !x.is_qualified() =>
{
for arg_expr in &x.args {
_arg_values.push(
self.get_arg_value(global, caches, scope, this_ptr, arg_expr)?
.0
.flatten(),
);
for expr in &x.args {
let arg_value = self.get_arg_value(
global,
caches,
scope,
this_ptr.as_deref_mut(),
expr,
)?;
_arg_values.push(arg_value.0.flatten());
}
}
#[cfg(not(feature = "no_object"))]
@ -493,7 +491,7 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
_ if chain_type == ChainType::Indexing => {
_arg_values.push(
self.eval_expr(global, caches, scope, this_ptr, lhs)?
self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), lhs)?
.flatten(),
);
}
@ -530,7 +528,7 @@ impl Engine {
&self,
global: &mut GlobalRuntimeState,
caches: &mut Caches,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
root: &Expr,
parent: &Expr,
target: &mut Target,
@ -560,7 +558,7 @@ impl Engine {
if !parent.options().contains(ASTFlags::BREAK) =>
{
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, parent)?;
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), parent)?;
let idx_val = &mut idx_values.pop().unwrap();
let mut idx_val_for_setter = idx_val.clone();
@ -843,7 +841,13 @@ impl Engine {
let val_target = &mut match x.lhs {
Expr::Property(ref p, pos) => {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, _node)?;
self.run_debugger(
global,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
let index = &mut p.2.clone().into();
self.get_indexed_mut(
@ -854,7 +858,11 @@ impl Engine {
Expr::MethodCall(ref x, pos) if !x.is_qualified() => {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(
global, caches, scope, this_ptr, _node,
global,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
@ -896,7 +904,13 @@ impl Engine {
// xxx.prop[expr] | xxx.prop.expr
Expr::Property(ref p, pos) => {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, _node)?;
self.run_debugger(
global,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
let ((getter, hash_get), (setter, hash_set), name) = &**p;
let args = &mut [target.as_mut()];
@ -972,7 +986,11 @@ impl Engine {
let val = {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(
global, caches, scope, this_ptr, _node,
global,
caches,
scope,
this_ptr.as_deref_mut(),
_node,
)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));

View File

@ -410,7 +410,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
this_ptr: Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
) -> RhaiResultOf<()> {
if self.is_debugger_registered() {
@ -435,7 +435,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
this_ptr: Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
) -> RhaiResultOf<Option<DebuggerStatus>> {
if self.is_debugger_registered() {
@ -456,7 +456,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
this_ptr: Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
) -> RhaiResultOf<Option<DebuggerStatus>> {
let node = node.into();
@ -502,7 +502,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
this_ptr: Option<&mut Dynamic>,
node: ASTNode<'a>,
event: DebuggerEvent,
) -> Result<Option<DebuggerStatus>, Box<crate::EvalAltResult>> {

View File

@ -17,7 +17,7 @@ pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
/// The current [`Scope`].
scope: &'s mut Scope<'ps>,
/// The current bound `this` pointer, if any.
this_ptr: &'t mut Dynamic,
this_ptr: Option<&'t mut Dynamic>,
}
impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
@ -29,7 +29,7 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
global: &'g mut GlobalRuntimeState,
caches: &'c mut Caches,
scope: &'s mut Scope<'ps>,
this_ptr: &'t mut Dynamic,
this_ptr: Option<&'t mut Dynamic>,
) -> Self {
Self {
engine,
@ -118,24 +118,16 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
&self.global.lib
}
/// The current bound `this` pointer, if any.
#[inline]
#[inline(always)]
#[must_use]
pub fn this_ptr(&self) -> Option<&Dynamic> {
if self.this_ptr.is_null() {
None
} else {
Some(self.this_ptr)
}
self.this_ptr.as_deref()
}
/// Mutable reference to the current bound `this` pointer, if any.
#[inline]
#[inline(always)]
#[must_use]
pub fn this_ptr_mut(&mut self) -> Option<&mut Dynamic> {
if self.this_ptr.is_null() {
None
} else {
Some(self.this_ptr)
}
self.this_ptr.as_deref_mut()
}
/// The current nesting level of function calls.
#[inline(always)]
@ -177,19 +169,20 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
rewind_scope: bool,
) -> crate::RhaiResult {
let expr: &crate::ast::Expr = expr;
let this_ptr = self.this_ptr.as_deref_mut();
match expr {
crate::ast::Expr::Stmt(statements) => self.engine.eval_stmt_block(
crate::ast::Expr::Stmt(stmts) => self.engine.eval_stmt_block(
self.global,
self.caches,
self.scope,
self.this_ptr,
statements,
this_ptr,
stmts,
rewind_scope,
),
_ => self
.engine
.eval_expr(self.global, self.caches, self.scope, self.this_ptr, expr),
.eval_expr(self.global, self.caches, self.scope, this_ptr, expr),
}
}
}

View File

@ -53,7 +53,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &'s mut Scope,
this_ptr: &'s mut Dynamic,
this_ptr: Option<&'s mut Dynamic>,
expr: &Expr,
) -> RhaiResultOf<Target<'s>> {
match expr {
@ -132,7 +132,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &'s mut Scope,
this_ptr: &'s mut Dynamic,
this_ptr: Option<&'s mut Dynamic>,
expr: &Expr,
) -> RhaiResultOf<Target<'s>> {
// Make sure that the pointer indirection is taken only when absolutely necessary.
@ -142,10 +142,10 @@ impl Engine {
Expr::Variable(v, None, ..)
if v.0.is_none() && v.1.is_empty() && v.3 == KEYWORD_THIS =>
{
return if this_ptr.is_null() {
Err(ERR::ErrorUnboundThis(expr.position()).into())
} else {
return if let Some(this_ptr) = this_ptr {
Ok(this_ptr.into())
} else {
Err(ERR::ErrorUnboundThis(expr.position()).into())
};
}
@ -223,7 +223,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
expr: &Expr,
) -> RhaiResult {
// Coded this way for better branch prediction.
@ -233,7 +233,8 @@ impl Engine {
// binary operators are also function calls.
if let Expr::FnCall(x, pos) = expr {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?;
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
@ -247,16 +248,14 @@ impl Engine {
// will cost more than the mis-predicted `match` branch.
if let Expr::Variable(x, index, var_pos) = expr {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, expr)?;
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
self.track_operation(global, expr.position())?;
return if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS {
if this_ptr.is_null() {
ERR::ErrorUnboundThis(*var_pos).into()
} else {
Ok(this_ptr.clone())
}
this_ptr
.ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into())
.cloned()
} else {
self.search_namespace(global, caches, scope, this_ptr, expr)
.map(Target::take_or_clone)
@ -264,7 +263,8 @@ impl Engine {
}
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?;
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
@ -291,7 +291,7 @@ impl Engine {
x.iter()
.try_for_each(|expr| {
let item = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
.flatten();
op_info.pos = expr.start_position();
@ -312,7 +312,13 @@ impl Engine {
crate::Array::with_capacity(x.len()),
|mut array, item_expr| {
let value = self
.eval_expr(global, caches, scope, this_ptr, item_expr)?
.eval_expr(
global,
caches,
scope,
this_ptr.as_deref_mut(),
item_expr,
)?
.flatten();
#[cfg(not(feature = "unchecked"))]
@ -344,7 +350,7 @@ impl Engine {
x.0.iter()
.try_fold(x.1.clone(), |mut map, (key, value_expr)| {
let value = self
.eval_expr(global, caches, scope, this_ptr, value_expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)?
.flatten();
#[cfg(not(feature = "unchecked"))]
@ -367,7 +373,7 @@ impl Engine {
}
Expr::And(x, ..) => Ok((self
.eval_expr(global, caches, scope, this_ptr, &x.lhs)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
&& self
@ -377,7 +383,7 @@ impl Engine {
.into()),
Expr::Or(x, ..) => Ok((self
.eval_expr(global, caches, scope, this_ptr, &x.lhs)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
|| self
@ -387,7 +393,8 @@ impl Engine {
.into()),
Expr::Coalesce(x, ..) => {
let value = self.eval_expr(global, caches, scope, this_ptr, &x.lhs)?;
let value =
self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), &x.lhs)?;
if value.is_unit() {
self.eval_expr(global, caches, scope, this_ptr, &x.rhs)

View File

@ -32,7 +32,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
statements: &[Stmt],
restore_orig_state: bool,
) -> RhaiResult {
@ -71,6 +71,8 @@ impl Engine {
// Run the statements
statements.iter().try_fold(Dynamic::UNIT, |_, stmt| {
let this_ptr = this_ptr.as_deref_mut();
#[cfg(not(feature = "no_module"))]
let imports_len = global.num_imports();
@ -198,12 +200,13 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
stmt: &Stmt,
rewind_scope: bool,
) -> RhaiResult {
#[cfg(feature = "debugging")]
let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, stmt)?;
let reset =
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?;
#[cfg(feature = "debugging")]
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
@ -227,7 +230,7 @@ impl Engine {
if let Expr::Variable(x, ..) = lhs {
let rhs_val = self
.eval_expr(global, caches, scope, this_ptr, rhs)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)?
.flatten();
let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?;
@ -259,7 +262,7 @@ impl Engine {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
{
let rhs_val = self
.eval_expr(global, caches, scope, this_ptr, rhs)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), rhs)?
.flatten()
.intern_string(self);
@ -310,7 +313,7 @@ impl Engine {
let (expr, if_block, else_block) = &**x;
let guard_val = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
@ -337,7 +340,7 @@ impl Engine {
let mut result = None;
let value = self.eval_expr(global, caches, scope, this_ptr, expr)?;
let value = self.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
if value.is_hashable() {
let hasher = &mut get_hasher();
@ -354,7 +357,7 @@ impl Engine {
let cond_result = match block.condition {
Expr::BoolConstant(b, ..) => b,
ref c => self
.eval_expr(global, caches, scope, this_ptr, c)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
.as_bool()
.map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, c.position())
@ -376,7 +379,7 @@ impl Engine {
let cond_result = match block.condition {
Expr::BoolConstant(b, ..) => b,
ref c => self
.eval_expr(global, caches, scope, this_ptr, c)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
.as_bool()
.map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, c.position())
@ -409,14 +412,15 @@ impl Engine {
}
loop {
if let Err(err) =
self.eval_stmt_block(global, caches, scope, this_ptr, body, true)
{
match *err {
let this_ptr = this_ptr.as_deref_mut();
match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) {
Ok(..) => (),
Err(err) => match *err {
ERR::LoopBreak(false, ..) => (),
ERR::LoopBreak(true, value, ..) => break Ok(value),
_ => break Err(err),
}
},
}
}
}
@ -427,7 +431,7 @@ impl Engine {
loop {
let condition = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
@ -439,14 +443,15 @@ impl Engine {
continue;
}
if let Err(err) =
self.eval_stmt_block(global, caches, scope, this_ptr, body, true)
{
match *err {
let this_ptr = this_ptr.as_deref_mut();
match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) {
Ok(..) => (),
Err(err) => match *err {
ERR::LoopBreak(false, ..) => (),
ERR::LoopBreak(true, value, ..) => break Ok(value),
_ => break Err(err),
}
},
}
}
}
@ -458,19 +463,20 @@ impl Engine {
loop {
if !body.is_empty() {
if let Err(err) =
self.eval_stmt_block(global, caches, scope, this_ptr, body, true)
{
match *err {
let this_ptr = this_ptr.as_deref_mut();
match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) {
Ok(..) => (),
Err(err) => match *err {
ERR::LoopBreak(false, ..) => continue,
ERR::LoopBreak(true, value, ..) => break Ok(value),
_ => break Err(err),
}
},
}
}
let condition = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
@ -485,7 +491,7 @@ impl Engine {
let (var_name, counter, expr, statements) = &**x;
let iter_obj = self
.eval_expr(global, caches, scope, this_ptr, expr)?
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
.flatten();
let iter_type = iter_obj.type_id();
@ -566,6 +572,8 @@ impl Engine {
continue;
}
let this_ptr = this_ptr.as_deref_mut();
match self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) {
Ok(_) => (),
Err(err) => match *err {
@ -603,7 +611,14 @@ impl Engine {
catch_block,
} = &**x;
match self.eval_stmt_block(global, caches, scope, this_ptr, try_block, true) {
match self.eval_stmt_block(
global,
caches,
scope,
this_ptr.as_deref_mut(),
try_block,
true,
) {
r @ Ok(_) => r,
Err(err) if err.is_pseudo_error() => Err(err),
Err(err) if !err.is_catchable() => Err(err),
@ -650,6 +665,8 @@ impl Engine {
scope.push(catch_var.name.clone(), err_value);
}
let this_ptr = this_ptr.as_deref_mut();
self.eval_stmt_block(global, caches, scope, this_ptr, catch_block, true)
.map(|_| Dynamic::UNIT)
.map_err(|result_err| match *result_err {
@ -707,7 +724,8 @@ impl Engine {
nesting_level: global.scope_level,
will_shadow,
};
let context = EvalContext::new(self, global, caches, scope, this_ptr);
let context =
EvalContext::new(self, global, caches, scope, this_ptr.as_deref_mut());
if !filter(true, info, context)? {
return Err(ERR::ErrorForbiddenVariable(var_name.to_string(), *pos).into());
@ -866,7 +884,7 @@ impl Engine {
/// Evaluate a list of statements with no `this` pointer.
/// This is commonly used to evaluate a list of statements in an [`AST`][crate::AST] or a script function body.
#[inline]
#[inline(always)]
pub(crate) fn eval_global_statements(
&self,
global: &mut GlobalRuntimeState,
@ -874,9 +892,7 @@ impl Engine {
scope: &mut Scope,
statements: &[Stmt],
) -> RhaiResult {
let mut this_ptr = Dynamic::NULL;
self.eval_stmt_block(global, caches, scope, &mut this_ptr, statements, false)
self.eval_stmt_block(global, caches, scope, None, statements, false)
.or_else(|err| match *err {
ERR::Return(out, ..) => Ok(out),
ERR::LoopBreak(..) => {

View File

@ -118,6 +118,11 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
let y = args[1].$yy().unwrap() as $base;
Ok(x.$func(y as $yyy).into())
}, false) };
($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().unwrap() as $base;
Ok($func(x, y).into())
}, false) };
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().unwrap() as $base;
@ -133,6 +138,11 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
let y = <$base>::from(args[1].$yy().unwrap());
Ok(x.$func(y).into())
}, false) };
(from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().unwrap());
Ok($func(x, y).into())
}, false) };
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().unwrap());
@ -155,8 +165,8 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
Divide => return Some(impl_op!(INT => divide(as_int, as_int))),
Modulo => return Some(impl_op!(INT => modulo(as_int, as_int))),
PowerOf => return Some(impl_op!(INT => power(as_int, as_int))),
RightShift => return Some(impl_op!(INT => shift_right(as_int, as_int))),
LeftShift => return Some(impl_op!(INT => shift_left(as_int, as_int))),
RightShift => return Some(impl_op!(INT => Ok(shift_right(as_int, as_int)))),
LeftShift => return Some(impl_op!(INT => Ok(shift_left(as_int, as_int)))),
_ => (),
}
@ -168,8 +178,26 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
Divide => return Some(impl_op!(INT => as_int / as_int)),
Modulo => return Some(impl_op!(INT => as_int % as_int)),
PowerOf => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
RightShift => return Some(impl_op!(INT => as_int >> as_int)),
LeftShift => return Some(impl_op!(INT => as_int << as_int)),
RightShift => {
return Some((
|_, args| {
let x = args[0].as_int().unwrap();
let y = args[1].as_int().unwrap();
Ok((if y < 0 { x << -y } else { x >> y }).into())
},
false,
))
}
LeftShift => {
return Some((
|_, args| {
let x = args[0].as_int().unwrap();
let y = args[1].as_int().unwrap();
Ok((if y < 0 { x >> -y } else { x << y }).into())
},
false,
))
}
_ => (),
}
@ -614,6 +642,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into())
}, false) };
($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
let x = args[0].$xx().unwrap();
let y = args[1].$yy().unwrap() as $x;
let v: Dynamic = $func(x, y).into();
Ok((*args[0].write_lock().unwrap() = v).into())
}, false) };
($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = args[0].$xx().unwrap();
let y = args[1].$yy().unwrap() as $x;
@ -628,6 +662,11 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into())
}, false) };
(from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
let x = args[0].$xx().unwrap();
let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock().unwrap() = $func(x, y).into()).into())
}, false) };
(from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
let x = args[0].$xx().unwrap();
let y = <$x>::from(args[1].$yy().unwrap());
@ -650,8 +689,8 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
DivideAssign => return Some(impl_op!(INT => divide(as_int, as_int))),
ModuloAssign => return Some(impl_op!(INT => modulo(as_int, as_int))),
PowerOfAssign => return Some(impl_op!(INT => power(as_int, as_int))),
RightShiftAssign => return Some(impl_op!(INT => shift_right(as_int, as_int))),
LeftShiftAssign => return Some(impl_op!(INT => shift_left(as_int, as_int))),
RightShiftAssign => return Some(impl_op!(INT => Ok(shift_right(as_int, as_int)))),
LeftShiftAssign => return Some(impl_op!(INT => Ok(shift_left(as_int, as_int)))),
_ => (),
}
@ -663,8 +702,28 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
DivideAssign => return Some(impl_op!(INT /= as_int)),
ModuloAssign => return Some(impl_op!(INT %= as_int)),
PowerOfAssign => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
RightShiftAssign => return Some(impl_op!(INT >>= as_int)),
LeftShiftAssign => return Some(impl_op!(INT <<= as_int)),
RightShiftAssign => {
return Some((
|_, args| {
let x = args[0].as_int().unwrap();
let y = args[1].as_int().unwrap();
let v = if y < 0 { x << -y } else { x >> y };
Ok((*args[0].write_lock::<Dynamic>().unwrap() = v.into()).into())
},
false,
))
}
LeftShiftAssign => {
return Some((
|_, args| {
let x = args[0].as_int().unwrap();
let y = args[1].as_int().unwrap();
let v = if y < 0 { x >> -y } else { x << y };
Ok((*args[0].write_lock::<Dynamic>().unwrap() = v.into()).into())
},
false,
))
}
_ => (),
}

View File

@ -270,21 +270,27 @@ impl Engine {
}
// Try to find a built-in version
let builtin =
args.and_then(|args| match op_token {
let builtin = args.and_then(|args| match op_token {
Token::NONE => None,
token if token.is_op_assignment() => {
let (first_arg, rest_args) = args.split_first().unwrap();
get_builtin_op_assignment_fn(token, first_arg, rest_args[0])
.map(|(f, ctx)| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f), ctx),
get_builtin_op_assignment_fn(token, first_arg, rest_args[0]).map(
|(f, has_context)| FnResolutionCacheEntry {
func: CallableFunction::Method {
func: Shared::new(f),
has_context,
},
source: None,
})
},
)
}
token => get_builtin_binary_op_fn(token, args[0], args[1]).map(
|(f, ctx)| FnResolutionCacheEntry {
func: CallableFunction::Method(Shared::new(f), ctx),
|(f, has_context)| FnResolutionCacheEntry {
func: CallableFunction::Method {
func: Shared::new(f),
has_context,
},
source: None,
},
),
@ -380,7 +386,6 @@ impl Engine {
// Clone the first argument
backup.change_first_arg_to_copy(args);
}
auto_restore!(args if swap => move |a| backup.restore_first_arg(a));
#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
@ -418,6 +423,10 @@ impl Engine {
.and_then(|r| self.check_data_size(r, pos))
.map_err(|err| err.fill_position(pos));
if swap {
backup.restore_first_arg(args);
}
#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
use crate::eval::{DebuggerEvent, DebuggerStatus};
@ -429,7 +438,6 @@ impl Engine {
};
if trigger {
let scope = &mut Scope::new();
let mut this_ptr = Dynamic::NULL;
let node = crate::ast::Stmt::Noop(pos);
let node = (&node).into();
let event = match _result {
@ -437,10 +445,9 @@ impl Engine {
Err(ref err) => DebuggerEvent::FunctionExitWithError(err),
};
if let Err(err) =
self.run_debugger_raw(global, caches, scope, &mut this_ptr, node, event)
{
_result = Err(err);
match self.run_debugger_raw(global, caches, scope, None, node, event) {
Ok(..) => (),
Err(err) => _result = Err(err),
}
}
@ -644,9 +651,10 @@ impl Engine {
// Script function call
assert!(func.is_script());
let func = func.get_script_fn_def().expect("script-defined function");
let f = func.get_script_fn_def().expect("script-defined function");
let environ = func.get_encapsulated_environ();
if func.body.is_empty() {
if f.body.is_empty() {
return Ok((Dynamic::UNIT, false));
}
@ -666,7 +674,15 @@ impl Engine {
let (first_arg, rest_args) = _args.split_first_mut().unwrap();
self.call_script_fn(
global, caches, scope, first_arg, func, rest_args, true, pos,
global,
caches,
scope,
Some(first_arg),
environ,
f,
rest_args,
true,
pos,
)
} else {
// Normal call of script function
@ -681,9 +697,7 @@ impl Engine {
auto_restore!(args = (_args) if swap => move |a| backup.restore_first_arg(a));
let mut this_ptr = Dynamic::NULL;
self.call_script_fn(global, caches, scope, &mut this_ptr, func, args, true, pos)
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
}
.map(|r| (r, false));
}
@ -704,7 +718,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
this_ptr: Option<&mut Dynamic>,
arg_expr: &Expr,
) -> RhaiResultOf<(Dynamic, Position)> {
// Literal values
@ -762,14 +776,13 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
if let Some(fn_def) = fn_ptr.fn_def() {
if fn_def.params.len() == args.len() {
let mut this_ptr = Dynamic::NULL;
return self
.call_script_fn(
global,
caches,
&mut Scope::new(),
&mut this_ptr,
None,
fn_ptr.encapsulated_environ(),
fn_def,
args,
true,
@ -822,15 +835,15 @@ impl Engine {
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>();
#[cfg(not(feature = "no_function"))]
let (fn_name, is_anon, fn_curry, fn_def) = {
let (fn_name, is_anon, fn_curry, _environ, fn_def) = {
let is_anon = fn_ptr.is_anonymous();
let (fn_name, fn_curry, fn_def) = fn_ptr.take_data();
(fn_name, is_anon, fn_curry, fn_def)
let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data();
(fn_name, is_anon, fn_curry, environ, fn_def)
};
#[cfg(feature = "no_function")]
let (fn_name, is_anon, fn_curry) = {
let (fn_name, fn_curry) = fn_ptr.take_data();
(fn_name, false, fn_curry)
let (fn_name, is_anon, fn_curry, _environ) = {
let (fn_name, fn_curry, environ) = fn_ptr.take_data();
(fn_name, false, fn_curry, environ)
};
// Replace the first argument with the object pointer, adding the curried arguments
@ -855,7 +868,8 @@ impl Engine {
global,
caches,
&mut Scope::new(),
target,
Some(target),
_environ.as_deref(),
&fn_def,
args,
true,
@ -996,7 +1010,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
fn_name: &str,
op_token: Token,
first_arg: Option<&Expr>,
@ -1020,7 +1034,7 @@ impl Engine {
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let arg = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(global, caches, scope, this_ptr, arg)?;
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?;
if !arg_value.is_fnptr() {
let typ = self.map_type_name(arg_value.type_name());
@ -1030,15 +1044,15 @@ impl Engine {
let fn_ptr = arg_value.cast::<FnPtr>();
#[cfg(not(feature = "no_function"))]
let (fn_name, is_anon, fn_curry, fn_def) = {
let (fn_name, is_anon, fn_curry, _environ, fn_def) = {
let is_anon = fn_ptr.is_anonymous();
let (fn_name, fn_curry, fn_def) = fn_ptr.take_data();
(fn_name, is_anon, fn_curry, fn_def)
let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data();
(fn_name, is_anon, fn_curry, environ, fn_def)
};
#[cfg(feature = "no_function")]
let (fn_name, is_anon, fn_curry) = {
let (fn_name, fn_curry) = fn_ptr.take_data();
(fn_name, false, fn_curry)
let (fn_name, is_anon, fn_curry, _environ) = {
let (fn_name, fn_curry, environ) = fn_ptr.take_data();
(fn_name, false, fn_curry, environ)
};
curry.extend(fn_curry.into_iter());
@ -1052,22 +1066,17 @@ impl Engine {
.into_iter()
.map(Ok)
.chain(a_expr.iter().map(|expr| -> Result<_, RhaiError> {
let this_ptr = this_ptr.as_deref_mut();
self.get_arg_value(global, caches, scope, this_ptr, expr)
.map(|(v, ..)| v)
}))
.collect::<RhaiResultOf<FnArgsVec<_>>>()?;
let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();
let mut this_ptr = Dynamic::NULL;
let scope = &mut Scope::new();
let environ = _environ.as_deref();
return self.call_script_fn(
global,
caches,
&mut Scope::new(),
&mut this_ptr,
&fn_def,
args,
true,
pos,
global, caches, scope, None, environ, &fn_def, args, true, pos,
);
}
}
@ -1111,7 +1120,7 @@ impl Engine {
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let first = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(global, caches, scope, this_ptr, first)?;
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?;
if !arg_value.is_fnptr() {
let typ = self.map_type_name(arg_value.type_name());
@ -1122,7 +1131,8 @@ impl Engine {
// Append the new curried arguments to the existing list.
a_expr.iter().try_for_each(|expr| -> Result<_, RhaiError> {
let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr, expr)?;
let (value, ..) =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
fn_ptr.add_curry(value);
Ok(())
})?;
@ -1134,7 +1144,8 @@ impl Engine {
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = first_arg.unwrap();
let (arg_value, ..) = self.get_arg_value(global, caches, scope, this_ptr, arg)?;
let (arg_value, ..) =
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?;
return Ok(arg_value.is_shared().into());
}
@ -1143,7 +1154,7 @@ impl Engine {
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
let first = first_arg.unwrap();
let (arg_value, arg_pos) =
self.get_arg_value(global, caches, scope, this_ptr, first)?;
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?;
let fn_name = arg_value
.into_immutable_string()
@ -1235,7 +1246,7 @@ impl Engine {
.copied()
.chain(a_expr.iter())
.try_for_each(|expr| {
self.get_arg_value(global, caches, scope, this_ptr, expr)
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)
.map(|(value, ..)| arg_values.push(value.flatten()))
})?;
args.extend(curry.iter_mut());
@ -1263,11 +1274,11 @@ impl Engine {
let first_expr = first_arg.unwrap();
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, first_expr)?;
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), first_expr)?;
// func(x, ...) -> x.func(...)
a_expr.iter().try_for_each(|expr| {
self.get_arg_value(global, caches, scope, this_ptr, expr)
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)
.map(|(value, ..)| arg_values.push(value.flatten()))
})?;
@ -1294,7 +1305,7 @@ impl Engine {
.into_iter()
.chain(a_expr.iter())
.try_for_each(|expr| {
self.get_arg_value(global, caches, scope, this_ptr, expr)
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)
.map(|(value, ..)| arg_values.push(value.flatten()))
})?;
args.extend(curry.iter_mut());
@ -1316,7 +1327,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
namespace: &crate::ast::Namespace,
fn_name: &str,
args_expr: &[Expr],
@ -1335,13 +1346,19 @@ impl Engine {
// and avoid cloning the value
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
#[cfg(feature = "debugging")]
self.run_debugger(global, caches, scope, this_ptr, &args_expr[0])?;
self.run_debugger(
global,
caches,
scope,
this_ptr.as_deref_mut(),
&args_expr[0],
)?;
// func(x, ...) -> x.func(...)
arg_values.push(Dynamic::UNIT);
args_expr.iter().skip(1).try_for_each(|expr| {
self.get_arg_value(global, caches, scope, this_ptr, expr)
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)
.map(|(value, ..)| arg_values.push(value.flatten()))
})?;
@ -1370,7 +1387,7 @@ impl Engine {
} else {
// func(..., ...) or func(mod::x, ...)
args_expr.iter().try_for_each(|expr| {
self.get_arg_value(global, caches, scope, this_ptr, expr)
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)
.map(|(value, ..)| arg_values.push(value.flatten()))
})?;
args.extend(arg_values.iter_mut());
@ -1441,15 +1458,16 @@ impl Engine {
match func {
#[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => {
let f = f.get_script_fn_def().expect("script-defined function");
Some(func) if func.is_script() => {
let f = func.get_script_fn_def().expect("script-defined function");
let environ = func.get_encapsulated_environ();
let scope = &mut Scope::new();
let mut this_ptr = Dynamic::NULL;
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
auto_restore!(global => move |g| g.source = orig_source);
self.call_script_fn(global, caches, scope, &mut this_ptr, f, args, true, pos)
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
}
Some(f) if f.is_plugin_fn() => {
@ -1546,7 +1564,7 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
expr: &FnCallExpr,
pos: Position,
) -> RhaiResult {
@ -1566,7 +1584,7 @@ impl Engine {
// Short-circuit native unary operator call if under Fast Operators mode
if op_token == Token::Bang && self.fast_operators() && args.len() == 1 {
let mut value = self
.get_arg_value(global, caches, scope, this_ptr, &args[0])?
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
.0
.flatten();
@ -1582,7 +1600,7 @@ impl Engine {
// Short-circuit native binary operator call if under Fast Operators mode
if op_token != NO_TOKEN && self.fast_operators() && args.len() == 2 {
let mut lhs = self
.get_arg_value(global, caches, scope, this_ptr, &args[0])?
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
.0
.flatten();

View File

@ -8,23 +8,64 @@ use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// _(internals)_ Encapsulated AST environment.
/// Exported under the `internals` feature only.
///
/// 1) functions defined within the same AST
/// 2) the stack of imported [modules][crate::Module]
/// 3) global constants
#[derive(Debug, Clone)]
pub struct EncapsulatedEnviron {
/// Functions defined within the same [`AST`][crate::AST].
#[cfg(not(feature = "no_function"))]
pub lib: crate::SharedModule,
/// Imported [modules][crate::Module].
#[cfg(not(feature = "no_module"))]
pub imports: Box<crate::StaticVec<(crate::ImmutableString, crate::SharedModule)>>,
/// Globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub constants: Option<crate::eval::SharedGlobalConstants>,
}
/// _(internals)_ A type encapsulating a function callable by Rhai.
/// Exported under the `internals` feature only.
#[derive(Clone)]
#[non_exhaustive]
pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>, bool),
Pure {
/// Shared function pointer.
func: Shared<FnAny>,
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
has_context: bool,
},
/// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value.
Method(Shared<FnAny>, bool),
Method {
/// Shared function pointer.
func: Shared<FnAny>,
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
has_context: bool,
},
/// An iterator function.
Iterator(Shared<IteratorFn>),
Iterator {
/// Shared function pointer.
func: Shared<IteratorFn>,
},
/// A plugin function,
Plugin(Shared<FnPlugin>),
Plugin {
/// Shared function pointer.
func: Shared<FnPlugin>,
},
/// A script-defined function.
#[cfg(not(feature = "no_function"))]
Script(Shared<crate::ast::ScriptFnDef>),
Script {
/// Shared reference to the [`ScriptFnDef`][crate::ast::ScriptFnDef] function definition.
fn_def: Shared<crate::ast::ScriptFnDef>,
/// Encapsulated environment, if any.
environ: Option<Shared<EncapsulatedEnviron>>,
},
}
impl fmt::Debug for CallableFunction {
@ -32,13 +73,13 @@ impl fmt::Debug for CallableFunction {
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(..) => f.write_str("NativePureFunction"),
Self::Method(..) => f.write_str("NativeMethod"),
Self::Iterator(..) => f.write_str("NativeIterator"),
Self::Plugin(..) => f.write_str("PluginFunction"),
Self::Pure { .. } => f.write_str("NativePureFunction"),
Self::Method { .. } => f.write_str("NativeMethod"),
Self::Iterator { .. } => f.write_str("NativeIterator"),
Self::Plugin { .. } => f.write_str("PluginFunction"),
#[cfg(not(feature = "no_function"))]
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
Self::Script { fn_def, .. } => fmt::Debug::fmt(fn_def, f),
}
}
}
@ -46,13 +87,13 @@ impl fmt::Debug for CallableFunction {
impl fmt::Display for CallableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(..) => f.write_str("NativePureFunction"),
Self::Method(..) => f.write_str("NativeMethod"),
Self::Iterator(..) => f.write_str("NativeIterator"),
Self::Plugin(..) => f.write_str("PluginFunction"),
Self::Pure { .. } => f.write_str("NativePureFunction"),
Self::Method { .. } => f.write_str("NativeMethod"),
Self::Iterator { .. } => f.write_str("NativeIterator"),
Self::Plugin { .. } => f.write_str("PluginFunction"),
#[cfg(not(feature = "no_function"))]
Self::Script(s) => fmt::Display::fmt(s, f),
Self::Script { fn_def, .. } => fmt::Display::fmt(fn_def, f),
}
}
}
@ -63,13 +104,13 @@ impl CallableFunction {
#[must_use]
pub fn is_pure(&self) -> bool {
match self {
Self::Pure(..) => true,
Self::Method(..) | Self::Iterator(..) => false,
Self::Pure { .. } => true,
Self::Method { .. } | Self::Iterator { .. } => false,
Self::Plugin(p) => !p.is_method_call(),
Self::Plugin { func, .. } => !func.is_method_call(),
#[cfg(not(feature = "no_function"))]
Self::Script(..) => false,
Self::Script { .. } => false,
}
}
/// Is this a native Rust method function?
@ -77,13 +118,13 @@ impl CallableFunction {
#[must_use]
pub fn is_method(&self) -> bool {
match self {
Self::Method(..) => true,
Self::Pure(..) | Self::Iterator(..) => false,
Self::Method { .. } => true,
Self::Pure { .. } | Self::Iterator { .. } => false,
Self::Plugin(p) => p.is_method_call(),
Self::Plugin { func, .. } => func.is_method_call(),
#[cfg(not(feature = "no_function"))]
Self::Script(..) => false,
Self::Script { .. } => false,
}
}
/// Is this an iterator function?
@ -91,11 +132,11 @@ impl CallableFunction {
#[must_use]
pub const fn is_iter(&self) -> bool {
match self {
Self::Iterator(..) => true,
Self::Pure(..) | Self::Method(..) | Self::Plugin(..) => false,
Self::Iterator { .. } => true,
Self::Pure { .. } | Self::Method { .. } | Self::Plugin { .. } => false,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => false,
Self::Script { .. } => false,
}
}
/// Is this a script-defined function?
@ -107,8 +148,11 @@ impl CallableFunction {
#[cfg(not(feature = "no_function"))]
match self {
Self::Script(..) => true,
Self::Pure(..) | Self::Method(..) | Self::Iterator(..) | Self::Plugin(..) => false,
Self::Script { .. } => true,
Self::Pure { .. }
| Self::Method { .. }
| Self::Iterator { .. }
| Self::Plugin { .. } => false,
}
}
/// Is this a plugin function?
@ -116,11 +160,11 @@ impl CallableFunction {
#[must_use]
pub const fn is_plugin_fn(&self) -> bool {
match self {
Self::Plugin(..) => true,
Self::Pure(..) | Self::Method(..) | Self::Iterator(..) => false,
Self::Plugin { .. } => true,
Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } => false,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => false,
Self::Script { .. } => false,
}
}
/// Is this a native Rust function?
@ -132,8 +176,11 @@ impl CallableFunction {
#[cfg(not(feature = "no_function"))]
match self {
Self::Pure(..) | Self::Method(..) | Self::Plugin(..) | Self::Iterator(..) => true,
Self::Script(..) => false,
Self::Pure { .. }
| Self::Method { .. }
| Self::Plugin { .. }
| Self::Iterator { .. } => true,
Self::Script { .. } => false,
}
}
/// Is there a [`NativeCallContext`][crate::NativeCallContext] parameter?
@ -141,11 +188,11 @@ impl CallableFunction {
#[must_use]
pub fn has_context(&self) -> bool {
match self {
Self::Pure(.., ctx) | Self::Method(.., ctx) => *ctx,
Self::Plugin(f) => f.has_context(),
Self::Iterator(..) => false,
Self::Pure { has_context, .. } | Self::Method { has_context, .. } => *has_context,
Self::Plugin { func, .. } => func.has_context(),
Self::Iterator { .. } => false,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => false,
Self::Script { .. } => false,
}
}
/// Get the access mode.
@ -157,10 +204,11 @@ impl CallableFunction {
#[cfg(not(feature = "no_function"))]
match self {
Self::Plugin(..) | Self::Pure(..) | Self::Method(..) | Self::Iterator(..) => {
FnAccess::Public
}
Self::Script(f) => f.access,
Self::Plugin { .. }
| Self::Pure { .. }
| Self::Method { .. }
| Self::Iterator { .. } => FnAccess::Public,
Self::Script { fn_def, .. } => fn_def.access,
}
}
/// Get a shared reference to a native Rust function.
@ -168,11 +216,11 @@ impl CallableFunction {
#[must_use]
pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self {
Self::Pure(f, ..) | Self::Method(f, ..) => Some(f),
Self::Iterator(..) | Self::Plugin(..) => None,
Self::Pure { func, .. } | Self::Method { func, .. } => Some(func),
Self::Iterator { .. } | Self::Plugin { .. } => None,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => None,
Self::Script { .. } => None,
}
}
/// Get a shared reference to a script-defined function definition.
@ -183,8 +231,27 @@ impl CallableFunction {
#[must_use]
pub const fn get_script_fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
match self {
Self::Pure(..) | Self::Method(..) | Self::Iterator(..) | Self::Plugin(..) => None,
Self::Script(f) => Some(f),
Self::Pure { .. }
| Self::Method { .. }
| Self::Iterator { .. }
| Self::Plugin { .. } => None,
Self::Script { fn_def, .. } => Some(fn_def),
}
}
/// Get a reference to the shared encapsulated environment of the function definition.
///
/// Not available under `no_function` or `no_module`.
#[inline]
#[must_use]
pub fn get_encapsulated_environ(&self) -> Option<&EncapsulatedEnviron> {
match self {
Self::Pure { .. }
| Self::Method { .. }
| Self::Iterator { .. }
| Self::Plugin { .. } => None,
#[cfg(not(feature = "no_function"))]
Self::Script { environ, .. } => environ.as_deref(),
}
}
/// Get a reference to an iterator function.
@ -192,11 +259,11 @@ impl CallableFunction {
#[must_use]
pub fn get_iter_fn(&self) -> Option<&IteratorFn> {
match self {
Self::Iterator(f) => Some(&**f),
Self::Pure(..) | Self::Method(..) | Self::Plugin(..) => None,
Self::Iterator { func, .. } => Some(&**func),
Self::Pure { .. } | Self::Method { .. } | Self::Plugin { .. } => None,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => None,
Self::Script { .. } => None,
}
}
/// Get a shared reference to a plugin function.
@ -204,11 +271,11 @@ impl CallableFunction {
#[must_use]
pub fn get_plugin_fn(&self) -> Option<&Shared<FnPlugin>> {
match self {
Self::Plugin(f) => Some(f),
Self::Pure(..) | Self::Method(..) | Self::Iterator(..) => None,
Self::Plugin { func, .. } => Some(func),
Self::Pure { .. } | Self::Method { .. } | Self::Iterator { .. } => None,
#[cfg(not(feature = "no_function"))]
Self::Script(..) => None,
Self::Script { .. } => None,
}
}
}
@ -216,29 +283,37 @@ impl CallableFunction {
#[cfg(not(feature = "no_function"))]
impl From<crate::ast::ScriptFnDef> for CallableFunction {
#[inline(always)]
fn from(func: crate::ast::ScriptFnDef) -> Self {
Self::Script(func.into())
fn from(fn_def: crate::ast::ScriptFnDef) -> Self {
Self::Script {
fn_def: fn_def.into(),
environ: None,
}
}
}
#[cfg(not(feature = "no_function"))]
impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction {
#[inline(always)]
fn from(func: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script(func)
fn from(fn_def: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script {
fn_def,
environ: None,
}
}
}
impl<T: PluginFunction + 'static + SendSync> From<T> for CallableFunction {
#[inline(always)]
fn from(func: T) -> Self {
Self::Plugin(Shared::new(func))
Self::Plugin {
func: Shared::new(func),
}
}
}
impl From<Shared<FnPlugin>> for CallableFunction {
#[inline(always)]
fn from(func: Shared<FnPlugin>) -> Self {
Self::Plugin(func)
Self::Plugin { func }
}
}

View File

@ -18,7 +18,7 @@ pub use call::ensure_no_data_race;
#[cfg(not(feature = "no_function"))]
pub use call::is_anonymous_fn;
pub use call::FnCallArgs;
pub use callable_function::CallableFunction;
pub use callable_function::{CallableFunction, EncapsulatedEnviron};
#[cfg(not(feature = "no_function"))]
pub use func::Func;
pub use hashing::{calc_fn_hash, calc_fn_hash_full, calc_var_hash, get_hasher, StraightHashMap};

View File

@ -162,7 +162,7 @@ macro_rules! def_register {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |_, args: &mut FnCallArgs| {
CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types!
check_constant!($abi, $n, fn_name, no_const, args);
@ -174,7 +174,7 @@ macro_rules! def_register {
// Map the result
Ok(Dynamic::from(r))
}), false)
}), has_context: false }
}
}
@ -186,7 +186,7 @@ macro_rules! def_register {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types!
@ -200,7 +200,7 @@ macro_rules! def_register {
// Map the result
Ok(Dynamic::from(r))
}), true)
}), has_context: true }
}
}
@ -213,7 +213,7 @@ macro_rules! def_register {
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |_, args: &mut FnCallArgs| {
CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types!
check_constant!($abi, $n, fn_name, no_const, args);
@ -222,7 +222,7 @@ macro_rules! def_register {
// Call the function with each argument value
self($($arg),*).map(Dynamic::from)
}), false)
}), has_context: false }
}
}
@ -235,7 +235,7 @@ macro_rules! def_register {
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction {
CallableFunction::$abi(Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types!
@ -246,7 +246,7 @@ macro_rules! def_register {
// Call the function with each argument value
self(ctx, $($arg),*).map(Dynamic::from)
}), true)
}), has_context: true }
}
}

View File

@ -4,6 +4,7 @@
use super::call::FnCallArgs;
use crate::ast::ScriptFnDef;
use crate::eval::{Caches, GlobalRuntimeState};
use crate::func::EncapsulatedEnviron;
use crate::{Dynamic, Engine, Position, RhaiResult, Scope, ERR};
use std::mem;
#[cfg(feature = "no_std")]
@ -27,7 +28,8 @@ impl Engine {
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
this_ptr: &mut Dynamic,
mut this_ptr: Option<&mut Dynamic>,
_environ: Option<&EncapsulatedEnviron>,
fn_def: &ScriptFnDef,
args: &mut FnCallArgs,
rewind_scope: bool,
@ -84,12 +86,12 @@ impl Engine {
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();
#[cfg(not(feature = "no_module"))]
let orig_constants = if let Some(ref environ) = fn_def.environ {
let crate::ast::EncapsulatedEnviron {
ref lib,
ref imports,
ref constants,
} = **environ;
let orig_constants = if let Some(environ) = _environ {
let EncapsulatedEnviron {
lib,
imports,
constants,
} = environ;
imports
.iter()
@ -106,12 +108,19 @@ impl Engine {
#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
let node = crate::ast::Stmt::Noop(fn_def.body.position());
self.run_debugger(global, caches, scope, this_ptr, &node)?;
self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), &node)?;
}
// Evaluate the function
let mut _result: RhaiResult = self
.eval_stmt_block(global, caches, scope, this_ptr, &fn_def.body, rewind_scope)
.eval_stmt_block(
global,
caches,
scope,
this_ptr.as_deref_mut(),
&fn_def.body,
rewind_scope,
)
.or_else(|err| match *err {
// Convert return statement to return value
ERR::Return(x, ..) => Ok(x),
@ -124,8 +133,7 @@ impl Engine {
_ => Err(ERR::ErrorInFunctionCall(
fn_def.name.to_string(),
#[cfg(not(feature = "no_module"))]
fn_def
.environ
_environ
.as_deref()
.and_then(|environ| environ.lib.id())
.unwrap_or_else(|| global.source().unwrap_or(""))

View File

@ -88,6 +88,8 @@ use std::prelude::v1::*;
#[macro_use]
mod reify;
#[macro_use]
mod restore;
#[macro_use]
mod types;
mod api;
@ -100,6 +102,8 @@ mod module;
mod optimizer;
pub mod packages;
mod parser;
#[cfg(feature = "serde")]
pub mod serde;
mod tests;
mod tokenizer;
@ -201,6 +205,8 @@ type InclusiveRange = std::ops::RangeInclusive<INT>;
#[allow(deprecated)]
pub use api::build_type::{CustomType, TypeBuilder};
#[cfg(not(feature = "no_custom_syntax"))]
pub use api::custom_syntax::Expression;
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
pub use api::files::{eval_file, run_file};
@ -208,18 +214,18 @@ pub use api::{eval::eval, events::VarDefInfo, run::run};
pub use ast::{FnAccess, AST};
pub use engine::{Engine, OP_CONTAINS, OP_EQUALS};
pub use eval::EvalContext;
pub use func::{NativeCallContext, RegisterNativeFunction};
use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash};
pub use func::{plugin, FuncArgs, NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module};
use restore::RestoreOnDrop;
pub use rhai_codegen::*;
#[cfg(not(feature = "no_time"))]
pub use types::Instant;
pub use types::Position;
pub use types::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Position,
Scope,
};
#[cfg(not(feature = "no_custom_syntax"))]
pub use api::custom_syntax::Expression;
/// _(debugging)_ Module containing types for debugging.
/// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")]
@ -245,15 +251,9 @@ pub use func::Shared;
/// Alias to [`RefCell`][std::cell::RefCell] or [`RwLock`][std::sync::RwLock] depending on the `sync` feature flag.
pub use func::Locked;
use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash};
/// A shared [`Module`].
type SharedModule = Shared<Module>;
pub use rhai_codegen::*;
pub use func::{plugin, FuncArgs};
#[cfg(not(feature = "no_function"))]
pub use func::Func;
@ -294,9 +294,6 @@ pub use module::ModuleResolver;
#[cfg(not(feature = "no_module"))]
pub use module::resolvers as module_resolvers;
#[cfg(feature = "serde")]
pub mod serde;
#[cfg(not(feature = "no_optimize"))]
pub use optimizer::OptimizationLevel;
@ -341,9 +338,7 @@ pub use ast::CustomExpr;
pub use ast::Namespace;
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub use ast::EncapsulatedEnviron;
pub use func::EncapsulatedEnviron;
#[cfg(feature = "internals")]
pub use eval::{Caches, FnResolutionCache, FnResolutionCacheEntry, GlobalRuntimeState};

View File

@ -1,7 +1,7 @@
//! Module defining external-loaded modules for Rhai.
#[cfg(feature = "metadata")]
use crate::api::type_names::format_type;
use crate::api::formatting::format_type;
use crate::ast::FnAccess;
use crate::func::{
shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction,
@ -1221,7 +1221,10 @@ impl Module {
access,
None,
arg_types,
CallableFunction::Method(Shared::new(f), true),
CallableFunction::Method {
func: Shared::new(f),
has_context: true,
},
)
}
@ -2138,31 +2141,22 @@ impl Module {
// The return value is thrown away and not used
let _ = result?;
// Variables with an alias left in the scope become module variables
for (_name, value, mut aliases) in scope {
// It is an error to export function pointers that refer to encapsulated local functions.
//
// Even if the function pointer already links to a scripted function definition, it may
// cross-call other functions inside the module and won't have the full encapsulated
// environment available.
// Encapsulated environment
let environ = Shared::new(crate::func::EncapsulatedEnviron {
#[cfg(not(feature = "no_function"))]
if let Some(fn_ptr) = value.downcast_ref::<crate::FnPtr>() {
if ast.iter_fn_def().any(|f| f.name == fn_ptr.fn_name()) {
return Err(crate::ERR::ErrorMismatchDataType(
String::new(),
if fn_ptr.is_anonymous() {
format!("cannot export closure in variable {_name}")
} else {
format!(
"cannot export function pointer to local function '{}' in variable {_name}",
fn_ptr.fn_name()
)
},
crate::Position::NONE,
)
.into());
}
lib: ast.shared_lib().clone(),
imports: imports.into(),
#[cfg(not(feature = "no_function"))]
constants,
});
// Variables with an alias left in the scope become module variables
for (_name, mut value, mut aliases) in scope {
value.deep_scan(|v| {
if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() {
fn_ptr.set_encapsulated_environ(Some(environ.clone()));
}
});
match aliases.len() {
0 => (),
@ -2183,34 +2177,21 @@ impl Module {
// Non-private functions defined become module functions
#[cfg(not(feature = "no_function"))]
{
let environ = Shared::new(crate::ast::EncapsulatedEnviron {
lib: ast.shared_lib().clone(),
imports: imports.into_boxed_slice(),
constants,
});
ast.shared_lib()
.iter_fn()
.filter(|&f| match f.metadata.access {
ast.iter_fn_def()
.filter(|&f| match f.access {
FnAccess::Public => true,
FnAccess::Private => false,
})
.filter(|&f| f.func.is_script())
.for_each(|f| {
let mut func = f
.func
.get_script_fn_def()
.expect("script-defined function")
.as_ref()
.clone();
let hash = module.set_script_fn(f.clone());
let f = module.functions.as_mut().unwrap().get_mut(&hash).unwrap();
// Encapsulate AST environment
func.environ = Some(environ.clone());
module.set_script_fn(func);
});
match &mut f.func {
CallableFunction::Script { environ: e, .. } => *e = Some(environ.clone()),
_ => (),
}
});
module.id = ast.source_raw().cloned();

View File

@ -54,7 +54,7 @@ struct OptimizerState<'a> {
/// Has the [`AST`] been changed during this pass?
changed: bool,
/// Collection of constants to use for eager function evaluations.
variables: StaticVec<(Identifier, AccessMode, Dynamic)>,
variables: StaticVec<(Identifier, AccessMode, Option<Dynamic>)>,
/// Activate constants propagation?
propagate_constants: bool,
/// An [`Engine`] instance for eager function evaluation.
@ -115,7 +115,12 @@ impl<'a> OptimizerState<'a> {
}
/// Add a new variable to the list.
#[inline(always)]
pub fn push_var(&mut self, name: impl Into<Identifier>, access: AccessMode, value: Dynamic) {
pub fn push_var(
&mut self,
name: impl Into<Identifier>,
access: AccessMode,
value: Option<Dynamic>,
) {
self.variables.push((name.into(), access, value));
}
/// Look up a constant from the list.
@ -129,8 +134,7 @@ impl<'a> OptimizerState<'a> {
if n == name {
return match access {
AccessMode::ReadWrite => None,
AccessMode::ReadOnly if value.is_null() => None,
AccessMode::ReadOnly => Some(value),
AccessMode::ReadOnly => value.as_ref(),
};
}
}
@ -144,7 +148,7 @@ impl<'a> OptimizerState<'a> {
fn_name: &str,
op_token: Token,
arg_values: &mut [Dynamic],
) -> Dynamic {
) -> Option<Dynamic> {
self.engine
.exec_native_fn_call(
&mut self.global,
@ -156,7 +160,8 @@ impl<'a> OptimizerState<'a> {
false,
Position::NONE,
)
.map_or(Dynamic::NULL, |(v, ..)| v)
.ok()
.map(|(v, ..)| v)
}
}
@ -234,13 +239,13 @@ fn optimize_stmt_block(
state.push_var(
x.0.as_str(),
AccessMode::ReadOnly,
x.1.get_literal_value().unwrap_or(Dynamic::NULL),
x.1.get_literal_value(),
);
}
} else {
// Add variables into the state
optimize_expr(&mut x.1, state, false);
state.push_var(x.0.as_str(), AccessMode::ReadWrite, Dynamic::NULL);
state.push_var(x.0.as_str(), AccessMode::ReadWrite, None);
}
}
// Optimize the statement
@ -1190,15 +1195,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::<Option<StaticVec<_>>>().unwrap();
let result = match x.name.as_str() {
KEYWORD_TYPE_OF if arg_values.len() == 1 => state.engine.map_type_name(arg_values[0].type_name()).into(),
KEYWORD_TYPE_OF if arg_values.len() == 1 => Some(state.engine.map_type_name(arg_values[0].type_name()).into()),
#[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Dynamic::FALSE,
crate::engine::KEYWORD_IS_SHARED if arg_values.len() == 1 => Some(Dynamic::FALSE),
_ => state.call_fn_with_constant_arguments(&x.name, x.op_token.clone(), arg_values)
};
if !result.is_null() {
if let Some(r) = result {
state.set_dirty();
*expr = Expr::from_dynamic(result, *pos);
*expr = Expr::from_dynamic(r, *pos);
return;
}
}
@ -1303,15 +1308,15 @@ impl Engine {
// Add constants from global modules
for (name, value) in self.global_modules.iter().rev().flat_map(|m| m.iter_var()) {
state.push_var(name, AccessMode::ReadOnly, value.clone());
state.push_var(name, AccessMode::ReadOnly, Some(value.clone()));
}
// Add constants and variables from the scope
for (name, constant, value) in scope.iter() {
if constant {
state.push_var(name, AccessMode::ReadOnly, value);
state.push_var(name, AccessMode::ReadOnly, Some(value));
} else {
state.push_var(name, AccessMode::ReadWrite, Dynamic::NULL);
state.push_var(name, AccessMode::ReadWrite, None);
}
}
@ -1344,8 +1349,6 @@ impl Engine {
access: fn_def.access,
body: crate::ast::StmtBlock::NONE,
params: fn_def.params.clone(),
#[cfg(not(feature = "no_module"))]
environ: None,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: Box::default(),

View File

@ -72,9 +72,9 @@ macro_rules! gen_arithmetic_functions {
pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
Err(make_err(format!("Integer raised to too large an index: {x} ** {y}")))
Err(make_err(format!("Exponential overflow: {x} ** {y}")))
} else if y < 0 {
Err(make_err(format!("Integer raised to a negative index: {x} ** {y}")))
Err(make_err(format!("Integer raised to a negative power: {x} ** {y}")))
} else {
x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}")))
}
@ -83,32 +83,36 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = "<<", return_raw)]
pub fn shift_left(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
#[rhai_fn(name = "<<")]
pub fn shift_left(x: $arg_type, y: INT) -> $arg_type {
if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
Err(make_err(format!("Left-shift by too many bits: {x} << {y}")))
0
} else if y < 0 {
Err(make_err(format!("Left-shift by a negative number: {x} << {y}")))
shift_right(x, y.checked_abs().unwrap_or(INT::MAX))
} else {
x.checked_shl(y as u32).ok_or_else(|| make_err(format!("Left-shift by too many bits: {x} << {y}")))
x.checked_shl(y as u32).unwrap_or_else(|| 0)
}
} else if y < 0 {
x >> -y
} else {
Ok(x << y)
x << y
}
}
#[rhai_fn(name = ">>", return_raw)]
pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
#[rhai_fn(name = ">>")]
pub fn shift_right(x: $arg_type, y: INT) -> $arg_type {
if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
Err(make_err(format!("Right-shift by too many bits: {x} >> {y}")))
x.wrapping_shr(u32::MAX)
} else if y < 0 {
Err(make_err(format!("Right-shift by a negative number: {x} >> {y}")))
shift_left(x, y.checked_abs().unwrap_or(INT::MAX))
} else {
x.checked_shr(y as u32).ok_or_else(|| make_err(format!("Right-shift by too many bits: {x} >> {y}")))
x.checked_shr(y as u32).unwrap_or_else(|| x.wrapping_shr(u32::MAX))
}
} else if y < 0 {
x << -y
} else {
Ok(x >> y)
x >> y
}
}
#[rhai_fn(name = "&")]

File diff suppressed because it is too large Load Diff

View File

@ -2890,8 +2890,7 @@ impl Engine {
will_shadow,
};
let caches = &mut Caches::new();
let mut this_ptr = Dynamic::NULL;
let context = EvalContext::new(self, global, caches, stack, &mut this_ptr);
let context = EvalContext::new(self, global, caches, stack, None);
match filter(false, info, context) {
Ok(true) => (),
@ -3629,8 +3628,6 @@ impl Engine {
access,
params,
body,
#[cfg(not(feature = "no_module"))]
environ: None,
#[cfg(feature = "metadata")]
comments: comments.into_iter().collect(),
})
@ -3791,8 +3788,6 @@ impl Engine {
access: crate::FnAccess::Public,
params,
body: body.into(),
#[cfg(not(feature = "no_module"))]
environ: None,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: Box::default(),

View File

@ -34,7 +34,7 @@ macro_rules! auto_restore {
auto_restore!($var = $var => $restore);
};
($var:ident = $value:expr => $restore:expr) => {
let $var = &mut *crate::types::RestoreOnDrop::lock($value, $restore);
let $var = &mut *crate::RestoreOnDrop::lock($value, $restore);
};
($var:ident if Some($guard:ident) => $restore:expr) => {
auto_restore!($var = ($var) if Some($guard) => $restore);
@ -42,7 +42,7 @@ macro_rules! auto_restore {
($var:ident = ( $value:expr ) if Some($guard:ident) => $restore:expr) => {
let mut __rx__;
let $var = if let Some($guard) = $guard {
__rx__ = crate::types::RestoreOnDrop::lock($value, $restore);
__rx__ = crate::RestoreOnDrop::lock($value, $restore);
&mut *__rx__
} else {
&mut *$value
@ -54,7 +54,7 @@ macro_rules! auto_restore {
($var:ident = ( $value:expr ) if $guard:expr => $restore:expr) => {
let mut __rx__;
let $var = if $guard {
__rx__ = crate::types::RestoreOnDrop::lock($value, $restore);
__rx__ = crate::RestoreOnDrop::lock($value, $restore);
&mut *__rx__
} else {
&mut *$value

View File

@ -125,8 +125,6 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> {
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
match self.0 .0 {
Union::Null => unreachable!(),
Union::Unit(..) => self.deserialize_unit(visitor),
Union::Bool(..) => self.deserialize_bool(visitor),
Union::Str(..) => self.deserialize_str(visitor),

View File

@ -1,7 +1,7 @@
//! Serialization of functions metadata.
#![cfg(feature = "metadata")]
use crate::api::type_names::format_type;
use crate::api::formatting::format_type;
use crate::module::{calc_native_fn_hash, FuncInfo, ModuleFlags};
use crate::{calc_fn_hash, Engine, FnAccess, SmartString, StaticVec, AST};
use serde::Serialize;

View File

@ -15,8 +15,6 @@ use crate::types::dynamic::Variant;
impl Serialize for Dynamic {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(..) => ser.serialize_unit(),
Union::Bool(x, ..) => ser.serialize_bool(x),
Union::Str(ref s, ..) => ser.serialize_str(s.as_str()),

View File

@ -32,15 +32,27 @@ fn check_struct_sizes() {
if IS_32_BIT { 12 } else { 16 }
);
#[cfg(feature = "internals")]
{
assert_eq!(
size_of::<CallableFunction>(),
if IS_32_BIT { 12 } else { 24 }
);
assert_eq!(
size_of::<module::FuncInfo>(),
if IS_32_BIT { 16 } else { 32 }
);
}
#[cfg(target_pointer_width = "64")]
{
assert_eq!(size_of::<Scope>(), 536);
assert_eq!(
size_of::<FnPtr>(),
if cfg!(feature = "no_function") {
64
} else {
72
} else {
80
}
);
assert_eq!(size_of::<LexError>(), 56);

View File

@ -26,6 +26,8 @@ pub struct TokenizerControlBlock {
/// Global comments.
#[cfg(feature = "metadata")]
pub global_comments: String,
/// Whitespace-compressed version of the script (if any).
pub compressed: Option<String>,
}
impl TokenizerControlBlock {
@ -37,6 +39,7 @@ impl TokenizerControlBlock {
is_within_text: false,
#[cfg(feature = "metadata")]
global_comments: String::new(),
compressed: None,
}
}
}
@ -879,6 +882,8 @@ pub struct TokenizeState {
pub include_comments: bool,
/// Is the current tokenizer position within the text stream of an interpolated string?
pub is_within_text_terminated_by: Option<char>,
/// Last token
pub last_token: Option<SmartString>,
}
/// _(internals)_ Trait that encapsulates a peekable character input stream.
@ -956,6 +961,10 @@ pub fn parse_string_literal(
let mut skip_whitespace_until = 0;
state.is_within_text_terminated_by = Some(termination_char);
state.last_token.as_mut().map(|last| {
last.clear();
last.push(termination_char);
});
loop {
assert!(
@ -985,6 +994,8 @@ pub fn parse_string_literal(
}
};
state.last_token.as_mut().map(|last| last.push(next_char));
// String interpolation?
if allow_interpolation
&& next_char == '$'
@ -1004,6 +1015,10 @@ pub fn parse_string_literal(
// Double wrapper
if stream.peek_next().map_or(false, |c| c == termination_char) {
eat_next(stream, pos);
state
.last_token
.as_mut()
.map(|last| last.push(termination_char));
} else {
state.is_within_text_terminated_by = None;
break;
@ -1060,6 +1075,7 @@ pub fn parse_string_literal(
.get_next()
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?;
state.last_token.as_mut().map(|last| last.push(c));
seq.push(c);
pos.advance();
@ -1240,6 +1256,8 @@ fn get_next_token_inner(
state: &mut TokenizeState,
pos: &mut Position,
) -> Option<(Token, Position)> {
state.last_token.as_mut().map(|last| last.clear());
// Still inside a comment?
if state.comment_level > 0 {
let start_pos = *pos;
@ -1398,6 +1416,8 @@ fn get_next_token_inner(
negated_pos
});
state.last_token.as_mut().map(|last| *last = result.clone());
// Parse number
let token = radix_base.map_or_else(
|| {
@ -1452,14 +1472,14 @@ fn get_next_token_inner(
#[cfg(not(feature = "unicode-xid-ident"))]
('a'..='z' | '_' | 'A'..='Z', ..) => {
return Some(
parse_identifier_token(stream, pos, start_pos, c)
parse_identifier_token(stream, state, pos, start_pos, c)
.unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)),
);
}
#[cfg(feature = "unicode-xid-ident")]
(ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => {
return Some(
parse_identifier_token(stream, pos, start_pos, c)
parse_identifier_token(stream, state, pos, start_pos, c)
.unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)),
);
}
@ -1942,18 +1962,24 @@ fn get_next_token_inner(
/// Get the next token, parsing it as an identifier.
fn parse_identifier_token(
stream: &mut impl InputStream,
state: &mut TokenizeState,
pos: &mut Position,
start_pos: Position,
first_char: char,
) -> Result<(Token, Position), LexError> {
let mut identifier = SmartString::new_const();
identifier.push(first_char);
state.last_token.as_mut().map(|last| {
last.clear();
last.push(first_char);
});
while let Some(next_char) = stream.peek_next() {
match next_char {
x if is_id_continue(x) => {
identifier.push(x);
eat_next(stream, pos);
identifier.push(x);
state.last_token.as_mut().map(|last| last.push(x));
}
_ => break,
}
@ -2129,7 +2155,7 @@ impl<'a> Iterator for TokenIterator<'a> {
type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> {
{
let (within_interpolated, compress_script) = {
let control = &mut *self.state.tokenizer_control.borrow_mut();
if control.is_within_text {
@ -2138,7 +2164,12 @@ impl<'a> Iterator for TokenIterator<'a> {
// Reset it
control.is_within_text = false;
}
}
(
self.state.is_within_text_terminated_by.is_some(),
control.compressed.is_some(),
)
};
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
// {EOF}
@ -2230,6 +2261,49 @@ impl<'a> Iterator for TokenIterator<'a> {
None => token,
};
// Collect the compressed script, if needed
if compress_script {
let control = &mut *self.state.tokenizer_control.borrow_mut();
if let Some(ref mut compressed) = control.compressed {
if !matches!(token, Token::EOF) {
use std::fmt::Write;
let last_token = self.state.last_token.as_ref().unwrap();
let mut buf = SmartString::new_const();
if last_token.is_empty() {
write!(buf, "{token}").unwrap();
} else if within_interpolated
&& matches!(
token,
Token::StringConstant(..) | Token::InterpolatedString(..)
)
{
compressed.push_str(&last_token[1..]);
} else {
buf = last_token.clone();
}
if !buf.is_empty() {
if !compressed.is_empty() {
let prev = compressed.chars().last().unwrap();
let cur = buf.chars().next().unwrap();
if (prev == '_' || is_id_first_alphabetic(prev) || is_id_continue(prev))
&& (cur == '_'
|| is_id_first_alphabetic(cur)
|| is_id_continue(cur))
{
compressed.push(' ');
}
}
compressed.push_str(&buf);
}
}
}
}
Some((token, pos))
}
}
@ -2281,6 +2355,7 @@ impl Engine {
comment_level: 0,
include_comments: false,
is_within_text_terminated_by: None,
last_token: None,
},
pos: Position::new(1, 0),
stream: MultiInputsStream {

View File

@ -57,9 +57,6 @@ pub struct Dynamic(pub(crate) Union);
/// Most variants are boxed to reduce the size.
#[must_use]
pub enum Union {
/// An error value which should not exist.
Null,
/// The Unit value - ().
Unit((), Tag, AccessMode),
/// A boolean value.
@ -187,8 +184,6 @@ impl Dynamic {
#[must_use]
pub const fn tag(&self) -> Tag {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(_, tag, _)
| Union::Bool(_, tag, _)
| Union::Str(_, tag, _)
@ -214,8 +209,6 @@ impl Dynamic {
/// Attach arbitrary data to this [`Dynamic`].
pub fn set_tag(&mut self, value: Tag) -> &mut Self {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(_, ref mut tag, _)
| Union::Bool(_, ref mut tag, _)
| Union::Str(_, ref mut tag, _)
@ -239,12 +232,6 @@ impl Dynamic {
}
self
}
/// Is this [`Dynamic`] null?
#[inline(always)]
#[must_use]
pub(crate) const fn is_null(&self) -> bool {
matches!(self.0, Union::Null)
}
/// Does this [`Dynamic`] hold a variant data type instead of one of the supported system
/// primitive types?
#[inline(always)]
@ -334,8 +321,6 @@ impl Dynamic {
#[must_use]
pub fn type_id(&self) -> TypeId {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(..) => TypeId::of::<()>(),
Union::Bool(..) => TypeId::of::<bool>(),
Union::Str(..) => TypeId::of::<ImmutableString>(),
@ -370,8 +355,6 @@ impl Dynamic {
#[must_use]
pub fn type_name(&self) -> &'static str {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(..) => "()",
Union::Bool(..) => "bool",
Union::Str(..) => "string",
@ -416,8 +399,6 @@ impl Hash for Dynamic {
mem::discriminant(&self.0).hash(state);
match self.0 {
Union::Null => unreachable!(),
Union::Unit(..) => (),
Union::Bool(ref b, ..) => b.hash(state),
Union::Str(ref s, ..) => s.hash(state),
@ -449,8 +430,6 @@ impl Hash for Dynamic {
impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(..) => Ok(()),
Union::Bool(ref v, ..) => fmt::Display::fmt(v, f),
Union::Str(ref v, ..) => fmt::Display::fmt(v, f),
@ -544,8 +523,6 @@ impl fmt::Debug for Dynamic {
#[inline(never)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(ref v, ..) => fmt::Debug::fmt(v, f),
Union::Bool(ref v, ..) => fmt::Debug::fmt(v, f),
Union::Str(ref v, ..) => fmt::Debug::fmt(v, f),
@ -657,8 +634,6 @@ impl Clone for Dynamic {
/// The cloned copy is marked read-write even if the original is read-only.
fn clone(&self) -> Self {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(v, tag, ..) => Self(Union::Unit(v, tag, ReadWrite)),
Union::Bool(v, tag, ..) => Self(Union::Bool(v, tag, ReadWrite)),
Union::Str(ref v, tag, ..) => Self(Union::Str(v.clone(), tag, ReadWrite)),
@ -705,9 +680,6 @@ use std::f32::consts as FloatConstants;
use std::f64::consts as FloatConstants;
impl Dynamic {
/// A [`Dynamic`] containing a `null`.
pub(crate) const NULL: Self = Self(Union::Null);
/// A [`Dynamic`] containing a `()`.
pub const UNIT: Self = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing a `true`.
@ -921,8 +893,6 @@ impl Dynamic {
#[must_use]
pub(crate) const fn access_mode(&self) -> AccessMode {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(.., access)
| Union::Bool(.., access)
| Union::Str(.., access)
@ -948,8 +918,6 @@ impl Dynamic {
/// Set the [`AccessMode`] for this [`Dynamic`].
pub(crate) fn set_access_mode(&mut self, typ: AccessMode) -> &mut Self {
match self.0 {
Union::Null => unreachable!(),
Union::Unit(.., ref mut access)
| Union::Bool(.., ref mut access)
| Union::Str(.., ref mut access)
@ -1138,7 +1106,6 @@ impl Dynamic {
let _access = self.access_mode();
match self.0 {
Union::Null => unreachable!(),
Union::Shared(..) => self,
_ => Self(Union::Shared(
crate::Locked::new(self).into(),
@ -1584,7 +1551,6 @@ impl Dynamic {
}
match self.0 {
Union::Null => unreachable!(),
Union::Variant(ref v, ..) => (***v).as_any().downcast_ref::<T>(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => None,
@ -1683,7 +1649,6 @@ impl Dynamic {
}
match self.0 {
Union::Null => unreachable!(),
Union::Variant(ref mut v, ..) => (***v).as_any_mut().downcast_mut::<T>(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(..) => None,
@ -2084,6 +2049,27 @@ impl Dynamic {
_ => Err(self.type_name()),
}
}
/// Recursively scan for [`Dynamic`] values within this [`Dynamic`] (e.g. items in an array or map),
/// calling a filter function on each.
///
/// Shared values are _NOT_ scanned.
#[inline]
pub fn deep_scan(&mut self, mut filter: impl FnMut(&mut Self)) {
fn scan_inner(value: &mut Dynamic, filter: &mut impl FnMut(&mut Dynamic)) {
match &mut value.0 {
#[cfg(not(feature = "no_index"))]
Union::Array(a, ..) => a.iter_mut().for_each(|v| scan_inner(v, filter)),
#[cfg(not(feature = "no_object"))]
Union::Map(m, ..) => m.values_mut().for_each(|v| scan_inner(v, filter)),
Union::FnPtr(f, ..) => f.iter_curry_mut().for_each(|v| scan_inner(v, filter)),
_ => (),
}
}
filter(self);
scan_inner(self, &mut filter);
}
}
impl From<()> for Dynamic {

View File

@ -1,11 +1,12 @@
//! The `FnPtr` type.
use crate::eval::GlobalRuntimeState;
use crate::func::EncapsulatedEnviron;
use crate::tokenizer::is_valid_function_name;
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError,
RhaiResult, RhaiResultOf, StaticVec, AST, ERR,
RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -13,8 +14,9 @@ use std::{
any::{type_name, TypeId},
convert::{TryFrom, TryInto},
fmt,
hash::Hash,
hash::{Hash, Hasher},
mem,
ops::{Index, IndexMut},
};
/// A general function pointer, which may carry additional (i.e. curried) argument values
@ -23,22 +25,23 @@ use std::{
pub struct FnPtr {
name: ImmutableString,
curry: StaticVec<Dynamic>,
environ: Option<Shared<EncapsulatedEnviron>>,
#[cfg(not(feature = "no_function"))]
fn_def: Option<crate::Shared<crate::ast::ScriptFnDef>>,
fn_def: Option<Shared<crate::ast::ScriptFnDef>>,
}
impl Hash for FnPtr {
#[inline(always)]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.curry.hash(state);
// Hash the shared [`EncapsulatedEnviron`] by hashing its shared pointer.
self.environ.as_ref().map(|e| Shared::as_ptr(e)).hash(state);
// Hash the linked [`ScriptFnDef`][crate::ast::ScriptFnDef] by hashing its shared pointer.
#[cfg(not(feature = "no_function"))]
self.fn_def
.as_ref()
.map(|f| crate::Shared::as_ptr(f))
.hash(state);
self.fn_def.as_ref().map(|f| Shared::as_ptr(f)).hash(state);
}
}
@ -75,6 +78,7 @@ impl FnPtr {
Self {
name: name.into(),
curry,
environ: None,
#[cfg(not(feature = "no_function"))]
fn_def: None,
}
@ -100,16 +104,23 @@ impl FnPtr {
) -> (
ImmutableString,
StaticVec<Dynamic>,
Option<crate::Shared<crate::ast::ScriptFnDef>>,
Option<Shared<EncapsulatedEnviron>>,
Option<Shared<crate::ast::ScriptFnDef>>,
) {
(self.name, self.curry, self.fn_def)
(self.name, self.curry, self.environ, self.fn_def)
}
/// Get the underlying data of the function pointer.
#[cfg(feature = "no_function")]
#[inline(always)]
#[must_use]
pub(crate) fn take_data(self) -> (ImmutableString, StaticVec<Dynamic>) {
(self.name, self.curry)
pub(crate) fn take_data(
self,
) -> (
ImmutableString,
StaticVec<Dynamic>,
Option<Shared<EncapsulatedEnviron>>,
) {
(self.name, self.curry, self.environ)
}
/// Get the curried arguments.
#[inline(always)]
@ -117,6 +128,16 @@ impl FnPtr {
pub fn curry(&self) -> &[Dynamic] {
self.curry.as_ref()
}
/// Iterate the curried arguments.
#[inline(always)]
pub fn iter_curry(&self) -> impl Iterator<Item = &Dynamic> {
self.curry.iter()
}
/// Mutably-iterate the curried arguments.
#[inline(always)]
pub fn iter_curry_mut(&mut self) -> impl Iterator<Item = &mut Dynamic> {
self.curry.iter_mut()
}
/// Add a new curried argument.
#[inline(always)]
pub fn add_curry(&mut self, value: Dynamic) -> &mut Self {
@ -183,7 +204,7 @@ impl FnPtr {
args: impl FuncArgs,
) -> RhaiResultOf<T> {
let _ast = ast;
let mut arg_values = crate::StaticVec::new_const();
let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);
let global = &mut GlobalRuntimeState::new(engine);
@ -219,7 +240,7 @@ impl FnPtr {
context: &NativeCallContext,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
let mut arg_values = crate::StaticVec::new_const();
let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);
self.call_raw(context, None, arg_values).and_then(|result| {
@ -277,19 +298,20 @@ impl FnPtr {
// Linked to scripted function?
#[cfg(not(feature = "no_function"))]
if let Some(fn_def) = self.fn_def() {
if let Some(ref fn_def) = self.fn_def {
if fn_def.params.len() == args.len() {
let global = &mut context.global_runtime_state().clone();
global.level += 1;
let caches = &mut crate::eval::Caches::new();
let mut null_ptr = Dynamic::NULL;
let mut this_ptr = this_ptr;
return context.engine().call_script_fn(
global,
caches,
&mut crate::Scope::new(),
this_ptr.unwrap_or(&mut null_ptr),
this_ptr.as_deref_mut(),
self.encapsulated_environ(),
&fn_def,
args,
true,
@ -306,22 +328,119 @@ impl FnPtr {
context.call_fn_raw(self.fn_name(), is_method, is_method, args)
}
/// Get a reference to the [encapsulated environment][EncapsulatedEnviron].
#[inline(always)]
#[must_use]
#[allow(dead_code)]
pub(crate) fn encapsulated_environ(&self) -> Option<&EncapsulatedEnviron> {
self.environ.as_deref()
}
/// Set a reference to the [encapsulated environment][EncapsulatedEnviron].
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn set_encapsulated_environ(
&mut self,
value: Option<impl Into<Shared<EncapsulatedEnviron>>>,
) {
self.environ = value.map(Into::into);
}
/// Get a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef].
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub(crate) fn fn_def(&self) -> Option<&crate::Shared<crate::ast::ScriptFnDef>> {
self.fn_def.as_ref()
pub(crate) fn fn_def(&self) -> Option<&crate::ast::ScriptFnDef> {
self.fn_def.as_deref()
}
/// Set a reference to the linked [`ScriptFnDef`][crate::ast::ScriptFnDef].
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub(crate) fn set_fn_def(
&mut self,
value: Option<impl Into<crate::Shared<crate::ast::ScriptFnDef>>>,
) {
pub(crate) fn set_fn_def(&mut self, value: Option<impl Into<Shared<crate::ast::ScriptFnDef>>>) {
self.fn_def = value.map(Into::into);
}
/// Make a call to a function pointer with either a specified number of arguments, or with extra
/// arguments attached.
///
/// This is useful for calling predicate closures within an iteration loop where the extra argument
/// is the current element's index.
///
/// If the function pointer is linked to a scripted function definition, use the appropriate number
/// of arguments to call it directly (one version attaches extra arguments).
#[cfg(not(feature = "internals"))]
#[inline(always)]
pub(crate) fn call_raw_with_extra_args<const N: usize, const E: usize>(
&self,
fn_name: &str,
ctx: &NativeCallContext,
this_ptr: Option<&mut Dynamic>,
items: [Dynamic; N],
extras: [Dynamic; E],
) -> RhaiResult {
self._call_with_extra_args(fn_name, ctx, this_ptr, items, extras)
}
/// _(internals)_ Make a call to a function pointer with either a specified number of arguments,
/// or with extra arguments attached.
/// Exported under the `internals` feature only.
///
/// This is useful for calling predicate closures within an iteration loop where the extra
/// argument is the current element's index.
///
/// If the function pointer is linked to a scripted function definition, use the appropriate
/// number of arguments to call it directly (one version attaches extra arguments).
#[cfg(feature = "internals")]
#[inline(always)]
pub fn call_raw_with_extra_args<const N: usize, const E: usize>(
&self,
fn_name: &str,
ctx: &NativeCallContext,
this_ptr: Option<&mut Dynamic>,
items: [Dynamic; N],
extras: [Dynamic; E],
) -> RhaiResult {
self._call_with_extra_args(fn_name, ctx, this_ptr, items, extras)
}
/// Make a call to a function pointer with either a specified number of arguments, or with extra
/// arguments attached.
fn _call_with_extra_args<const N: usize, const E: usize>(
&self,
fn_name: &str,
ctx: &NativeCallContext,
mut this_ptr: Option<&mut Dynamic>,
items: [Dynamic; N],
extras: [Dynamic; E],
) -> RhaiResult {
#[cfg(not(feature = "no_function"))]
if let Some(arity) = self.fn_def().map(|f| f.params.len()) {
if arity == N {
return self.call_raw(&ctx, None, items);
}
if arity == N + E {
let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len());
items2.extend(IntoIterator::into_iter(items));
items2.extend(IntoIterator::into_iter(extras));
return self.call_raw(&ctx, this_ptr, items2);
}
}
self.call_raw(&ctx, this_ptr.as_deref_mut(), items.clone())
.or_else(|err| match *err {
ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => {
let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len());
items2.extend(IntoIterator::into_iter(items));
items2.extend(IntoIterator::into_iter(extras));
self.call_raw(&ctx, this_ptr, items2)
}
_ => Err(err),
})
.map_err(|err| {
Box::new(ERR::ErrorInFunctionCall(
fn_name.to_string(),
ctx.source().unwrap_or("").to_string(),
err,
Position::NONE,
))
})
}
}
impl fmt::Display for FnPtr {
@ -339,6 +458,7 @@ impl TryFrom<ImmutableString> for FnPtr {
Ok(Self {
name: value,
curry: StaticVec::new_const(),
environ: None,
#[cfg(not(feature = "no_function"))]
fn_def: None,
})
@ -349,7 +469,7 @@ impl TryFrom<ImmutableString> for FnPtr {
}
#[cfg(not(feature = "no_function"))]
impl<T: Into<crate::Shared<crate::ast::ScriptFnDef>>> From<T> for FnPtr {
impl<T: Into<Shared<crate::ast::ScriptFnDef>>> From<T> for FnPtr {
#[inline(always)]
fn from(value: T) -> Self {
let fn_def = value.into();
@ -357,7 +477,24 @@ impl<T: Into<crate::Shared<crate::ast::ScriptFnDef>>> From<T> for FnPtr {
Self {
name: fn_def.name.clone(),
curry: StaticVec::new_const(),
environ: None,
fn_def: Some(fn_def),
}
}
}
impl Index<usize> for FnPtr {
type Output = Dynamic;
#[inline(always)]
fn index(&self, index: usize) -> &Self::Output {
self.curry.index(index)
}
}
impl IndexMut<usize> for FnPtr {
#[inline(always)]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.curry.index_mut(index)
}
}

View File

@ -1,8 +1,5 @@
//! Module defining Rhai data types.
#[macro_use]
pub mod restore;
pub mod bloom_filter;
pub mod custom_types;
pub mod dynamic;
@ -35,6 +32,5 @@ pub use position::{Position, Span};
#[cfg(feature = "no_position")]
pub use position_none::{Position, Span};
pub use restore::RestoreOnDrop;
pub use scope::Scope;
pub use variant::Variant;

36444
t.txt

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,9 @@ fn test_binary_ops() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let x = 10; x %= 4; x")?, 2);
assert_eq!(engine.eval::<INT>("let x = 10; x **= 4; x")?, 10000);
assert_eq!(engine.eval::<INT>("let x = 10; x <<= 4; x")?, 160);
assert_eq!(engine.eval::<INT>("let x = 10; x <<= -1; x")?, 5);
assert_eq!(engine.eval::<INT>("let x = 10; x >>= 4; x")?, 0);
assert_eq!(engine.eval::<INT>("let x = 10; x >>= -2; x")?, 40);
assert_eq!(engine.eval::<INT>("let x = 10; x &= 4; x")?, 0);
assert_eq!(engine.eval::<INT>("let x = 10; x |= 4; x")?, 14);
assert_eq!(engine.eval::<INT>("let x = 10; x ^= 4; x")?, 14);