Merge pull request #270 from schungx/master

Code enhancements.
This commit is contained in:
Stephen Chung 2020-10-28 17:44:12 +08:00 committed by GitHub
commit a87c4de22b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1225 additions and 987 deletions

View File

@ -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
============== ==============

View File

@ -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`. |

View File

@ -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.

View File

@ -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
@ -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.

View File

@ -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;
} }
_ => (), _ => (),

View File

@ -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
} }

View File

@ -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!(),
// 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!(), _ => 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();
EvalAltResult::Return(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
(x.0).1, *pos,
) )
.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(),

View File

@ -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)
} }

View File

@ -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,13 +975,9 @@ 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(false.into());
} else {
return Ok(scope.contains(var_name).into()); return Ok(scope.contains(var_name).into());
} }
} }
}
// Handle is_def_fn() // Handle is_def_fn()
if name == KEYWORD_IS_DEF_FN && args_expr.len() == 2 { if name == KEYWORD_IS_DEF_FN && args_expr.len() == 2 {
@ -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()

View File

@ -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>> {

View File

@ -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());
}
if !other.variables.is_empty() {
self.variables.extend(other.variables.into_iter()); self.variables.extend(other.variables.into_iter());
}
if !other.functions.is_empty() {
self.functions.extend(other.functions.into_iter()); self.functions.extend(other.functions.into_iter());
}
if !other.type_iterators.is_empty() {
self.type_iterators.extend(other.type_iterators.into_iter()); 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);
}); });
}
if !other.variables.is_empty() {
self.variables.extend(other.variables.into_iter()); self.variables.extend(other.variables.into_iter());
}
if !other.functions.is_empty() {
self.functions.extend(other.functions.into_iter()); self.functions.extend(other.functions.into_iter());
}
if !other.type_iterators.is_empty() {
self.type_iterators.extend(other.type_iterators.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter());
self.all_functions.clear();
self.all_variables.clear();
self.indexed = false;
self
} }
/// 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,23 +1191,17 @@ 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 self.variables
.extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone())));
}
if !other.functions.is_empty() {
self.functions.extend( self.functions.extend(
other other
.functions .functions
@ -1211,12 +1215,8 @@ impl Module {
}) })
.map(|(&k, v)| (k, v.clone())), .map(|(&k, v)| (k, v.clone())),
); );
}
if !other.type_iterators.is_empty() { self.type_iterators.extend(other.type_iterators.iter());
self.type_iterators
.extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone())));
}
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>> {

View File

@ -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,10 +80,8 @@ 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());
} }
}
} }
impl ModuleResolver for ModuleResolversCollection { impl ModuleResolver for ModuleResolversCollection {

View File

@ -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;

View File

@ -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};

View File

@ -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 {
// if expr { Noop } // id op= expr
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) && x.2.is_none() => { Stmt::Assignment(x, pos) => Stmt::Assignment(
state.set_dirty(); Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))),
pos,
let pos = x.0.position(); ),
let expr = optimize_expr(x.0, state);
if preserve_result {
// -> { expr, Noop }
let mut statements = StaticVec::new();
statements.push(Stmt::Expr(Box::new(expr)));
statements.push(x.1);
Stmt::Block(Box::new((statements, pos)))
} else {
// -> expr
Stmt::Expr(Box::new(expr))
}
}
// if expr { if_block }
Stmt::IfThenElse(x) if x.2.is_none() => match x.0 {
// if false { if_block } -> Noop // if false { if_block } -> Noop
Expr::False(pos) => { Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// if true { if_block } -> if_block // if true { if_block } -> if_block
Expr::True(_) => optimize_stmt(x.1, state, true), Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => optimize_stmt(x.0, state, true),
// if expr { Noop }
Stmt::IfThenElse(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
state.set_dirty();
let pos = condition.position();
let expr = optimize_expr(condition, state);
if preserve_result {
// -> { expr, Noop }
let mut statements = Vec::new();
statements.push(Stmt::Expr(expr));
statements.push(x.0);
Stmt::Block(statements, pos)
} else {
// -> expr
Stmt::Expr(expr)
}
}
// if expr { if_block } // if expr { if_block }
expr => Stmt::IfThenElse(Box::new(( Stmt::IfThenElse(condition, x, pos) if x.1.is_none() => Stmt::IfThenElse(
optimize_expr(expr, state), optimize_expr(condition, state),
optimize_stmt(x.1, state, true), Box::new((optimize_stmt(x.0, state, true), None)),
None, pos,
x.3, ),
))),
},
// if expr { if_block } else { else_block }
Stmt::IfThenElse(x) if x.2.is_some() => match x.0 {
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Expr::False(_) => optimize_stmt(x.2.unwrap(), state, true), Stmt::IfThenElse(Expr::False(_), x, _) if x.1.is_some() => {
optimize_stmt(x.1.unwrap(), state, true)
}
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(x.1, state, true), Stmt::IfThenElse(Expr::True(_), x, _) => optimize_stmt(x.0, state, true),
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
expr => Stmt::IfThenElse(Box::new(( Stmt::IfThenElse(condition, x, pos) => Stmt::IfThenElse(
optimize_expr(expr, state), optimize_expr(condition, state),
optimize_stmt(x.1, state, true), Box::new((
match optimize_stmt(x.2.unwrap(), state, true) { optimize_stmt(x.0, state, true),
match optimize_stmt(x.1.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 expr { block }
Stmt::While(x) => match x.0 {
// while false { block } -> Noop // while false { block } -> Noop
Expr::False(pos) => { Stmt::While(Expr::False(pos), _, _) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
// while true { block } -> loop { block } // while true { block } -> loop { block }
Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))), Stmt::While(Expr::True(_), block, pos) => {
Stmt::Loop(Box::new(optimize_stmt(*block, state, false)), pos)
}
// while expr { block } // while expr { block }
expr => match optimize_stmt(x.1, state, false) { Stmt::While(condition, block, pos) => {
match optimize_stmt(*block, 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,57 +255,50 @@ 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();
state.push_constant(&name.0, expr);
Stmt::Noop(pos) // No need to keep constants
}
Stmt::Const(name, Some(expr), pos) if expr.is_literal() => {
let expr = optimize_expr(expr, state); let expr = optimize_expr(expr, state);
Stmt::Const(name, Some(expr), pos)
if expr.is_literal() {
state.set_dirty();
state.push_constant(&(v.0).0, expr);
Stmt::Noop(pos) // No need to keep constants
} else {
v.1 = Some(expr);
Stmt::Const(v)
} }
} else { Stmt::Const(name, None, pos) => {
state.set_dirty(); state.set_dirty();
state.push_constant(&(v.0).0, Expr::Unit((v.0).1)); state.push_constant(&name.0, Expr::Unit(name.1));
Stmt::Noop(pos) // No need to keep constants Stmt::Noop(pos) // No need to keep constants
} }
}
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), stmt => optimize_stmt(stmt, state, preserve_result),
}) })
.collect(); .collect();
@ -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,

View File

@ -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 ),*) => (
$( $(

View File

@ -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};

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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};

View File

@ -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>> {

View File

@ -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>> {

View File

@ -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)
} }

View File

@ -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
if !self.custom_keywords.as_ref().unwrap().contains_key(s) { s if segments.is_empty() && is_valid_identifier(s.chars()) => {
self.custom_keywords.as_mut().unwrap().insert(s.into(), 0); if !self.custom_keywords.contains_key(s) {
self.custom_keywords.insert(s.into(), None);
} }
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)
} }
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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
.eval::<()>(
r" 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(())

View File

@ -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(())