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