commit
737c0aca44
@ -4,6 +4,8 @@ Rhai Release Notes
|
|||||||
Version 0.20.0
|
Version 0.20.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
This version adds a variable resolver with the ability to short-circuit variable access.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -17,6 +19,8 @@ Breaking changes
|
|||||||
* `rhai::ser` and `rhai::de` namespaces are merged into `rhai::serde`.
|
* `rhai::ser` and `rhai::de` namespaces are merged into `rhai::serde`.
|
||||||
* New reserved symbols: `++`, `--`, `..`, `...`.
|
* New reserved symbols: `++`, `--`, `..`, `...`.
|
||||||
* Callback signature for custom syntax implementation function is changed to allow for more flexibility.
|
* Callback signature for custom syntax implementation function is changed to allow for more flexibility.
|
||||||
|
* Default call stack depth for `debug` builds is reduced to 12 (from 16).
|
||||||
|
* Precedence for `~` and `%` is raised.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
@ -24,15 +28,15 @@ New features
|
|||||||
* New `Engine::on_var` to register a _variable resolver_.
|
* New `Engine::on_var` to register a _variable resolver_.
|
||||||
* `const` statements can now take any expression (or none at all) instead of only constant values.
|
* `const` statements can now take any expression (or none at all) instead of only constant values.
|
||||||
* `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded).
|
* `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded).
|
||||||
* Added `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined.
|
* `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined.
|
||||||
* `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value.
|
* `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value.
|
||||||
* `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one.
|
* `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one.
|
||||||
|
* `map`, `filter`, `reduce`, `reduce_rev`, `some`, `all`, `splice` and `sort` functions for arrays.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* Many one-liners and few-liners are now marked `#[inline]` or `[inline(always)]`, just in case it helps when LTO is not turned on.
|
* Many one-liners and few-liners are now marked `#[inline]` or `[inline(always)]`, just in case it helps when LTO is not turned on.
|
||||||
* Default call stack depth for `debug` builds is reduced to 12 (from 16).
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.19.0
|
Version 0.19.0
|
||||||
|
@ -17,6 +17,8 @@ Easy
|
|||||||
* Very few additional dependencies - right now only [`smallvec`](https://crates.io/crates/smallvec/) plus crates for procedural macros;
|
* Very few additional dependencies - right now only [`smallvec`](https://crates.io/crates/smallvec/) plus crates for procedural macros;
|
||||||
for [`no-std`] and `WASM` builds, a number of additional dependencies are pulled in to provide for missing functionalities.
|
for [`no-std`] and `WASM` builds, a number of additional dependencies are pulled in to provide for missing functionalities.
|
||||||
|
|
||||||
|
* [Plugins] system powered by procedural macros simplifies custom API development.
|
||||||
|
|
||||||
Fast
|
Fast
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ Dynamic
|
|||||||
|
|
||||||
* Some support for [object-oriented programming (OOP)][OOP].
|
* Some support for [object-oriented programming (OOP)][OOP].
|
||||||
|
|
||||||
|
* Hook into variables access via [variable resolver].
|
||||||
|
|
||||||
Safe
|
Safe
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -66,9 +70,6 @@ Flexible
|
|||||||
|
|
||||||
* Supports [most build targets](targets.md) including `no-std` and [WASM].
|
* Supports [most build targets](targets.md) including `no-std` and [WASM].
|
||||||
|
|
||||||
* [Plugins] system powered by procedural macros simplifies custom API development.
|
|
||||||
|
|
||||||
* Surgically [disable keywords and operators] to restrict the language.
|
* Surgically [disable keywords and operators] to restrict the language.
|
||||||
|
|
||||||
* Use as a [DSL] by [disabling keywords/operators][disable keywords and operators], [custom operators]
|
* Use as a [DSL] by defining [custom operators] and/or extending the language with [custom syntax].
|
||||||
and extending the language with [custom syntax].
|
|
||||||
|
@ -10,7 +10,7 @@ It doesn't attempt to be a new language. For example:
|
|||||||
|
|
||||||
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
||||||
|
|
||||||
* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
|
* No structures/records/tuples - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
|
||||||
|
|
||||||
There is, however, a built-in [object map] type which is adequate for most uses.
|
There is, however, a built-in [object map] type which is adequate for most uses.
|
||||||
It is possible to simulate [object-oriented programming (OOP)][OOP] by storing [function pointers]
|
It is possible to simulate [object-oriented programming (OOP)][OOP] by storing [function pointers]
|
||||||
@ -32,7 +32,7 @@ It doesn't attempt to be a new language. For example:
|
|||||||
integrate with native Rust applications.
|
integrate with native Rust applications.
|
||||||
|
|
||||||
* No formal language grammar - Rhai uses a hand-coded lexer, a hand-coded top-down recursive-descent parser
|
* No formal language grammar - Rhai uses a hand-coded lexer, a hand-coded top-down recursive-descent parser
|
||||||
for statements and a Pratt parser for expressions.
|
for statements, and a hand-coded Pratt parser for expressions.
|
||||||
|
|
||||||
This lack of formalism allows the parser itself to be exposed as a service in order to support
|
This lack of formalism allows the parser itself to be exposed as a service in order to support
|
||||||
[disabling keywords/operators][disable keywords and operators], adding [custom operators],
|
[disabling keywords/operators][disable keywords and operators], adding [custom operators],
|
||||||
@ -43,8 +43,8 @@ Do Not Write The Next 4D VR Game in Rhai
|
|||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting
|
Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting
|
||||||
advanced language features such as classes, inheritance, first-class functions, closures,
|
advanced language features such as classes, inheritance, interfaces, generics,
|
||||||
concurrency, byte-codes VM, JIT etc.
|
first-class functions/closures, pattern matching, concurrency, byte-codes VM, JIT etc.
|
||||||
|
|
||||||
Avoid the temptation to write full-fledge application logic entirely in Rhai -
|
Avoid the temptation to write full-fledge application logic entirely in Rhai -
|
||||||
that use case is best fulfilled by more complete languages such as JavaScript or Lua.
|
that use case is best fulfilled by more complete languages such as JavaScript or Lua.
|
||||||
|
@ -95,7 +95,8 @@ The following _precedence table_ show the built-in precedence of standard Rhai o
|
|||||||
| Comparisons | `>`, `>=`, `<`, `<=` | 110 |
|
| Comparisons | `>`, `>=`, `<`, `<=` | 110 |
|
||||||
| | `in` | 130 |
|
| | `in` | 130 |
|
||||||
| Arithmetic | `+`, `-` | 150 |
|
| Arithmetic | `+`, `-` | 150 |
|
||||||
| Arithmetic | `*`, `/`, `~`, `%` | 180 |
|
| Arithmetic | `*`, `/` | 180 |
|
||||||
|
| Arithmetic | `~`, `%` | 190 |
|
||||||
| Bit-shifts | `<<`, `>>` | 210 |
|
| Bit-shifts | `<<`, `>>` | 210 |
|
||||||
| Object | `.` _(binds to right)_ | 240 |
|
| Object | `.` _(binds to right)_ | 240 |
|
||||||
| _Others_ | | 0 |
|
| _Others_ | | 0 |
|
||||||
|
@ -10,7 +10,7 @@ searches the [`Scope`] that is passed into the `Engine::eval` call.
|
|||||||
There is a built-in facility for advanced users to _hook_ into the variable
|
There is a built-in facility for advanced users to _hook_ into the variable
|
||||||
resolution service and to override its default behavior.
|
resolution service and to override its default behavior.
|
||||||
|
|
||||||
To do so, provide a closure to the [`Engine`] via the [`Engine::on_var`] method:
|
To do so, provide a closure to the [`Engine`] via the `Engine::on_var` method:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
@ -45,6 +45,18 @@ a variable resolver. Then these variables can be assigned to and their updated v
|
|||||||
the script is evaluated.
|
the script is evaluated.
|
||||||
|
|
||||||
|
|
||||||
|
Benefits of Using a Variable Resolver
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
1. Avoid having to maintain a custom [`Scope`] with all variables regardless of need (because a script may not use them all).
|
||||||
|
|
||||||
|
2. _Short-circuit_ variable access, essentially overriding standard behavior.
|
||||||
|
|
||||||
|
3. _Lazy-load_ variables when they are accessed, not up-front. This benefits when the number of variables is very large, when they are timing-dependent, or when they are expensive to load.
|
||||||
|
|
||||||
|
4. Rename system variables on a script-by-script basis without having to construct different [`Scope`]'s.
|
||||||
|
|
||||||
|
|
||||||
Function Signature
|
Function Signature
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@ -68,5 +80,13 @@ where:
|
|||||||
* `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions.
|
* `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions.
|
||||||
* `context.call_level(): usize` - the current nesting level of function calls.
|
* `context.call_level(): usize` - the current nesting level of function calls.
|
||||||
|
|
||||||
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where `Ok(None)` indicates that the normal
|
### Return Value
|
||||||
variable resolution process should continue.
|
|
||||||
|
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where:
|
||||||
|
|
||||||
|
* `Ok(None)` - normal variable resolution process should continue, meaning to continue searching through the [`Scope`].
|
||||||
|
|
||||||
|
* `Ok(Some(Dynamic))` - wrapped [`Dynamic`] is taken as the value of the variable, which is treated as a constant.
|
||||||
|
|
||||||
|
* `Err(Box<EvalAltResult>)` - error is reflected back to the [`Engine`].
|
||||||
|
Normally this is `EvalAltResult::ErrorVariableNotFound` to indicate that the variable does not exist, but it can be any error.
|
||||||
|
@ -31,7 +31,7 @@ Built-in Functions
|
|||||||
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
|
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
|
||||||
|
|
||||||
| Function | Parameter(s) | Description |
|
| Function | Parameter(s) | Description |
|
||||||
| ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `push` | element to insert | inserts an element at the end |
|
| `push` | element to insert | inserts an element at the end |
|
||||||
| `append` | array to append | concatenates the second array to the end of the first |
|
| `append` | array to append | concatenates the second array to the end of the first |
|
||||||
| `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
|
| `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
|
||||||
@ -40,12 +40,20 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but
|
|||||||
| `insert` | 1) element to insert<br/>2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index |
|
| `insert` | 1) element to insert<br/>2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index |
|
||||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) |
|
||||||
| `reverse` | _none_ | reverses the array |
|
| `reverse` | _none_ | reverses the array |
|
||||||
| `len` method and property | _none_ | returns the number of elements |
|
| `len` method and property | _none_ | returns the number of elements |
|
||||||
| `pad` | 1) target length<br/>2) element to pad | pads the array with an element to at least a specified length |
|
| `pad` | 1) target length<br/>2) element to pad | pads the array with an element to at least a specified length |
|
||||||
| `clear` | _none_ | empties the array |
|
| `clear` | _none_ | empties the array |
|
||||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||||
|
| `splice` | 1) start position (beginning if <= 0, end if >= length),<br/>2) number of items to remove (none if <= 0),<br/>3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
|
||||||
|
| `filter` | [function pointer] to predicate (can be a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
|
||||||
|
| `map` | [function pointer] to conversion function (can be a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
|
||||||
|
| `reduce` | [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items accumulated by the accumulator function:<br/>1st parameter: accumulated value ([`()`] initially),<br/>2nd parameter: array item,<br/>3rd parameter: offset index (optional) |
|
||||||
|
| `reduce_rev` | [function pointer] to accumulator function (can be a [closure]) | constructs a new array with all items (in reverse order) accumulated by the accumulator function:<br/>1st parameter: accumulated value ([`()`] initially),<br/>2nd parameter: array item,<br/>3rd parameter: offset index (optional) |
|
||||||
|
| `some` | [function pointer] to predicate (can be a [closure]) | returns `true` if any item returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
|
||||||
|
| `all` | [function pointer] to predicate (can be a [closure]) | returns `true` if all item returns `true` when called with the predicate function:<br/>1st parameter: array item,<br/>2nd parameter: offset index (optional) |
|
||||||
|
| `sort` | [function pointer] to a comparison function (can be a [closure]) | sorts the array with a comparison function:<br/>1st parameter: first item,<br/>2nd parameter: second item |
|
||||||
|
|
||||||
|
|
||||||
Use Custom Types With Arrays
|
Use Custom Types With Arrays
|
||||||
@ -136,4 +144,48 @@ y.len == 5;
|
|||||||
y.clear(); // empty the array
|
y.clear(); // empty the array
|
||||||
|
|
||||||
y.len == 0;
|
y.len == 0;
|
||||||
|
|
||||||
|
let a = [42, 123, 99];
|
||||||
|
|
||||||
|
a.map(|v| v + 1); // [43, 124, 100]
|
||||||
|
|
||||||
|
a.map(|v, i| v + i); // [42, 124, 101]
|
||||||
|
|
||||||
|
a.filter(|v| v > 50); // [123, 99]
|
||||||
|
|
||||||
|
a.filter(|v, i| i == 1); // [123]
|
||||||
|
|
||||||
|
a.reduce(|sum, v| {
|
||||||
|
// Detect the initial value of '()'
|
||||||
|
if sum.type_of() == "()" { v } else { sum + v }
|
||||||
|
) == 264;
|
||||||
|
|
||||||
|
a.reduce(|sum, v, i| {
|
||||||
|
if i == 0 { v } else { sum + v }
|
||||||
|
) == 264;
|
||||||
|
|
||||||
|
a.reduce_rev(|sum, v| {
|
||||||
|
// Detect the initial value of '()'
|
||||||
|
if sum.type_of() == "()" { v } else { sum + v }
|
||||||
|
) == 264;
|
||||||
|
|
||||||
|
a.reduce_rev(|sum, v, i| {
|
||||||
|
if i == 0 { v } else { sum + v }
|
||||||
|
) == 264;
|
||||||
|
|
||||||
|
a.some(|v| v > 50) == true;
|
||||||
|
|
||||||
|
a.some(|v, i| v < i) == false;
|
||||||
|
|
||||||
|
a.all(|v| v > 50) == false;
|
||||||
|
|
||||||
|
a.all(|v, i| v > i) == true;
|
||||||
|
|
||||||
|
a.splice(1, 1, [1, 3, 2]);
|
||||||
|
|
||||||
|
a == [42, 1, 3, 2, 99];
|
||||||
|
|
||||||
|
a.sort(|x, y| x - y);
|
||||||
|
|
||||||
|
a == [1, 2, 3, 42, 99];
|
||||||
```
|
```
|
||||||
|
@ -43,6 +43,12 @@ let f = Fn("foo");
|
|||||||
call!(f, 41) == 42; // must use function-call style
|
call!(f, 41) == 42; // must use function-call style
|
||||||
|
|
||||||
f.call!(41); // <- syntax error: capturing is not allowed in method-call style
|
f.call!(41); // <- syntax error: capturing is not allowed in method-call style
|
||||||
|
|
||||||
|
// Capturing is not available for module functions
|
||||||
|
|
||||||
|
import "hello" as h;
|
||||||
|
|
||||||
|
h::greet!(); // <- syntax error: capturing is not allowed in namespace-qualified calls
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ Implementation
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
// Create shared data provider.
|
// Create shared data provider.
|
||||||
// Assume that Provider::get(&str) -> Option<value> gets a system constant.
|
// Assume that SystemValuesProvider::get(&str) -> Option<value> gets a value.
|
||||||
let provider: Arc<Provider> = get_data_provider();
|
let provider = Arc::new(SystemValuesProvider::new());
|
||||||
|
|
||||||
// Clone the shared provider
|
// Clone the shared provider
|
||||||
let db = provider.clone();
|
let db = provider.clone();
|
||||||
|
@ -37,6 +37,7 @@ use crate::stdlib::{
|
|||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fmt, format,
|
fmt, format,
|
||||||
iter::{empty, once},
|
iter::{empty, once},
|
||||||
|
num::NonZeroUsize,
|
||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
@ -250,8 +251,8 @@ impl<'a> Target<'a> {
|
|||||||
Self::LockGuard(_) => (),
|
Self::LockGuard(_) => (),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::StringChar(_, _, ch) => {
|
Self::StringChar(_, _, ch) => {
|
||||||
let new_val = ch.clone();
|
let char_value = ch.clone();
|
||||||
self.set_value((new_val, Position::none()), Position::none())
|
self.set_value((char_value, Position::none()), Position::none())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,10 +285,9 @@ impl<'a> Target<'a> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut chars = s.chars().collect::<StaticVec<_>>();
|
let mut chars = s.chars().collect::<StaticVec<_>>();
|
||||||
let ch = chars[*index];
|
|
||||||
|
|
||||||
// See if changed - if so, update the String
|
// See if changed - if so, update the String
|
||||||
if ch != new_ch {
|
if chars[*index] != new_ch {
|
||||||
chars[*index] = new_ch;
|
chars[*index] = new_ch;
|
||||||
*s = chars.iter().collect::<String>().into();
|
*s = chars.iter().collect::<String>().into();
|
||||||
}
|
}
|
||||||
@ -502,6 +502,13 @@ pub fn make_setter(id: &str) -> String {
|
|||||||
format!("{}{}", FN_SET, id)
|
format!("{}{}", FN_SET, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this function an anonymous function?
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_anonymous_fn(fn_name: &str) -> bool {
|
||||||
|
fn_name.starts_with(FN_ANONYMOUS)
|
||||||
|
}
|
||||||
|
|
||||||
/// Print/debug to stdout
|
/// Print/debug to stdout
|
||||||
fn default_print(_s: &str) {
|
fn default_print(_s: &str) {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
@ -520,13 +527,13 @@ pub fn search_imports<'s>(
|
|||||||
|
|
||||||
// Qualified - check if the root module is directly indexed
|
// Qualified - check if the root module is directly indexed
|
||||||
let index = if state.always_search {
|
let index = if state.always_search {
|
||||||
None
|
0
|
||||||
} else {
|
} else {
|
||||||
modules.index()
|
modules.index().map_or(0, NonZeroUsize::get)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(if let Some(index) = index {
|
Ok(if index > 0 {
|
||||||
let offset = mods.len() - index.get();
|
let offset = mods.len() - index;
|
||||||
&mods.get(offset).unwrap().1
|
&mods.get(offset).unwrap().1
|
||||||
} else {
|
} else {
|
||||||
mods.iter()
|
mods.iter()
|
||||||
@ -548,13 +555,13 @@ pub fn search_imports_mut<'s>(
|
|||||||
|
|
||||||
// Qualified - check if the root module is directly indexed
|
// Qualified - check if the root module is directly indexed
|
||||||
let index = if state.always_search {
|
let index = if state.always_search {
|
||||||
None
|
0
|
||||||
} else {
|
} else {
|
||||||
modules.index()
|
modules.index().map_or(0, NonZeroUsize::get)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(if let Some(index) = index {
|
Ok(if index > 0 {
|
||||||
let offset = mods.len() - index.get();
|
let offset = mods.len() - index;
|
||||||
&mut mods.get_mut(offset).unwrap().1
|
&mut mods.get_mut(offset).unwrap().1
|
||||||
} else {
|
} else {
|
||||||
mods.iter_mut()
|
mods.iter_mut()
|
||||||
@ -684,18 +691,14 @@ impl Engine {
|
|||||||
// Qualified variable
|
// Qualified variable
|
||||||
((name, pos), Some(modules), hash_var, _) => {
|
((name, pos), Some(modules), hash_var, _) => {
|
||||||
let module = search_imports_mut(mods, state, modules)?;
|
let module = search_imports_mut(mods, state, modules)?;
|
||||||
let target =
|
let target = module.get_qualified_var_mut(*hash_var).map_err(|mut err| {
|
||||||
module
|
match *err {
|
||||||
.get_qualified_var_mut(*hash_var)
|
EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => {
|
||||||
.map_err(|err| match *err {
|
*err_name = format!("{}{}", modules, name);
|
||||||
EvalAltResult::ErrorVariableNotFound(_, _) => {
|
|
||||||
EvalAltResult::ErrorVariableNotFound(
|
|
||||||
format!("{}{}", modules, name),
|
|
||||||
*pos,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
_ => err.fill_position(*pos),
|
_ => (),
|
||||||
|
}
|
||||||
|
err.fill_position(*pos)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Module variables are constant
|
// Module variables are constant
|
||||||
@ -733,7 +736,11 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if it is directly indexed
|
// Check if it is directly indexed
|
||||||
let index = if state.always_search { None } else { *index };
|
let index = if state.always_search {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
index.map_or(0, NonZeroUsize::get)
|
||||||
|
};
|
||||||
|
|
||||||
// Check the variable resolver, if any
|
// Check the variable resolver, if any
|
||||||
if let Some(ref resolve_var) = self.resolve_var {
|
if let Some(ref resolve_var) = self.resolve_var {
|
||||||
@ -745,15 +752,15 @@ impl Engine {
|
|||||||
this_ptr,
|
this_ptr,
|
||||||
level: 0,
|
level: 0,
|
||||||
};
|
};
|
||||||
if let Some(result) = resolve_var(name, index.map_or(0, |v| v.get()), scope, &context)
|
if let Some(result) =
|
||||||
.map_err(|err| err.fill_position(*pos))?
|
resolve_var(name, index, scope, &context).map_err(|err| err.fill_position(*pos))?
|
||||||
{
|
{
|
||||||
return Ok((result.into(), name, ScopeEntryType::Constant, *pos));
|
return Ok((result.into(), name, ScopeEntryType::Constant, *pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = if let Some(index) = index {
|
let index = if index > 0 {
|
||||||
scope.len() - index.get()
|
scope.len() - index
|
||||||
} else {
|
} else {
|
||||||
// Find the variable in the scope
|
// Find the variable in the scope
|
||||||
scope
|
scope
|
||||||
@ -1604,10 +1611,10 @@ impl Engine {
|
|||||||
|
|
||||||
// Module-qualified function call
|
// Module-qualified function call
|
||||||
Expr::FnCall(x) if x.1.is_some() => {
|
Expr::FnCall(x) if x.1.is_some() => {
|
||||||
let ((name, _, capture, pos), modules, hash, args_expr, def_val) = x.as_ref();
|
let ((name, _, _, pos), modules, hash, args_expr, def_val) = x.as_ref();
|
||||||
self.make_qualified_function_call(
|
self.make_qualified_function_call(
|
||||||
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
|
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
|
||||||
*capture, level,
|
level,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
}
|
}
|
||||||
|
@ -50,10 +50,6 @@ use crate::stdlib::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
use crate::stdlib::{collections::HashSet, string::String};
|
|
||||||
|
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use num_traits::float::Float;
|
use num_traits::float::Float;
|
||||||
@ -146,29 +142,6 @@ impl Drop for ArgBackup<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add captured variables into scope
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
fn add_captured_variables_into_scope<'s>(
|
|
||||||
externals: &HashSet<String>,
|
|
||||||
captured: Scope<'s>,
|
|
||||||
scope: &mut Scope<'s>,
|
|
||||||
) {
|
|
||||||
captured
|
|
||||||
.into_iter()
|
|
||||||
.filter(|ScopeEntry { name, .. }| externals.contains(name.as_ref()))
|
|
||||||
.for_each(
|
|
||||||
|ScopeEntry {
|
|
||||||
name, typ, value, ..
|
|
||||||
}| {
|
|
||||||
match typ {
|
|
||||||
ScopeEntryType::Normal => scope.push(name, value),
|
|
||||||
ScopeEntryType::Constant => scope.push_constant(name, value),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn ensure_no_data_race(
|
pub fn ensure_no_data_race(
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -411,9 +384,27 @@ impl Engine {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Merge in encapsulated environment, if any
|
||||||
|
let mut lib_merged;
|
||||||
|
|
||||||
|
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
|
||||||
|
if !lib.is_empty() {
|
||||||
|
// In the special case of the main script not defining any function
|
||||||
|
env_lib
|
||||||
|
} else {
|
||||||
|
lib_merged = lib.clone();
|
||||||
|
lib_merged.merge(env_lib);
|
||||||
|
&lib_merged
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lib
|
||||||
|
};
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
// Evaluate the function at one higher level of call depth
|
||||||
|
let stmt = &fn_def.body;
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, mods, state, lib, this_ptr, &fn_def.body, level + 1)
|
.eval_stmt(scope, mods, state, unified_lib, this_ptr, stmt, level + 1)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
EvalAltResult::Return(x, _) => Ok(x),
|
||||||
@ -574,10 +565,27 @@ impl Engine {
|
|||||||
let scope = &mut Scope::new();
|
let scope = &mut Scope::new();
|
||||||
let mods = &mut Imports::new();
|
let mods = &mut Imports::new();
|
||||||
|
|
||||||
// Add captured variables into scope
|
// Move captured variables into scope
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
if let Some(captured) = _capture {
|
if let Some(captured) = _capture {
|
||||||
add_captured_variables_into_scope(&func.externals, captured, scope);
|
captured
|
||||||
|
.into_iter()
|
||||||
|
.filter(|ScopeEntry { name, .. }| {
|
||||||
|
func.externals.contains(name.as_ref())
|
||||||
|
})
|
||||||
|
.for_each(
|
||||||
|
|ScopeEntry {
|
||||||
|
name, typ, value, ..
|
||||||
|
}| {
|
||||||
|
// Consume the scope values.
|
||||||
|
match typ {
|
||||||
|
ScopeEntryType::Normal => scope.push(name, value),
|
||||||
|
ScopeEntryType::Constant => {
|
||||||
|
scope.push_constant(name, value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if _is_method {
|
let result = if _is_method {
|
||||||
@ -1020,7 +1028,7 @@ impl Engine {
|
|||||||
let mut args: StaticVec<_>;
|
let mut args: StaticVec<_>;
|
||||||
let mut is_ref = false;
|
let mut is_ref = false;
|
||||||
let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() {
|
let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() {
|
||||||
Some(scope.flatten_clone())
|
Some(scope.clone_visible())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -1092,7 +1100,6 @@ impl Engine {
|
|||||||
args_expr: &[Expr],
|
args_expr: &[Expr],
|
||||||
def_val: Option<bool>,
|
def_val: Option<bool>,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
_capture: bool,
|
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let modules = modules.as_ref().unwrap();
|
let modules = modules.as_ref().unwrap();
|
||||||
@ -1189,20 +1196,12 @@ impl Engine {
|
|||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
let func = f.get_fn_def();
|
let func = f.get_fn_def();
|
||||||
|
|
||||||
let scope = &mut Scope::new();
|
let new_scope = &mut Scope::new();
|
||||||
let mods = &mut Imports::new();
|
let mods = &mut Imports::new();
|
||||||
|
|
||||||
// Add captured variables into scope
|
self.call_script_fn(
|
||||||
#[cfg(not(feature = "no_closure"))]
|
new_scope, mods, state, lib, &mut None, name, func, args, level,
|
||||||
if _capture && !scope.is_empty() {
|
)
|
||||||
add_captured_variables_into_scope(
|
|
||||||
&func.externals,
|
|
||||||
scope.flatten_clone(),
|
|
||||||
scope,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level)
|
|
||||||
}
|
}
|
||||||
Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut()),
|
Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut()),
|
||||||
Some(f) if f.is_native() => {
|
Some(f) if f.is_native() => {
|
||||||
|
@ -9,14 +9,11 @@ use crate::result::EvalAltResult;
|
|||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::token::{is_valid_identifier, Position};
|
use crate::token::{is_valid_identifier, Position};
|
||||||
use crate::utils::ImmutableString;
|
use crate::utils::ImmutableString;
|
||||||
|
use crate::{calc_fn_hash, StaticVec};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
use crate::stdlib::{
|
||||||
use crate::{calc_fn_hash, module::FuncReturn, StaticVec};
|
boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String, vec::Vec,
|
||||||
|
};
|
||||||
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
use crate::stdlib::{iter::empty, mem};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
use crate::stdlib::rc::Rc;
|
use crate::stdlib::rc::Rc;
|
||||||
@ -114,14 +111,13 @@ impl FnPtr {
|
|||||||
/// This is to avoid unnecessarily cloning the arguments.
|
/// This is to avoid unnecessarily cloning the arguments.
|
||||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||||
/// clone them _before_ calling this function.
|
/// clone them _before_ calling this function.
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
pub fn call_dynamic(
|
pub fn call_dynamic(
|
||||||
&self,
|
&self,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
lib: impl AsRef<Module>,
|
lib: impl AsRef<Module>,
|
||||||
this_ptr: Option<&mut Dynamic>,
|
this_ptr: Option<&mut Dynamic>,
|
||||||
mut arg_values: impl AsMut<[Dynamic]>,
|
mut arg_values: impl AsMut<[Dynamic]>,
|
||||||
) -> FuncReturn<Dynamic> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let mut args_data = self
|
let mut args_data = self
|
||||||
.1
|
.1
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -288,7 +288,7 @@ impl Module {
|
|||||||
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> u64 {
|
pub(crate) fn set_script_fn(&mut self, fn_def: Shared<ScriptFnDef>) -> u64 {
|
||||||
// None + function name + number of arguments.
|
// None + function name + number of arguments.
|
||||||
let num_params = fn_def.params.len();
|
let num_params = fn_def.params.len();
|
||||||
let hash_script = calc_fn_hash(empty(), &fn_def.name, num_params, empty());
|
let hash_script = calc_fn_hash(empty(), &fn_def.name, num_params, empty());
|
||||||
@ -554,6 +554,7 @@ impl Module {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn set_raw_fn_as_scripted(
|
pub(crate) fn set_raw_fn_as_scripted(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
@ -1392,53 +1393,18 @@ impl Module {
|
|||||||
module.modules.insert(alias.to_string(), m);
|
module.modules.insert(alias.to_string(), m);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Non-private functions defined become module functions
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
let ast_lib: Shared<Module> = ast.lib().clone().into();
|
let ast_lib: Shared<Module> = ast.lib().clone().into();
|
||||||
|
|
||||||
ast.iter_functions()
|
ast.iter_functions()
|
||||||
.filter(|(access, _, _, _)| access.is_public())
|
.filter(|(access, _, _, _)| !access.is_private())
|
||||||
.for_each(|(_, name, num_params, func)| {
|
.for_each(|(_, _, _, func)| {
|
||||||
let ast_lib = ast_lib.clone();
|
// Encapsulate AST environment
|
||||||
|
let mut func = func.as_ref().clone();
|
||||||
module.set_raw_fn_as_scripted(
|
func.lib = Some(ast_lib.clone());
|
||||||
name,
|
module.set_script_fn(func.into());
|
||||||
num_params,
|
|
||||||
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
|
||||||
let mut lib_merged;
|
|
||||||
|
|
||||||
let unified_lib = if lib.is_empty() {
|
|
||||||
// In the special case of the main script not defining any function
|
|
||||||
&ast_lib
|
|
||||||
} else {
|
|
||||||
lib_merged = lib.clone();
|
|
||||||
lib_merged.merge(&ast_lib);
|
|
||||||
&lib_merged
|
|
||||||
};
|
|
||||||
|
|
||||||
engine
|
|
||||||
.call_script_fn(
|
|
||||||
&mut Default::default(),
|
|
||||||
&mut Default::default(),
|
|
||||||
&mut Default::default(),
|
|
||||||
unified_lib,
|
|
||||||
&mut None,
|
|
||||||
&func.name,
|
|
||||||
func.as_ref(),
|
|
||||||
args,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.map_err(|err| {
|
|
||||||
// Wrap the error in a module-error
|
|
||||||
EvalAltResult::ErrorInModule(
|
|
||||||
"".to_string(),
|
|
||||||
err,
|
|
||||||
Position::none(),
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -823,6 +823,7 @@ pub fn optimize_into_ast(
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
externals: fn_def.externals.clone(),
|
externals: fn_def.externals.clone(),
|
||||||
pos: fn_def.pos,
|
pos: fn_def.pos,
|
||||||
|
lib: None,
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
@ -862,7 +863,7 @@ pub fn optimize_into_ast(
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_functions.into_iter().for_each(|fn_def| {
|
_functions.into_iter().for_each(|fn_def| {
|
||||||
module.set_script_fn(fn_def);
|
module.set_script_fn(fn_def.into());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,17 +7,13 @@ use crate::engine::{Array, Engine};
|
|||||||
use crate::fn_native::FnPtr;
|
use crate::fn_native::FnPtr;
|
||||||
use crate::parser::{ImmutableString, INT};
|
use crate::parser::{ImmutableString, INT};
|
||||||
use crate::plugin::*;
|
use crate::plugin::*;
|
||||||
|
use crate::result::EvalAltResult;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
use crate::token::Position;
|
||||||
use crate::{result::EvalAltResult, token::Position};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
use crate::engine::Map;
|
use crate::engine::Map;
|
||||||
|
|
||||||
use crate::stdlib::{any::TypeId, boxed::Box};
|
use crate::stdlib::{any::TypeId, boxed::Box, cmp::Ordering, string::ToString};
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
use crate::stdlib::string::ToString;
|
|
||||||
|
|
||||||
pub type Unit = ();
|
pub type Unit = ();
|
||||||
|
|
||||||
@ -75,6 +71,14 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
reg_functions!(lib += map; Map);
|
reg_functions!(lib += map; Map);
|
||||||
|
|
||||||
|
lib.set_raw_fn("map", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], map);
|
||||||
|
lib.set_raw_fn("filter", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], filter);
|
||||||
|
lib.set_raw_fn("reduce", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], reduce);
|
||||||
|
lib.set_raw_fn("reduce_rev", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], reduce_rev);
|
||||||
|
lib.set_raw_fn("some", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], some);
|
||||||
|
lib.set_raw_fn("all", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], all);
|
||||||
|
lib.set_raw_fn("sort", &[TypeId::of::<Array>(), TypeId::of::<FnPtr>()], sort);
|
||||||
|
|
||||||
// Merge in the module at the end to override `+=` for arrays
|
// Merge in the module at the end to override `+=` for arrays
|
||||||
combine_with_exported_module!(lib, "array", array_functions);
|
combine_with_exported_module!(lib, "array", array_functions);
|
||||||
|
|
||||||
@ -130,6 +134,25 @@ mod array_functions {
|
|||||||
pub fn reverse(list: &mut Array) {
|
pub fn reverse(list: &mut Array) {
|
||||||
list.reverse();
|
list.reverse();
|
||||||
}
|
}
|
||||||
|
pub fn splice(list: &mut Array, start: INT, len: INT, replace: Array) {
|
||||||
|
let start = if start < 0 {
|
||||||
|
0
|
||||||
|
} else if start as usize >= list.len() {
|
||||||
|
list.len() - 1
|
||||||
|
} else {
|
||||||
|
start as usize
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = if len < 0 {
|
||||||
|
0
|
||||||
|
} else if len as usize > list.len() - start {
|
||||||
|
list.len() - start
|
||||||
|
} else {
|
||||||
|
len as usize
|
||||||
|
};
|
||||||
|
|
||||||
|
list.splice(start..start + len, replace.into_iter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad<T: Variant + Clone>(
|
fn pad<T: Variant + Clone>(
|
||||||
@ -165,6 +188,250 @@ fn pad<T: Variant + Clone>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let list = args[0].read_lock::<Array>().unwrap();
|
||||||
|
let mapper = args[1].read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
let mut array = Array::with_capacity(list.len());
|
||||||
|
|
||||||
|
for (i, item) in list.iter().enumerate() {
|
||||||
|
array.push(
|
||||||
|
mapper
|
||||||
|
.call_dynamic(engine, lib, None, [item.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(_, _) => {
|
||||||
|
mapper.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()])
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
"map".to_string(),
|
||||||
|
err,
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<Array, Box<EvalAltResult>> {
|
||||||
|
let list = args[0].read_lock::<Array>().unwrap();
|
||||||
|
let filter = args[1].read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
let mut array = Array::with_capacity(list.len());
|
||||||
|
|
||||||
|
for (i, item) in list.iter().enumerate() {
|
||||||
|
if filter
|
||||||
|
.call_dynamic(engine, lib, None, [item.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(_, _) => {
|
||||||
|
filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()])
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
"filter".to_string(),
|
||||||
|
err,
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
array.push(item.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn some(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<bool, Box<EvalAltResult>> {
|
||||||
|
let list = args[0].read_lock::<Array>().unwrap();
|
||||||
|
let filter = args[1].read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
for (i, item) in list.iter().enumerate() {
|
||||||
|
if filter
|
||||||
|
.call_dynamic(engine, lib, None, [item.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(_, _) => {
|
||||||
|
filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()])
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
"filter".to_string(),
|
||||||
|
err,
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok(true.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<bool, Box<EvalAltResult>> {
|
||||||
|
let list = args[0].read_lock::<Array>().unwrap();
|
||||||
|
let filter = args[1].read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
for (i, item) in list.iter().enumerate() {
|
||||||
|
if !filter
|
||||||
|
.call_dynamic(engine, lib, None, [item.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(_, _) => {
|
||||||
|
filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()])
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
"filter".to_string(),
|
||||||
|
err,
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok(false.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
let list = args[0].read_lock::<Array>().unwrap();
|
||||||
|
let reducer = args[1].read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
let mut result: Dynamic = ().into();
|
||||||
|
|
||||||
|
for (i, item) in list.iter().enumerate() {
|
||||||
|
result = reducer
|
||||||
|
.call_dynamic(engine, lib, None, [result.clone(), item.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic(
|
||||||
|
engine,
|
||||||
|
lib,
|
||||||
|
None,
|
||||||
|
[result, item.clone(), (i as INT).into()],
|
||||||
|
),
|
||||||
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
"reduce".to_string(),
|
||||||
|
err,
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce_rev(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
let list = args[0].read_lock::<Array>().unwrap();
|
||||||
|
let reducer = args[1].read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
let mut result: Dynamic = ().into();
|
||||||
|
|
||||||
|
for (i, item) in list.iter().enumerate().rev() {
|
||||||
|
result = reducer
|
||||||
|
.call_dynamic(engine, lib, None, [result.clone(), item.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic(
|
||||||
|
engine,
|
||||||
|
lib,
|
||||||
|
None,
|
||||||
|
[result, item.clone(), (i as INT).into()],
|
||||||
|
),
|
||||||
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
"reduce".to_string(),
|
||||||
|
err,
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort(
|
||||||
|
engine: &Engine,
|
||||||
|
lib: &Module,
|
||||||
|
args: &mut [&mut Dynamic],
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
let comparer = args[1].read_lock::<FnPtr>().unwrap().clone();
|
||||||
|
let mut list = args[0].write_lock::<Array>().unwrap();
|
||||||
|
|
||||||
|
list.sort_by(|x, y| {
|
||||||
|
comparer
|
||||||
|
.call_dynamic(engine, lib, None, [x.clone(), y.clone()])
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.as_int().ok())
|
||||||
|
.map(|v| {
|
||||||
|
if v > 0 {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if v < 0 {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let x_type_id = x.type_id();
|
||||||
|
let y_type_id = y.type_id();
|
||||||
|
|
||||||
|
if x_type_id > y_type_id {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if x_type_id < y_type_id {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit);
|
gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit);
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
@ -549,6 +549,8 @@ pub struct ScriptFnDef {
|
|||||||
pub body: Stmt,
|
pub body: Stmt,
|
||||||
/// Position of the function definition.
|
/// Position of the function definition.
|
||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
|
/// Encapsulated running environment, if any.
|
||||||
|
pub lib: Option<Shared<Module>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ScriptFnDef {
|
impl fmt::Display for ScriptFnDef {
|
||||||
@ -1949,7 +1951,17 @@ fn parse_primary(
|
|||||||
settings.pos = token_pos;
|
settings.pos = token_pos;
|
||||||
|
|
||||||
root_expr = match (root_expr, token) {
|
root_expr = match (root_expr, token) {
|
||||||
// Function call
|
// Qualified function call with !
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
(Expr::Variable(x), Token::Bang) if x.1.is_some() => {
|
||||||
|
return Err(if !match_token(input, Token::LeftParen)? {
|
||||||
|
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos)
|
||||||
|
} else {
|
||||||
|
PERR::BadInput("'!' cannot be used to call module functions".to_string())
|
||||||
|
.into_err(token_pos)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Function call with !
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
(Expr::Variable(x), Token::Bang) => {
|
(Expr::Variable(x), Token::Bang) => {
|
||||||
if !match_token(input, Token::LeftParen)? {
|
if !match_token(input, Token::LeftParen)? {
|
||||||
@ -3363,6 +3375,7 @@ fn parse_fn(
|
|||||||
externals,
|
externals,
|
||||||
body,
|
body,
|
||||||
pos: settings.pos,
|
pos: settings.pos,
|
||||||
|
lib: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3540,6 +3553,7 @@ fn parse_anon_fn(
|
|||||||
externals: Default::default(),
|
externals: Default::default(),
|
||||||
body,
|
body,
|
||||||
pos: settings.pos,
|
pos: settings.pos,
|
||||||
|
lib: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
|
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
|
||||||
|
@ -5,6 +5,9 @@ use crate::error::ParseErrorType;
|
|||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
use crate::engine::is_anonymous_fn;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -166,6 +169,10 @@ impl fmt::Display for EvalAltResult {
|
|||||||
|
|
||||||
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
|
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Self::ErrorInFunctionCall(s, err, _) if is_anonymous_fn(s) => {
|
||||||
|
write!(f, "Error in call to closure: {}", err)?
|
||||||
|
}
|
||||||
Self::ErrorInFunctionCall(s, err, _) => {
|
Self::ErrorInFunctionCall(s, err, _) => {
|
||||||
write!(f, "Error in call to function '{}': {}", s, err)?
|
write!(f, "Error in call to function '{}': {}", s, err)?
|
||||||
}
|
}
|
||||||
|
@ -420,7 +420,7 @@ impl<'a> Scope<'a> {
|
|||||||
/// Clone the Scope, keeping only the last instances of each variable name.
|
/// Clone the Scope, keeping only the last instances of each variable name.
|
||||||
/// Shadowed variables are omitted in the copy.
|
/// Shadowed variables are omitted in the copy.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn flatten_clone(&self) -> Self {
|
pub(crate) fn clone_visible(&self) -> Self {
|
||||||
let mut entries: Vec<Entry> = Default::default();
|
let mut entries: Vec<Entry> = Default::default();
|
||||||
|
|
||||||
self.0.iter().rev().for_each(|entry| {
|
self.0.iter().rev().for_each(|entry| {
|
||||||
|
@ -623,7 +623,9 @@ impl Token {
|
|||||||
|
|
||||||
Plus | Minus => 150,
|
Plus | Minus => 150,
|
||||||
|
|
||||||
Divide | Multiply | PowerOf | Modulo => 180,
|
Divide | Multiply => 180,
|
||||||
|
|
||||||
|
PowerOf | Modulo => 190,
|
||||||
|
|
||||||
LeftShift | RightShift => 210,
|
LeftShift | RightShift => 210,
|
||||||
|
|
||||||
|
122
tests/arrays.rs
122
tests/arrays.rs
@ -112,3 +112,125 @@ fn test_array_with_structs() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
#[test]
|
||||||
|
fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
let y = x.filter(|v| v > 2);
|
||||||
|
y[0]
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
let y = x.filter(|v, i| v > i);
|
||||||
|
y.len()
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
let y = x.map(|v| v * 2);
|
||||||
|
y[2]
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
6
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
let y = x.map(|v, i| v * i);
|
||||||
|
y[2]
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
6
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.reduce(|sum, v| if sum.type_of() == "()" { v * v } else { sum + v * v })
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
14
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.reduce(|sum, v, i| { if i == 0 { sum = 10 } sum + v * v })
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
24
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.reduce_rev(|sum, v| if sum.type_of() == "()" { v * v } else { sum + v * v })
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
14
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.reduce_rev(|sum, v, i| { if i == 2 { sum = 10 } sum + v * v })
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
24
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.some(|v| v > 1)
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.some(|v, i| v * i == 0)
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
|
||||||
|
assert!(!engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.all(|v| v > 1)
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let x = [1, 2, 3];
|
||||||
|
x.all(|v, i| v > i)
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user