commit
a87c4de22b
23
RELEASES.md
23
RELEASES.md
@ -2,6 +2,29 @@ Rhai Release Notes
|
|||||||
==================
|
==================
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.19.4
|
||||||
|
==============
|
||||||
|
|
||||||
|
This version adds a low-level API for more flexibility when defining custom syntax.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Fixes `Send + Sync` for `EvalAltResult` under the `sync` feature. Bug introduced with `0.19.3`.
|
||||||
|
|
||||||
|
Breaking changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* Custom syntax can no longer start with a keyword (even a _reserved_ one), even if it has been disabled. That is to avoid breaking scripts later when the keyword is no longer disabled.
|
||||||
|
* `EvalAltResult::ErrorAssignmentToUnknownLHS` is moved to `ParseError::AssignmentToInvalidLHS`. `ParseError::AssignmentToCopy` is removed.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Low-level API for custom syntax allowing more flexibility in designing the syntax.
|
||||||
|
* `Module::fill_with` to poly-fill a module with another.
|
||||||
|
|
||||||
|
|
||||||
Version 0.19.3
|
Version 0.19.3
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -54,16 +54,14 @@ These symbol types can be used:
|
|||||||
|
|
||||||
* `$ident$` - any [variable] name.
|
* `$ident$` - any [variable] name.
|
||||||
|
|
||||||
### The First Symbol Must be a Keyword
|
### The First Symbol Must be an Identifier
|
||||||
|
|
||||||
There is no specific limit on the combination and sequencing of each symbol type,
|
There is no specific limit on the combination and sequencing of each symbol type,
|
||||||
except the _first_ symbol which must be a custom keyword that follows the naming rules
|
except the _first_ symbol which must be a custom keyword that follows the naming rules
|
||||||
of [variables].
|
of [variables].
|
||||||
|
|
||||||
The first symbol also cannot be a reserved [keyword], unless that keyword
|
The first symbol also cannot be a normal or reserved [keyword].
|
||||||
has been [disabled][disable keywords and operators].
|
In other words, any valid identifier that is not a [keyword] will work fine.
|
||||||
|
|
||||||
In other words, any valid identifier that is not an active [keyword] will work fine.
|
|
||||||
|
|
||||||
### The First Symbol Must be Unique
|
### The First Symbol Must be Unique
|
||||||
|
|
||||||
@ -118,14 +116,19 @@ The function signature of an implementation is:
|
|||||||
|
|
||||||
where:
|
where:
|
||||||
|
|
||||||
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following:
|
| Parameter | Type | Description |
|
||||||
* `context.scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to/removed from it.
|
| ----------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------- |
|
||||||
* `context.engine(): &Engine` - reference to the current [`Engine`].
|
| `context` | `&mut EvalContext` | mutable reference to the current evaluation _context_ |
|
||||||
* `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
|
| - `context.scope` | `&mut Scope` | mutable reference to the current [`Scope`]; variables can be added to/removed from it |
|
||||||
* `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
|
| - `context.engine()` | `&Engine` | reference to the current [`Engine`] |
|
||||||
* `context.call_level(): usize` - the current nesting level of function calls.
|
| - `context.iter_namespaces()` | `impl Iterator<Item = &Module>` | iterator of the namespaces (as [modules]) containing all script-defined functions |
|
||||||
|
| - `context.this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any |
|
||||||
|
| - `context.call_level()` | `usize` | the current nesting level of function calls |
|
||||||
|
| `inputs` | `&[Expression]` | a list of input expression trees |
|
||||||
|
|
||||||
* `inputs: &[Expression]` - a list of input expression trees.
|
### Return Value
|
||||||
|
|
||||||
|
Return value is the result of evaluating the custom syntax expression.
|
||||||
|
|
||||||
### Access Arguments
|
### Access Arguments
|
||||||
|
|
||||||
@ -215,9 +218,9 @@ fn implementation_func(
|
|||||||
Ok(().into())
|
Ok(().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the custom syntax (sample): do |x| -> { x += 1 } while x < 0;
|
// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0;
|
||||||
engine.register_custom_syntax(
|
engine.register_custom_syntax(
|
||||||
&[ "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
|
&[ "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax
|
||||||
1, // the number of new variables declared within this custom syntax
|
1, // the number of new variables declared within this custom syntax
|
||||||
implementation_func
|
implementation_func
|
||||||
)?;
|
)?;
|
||||||
@ -252,3 +255,88 @@ Make sure there are _lots_ of examples for users to follow.
|
|||||||
|
|
||||||
Step Six - Profit!
|
Step Six - Profit!
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
||||||
|
Really Advanced - Low Level Custom Syntax API
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
Sometimes it is desirable to have multiple custom syntax starting with the
|
||||||
|
same symbol. This is especially common for _command-style_ syntax where the
|
||||||
|
second symbol calls a particular command:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// The following simulates a command-style syntax, all starting with 'perform'.
|
||||||
|
perform hello world; // A fixed sequence of symbols
|
||||||
|
perform action 42; // Perform a system action with a parameter
|
||||||
|
perform update system; // Update the system
|
||||||
|
perform check all; // Check all system settings
|
||||||
|
perform cleanup; // Clean up the system
|
||||||
|
perform add something; // Add something to the system
|
||||||
|
perform remove something; // Delete something from the system
|
||||||
|
```
|
||||||
|
|
||||||
|
For even more flexibility, there is a _low level_ API for custom syntax that
|
||||||
|
allows the registration of an entire mini-parser.
|
||||||
|
|
||||||
|
Use `Engine::register_custom_syntax_raw` to register a custom syntax _parser_
|
||||||
|
together with the implementation function:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
engine.register_custom_syntax_raw(
|
||||||
|
"perform",
|
||||||
|
|stream| match stream.len() {
|
||||||
|
// perform ...
|
||||||
|
1 => Ok(Some("$ident$".to_string())),
|
||||||
|
// perform command ...
|
||||||
|
2 => match stream[1].as_str() {
|
||||||
|
"action" => Ok(Some("$expr$".to_string())),
|
||||||
|
"hello" => Ok(Some("world".to_string())),
|
||||||
|
"update" | "check" | "add" | "remove" => Ok(Some("$ident$".to_string())),
|
||||||
|
"cleanup" => Ok(None),
|
||||||
|
cmd => Err(ParseError(Box::new(ParseErrorType::BadInput(
|
||||||
|
format!("Improper command: {}", cmd))),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
// perform command arg ...
|
||||||
|
3 => match (stream[1].as_str(), stream[2].as_str()) {
|
||||||
|
("action", _) => Ok(None),
|
||||||
|
("hello", "world") => Ok(None),
|
||||||
|
("update", arg) if arg == "system" => Ok(None),
|
||||||
|
("update", arg) if arg == "client" => Ok(None),
|
||||||
|
("check", arg) => Ok(None),
|
||||||
|
("add", arg) => Ok(None),
|
||||||
|
("remove", arg) => Ok(None),
|
||||||
|
(cmd, arg) => Err(ParseError(Box::new(ParseErrorType::BadInput(
|
||||||
|
format!("Invalid argument for command {}: {}", cmd, arg))),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
0, // the number of new variables declared within this custom syntax
|
||||||
|
implementation_func
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Signature
|
||||||
|
|
||||||
|
The custom syntax parser has the following signature:
|
||||||
|
|
||||||
|
> `Fn(stream: &[String]) -> Result<Option<String>, ParseError>`
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | :---------: | -------------------------------------------------------------------------------------------------- |
|
||||||
|
| `stream` | `&[String]` | a slice of symbols that have been parsed so far, possibly containing `"$expr$"` and/or `"$block$"` |
|
||||||
|
|
||||||
|
### Return Value
|
||||||
|
|
||||||
|
The return value is `Result<Option<String>, ParseError>` where:
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `Ok(None)` | parsing complete and there are no more symbols to match |
|
||||||
|
| `Ok(Some(symbol))` | next symbol to match, which can also be `"$expr$"`, `"$ident$"` or `"$block$"` |
|
||||||
|
| `Err(ParseError)` | error that is reflected back to the [`Engine`].<br/>Normally this is `ParseError(ParseErrorType::BadInput(message), Position::none())` to indicate that there is a syntax error, but it can be any `ParseError`. |
|
||||||
|
@ -16,7 +16,7 @@ To do so, provide a closure to the [`Engine`] via the `Engine::on_var` method:
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
// Register a variable resolver.
|
// Register a variable resolver.
|
||||||
engine.on_var(|name, index, scope, context| {
|
engine.on_var(|name, index, context| {
|
||||||
match name {
|
match name {
|
||||||
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
|
"MYSTIC_NUMBER" => Ok(Some((42 as INT).into())),
|
||||||
// Override a variable - make it not found even if it exists!
|
// Override a variable - make it not found even if it exists!
|
||||||
@ -24,7 +24,7 @@ engine.on_var(|name, index, scope, context| {
|
|||||||
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
|
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
|
||||||
)),
|
)),
|
||||||
// Silently maps 'chameleon' into 'innocent'.
|
// Silently maps 'chameleon' into 'innocent'.
|
||||||
"chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
|
"chameleon" => context.scope.get_value("innocent").map(Some).ok_or_else(|| Box::new(
|
||||||
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
|
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none())
|
||||||
)),
|
)),
|
||||||
// Return Ok(None) to continue with the normal variable resolution process.
|
// Return Ok(None) to continue with the normal variable resolution process.
|
||||||
@ -67,27 +67,23 @@ The function signature passed to `Engine::on_var` takes the following form:
|
|||||||
|
|
||||||
where:
|
where:
|
||||||
|
|
||||||
* `name: &str` - variable name.
|
| Parameter | Type | Description |
|
||||||
|
| ----------------------------- | :-----------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
* `index: usize` - an offset from the bottom of the current [`Scope`] that the variable is supposed to reside.
|
| `name` | `&str` | variable name |
|
||||||
Offsets start from 1, with 1 meaning the last variable in the current [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.
|
| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the variable is supposed to reside.<br/>Offsets start from 1, with 1 meaning the last variable in the current [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.<br/>If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. |
|
||||||
|
| `context` | `&EvalContext` | reference to the current evaluation _context_ |
|
||||||
If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed.
|
| - `context.scope` | `&Scope` | reference to the current [`Scope`] containing all variables up to the current evaluation position |
|
||||||
|
| - `context.engine()` | `&Engine` | reference to the current [`Engine`] |
|
||||||
* `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields:
|
| - `context.iter_namespaces()` | `impl Iterator<Item = &Module>` | iterator of the namespaces (as [modules]) containing all script-defined functions |
|
||||||
* `context.scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position.
|
| - `context.this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any |
|
||||||
* `context.engine(): &Engine` - reference to the current [`Engine`].
|
| - `context.call_level()` | `usize` | the current nesting level of function calls |
|
||||||
* `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
|
|
||||||
* `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
|
|
||||||
* `context.call_level(): usize` - the current nesting level of function calls.
|
|
||||||
|
|
||||||
### Return Value
|
### Return Value
|
||||||
|
|
||||||
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where:
|
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`].
|
| Value | Description |
|
||||||
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
* `Ok(Some(Dynamic))` - wrapped [`Dynamic`] is taken as the value of the variable, which is treated as a constant.
|
| `Ok(None)` | normal variable resolution process should continue, i.e. continue searching through the [`Scope`] |
|
||||||
|
| `Ok(Some(Dynamic))` | value of the variable, treated as a constant |
|
||||||
* `Err(Box<EvalAltResult>)` - error is reflected back to the [`Engine`].
|
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`].<br/>Normally this is `EvalAltResult::ErrorVariableNotFound(var_name, Position::none())` to indicate that the variable does not exist, but it can be any `EvalAltResult`. |
|
||||||
Normally this is `EvalAltResult::ErrorVariableNotFound` to indicate that the variable does not exist, but it can be any error.
|
|
||||||
|
@ -65,16 +65,17 @@ The function signature passed to `Engine::register_raw_fn` takes the following f
|
|||||||
|
|
||||||
where:
|
where:
|
||||||
|
|
||||||
* `T: Clone` - return type of the function.
|
| Parameter | Type | Description |
|
||||||
|
| ----------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `T` | `impl Clone` | return type of the function |
|
||||||
|
| `context` | `NativeCallContext` | the current _native call context_ |
|
||||||
|
| - `context.engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.<br/>This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. |
|
||||||
|
| - `context.iter_namespaces()` | `impl Iterator<Item = &Module>` | iterator of the namespaces (as [modules]) containing all script-defined functions |
|
||||||
|
| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.<br/>The slice is guaranteed to contain enough arguments _of the correct types_. |
|
||||||
|
|
||||||
* `context: NativeCallContext` - the current _native call context_, which exposes the following:
|
### Return value
|
||||||
|
|
||||||
* `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings.
|
The return value is the result of the function call.
|
||||||
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer].
|
|
||||||
* `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
|
|
||||||
|
|
||||||
* `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values.
|
|
||||||
The slice is guaranteed to contain enough arguments _of the correct types_.
|
|
||||||
|
|
||||||
Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned).
|
Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned).
|
||||||
Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations
|
Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations
|
||||||
@ -116,16 +117,16 @@ let mut engine = Engine::new();
|
|||||||
engine.register_raw_fn(
|
engine.register_raw_fn(
|
||||||
"bar",
|
"bar",
|
||||||
&[
|
&[
|
||||||
std::any::TypeId::of::<i64>(), // parameter types
|
std::any::TypeId::of::<i64>(), // parameter types
|
||||||
std::any::TypeId::of::<FnPtr>(),
|
std::any::TypeId::of::<FnPtr>(),
|
||||||
std::any::TypeId::of::<i64>(),
|
std::any::TypeId::of::<i64>(),
|
||||||
],
|
],
|
||||||
|context, args| {
|
|context, args| {
|
||||||
// 'args' is guaranteed to contain enough arguments of the correct types
|
// 'args' is guaranteed to contain enough arguments of the correct types
|
||||||
|
|
||||||
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
|
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
|
||||||
let value = args[2].clone(); // 3rd argument - function argument
|
let value = args[2].clone(); // 3rd argument - function argument
|
||||||
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
|
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
|
||||||
|
|
||||||
// Use 'FnPtr::call_dynamic' to call the function pointer.
|
// Use 'FnPtr::call_dynamic' to call the function pointer.
|
||||||
// Beware, private script-defined functions will not be found.
|
// Beware, private script-defined functions will not be found.
|
||||||
@ -133,13 +134,14 @@ engine.register_raw_fn(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = engine.eval::<i64>(r#"
|
let result = engine.eval::<i64>(
|
||||||
|
r#"
|
||||||
fn foo(x) { this += x; } // script-defined function 'foo'
|
fn foo(x) { this += x; } // script-defined function 'foo'
|
||||||
|
|
||||||
let x = 41; // object
|
let x = 41; // object
|
||||||
x.bar(Fn("foo"), 1); // pass 'foo' as function pointer
|
x.bar(Fn("foo"), 1); // pass 'foo' as function pointer
|
||||||
x
|
x
|
||||||
"#)?;
|
"#)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -156,7 +158,8 @@ Shared values are implemented as `Rc<RefCell<Dynamic>>` (`Arc<RwLock<Dynamic>>`
|
|||||||
|
|
||||||
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
|
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
|
||||||
no [capturing][automatic currying], this API de-sugars to a simple `Dynamic::downcast_ref` and
|
no [capturing][automatic currying], this API de-sugars to a simple `Dynamic::downcast_ref` and
|
||||||
`Dynamic::downcast_mut`.
|
`Dynamic::downcast_mut`. In other words, there is no locking and reference counting overhead
|
||||||
|
for the vast majority of non-shared values.
|
||||||
|
|
||||||
If the value is a shared value, then it is first locked and the returned lock guard
|
If the value is a shared value, then it is first locked and the returned lock guard
|
||||||
then allows access to the underlying value in the specified type.
|
then allows access to the underlying value in the specified type.
|
||||||
|
@ -120,16 +120,17 @@ fn main() {
|
|||||||
*value.read_lock::<Dynamic>().unwrap(),
|
*value.read_lock::<Dynamic>().unwrap(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
println!();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
"astu" => {
|
"astu" => {
|
||||||
// print the last un-optimized AST
|
// print the last un-optimized AST
|
||||||
println!("{:#?}", &ast_u);
|
println!("{:#?}\n", &ast_u);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
"ast" => {
|
"ast" => {
|
||||||
// print the last AST
|
// print the last AST
|
||||||
println!("{:#?}", &ast);
|
println!("{:#?}\n", &ast);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -164,14 +164,8 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
|
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
|
||||||
if self.type_names.is_none() {
|
|
||||||
self.type_names = Some(Default::default());
|
|
||||||
}
|
|
||||||
// Add the pretty-print type name into the map
|
// Add the pretty-print type name into the map
|
||||||
self.type_names
|
self.type_names.insert(type_name::<T>().into(), name.into());
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.insert(type_name::<T>().into(), name.into());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
488
src/engine.rs
488
src/engine.rs
@ -6,7 +6,7 @@ use crate::fn_native::{Callback, FnPtr, OnVarCallback};
|
|||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
||||||
use crate::parser::{Expr, ReturnType, Stmt};
|
use crate::parser::{BinaryExpr, Expr, ReturnType, Stmt};
|
||||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
@ -302,26 +302,19 @@ impl<'a> Target<'a> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::StringChar(_, _, ch) => {
|
Self::StringChar(_, _, ch) => {
|
||||||
let char_value = ch.clone();
|
let char_value = ch.clone();
|
||||||
self.set_value((char_value, Position::none()), Position::none())
|
self.set_value((char_value, Position::none())).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Update the value of the `Target`.
|
/// Update the value of the `Target`.
|
||||||
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
||||||
pub fn set_value(
|
pub fn set_value(&mut self, new_val: (Dynamic, Position)) -> Result<(), Box<EvalAltResult>> {
|
||||||
&mut self,
|
|
||||||
new_val: (Dynamic, Position),
|
|
||||||
target_pos: Position,
|
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => **r = new_val.0,
|
Self::Ref(r) => **r = new_val.0,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Self::LockGuard((r, _)) => **r = new_val.0,
|
Self::LockGuard((r, _)) => **r = new_val.0,
|
||||||
Self::Value(_) => {
|
Self::Value(_) => unreachable!(),
|
||||||
return EvalAltResult::ErrorAssignmentToUnknownLHS(target_pos).into();
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
|
Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
|
||||||
let mut s = string.write_lock::<ImmutableString>().unwrap();
|
let mut s = string.write_lock::<ImmutableString>().unwrap();
|
||||||
@ -484,6 +477,14 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm,
|
|||||||
|
|
||||||
/// Rhai main scripting engine.
|
/// Rhai main scripting engine.
|
||||||
///
|
///
|
||||||
|
/// # Thread Safety
|
||||||
|
///
|
||||||
|
/// `Engine` is re-entrant.
|
||||||
|
///
|
||||||
|
/// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
@ -496,11 +497,9 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm,
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`.
|
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
/// A unique ID identifying this scripting `Engine`.
|
/// A unique ID identifying this scripting `Engine`.
|
||||||
pub id: Option<String>,
|
pub id: String,
|
||||||
|
|
||||||
/// A module containing all functions directly loaded into the Engine.
|
/// A module containing all functions directly loaded into the Engine.
|
||||||
pub(crate) global_module: Module,
|
pub(crate) global_module: Module,
|
||||||
@ -512,14 +511,14 @@ pub struct Engine {
|
|||||||
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
||||||
|
|
||||||
/// A hashmap mapping type names to pretty-print names.
|
/// A hashmap mapping type names to pretty-print names.
|
||||||
pub(crate) type_names: Option<HashMap<String, String>>,
|
pub(crate) type_names: HashMap<String, String>,
|
||||||
|
|
||||||
/// A hashset containing symbols to disable.
|
/// A hashset containing symbols to disable.
|
||||||
pub(crate) disabled_symbols: Option<HashSet<String>>,
|
pub(crate) disabled_symbols: HashSet<String>,
|
||||||
/// A hashset containing custom keywords and precedence to recognize.
|
/// A hashset containing custom keywords and precedence to recognize.
|
||||||
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
|
pub(crate) custom_keywords: HashMap<String, Option<u8>>,
|
||||||
/// Custom syntax.
|
/// Custom syntax.
|
||||||
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
|
pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>,
|
||||||
/// Callback closure for resolving variable access.
|
/// Callback closure for resolving variable access.
|
||||||
pub(crate) resolve_var: Option<OnVarCallback>,
|
pub(crate) resolve_var: Option<OnVarCallback>,
|
||||||
|
|
||||||
@ -541,9 +540,10 @@ pub struct Engine {
|
|||||||
impl fmt::Debug for Engine {
|
impl fmt::Debug for Engine {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.id.as_ref() {
|
if !self.id.is_empty() {
|
||||||
Some(id) => write!(f, "Engine({})", id),
|
write!(f, "Engine({})", self.id)
|
||||||
None => f.write_str("Engine"),
|
} else {
|
||||||
|
f.write_str("Engine")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -645,7 +645,7 @@ impl Engine {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// Create the new scripting Engine
|
// Create the new scripting Engine
|
||||||
let mut engine = Self {
|
let mut engine = Self {
|
||||||
id: None,
|
id: Default::default(),
|
||||||
|
|
||||||
packages: Default::default(),
|
packages: Default::default(),
|
||||||
global_module: Default::default(),
|
global_module: Default::default(),
|
||||||
@ -658,10 +658,10 @@ impl Engine {
|
|||||||
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
|
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: None,
|
type_names: Default::default(),
|
||||||
disabled_symbols: None,
|
disabled_symbols: Default::default(),
|
||||||
custom_keywords: None,
|
custom_keywords: Default::default(),
|
||||||
custom_syntax: None,
|
custom_syntax: Default::default(),
|
||||||
|
|
||||||
// variable resolver
|
// variable resolver
|
||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
@ -707,7 +707,7 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new_raw() -> Self {
|
pub fn new_raw() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: None,
|
id: Default::default(),
|
||||||
|
|
||||||
packages: Default::default(),
|
packages: Default::default(),
|
||||||
global_module: Default::default(),
|
global_module: Default::default(),
|
||||||
@ -715,10 +715,10 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: None,
|
type_names: Default::default(),
|
||||||
disabled_symbols: None,
|
disabled_symbols: Default::default(),
|
||||||
custom_keywords: None,
|
custom_keywords: Default::default(),
|
||||||
custom_syntax: None,
|
custom_syntax: Default::default(),
|
||||||
|
|
||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
|
|
||||||
@ -894,18 +894,17 @@ impl Engine {
|
|||||||
match rhs {
|
match rhs {
|
||||||
// xxx[idx].expr... | xxx[idx][expr]...
|
// xxx[idx].expr... | xxx[idx][expr]...
|
||||||
Expr::Dot(x) | Expr::Index(x) => {
|
Expr::Dot(x) | Expr::Index(x) => {
|
||||||
let (idx, expr, pos) = x.as_ref();
|
let idx_pos = x.lhs.position();
|
||||||
let idx_pos = idx.position();
|
|
||||||
let idx_val = idx_val.as_value();
|
let idx_val = idx_val.as_value();
|
||||||
let obj_ptr = &mut self.get_indexed_mut(
|
let obj_ptr = &mut self.get_indexed_mut(
|
||||||
state, lib, target, idx_val, idx_pos, false, true, level,
|
state, lib, target, idx_val, idx_pos, false, true, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
state, lib, this_ptr, obj_ptr, &x.rhs, idx_values, next_chain, level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*pos))
|
.map_err(|err| err.fill_position(x.pos))
|
||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
@ -918,7 +917,7 @@ impl Engine {
|
|||||||
{
|
{
|
||||||
// Indexed value is a reference - update directly
|
// Indexed value is a reference - update directly
|
||||||
Ok(ref mut obj_ptr) => {
|
Ok(ref mut obj_ptr) => {
|
||||||
obj_ptr.set_value(new_val.unwrap(), rhs.position())?;
|
obj_ptr.set_value(new_val.unwrap())?;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
@ -986,7 +985,7 @@ impl Engine {
|
|||||||
let mut val = self
|
let mut val = self
|
||||||
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
|
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
|
||||||
|
|
||||||
val.set_value(new_val.unwrap(), rhs.position())?;
|
val.set_value(new_val.unwrap())?;
|
||||||
Ok((Default::default(), true))
|
Ok((Default::default(), true))
|
||||||
}
|
}
|
||||||
// {xxx:map}.id
|
// {xxx:map}.id
|
||||||
@ -1024,9 +1023,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
|
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
|
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
|
||||||
let (sub_lhs, expr, pos) = x.as_ref();
|
let mut val = match &x.lhs {
|
||||||
|
|
||||||
let mut val = match sub_lhs {
|
|
||||||
Expr::Property(p) => {
|
Expr::Property(p) => {
|
||||||
let ((prop, _, _), pos) = p.as_ref();
|
let ((prop, _, _), pos) = p.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
@ -1054,16 +1051,14 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
|
state, lib, this_ptr, &mut val, &x.rhs, idx_values, next_chain, level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*pos))
|
.map_err(|err| err.fill_position(x.pos))
|
||||||
}
|
}
|
||||||
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
let (sub_lhs, expr, _) = x.as_ref();
|
match &x.lhs {
|
||||||
|
|
||||||
match sub_lhs {
|
|
||||||
// xxx.prop[expr] | xxx.prop.expr
|
// xxx.prop[expr] | xxx.prop.expr
|
||||||
Expr::Property(p) => {
|
Expr::Property(p) => {
|
||||||
let ((_, getter, setter), pos) = p.as_ref();
|
let ((_, getter, setter), pos) = p.as_ref();
|
||||||
@ -1084,13 +1079,13 @@ impl Engine {
|
|||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
&mut val.into(),
|
&mut val.into(),
|
||||||
expr,
|
&x.rhs,
|
||||||
idx_values,
|
idx_values,
|
||||||
next_chain,
|
next_chain,
|
||||||
level,
|
level,
|
||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*pos))?;
|
.map_err(|err| err.fill_position(x.pos))?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
if updated || may_be_changed {
|
if updated || may_be_changed {
|
||||||
@ -1106,7 +1101,7 @@ impl Engine {
|
|||||||
EvalAltResult::ErrorDotExpr(_, _) => {
|
EvalAltResult::ErrorDotExpr(_, _) => {
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
_ => Err(err.fill_position(*pos)),
|
_ => Err(err.fill_position(x.pos)),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@ -1114,8 +1109,8 @@ impl Engine {
|
|||||||
Ok((result, may_be_changed))
|
Ok((result, may_be_changed))
|
||||||
}
|
}
|
||||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||||
Expr::FnCall(x) if x.1.is_none() => {
|
Expr::FnCall(f) if f.1.is_none() => {
|
||||||
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
|
let ((name, native, _, pos), _, hash, _, def_val) = f.as_ref();
|
||||||
let def_val = def_val.map(Into::<Dynamic>::into);
|
let def_val = def_val.map(Into::<Dynamic>::into);
|
||||||
let args = idx_val.as_fn_call_args();
|
let args = idx_val.as_fn_call_args();
|
||||||
let (mut val, _) = self
|
let (mut val, _) = self
|
||||||
@ -1128,7 +1123,7 @@ impl Engine {
|
|||||||
let target = &mut val.into();
|
let target = &mut val.into();
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, target, expr, idx_values, next_chain,
|
state, lib, this_ptr, target, &x.rhs, idx_values, next_chain,
|
||||||
level, new_val,
|
level, new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*pos))
|
.map_err(|err| err.fill_position(*pos))
|
||||||
@ -1161,7 +1156,14 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
new_val: Option<(Dynamic, Position)>,
|
new_val: Option<(Dynamic, Position)>,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let ((dot_lhs, dot_rhs, op_pos), chain_type) = match expr {
|
let (
|
||||||
|
BinaryExpr {
|
||||||
|
lhs: dot_lhs,
|
||||||
|
rhs: dot_rhs,
|
||||||
|
pos: op_pos,
|
||||||
|
},
|
||||||
|
chain_type,
|
||||||
|
) = match expr {
|
||||||
Expr::Index(x) => (x.as_ref(), ChainType::Index),
|
Expr::Index(x) => (x.as_ref(), ChainType::Index),
|
||||||
Expr::Dot(x) => (x.as_ref(), ChainType::Dot),
|
Expr::Dot(x) => (x.as_ref(), ChainType::Dot),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -1210,9 +1212,7 @@ impl Engine {
|
|||||||
.map_err(|err| err.fill_position(*op_pos))
|
.map_err(|err| err.fill_position(*op_pos))
|
||||||
}
|
}
|
||||||
// {expr}.??? = ??? or {expr}[???] = ???
|
// {expr}.??? = ??? or {expr}[???] = ???
|
||||||
expr if new_val.is_some() => {
|
_ if new_val.is_some() => unreachable!(),
|
||||||
return EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into();
|
|
||||||
}
|
|
||||||
// {expr}.??? or {expr}[???]
|
// {expr}.??? or {expr}[???]
|
||||||
expr => {
|
expr => {
|
||||||
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
@ -1259,7 +1259,7 @@ impl Engine {
|
|||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
Expr::Property(_) => idx_values.push(IndexChainValue::None),
|
Expr::Property(_) => idx_values.push(IndexChainValue::None),
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
let (lhs, rhs, _) = x.as_ref();
|
let BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
||||||
|
|
||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_val = match lhs {
|
let lhs_val = match lhs {
|
||||||
@ -1511,9 +1511,146 @@ impl Engine {
|
|||||||
// Statement block
|
// Statement block
|
||||||
Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level),
|
Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level),
|
||||||
|
|
||||||
|
// lhs[idx_expr]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(_) => {
|
||||||
|
self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lhs.dot_rhs
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Dot(_) => {
|
||||||
|
self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
|
||||||
|
x.0.iter()
|
||||||
|
.map(|item| self.eval_expr(scope, mods, state, lib, this_ptr, item, level))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
)))),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Expr::Map(x) => Ok(Dynamic(Union::Map(Box::new(
|
||||||
|
x.0.iter()
|
||||||
|
.map(|((key, _), expr)| {
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||||
|
.map(|val| (key.clone(), val))
|
||||||
|
})
|
||||||
|
.collect::<Result<HashMap<_, _>, _>>()?,
|
||||||
|
)))),
|
||||||
|
|
||||||
|
// Normal function call
|
||||||
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
|
let ((name, native, cap_scope, pos), _, hash, args_expr, def_val) = x.as_ref();
|
||||||
|
let def_val = def_val.map(Into::<Dynamic>::into);
|
||||||
|
self.make_function_call(
|
||||||
|
scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
|
||||||
|
false, *cap_scope, level,
|
||||||
|
)
|
||||||
|
.map_err(|err| err.fill_position(*pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module-qualified function call
|
||||||
|
Expr::FnCall(x) if x.1.is_some() => {
|
||||||
|
let ((name, _, _, pos), modules, hash, args_expr, def_val) = x.as_ref();
|
||||||
|
self.make_qualified_function_call(
|
||||||
|
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
|
||||||
|
level,
|
||||||
|
)
|
||||||
|
.map_err(|err| err.fill_position(*pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::In(x) => {
|
||||||
|
self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.lhs, &x.rhs, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::And(x) => {
|
||||||
|
Ok((self
|
||||||
|
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
|
||||||
|
.as_bool()
|
||||||
|
.map_err(|err| self.make_type_mismatch_err::<bool>(err, x.lhs.position()))?
|
||||||
|
&& // Short-circuit using &&
|
||||||
|
self
|
||||||
|
.eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)?
|
||||||
|
.as_bool()
|
||||||
|
.map_err(|err| self.make_type_mismatch_err::<bool>(err, x.rhs.position()))?)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Or(x) => {
|
||||||
|
Ok((self
|
||||||
|
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
|
||||||
|
.as_bool()
|
||||||
|
.map_err(|err| self.make_type_mismatch_err::<bool>(err, x.lhs.position()))?
|
||||||
|
|| // Short-circuit using ||
|
||||||
|
self
|
||||||
|
.eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)?
|
||||||
|
.as_bool()
|
||||||
|
.map_err(|err| self.make_type_mismatch_err::<bool>(err, x.rhs.position()))?)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::True(_) => Ok(true.into()),
|
||||||
|
Expr::False(_) => Ok(false.into()),
|
||||||
|
Expr::Unit(_) => Ok(().into()),
|
||||||
|
|
||||||
|
Expr::Custom(custom) => {
|
||||||
|
let func = custom.func();
|
||||||
|
let expressions = custom
|
||||||
|
.keywords()
|
||||||
|
.iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
|
let mut context = EvalContext {
|
||||||
|
engine: self,
|
||||||
|
scope,
|
||||||
|
mods,
|
||||||
|
state,
|
||||||
|
lib,
|
||||||
|
this_ptr,
|
||||||
|
level,
|
||||||
|
};
|
||||||
|
func(&mut context, &expressions)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.check_data_size(result)
|
||||||
|
.map_err(|err| err.fill_position(expr.position()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate a statement
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via
|
||||||
|
/// direct lifetime casting.
|
||||||
|
pub(crate) fn eval_stmt(
|
||||||
|
&self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
mods: &mut Imports,
|
||||||
|
state: &mut State,
|
||||||
|
lib: &[&Module],
|
||||||
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
|
stmt: &Stmt,
|
||||||
|
level: usize,
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state)
|
||||||
|
.map_err(|err| err.fill_position(stmt.position()))?;
|
||||||
|
|
||||||
|
let result = match stmt {
|
||||||
|
// No-op
|
||||||
|
Stmt::Noop(_) => Ok(Default::default()),
|
||||||
|
|
||||||
|
// Expression as statement
|
||||||
|
Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level),
|
||||||
|
|
||||||
// var op= rhs
|
// var op= rhs
|
||||||
Expr::Assignment(x) if x.0.get_variable_access(false).is_some() => {
|
Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => {
|
||||||
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
|
let (lhs_expr, op, rhs_expr) = x.as_ref();
|
||||||
let mut rhs_val = self
|
let mut rhs_val = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -1611,8 +1748,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lhs op= rhs
|
// lhs op= rhs
|
||||||
Expr::Assignment(x) => {
|
Stmt::Assignment(x, op_pos) => {
|
||||||
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
|
let (lhs_expr, op, rhs_expr) = x.as_ref();
|
||||||
let mut rhs_val =
|
let mut rhs_val =
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
|
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
|
||||||
|
|
||||||
@ -1657,157 +1794,18 @@ impl Engine {
|
|||||||
)?;
|
)?;
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
// Constant expression (should be caught during parsing)
|
// Non-lvalue expression (should be caught during parsing)
|
||||||
expr if expr.is_constant() => unreachable!(),
|
_ => unreachable!(),
|
||||||
// Syntax error
|
|
||||||
expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lhs[idx_expr]
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
Expr::Index(_) => {
|
|
||||||
self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lhs.dot_rhs
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
Expr::Dot(_) => {
|
|
||||||
self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
|
|
||||||
x.0.iter()
|
|
||||||
.map(|item| self.eval_expr(scope, mods, state, lib, this_ptr, item, level))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
|
||||||
)))),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
Expr::Map(x) => Ok(Dynamic(Union::Map(Box::new(
|
|
||||||
x.0.iter()
|
|
||||||
.map(|((key, _), expr)| {
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
|
||||||
.map(|val| (key.clone(), val))
|
|
||||||
})
|
|
||||||
.collect::<Result<HashMap<_, _>, _>>()?,
|
|
||||||
)))),
|
|
||||||
|
|
||||||
// Normal function call
|
|
||||||
Expr::FnCall(x) if x.1.is_none() => {
|
|
||||||
let ((name, native, cap_scope, pos), _, hash, args_expr, def_val) = x.as_ref();
|
|
||||||
let def_val = def_val.map(Into::<Dynamic>::into);
|
|
||||||
self.make_function_call(
|
|
||||||
scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
|
|
||||||
false, *cap_scope, level,
|
|
||||||
)
|
|
||||||
.map_err(|err| err.fill_position(*pos))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module-qualified function call
|
|
||||||
Expr::FnCall(x) if x.1.is_some() => {
|
|
||||||
let ((name, _, _, pos), modules, hash, args_expr, def_val) = x.as_ref();
|
|
||||||
self.make_qualified_function_call(
|
|
||||||
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
|
|
||||||
level,
|
|
||||||
)
|
|
||||||
.map_err(|err| err.fill_position(*pos))
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level),
|
|
||||||
|
|
||||||
Expr::And(x) => {
|
|
||||||
let (lhs, rhs, _) = x.as_ref();
|
|
||||||
Ok((self
|
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
|
|
||||||
.as_bool()
|
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
|
|
||||||
&& // Short-circuit using &&
|
|
||||||
self
|
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
|
|
||||||
.as_bool()
|
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::Or(x) => {
|
|
||||||
let (lhs, rhs, _) = x.as_ref();
|
|
||||||
Ok((self
|
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
|
|
||||||
.as_bool()
|
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
|
|
||||||
|| // Short-circuit using ||
|
|
||||||
self
|
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
|
|
||||||
.as_bool()
|
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::True(_) => Ok(true.into()),
|
|
||||||
Expr::False(_) => Ok(false.into()),
|
|
||||||
Expr::Unit(_) => Ok(().into()),
|
|
||||||
|
|
||||||
Expr::Custom(x) => {
|
|
||||||
let func = (x.0).func();
|
|
||||||
let expressions = (x.0)
|
|
||||||
.keywords()
|
|
||||||
.iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect::<StaticVec<_>>();
|
|
||||||
let mut context = EvalContext {
|
|
||||||
engine: self,
|
|
||||||
scope,
|
|
||||||
mods,
|
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
this_ptr,
|
|
||||||
level,
|
|
||||||
};
|
|
||||||
func(&mut context, &expressions)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.check_data_size(result)
|
|
||||||
.map_err(|err| err.fill_position(expr.position()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate a statement
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via
|
|
||||||
/// direct lifetime casting.
|
|
||||||
pub(crate) fn eval_stmt(
|
|
||||||
&self,
|
|
||||||
scope: &mut Scope,
|
|
||||||
mods: &mut Imports,
|
|
||||||
state: &mut State,
|
|
||||||
lib: &[&Module],
|
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
|
||||||
stmt: &Stmt,
|
|
||||||
level: usize,
|
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
|
||||||
self.inc_operations(state)
|
|
||||||
.map_err(|err| err.fill_position(stmt.position()))?;
|
|
||||||
|
|
||||||
let result = match stmt {
|
|
||||||
// No-op
|
|
||||||
Stmt::Noop(_) => Ok(Default::default()),
|
|
||||||
|
|
||||||
// Expression as statement
|
|
||||||
Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level),
|
|
||||||
|
|
||||||
// Block scope
|
// Block scope
|
||||||
Stmt::Block(x) => {
|
Stmt::Block(statements, _) => {
|
||||||
let prev_scope_len = scope.len();
|
let prev_scope_len = scope.len();
|
||||||
let prev_mods_len = mods.len();
|
let prev_mods_len = mods.len();
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
let result = x.0.iter().try_fold(Default::default(), |_, stmt| {
|
let result = statements.iter().try_fold(Default::default(), |_, stmt| {
|
||||||
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
|
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1823,9 +1821,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If-else statement
|
// If-else statement
|
||||||
Stmt::IfThenElse(x) => {
|
Stmt::IfThenElse(expr, x, _) => {
|
||||||
let (expr, if_block, else_block, _) = x.as_ref();
|
let (if_block, else_block) = x.as_ref();
|
||||||
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
||||||
@ -1841,9 +1838,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// While loop
|
// While loop
|
||||||
Stmt::While(x) => loop {
|
Stmt::While(expr, body, _) => loop {
|
||||||
let (expr, body, _) = x.as_ref();
|
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
@ -1866,8 +1861,8 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Loop statement
|
// Loop statement
|
||||||
Stmt::Loop(x) => loop {
|
Stmt::Loop(block, _) => loop {
|
||||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) {
|
match self.eval_stmt(scope, mods, state, lib, this_ptr, block, level) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
EvalAltResult::LoopBreak(false, _) => (),
|
EvalAltResult::LoopBreak(false, _) => (),
|
||||||
@ -1878,8 +1873,8 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// For loop
|
// For loop
|
||||||
Stmt::For(x) => {
|
Stmt::For(expr, x, _) => {
|
||||||
let (name, expr, stmt, _) = x.as_ref();
|
let (name, stmt) = x.as_ref();
|
||||||
let iter_obj = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let iter_obj = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
let iter_type = iter_obj.type_id();
|
let iter_type = iter_obj.type_id();
|
||||||
|
|
||||||
@ -1922,7 +1917,7 @@ impl Engine {
|
|||||||
scope.rewind(scope.len() - 1);
|
scope.rewind(scope.len() - 1);
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
} else {
|
} else {
|
||||||
EvalAltResult::ErrorFor(x.1.position()).into()
|
EvalAltResult::ErrorFor(expr.position()).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1934,17 +1929,17 @@ impl Engine {
|
|||||||
|
|
||||||
// Try/Catch statement
|
// Try/Catch statement
|
||||||
Stmt::TryCatch(x) => {
|
Stmt::TryCatch(x) => {
|
||||||
let ((body, _), var_def, (catch_body, _)) = x.as_ref();
|
let ((try_body, _), var_def, (catch_body, _)) = x.as_ref();
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, mods, state, lib, this_ptr, body, level)
|
.eval_stmt(scope, mods, state, lib, this_ptr, try_body, level)
|
||||||
.map(|_| ().into());
|
.map(|_| ().into());
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => result,
|
Ok(_) => result,
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
mut err @ EvalAltResult::ErrorRuntime(_, _) | mut err
|
mut err @ EvalAltResult::ErrorRuntime(_, _) | mut err
|
||||||
if err.catchable() =>
|
if err.is_catchable() =>
|
||||||
{
|
{
|
||||||
let value = if let EvalAltResult::ErrorRuntime(ref x, _) = err {
|
let value = if let EvalAltResult::ErrorRuntime(ref x, _) = err {
|
||||||
x.clone()
|
x.clone()
|
||||||
@ -1985,40 +1980,33 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return value
|
// Return value
|
||||||
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
|
Stmt::ReturnWithVal((ReturnType::Return, pos), Some(expr), _) => EvalAltResult::Return(
|
||||||
let expr = x.1.as_ref().unwrap();
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
|
||||||
EvalAltResult::Return(
|
*pos,
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
|
)
|
||||||
(x.0).1,
|
.into(),
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty return
|
// Empty return
|
||||||
Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Return => {
|
Stmt::ReturnWithVal((ReturnType::Return, pos), None, _) => {
|
||||||
EvalAltResult::Return(Default::default(), (x.0).1).into()
|
EvalAltResult::Return(Default::default(), *pos).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw value
|
// Throw value
|
||||||
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
|
Stmt::ReturnWithVal((ReturnType::Exception, pos), Some(expr), _) => {
|
||||||
let expr = x.1.as_ref().unwrap();
|
|
||||||
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
EvalAltResult::ErrorRuntime(val, (x.0).1).into()
|
EvalAltResult::ErrorRuntime(val, *pos).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty throw
|
// Empty throw
|
||||||
Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => {
|
Stmt::ReturnWithVal((ReturnType::Exception, pos), None, _) => {
|
||||||
EvalAltResult::ErrorRuntime(().into(), (x.0).1).into()
|
EvalAltResult::ErrorRuntime(().into(), *pos).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::ReturnWithVal(_) => unreachable!(),
|
|
||||||
|
|
||||||
// Let/const statement
|
// Let/const statement
|
||||||
Stmt::Let(x) | Stmt::Const(x) => {
|
Stmt::Let(var_def, expr, _) | Stmt::Const(var_def, expr, _) => {
|
||||||
let ((var_name, _), expr, _) = x.as_ref();
|
|
||||||
let entry_type = match stmt {
|
let entry_type = match stmt {
|
||||||
Stmt::Let(_) => ScopeEntryType::Normal,
|
Stmt::Let(_, _, _) => ScopeEntryType::Normal,
|
||||||
Stmt::Const(_) => ScopeEntryType::Constant,
|
Stmt::Const(_, _, _) => ScopeEntryType::Constant,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2028,16 +2016,14 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
().into()
|
().into()
|
||||||
};
|
};
|
||||||
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(&var_def.0, &state);
|
||||||
scope.push_dynamic_value(var_name, entry_type, val, false);
|
scope.push_dynamic_value(var_name, entry_type, val, false);
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import statement
|
// Import statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => {
|
Stmt::Import(expr, alias, _pos) => {
|
||||||
let (expr, alias, _pos) = x.as_ref();
|
|
||||||
|
|
||||||
// Guard against too many modules
|
// Guard against too many modules
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if state.modules >= self.max_modules() {
|
if state.modules >= self.max_modules() {
|
||||||
@ -2051,9 +2037,9 @@ impl Engine {
|
|||||||
if let Some(resolver) = &self.module_resolver {
|
if let Some(resolver) = &self.module_resolver {
|
||||||
let mut module = resolver.resolve(self, &path, expr.position())?;
|
let mut module = resolver.resolve(self, &path, expr.position())?;
|
||||||
|
|
||||||
if let Some((name, _)) = alias {
|
if let Some(name_def) = alias {
|
||||||
module.index_all_sub_modules();
|
module.index_all_sub_modules();
|
||||||
mods.push((name.clone(), module));
|
mods.push((name_def.0.clone(), module));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.modules += 1;
|
state.modules += 1;
|
||||||
@ -2072,8 +2058,8 @@ impl Engine {
|
|||||||
|
|
||||||
// Export statement
|
// Export statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Export(x) => {
|
Stmt::Export(list, _) => {
|
||||||
for ((id, id_pos), rename) in x.0.iter() {
|
for ((id, id_pos), rename) in list.iter() {
|
||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some(index) = scope.get_index(id).map(|(i, _)| i) {
|
if let Some(index) = scope.get_index(id).map(|(i, _)| i) {
|
||||||
let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);
|
let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);
|
||||||
@ -2087,9 +2073,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Share statement
|
// Share statement
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Stmt::Share(x) => {
|
Stmt::Share(var_name, _) => {
|
||||||
let (var_name, _) = x.as_ref();
|
|
||||||
|
|
||||||
match scope.get_index(var_name) {
|
match scope.get_index(var_name) {
|
||||||
Some((index, ScopeEntryType::Normal)) => {
|
Some((index, ScopeEntryType::Normal)) => {
|
||||||
let (val, _) = scope.get_mut(index);
|
let (val, _) = scope.get_mut(index);
|
||||||
@ -2274,14 +2258,14 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
self.type_names
|
self.type_names
|
||||||
.as_ref()
|
.get(name)
|
||||||
.and_then(|t| t.get(name).map(String::as_str))
|
.map(String::as_str)
|
||||||
.unwrap_or_else(|| map_std_type_name(name))
|
.unwrap_or_else(|| map_std_type_name(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a Box<EvalAltResult<ErrorMismatchDataType>>.
|
/// Make a Box<EvalAltResult<ErrorMismatchDataType>>.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> Box<EvalAltResult> {
|
pub(crate) fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> Box<EvalAltResult> {
|
||||||
EvalAltResult::ErrorMismatchDataType(
|
EvalAltResult::ErrorMismatchDataType(
|
||||||
typ.into(),
|
typ.into(),
|
||||||
self.map_type_name(type_name::<T>()).into(),
|
self.map_type_name(type_name::<T>()).into(),
|
||||||
|
11
src/error.rs
11
src/error.rs
@ -138,10 +138,11 @@ pub enum ParseErrorType {
|
|||||||
///
|
///
|
||||||
/// Never appears under the `no_module` feature.
|
/// Never appears under the `no_module` feature.
|
||||||
WrongExport,
|
WrongExport,
|
||||||
/// Assignment to a copy of a value.
|
|
||||||
AssignmentToCopy,
|
|
||||||
/// Assignment to an a constant variable. Wrapped value is the constant variable name.
|
/// Assignment to an a constant variable. Wrapped value is the constant variable name.
|
||||||
AssignmentToConstant(String),
|
AssignmentToConstant(String),
|
||||||
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
|
/// Wrapped value is the error message (if any).
|
||||||
|
AssignmentToInvalidLHS(String),
|
||||||
/// Expression exceeding the maximum levels of complexity.
|
/// Expression exceeding the maximum levels of complexity.
|
||||||
///
|
///
|
||||||
/// Never appears under the `unchecked` feature.
|
/// Never appears under the `unchecked` feature.
|
||||||
@ -183,8 +184,8 @@ impl ParseErrorType {
|
|||||||
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
|
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
|
||||||
Self::DuplicatedExport(_) => "Duplicated variable/function in export statement",
|
Self::DuplicatedExport(_) => "Duplicated variable/function in export statement",
|
||||||
Self::WrongExport => "Export statement can only appear at global level",
|
Self::WrongExport => "Export statement can only appear at global level",
|
||||||
Self::AssignmentToCopy => "Only a copy of the value is change with this assignment",
|
|
||||||
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
|
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
|
||||||
|
Self::AssignmentToInvalidLHS(_) => "Expression cannot be assigned to",
|
||||||
Self::ExprTooDeep => "Expression exceeds maximum complexity",
|
Self::ExprTooDeep => "Expression exceeds maximum complexity",
|
||||||
Self::LiteralTooLarge(_, _) => "Literal exceeds maximum limit",
|
Self::LiteralTooLarge(_, _) => "Literal exceeds maximum limit",
|
||||||
Self::LoopBreak => "Break statement should only be used inside a loop"
|
Self::LoopBreak => "Break statement should only be used inside a loop"
|
||||||
@ -233,6 +234,10 @@ impl fmt::Display for ParseErrorType {
|
|||||||
|
|
||||||
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
|
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
|
||||||
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
|
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
|
||||||
|
|
||||||
|
Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str(self.desc()),
|
||||||
|
Self::AssignmentToInvalidLHS(s) => f.write_str(s),
|
||||||
|
|
||||||
Self::LiteralTooLarge(typ, max) => {
|
Self::LiteralTooLarge(typ, max) => {
|
||||||
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
write!(f, "{} exceeds the maximum limit ({})", typ, max)
|
||||||
}
|
}
|
||||||
|
@ -387,9 +387,7 @@ impl Engine {
|
|||||||
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
|
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
|
||||||
lib_merged = Default::default();
|
lib_merged = Default::default();
|
||||||
lib_merged.push(env_lib.as_ref());
|
lib_merged.push(env_lib.as_ref());
|
||||||
if !lib.is_empty() {
|
lib_merged.extend(lib.iter().cloned());
|
||||||
lib_merged.extend(lib.iter().cloned());
|
|
||||||
}
|
|
||||||
lib_merged.as_ref()
|
lib_merged.as_ref()
|
||||||
} else {
|
} else {
|
||||||
lib
|
lib
|
||||||
@ -411,6 +409,9 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
// System errors are passed straight-through
|
||||||
|
err if err.is_system_exception() => Err(Box::new(err)),
|
||||||
|
// Other errors are wrapped in `ErrorInFunctionCall`
|
||||||
_ => EvalAltResult::ErrorInFunctionCall(
|
_ => EvalAltResult::ErrorInFunctionCall(
|
||||||
fn_def.name.to_string(),
|
fn_def.name.to_string(),
|
||||||
err,
|
err,
|
||||||
@ -671,6 +672,11 @@ impl Engine {
|
|||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
|
|
||||||
|
let script = script.trim();
|
||||||
|
if script.is_empty() {
|
||||||
|
return Ok(().into());
|
||||||
|
}
|
||||||
|
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -969,11 +975,7 @@ impl Engine {
|
|||||||
let var_name = var_name.as_str().map_err(|err| {
|
let var_name = var_name.as_str().map_err(|err| {
|
||||||
self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
|
self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
|
||||||
})?;
|
})?;
|
||||||
if var_name.is_empty() {
|
return Ok(scope.contains(var_name).into());
|
||||||
return Ok(false.into());
|
|
||||||
} else {
|
|
||||||
return Ok(scope.contains(var_name).into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1001,12 +1003,13 @@ impl Engine {
|
|||||||
self.make_type_mismatch_err::<INT>(err, args_expr[1].position())
|
self.make_type_mismatch_err::<INT>(err, args_expr[1].position())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if fn_name.is_empty() || num_params < 0 {
|
return Ok(if num_params < 0 {
|
||||||
return Ok(false.into());
|
false
|
||||||
} else {
|
} else {
|
||||||
let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty());
|
let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty());
|
||||||
return Ok(lib.iter().any(|&m| m.contains_fn(hash, false)).into());
|
lib.iter().any(|&m| m.contains_fn(hash, false))
|
||||||
}
|
}
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1022,12 +1025,9 @@ impl Engine {
|
|||||||
let script = script.as_str().map_err(|typ| {
|
let script = script.as_str().map_err(|typ| {
|
||||||
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
|
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
|
||||||
})?;
|
})?;
|
||||||
let result = if !script.is_empty() {
|
let result = self
|
||||||
self.eval_script_expr(scope, mods, state, lib, script, level + 1)
|
.eval_script_expr(scope, mods, state, lib, script, level + 1)
|
||||||
.map_err(|err| err.fill_position(args_expr[0].position()))
|
.map_err(|err| err.fill_position(args_expr[0].position()));
|
||||||
} else {
|
|
||||||
Ok(().into())
|
|
||||||
};
|
|
||||||
|
|
||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
// all variable offsets from this point on will be mis-aligned.
|
// all variable offsets from this point on will be mis-aligned.
|
||||||
@ -1055,7 +1055,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
// If the first argument is a variable, and there is no curried arguments, convert to method-call style
|
// If the first argument is a variable, and there is no curried arguments, convert to method-call style
|
||||||
// in order to leverage potential &mut first argument and avoid cloning the value
|
// in order to leverage potential &mut first argument and avoid cloning the value
|
||||||
if args_expr[0].get_variable_access(false).is_some() && curry.is_empty() {
|
if curry.is_empty() && args_expr[0].get_variable_access(false).is_some() {
|
||||||
// func(x, ...) -> x.func(...)
|
// func(x, ...) -> x.func(...)
|
||||||
arg_values = args_expr
|
arg_values = args_expr
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -19,7 +19,7 @@ pub trait Func<ARGS, RET> {
|
|||||||
/// Create a Rust closure from an `AST`.
|
/// Create a Rust closure from an `AST`.
|
||||||
/// The `Engine` and `AST` are consumed and basically embedded into the closure.
|
/// The `Engine` and `AST` are consumed and basically embedded into the closure.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
@ -50,7 +50,7 @@ pub trait Func<ARGS, RET> {
|
|||||||
/// Create a Rust closure from a script.
|
/// Create a Rust closure from a script.
|
||||||
/// The `Engine` is consumed and basically embedded into the closure.
|
/// The `Engine` is consumed and basically embedded into the closure.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
@ -125,7 +125,7 @@ impl AsRef<Module> for Module {
|
|||||||
impl Module {
|
impl Module {
|
||||||
/// Create a new module.
|
/// Create a new module.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -141,7 +141,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Create a new module with a specified capacity for native Rust functions.
|
/// Create a new module with a specified capacity for native Rust functions.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -160,7 +160,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Is the module empty?
|
/// Is the module empty?
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -199,7 +199,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Does a variable exist in the module?
|
/// Does a variable exist in the module?
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -215,7 +215,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Get the value of a module variable.
|
/// Get the value of a module variable.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -231,7 +231,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Get a module variable as a `Dynamic`.
|
/// Get a module variable as a `Dynamic`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -249,7 +249,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is an existing variable of the same name, it is replaced.
|
/// If there is an existing variable of the same name, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -327,7 +327,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Does a sub-module exist in the module?
|
/// Does a sub-module exist in the module?
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -344,7 +344,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Get a sub-module.
|
/// Get a sub-module.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -361,7 +361,7 @@ impl Module {
|
|||||||
|
|
||||||
/// Get a mutable reference to a sub-module.
|
/// Get a mutable reference to a sub-module.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -380,7 +380,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is an existing sub-module of the same name, it is replaced.
|
/// If there is an existing sub-module of the same name, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -402,7 +402,7 @@ impl Module {
|
|||||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
||||||
/// It is also returned by the `set_fn_XXX` calls.
|
/// It is also returned by the `set_fn_XXX` calls.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -500,7 +500,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
|
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -555,7 +555,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -584,7 +584,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -615,7 +615,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -646,7 +646,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust getter function, it is replaced.
|
/// If there is a similar existing Rust getter function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Module;
|
/// use rhai::Module;
|
||||||
@ -669,7 +669,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -706,7 +706,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -743,7 +743,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing setter Rust function, it is replaced.
|
/// If there is a similar existing setter Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -775,7 +775,7 @@ impl Module {
|
|||||||
/// Panics if the type is `Array` or `Map`.
|
/// Panics if the type is `Array` or `Map`.
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// Indexers for arrays, object maps and strings cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -813,7 +813,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -856,7 +856,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -904,7 +904,7 @@ impl Module {
|
|||||||
/// Panics if the type is `Array` or `Map`.
|
/// Panics if the type is `Array` or `Map`.
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// Indexers for arrays, object maps and strings cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -962,7 +962,7 @@ impl Module {
|
|||||||
/// Panics if the type is `Array` or `Map`.
|
/// Panics if the type is `Array` or `Map`.
|
||||||
/// Indexers for arrays, object maps and strings cannot be registered.
|
/// Indexers for arrays, object maps and strings cannot be registered.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -997,7 +997,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -1047,7 +1047,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// If there is a similar existing Rust function, it is replaced.
|
/// If there is a similar existing Rust function, it is replaced.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Module, ImmutableString};
|
/// use rhai::{Module, ImmutableString};
|
||||||
@ -1125,18 +1125,10 @@ impl Module {
|
|||||||
/// The other module is consumed to merge into this module.
|
/// The other module is consumed to merge into this module.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn combine(&mut self, other: Self) -> &mut Self {
|
pub fn combine(&mut self, other: Self) -> &mut Self {
|
||||||
if !other.modules.is_empty() {
|
self.modules.extend(other.modules.into_iter());
|
||||||
self.modules.extend(other.modules.into_iter());
|
self.variables.extend(other.variables.into_iter());
|
||||||
}
|
self.functions.extend(other.functions.into_iter());
|
||||||
if !other.variables.is_empty() {
|
self.type_iterators.extend(other.type_iterators.into_iter());
|
||||||
self.variables.extend(other.variables.into_iter());
|
|
||||||
}
|
|
||||||
if !other.functions.is_empty() {
|
|
||||||
self.functions.extend(other.functions.into_iter());
|
|
||||||
}
|
|
||||||
if !other.type_iterators.is_empty() {
|
|
||||||
self.type_iterators.extend(other.type_iterators.into_iter());
|
|
||||||
}
|
|
||||||
self.all_functions.clear();
|
self.all_functions.clear();
|
||||||
self.all_variables.clear();
|
self.all_variables.clear();
|
||||||
self.indexed = false;
|
self.indexed = false;
|
||||||
@ -1148,20 +1140,38 @@ impl Module {
|
|||||||
/// Sub-modules are flattened onto the root module, with higher level overriding lower level.
|
/// Sub-modules are flattened onto the root module, with higher level overriding lower level.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn combine_flatten(&mut self, other: Self) -> &mut Self {
|
pub fn combine_flatten(&mut self, other: Self) -> &mut Self {
|
||||||
if !other.modules.is_empty() {
|
other.modules.into_iter().for_each(|(_, m)| {
|
||||||
other.modules.into_iter().for_each(|(_, m)| {
|
self.combine_flatten(m);
|
||||||
self.combine_flatten(m);
|
});
|
||||||
});
|
self.variables.extend(other.variables.into_iter());
|
||||||
}
|
self.functions.extend(other.functions.into_iter());
|
||||||
if !other.variables.is_empty() {
|
self.type_iterators.extend(other.type_iterators.into_iter());
|
||||||
self.variables.extend(other.variables.into_iter());
|
self.all_functions.clear();
|
||||||
}
|
self.all_variables.clear();
|
||||||
if !other.functions.is_empty() {
|
self.indexed = false;
|
||||||
self.functions.extend(other.functions.into_iter());
|
self
|
||||||
}
|
}
|
||||||
if !other.type_iterators.is_empty() {
|
|
||||||
self.type_iterators.extend(other.type_iterators.into_iter());
|
/// Poly-fill this module with another module.
|
||||||
}
|
/// Only items not existing in this module are added.
|
||||||
|
#[inline]
|
||||||
|
pub fn fill_with(&mut self, other: &Self) -> &mut Self {
|
||||||
|
other.modules.iter().for_each(|(k, v)| {
|
||||||
|
if !self.modules.contains_key(k) {
|
||||||
|
self.modules.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
other.variables.iter().for_each(|(k, v)| {
|
||||||
|
if !self.variables.contains_key(k) {
|
||||||
|
self.variables.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
other.functions.iter().for_each(|(&k, v)| {
|
||||||
|
self.functions.entry(k).or_insert_with(|| v.clone());
|
||||||
|
});
|
||||||
|
other.type_iterators.iter().for_each(|(&k, &v)| {
|
||||||
|
self.type_iterators.entry(k).or_insert(v);
|
||||||
|
});
|
||||||
self.all_functions.clear();
|
self.all_functions.clear();
|
||||||
self.all_variables.clear();
|
self.all_variables.clear();
|
||||||
self.indexed = false;
|
self.indexed = false;
|
||||||
@ -1181,42 +1191,32 @@ impl Module {
|
|||||||
mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool,
|
mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if !other.modules.is_empty() {
|
other.modules.iter().for_each(|(k, v)| {
|
||||||
other.modules.iter().for_each(|(k, v)| {
|
let mut m = Self::new();
|
||||||
let mut m = Self::new();
|
m.merge_filtered(v, _filter);
|
||||||
m.merge_filtered(v, _filter);
|
self.modules.insert(k.clone(), m);
|
||||||
self.modules.insert(k.clone(), m);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
if !other.modules.is_empty() {
|
self.modules
|
||||||
self.modules
|
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||||
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
|
|
||||||
}
|
|
||||||
if !other.variables.is_empty() {
|
|
||||||
self.variables
|
|
||||||
.extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone())));
|
|
||||||
}
|
|
||||||
if !other.functions.is_empty() {
|
|
||||||
self.functions.extend(
|
|
||||||
other
|
|
||||||
.functions
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, (_, _, _, _, v))| match v {
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
CallableFunction::Script(f) => {
|
|
||||||
_filter(f.access, f.name.as_str(), f.params.len())
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
})
|
|
||||||
.map(|(&k, v)| (k, v.clone())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !other.type_iterators.is_empty() {
|
self.variables
|
||||||
self.type_iterators
|
.extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||||
.extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone())));
|
self.functions.extend(
|
||||||
}
|
other
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, (_, _, _, _, v))| match v {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
CallableFunction::Script(f) => {
|
||||||
|
_filter(f.access, f.name.as_str(), f.params.len())
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
.map(|(&k, v)| (k, v.clone())),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.type_iterators.extend(other.type_iterators.iter());
|
||||||
self.all_functions.clear();
|
self.all_functions.clear();
|
||||||
self.all_variables.clear();
|
self.all_variables.clear();
|
||||||
self.indexed = false;
|
self.indexed = false;
|
||||||
@ -1327,7 +1327,7 @@ impl Module {
|
|||||||
/// defined in the module, are _merged_ into a _unified_ namespace before each call.
|
/// defined in the module, are _merged_ into a _unified_ namespace before each call.
|
||||||
/// Therefore, all functions will be found.
|
/// Therefore, all functions will be found.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
@ -8,7 +8,7 @@ use crate::stdlib::{boxed::Box, ops::AddAssign, vec::Vec};
|
|||||||
/// Module resolution service that holds a collection of module resolves,
|
/// Module resolution service that holds a collection of module resolves,
|
||||||
/// to be searched in sequential order.
|
/// to be searched in sequential order.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Engine, Module};
|
/// use rhai::{Engine, Module};
|
||||||
@ -28,7 +28,7 @@ pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
|
|||||||
impl ModuleResolversCollection {
|
impl ModuleResolversCollection {
|
||||||
/// Create a new `ModuleResolversCollection`.
|
/// Create a new `ModuleResolversCollection`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Engine, Module};
|
/// use rhai::{Engine, Module};
|
||||||
@ -80,9 +80,7 @@ impl ModuleResolversCollection {
|
|||||||
/// The other `ModuleResolversCollection` is consumed.
|
/// The other `ModuleResolversCollection` is consumed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn append(&mut self, other: Self) {
|
pub fn append(&mut self, other: Self) {
|
||||||
if !other.is_empty() {
|
self.0.extend(other.0.into_iter());
|
||||||
self.0.extend(other.0.into_iter());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::Str
|
|||||||
/// plus all those defined within the same module are _merged_ into a _unified_ namespace before
|
/// plus all those defined within the same module are _merged_ into a _unified_ namespace before
|
||||||
/// the call. Therefore, functions in a module script can cross-call each other.
|
/// the call. Therefore, functions in a module script can cross-call each other.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
@ -53,7 +53,7 @@ impl Default for FileModuleResolver {
|
|||||||
impl FileModuleResolver {
|
impl FileModuleResolver {
|
||||||
/// Create a new `FileModuleResolver` with a specific base path.
|
/// Create a new `FileModuleResolver` with a specific base path.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
@ -75,7 +75,7 @@ impl FileModuleResolver {
|
|||||||
///
|
///
|
||||||
/// The default extension is `.rhai`.
|
/// The default extension is `.rhai`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
@ -102,7 +102,7 @@ impl FileModuleResolver {
|
|||||||
|
|
||||||
/// Create a new `FileModuleResolver` with the current directory as base path.
|
/// Create a new `FileModuleResolver` with the current directory as base path.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
|
@ -7,7 +7,7 @@ use crate::stdlib::{boxed::Box, collections::HashMap, ops::AddAssign, string::St
|
|||||||
|
|
||||||
/// Module resolution service that serves modules added into it.
|
/// Module resolution service that serves modules added into it.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Engine, Module};
|
/// use rhai::{Engine, Module};
|
||||||
@ -28,7 +28,7 @@ pub struct StaticModuleResolver(HashMap<String, Module>);
|
|||||||
impl StaticModuleResolver {
|
impl StaticModuleResolver {
|
||||||
/// Create a new `StaticModuleResolver`.
|
/// Create a new `StaticModuleResolver`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Engine, Module};
|
/// use rhai::{Engine, Module};
|
||||||
|
349
src/optimize.rs
349
src/optimize.rs
@ -7,7 +7,7 @@ use crate::engine::{
|
|||||||
};
|
};
|
||||||
use crate::fn_call::run_builtin_binary_op;
|
use crate::fn_call::run_builtin_binary_op;
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST};
|
use crate::parser::{map_dynamic_to_expr, BinaryExpr, Expr, ScriptFnDef, Stmt, AST};
|
||||||
use crate::scope::{Entry as ScopeEntry, Scope};
|
use crate::scope::{Entry as ScopeEntry, Scope};
|
||||||
use crate::token::{is_valid_identifier, Position};
|
use crate::token::{is_valid_identifier, Position};
|
||||||
use crate::{calc_fn_hash, StaticVec};
|
use crate::{calc_fn_hash, StaticVec};
|
||||||
@ -163,87 +163,91 @@ fn call_fn_with_constant_arguments(
|
|||||||
/// Optimize a statement.
|
/// Optimize a statement.
|
||||||
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
|
// id op= expr
|
||||||
|
Stmt::Assignment(x, pos) => Stmt::Assignment(
|
||||||
|
Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))),
|
||||||
|
pos,
|
||||||
|
),
|
||||||
|
// if false { if_block } -> Noop
|
||||||
|
Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => {
|
||||||
|
state.set_dirty();
|
||||||
|
Stmt::Noop(pos)
|
||||||
|
}
|
||||||
|
// if true { if_block } -> if_block
|
||||||
|
Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => optimize_stmt(x.0, state, true),
|
||||||
// if expr { Noop }
|
// if expr { Noop }
|
||||||
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) && x.2.is_none() => {
|
Stmt::IfThenElse(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = x.0.position();
|
let pos = condition.position();
|
||||||
let expr = optimize_expr(x.0, state);
|
let expr = optimize_expr(condition, state);
|
||||||
|
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
// -> { expr, Noop }
|
// -> { expr, Noop }
|
||||||
let mut statements = StaticVec::new();
|
let mut statements = Vec::new();
|
||||||
statements.push(Stmt::Expr(Box::new(expr)));
|
statements.push(Stmt::Expr(expr));
|
||||||
statements.push(x.1);
|
statements.push(x.0);
|
||||||
|
|
||||||
Stmt::Block(Box::new((statements, pos)))
|
Stmt::Block(statements, pos)
|
||||||
} else {
|
} else {
|
||||||
// -> expr
|
// -> expr
|
||||||
Stmt::Expr(Box::new(expr))
|
Stmt::Expr(expr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if expr { if_block }
|
// if expr { if_block }
|
||||||
Stmt::IfThenElse(x) if x.2.is_none() => match x.0 {
|
Stmt::IfThenElse(condition, x, pos) if x.1.is_none() => Stmt::IfThenElse(
|
||||||
// if false { if_block } -> Noop
|
optimize_expr(condition, state),
|
||||||
Expr::False(pos) => {
|
Box::new((optimize_stmt(x.0, state, true), None)),
|
||||||
state.set_dirty();
|
pos,
|
||||||
Stmt::Noop(pos)
|
),
|
||||||
}
|
// if false { if_block } else { else_block } -> else_block
|
||||||
// if true { if_block } -> if_block
|
Stmt::IfThenElse(Expr::False(_), x, _) if x.1.is_some() => {
|
||||||
Expr::True(_) => optimize_stmt(x.1, state, true),
|
optimize_stmt(x.1.unwrap(), state, true)
|
||||||
// if expr { if_block }
|
}
|
||||||
expr => Stmt::IfThenElse(Box::new((
|
// if true { if_block } else { else_block } -> if_block
|
||||||
optimize_expr(expr, state),
|
Stmt::IfThenElse(Expr::True(_), x, _) => optimize_stmt(x.0, state, true),
|
||||||
optimize_stmt(x.1, state, true),
|
|
||||||
None,
|
|
||||||
x.3,
|
|
||||||
))),
|
|
||||||
},
|
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
Stmt::IfThenElse(x) if x.2.is_some() => match x.0 {
|
Stmt::IfThenElse(condition, x, pos) => Stmt::IfThenElse(
|
||||||
// if false { if_block } else { else_block } -> else_block
|
optimize_expr(condition, state),
|
||||||
Expr::False(_) => optimize_stmt(x.2.unwrap(), state, true),
|
Box::new((
|
||||||
// if true { if_block } else { else_block } -> if_block
|
optimize_stmt(x.0, state, true),
|
||||||
Expr::True(_) => optimize_stmt(x.1, state, true),
|
match optimize_stmt(x.1.unwrap(), state, true) {
|
||||||
// if expr { if_block } else { else_block }
|
|
||||||
expr => Stmt::IfThenElse(Box::new((
|
|
||||||
optimize_expr(expr, state),
|
|
||||||
optimize_stmt(x.1, state, true),
|
|
||||||
match optimize_stmt(x.2.unwrap(), state, true) {
|
|
||||||
Stmt::Noop(_) => None, // Noop -> no else block
|
Stmt::Noop(_) => None, // Noop -> no else block
|
||||||
stmt => Some(stmt),
|
stmt => Some(stmt),
|
||||||
},
|
},
|
||||||
x.3,
|
)),
|
||||||
))),
|
pos,
|
||||||
},
|
),
|
||||||
|
|
||||||
|
// while false { block } -> Noop
|
||||||
|
Stmt::While(Expr::False(pos), _, _) => {
|
||||||
|
state.set_dirty();
|
||||||
|
Stmt::Noop(pos)
|
||||||
|
}
|
||||||
|
// while true { block } -> loop { block }
|
||||||
|
Stmt::While(Expr::True(_), block, pos) => {
|
||||||
|
Stmt::Loop(Box::new(optimize_stmt(*block, state, false)), pos)
|
||||||
|
}
|
||||||
// while expr { block }
|
// while expr { block }
|
||||||
Stmt::While(x) => match x.0 {
|
Stmt::While(condition, block, pos) => {
|
||||||
// while false { block } -> Noop
|
match optimize_stmt(*block, state, false) {
|
||||||
Expr::False(pos) => {
|
|
||||||
state.set_dirty();
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
// while true { block } -> loop { block }
|
|
||||||
Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))),
|
|
||||||
// while expr { block }
|
|
||||||
expr => match optimize_stmt(x.1, state, false) {
|
|
||||||
// while expr { break; } -> { expr; }
|
// while expr { break; } -> { expr; }
|
||||||
Stmt::Break(pos) => {
|
Stmt::Break(pos) => {
|
||||||
// Only a single break statement - turn into running the guard expression once
|
// Only a single break statement - turn into running the guard expression once
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let mut statements = StaticVec::new();
|
let mut statements = Vec::new();
|
||||||
statements.push(Stmt::Expr(Box::new(optimize_expr(expr, state))));
|
statements.push(Stmt::Expr(optimize_expr(condition, state)));
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
statements.push(Stmt::Noop(pos))
|
statements.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
Stmt::Block(Box::new((statements, pos)))
|
Stmt::Block(statements, pos)
|
||||||
}
|
}
|
||||||
// while expr { block }
|
// while expr { block }
|
||||||
stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt, x.2))),
|
stmt => Stmt::While(optimize_expr(condition, state), Box::new(stmt), pos),
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
// loop { block }
|
// loop { block }
|
||||||
Stmt::Loop(x) => match optimize_stmt(x.0, state, false) {
|
Stmt::Loop(block, pos) => match optimize_stmt(*block, state, false) {
|
||||||
// loop { break; } -> Noop
|
// loop { break; } -> Noop
|
||||||
Stmt::Break(pos) => {
|
Stmt::Break(pos) => {
|
||||||
// Only a single break statement
|
// Only a single break statement
|
||||||
@ -251,59 +255,52 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
// loop { block }
|
// loop { block }
|
||||||
stmt => Stmt::Loop(Box::new((stmt, x.1))),
|
stmt => Stmt::Loop(Box::new(stmt), pos),
|
||||||
},
|
},
|
||||||
// for id in expr { block }
|
// for id in expr { block }
|
||||||
Stmt::For(x) => Stmt::For(Box::new((
|
Stmt::For(iterable, x, pos) => {
|
||||||
x.0,
|
let (var_name, block) = *x;
|
||||||
optimize_expr(x.1, state),
|
Stmt::For(
|
||||||
optimize_stmt(x.2, state, false),
|
optimize_expr(iterable, state),
|
||||||
x.3,
|
Box::new((var_name, optimize_stmt(block, state, false))),
|
||||||
))),
|
pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
// let id = expr;
|
// let id = expr;
|
||||||
Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new((
|
Stmt::Let(name, Some(expr), pos) => Stmt::Let(name, Some(optimize_expr(expr, state)), pos),
|
||||||
x.0,
|
|
||||||
Some(optimize_expr(x.1.unwrap(), state)),
|
|
||||||
x.2,
|
|
||||||
))),
|
|
||||||
// let id;
|
// let id;
|
||||||
stmt @ Stmt::Let(_) => stmt,
|
stmt @ Stmt::Let(_, None, _) => stmt,
|
||||||
// import expr as var;
|
// import expr as var;
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
|
Stmt::Import(expr, alias, pos) => Stmt::Import(optimize_expr(expr, state), alias, pos),
|
||||||
// { block }
|
// { block }
|
||||||
Stmt::Block(x) => {
|
Stmt::Block(statements, pos) => {
|
||||||
let orig_len = x.0.len(); // Original number of statements in the block, for change detection
|
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
||||||
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
|
||||||
let pos = x.1;
|
|
||||||
|
|
||||||
// Optimize each statement in the block
|
// Optimize each statement in the block
|
||||||
let mut result: Vec<_> =
|
let mut result: Vec<_> = statements
|
||||||
x.0.into_iter()
|
.into_iter()
|
||||||
.map(|stmt| match stmt {
|
.map(|stmt| match stmt {
|
||||||
// Add constant literals into the state
|
// Add constant literals into the state
|
||||||
Stmt::Const(mut v) => {
|
Stmt::Const(name, Some(expr), pos) if expr.is_literal() => {
|
||||||
if let Some(expr) = v.1 {
|
state.set_dirty();
|
||||||
let expr = optimize_expr(expr, state);
|
state.push_constant(&name.0, expr);
|
||||||
|
Stmt::Noop(pos) // No need to keep constants
|
||||||
if expr.is_literal() {
|
}
|
||||||
state.set_dirty();
|
Stmt::Const(name, Some(expr), pos) if expr.is_literal() => {
|
||||||
state.push_constant(&(v.0).0, expr);
|
let expr = optimize_expr(expr, state);
|
||||||
Stmt::Noop(pos) // No need to keep constants
|
Stmt::Const(name, Some(expr), pos)
|
||||||
} else {
|
}
|
||||||
v.1 = Some(expr);
|
Stmt::Const(name, None, pos) => {
|
||||||
Stmt::Const(v)
|
state.set_dirty();
|
||||||
}
|
state.push_constant(&name.0, Expr::Unit(name.1));
|
||||||
} else {
|
Stmt::Noop(pos) // No need to keep constants
|
||||||
state.set_dirty();
|
}
|
||||||
state.push_constant(&(v.0).0, Expr::Unit((v.0).1));
|
// Optimize the statement
|
||||||
Stmt::Noop(pos) // No need to keep constants
|
stmt => optimize_stmt(stmt, state, preserve_result),
|
||||||
}
|
})
|
||||||
}
|
.collect();
|
||||||
// Optimize the statement
|
|
||||||
_ => optimize_stmt(stmt, state, preserve_result),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Remove all raw expression statements that are pure except for the very last statement
|
// Remove all raw expression statements that are pure except for the very last statement
|
||||||
let last_stmt = if preserve_result { result.pop() } else { None };
|
let last_stmt = if preserve_result { result.pop() } else { None };
|
||||||
@ -321,9 +318,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
|
|
||||||
while let Some(expr) = result.pop() {
|
while let Some(expr) = result.pop() {
|
||||||
match expr {
|
match expr {
|
||||||
Stmt::Let(x) => removed = x.1.as_ref().map(Expr::is_pure).unwrap_or(true),
|
Stmt::Let(_, expr, _) => {
|
||||||
|
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||||
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => removed = x.0.is_pure(),
|
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
||||||
_ => {
|
_ => {
|
||||||
result.push(expr);
|
result.push(expr);
|
||||||
break;
|
break;
|
||||||
@ -341,7 +340,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, s)| optimize_stmt(s, state, i == 0))
|
.map(|(i, stmt)| optimize_stmt(stmt, state, i == 0))
|
||||||
.rev()
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
@ -355,7 +354,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::ReturnWithVal(_) | Stmt::Break(_) => dead_code = true,
|
Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => dead_code = true,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,23 +369,23 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
// Pop the stack and remove all the local constants
|
// Pop the stack and remove all the local constants
|
||||||
state.restore_constants(orig_constants_len);
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
match result[..] {
|
match &result[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
// Only one let statement - leave it alone
|
// Only one let statement - leave it alone
|
||||||
[Stmt::Let(_)] => Stmt::Block(Box::new((result.into(), pos))),
|
[x] if matches!(x, Stmt::Let(_, _, _)) => Stmt::Block(result, pos),
|
||||||
// Only one import statement - leave it alone
|
// Only one import statement - leave it alone
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
[Stmt::Import(_)] => Stmt::Block(Box::new((result.into(), pos))),
|
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(result, pos),
|
||||||
// Only one statement - promote
|
// Only one statement - promote
|
||||||
[_] => {
|
[_] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
result.remove(0)
|
result.remove(0)
|
||||||
}
|
}
|
||||||
_ => Stmt::Block(Box::new((result.into(), pos))),
|
_ => Stmt::Block(result, pos),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// try { block } catch ( var ) { block }
|
// try { block } catch ( var ) { block }
|
||||||
@ -394,25 +393,31 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
// If try block is pure, there will never be any exceptions
|
// If try block is pure, there will never be any exceptions
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let pos = (x.0).0.position();
|
let pos = (x.0).0.position();
|
||||||
let mut statements: StaticVec<_> = Default::default();
|
let mut statements: Vec<_> = Default::default();
|
||||||
statements.push(optimize_stmt((x.0).0, state, preserve_result));
|
statements.push(optimize_stmt((x.0).0, state, preserve_result));
|
||||||
statements.push(Stmt::Noop(pos));
|
statements.push(Stmt::Noop(pos));
|
||||||
Stmt::Block(Box::new((statements, pos)))
|
Stmt::Block(statements, pos)
|
||||||
}
|
}
|
||||||
// try { block } catch ( var ) { block }
|
// try { block } catch ( var ) { block }
|
||||||
Stmt::TryCatch(x) => Stmt::TryCatch(Box::new((
|
Stmt::TryCatch(x) => {
|
||||||
(optimize_stmt((x.0).0, state, false), (x.0).1),
|
let ((try_block, try_pos), var_name, (catch_block, catch_pos)) = *x;
|
||||||
x.1,
|
Stmt::TryCatch(Box::new((
|
||||||
(optimize_stmt((x.2).0, state, false), (x.2).1),
|
(optimize_stmt(try_block, state, false), try_pos),
|
||||||
))),
|
var_name,
|
||||||
|
(optimize_stmt(catch_block, state, false), catch_pos),
|
||||||
|
)))
|
||||||
|
}
|
||||||
// expr;
|
// expr;
|
||||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
Stmt::Expr(Expr::Stmt(x)) if matches!(x.0, Stmt::Expr(_)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
optimize_stmt(x.0, state, preserve_result)
|
||||||
|
}
|
||||||
|
// expr;
|
||||||
|
Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)),
|
||||||
// return expr;
|
// return expr;
|
||||||
Stmt::ReturnWithVal(x) if x.1.is_some() => Stmt::ReturnWithVal(Box::new((
|
Stmt::ReturnWithVal(ret, Some(expr), pos) => {
|
||||||
x.0,
|
Stmt::ReturnWithVal(ret, Some(optimize_expr(expr, state)), pos)
|
||||||
Some(optimize_expr(x.1.unwrap(), state)),
|
}
|
||||||
x.2,
|
|
||||||
))),
|
|
||||||
// All other statements - skip
|
// All other statements - skip
|
||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
}
|
}
|
||||||
@ -432,27 +437,25 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
match expr {
|
match expr {
|
||||||
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
||||||
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
|
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
|
||||||
// ( stmt )
|
// { stmt }
|
||||||
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
|
Expr::Stmt(x) => match x.0 {
|
||||||
// ( Noop ) -> ()
|
// {} -> ()
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::Unit(x.1)
|
Expr::Unit(x.1)
|
||||||
}
|
}
|
||||||
// ( expr ) -> expr
|
// { expr } -> expr
|
||||||
Stmt::Expr(expr) => {
|
Stmt::Expr(expr) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr
|
optimize_expr(expr, state)
|
||||||
}
|
}
|
||||||
// ( stmt )
|
// { stmt }
|
||||||
stmt => Expr::Stmt(Box::new((stmt, x.1))),
|
stmt => Expr::Stmt(Box::new((optimize_stmt(stmt, state, true), x.1))),
|
||||||
},
|
},
|
||||||
// id op= expr
|
|
||||||
Expr::Assignment(x) => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state), x.3))),
|
|
||||||
|
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x) => match (x.0, x.1) {
|
Expr::Dot(x) => match (x.lhs, x.rhs) {
|
||||||
// map.string
|
// map.string
|
||||||
(Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
(Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||||
let ((prop, _, _), _) = p.as_ref();
|
let ((prop, _, _), _) = p.as_ref();
|
||||||
@ -465,12 +468,16 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
.unwrap_or_else(|| Expr::Unit(pos))
|
.unwrap_or_else(|| Expr::Unit(pos))
|
||||||
}
|
}
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
(lhs, rhs) => Expr::Dot(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2)))
|
(lhs, rhs) => Expr::Dot(Box::new(BinaryExpr {
|
||||||
|
lhs: optimize_expr(lhs, state),
|
||||||
|
rhs: optimize_expr(rhs, state),
|
||||||
|
pos: x.pos
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(x) => match (x.0, x.1) {
|
Expr::Index(x) => match (x.lhs, x.rhs) {
|
||||||
// array[int]
|
// array[int]
|
||||||
(Expr::Array(mut a), Expr::IntegerConstant(i))
|
(Expr::Array(mut a), Expr::IntegerConstant(i))
|
||||||
if i.0 >= 0 && (i.0 as usize) < a.0.len() && a.0.iter().all(Expr::is_pure) =>
|
if i.0 >= 0 && (i.0 as usize) < a.0.len() && a.0.iter().all(Expr::is_pure) =>
|
||||||
@ -499,7 +506,11 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1)))
|
Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1)))
|
||||||
}
|
}
|
||||||
// lhs[rhs]
|
// lhs[rhs]
|
||||||
(lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
|
(lhs, rhs) => Expr::Index(Box::new(BinaryExpr {
|
||||||
|
lhs: optimize_expr(lhs, state),
|
||||||
|
rhs: optimize_expr(rhs, state),
|
||||||
|
pos: x.pos
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -512,7 +523,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
.into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
|
.into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
|
||||||
.collect(), m.1))),
|
.collect(), m.1))),
|
||||||
// lhs in rhs
|
// lhs in rhs
|
||||||
Expr::In(x) => match (x.0, x.1) {
|
Expr::In(x) => match (x.lhs, x.rhs) {
|
||||||
// "xxx" in "xxxxx"
|
// "xxx" in "xxxxx"
|
||||||
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
|
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -544,10 +555,14 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// lhs in rhs
|
// lhs in rhs
|
||||||
(lhs, rhs) => Expr::In(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
|
(lhs, rhs) => Expr::In(Box::new(BinaryExpr {
|
||||||
|
lhs: optimize_expr(lhs, state),
|
||||||
|
rhs: optimize_expr(rhs, state),
|
||||||
|
pos: x.pos
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
Expr::And(x) => match (x.0, x.1) {
|
Expr::And(x) => match (x.lhs, x.rhs) {
|
||||||
// true && rhs -> rhs
|
// true && rhs -> rhs
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -564,10 +579,14 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
optimize_expr(lhs, state)
|
optimize_expr(lhs, state)
|
||||||
}
|
}
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
(lhs, rhs) => Expr::And(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
|
(lhs, rhs) => Expr::And(Box::new(BinaryExpr {
|
||||||
|
lhs: optimize_expr(lhs, state),
|
||||||
|
rhs: optimize_expr(rhs, state),
|
||||||
|
pos: x.pos
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
// lhs || rhs
|
// lhs || rhs
|
||||||
Expr::Or(x) => match (x.0, x.1) {
|
Expr::Or(x) => match (x.lhs, x.rhs) {
|
||||||
// false || rhs -> rhs
|
// false || rhs -> rhs
|
||||||
(Expr::False(_), rhs) => {
|
(Expr::False(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -584,7 +603,11 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
optimize_expr(lhs, state)
|
optimize_expr(lhs, state)
|
||||||
}
|
}
|
||||||
// lhs || rhs
|
// lhs || rhs
|
||||||
(lhs, rhs) => Expr::Or(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
|
(lhs, rhs) => Expr::Or(Box::new(BinaryExpr {
|
||||||
|
lhs: optimize_expr(lhs, state),
|
||||||
|
rhs: optimize_expr(rhs, state),
|
||||||
|
pos: x.pos
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Do not call some special keywords
|
// Do not call some special keywords
|
||||||
@ -748,35 +771,35 @@ fn optimize(
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, stmt)| {
|
.map(|(i, stmt)| {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Const(mut v) => {
|
Stmt::Const(var_def, Some(expr), pos) => {
|
||||||
// Load constants
|
// Load constants
|
||||||
if let Some(expr) = v.1 {
|
let expr = optimize_expr(expr, &mut state);
|
||||||
let expr = optimize_expr(expr, &mut state);
|
|
||||||
|
|
||||||
if expr.is_literal() {
|
if expr.is_literal() {
|
||||||
state.push_constant(&(v.0).0, expr.clone());
|
state.push_constant(&var_def.0, expr.clone());
|
||||||
}
|
|
||||||
|
|
||||||
v.1 = if expr.is_unit() {
|
|
||||||
state.set_dirty();
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(expr)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
state.push_constant(&(v.0).0, Expr::Unit((v.0).1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep it in the global scope
|
// Keep it in the global scope
|
||||||
Stmt::Const(v)
|
if expr.is_unit() {
|
||||||
|
state.set_dirty();
|
||||||
|
Stmt::Const(var_def, None, pos)
|
||||||
|
} else {
|
||||||
|
Stmt::Const(var_def, Some(expr), pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stmt::Const(ref var_def, None, _) => {
|
||||||
|
state.push_constant(&var_def.0, Expr::Unit(var_def.1));
|
||||||
|
|
||||||
|
// Keep it in the global scope
|
||||||
|
stmt
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Keep all variable declarations at this level
|
// Keep all variable declarations at this level
|
||||||
// and always keep the last return value
|
// and always keep the last return value
|
||||||
let keep = match stmt {
|
let keep = match stmt {
|
||||||
Stmt::Let(_) => true,
|
Stmt::Let(_, _, _) => true,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(_) => true,
|
Stmt::Import(_, _, _) => true,
|
||||||
_ => i == num_statements - 1,
|
_ => i == num_statements - 1,
|
||||||
};
|
};
|
||||||
optimize_stmt(stmt, &mut state, keep)
|
optimize_stmt(stmt, &mut state, keep)
|
||||||
@ -798,7 +821,7 @@ fn optimize(
|
|||||||
|
|
||||||
// Add back the last statement unless it is a lone No-op
|
// Add back the last statement unless it is a lone No-op
|
||||||
if let Some(stmt) = last_stmt {
|
if let Some(stmt) = last_stmt {
|
||||||
if !result.is_empty() || !matches!(stmt, Stmt::Noop(_)) {
|
if !result.is_empty() || !stmt.is_noop() {
|
||||||
result.push(stmt);
|
result.push(stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -859,16 +882,12 @@ pub fn optimize_into_ast(
|
|||||||
// {} -> Noop
|
// {} -> Noop
|
||||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||||
// { return val; } -> val
|
// { return val; } -> val
|
||||||
Stmt::ReturnWithVal(x)
|
Stmt::ReturnWithVal((ReturnType::Return, _), Some(expr), _) => {
|
||||||
if x.1.is_some() && (x.0).0 == ReturnType::Return =>
|
Stmt::Expr(expr)
|
||||||
{
|
|
||||||
Stmt::Expr(Box::new(x.1.unwrap()))
|
|
||||||
}
|
}
|
||||||
// { return; } -> ()
|
// { return; } -> ()
|
||||||
Stmt::ReturnWithVal(x)
|
Stmt::ReturnWithVal((ReturnType::Return, pos), None, _) => {
|
||||||
if x.1.is_none() && (x.0).0 == ReturnType::Return =>
|
Stmt::Expr(Expr::Unit(pos))
|
||||||
{
|
|
||||||
Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
|
|
||||||
}
|
}
|
||||||
// All others
|
// All others
|
||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
|
@ -45,6 +45,8 @@ where
|
|||||||
Ok(StepRange::<T>(from, to, step))
|
Ok(StepRange::<T>(from, to, step))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
#[cfg(not(feature = "only_i64"))]
|
||||||
macro_rules! reg_range {
|
macro_rules! reg_range {
|
||||||
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
||||||
$(
|
$(
|
||||||
@ -54,6 +56,8 @@ macro_rules! reg_range {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
#[cfg(not(feature = "only_i64"))]
|
||||||
macro_rules! reg_step {
|
macro_rules! reg_step {
|
||||||
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
||||||
$(
|
$(
|
||||||
|
@ -94,7 +94,7 @@ impl PackagesCollection {
|
|||||||
/// Functions can be added to the package using the standard module methods such as
|
/// Functions can be added to the package using the standard module methods such as
|
||||||
/// `set_fn_2`, `set_fn_3_mut`, `set_fn_0` etc.
|
/// `set_fn_2`, `set_fn_3_mut`, `set_fn_0` etc.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Dynamic, EvalAltResult};
|
/// use rhai::{Dynamic, EvalAltResult};
|
||||||
|
547
src/parser.rs
547
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -20,12 +20,18 @@ use crate::stdlib::{
|
|||||||
///
|
///
|
||||||
/// All wrapped `Position` values represent the location in the script where the error occurs.
|
/// All wrapped `Position` values represent the location in the script where the error occurs.
|
||||||
///
|
///
|
||||||
|
/// # Thread Safety
|
||||||
|
///
|
||||||
/// Currently, `EvalAltResult` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
/// Currently, `EvalAltResult` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum EvalAltResult {
|
pub enum EvalAltResult {
|
||||||
/// System error. Wrapped values are the error message and the internal error.
|
/// System error. Wrapped values are the error message and the internal error.
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
ErrorSystem(String, Box<dyn Error>),
|
ErrorSystem(String, Box<dyn Error>),
|
||||||
|
/// System error. Wrapped values are the error message and the internal error.
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
ErrorSystem(String, Box<dyn Error + Send + Sync>),
|
||||||
|
|
||||||
/// Syntax error.
|
/// Syntax error.
|
||||||
ErrorParsing(ParseErrorType, Position),
|
ErrorParsing(ParseErrorType, Position),
|
||||||
@ -65,8 +71,6 @@ pub enum EvalAltResult {
|
|||||||
ErrorFor(Position),
|
ErrorFor(Position),
|
||||||
/// Data race detected when accessing a variable. Wrapped value is the variable name.
|
/// Data race detected when accessing a variable. Wrapped value is the variable name.
|
||||||
ErrorDataRace(String, Position),
|
ErrorDataRace(String, Position),
|
||||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
|
||||||
ErrorAssignmentToUnknownLHS(Position),
|
|
||||||
/// Assignment to a constant variable. Wrapped value is the variable name.
|
/// Assignment to a constant variable. Wrapped value is the variable name.
|
||||||
ErrorAssignmentToConstant(String, Position),
|
ErrorAssignmentToConstant(String, Position),
|
||||||
/// Inappropriate property access. Wrapped value is the property name.
|
/// Inappropriate property access. Wrapped value is the property name.
|
||||||
@ -123,9 +127,6 @@ impl EvalAltResult {
|
|||||||
Self::ErrorVariableNotFound(_, _) => "Variable not found",
|
Self::ErrorVariableNotFound(_, _) => "Variable not found",
|
||||||
Self::ErrorModuleNotFound(_, _) => "Module not found",
|
Self::ErrorModuleNotFound(_, _) => "Module not found",
|
||||||
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
|
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
|
||||||
Self::ErrorAssignmentToUnknownLHS(_) => {
|
|
||||||
"Assignment to an unsupported left-hand side expression"
|
|
||||||
}
|
|
||||||
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||||
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
|
||||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||||
@ -179,7 +180,6 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorIndexingType(_, _)
|
Self::ErrorIndexingType(_, _)
|
||||||
| Self::ErrorUnboundThis(_)
|
| Self::ErrorUnboundThis(_)
|
||||||
| Self::ErrorFor(_)
|
| Self::ErrorFor(_)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(_)
|
|
||||||
| Self::ErrorInExpr(_)
|
| Self::ErrorInExpr(_)
|
||||||
| Self::ErrorDotExpr(_, _)
|
| Self::ErrorDotExpr(_, _)
|
||||||
| Self::ErrorTooManyOperations(_)
|
| Self::ErrorTooManyOperations(_)
|
||||||
@ -270,10 +270,10 @@ impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
|
|||||||
|
|
||||||
impl EvalAltResult {
|
impl EvalAltResult {
|
||||||
/// Can this error be caught?
|
/// Can this error be caught?
|
||||||
pub fn catchable(&self) -> bool {
|
pub fn is_catchable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::ErrorSystem(_, _) => false,
|
Self::ErrorSystem(_, _) => false,
|
||||||
Self::ErrorParsing(_, _) => false,
|
Self::ErrorParsing(_, _) => unreachable!(),
|
||||||
|
|
||||||
Self::ErrorFunctionNotFound(_, _)
|
Self::ErrorFunctionNotFound(_, _)
|
||||||
| Self::ErrorInFunctionCall(_, _, _)
|
| Self::ErrorInFunctionCall(_, _, _)
|
||||||
@ -287,7 +287,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorVariableNotFound(_, _)
|
| Self::ErrorVariableNotFound(_, _)
|
||||||
| Self::ErrorModuleNotFound(_, _)
|
| Self::ErrorModuleNotFound(_, _)
|
||||||
| Self::ErrorDataRace(_, _)
|
| Self::ErrorDataRace(_, _)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(_)
|
|
||||||
| Self::ErrorAssignmentToConstant(_, _)
|
| Self::ErrorAssignmentToConstant(_, _)
|
||||||
| Self::ErrorMismatchOutputType(_, _, _)
|
| Self::ErrorMismatchOutputType(_, _, _)
|
||||||
| Self::ErrorInExpr(_)
|
| Self::ErrorInExpr(_)
|
||||||
@ -299,9 +298,28 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorTooManyModules(_)
|
| Self::ErrorTooManyModules(_)
|
||||||
| Self::ErrorStackOverflow(_)
|
| Self::ErrorStackOverflow(_)
|
||||||
| Self::ErrorDataTooLarge(_, _, _, _)
|
| Self::ErrorDataTooLarge(_, _, _, _)
|
||||||
| Self::ErrorTerminated(_)
|
| Self::ErrorTerminated(_) => false,
|
||||||
| Self::LoopBreak(_, _)
|
|
||||||
| Self::Return(_, _) => false,
|
Self::LoopBreak(_, _) | Self::Return(_, _) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this error a system exception?
|
||||||
|
pub fn is_system_exception(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::ErrorSystem(_, _) => true,
|
||||||
|
Self::ErrorParsing(_, _) => unreachable!(),
|
||||||
|
|
||||||
|
Self::ErrorTooManyOperations(_)
|
||||||
|
| Self::ErrorTooManyModules(_)
|
||||||
|
| Self::ErrorStackOverflow(_)
|
||||||
|
| Self::ErrorDataTooLarge(_, _, _, _) => true,
|
||||||
|
|
||||||
|
Self::ErrorTerminated(_) => true,
|
||||||
|
|
||||||
|
Self::LoopBreak(_, _) | Self::Return(_, _) => unreachable!(),
|
||||||
|
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +341,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
@ -358,7 +375,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
|
32
src/scope.rs
32
src/scope.rs
@ -44,6 +44,10 @@ pub struct Entry<'a> {
|
|||||||
/// Type containing information about the current scope.
|
/// Type containing information about the current scope.
|
||||||
/// Useful for keeping state between `Engine` evaluation runs.
|
/// Useful for keeping state between `Engine` evaluation runs.
|
||||||
///
|
///
|
||||||
|
/// # Thread Safety
|
||||||
|
///
|
||||||
|
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -67,15 +71,13 @@ pub struct Entry<'a> {
|
|||||||
///
|
///
|
||||||
/// When searching for entries, newly-added entries are found before similarly-named but older entries,
|
/// When searching for entries, newly-added entries are found before similarly-named but older entries,
|
||||||
/// allowing for automatic _shadowing_.
|
/// allowing for automatic _shadowing_.
|
||||||
///
|
|
||||||
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Scope<'a>(Vec<Entry<'a>>);
|
pub struct Scope<'a>(Vec<Entry<'a>>);
|
||||||
|
|
||||||
impl<'a> Scope<'a> {
|
impl<'a> Scope<'a> {
|
||||||
/// Create a new Scope.
|
/// Create a new Scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -92,7 +94,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Empty the Scope.
|
/// Empty the Scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -117,7 +119,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Get the number of entries inside the Scope.
|
/// Get the number of entries inside the Scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -135,7 +137,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Is the Scope empty?
|
/// Is the Scope empty?
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -153,7 +155,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Add (push) a new entry to the Scope.
|
/// Add (push) a new entry to the Scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -174,7 +176,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Add (push) a new `Dynamic` entry to the Scope.
|
/// Add (push) a new `Dynamic` entry to the Scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Dynamic, Scope};
|
/// use rhai::{Dynamic, Scope};
|
||||||
@ -197,7 +199,7 @@ impl<'a> Scope<'a> {
|
|||||||
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
||||||
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -225,7 +227,7 @@ impl<'a> Scope<'a> {
|
|||||||
/// recognized types:
|
/// recognized types:
|
||||||
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Dynamic, Scope};
|
/// use rhai::{Dynamic, Scope};
|
||||||
@ -272,7 +274,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Truncate (rewind) the Scope to a previous size.
|
/// Truncate (rewind) the Scope to a previous size.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -304,7 +306,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Does the scope contain the entry?
|
/// Does the scope contain the entry?
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -350,7 +352,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Get the value of an entry in the Scope, starting from the last.
|
/// Get the value of an entry in the Scope, starting from the last.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -374,7 +376,7 @@ impl<'a> Scope<'a> {
|
|||||||
///
|
///
|
||||||
/// Panics when trying to update the value of a constant.
|
/// Panics when trying to update the value of a constant.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::Scope;
|
/// use rhai::Scope;
|
||||||
@ -450,7 +452,7 @@ impl<'a> Scope<'a> {
|
|||||||
|
|
||||||
/// Get an iterator to entries in the Scope.
|
/// Get an iterator to entries in the Scope.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::{Dynamic, Scope};
|
/// use rhai::{Dynamic, Scope};
|
||||||
|
@ -70,7 +70,7 @@ impl<'de> DynamicDeserializer<'de> {
|
|||||||
|
|
||||||
/// Deserialize a `Dynamic` value into a Rust type that implements `serde::Deserialize`.
|
/// Deserialize a `Dynamic` value into a Rust type that implements `serde::Deserialize`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
@ -46,7 +46,7 @@ impl DynamicSerializer {
|
|||||||
|
|
||||||
/// Serialize a Rust type that implements `serde::Serialize` into a `Dynamic`.
|
/// Serialize a Rust type that implements `serde::Serialize` into a `Dynamic`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
@ -240,15 +240,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn disable_symbol(&mut self, symbol: &str) -> &mut Self {
|
pub fn disable_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||||
if self.disabled_symbols.is_none() {
|
self.disabled_symbols.insert(symbol.into());
|
||||||
self.disabled_symbols = Some(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.disabled_symbols
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.insert(symbol.into());
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +248,7 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// The operator must be a valid identifier (i.e. it cannot be a symbol).
|
/// The operator must be a valid identifier (i.e. it cannot be a symbol).
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
@ -291,28 +283,14 @@ impl Engine {
|
|||||||
// Standard identifiers, reserved keywords and custom keywords are OK
|
// Standard identifiers, reserved keywords and custom keywords are OK
|
||||||
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
|
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
|
||||||
// Disabled keywords are also OK
|
// Disabled keywords are also OK
|
||||||
Some(token)
|
Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => (),
|
||||||
if !self
|
|
||||||
.disabled_symbols
|
|
||||||
.as_ref()
|
|
||||||
.map(|d| d.contains(token.syntax().as_ref()))
|
|
||||||
.unwrap_or(false) =>
|
|
||||||
{
|
|
||||||
()
|
|
||||||
}
|
|
||||||
// Active standard keywords cannot be made custom
|
// Active standard keywords cannot be made custom
|
||||||
Some(_) => return Err(format!("'{}' is a reserved keyword", keyword).into()),
|
Some(_) => return Err(format!("'{}' is a reserved keyword", keyword).into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to custom keywords
|
// Add to custom keywords
|
||||||
if self.custom_keywords.is_none() {
|
|
||||||
self.custom_keywords = Some(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.custom_keywords
|
self.custom_keywords
|
||||||
.as_mut()
|
.insert(keyword.into(), Some(precedence));
|
||||||
.unwrap()
|
|
||||||
.insert(keyword.into(), precedence);
|
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
132
src/syntax.rs
132
src/syntax.rs
@ -7,11 +7,12 @@ use crate::fn_native::{SendSync, Shared};
|
|||||||
use crate::parser::Expr;
|
use crate::parser::Expr;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::token::{is_valid_identifier, Position, Token};
|
use crate::token::{is_valid_identifier, Position, Token};
|
||||||
|
use crate::utils::ImmutableString;
|
||||||
use crate::StaticVec;
|
use crate::StaticVec;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
fmt, format,
|
format,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,6 +25,14 @@ pub type FnCustomSyntaxEval =
|
|||||||
pub type FnCustomSyntaxEval =
|
pub type FnCustomSyntaxEval =
|
||||||
dyn Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
|
dyn Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
|
||||||
|
|
||||||
|
/// A general expression parsing trait object.
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub type FnCustomSyntaxParse = dyn Fn(&[String]) -> Result<Option<String>, ParseError>;
|
||||||
|
/// A general expression parsing trait object.
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub type FnCustomSyntaxParse =
|
||||||
|
dyn Fn(&[String]) -> Result<Option<String>, ParseError> + Send + Sync;
|
||||||
|
|
||||||
/// An expression sub-tree in an AST.
|
/// An expression sub-tree in an AST.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct Expression<'a>(&'a Expr);
|
pub struct Expression<'a>(&'a Expr);
|
||||||
@ -76,20 +85,12 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CustomSyntax {
|
pub struct CustomSyntax {
|
||||||
pub segments: StaticVec<String>,
|
pub parse: Box<FnCustomSyntaxParse>,
|
||||||
pub func: Shared<FnCustomSyntaxEval>,
|
pub func: Shared<FnCustomSyntaxEval>,
|
||||||
pub scope_delta: isize,
|
pub scope_delta: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for CustomSyntax {
|
|
||||||
#[inline(always)]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt::Debug::fmt(&self.segments, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Register a custom syntax with the `Engine`.
|
/// Register a custom syntax with the `Engine`.
|
||||||
///
|
///
|
||||||
@ -119,47 +120,40 @@ impl Engine {
|
|||||||
let seg = match s {
|
let seg = match s {
|
||||||
// Markers not in first position
|
// Markers not in first position
|
||||||
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
|
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
|
||||||
// Standard symbols not in first position
|
// Standard or reserved keyword/symbol not in first position
|
||||||
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
|
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
|
||||||
// Make it a custom keyword/operator if it is a disabled standard keyword/operator
|
// Make it a custom keyword/symbol
|
||||||
// or a reserved keyword/operator.
|
if !self.custom_keywords.contains_key(s) {
|
||||||
if self
|
self.custom_keywords.insert(s.into(), None);
|
||||||
.disabled_symbols
|
|
||||||
.as_ref()
|
|
||||||
.map(|d| d.contains(s))
|
|
||||||
.unwrap_or(false)
|
|
||||||
|| Token::lookup_from_syntax(s)
|
|
||||||
.map(|token| token.is_reserved())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
// If symbol is disabled, make it a custom keyword
|
|
||||||
if self.custom_keywords.is_none() {
|
|
||||||
self.custom_keywords = Some(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
|
|
||||||
self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.into()
|
s.into()
|
||||||
}
|
}
|
||||||
// Identifier
|
// Standard keyword in first position
|
||||||
s if is_valid_identifier(s.chars()) => {
|
s if segments.is_empty()
|
||||||
if self.custom_keywords.is_none() {
|
&& Token::lookup_from_syntax(s)
|
||||||
self.custom_keywords = Some(Default::default());
|
.map(|v| v.is_keyword() || v.is_reserved())
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
return Err(LexError::ImproperSymbol(format!(
|
||||||
|
"Improper symbol for custom syntax at position #{}: '{}'",
|
||||||
|
segments.len() + 1,
|
||||||
|
s
|
||||||
|
))
|
||||||
|
.into_err(Position::none())
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
// Identifier in first position
|
||||||
|
s if segments.is_empty() && is_valid_identifier(s.chars()) => {
|
||||||
|
if !self.custom_keywords.contains_key(s) {
|
||||||
|
self.custom_keywords.insert(s.into(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
|
|
||||||
self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
s.into()
|
s.into()
|
||||||
}
|
}
|
||||||
// Anything else is an error
|
// Anything else is an error
|
||||||
_ => {
|
_ => {
|
||||||
return Err(LexError::ImproperSymbol(format!(
|
return Err(LexError::ImproperSymbol(format!(
|
||||||
"Improper symbol for custom syntax: '{}'",
|
"Improper symbol for custom syntax at position #{}: '{}'",
|
||||||
|
segments.len() + 1,
|
||||||
s
|
s
|
||||||
))
|
))
|
||||||
.into_err(Position::none())
|
.into_err(Position::none())
|
||||||
@ -175,24 +169,54 @@ impl Engine {
|
|||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the first keyword as the discriminator
|
// The first keyword is the discriminator
|
||||||
let key = segments.remove(0);
|
let key = segments[0].clone();
|
||||||
|
|
||||||
|
self.register_custom_syntax_raw(
|
||||||
|
key,
|
||||||
|
// Construct the parsing function
|
||||||
|
move |stream| {
|
||||||
|
if stream.len() >= segments.len() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(segments[stream.len()].clone()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new_vars,
|
||||||
|
func,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a custom syntax with the `Engine`.
|
||||||
|
///
|
||||||
|
/// ## WARNING - Low Level API
|
||||||
|
///
|
||||||
|
/// This function is very low level.
|
||||||
|
///
|
||||||
|
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
|
||||||
|
/// * `parse` is the parsing function.
|
||||||
|
/// * `func` is the implementation function.
|
||||||
|
///
|
||||||
|
/// All custom keywords must be manually registered via `Engine::register_custom_operator`.
|
||||||
|
/// Otherwise, custom keywords won't be recognized.
|
||||||
|
pub fn register_custom_syntax_raw(
|
||||||
|
&mut self,
|
||||||
|
key: impl Into<ImmutableString>,
|
||||||
|
parse: impl Fn(&[String]) -> Result<Option<String>, ParseError> + SendSync + 'static,
|
||||||
|
new_vars: isize,
|
||||||
|
func: impl Fn(&mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
||||||
|
+ SendSync
|
||||||
|
+ 'static,
|
||||||
|
) -> &mut Self {
|
||||||
let syntax = CustomSyntax {
|
let syntax = CustomSyntax {
|
||||||
segments,
|
parse: Box::new(parse),
|
||||||
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
||||||
scope_delta: new_vars,
|
scope_delta: new_vars,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.custom_syntax.is_none() {
|
self.custom_syntax.insert(key.into(), syntax);
|
||||||
self.custom_syntax = Some(Default::default());
|
self
|
||||||
}
|
|
||||||
|
|
||||||
self.custom_syntax
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.insert(key, syntax.into());
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
src/token.rs
43
src/token.rs
@ -18,9 +18,7 @@ use crate::parser::FLOAT;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
char,
|
char, fmt, format,
|
||||||
collections::HashMap,
|
|
||||||
fmt, format,
|
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -610,7 +608,7 @@ impl Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the precedence number of the token.
|
/// Get the precedence number of the token.
|
||||||
pub fn precedence(&self, custom: Option<&HashMap<String, u8>>) -> u8 {
|
pub fn precedence(&self) -> u8 {
|
||||||
use Token::*;
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
@ -639,9 +637,6 @@ impl Token {
|
|||||||
|
|
||||||
Period => 240,
|
Period => 240,
|
||||||
|
|
||||||
// Custom operators
|
|
||||||
Custom(s) => custom.map_or(0, |c| *c.get(s).unwrap()),
|
|
||||||
|
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -692,7 +687,7 @@ impl Token {
|
|||||||
Import | Export | As => true,
|
Import | Export | As => true,
|
||||||
|
|
||||||
True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break
|
True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break
|
||||||
| Return | Throw => true,
|
| Return | Throw | Try | Catch => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -1647,16 +1642,12 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
type Item = (Token, Position);
|
type Item = (Token, Position);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let token = match (
|
let token = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
|
||||||
get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
|
|
||||||
self.engine.disabled_symbols.as_ref(),
|
|
||||||
self.engine.custom_keywords.as_ref(),
|
|
||||||
) {
|
|
||||||
// {EOF}
|
// {EOF}
|
||||||
(None, _, _) => None,
|
None => None,
|
||||||
// Reserved keyword/symbol
|
// Reserved keyword/symbol
|
||||||
(Some((Token::Reserved(s), pos)), disabled, custom) => Some((match
|
Some((Token::Reserved(s), pos)) => Some((match
|
||||||
(s.as_str(), custom.map(|c| c.contains_key(&s)).unwrap_or(false))
|
(s.as_str(), self.engine.custom_keywords.contains_key(&s))
|
||||||
{
|
{
|
||||||
("===", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
("===", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||||
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
|
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
|
||||||
@ -1691,21 +1682,19 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
format!("'{}' is a reserved symbol", token)
|
format!("'{}' is a reserved symbol", token)
|
||||||
))),
|
))),
|
||||||
// Reserved keyword that is not custom and disabled.
|
// Reserved keyword that is not custom and disabled.
|
||||||
(token, false) if disabled.map(|d| d.contains(token)).unwrap_or(false) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
(token, false) if self.engine.disabled_symbols.contains(token) => Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||||
format!("reserved symbol '{}' is disabled", token)
|
format!("reserved symbol '{}' is disabled", token)
|
||||||
))),
|
))),
|
||||||
// Reserved keyword/operator that is not custom.
|
// Reserved keyword/operator that is not custom.
|
||||||
(_, false) => Token::Reserved(s),
|
(_, false) => Token::Reserved(s),
|
||||||
}, pos)),
|
}, pos)),
|
||||||
// Custom keyword
|
// Custom keyword
|
||||||
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
|
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => {
|
||||||
Some((Token::Custom(s), pos))
|
Some((Token::Custom(s), pos))
|
||||||
}
|
}
|
||||||
// Custom standard keyword - must be disabled
|
// Custom standard keyword - must be disabled
|
||||||
(Some((token, pos)), Some(disabled), Some(custom))
|
Some((token, pos)) if token.is_keyword() && self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => {
|
||||||
if token.is_keyword() && custom.contains_key(token.syntax().as_ref()) =>
|
if self.engine.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||||
{
|
|
||||||
if disabled.contains(token.syntax().as_ref()) {
|
|
||||||
// Disabled standard keyword
|
// Disabled standard keyword
|
||||||
Some((Token::Custom(token.syntax().into()), pos))
|
Some((Token::Custom(token.syntax().into()), pos))
|
||||||
} else {
|
} else {
|
||||||
@ -1714,21 +1703,17 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Disabled operator
|
// Disabled operator
|
||||||
(Some((token, pos)), Some(disabled), _)
|
Some((token, pos)) if token.is_operator() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||||
if token.is_operator() && disabled.contains(token.syntax().as_ref()) =>
|
|
||||||
{
|
|
||||||
Some((
|
Some((
|
||||||
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
|
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
|
||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// Disabled standard keyword
|
// Disabled standard keyword
|
||||||
(Some((token, pos)), Some(disabled), _)
|
Some((token, pos)) if token.is_keyword() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||||
if token.is_keyword() && disabled.contains(token.syntax().as_ref()) =>
|
|
||||||
{
|
|
||||||
Some((Token::Reserved(token.syntax().into()), pos))
|
Some((Token::Reserved(token.syntax().into()), pos))
|
||||||
}
|
}
|
||||||
(r, _, _) => r,
|
r => r,
|
||||||
};
|
};
|
||||||
|
|
||||||
match token {
|
match token {
|
||||||
|
@ -96,7 +96,7 @@ pub fn calc_fn_hash<'a>(
|
|||||||
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` under the `sync` feature)
|
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` under the `sync` feature)
|
||||||
/// so that it can be simply shared and not cloned.
|
/// so that it can be simply shared and not cloned.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rhai::ImmutableString;
|
/// use rhai::ImmutableString;
|
||||||
|
@ -18,12 +18,15 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<()>(
|
*engine
|
||||||
r"
|
.eval::<()>(
|
||||||
|
r"
|
||||||
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
|
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
|
||||||
foo(1000)
|
foo(1000)
|
||||||
").expect_err("should error"),
|
"
|
||||||
EvalAltResult::ErrorInFunctionCall(name, _, _) if name.starts_with("foo > foo > foo")
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorStackOverflow(_)
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, INT};
|
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, Position, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -19,10 +19,10 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
engine.register_custom_syntax(
|
engine.register_custom_syntax(
|
||||||
&[
|
&[
|
||||||
"do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
|
"exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
|
||||||
],
|
],
|
||||||
1,
|
1,
|
||||||
|context: &mut EvalContext, inputs: &[Expression]| {
|
|context, inputs| {
|
||||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||||
let stmt = inputs.get(1).unwrap();
|
let stmt = inputs.get(1).unwrap();
|
||||||
let condition = inputs.get(2).unwrap();
|
let condition = inputs.get(2).unwrap();
|
||||||
@ -52,13 +52,10 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// 'while' is now a custom keyword so this it can no longer be a variable
|
|
||||||
engine.consume("let while = 0").expect_err("should error");
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r"
|
r"
|
||||||
do |x| -> { x += 1 } while x < 42;
|
exec |x| -> { x += 1 } while x < 42;
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -71,7 +68,48 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
|||||||
.register_custom_syntax(&["!"], 0, |_, _| Ok(().into()))
|
.register_custom_syntax(&["!"], 0, |_, _| Ok(().into()))
|
||||||
.expect_err("should error")
|
.expect_err("should error")
|
||||||
.0,
|
.0,
|
||||||
ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
|
ParseErrorType::BadInput(
|
||||||
|
"Improper symbol for custom syntax at position #1: '!'".to_string()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.register_custom_syntax_raw(
|
||||||
|
"hello",
|
||||||
|
|stream| match stream.len() {
|
||||||
|
0 => unreachable!(),
|
||||||
|
1 => Ok(Some("$ident$".to_string())),
|
||||||
|
2 => match stream[1].as_str() {
|
||||||
|
"world" | "kitty" => Ok(None),
|
||||||
|
s => Err(ParseError(
|
||||||
|
Box::new(ParseErrorType::BadInput(s.to_string())),
|
||||||
|
Position::none(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
|_, inputs| {
|
||||||
|
Ok(match inputs[0].get_variable_name().unwrap() {
|
||||||
|
"world" => 123 as INT,
|
||||||
|
"kitty" => 42 as INT,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("hello world")?, 123);
|
||||||
|
assert_eq!(engine.eval::<INT>("hello kitty")?, 42);
|
||||||
|
assert_eq!(
|
||||||
|
*engine.compile("hello hey").expect_err("should error").0,
|
||||||
|
ParseErrorType::BadInput("hey".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user