Merge pull request #270 from schungx/master

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

View File

@ -2,6 +2,29 @@ Rhai Release Notes
==================
Version 0.19.4
==============
This version adds a low-level API for more flexibility when defining custom syntax.
Bug fixes
---------
* Fixes `Send + Sync` for `EvalAltResult` under the `sync` feature. Bug introduced with `0.19.3`.
Breaking changes
----------------
* Custom syntax can no longer start with a keyword (even a _reserved_ one), even if it has been disabled. That is to avoid breaking scripts later when the keyword is no longer disabled.
* `EvalAltResult::ErrorAssignmentToUnknownLHS` is moved to `ParseError::AssignmentToInvalidLHS`. `ParseError::AssignmentToCopy` is removed.
New features
------------
* Low-level API for custom syntax allowing more flexibility in designing the syntax.
* `Module::fill_with` to poly-fill a module with another.
Version 0.19.3
==============

View File

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

View File

@ -16,7 +16,7 @@ To do so, provide a closure to the [`Engine`] via the `Engine::on_var` method:
let mut engine = Engine::new();
// 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`. |

View File

@ -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
@ -116,16 +117,16 @@ let mut engine = Engine::new();
engine.register_raw_fn(
"bar",
&[
std::any::TypeId::of::<i64>(), // parameter types
std::any::TypeId::of::<i64>(), // parameter types
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
|context, args| {
// 'args' is guaranteed to contain enough arguments of the correct types
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found.
@ -133,13 +134,14 @@ engine.register_raw_fn(
},
);
let result = engine.eval::<i64>(r#"
let result = engine.eval::<i64>(
r#"
fn foo(x) { this += x; } // script-defined function 'foo'
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.

View File

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

View File

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

View File

@ -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(),
// Non-lvalue expression (should be caught during parsing)
_ => unreachable!(),
}
}
// lhs[idx_expr]
#[cfg(not(feature = "no_index"))]
Expr::Index(_) => {
self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None)
}
// lhs.dot_rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(_) => {
self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None)
}
#[cfg(not(feature = "no_index"))]
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
x.0.iter()
.map(|item| self.eval_expr(scope, mods, state, lib, this_ptr, item, level))
.collect::<Result<Vec<_>, _>>()?,
)))),
#[cfg(not(feature = "no_object"))]
Expr::Map(x) => Ok(Dynamic(Union::Map(Box::new(
x.0.iter()
.map(|((key, _), expr)| {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|val| (key.clone(), val))
})
.collect::<Result<HashMap<_, _>, _>>()?,
)))),
// Normal function call
Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, cap_scope, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.map(Into::<Dynamic>::into);
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
false, *cap_scope, level,
)
.map_err(|err| err.fill_position(*pos))
}
// Module-qualified function call
Expr::FnCall(x) if x.1.is_some() => {
let ((name, _, _, pos), modules, hash, args_expr, def_val) = x.as_ref();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
level,
)
.map_err(|err| err.fill_position(*pos))
}
Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level),
Expr::And(x) => {
let (lhs, rhs, _) = x.as_ref();
Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
&& // Short-circuit using &&
self
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
.into())
}
Expr::Or(x) => {
let (lhs, rhs, _) = x.as_ref();
Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
|| // Short-circuit using ||
self
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
.into())
}
Expr::True(_) => Ok(true.into()),
Expr::False(_) => Ok(false.into()),
Expr::Unit(_) => Ok(().into()),
Expr::Custom(x) => {
let func = (x.0).func();
let expressions = (x.0)
.keywords()
.iter()
.map(Into::into)
.collect::<StaticVec<_>>();
let mut context = EvalContext {
engine: self,
scope,
mods,
state,
lib,
this_ptr,
level,
};
func(&mut context, &expressions)
}
_ => unreachable!(),
};
self.check_data_size(result)
.map_err(|err| err.fill_position(expr.position()))
}
/// Evaluate a statement
///
///
/// # Safety
///
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via
/// direct lifetime casting.
pub(crate) fn eval_stmt(
&self,
scope: &mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
stmt: &Stmt,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)
.map_err(|err| err.fill_position(stmt.position()))?;
let result = match stmt {
// No-op
Stmt::Noop(_) => Ok(Default::default()),
// Expression as statement
Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level),
// Block scope
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(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
(x.0).1,
)
.into()
}
Stmt::ReturnWithVal((ReturnType::Return, pos), Some(expr), _) => EvalAltResult::Return(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
*pos,
)
.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(),

View File

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

View File

@ -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.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,11 +975,7 @@ 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());
}
return Ok(scope.contains(var_name).into());
}
}
@ -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()

View File

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

View File

@ -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.modules.extend(other.modules.into_iter());
self.variables.extend(other.variables.into_iter());
self.functions.extend(other.functions.into_iter());
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());
}
other.modules.into_iter().for_each(|(_, m)| {
self.combine_flatten(m);
});
self.variables.extend(other.variables.into_iter());
self.functions.extend(other.functions.into_iter());
self.type_iterators.extend(other.type_iterators.into_iter());
self.all_functions.clear();
self.all_variables.clear();
self.indexed = false;
self
}
/// Poly-fill this module with another module.
/// Only items not existing in this module are added.
#[inline]
pub fn fill_with(&mut self, other: &Self) -> &mut Self {
other.modules.iter().for_each(|(k, v)| {
if !self.modules.contains_key(k) {
self.modules.insert(k.clone(), v.clone());
}
});
other.variables.iter().for_each(|(k, v)| {
if !self.variables.contains_key(k) {
self.variables.insert(k.clone(), v.clone());
}
});
other.functions.iter().for_each(|(&k, v)| {
self.functions.entry(k).or_insert_with(|| v.clone());
});
other.type_iterators.iter().for_each(|(&k, &v)| {
self.type_iterators.entry(k).or_insert(v);
});
self.all_functions.clear();
self.all_variables.clear();
self.indexed = false;
@ -1181,42 +1191,32 @@ 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);
});
}
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
.iter()
.filter(|(_, (_, _, _, _, v))| match v {
#[cfg(not(feature = "no_function"))]
CallableFunction::Script(f) => {
_filter(f.access, f.name.as_str(), f.params.len())
}
_ => true,
})
.map(|(&k, v)| (k, v.clone())),
);
}
self.modules
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
if !other.type_iterators.is_empty() {
self.type_iterators
.extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone())));
}
self.variables
.extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone())));
self.functions.extend(
other
.functions
.iter()
.filter(|(_, (_, _, _, _, v))| match v {
#[cfg(not(feature = "no_function"))]
CallableFunction::Script(f) => {
_filter(f.access, f.name.as_str(), f.params.len())
}
_ => true,
})
.map(|(&k, v)| (k, v.clone())),
);
self.type_iterators.extend(other.type_iterators.iter());
self.all_functions.clear();
self.all_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>> {

View File

@ -8,7 +8,7 @@ use crate::stdlib::{boxed::Box, ops::AddAssign, vec::Vec};
/// Module resolution service that holds a collection of module resolves,
/// 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,9 +80,7 @@ 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());
}
self.0.extend(other.0.into_iter());
}
}

View File

@ -22,7 +22,7 @@ use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::Str
/// plus all those defined within the same module are _merged_ into a _unified_ namespace before
/// 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;

View File

@ -7,7 +7,7 @@ use crate::stdlib::{boxed::Box, collections::HashMap, ops::AddAssign, string::St
/// Module resolution service that serves modules added into it.
///
/// # 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};

View File

@ -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 {
// id op= expr
Stmt::Assignment(x, pos) => Stmt::Assignment(
Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))),
pos,
),
// if false { if_block } -> Noop
Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => {
state.set_dirty();
Stmt::Noop(pos)
}
// if true { if_block } -> if_block
Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => optimize_stmt(x.0, state, true),
// if expr { Noop }
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) && x.2.is_none() => {
Stmt::IfThenElse(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
state.set_dirty();
let pos = x.0.position();
let expr = optimize_expr(x.0, state);
let pos = condition.position();
let expr = optimize_expr(condition, state);
if preserve_result {
// -> { expr, Noop }
let mut statements = StaticVec::new();
statements.push(Stmt::Expr(Box::new(expr)));
statements.push(x.1);
let mut statements = Vec::new();
statements.push(Stmt::Expr(expr));
statements.push(x.0);
Stmt::Block(Box::new((statements, pos)))
Stmt::Block(statements, pos)
} else {
// -> expr
Stmt::Expr(Box::new(expr))
Stmt::Expr(expr)
}
}
// if expr { if_block }
Stmt::IfThenElse(x) if x.2.is_none() => match x.0 {
// if false { if_block } -> Noop
Expr::False(pos) => {
state.set_dirty();
Stmt::Noop(pos)
}
// if true { if_block } -> if_block
Expr::True(_) => optimize_stmt(x.1, state, true),
// if expr { if_block }
expr => Stmt::IfThenElse(Box::new((
optimize_expr(expr, state),
optimize_stmt(x.1, state, true),
None,
x.3,
))),
},
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
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
Stmt::IfThenElse(Expr::True(_), x, _) => optimize_stmt(x.0, state, true),
// if expr { if_block } else { else_block }
Stmt::IfThenElse(x) if x.2.is_some() => match x.0 {
// if false { if_block } else { else_block } -> else_block
Expr::False(_) => optimize_stmt(x.2.unwrap(), state, true),
// if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(x.1, 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,
))),
},
)),
pos,
),
// while false { block } -> Noop
Stmt::While(Expr::False(pos), _, _) => {
state.set_dirty();
Stmt::Noop(pos)
}
// while true { block } -> loop { block }
Stmt::While(Expr::True(_), block, pos) => {
Stmt::Loop(Box::new(optimize_stmt(*block, state, false)), pos)
}
// while expr { block }
Stmt::While(x) => match x.0 {
// while false { block } -> Noop
Expr::False(pos) => {
state.set_dirty();
Stmt::Noop(pos)
}
// while true { block } -> loop { block }
Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))),
// while expr { block }
expr => match optimize_stmt(x.1, state, false) {
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,59 +255,52 @@ 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()
.map(|stmt| match stmt {
// Add constant literals into the state
Stmt::Const(mut v) => {
if let Some(expr) = v.1 {
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)
}
} else {
state.set_dirty();
state.push_constant(&(v.0).0, Expr::Unit((v.0).1));
Stmt::Noop(pos) // No need to keep constants
}
}
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
})
.collect();
let mut result: Vec<_> = statements
.into_iter()
.map(|stmt| match stmt {
// Add constant literals into the state
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);
Stmt::Const(name, Some(expr), pos)
}
Stmt::Const(name, None, pos) => {
state.set_dirty();
state.push_constant(&name.0, Expr::Unit(name.1));
Stmt::Noop(pos) // No need to keep constants
}
// Optimize the statement
stmt => optimize_stmt(stmt, state, preserve_result),
})
.collect();
// Remove all raw expression statements that are pure except for the very last statement
let last_stmt = if preserve_result { result.pop() } else { None };
@ -321,9 +318,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
while let Some(expr) = result.pop() {
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);
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));
if expr.is_literal() {
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,

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -20,12 +20,18 @@ use crate::stdlib::{
///
/// All wrapped `Position` values represent the location in the script where the error occurs.
///
/// # 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)

View File

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

View File

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

View File

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

View File

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

View File

@ -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());
}
if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
}
// Make it a custom keyword/symbol
if !self.custom_keywords.contains_key(s) {
self.custom_keywords.insert(s.into(), None);
}
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());
}
// Identifier in first position
s if segments.is_empty() && is_valid_identifier(s.chars()) => {
if !self.custom_keywords.contains_key(s) {
self.custom_keywords.insert(s.into(), None);
}
if !self.custom_keywords.as_ref().unwrap().contains_key(s) {
self.custom_keywords.as_mut().unwrap().insert(s.into(), 0);
}
s.into()
}
// 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
}
}

View File

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

View File

@ -96,7 +96,7 @@ pub fn calc_fn_hash<'a>(
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` under the `sync` feature)
/// so that it can be simply shared and not cloned.
///
/// # Examples
/// # Example
///
/// ```
/// use rhai::ImmutableString;

View File

@ -18,12 +18,15 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
assert!(matches!(
*engine.eval::<()>(
r"
*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(())

View File

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