Merge pull request #372 from schungx/master

Improve speed by optimizing AST layout.
This commit is contained in:
Stephen Chung 2021-03-11 22:52:25 +08:00 committed by GitHub
commit 7a30030647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1288 additions and 949 deletions

View File

@ -4,6 +4,9 @@ Rhai Release Notes
Version 0.19.14 Version 0.19.14
=============== ===============
This version runs faster due to optimizations done on AST node structures. It also fixes a number of
panic bugs related to passing shared values as function call arguments.
Bug fixes Bug fixes
--------- ---------
@ -27,18 +30,24 @@ Breaking changes
* `Module::update_fn_metadata` input parameter is changed. * `Module::update_fn_metadata` input parameter is changed.
* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`. * Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`.
* `is_def_var` and `is_def_fn` are now reserved keywords. * `is_def_var` and `is_def_fn` are now reserved keywords.
* `Engine::id` field is removed. * `Engine::id` field is removed because it is never used.
* `num-traits` is now a required dependency. * `num-traits` is now a required dependency.
* The `in` operator is now implemented on top of the `contains` function and is no longer restricted to a few specific types.
* `EvalAltResult::ErrorInExpr` is removed because the `in` operator now calls `contains`.
* The methods `AST::walk`, `Expr::walk`, `Stmt::walk` and `ASTNode::walk` and the callbacks they take now return `bool` to optionally terminate the recursive walk.
Enhancements Enhancements
------------ ------------
* Layout of AST nodes is optimized to reduce redirections, so speed is improved.
* Function calls are more optimized and should now run faster. * Function calls are more optimized and should now run faster.
* `range` function now supports negative step and decreasing streams (i.e. to < from). * `range` function now supports negative step and decreasing streams (i.e. to < from).
* More information is provided to the error variable captured by the `catch` statement in an _object map_. * More information is provided to the error variable captured by the `catch` statement in an _object map_.
* Previously, `private` functions in an `AST` cannot be called with `call_fn` etc. This is inconvenient when trying to call a function inside a script which also serves as a loadable module exporting part (but not all) of the functions. Now, all functions (`private` or not) can be called in an `AST`. The `private` keyword is relegated to preventing a function from being exported. * Previously, `private` functions in an `AST` cannot be called with `call_fn` etc. This is inconvenient when trying to call a function inside a script which also serves as a loadable module exporting part (but not all) of the functions. Now, all functions (`private` or not) can be called in an `AST`. The `private` keyword is relegated to preventing a function from being exported.
* `Dynamic::as_unit` just for completeness sake. * `Dynamic::as_unit` just for completeness sake.
* `bytes` method added for strings to get length quickly (if the string is ASCII-only). * `bytes` method added for strings to get length quickly (if the string is ASCII-only).
* `FileModuleResolver` can now enable/disable caching.
* Recursively walking an `AST` can now be terminated in the middle.
Version 0.19.13 Version 0.19.13

View File

@ -47,7 +47,7 @@ pub enum FnAccess {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ScriptFnDef { pub struct ScriptFnDef {
/// Function body. /// Function body.
pub body: Stmt, pub body: StmtBlock,
/// Encapsulated running environment, if any. /// Encapsulated running environment, if any.
pub lib: Option<Shared<Module>>, pub lib: Option<Shared<Module>>,
/// Encapsulated imported modules. /// Encapsulated imported modules.
@ -61,9 +61,9 @@ pub struct ScriptFnDef {
pub params: StaticVec<ImmutableString>, pub params: StaticVec<ImmutableString>,
/// Access to external variables. /// Access to external variables.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub externals: Vec<ImmutableString>, pub externals: StaticVec<ImmutableString>,
/// Function doc-comments (if any). /// Function doc-comments (if any).
pub comments: Vec<String>, pub comments: StaticVec<String>,
} }
impl fmt::Display for ScriptFnDef { impl fmt::Display for ScriptFnDef {
@ -149,7 +149,7 @@ pub struct AST {
/// Source of the [`AST`]. /// Source of the [`AST`].
source: Option<ImmutableString>, source: Option<ImmutableString>,
/// Global statements. /// Global statements.
statements: Vec<Stmt>, body: StmtBlock,
/// Script-defined functions. /// Script-defined functions.
functions: Shared<Module>, functions: Shared<Module>,
/// Embedded module resolver, if any. /// Embedded module resolver, if any.
@ -162,7 +162,7 @@ impl Default for AST {
fn default() -> Self { fn default() -> Self {
Self { Self {
source: None, source: None,
statements: Vec::with_capacity(16), body: Default::default(),
functions: Default::default(), functions: Default::default(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
@ -179,7 +179,10 @@ impl AST {
) -> Self { ) -> Self {
Self { Self {
source: None, source: None,
body: StmtBlock {
statements: statements.into_iter().collect(), statements: statements.into_iter().collect(),
pos: Position::NONE,
},
functions: functions.into(), functions: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
@ -194,7 +197,10 @@ impl AST {
) -> Self { ) -> Self {
Self { Self {
source: Some(source.into()), source: Some(source.into()),
body: StmtBlock {
statements: statements.into_iter().collect(), statements: statements.into_iter().collect(),
pos: Position::NONE,
},
functions: functions.into(), functions: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
@ -231,7 +237,7 @@ impl AST {
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[inline(always)] #[inline(always)]
pub(crate) fn statements(&self) -> &[Stmt] { pub(crate) fn statements(&self) -> &[Stmt] {
&self.statements &self.body.statements
} }
/// _(INTERNALS)_ Get the statements. /// _(INTERNALS)_ Get the statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -239,13 +245,13 @@ impl AST {
#[deprecated = "this method is volatile and may change"] #[deprecated = "this method is volatile and may change"]
#[inline(always)] #[inline(always)]
pub fn statements(&self) -> &[Stmt] { pub fn statements(&self) -> &[Stmt] {
&self.statements &self.body.statements
} }
/// Get a mutable reference to the statements. /// Get a mutable reference to the statements.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline(always)] #[inline(always)]
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> { pub(crate) fn statements_mut(&mut self) -> &mut StaticVec<Stmt> {
&mut self.statements &mut self.body.statements
} }
/// Get the internal shared [`Module`] containing all script-defined functions. /// Get the internal shared [`Module`] containing all script-defined functions.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -333,7 +339,7 @@ impl AST {
functions.merge_filtered(&self.functions, &filter); functions.merge_filtered(&self.functions, &filter);
Self { Self {
source: self.source.clone(), source: self.source.clone(),
statements: Default::default(), body: Default::default(),
functions: functions.into(), functions: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: self.resolver.clone(), resolver: self.resolver.clone(),
@ -345,7 +351,7 @@ impl AST {
pub fn clone_statements_only(&self) -> Self { pub fn clone_statements_only(&self) -> Self {
Self { Self {
source: self.source.clone(), source: self.source.clone(),
statements: self.statements.clone(), body: self.body.clone(),
functions: Default::default(), functions: Default::default(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: self.resolver.clone(), resolver: self.resolver.clone(),
@ -515,20 +521,19 @@ impl AST {
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self { ) -> Self {
let Self { let Self {
statements, body, functions, ..
functions,
..
} = self; } = self;
let ast = match (statements.is_empty(), other.statements.is_empty()) { let merged = match (body.is_empty(), other.body.is_empty()) {
(false, false) => { (false, false) => {
let mut statements = statements.clone(); let mut body = body.clone();
statements.extend(other.statements.iter().cloned()); body.statements
statements .extend(other.body.statements.iter().cloned());
body
} }
(false, true) => statements.clone(), (false, true) => body.clone(),
(true, false) => other.statements.clone(), (true, false) => other.body.clone(),
(true, true) => vec![], (true, true) => Default::default(),
}; };
let source = other.source.clone().or_else(|| self.source.clone()); let source = other.source.clone().or_else(|| self.source.clone());
@ -537,9 +542,9 @@ impl AST {
functions.merge_filtered(&other.functions, &filter); functions.merge_filtered(&other.functions, &filter);
if let Some(source) = source { if let Some(source) = source {
Self::new_with_source(ast, functions, source) Self::new_with_source(merged.statements, functions, source)
} else { } else {
Self::new(ast, functions) Self::new(merged.statements, functions)
} }
} }
/// Combine one [`AST`] with another. The second [`AST`] is consumed. /// Combine one [`AST`] with another. The second [`AST`] is consumed.
@ -599,7 +604,10 @@ impl AST {
other: Self, other: Self,
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
self.statements.extend(other.statements.into_iter()); self.body
.statements
.extend(other.body.statements.into_iter());
if !other.functions.is_empty() { if !other.functions.is_empty() {
shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter); shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter);
} }
@ -673,45 +681,51 @@ impl AST {
/// Clear all statements in the [`AST`], leaving only function definitions. /// Clear all statements in the [`AST`], leaving only function definitions.
#[inline(always)] #[inline(always)]
pub fn clear_statements(&mut self) { pub fn clear_statements(&mut self) {
self.statements = vec![]; self.body = Default::default();
} }
/// Recursively walk the [`AST`], including function bodies (if any). /// Recursively walk the [`AST`], including function bodies (if any).
/// Return `false` from the callback to terminate the walk.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode])) { pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
self.statements() let path = &mut Default::default();
.iter()
.chain({ for stmt in self.statements() {
if !stmt.walk(path, on_node) {
return false;
}
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
self.iter_fn_def().map(|f| &f.body) if !stmt.walk(path, on_node) {
return false;
} }
#[cfg(feature = "no_function")]
{
crate::stdlib::iter::empty()
} }
})
.for_each(|stmt| stmt.walk(&mut Default::default(), on_node)); true
} }
/// _(INTERNALS)_ Recursively walk the [`AST`], including function bodies (if any). /// _(INTERNALS)_ Recursively walk the [`AST`], including function bodies (if any).
/// Return `false` from the callback to terminate the walk.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode])) { pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
self.statements() let path = &mut Default::default();
.iter()
.chain({ for stmt in self.statements() {
if !stmt.walk(path, on_node) {
return false;
}
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
self.iter_fn_def().map(|f| &f.body) if !stmt.walk(path, on_node) {
return false;
} }
#[cfg(feature = "no_function")]
{
crate::stdlib::iter::empty()
} }
})
.for_each(|stmt| stmt.walk(&mut Default::default(), on_node)); true
} }
} }
@ -804,6 +818,41 @@ impl<'a> From<&'a Expr> for ASTNode<'a> {
} }
} }
/// _(INTERNALS)_ A statements block.
/// Exported under the `internals` feature only.
///
/// # Volatile Data Structure
///
/// This type is volatile and may change.
#[derive(Clone, Hash, Default)]
pub struct StmtBlock {
pub statements: StaticVec<Stmt>,
pub pos: Position,
}
impl StmtBlock {
/// Is this statements block empty?
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.statements.is_empty()
}
/// Number of statements in this statements block.
#[inline(always)]
pub fn len(&self) -> usize {
self.statements.len()
}
}
impl fmt::Debug for StmtBlock {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.pos.is_none() {
write!(f, "{} @ ", self.pos)?;
}
fmt::Debug::fmt(&self.statements, f)
}
}
/// _(INTERNALS)_ A statement. /// _(INTERNALS)_ A statement.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
@ -815,32 +864,36 @@ pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),
/// `if` expr `{` stmt `}` `else` `{` stmt `}` /// `if` expr `{` stmt `}` `else` `{` stmt `}`
If(Expr, Box<(Stmt, Option<Stmt>)>, Position), If(Expr, Box<(StmtBlock, StmtBlock)>, Position),
/// `switch` expr `{` literal or _ `=>` stmt `,` ... `}` /// `switch` expr `{` literal or _ `=>` stmt `,` ... `}`
Switch( Switch(
Expr, Expr,
Box<( Box<(
HashableHashMap<u64, Stmt, StraightHasherBuilder>, HashableHashMap<u64, StmtBlock, StraightHasherBuilder>,
Option<Stmt>, StmtBlock,
)>, )>,
Position, Position,
), ),
/// `while` expr `{` stmt `}` /// `while` expr `{` stmt `}`
While(Option<Expr>, Box<Stmt>, Position), While(Expr, Box<StmtBlock>, Position),
/// `do` `{` stmt `}` `while`|`until` expr /// `do` `{` stmt `}` `while`|`until` expr
Do(Box<Stmt>, Expr, bool, Position), Do(Box<StmtBlock>, Expr, bool, Position),
/// `for` id `in` expr `{` stmt `}` /// `for` id `in` expr `{` stmt `}`
For(Expr, Box<(String, Stmt)>, Position), For(Expr, Box<(String, StmtBlock)>, Position),
/// \[`export`\] `let` id `=` expr /// \[`export`\] `let` id `=` expr
Let(Box<Ident>, Option<Expr>, bool, Position), Let(Expr, Ident, bool, Position),
/// \[`export`\] `const` id `=` expr /// \[`export`\] `const` id `=` expr
Const(Box<Ident>, Option<Expr>, bool, Position), Const(Expr, Ident, bool, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Expr, Expr, Option<OpAssignment>)>, Position), Assignment(Box<(Expr, Expr, Option<OpAssignment>)>, Position),
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch(Box<(Stmt, Option<Ident>, Stmt)>, Position, Position), TryCatch(
Box<(StmtBlock, Option<Ident>, StmtBlock)>,
Position,
Position,
),
/// [expression][Expr] /// [expression][Expr]
Expr(Expr), Expr(Expr),
/// `continue` /// `continue`
@ -848,10 +901,10 @@ pub enum Stmt {
/// `break` /// `break`
Break(Position), Break(Position),
/// `return`/`throw` /// `return`/`throw`
Return((ReturnType, Position), Option<Expr>, Position), Return(ReturnType, Option<Expr>, Position),
/// `import` expr `as` var /// `import` expr `as` var
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import(Expr, Option<Box<Ident>>, Position), Import(Expr, Option<Ident>, Position),
/// `export` var `as` var `,` ... /// `export` var `as` var `,` ...
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export(Vec<(Ident, Option<Ident>)>, Position), Export(Vec<(Ident, Option<Ident>)>, Position),
@ -867,6 +920,27 @@ impl Default for Stmt {
} }
} }
impl From<Stmt> for StmtBlock {
#[inline(always)]
fn from(stmt: Stmt) -> Self {
match stmt {
Stmt::Block(block, pos) => Self {
statements: block.into(),
pos,
},
Stmt::Noop(pos) => Self {
statements: Default::default(),
pos,
},
_ => {
let pos = stmt.position();
let statements = vec![stmt].into();
Self { statements, pos }
}
}
}
}
impl Stmt { impl Stmt {
/// Is this statement [`Noop`][Stmt::Noop]? /// Is this statement [`Noop`][Stmt::Noop]?
#[inline(always)] #[inline(always)]
@ -889,7 +963,7 @@ impl Stmt {
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Do(_, _, _, pos) | Self::Do(_, _, _, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::Return((_, pos), _, _) | Self::Return(_, _, pos)
| Self::Let(_, _, _, pos) | Self::Let(_, _, _, pos)
| Self::Const(_, _, _, pos) | Self::Const(_, _, _, pos)
| Self::TryCatch(_, pos, _) => *pos, | Self::TryCatch(_, pos, _) => *pos,
@ -918,7 +992,7 @@ impl Stmt {
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Do(_, _, _, pos) | Self::Do(_, _, _, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::Return((_, pos), _, _) | Self::Return(_, _, pos)
| Self::Let(_, _, _, pos) | Self::Let(_, _, _, pos)
| Self::Const(_, _, _, pos) | Self::Const(_, _, _, pos)
| Self::TryCatch(_, pos, _) => *pos = new_pos, | Self::TryCatch(_, pos, _) => *pos = new_pos,
@ -938,6 +1012,31 @@ impl Stmt {
self self
} }
/// Does this statement return a value?
pub fn returns_value(&self) -> bool {
match self {
Self::If(_, _, _) | Self::Switch(_, _, _) | Self::Block(_, _) | Self::Expr(_) => true,
Self::Noop(_)
| Self::While(_, _, _)
| Self::Do(_, _, _, _)
| Self::For(_, _, _)
| Self::TryCatch(_, _, _) => false,
Self::Let(_, _, _, _)
| Self::Const(_, _, _, _)
| Self::Assignment(_, _)
| Self::Continue(_)
| Self::Break(_)
| Self::Return(_, _, _) => false,
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => unreachable!("Stmt::Share should not be parsed"),
}
}
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)? /// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool { pub fn is_self_terminated(&self) -> bool {
match self { match self {
@ -976,23 +1075,29 @@ impl Stmt {
Self::Expr(expr) => expr.is_pure(), Self::Expr(expr) => expr.is_pure(),
Self::If(condition, x, _) => { Self::If(condition, x, _) => {
condition.is_pure() condition.is_pure()
&& x.0.is_pure() && x.0.statements.iter().all(Stmt::is_pure)
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true) && x.1.statements.iter().all(Stmt::is_pure)
} }
Self::Switch(expr, x, _) => { Self::Switch(expr, x, _) => {
expr.is_pure() expr.is_pure()
&& x.0.values().all(Stmt::is_pure) && x.0
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true) .values()
.flat_map(|block| block.statements.iter())
.all(Stmt::is_pure)
&& x.1.statements.iter().all(Stmt::is_pure)
} }
Self::While(Some(condition), block, _) | Self::Do(block, condition, _, _) => { Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
condition.is_pure() && block.is_pure() condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
}
Self::For(iterable, x, _) => {
iterable.is_pure() && x.1.statements.iter().all(Stmt::is_pure)
} }
Self::While(None, block, _) => block.is_pure(),
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false, Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(), Self::TryCatch(x, _, _) => {
x.0.statements.iter().all(Stmt::is_pure) && x.2.statements.iter().all(Stmt::is_pure)
}
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) => false, Self::Import(_, _, _) => false,
@ -1003,53 +1108,150 @@ impl Stmt {
Self::Share(_) => false, Self::Share(_) => false,
} }
} }
/// Recursively walk this statement. /// Is this statement _pure_ within the containing block?
///
/// An internally pure statement only has side effects that disappear outside the block.
///
/// Only variable declarations (i.e. `let` and `const`) and `import`/`export` statements
/// are internally pure.
#[inline(always)] #[inline(always)]
pub fn walk<'a>(&'a self, path: &mut Vec<ASTNode<'a>>, on_node: &mut impl FnMut(&[ASTNode])) { pub fn is_internally_pure(&self) -> bool {
match self {
Self::Let(expr, _, _, _) | Self::Const(expr, _, _, _) => expr.is_pure(),
#[cfg(not(feature = "no_module"))]
Self::Import(expr, _, _) => expr.is_pure(),
#[cfg(not(feature = "no_module"))]
Self::Export(_, _) => true,
_ => self.is_pure(),
}
}
/// Does this statement break the current control flow through the containing block?
///
/// Currently this is only true for `return`, `throw`, `break` and `continue`.
///
/// All statements following this statement will essentially be dead code.
#[inline(always)]
pub fn is_control_flow_break(&self) -> bool {
match self {
Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true,
_ => false,
}
}
/// Recursively walk this statement.
/// Return `false` from the callback to terminate the walk.
pub fn walk<'a>(
&'a self,
path: &mut Vec<ASTNode<'a>>,
on_node: &mut impl FnMut(&[ASTNode]) -> bool,
) -> bool {
path.push(self.into()); path.push(self.into());
on_node(path);
if !on_node(path) {
return false;
}
match self { match self {
Self::Let(_, Some(e), _, _) | Self::Const(_, Some(e), _, _) => e.walk(path, on_node), Self::Let(e, _, _, _) | Self::Const(e, _, _, _) => {
if !e.walk(path, on_node) {
return false;
}
}
Self::If(e, x, _) => { Self::If(e, x, _) => {
e.walk(path, on_node); if !e.walk(path, on_node) {
x.0.walk(path, on_node); return false;
if let Some(ref s) = x.1 { }
s.walk(path, on_node); for s in &x.0.statements {
if !s.walk(path, on_node) {
return false;
}
}
for s in &x.1.statements {
if !s.walk(path, on_node) {
return false;
}
} }
} }
Self::Switch(e, x, _) => { Self::Switch(e, x, _) => {
e.walk(path, on_node); if !e.walk(path, on_node) {
x.0.values().for_each(|s| s.walk(path, on_node)); return false;
if let Some(ref s) = x.1 { }
s.walk(path, on_node); for s in x.0.values().flat_map(|block| block.statements.iter()) {
if !s.walk(path, on_node) {
return false;
}
}
for s in &x.1.statements {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::While(e, s, _) | Self::Do(s, e, _, _) => {
if !e.walk(path, on_node) {
return false;
}
for s in &s.statements {
if !s.walk(path, on_node) {
return false;
} }
} }
Self::While(Some(e), s, _) | Self::Do(s, e, _, _) => {
e.walk(path, on_node);
s.walk(path, on_node);
} }
Self::While(None, s, _) => s.walk(path, on_node),
Self::For(e, x, _) => { Self::For(e, x, _) => {
e.walk(path, on_node); if !e.walk(path, on_node) {
x.1.walk(path, on_node); return false;
}
for s in &x.1.statements {
if !s.walk(path, on_node) {
return false;
}
}
} }
Self::Assignment(x, _) => { Self::Assignment(x, _) => {
x.0.walk(path, on_node); if !x.0.walk(path, on_node) {
x.1.walk(path, on_node); return false;
}
if !x.1.walk(path, on_node) {
return false;
}
}
Self::Block(x, _) => {
for s in x {
if !s.walk(path, on_node) {
return false;
}
}
} }
Self::Block(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
Self::TryCatch(x, _, _) => { Self::TryCatch(x, _, _) => {
x.0.walk(path, on_node); for s in &x.0.statements {
x.2.walk(path, on_node); if !s.walk(path, on_node) {
return false;
}
}
for s in &x.2.statements {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::Expr(e) | Self::Return(_, Some(e), _) => {
if !e.walk(path, on_node) {
return false;
}
} }
Self::Expr(e) | Self::Return(_, Some(e), _) => e.walk(path, on_node),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(e, _, _) => e.walk(path, on_node), Self::Import(e, _, _) => {
if !e.walk(path, on_node) {
return false;
}
}
_ => (), _ => (),
} }
path.pop().unwrap(); path.pop().unwrap();
true
} }
} }
@ -1102,7 +1304,7 @@ pub struct OpAssignment {
/// # Volatile Data Structure /// # Volatile Data Structure
/// ///
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] #[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
pub struct FnHash { pub struct FnHash {
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only). /// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
script: Option<u64>, script: Option<u64>,
@ -1110,6 +1312,20 @@ pub struct FnHash {
native: u64, native: u64,
} }
impl fmt::Debug for FnHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(script) = self.script {
if script == self.native {
write!(f, "({}=={})", script, self.native)
} else {
write!(f, "({}, {})", script, self.native)
}
} else {
write!(f, "{}", self.native)
}
}
}
impl FnHash { impl FnHash {
/// Create a [`FnHash`] with only the native Rust hash. /// Create a [`FnHash`] with only the native Rust hash.
#[inline(always)] #[inline(always)]
@ -1185,6 +1401,7 @@ pub struct FloatWrapper(FLOAT);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper { impl Hash for FloatWrapper {
#[inline(always)]
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) { fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
self.0.to_ne_bytes().hash(state); self.0.to_ne_bytes().hash(state);
} }
@ -1192,6 +1409,7 @@ impl Hash for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl AsRef<FLOAT> for FloatWrapper { impl AsRef<FLOAT> for FloatWrapper {
#[inline(always)]
fn as_ref(&self) -> &FLOAT { fn as_ref(&self) -> &FLOAT {
&self.0 &self.0
} }
@ -1199,6 +1417,7 @@ impl AsRef<FLOAT> for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl AsMut<FLOAT> for FloatWrapper { impl AsMut<FLOAT> for FloatWrapper {
#[inline(always)]
fn as_mut(&mut self) -> &mut FLOAT { fn as_mut(&mut self) -> &mut FLOAT {
&mut self.0 &mut self.0
} }
@ -1208,6 +1427,7 @@ impl AsMut<FLOAT> for FloatWrapper {
impl crate::stdlib::ops::Deref for FloatWrapper { impl crate::stdlib::ops::Deref for FloatWrapper {
type Target = FLOAT; type Target = FLOAT;
#[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
@ -1215,6 +1435,7 @@ impl crate::stdlib::ops::Deref for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl crate::stdlib::ops::DerefMut for FloatWrapper { impl crate::stdlib::ops::DerefMut for FloatWrapper {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
} }
@ -1222,6 +1443,7 @@ impl crate::stdlib::ops::DerefMut for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl fmt::Debug for FloatWrapper { impl fmt::Debug for FloatWrapper {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f) fmt::Display::fmt(self, f)
} }
@ -1229,6 +1451,7 @@ impl fmt::Debug for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl fmt::Display for FloatWrapper { impl fmt::Display for FloatWrapper {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use num_traits::Float; use num_traits::Float;
@ -1244,6 +1467,7 @@ impl fmt::Display for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FLOAT> for FloatWrapper { impl From<FLOAT> for FloatWrapper {
#[inline(always)]
fn from(value: FLOAT) -> Self { fn from(value: FLOAT) -> Self {
Self::new(value) Self::new(value)
} }
@ -1253,6 +1477,7 @@ impl From<FLOAT> for FloatWrapper {
impl FromStr for FloatWrapper { impl FromStr for FloatWrapper {
type Err = <FLOAT as FromStr>::Err; type Err = <FLOAT as FromStr>::Err;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
FLOAT::from_str(s).map(Into::<Self>::into) FLOAT::from_str(s).map(Into::<Self>::into)
} }
@ -1260,6 +1485,7 @@ impl FromStr for FloatWrapper {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl FloatWrapper { impl FloatWrapper {
#[inline(always)]
pub const fn new(value: FLOAT) -> Self { pub const fn new(value: FLOAT) -> Self {
Self(value) Self(value)
} }
@ -1298,18 +1524,16 @@ pub enum Expr {
Unit(Position), Unit(Position),
/// Variable access - (optional index, optional (hash, modules), variable name) /// Variable access - (optional index, optional (hash, modules), variable name)
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>), Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
/// Property access - (getter, hash, setter, hash, prop) /// Property access - ((getter, hash), (setter, hash), prop)
Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>), Property(Box<((ImmutableString, u64), (ImmutableString, u64), Ident)>),
/// { [statement][Stmt] } /// { [statement][Stmt] ... }
Stmt(Box<StaticVec<Stmt>>, Position), Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
/// lhs `.` rhs /// lhs `.` rhs
Dot(Box<BinaryExpr>, Position), Dot(Box<BinaryExpr>, Position),
/// expr `[` expr `]` /// expr `[` expr `]`
Index(Box<BinaryExpr>, Position), Index(Box<BinaryExpr>, Position),
/// lhs `in` rhs
In(Box<BinaryExpr>, Position),
/// lhs `&&` rhs /// lhs `&&` rhs
And(Box<BinaryExpr>, Position), And(Box<BinaryExpr>, Position),
/// lhs `||` rhs /// lhs `||` rhs
@ -1329,6 +1553,7 @@ impl Expr {
/// Get the [`Dynamic`] value of a constant expression. /// Get the [`Dynamic`] value of a constant expression.
/// ///
/// Returns [`None`] if the expression is not constant. /// Returns [`None`] if the expression is not constant.
#[inline]
pub fn get_constant_value(&self) -> Option<Dynamic> { pub fn get_constant_value(&self) -> Option<Dynamic> {
Some(match self { Some(match self {
Self::DynamicConstant(x, _) => x.as_ref().clone(), Self::DynamicConstant(x, _) => x.as_ref().clone(),
@ -1379,6 +1604,7 @@ impl Expr {
} }
} }
/// Get the [position][Position] of the expression. /// Get the [position][Position] of the expression.
#[inline]
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -1392,12 +1618,12 @@ impl Expr {
Self::FnPointer(_, pos) => *pos, Self::FnPointer(_, pos) => *pos,
Self::Array(_, pos) => *pos, Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos, Self::Map(_, pos) => *pos,
Self::Property(x) => (x.4).pos, Self::Property(x) => (x.2).pos,
Self::Stmt(_, pos) => *pos, Self::Stmt(x) => x.pos,
Self::Variable(x) => (x.2).pos, Self::Variable(x) => (x.2).pos,
Self::FnCall(_, pos) => *pos, Self::FnCall(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(), Self::And(x, _) | Self::Or(x, _) => x.lhs.position(),
Self::Unit(pos) => *pos, Self::Unit(pos) => *pos,
@ -1407,6 +1633,7 @@ impl Expr {
} }
} }
/// Override the [position][Position] of the expression. /// Override the [position][Position] of the expression.
#[inline]
pub fn set_position(&mut self, new_pos: Position) -> &mut Self { pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self { match self {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -1421,10 +1648,10 @@ impl Expr {
Self::Array(_, pos) => *pos = new_pos, Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos,
Self::Variable(x) => (x.2).pos = new_pos, Self::Variable(x) => (x.2).pos = new_pos,
Self::Property(x) => (x.4).pos = new_pos, Self::Property(x) => (x.2).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos, Self::Stmt(x) => x.pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos,
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, Self::And(_, pos) | Self::Or(_, pos) => *pos = new_pos,
Self::Unit(pos) => *pos = new_pos, Self::Unit(pos) => *pos = new_pos,
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos, Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
Self::Custom(_, pos) => *pos = new_pos, Self::Custom(_, pos) => *pos = new_pos,
@ -1435,17 +1662,18 @@ impl Expr {
/// Is the expression pure? /// Is the expression pure?
/// ///
/// A pure expression has no side effects. /// A pure expression has no side effects.
#[inline]
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Self::Array(x, _) => x.iter().all(Self::is_pure), Self::Array(x, _) => x.iter().all(Self::is_pure),
Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure), Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure),
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => { Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) => {
x.lhs.is_pure() && x.rhs.is_pure() x.lhs.is_pure() && x.rhs.is_pure()
} }
Self::Stmt(x, _) => x.iter().all(Stmt::is_pure), Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
Self::Variable(_) => true, Self::Variable(_) => true,
@ -1461,6 +1689,7 @@ impl Expr {
} }
} }
/// Is the expression a constant? /// Is the expression a constant?
#[inline]
pub fn is_constant(&self) -> bool { pub fn is_constant(&self) -> bool {
match self { match self {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -1480,17 +1709,11 @@ impl Expr {
// An map literal is constant if all items are constant // An map literal is constant if all items are constant
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant), Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant),
// Check in expression
Self::In(x, _) => match (&x.lhs, &x.rhs) {
(Self::StringConstant(_, _), Self::StringConstant(_, _))
| (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true,
_ => false,
},
_ => false, _ => false,
} }
} }
/// Is a particular [token][Token] allowed as a postfix operator to this expression? /// Is a particular [token][Token] allowed as a postfix operator to this expression?
#[inline]
pub fn is_valid_postfix(&self, token: &Token) -> bool { pub fn is_valid_postfix(&self, token: &Token) -> bool {
match token { match token {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -1507,14 +1730,13 @@ impl Expr {
| Self::IntegerConstant(_, _) | Self::IntegerConstant(_, _)
| Self::CharConstant(_, _) | Self::CharConstant(_, _)
| Self::FnPointer(_, _) | Self::FnPointer(_, _)
| Self::In(_, _)
| Self::And(_, _) | Self::And(_, _)
| Self::Or(_, _) | Self::Or(_, _)
| Self::Unit(_) => false, | Self::Unit(_) => false,
Self::StringConstant(_, _) Self::StringConstant(_, _)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Stmt(_, _) | Self::Stmt(_)
| Self::Dot(_, _) | Self::Dot(_, _)
| Self::Index(_, _) | Self::Index(_, _)
| Self::Array(_, _) | Self::Array(_, _)
@ -1544,29 +1766,68 @@ impl Expr {
} }
} }
/// Recursively walk this expression. /// Recursively walk this expression.
#[inline(always)] /// Return `false` from the callback to terminate the walk.
pub fn walk<'a>(&'a self, path: &mut Vec<ASTNode<'a>>, on_node: &mut impl FnMut(&[ASTNode])) { pub fn walk<'a>(
&'a self,
path: &mut Vec<ASTNode<'a>>,
on_node: &mut impl FnMut(&[ASTNode]) -> bool,
) -> bool {
path.push(self.into()); path.push(self.into());
on_node(path);
if !on_node(path) {
return false;
}
match self { match self {
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)), Self::Stmt(x) => {
Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)), for s in &x.statements {
Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)), if !s.walk(path, on_node) {
Self::Index(x, _) return false;
| Self::Dot(x, _) }
| Expr::In(x, _) }
| Expr::And(x, _) }
| Expr::Or(x, _) => { Self::Array(x, _) => {
x.lhs.walk(path, on_node); for e in x.as_ref() {
x.rhs.walk(path, on_node); if !e.walk(path, on_node) {
return false;
}
}
}
Self::Map(x, _) => {
for (_, e) in x.as_ref() {
if !e.walk(path, on_node) {
return false;
}
}
}
Self::Index(x, _) | Self::Dot(x, _) | Expr::And(x, _) | Expr::Or(x, _) => {
if !x.lhs.walk(path, on_node) {
return false;
}
if !x.rhs.walk(path, on_node) {
return false;
}
}
Self::FnCall(x, _) => {
for e in &x.args {
if !e.walk(path, on_node) {
return false;
}
}
}
Self::Custom(x, _) => {
for e in &x.keywords {
if !e.walk(path, on_node) {
return false;
}
}
} }
Self::FnCall(x, _) => x.args.iter().for_each(|e| e.walk(path, on_node)),
Self::Custom(x, _) => x.keywords.iter().for_each(|e| e.walk(path, on_node)),
_ => (), _ => (),
} }
path.pop().unwrap(); path.pop().unwrap();
true
} }
} }
@ -1583,8 +1844,8 @@ mod tests {
assert_eq!(size_of::<Position>(), 4); assert_eq!(size_of::<Position>(), 4);
assert_eq!(size_of::<ast::Expr>(), 16); assert_eq!(size_of::<ast::Expr>(), 16);
assert_eq!(size_of::<Option<ast::Expr>>(), 16); assert_eq!(size_of::<Option<ast::Expr>>(), 16);
assert_eq!(size_of::<ast::Stmt>(), 32); assert_eq!(size_of::<ast::Stmt>(), 40);
assert_eq!(size_of::<Option<ast::Stmt>>(), 32); assert_eq!(size_of::<Option<ast::Stmt>>(), 40);
assert_eq!(size_of::<FnPtr>(), 32); assert_eq!(size_of::<FnPtr>(), 32);
assert_eq!(size_of::<Scope>(), 48); assert_eq!(size_of::<Scope>(), 48);
assert_eq!(size_of::<LexError>(), 56); assert_eq!(size_of::<LexError>(), 56);

View File

@ -1,8 +1,5 @@
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST}; use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST};
#[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel;
use std::{ use std::{
env, env,
fs::File, fs::File,
@ -56,21 +53,25 @@ fn print_help() {
} }
fn main() { fn main() {
let mut engine = Engine::new();
println!("Rhai REPL tool"); println!("Rhai REPL tool");
println!("=============="); println!("==============");
print_help(); print_help();
// Load init scripts // Initialize scripting engine
let mut engine = Engine::new();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
// Set a file module resolver without caching
let mut resolver = rhai::module_resolvers::FileModuleResolver::new();
resolver.enable_cache(false);
engine.set_module_resolver(resolver);
// Load init scripts
let mut contents = String::new(); let mut contents = String::new();
let mut has_init_scripts = false; let mut has_init_scripts = false;
for filename in env::args().skip(1) { for filename in env::args().skip(1) {
{
contents.clear(); contents.clear();
let mut f = match File::open(&filename) { let mut f = match File::open(&filename) {
@ -85,7 +86,6 @@ fn main() {
println!("Error reading script file: {}\n{}", filename, err); println!("Error reading script file: {}\n{}", filename, err);
exit(1); exit(1);
} }
}
let module = match engine let module = match engine
.compile(&contents) .compile(&contents)
@ -119,9 +119,8 @@ fn main() {
} }
// Setup Engine // Setup Engine
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(OptimizationLevel::None); engine.set_optimization_level(rhai::OptimizationLevel::None);
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -130,8 +129,10 @@ fn main() {
let mut ast_u: AST = Default::default(); let mut ast_u: AST = Default::default();
let mut ast: AST = Default::default(); let mut ast: AST = Default::default();
// REPL loop // Make Engine immutable
let engine = engine;
// REPL loop
'main_loop: loop { 'main_loop: loop {
print!("rhai-repl> "); print!("rhai-repl> ");
stdout().flush().expect("couldn't flush stdout"); stdout().flush().expect("couldn't flush stdout");
@ -233,7 +234,7 @@ fn main() {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
{ {
ast = engine.optimize_ast(&scope, r, OptimizationLevel::Simple); ast = engine.optimize_ast(&scope, r, rhai::OptimizationLevel::Simple);
} }
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]

View File

@ -1,6 +1,6 @@
//! Main module defining the script evaluation [`Engine`]. //! Main module defining the script evaluation [`Engine`].
use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt, StmtBlock};
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_native::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
@ -17,7 +17,6 @@ use crate::stdlib::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt, format, fmt, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty,
num::{NonZeroU64, NonZeroU8, NonZeroUsize}, num::{NonZeroU64, NonZeroU8, NonZeroUsize},
ops::DerefMut, ops::DerefMut,
string::{String, ToString}, string::{String, ToString},
@ -25,12 +24,12 @@ use crate::stdlib::{
use crate::syntax::CustomSyntax; use crate::syntax::CustomSyntax;
use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::utils::{get_hasher, StraightHasherBuilder};
use crate::{ use crate::{
calc_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared,
Scope, Shared, StaticVec, StaticVec,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::Array; use crate::{calc_fn_hash, stdlib::iter::empty, Array};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub const TYPICAL_ARRAY_SIZE: usize = 8; // Small arrays are typical pub const TYPICAL_ARRAY_SIZE: usize = 8; // Small arrays are typical
@ -203,9 +202,15 @@ pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub const FN_ANONYMOUS: &str = "anon$"; pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
/// Standard equality comparison operator.
pub const OP_EQUALS: &str = "=="; pub const OP_EQUALS: &str = "==";
/// Standard method function for containment testing.
///
/// The `in` operator is implemented as a call to this method.
pub const OP_CONTAINS: &str = "contains";
/// Method of chaining. /// Method of chaining.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@ -223,11 +228,11 @@ pub enum ChainType {
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum ChainArgument { pub enum ChainArgument {
/// Dot-property access. /// Dot-property access.
Property, Property(Position),
/// Arguments to a dot-function call. /// Arguments to a dot-function call.
FnCallArgs(StaticVec<Dynamic>), FnCallArgs(StaticVec<Dynamic>, StaticVec<Position>),
/// Index value. /// Index value.
IndexValue(Dynamic), IndexValue(Dynamic, Position),
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
@ -241,8 +246,10 @@ impl ChainArgument {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn as_index_value(self) -> Dynamic { pub fn as_index_value(self) -> Dynamic {
match self { match self {
Self::Property | Self::FnCallArgs(_) => panic!("expecting ChainArgument::IndexValue"), Self::Property(_) | Self::FnCallArgs(_, _) => {
Self::IndexValue(value) => value, panic!("expecting ChainArgument::IndexValue")
}
Self::IndexValue(value, _) => value,
} }
} }
/// Return the `StaticVec<Dynamic>` value. /// Return the `StaticVec<Dynamic>` value.
@ -252,27 +259,29 @@ impl ChainArgument {
/// Panics if not `ChainArgument::FnCallArgs`. /// Panics if not `ChainArgument::FnCallArgs`.
#[inline(always)] #[inline(always)]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn as_fn_call_args(self) -> StaticVec<Dynamic> { pub fn as_fn_call_args(self) -> (StaticVec<Dynamic>, StaticVec<Position>) {
match self { match self {
Self::Property | Self::IndexValue(_) => panic!("expecting ChainArgument::FnCallArgs"), Self::Property(_) | Self::IndexValue(_, _) => {
Self::FnCallArgs(value) => value, panic!("expecting ChainArgument::FnCallArgs")
}
Self::FnCallArgs(values, positions) => (values, positions),
} }
} }
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl From<StaticVec<Dynamic>> for ChainArgument { impl From<(StaticVec<Dynamic>, StaticVec<Position>)> for ChainArgument {
#[inline(always)] #[inline(always)]
fn from(value: StaticVec<Dynamic>) -> Self { fn from((values, positions): (StaticVec<Dynamic>, StaticVec<Position>)) -> Self {
Self::FnCallArgs(value) Self::FnCallArgs(values, positions)
} }
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl From<Dynamic> for ChainArgument { impl From<(Dynamic, Position)> for ChainArgument {
#[inline(always)] #[inline(always)]
fn from(value: Dynamic) -> Self { fn from((value, pos): (Dynamic, Position)) -> Self {
Self::IndexValue(value) Self::IndexValue(value, pos)
} }
} }
@ -395,7 +404,11 @@ impl<'a> Target<'a> {
} }
/// Update the value of the `Target`. /// Update the value of the `Target`.
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
pub fn set_value(&mut self, new_val: Dynamic, pos: Position) -> Result<(), Box<EvalAltResult>> { pub fn set_value(
&mut self,
new_val: Dynamic,
_pos: Position,
) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(r) => **r = new_val, Self::Ref(r) => **r = new_val,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -411,7 +424,7 @@ impl<'a> Target<'a> {
Box::new(EvalAltResult::ErrorMismatchDataType( Box::new(EvalAltResult::ErrorMismatchDataType(
"char".to_string(), "char".to_string(),
err.to_string(), err.to_string(),
pos, _pos,
)) ))
})?; })?;
@ -893,6 +906,7 @@ impl Engine {
} }
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
#[inline]
pub(crate) fn search_imports( pub(crate) fn search_imports(
&self, &self,
mods: &Imports, mods: &Imports,
@ -932,7 +946,7 @@ impl Engine {
match expr { match expr {
Expr::Variable(v) => match v.as_ref() { Expr::Variable(v) => match v.as_ref() {
// Qualified variable // Qualified variable
(_, Some((hash_var, modules)), Ident { name, pos }) => { (_, Some((hash_var, modules)), Ident { name, pos, .. }) => {
let module = self.search_imports(mods, state, modules).ok_or_else(|| { let module = self.search_imports(mods, state, modules).ok_or_else(|| {
EvalAltResult::ErrorModuleNotFound( EvalAltResult::ErrorModuleNotFound(
modules[0].name.to_string(), modules[0].name.to_string(),
@ -971,7 +985,7 @@ impl Engine {
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
let (index, _, Ident { name, pos }) = match expr { let (index, _, Ident { name, pos, .. }) = match expr {
Expr::Variable(v) => v.as_ref(), Expr::Variable(v) => v.as_ref(),
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr), _ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
}; };
@ -1152,9 +1166,9 @@ impl Engine {
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
let FnCallExpr { name, hash, .. } = x.as_ref(); let FnCallExpr { name, hash, .. } = x.as_ref();
let args = idx_val.as_fn_call_args(); let mut args = idx_val.as_fn_call_args();
self.make_method_call( self.make_method_call(
mods, state, lib, name, *hash, target, args, *pos, level, mods, state, lib, name, *hash, target, &mut args, *pos, level,
) )
} }
// xxx.fn_name(...) = ??? // xxx.fn_name(...) = ???
@ -1167,7 +1181,7 @@ impl Engine {
} }
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => { Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
let Ident { name, pos } = &x.4; let Ident { name, pos, .. } = &x.2;
let index = name.clone().into(); let index = name.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, true, is_ref, false, level, mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
@ -1180,7 +1194,7 @@ impl Engine {
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target_val.is::<Map>() => { Expr::Property(x) if target_val.is::<Map>() => {
let Ident { name, pos } = &x.4; let Ident { name, pos, .. } = &x.2;
let index = name.clone().into(); let index = name.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, false, is_ref, false, level, mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
@ -1190,7 +1204,7 @@ impl Engine {
} }
// xxx.id = ??? // xxx.id = ???
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
let (_, _, setter, hash_set, Ident { pos, .. }) = x.as_ref(); let (_, (setter, hash_set), Ident { pos, .. }) = x.as_ref();
let hash = FnHash::from_native(*hash_set); let hash = FnHash::from_native(*hash_set);
let mut new_val = new_val; let mut new_val = new_val;
let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0]; let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
@ -1202,7 +1216,7 @@ impl Engine {
} }
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
let (getter, hash_get, _, _, Ident { pos, .. }) = x.as_ref(); let ((getter, hash_get), _, Ident { pos, .. }) = x.as_ref();
let hash = FnHash::from_native(*hash_get); let hash = FnHash::from_native(*hash_get);
let mut args = [target_val]; let mut args = [target_val];
self.exec_fn_call( self.exec_fn_call(
@ -1215,7 +1229,7 @@ impl Engine {
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => {
let mut val = match &x.lhs { let mut val = match &x.lhs {
Expr::Property(p) => { Expr::Property(p) => {
let Ident { name, pos } = &p.4; let Ident { name, pos, .. } = &p.2;
let index = name.clone().into(); let index = name.clone().into();
self.get_indexed_mut( self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, false, is_ref, true, mods, state, lib, target_val, index, *pos, false, is_ref, true,
@ -1225,9 +1239,9 @@ impl Engine {
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x, pos) if x.namespace.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { name, hash, .. } = x.as_ref(); let FnCallExpr { name, hash, .. } = x.as_ref();
let args = idx_val.as_fn_call_args(); let mut args = idx_val.as_fn_call_args();
let (val, _) = self.make_method_call( let (val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, *pos, level, mods, state, lib, name, *hash, target, &mut args, *pos, level,
)?; )?;
val.into() val.into()
} }
@ -1250,7 +1264,7 @@ impl Engine {
match &x.lhs { match &x.lhs {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => { Expr::Property(p) => {
let (getter, hash_get, setter, hash_set, Ident { pos, .. }) = let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) =
p.as_ref(); p.as_ref();
let hash_get = FnHash::from_native(*hash_get); let hash_get = FnHash::from_native(*hash_get);
let hash_set = FnHash::from_native(*hash_set); let hash_set = FnHash::from_native(*hash_set);
@ -1303,9 +1317,9 @@ impl Engine {
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(f, pos) if f.namespace.is_none() => { Expr::FnCall(f, pos) if f.namespace.is_none() => {
let FnCallExpr { name, hash, .. } = f.as_ref(); let FnCallExpr { name, hash, .. } = f.as_ref();
let args = idx_val.as_fn_call_args(); let mut args = idx_val.as_fn_call_args();
let (mut val, _) = self.make_method_call( let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, *pos, level, mods, state, lib, name, *hash, target, &mut args, *pos, level,
)?; )?;
let val = &mut val; let val = &mut val;
let target = &mut val.into(); let target = &mut val.into();
@ -1364,6 +1378,7 @@ impl Engine {
let Ident { let Ident {
name: var_name, name: var_name,
pos: var_pos, pos: var_pos,
..
} = &x.2; } = &x.2;
self.inc_operations(state, *var_pos)?; self.inc_operations(state, *var_pos)?;
@ -1422,23 +1437,26 @@ impl Engine {
match expr { match expr {
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => { Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => {
let mut arg_positions: StaticVec<_> = Default::default();
let arg_values = x let arg_values = x
.args .args
.iter() .iter()
.map(|arg_expr| { .map(|arg_expr| {
arg_positions.push(arg_expr.position());
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
.map(Dynamic::flatten) .map(Dynamic::flatten)
}) })
.collect::<Result<StaticVec<_>, _>>()?; .collect::<Result<StaticVec<_>, _>>()?;
idx_values.push(arg_values.into()); idx_values.push((arg_values, arg_positions).into());
} }
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => { Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
unreachable!("function call in dot chain should not be namespace-qualified") unreachable!("function call in dot chain should not be namespace-qualified")
} }
Expr::Property(_) if parent_chain_type == ChainType::Dot => { Expr::Property(x) if parent_chain_type == ChainType::Dot => {
idx_values.push(ChainArgument::Property) idx_values.push(ChainArgument::Property(x.2.pos))
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
@ -1447,29 +1465,33 @@ impl Engine {
// Evaluate in left-to-right order // Evaluate in left-to-right order
let lhs_val = match lhs { let lhs_val = match lhs {
Expr::Property(_) if parent_chain_type == ChainType::Dot => { Expr::Property(x) if parent_chain_type == ChainType::Dot => {
ChainArgument::Property ChainArgument::Property(x.2.pos)
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
Expr::FnCall(x, _) Expr::FnCall(x, _)
if parent_chain_type == ChainType::Dot && x.namespace.is_none() => if parent_chain_type == ChainType::Dot && x.namespace.is_none() =>
{ {
x.args let mut arg_positions: StaticVec<_> = Default::default();
let arg_values = x
.args
.iter() .iter()
.map(|arg_expr| { .map(|arg_expr| {
arg_positions.push(arg_expr.position());
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
.map(Dynamic::flatten) .map(Dynamic::flatten)
}) })
.collect::<Result<StaticVec<Dynamic>, _>>()? .collect::<Result<StaticVec<_>, _>>()?;
.into()
(arg_values, arg_positions).into()
} }
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => { Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
unreachable!("function call in dot chain should not be namespace-qualified") unreachable!("function call in dot chain should not be namespace-qualified")
} }
_ => self _ => self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, lhs, level)
.flatten() .map(|v| (v.flatten(), lhs.position()).into())?,
.into(),
}; };
// Push in reverse order // Push in reverse order
@ -1486,9 +1508,8 @@ impl Engine {
} }
_ => idx_values.push( _ => idx_values.push(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.flatten() .map(|v| (v.flatten(), expr.position()).into())?,
.into(),
), ),
} }
@ -1603,63 +1624,6 @@ impl Engine {
} }
} }
// Evaluate an 'in' expression.
fn eval_in_expr(
&self,
scope: &mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
lhs: &Expr,
rhs: &Expr,
level: usize,
) -> RhaiResult {
self.inc_operations(state, rhs.position())?;
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
let rhs_value = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
.flatten();
match rhs_value {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value, _)) => {
// Call the `==` operator to compare each value
let hash = calc_fn_hash(empty(), OP_EQUALS, 2);
for value in rhs_value.iter_mut() {
let args = &mut [&mut lhs_value.clone(), value];
let pos = rhs.position();
if self
.call_native_fn(mods, state, lib, OP_EQUALS, hash, args, false, false, pos)?
.0
.as_bool()
.unwrap_or(false)
{
return Ok(true.into());
}
}
Ok(false.into())
}
#[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value, _)) => match lhs_value {
// Only allows string or char
Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
},
Dynamic(Union::Str(rhs_value, _)) => match lhs_value {
// Only allows string or char
Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains(s.as_str()).into()),
Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains(c).into()),
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
},
_ => EvalAltResult::ErrorInExpr(rhs.position()).into(),
}
}
/// Evaluate an expression. /// Evaluate an expression.
pub(crate) fn eval_expr( pub(crate) fn eval_expr(
&self, &self,
@ -1691,16 +1655,11 @@ impl Engine {
.map(|(val, _)| val.take_or_clone()), .map(|(val, _)| val.take_or_clone()),
// Statement block // Statement block
Expr::Stmt(x, _) => self.eval_stmt_block( Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
scope, Expr::Stmt(x) => {
mods, let statements = &x.statements;
state, self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
lib, }
this_ptr,
x.as_ref().as_ref(),
true,
level,
),
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1745,13 +1704,13 @@ impl Engine {
Expr::FnCall(x, pos) if x.namespace.is_none() => { Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { let FnCallExpr {
name, name,
capture: cap_scope, capture,
hash, hash,
args, args,
.. ..
} = x.as_ref(); } = x.as_ref();
self.make_function_call( self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, *hash, *pos, *cap_scope, level, scope, mods, state, lib, this_ptr, name, args, *hash, *pos, *capture, level,
) )
} }
@ -1771,10 +1730,6 @@ impl Engine {
) )
} }
Expr::In(x, _) => {
self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.lhs, &x.rhs, level)
}
Expr::And(x, _) => { Expr::And(x, _) => {
Ok((self Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
@ -1844,6 +1799,10 @@ impl Engine {
restore_prev_state: bool, restore_prev_state: bool,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
if statements.is_empty() {
return Ok(Dynamic::UNIT);
}
let mut _extra_fn_resolution_cache = false; let mut _extra_fn_resolution_cache = false;
let prev_always_search = state.always_search; let prev_always_search = state.always_search;
let prev_scope_len = scope.len(); let prev_scope_len = scope.len();
@ -2065,24 +2024,44 @@ impl Engine {
} }
// Block scope // Block scope
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
Stmt::Block(statements, _) => { Stmt::Block(statements, _) => {
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level) self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
} }
// If statement // If statement
Stmt::If(expr, x, _) => { Stmt::If(expr, x, _) => {
let (if_block, else_block) = x.as_ref(); let (
StmtBlock {
statements: if_stmt,
..
},
StmtBlock {
statements: else_stmt,
..
},
) = x.as_ref();
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool() .as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position())) .map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
.and_then(|guard_val| { .and_then(|guard_val| {
if guard_val { if guard_val {
self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level) if !if_stmt.is_empty() {
} else if let Some(stmt) = else_block { self.eval_stmt_block(
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) scope, mods, state, lib, this_ptr, if_stmt, true, level,
)
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} else {
if !else_stmt.is_empty() {
self.eval_stmt_block(
scope, mods, state, lib, this_ptr, else_stmt, true, level,
)
} else {
Ok(Dynamic::UNIT)
}
}
}) })
} }
@ -2097,36 +2076,55 @@ impl Engine {
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
table table.get(&hash).map(|StmtBlock { statements, .. }| {
.get(&hash) if !statements.is_empty() {
.map(|stmt| self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)) self.eval_stmt_block(
scope, mods, state, lib, this_ptr, statements, true, level,
)
} else {
Ok(Dynamic::UNIT)
}
})
} else { } else {
// Non-hashable values never match any specific clause // Non-hashable values never match any specific clause
None None
} }
.unwrap_or_else(|| { .unwrap_or_else(|| {
// Default match clause // Default match clause
def_stmt.as_ref().map_or_else( let def_stmt = &def_stmt.statements;
|| Ok(Dynamic::UNIT), if !def_stmt.is_empty() {
|def_stmt| { self.eval_stmt_block(
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level) scope, mods, state, lib, this_ptr, def_stmt, true, level,
},
) )
} else {
Ok(Dynamic::UNIT)
}
}) })
} }
// While loop // While loop
Stmt::While(expr, body, _) => loop { Stmt::While(expr, body, _) => {
let condition = if let Some(expr) = expr { let body = &body.statements;
loop {
let condition = if !expr.is_unit() {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool() .as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))? .map_err(|err| {
self.make_type_mismatch_err::<bool>(err, expr.position())
})?
} else { } else {
true true
}; };
if condition { if !condition {
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { return Ok(Dynamic::UNIT);
}
if body.is_empty() {
continue;
}
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
{
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (), EvalAltResult::LoopBreak(false, _) => (),
@ -2134,14 +2132,18 @@ impl Engine {
_ => return Err(err), _ => return Err(err),
}, },
} }
} else {
return Ok(Dynamic::UNIT);
} }
}, }
// Do loop // Do loop
Stmt::Do(body, expr, is_while, _) => loop { Stmt::Do(body, expr, is_while, _) => {
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { let body = &body.statements;
loop {
if !body.is_empty() {
match self
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
{
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => continue, EvalAltResult::LoopBreak(false, _) => continue,
@ -2149,6 +2151,7 @@ impl Engine {
_ => return Err(err), _ => return Err(err),
}, },
} }
}
if self if self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
@ -2163,11 +2166,12 @@ impl Engine {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
} }
}, }
}
// For loop // For loop
Stmt::For(expr, x, _) => { Stmt::For(expr, x, _) => {
let (name, stmt) = x.as_ref(); let (name, StmtBlock { statements, pos }) = x.as_ref();
let iter_obj = self let iter_obj = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
@ -2216,9 +2220,15 @@ impl Engine {
*loop_var = value; *loop_var = value;
} }
self.inc_operations(state, stmt.position())?; self.inc_operations(state, *pos)?;
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) { if statements.is_empty() {
continue;
}
match self.eval_stmt_block(
scope, mods, state, lib, this_ptr, statements, true, level,
) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (), EvalAltResult::LoopBreak(false, _) => (),
@ -2244,10 +2254,20 @@ impl Engine {
// Try/Catch statement // Try/Catch statement
Stmt::TryCatch(x, _, _) => { Stmt::TryCatch(x, _, _) => {
let (try_body, err_var, catch_body) = x.as_ref(); let (
StmtBlock {
statements: try_body,
..
},
err_var,
StmtBlock {
statements: catch_body,
..
},
) = x.as_ref();
let result = self let result = self
.eval_stmt(scope, mods, state, lib, this_ptr, try_body, level) .eval_stmt_block(scope, mods, state, lib, this_ptr, try_body, true, level)
.map(|_| Dynamic::UNIT); .map(|_| Dynamic::UNIT);
match result { match result {
@ -2306,8 +2326,9 @@ impl Engine {
scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value); scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value);
} }
let result = let result = self.eval_stmt_block(
self.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level); scope, mods, state, lib, this_ptr, catch_body, true, level,
);
state.scope_level -= 1; state.scope_level -= 1;
scope.rewind(orig_scope_len); scope.rewind(orig_scope_len);
@ -2328,7 +2349,7 @@ impl Engine {
} }
// Return value // Return value
Stmt::Return((ReturnType::Return, pos), Some(expr), _) => { Stmt::Return(ReturnType::Return, Some(expr), pos) => {
let value = self let value = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
@ -2336,12 +2357,12 @@ impl Engine {
} }
// Empty return // Empty return
Stmt::Return((ReturnType::Return, pos), None, _) => { Stmt::Return(ReturnType::Return, None, pos) => {
EvalAltResult::Return(Default::default(), *pos).into() EvalAltResult::Return(Default::default(), *pos).into()
} }
// Throw value // Throw value
Stmt::Return((ReturnType::Exception, pos), Some(expr), _) => { Stmt::Return(ReturnType::Exception, Some(expr), pos) => {
let value = self let value = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(); .flatten();
@ -2349,38 +2370,34 @@ impl Engine {
} }
// Empty throw // Empty throw
Stmt::Return((ReturnType::Exception, pos), None, _) => { Stmt::Return(ReturnType::Exception, None, pos) => {
EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into() EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()
} }
// Let/const statement // Let/const statement
Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => { Stmt::Let(expr, Ident { name, .. }, export, _)
| Stmt::Const(expr, Ident { name, .. }, export, _) => {
let entry_type = match stmt { let entry_type = match stmt {
Stmt::Let(_, _, _, _) => AccessMode::ReadWrite, Stmt::Let(_, _, _, _) => AccessMode::ReadWrite,
Stmt::Const(_, _, _, _) => AccessMode::ReadOnly, Stmt::Const(_, _, _, _) => AccessMode::ReadOnly,
_ => unreachable!("should be Stmt::Let or Stmt::Const, but gets {:?}", stmt), _ => unreachable!("should be Stmt::Let or Stmt::Const, but gets {:?}", stmt),
}; };
let value = if let Some(expr) = expr { let value = self
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten() .flatten();
} else {
Dynamic::UNIT
};
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
( (
var_def.name.to_string().into(), name.to_string().into(),
if *export { if *export { Some(name.clone()) } else { None },
Some(var_def.name.clone())
} else {
None
},
) )
} else if *export { } else if *export {
unreachable!("exported variable not on global level"); unreachable!("exported variable not on global level");
} else { } else {
(unsafe_cast_var_name_to_lifetime(&var_def.name).into(), None) (unsafe_cast_var_name_to_lifetime(name).into(), None)
}; };
scope.push_dynamic_value(var_name, entry_type, value); scope.push_dynamic_value(var_name, entry_type, value);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -2392,7 +2409,7 @@ impl Engine {
// Import statement // Import statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(expr, alias, _pos) => { Stmt::Import(expr, export, _pos) => {
// Guard against too many modules // Guard against too many modules
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if state.modules >= self.max_modules() { if state.modules >= self.max_modules() {
@ -2419,14 +2436,14 @@ impl Engine {
}) })
.unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?; .unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?;
if let Some(name_def) = alias { if let Some(name) = export.as_ref().map(|x| x.name.clone()) {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not indexed // Index the module (making a clone copy if necessary) if it is not indexed
let mut module = crate::fn_native::shared_take_or_clone(module); let mut module = crate::fn_native::shared_take_or_clone(module);
module.build_index(); module.build_index();
mods.push(name_def.name.clone(), module); mods.push(name, module);
} else { } else {
mods.push(name_def.name.clone(), module); mods.push(name, module);
} }
} }
@ -2441,14 +2458,13 @@ impl Engine {
// Export statement // Export statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(list, _) => { Stmt::Export(list, _) => {
for (Ident { name, pos: id_pos }, rename) in list.iter() { for (Ident { name, pos, .. }, rename) in list.iter() {
// Mark scope variables as public // Mark scope variables as public
if let Some(index) = scope.get_index(name).map(|(i, _)| i) { if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name); let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
scope.add_entry_alias(index, alias.clone()); scope.add_entry_alias(index, alias.clone());
} else { } else {
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos) return EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into();
.into();
} }
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
@ -2482,6 +2498,7 @@ impl Engine {
/// Check a result to ensure that the data size is within allowable limit. /// Check a result to ensure that the data size is within allowable limit.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[inline(always)]
fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult { fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult {
// Simply return all errors // Simply return all errors
if result.is_err() { if result.is_err() {

View File

@ -926,8 +926,9 @@ impl Engine {
if !resolver.contains_path(s) && !imports.contains(s) => if !resolver.contains_path(s) && !imports.contains(s) =>
{ {
imports.insert(s.clone()); imports.insert(s.clone());
true
} }
_ => (), _ => true,
}); });
} }
@ -1832,7 +1833,7 @@ impl Engine {
let lib = Default::default(); let lib = Default::default();
let stmt = crate::stdlib::mem::take(ast.statements_mut()); let stmt = crate::stdlib::mem::take(ast.statements_mut());
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level) crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level)
} }
/// Generate a list of all registered functions. /// Generate a list of all registered functions.
/// ///

View File

@ -1,5 +1,6 @@
//! Built-in implementations for common operators. //! Built-in implementations for common operators.
use crate::engine::OP_CONTAINS;
use crate::fn_native::{FnCallArgs, NativeCallContext}; use crate::fn_native::{FnCallArgs, NativeCallContext};
use crate::stdlib::{any::TypeId, format, string::ToString}; use crate::stdlib::{any::TypeId, format, string::ToString};
use crate::{Dynamic, ImmutableString, RhaiResult, INT}; use crate::{Dynamic, ImmutableString, RhaiResult, INT};
@ -77,6 +78,13 @@ pub fn get_builtin_binary_op_fn(
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ty )) => {
return Some(|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().unwrap();
Ok(x.$func(y).into())
})
};
($func:ident ( $op:tt )) => { ($func:ident ( $op:tt )) => {
return Some(|_, args| { return Some(|_, args| {
let (x, y) = $func(args); let (x, y) = $func(args);
@ -259,6 +267,24 @@ pub fn get_builtin_binary_op_fn(
">=" => impl_op!(get_s1s2(>=)), ">=" => impl_op!(get_s1s2(>=)),
"<" => impl_op!(get_s1s2(<)), "<" => impl_op!(get_s1s2(<)),
"<=" => impl_op!(get_s1s2(<=)), "<=" => impl_op!(get_s1s2(<=)),
OP_CONTAINS => {
return Some(|_, args| {
let s = &*args[0].read_lock::<ImmutableString>().unwrap();
let c = args[1].as_char().unwrap();
Ok((s.contains(c)).into())
})
}
_ => return None,
}
}
// map op string
#[cfg(not(feature = "no_object"))]
if types_pair == (TypeId::of::<crate::Map>(), TypeId::of::<ImmutableString>()) {
use crate::Map;
match op {
OP_CONTAINS => impl_op!(Map.contains_key(ImmutableString)),
_ => return None, _ => return None,
} }
} }
@ -342,6 +368,13 @@ pub fn get_builtin_binary_op_fn(
">=" => impl_op!(ImmutableString >= ImmutableString), ">=" => impl_op!(ImmutableString >= ImmutableString),
"<" => impl_op!(ImmutableString < ImmutableString), "<" => impl_op!(ImmutableString < ImmutableString),
"<=" => impl_op!(ImmutableString <= ImmutableString), "<=" => impl_op!(ImmutableString <= ImmutableString),
OP_CONTAINS => {
return Some(|_, args| {
let s1 = &*args[0].read_lock::<ImmutableString>().unwrap();
let s2 = &*args[1].read_lock::<ImmutableString>().unwrap();
Ok((s1.contains(s2.as_str())).into())
})
}
_ => return None, _ => return None,
} }
} }

View File

@ -493,6 +493,10 @@ impl Engine {
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
}
// Check for stack overflow // Check for stack overflow
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -539,10 +543,10 @@ impl Engine {
} }
// Evaluate the function // Evaluate the function
let stmt = &fn_def.body; let body = &fn_def.body.statements;
let result = self let result = self
.eval_stmt(scope, mods, state, unified_lib, this_ptr, stmt, level) .eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
@ -722,6 +726,10 @@ impl Engine {
let func = func.get_fn_def(); let func = func.get_fn_def();
if func.body.is_empty() {
return Ok((Dynamic::UNIT, false));
}
let scope: &mut Scope = &mut Default::default(); let scope: &mut Scope = &mut Default::default();
// Move captured variables into scope // Move captured variables into scope
@ -831,6 +839,7 @@ impl Engine {
} }
/// Evaluate a text script in place - used primarily for 'eval'. /// Evaluate a text script in place - used primarily for 'eval'.
#[inline]
fn eval_script_expr_in_place( fn eval_script_expr_in_place(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -884,7 +893,7 @@ impl Engine {
fn_name: &str, fn_name: &str,
mut hash: FnHash, mut hash: FnHash,
target: &mut crate::engine::Target, target: &mut crate::engine::Target,
mut call_args: StaticVec<Dynamic>, (call_args, call_arg_positions): &mut (StaticVec<Dynamic>, StaticVec<Position>),
pos: Position, pos: Position,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
@ -902,7 +911,7 @@ impl Engine {
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hashes // Recalculate hashes
let hash = FnHash::from_script(calc_fn_hash(empty(), fn_name, args_len)); let new_hash = FnHash::from_script(calc_fn_hash(empty(), fn_name, args_len));
// Arguments are passed as-is, adding the curried arguments // Arguments are passed as-is, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>(); let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut arg_values = curry let mut arg_values = curry
@ -913,17 +922,32 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, fn_name, hash, args, false, false, pos, None, level, mods, state, lib, fn_name, new_hash, args, false, false, pos, None, level,
) )
} }
KEYWORD_FN_PTR_CALL if call_args.len() > 0 && call_args[0].is::<FnPtr>() => { KEYWORD_FN_PTR_CALL => {
if call_args.len() > 0 {
if !call_args[0].is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(obj.type_name()),
call_arg_positions[0],
));
}
} else {
return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(obj.type_name()),
pos,
));
}
// FnPtr call on object // FnPtr call on object
let fn_ptr = call_args.remove(0).cast::<FnPtr>(); let fn_ptr = call_args.remove(0).cast::<FnPtr>();
call_arg_positions.remove(0);
// Redirect function name // Redirect function name
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len(); let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // Recalculate hash
let hash = FnHash::from_script_and_native( let new_hash = FnHash::from_script_and_native(
calc_fn_hash(empty(), fn_name, args_len), calc_fn_hash(empty(), fn_name, args_len),
calc_fn_hash(empty(), fn_name, args_len + 1), calc_fn_hash(empty(), fn_name, args_len + 1),
); );
@ -937,22 +961,34 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level, mods, state, lib, fn_name, new_hash, args, is_ref, true, pos, None, level,
) )
} }
KEYWORD_FN_PTR_CURRY if obj.is::<FnPtr>() => { KEYWORD_FN_PTR_CURRY => {
// Curry call if !obj.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(obj.type_name()),
pos,
));
}
let fn_ptr = obj.read_lock::<FnPtr>().unwrap(); let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
// Curry call
Ok(( Ok((
if call_args.is_empty() {
fn_ptr.clone()
} else {
FnPtr::new_unchecked( FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(), fn_ptr.get_fn_name().clone(),
fn_ptr fn_ptr
.curry() .curry()
.iter() .iter()
.cloned() .cloned()
.chain(call_args.into_iter()) .chain(call_args.iter_mut().map(|v| mem::take(v)))
.collect(), .collect(),
) )
}
.into(), .into(),
false, false,
)) ))
@ -981,7 +1017,10 @@ impl Engine {
.iter() .iter()
.cloned() .cloned()
.enumerate() .enumerate()
.for_each(|(i, v)| call_args.insert(i, v)); .for_each(|(i, v)| {
call_args.insert(i, v);
call_arg_positions.insert(i, Position::NONE);
});
// Recalculate the hash based on the new function name and new arguments // Recalculate the hash based on the new function name and new arguments
hash = FnHash::from_script_and_native( hash = FnHash::from_script_and_native(
calc_fn_hash(empty(), fn_name, call_args.len()), calc_fn_hash(empty(), fn_name, call_args.len()),
@ -1065,7 +1104,6 @@ impl Engine {
FnHash::from_native(calc_fn_hash(empty(), name, args_len)) FnHash::from_native(calc_fn_hash(empty(), name, args_len))
}; };
} }
// Handle Fn() // Handle Fn()
KEYWORD_FN_PTR if args_expr.len() == 1 => { KEYWORD_FN_PTR if args_expr.len() == 1 => {
// Fn - only in function call style // Fn - only in function call style
@ -1361,9 +1399,13 @@ impl Engine {
match func { match func {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => { Some(f) if f.is_script() => {
let fn_def = f.get_fn_def();
if fn_def.body.is_empty() {
Ok(Dynamic::UNIT)
} else {
let args = args.as_mut(); let args = args.as_mut();
let new_scope = &mut Default::default(); let new_scope = &mut Default::default();
let fn_def = f.get_fn_def().clone();
let mut source = module.id_raw().cloned(); let mut source = module.id_raw().cloned();
mem::swap(&mut state.source, &mut source); mem::swap(&mut state.source, &mut source);
@ -1371,13 +1413,14 @@ impl Engine {
let level = level + 1; let level = level + 1;
let result = self.call_script_fn( let result = self.call_script_fn(
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level, new_scope, mods, state, lib, &mut None, fn_def, args, pos, level,
); );
state.source = source; state.source = source;
result result
} }
}
Some(f) if f.is_plugin_fn() => f Some(f) if f.is_plugin_fn() => f
.get_plugin_fn() .get_plugin_fn()

View File

@ -124,7 +124,7 @@ pub type FLOAT = f32;
pub use ast::{FnAccess, AST}; pub use ast::{FnAccess, AST};
pub use dynamic::Dynamic; pub use dynamic::Dynamic;
pub use engine::{Engine, EvalContext}; pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS};
pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_native::{FnPtr, NativeCallContext};
pub use fn_register::{RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterFn, RegisterResultFn};
pub use module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};
@ -192,7 +192,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use ast::{ pub use ast::{
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment, ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment,
ReturnType, ScriptFnDef, Stmt, ReturnType, ScriptFnDef, Stmt, StmtBlock,
}; };
#[cfg(feature = "internals")] #[cfg(feature = "internals")]

View File

@ -39,6 +39,7 @@ pub enum FnNamespace {
} }
impl Default for FnNamespace { impl Default for FnNamespace {
#[inline(always)]
fn default() -> Self { fn default() -> Self {
Self::Internal Self::Internal
} }
@ -150,6 +151,7 @@ pub struct Module {
} }
impl Default for Module { impl Default for Module {
#[inline(always)]
fn default() -> Self { fn default() -> Self {
Self { Self {
id: None, id: None,
@ -226,6 +228,7 @@ impl AsRef<Module> for Module {
impl<M: AsRef<Module>> Add<M> for &Module { impl<M: AsRef<Module>> Add<M> for &Module {
type Output = Module; type Output = Module;
#[inline(always)]
fn add(self, rhs: M) -> Self::Output { fn add(self, rhs: M) -> Self::Output {
let mut module = self.clone(); let mut module = self.clone();
module.merge(rhs.as_ref()); module.merge(rhs.as_ref());
@ -236,6 +239,7 @@ impl<M: AsRef<Module>> Add<M> for &Module {
impl<M: AsRef<Module>> Add<M> for Module { impl<M: AsRef<Module>> Add<M> for Module {
type Output = Self; type Output = Self;
#[inline(always)]
fn add(mut self, rhs: M) -> Self::Output { fn add(mut self, rhs: M) -> Self::Output {
self.merge(rhs.as_ref()); self.merge(rhs.as_ref());
self self
@ -243,6 +247,7 @@ impl<M: AsRef<Module>> Add<M> for Module {
} }
impl<M: Into<Module>> AddAssign<M> for Module { impl<M: Into<Module>> AddAssign<M> for Module {
#[inline(always)]
fn add_assign(&mut self, rhs: M) { fn add_assign(&mut self, rhs: M) {
self.combine(rhs.into()); self.combine(rhs.into());
} }
@ -1953,16 +1958,19 @@ impl Module {
} }
/// Does a type iterator exist in the entire module tree? /// Does a type iterator exist in the entire module tree?
#[inline(always)]
pub fn contains_qualified_iter(&self, id: TypeId) -> bool { pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
self.all_type_iterators.contains_key(&id) self.all_type_iterators.contains_key(&id)
} }
/// Does a type iterator exist in the module? /// Does a type iterator exist in the module?
#[inline(always)]
pub fn contains_iter(&self, id: TypeId) -> bool { pub fn contains_iter(&self, id: TypeId) -> bool {
self.type_iterators.contains_key(&id) self.type_iterators.contains_key(&id)
} }
/// Set a type iterator into the [`Module`]. /// Set a type iterator into the [`Module`].
#[inline(always)]
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) -> &mut Self { pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) -> &mut Self {
self.type_iterators.insert(typ, func); self.type_iterators.insert(typ, func);
self.indexed = false; self.indexed = false;
@ -1971,6 +1979,7 @@ impl Module {
} }
/// Set a type iterator into the [`Module`]. /// Set a type iterator into the [`Module`].
#[inline(always)]
pub fn set_iterable<T>(&mut self) -> &mut Self pub fn set_iterable<T>(&mut self) -> &mut Self
where where
T: Variant + Clone + IntoIterator, T: Variant + Clone + IntoIterator,
@ -1982,6 +1991,7 @@ impl Module {
} }
/// Set an iterator type into the [`Module`] as a type iterator. /// Set an iterator type into the [`Module`] as a type iterator.
#[inline(always)]
pub fn set_iterator<T>(&mut self) -> &mut Self pub fn set_iterator<T>(&mut self) -> &mut Self
where where
T: Variant + Clone + Iterator, T: Variant + Clone + Iterator,
@ -1993,11 +2003,13 @@ impl Module {
} }
/// Get the specified type iterator. /// Get the specified type iterator.
#[inline(always)]
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> { pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.all_type_iterators.get(&id).cloned() self.all_type_iterators.get(&id).cloned()
} }
/// Get the specified type iterator. /// Get the specified type iterator.
#[inline(always)]
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.type_iterators.get(&id).cloned() self.type_iterators.get(&id).cloned()
} }
@ -2021,32 +2033,41 @@ pub struct NamespaceRef {
} }
impl fmt::Debug for NamespaceRef { impl fmt::Debug for NamespaceRef {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.path, f)?;
if let Some(index) = self.index { if let Some(index) = self.index {
write!(f, " -> {}", index) write!(f, "{} -> ", index)?;
} else {
Ok(())
} }
f.write_str(
&self
.path
.iter()
.map(|Ident { name, .. }| name.as_str())
.collect::<Vec<_>>()
.join("::"),
)
} }
} }
impl Deref for NamespaceRef { impl Deref for NamespaceRef {
type Target = StaticVec<Ident>; type Target = StaticVec<Ident>;
#[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.path &self.path
} }
} }
impl DerefMut for NamespaceRef { impl DerefMut for NamespaceRef {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.path &mut self.path
} }
} }
impl fmt::Display for NamespaceRef { impl fmt::Display for NamespaceRef {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for Ident { name, .. } in self.path.iter() { for Ident { name, .. } in self.path.iter() {
write!(f, "{}{}", name, Token::DoubleColon.syntax())?; write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
@ -2056,6 +2077,7 @@ impl fmt::Display for NamespaceRef {
} }
impl From<StaticVec<Ident>> for NamespaceRef { impl From<StaticVec<Ident>> for NamespaceRef {
#[inline(always)]
fn from(path: StaticVec<Ident>) -> Self { fn from(path: StaticVec<Ident>) -> Self {
Self { index: None, path } Self { index: None, path }
} }
@ -2063,11 +2085,13 @@ impl From<StaticVec<Ident>> for NamespaceRef {
impl NamespaceRef { impl NamespaceRef {
/// Get the [`Scope`][crate::Scope] index offset. /// Get the [`Scope`][crate::Scope] index offset.
#[inline(always)]
pub(crate) fn index(&self) -> Option<NonZeroUsize> { pub(crate) fn index(&self) -> Option<NonZeroUsize> {
self.index self.index
} }
/// Set the [`Scope`][crate::Scope] index offset. /// Set the [`Scope`][crate::Scope] index offset.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)]
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) { pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.index = index self.index = index
} }

View File

@ -41,6 +41,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
pub struct FileModuleResolver { pub struct FileModuleResolver {
base_path: PathBuf, base_path: PathBuf,
extension: String, extension: String,
cache_enabled: bool,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>, cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>,
@ -101,6 +102,7 @@ impl FileModuleResolver {
Self { Self {
base_path: path.into(), base_path: path.into(),
extension: extension.into(), extension: extension.into(),
cache_enabled: true,
cache: Default::default(), cache: Default::default(),
} }
} }
@ -152,9 +154,25 @@ impl FileModuleResolver {
self self
} }
/// Enable/disable the cache.
#[inline(always)]
pub fn enable_cache(&mut self, enable: bool) -> &mut Self {
self.cache_enabled = enable;
self
}
/// Is the cache enabled?
#[inline(always)]
pub fn is_cache_enabled(&self) -> bool {
self.cache_enabled
}
/// Is a particular path cached? /// Is a particular path cached?
#[inline(always)] #[inline(always)]
pub fn is_cached(&self, path: &str) -> bool { pub fn is_cached(&self, path: &str) -> bool {
if !self.cache_enabled {
return false;
}
let file_path = self.get_file_path(path); let file_path = self.get_file_path(path);
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -211,7 +229,7 @@ impl ModuleResolver for FileModuleResolver {
let file_path = self.get_file_path(path); let file_path = self.get_file_path(path);
// See if it is cached // See if it is cached
{ if self.is_cache_enabled() {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let c = self.cache.borrow(); let c = self.cache.borrow();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
@ -242,10 +260,12 @@ impl ModuleResolver for FileModuleResolver {
.into(); .into();
// Put it into the cache // Put it into the cache
if self.is_cache_enabled() {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
self.cache.borrow_mut().insert(file_path, m.clone()); self.cache.borrow_mut().insert(file_path, m.clone());
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
self.cache.write().unwrap().insert(file_path, m.clone()); self.cache.write().unwrap().insert(file_path, m.clone());
}
Ok(m) Ok(m)
} }

View File

@ -1,6 +1,6 @@
//! Module implementing the [`AST`] optimizer. //! Module implementing the [`AST`] optimizer.
use crate::ast::{Expr, Stmt}; use crate::ast::{Expr, Ident, Stmt, StmtBlock};
use crate::dynamic::AccessMode; use crate::dynamic::AccessMode;
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::fn_builtin::get_builtin_binary_op_fn; use crate::fn_builtin::get_builtin_binary_op_fn;
@ -15,7 +15,6 @@ use crate::stdlib::{
vec, vec,
vec::Vec, vec::Vec,
}; };
use crate::token::is_valid_identifier;
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope,
@ -75,14 +74,18 @@ struct State<'a> {
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new State. /// Create a new State.
#[inline(always)] #[inline(always)]
pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self { pub fn new(
engine: &'a Engine,
lib: &'a [&'a Module],
optimization_level: OptimizationLevel,
) -> Self {
Self { Self {
changed: false, changed: false,
variables: vec![], variables: vec![],
propagate_constants: true, propagate_constants: true,
engine, engine,
lib, lib,
optimization_level: level, optimization_level,
} }
} }
/// Reset the state from dirty to clean. /// Reset the state from dirty to clean.
@ -95,6 +98,11 @@ impl<'a> State<'a> {
pub fn set_dirty(&mut self) { pub fn set_dirty(&mut self) {
self.changed = true; self.changed = true;
} }
/// Set the [`AST`] state to be not dirty (i.e. unchanged).
#[inline(always)]
pub fn clear_dirty(&mut self) {
self.changed = false;
}
/// Is the [`AST`] dirty (i.e. changed)? /// Is the [`AST`] dirty (i.e. changed)?
#[inline(always)] #[inline(always)]
pub fn is_dirty(&self) -> bool { pub fn is_dirty(&self) -> bool {
@ -169,139 +177,112 @@ fn call_fn_with_constant_arguments(
/// Optimize a block of [statements][Stmt]. /// Optimize a block of [statements][Stmt].
fn optimize_stmt_block( fn optimize_stmt_block(
mut statements: Vec<Stmt>, mut statements: Vec<Stmt>,
pos: Position,
state: &mut State, state: &mut State,
preserve_result: bool, preserve_result: bool,
count_promote_as_dirty: bool, ) -> Vec<Stmt> {
) -> Stmt { if statements.is_empty() {
let orig_len = statements.len(); // Original number of statements in the block, for change detection return statements;
}
let mut is_dirty = state.is_dirty();
loop {
state.clear_dirty();
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
let orig_propagate_constants = state.propagate_constants; let orig_propagate_constants = state.propagate_constants;
// Remove everything following control flow breaking statements
let mut dead_code = false;
statements.retain(|stmt| {
if dead_code {
state.set_dirty();
false
} else if stmt.is_control_flow_break() {
dead_code = true;
true
} else {
true
}
});
// Optimize each statement in the block // Optimize each statement in the block
statements.iter_mut().for_each(|stmt| { statements.iter_mut().for_each(|stmt| {
match stmt { match stmt {
// Add constant literals into the state // Add constant literals into the state
Stmt::Const(var_def, Some(value_expr), _, _) => { Stmt::Const(value_expr, Ident { name, .. }, _, _) => {
optimize_expr(value_expr, state); optimize_expr(value_expr, state);
if value_expr.is_constant() { if value_expr.is_constant() {
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone()); state.push_var(name, AccessMode::ReadOnly, value_expr.clone());
} }
} }
Stmt::Const(var_def, None, _, _) => {
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
}
// Add variables into the state // Add variables into the state
Stmt::Let(var_def, expr, _, _) => { Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => {
if let Some(value_expr) = expr {
optimize_expr(value_expr, state); optimize_expr(value_expr, state);
} state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos));
state.push_var(
&var_def.name,
AccessMode::ReadWrite,
Expr::Unit(var_def.pos),
);
} }
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
} }
}); });
// Remove all raw expression statements that are pure except for the very last statement // Remove all pure statements that do not return values at the end of a block.
let last_stmt = if preserve_result { // We cannot remove anything for non-pure statements due to potential side-effects.
statements.pop()
} else {
None
};
statements.retain(|stmt| !stmt.is_pure());
if let Some(stmt) = last_stmt {
statements.push(stmt);
}
// Remove all let/import statements at the end of a block - the new variables will go away anyway.
// But be careful only remove ones that have no initial values or have values that are pure expressions,
// otherwise there may be side effects.
let mut removed = false;
while let Some(expr) = statements.pop() {
match expr {
Stmt::Let(_, expr, _, _) | Stmt::Const(_, expr, _, _) => {
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
}
#[cfg(not(feature = "no_module"))]
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
_ => {
statements.push(expr);
break;
}
}
}
if preserve_result { if preserve_result {
if removed { loop {
statements.push(Stmt::Noop(pos)) match &statements[..] {
} [stmt] if !stmt.returns_value() && stmt.is_internally_pure() => {
// Optimize all the statements again
let num_statements = statements.len();
statements
.iter_mut()
.enumerate()
.for_each(|(i, stmt)| optimize_stmt(stmt, state, i == num_statements));
}
// Remove everything following the the first return/throw
let mut dead_code = false;
statements.retain(|stmt| {
if dead_code {
return false;
}
match stmt {
Stmt::Return(_, _, _) | Stmt::Break(_) => dead_code = true,
_ => (),
}
true
});
// Change detection
if orig_len != statements.len() {
state.set_dirty(); state.set_dirty();
statements.clear();
}
[.., second_last_stmt, Stmt::Noop(_)] if second_last_stmt.returns_value() => {}
[.., second_last_stmt, last_stmt]
if !last_stmt.returns_value() && last_stmt.is_internally_pure() =>
{
state.set_dirty();
if second_last_stmt.returns_value() {
*statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
} else {
statements.pop().unwrap();
}
}
_ => break,
}
}
} else {
loop {
match &statements[..] {
[stmt] if stmt.is_internally_pure() => {
state.set_dirty();
statements.clear();
}
[.., last_stmt] if last_stmt.is_internally_pure() => {
state.set_dirty();
statements.pop().unwrap();
}
_ => break,
}
}
} }
// Pop the stack and remove all the local constants // Pop the stack and remove all the local constants
state.restore_var(orig_constants_len); state.restore_var(orig_constants_len);
state.propagate_constants = orig_propagate_constants; state.propagate_constants = orig_propagate_constants;
match &statements[..] { if !state.is_dirty() {
// No statements in block - change to No-op break;
[] => {
state.set_dirty();
Stmt::Noop(pos)
} }
// Only one let statement - leave it alone
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos), is_dirty = true;
// Only one const statement - leave it alone }
[x] if matches!(x, Stmt::Const(_, _, _, _)) => Stmt::Block(statements, pos),
// Only one import statement - leave it alone if is_dirty {
#[cfg(not(feature = "no_module"))]
[x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos),
// Only one statement - promote
[_] => {
if count_promote_as_dirty {
state.set_dirty(); state.set_dirty();
} }
statements.remove(0)
} statements
_ => Stmt::Block(statements, pos),
}
} }
/// Optimize a [statement][Stmt]. /// Optimize a [statement][Stmt].
@ -316,18 +297,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
} }
}, },
// if false { if_block } -> Noop // if expr {}
Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_none() => { Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
state.set_dirty();
*stmt = Stmt::Noop(*pos);
}
// if true { if_block } -> if_block
Stmt::If(Expr::BoolConstant(true, _), x, _) if x.1.is_none() => {
*stmt = mem::take(&mut x.0);
optimize_stmt(stmt, state, true);
}
// if expr { Noop }
Stmt::If(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
state.set_dirty(); state.set_dirty();
let pos = condition.position(); let pos = condition.position();
@ -336,38 +307,56 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
*stmt = if preserve_result { *stmt = if preserve_result {
// -> { expr, Noop } // -> { expr, Noop }
Stmt::Block(vec![Stmt::Expr(expr), mem::take(&mut x.0)], pos) Stmt::Block(vec![Stmt::Expr(expr), Stmt::Noop(pos)], pos)
} else { } else {
// -> expr // -> expr
Stmt::Expr(expr) Stmt::Expr(expr)
}; };
} }
// if expr { if_block } // if false { if_block } -> Noop
Stmt::If(condition, x, _) if x.1.is_none() => { Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_empty() => {
optimize_expr(condition, state); state.set_dirty();
optimize_stmt(&mut x.0, state, true); *stmt = Stmt::Noop(*pos);
} }
// if false { if_block } else { else_block } -> else_block // if false { if_block } else { else_block } -> else_block
Stmt::If(Expr::BoolConstant(false, _), x, _) if x.1.is_some() => { Stmt::If(Expr::BoolConstant(false, _), x, _) => {
*stmt = mem::take(x.1.as_mut().unwrap()); state.set_dirty();
optimize_stmt(stmt, state, true); *stmt = match optimize_stmt_block(
mem::take(&mut x.1.statements).into_vec(),
state,
preserve_result,
) {
statements if statements.is_empty() => Stmt::Noop(x.1.pos),
statements => Stmt::Block(statements, x.1.pos),
}
} }
// if true { if_block } else { else_block } -> if_block // if true { if_block } else { else_block } -> if_block
Stmt::If(Expr::BoolConstant(true, _), x, _) => { Stmt::If(Expr::BoolConstant(true, _), x, _) => {
*stmt = mem::take(&mut x.0); state.set_dirty();
optimize_stmt(stmt, state, true); *stmt = match optimize_stmt_block(
mem::take(&mut x.0.statements).into_vec(),
state,
preserve_result,
) {
statements if statements.is_empty() => Stmt::Noop(x.0.pos),
statements => Stmt::Block(statements, x.0.pos),
}
} }
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::If(condition, x, _) => { Stmt::If(condition, x, _) => {
optimize_expr(condition, state); optimize_expr(condition, state);
optimize_stmt(&mut x.0, state, true); x.0.statements = optimize_stmt_block(
if let Some(else_block) = x.1.as_mut() { mem::take(&mut x.0.statements).into_vec(),
optimize_stmt(else_block, state, true); state,
match else_block { preserve_result,
Stmt::Noop(_) => x.1 = None, // Noop -> no else block )
_ => (), .into();
} x.1.statements = optimize_stmt_block(
} mem::take(&mut x.1.statements).into_vec(),
state,
preserve_result,
)
.into();
} }
// switch const { ... } // switch const { ... }
@ -381,50 +370,64 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
let table = &mut x.0; let table = &mut x.0;
if let Some(stmt) = table.get_mut(&hash) { let (statements, new_pos) = if let Some(block) = table.get_mut(&hash) {
optimize_stmt(stmt, state, true); (
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos); optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, true)
} else if let Some(def_stmt) = x.1.as_mut() { .into(),
optimize_stmt(def_stmt, state, true); block.pos,
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos); )
} else { } else {
*expr = Expr::Unit(*pos); (
} optimize_stmt_block(mem::take(&mut x.1.statements).into_vec(), state, true)
.into(),
if x.1.pos.is_none() { *pos } else { x.1.pos },
)
};
*expr = Expr::Stmt(Box::new(StmtBlock {
statements,
pos: new_pos,
}));
} }
// switch // switch
Stmt::Switch(expr, x, _) => { Stmt::Switch(expr, x, _) => {
optimize_expr(expr, state); optimize_expr(expr, state);
x.0.values_mut() x.0.values_mut().for_each(|block| {
.for_each(|stmt| optimize_stmt(stmt, state, true)); block.statements = optimize_stmt_block(
if let Some(def_stmt) = x.1.as_mut() { mem::take(&mut block.statements).into_vec(),
optimize_stmt(def_stmt, state, true); state,
preserve_result,
match def_stmt { )
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.1 = None, .into()
_ => (), });
} x.1.statements = optimize_stmt_block(
} mem::take(&mut x.1.statements).into_vec(),
state,
preserve_result,
)
.into()
} }
// while false { block } -> Noop // while false { block } -> Noop
Stmt::While(Some(Expr::BoolConstant(false, pos)), _, _) => { Stmt::While(Expr::BoolConstant(false, pos), _, _) => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(*pos) *stmt = Stmt::Noop(*pos)
} }
// while expr { block } // while expr { block }
Stmt::While(condition, block, _) => { Stmt::While(condition, block, _) => {
optimize_stmt(block, state, false);
if let Some(condition) = condition {
optimize_expr(condition, state); optimize_expr(condition, state);
}
match **block { block.statements =
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false)
.into();
if block.len() == 1 {
match block.statements[0] {
// while expr { break; } -> { expr; } // while expr { break; } -> { expr; }
Stmt::Break(pos) => { Stmt::Break(pos) => {
// Only a single break statement - turn into running the guard expression once // Only a single break statement - turn into running the guard expression once
state.set_dirty(); state.set_dirty();
if let Some(condition) = condition { if !condition.is_unit() {
let mut statements = vec![Stmt::Expr(mem::take(condition))]; let mut statements = vec![Stmt::Expr(mem::take(condition))];
if preserve_result { if preserve_result {
statements.push(Stmt::Noop(pos)) statements.push(Stmt::Noop(pos))
@ -437,61 +440,74 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
_ => (), _ => (),
} }
} }
}
// do { block } while false | do { block } until true -> { block } // do { block } while false | do { block } until true -> { block }
Stmt::Do(block, Expr::BoolConstant(true, _), false, _) Stmt::Do(block, Expr::BoolConstant(true, _), false, _)
| Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => { | Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => {
state.set_dirty(); state.set_dirty();
optimize_stmt(block.as_mut(), state, false); *stmt = Stmt::Block(
*stmt = mem::take(block.as_mut()); optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false),
block.pos,
);
} }
// do { block } while|until expr // do { block } while|until expr
Stmt::Do(block, condition, _, _) => { Stmt::Do(block, condition, _, _) => {
optimize_stmt(block.as_mut(), state, false);
optimize_expr(condition, state); optimize_expr(condition, state);
block.statements =
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false)
.into();
} }
// for id in expr { block } // for id in expr { block }
Stmt::For(iterable, x, _) => { Stmt::For(iterable, x, _) => {
optimize_expr(iterable, state); optimize_expr(iterable, state);
optimize_stmt(&mut x.1, state, false); x.1.statements =
optimize_stmt_block(mem::take(&mut x.1.statements).into_vec(), state, false).into();
} }
// let id = expr; // let id = expr;
Stmt::Let(_, Some(expr), _, _) => optimize_expr(expr, state), Stmt::Let(expr, _, _, _) => optimize_expr(expr, state),
// let id;
Stmt::Let(_, None, _, _) => (),
// import expr as var; // import expr as var;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(expr, _, _) => optimize_expr(expr, state), Stmt::Import(expr, _, _) => optimize_expr(expr, state),
// { block } // { block }
Stmt::Block(statements, pos) => { Stmt::Block(statements, pos) => {
*stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true); *stmt = match optimize_stmt_block(mem::take(statements), state, preserve_result) {
statements if statements.is_empty() => {
state.set_dirty();
Stmt::Noop(*pos)
} }
// try { block } catch ( var ) { block } // Only one statement - promote
Stmt::TryCatch(x, _, _) if x.0.is_pure() => { mut statements if statements.len() == 1 => {
state.set_dirty();
statements.pop().unwrap()
}
statements => Stmt::Block(statements, *pos),
};
}
// try { pure block } catch ( var ) { block }
Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => {
// If try block is pure, there will never be any exceptions // If try block is pure, there will never be any exceptions
state.set_dirty(); state.set_dirty();
let pos = x.0.position(); *stmt = Stmt::Block(
optimize_stmt(&mut x.0, state, preserve_result); optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false),
let mut statements = match mem::take(&mut x.0) { x.0.pos,
Stmt::Block(statements, _) => statements, );
stmt => vec![stmt],
};
statements.push(Stmt::Noop(pos));
*stmt = Stmt::Block(statements, pos);
} }
// try { block } catch ( var ) { block } // try { block } catch ( var ) { block }
Stmt::TryCatch(x, _, _) => { Stmt::TryCatch(x, _, _) => {
optimize_stmt(&mut x.0, state, false); x.0.statements =
optimize_stmt(&mut x.2, state, false); optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false).into();
x.2.statements =
optimize_stmt_block(mem::take(&mut x.2.statements).into_vec(), state, false).into();
} }
// {} // {}
Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => { Stmt::Expr(Expr::Stmt(x)) if x.statements.is_empty() => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(*pos); *stmt = Stmt::Noop(x.pos);
} }
// {...}; // {...};
Stmt::Expr(Expr::Stmt(x, pos)) => { Stmt::Expr(Expr::Stmt(x)) => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Block(mem::take(x).into_vec(), *pos); *stmt = Stmt::Block(mem::take(&mut x.statements).into_vec(), x.pos);
} }
// expr; // expr;
Stmt::Expr(expr) => optimize_expr(expr, state), Stmt::Expr(expr) => optimize_expr(expr, state),
@ -514,25 +530,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
match expr { match expr {
// {} // {}
Expr::Stmt(x, pos) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(*pos) } Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) }
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x, pos) => match optimize_stmt_block(mem::take(x).into_vec(), *pos, state, true, false) { Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true).into(),
// {}
Stmt::Noop(_) => { state.set_dirty(); *expr = Expr::Unit(*pos); }
// { stmt, .. }
Stmt::Block(statements, _) => *x = Box::new(statements.into()),
// { expr }
Stmt::Expr(inner) => { state.set_dirty(); *expr = inner; }
// { stmt }
stmt => x.push(stmt),
}
// lhs.rhs // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
// map.string // map.string
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => { (Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
let prop = &p.4.name; let prop = &p.2.name;
// Map literal where everything is pure - promote the indexed item. // Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
@ -598,32 +604,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// #{ key:value, .. } // #{ key:value, .. }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)), Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)),
// lhs in rhs
Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) {
// "xxx" in "xxxxx"
(Expr::StringConstant(a, pos), Expr::StringConstant(b, _)) => {
state.set_dirty();
*expr = Expr::BoolConstant( b.contains(a.as_str()), *pos);
}
// 'x' in "xxxxx"
(Expr::CharConstant(a, pos), Expr::StringConstant(b, _)) => {
state.set_dirty();
*expr = Expr::BoolConstant(b.contains(*a), *pos);
}
// "xxx" in #{...}
(Expr::StringConstant(a, pos), Expr::Map(b, _)) => {
state.set_dirty();
*expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == *a).is_some(), *pos);
}
// 'x' in #{...}
(Expr::CharConstant(a, pos), Expr::Map(b, _)) => {
state.set_dirty();
let ch = a.to_string();
*expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == &ch).is_some(), *pos);
}
// lhs in rhs
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
},
// lhs && rhs // lhs && rhs
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) { Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
// true && rhs -> rhs // true && rhs -> rhs
@ -684,7 +664,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations && state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.args.len() == 2 // binary call && x.args.len() == 2 // binary call
&& x.args.iter().all(Expr::is_constant) // all arguments are constants && x.args.iter().all(Expr::is_constant) // all arguments are constants
&& !is_valid_identifier(x.name.chars()) // cannot be scripted //&& !is_valid_identifier(x.name.chars()) // cannot be scripted
=> { => {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
@ -783,16 +763,16 @@ fn optimize_top_level(
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
lib: &[&Module], lib: &[&Module],
level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Vec<Stmt> { ) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
if level == OptimizationLevel::None { if optimization_level == OptimizationLevel::None {
statements.shrink_to_fit(); statements.shrink_to_fit();
return statements; return statements;
} }
// Set up the state // Set up the state
let mut state = State::new(engine, lib, level); let mut state = State::new(engine, lib, optimization_level);
// Add constants and variables from the scope // Add constants and variables from the scope
scope.iter().for_each(|(name, constant, value)| { scope.iter().for_each(|(name, constant, value)| {
@ -816,34 +796,17 @@ fn optimize_top_level(
statements.iter_mut().enumerate().for_each(|(i, stmt)| { statements.iter_mut().enumerate().for_each(|(i, stmt)| {
match stmt { match stmt {
Stmt::Const(var_def, expr, _, _) if expr.is_some() => { Stmt::Const(value_expr, Ident { name, .. }, _, _) => {
// Load constants // Load constants
let value_expr = expr.as_mut().unwrap();
optimize_expr(value_expr, &mut state); optimize_expr(value_expr, &mut state);
if value_expr.is_constant() { if value_expr.is_constant() {
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone()); state.push_var(name, AccessMode::ReadOnly, value_expr.clone());
}
// Keep it in the global scope
if value_expr.is_unit() {
state.set_dirty();
*expr = None;
} }
} }
Stmt::Const(var_def, None, _, _) => { Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => {
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
}
Stmt::Let(var_def, expr, _, _) => {
if let Some(value_expr) = expr {
optimize_expr(value_expr, &mut state); optimize_expr(value_expr, &mut state);
} state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos));
state.push_var(
&var_def.name,
AccessMode::ReadWrite,
Expr::Unit(var_def.pos),
);
} }
_ => { _ => {
// Keep all variable declarations at this level // Keep all variable declarations at this level
@ -852,7 +815,7 @@ fn optimize_top_level(
Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true, Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(_, _, _) => true, Stmt::Import(_, _, _) => true,
_ => i == num_statements - 1, _ => i >= num_statements - 1,
}; };
optimize_stmt(stmt, &mut state, keep); optimize_stmt(stmt, &mut state, keep);
} }
@ -887,12 +850,12 @@ pub fn optimize_into_ast(
scope: &Scope, scope: &Scope,
mut statements: Vec<Stmt>, mut statements: Vec<Stmt>,
_functions: Vec<crate::ast::ScriptFnDef>, _functions: Vec<crate::ast::ScriptFnDef>,
level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
let level = if cfg!(feature = "no_optimize") { let level = if cfg!(feature = "no_optimize") {
OptimizationLevel::None OptimizationLevel::None
} else { } else {
level optimization_level
}; };
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -921,33 +884,49 @@ pub fn optimize_into_ast(
lib2.set_script_fn(fn_def); lib2.set_script_fn(fn_def);
}); });
let lib2 = &[&lib2];
_functions _functions
.into_iter() .into_iter()
.map(|mut fn_def| { .map(|mut fn_def| {
let pos = fn_def.body.position(); let pos = fn_def.body.pos;
let mut body = fn_def.body.statements.into_vec();
loop {
// Optimize the function body // Optimize the function body
let mut body = optimize_top_level( let state = &mut State::new(engine, lib2, level);
vec![fn_def.body],
engine,
&Scope::new(),
&[&lib2],
level,
);
// {} -> Noop body = optimize_stmt_block(body, state, true);
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val match &mut body[..] {
Stmt::Return((crate::ast::ReturnType::Return, _), Some(expr), _) => { // { return; } -> {}
Stmt::Expr(expr) [Stmt::Return(crate::ast::ReturnType::Return, None, _)] => {
body.clear();
} }
// { return; } -> () // { ...; return; } -> { ... }
Stmt::Return((crate::ast::ReturnType::Return, pos), None, _) => { [.., last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)]
Stmt::Expr(Expr::Unit(pos)) if !last_stmt.returns_value() =>
{
body.pop().unwrap();
} }
// All others // { ...; return val; } -> { ...; val }
stmt => stmt, [.., Stmt::Return(crate::ast::ReturnType::Return, expr, pos)] => {
*body.last_mut().unwrap() = if let Some(expr) = expr {
Stmt::Expr(mem::take(expr))
} else {
Stmt::Noop(*pos)
}; };
}
_ => break,
}
}
fn_def.body = StmtBlock {
statements: body.into(),
pos,
};
fn_def fn_def
}) })
.for_each(|fn_def| { .for_each(|fn_def| {

View File

@ -164,7 +164,7 @@ mod array_functions {
result result
} }
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn map( pub fn map(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -197,7 +197,7 @@ mod array_functions {
Ok(ar.into()) Ok(ar.into())
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn filter( pub fn filter(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -233,8 +233,70 @@ mod array_functions {
Ok(ar.into()) Ok(ar.into())
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn contains(
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
for item in array.iter_mut() {
if ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) =>
{
if item.type_id() == value.type_id() {
// No default when comparing same type
Err(err)
} else {
Ok(Dynamic::FALSE)
}
}
_ => Err(err),
})?
.as_bool()
.unwrap_or(false)
{
return Ok(Dynamic::TRUE);
}
}
Ok(Dynamic::FALSE)
}
#[rhai_fn(return_raw, pure)]
pub fn index_of( pub fn index_of(
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
for (i, item) in array.iter_mut().enumerate() {
if ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) =>
{
if item.type_id() == value.type_id() {
// No default when comparing same type
Err(err)
} else {
Ok(Dynamic::FALSE)
}
}
_ => Err(err),
})?
.as_bool()
.unwrap_or(false)
{
return Ok((i as INT).into());
}
}
Ok((-1 as INT).into())
}
#[rhai_fn(name = "index_of", return_raw, pure)]
pub fn index_of_filter(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
filter: FnPtr, filter: FnPtr,
@ -267,7 +329,7 @@ mod array_functions {
Ok((-1 as INT).into()) Ok((-1 as INT).into())
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn some( pub fn some(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -301,7 +363,7 @@ mod array_functions {
Ok(false.into()) Ok(false.into())
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn all( pub fn all(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -335,7 +397,7 @@ mod array_functions {
Ok(true.into()) Ok(true.into())
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn reduce( pub fn reduce(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -366,7 +428,7 @@ mod array_functions {
Ok(result) Ok(result)
} }
#[rhai_fn(name = "reduce", return_raw)] #[rhai_fn(name = "reduce", return_raw, pure)]
pub fn reduce_with_initial( pub fn reduce_with_initial(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -405,7 +467,7 @@ mod array_functions {
Ok(result) Ok(result)
} }
#[rhai_fn(return_raw)] #[rhai_fn(return_raw, pure)]
pub fn reduce_rev( pub fn reduce_rev(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -436,7 +498,7 @@ mod array_functions {
Ok(result) Ok(result)
} }
#[rhai_fn(name = "reduce_rev", return_raw)] #[rhai_fn(name = "reduce_rev", return_raw, pure)]
pub fn reduce_rev_with_initial( pub fn reduce_rev_with_initial(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -634,7 +696,7 @@ mod array_functions {
drained drained
} }
#[rhai_fn(name = "==", return_raw)] #[rhai_fn(name = "==", return_raw, pure)]
pub fn equals( pub fn equals(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
@ -648,18 +710,31 @@ mod array_functions {
} }
for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) { for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) {
let equals = ctx if !ctx
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [a1, a2]) .call_fn_dynamic_raw(OP_EQUALS, true, &mut [a1, a2])
.map(|v| v.as_bool().unwrap_or(false))?; .or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if !equals { if fn_sig.starts_with(OP_EQUALS) =>
return Ok(false.into()); {
if a1.type_id() == a2.type_id() {
// No default when comparing same type
Err(err)
} else {
Ok(Dynamic::FALSE)
}
}
_ => Err(err),
})?
.as_bool()
.unwrap_or(false)
{
return Ok(Dynamic::FALSE);
} }
} }
Ok(true.into()) Ok(Dynamic::TRUE)
} }
#[rhai_fn(name = "!=", return_raw)] #[rhai_fn(name = "!=", return_raw, pure)]
pub fn not_equals( pub fn not_equals(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,

View File

@ -13,8 +13,8 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
#[export_module] #[export_module]
mod map_functions { mod map_functions {
#[rhai_fn(pure)] #[rhai_fn(name = "has", pure)]
pub fn has(map: &mut Map, prop: ImmutableString) -> bool { pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
map.contains_key(&prop) map.contains_key(&prop)
} }
#[rhai_fn(pure)] #[rhai_fn(pure)]

View File

@ -74,14 +74,6 @@ mod string_functions {
} }
} }
#[rhai_fn(name = "contains")]
pub fn contains_char(string: &str, character: char) -> bool {
string.contains(character)
}
pub fn contains(string: &str, find_string: &str) -> bool {
string.contains(find_string)
}
#[rhai_fn(name = "index_of")] #[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
let start = if start < 0 { let start = if start < 0 {

View File

@ -38,6 +38,7 @@ pub enum LexError {
impl Error for LexError {} impl Error for LexError {}
impl fmt::Display for LexError { impl fmt::Display for LexError {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s), Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
@ -293,7 +294,7 @@ pub struct ParseError(pub Box<ParseErrorType>, pub Position);
impl Error for ParseError {} impl Error for ParseError {}
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
#[inline] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)?; fmt::Display::fmt(&self.0, f)?;

View File

@ -2,10 +2,10 @@
use crate::ast::{ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef,
Stmt, Stmt, StmtBlock,
}; };
use crate::dynamic::{AccessMode, Union}; use crate::dynamic::{AccessMode, Union};
use crate::engine::KEYWORD_THIS; use crate::engine::{KEYWORD_THIS, OP_CONTAINS};
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::optimize_into_ast; use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
@ -243,7 +243,11 @@ impl Expr {
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
let hash_set = calc_fn_hash(empty(), &setter, 2); let hash_set = calc_fn_hash(empty(), &setter, 2);
Self::Property(Box::new((getter, hash_get, setter, hash_set, ident.into()))) Self::Property(Box::new((
(getter, hash_get),
(setter, hash_set),
ident.into(),
)))
} }
_ => self, _ => self,
} }
@ -480,7 +484,6 @@ fn parse_index_chain(
Expr::CharConstant(_, _) Expr::CharConstant(_, _)
| Expr::And(_, _) | Expr::And(_, _)
| Expr::Or(_, _) | Expr::Or(_, _)
| Expr::In(_, _)
| Expr::BoolConstant(_, _) | Expr::BoolConstant(_, _)
| Expr::Unit(_) => { | Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
@ -514,7 +517,6 @@ fn parse_index_chain(
Expr::CharConstant(_, _) Expr::CharConstant(_, _)
| Expr::And(_, _) | Expr::And(_, _)
| Expr::Or(_, _) | Expr::Or(_, _)
| Expr::In(_, _)
| Expr::BoolConstant(_, _) | Expr::BoolConstant(_, _)
| Expr::Unit(_) => { | Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
@ -548,8 +550,8 @@ fn parse_index_chain(
) )
.into_err(x.position())) .into_err(x.position()))
} }
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] // lhs[??? && ???], lhs[??? || ???]
x @ Expr::And(_, _) | x @ Expr::Or(_, _) | x @ Expr::In(_, _) => { x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
@ -811,7 +813,7 @@ fn parse_switch(
} }
} }
let mut table = HashMap::new(); let mut table = HashMap::<u64, StmtBlock>::new();
let mut def_stmt = None; let mut def_stmt = None;
loop { loop {
@ -871,10 +873,10 @@ fn parse_switch(
let need_comma = !stmt.is_self_terminated(); let need_comma = !stmt.is_self_terminated();
def_stmt = if let Some(hash) = hash { def_stmt = if let Some(hash) = hash {
table.insert(hash, stmt); table.insert(hash, stmt.into());
None None
} else { } else {
Some(stmt) Some(stmt.into())
}; };
match input.peek().unwrap() { match input.peek().unwrap() {
@ -905,7 +907,10 @@ fn parse_switch(
Ok(Stmt::Switch( Ok(Stmt::Switch(
item, item,
Box::new((final_table.into(), def_stmt)), Box::new((
final_table.into(),
def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()),
)),
settings.pos, settings.pos,
)) ))
} }
@ -956,7 +961,7 @@ fn parse_primary(
// { - block statement as expression // { - block statement as expression
Token::LeftBrace if settings.allow_stmt_expr => { Token::LeftBrace if settings.allow_stmt_expr => {
match parse_block(input, state, lib, settings.level_up())? { match parse_block(input, state, lib, settings.level_up())? {
Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos), block @ Stmt::Block(_, _) => Expr::Stmt(Box::new(block.into())),
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
} }
} }
@ -964,15 +969,14 @@ fn parse_primary(
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?, Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
// If statement is allowed to act as expressions // If statement is allowed to act as expressions
Token::If if settings.allow_if_expr => Expr::Stmt( Token::If if settings.allow_if_expr => Expr::Stmt(Box::new(
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()), parse_if(input, state, lib, settings.level_up())?.into(),
settings.pos, )),
),
// Switch statement is allowed to act as expressions // Switch statement is allowed to act as expressions
Token::Switch if settings.allow_switch_expr => Expr::Stmt( Token::Switch if settings.allow_switch_expr => Expr::Stmt(Box::new(
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()), parse_switch(input, state, lib, settings.level_up())?.into(),
settings.pos, )),
),
// | ... // | ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.allow_anonymous_fn => { Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
@ -1149,14 +1153,14 @@ fn parse_primary(
.into_err(pos)); .into_err(pos));
} }
let (_, namespace, Ident { name, pos }) = *x; let (_, namespace, Ident { name, pos, .. }) = *x;
settings.pos = pos; settings.pos = pos;
let ns = namespace.map(|(_, ns)| ns); let ns = namespace.map(|(_, ns)| ns);
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())? parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
} }
// Function call // Function call
(Expr::Variable(x), Token::LeftParen) => { (Expr::Variable(x), Token::LeftParen) => {
let (_, namespace, Ident { name, pos }) = *x; let (_, namespace, Ident { name, pos, .. }) = *x;
settings.pos = pos; settings.pos = pos;
let ns = namespace.map(|(_, ns)| ns); let ns = namespace.map(|(_, ns)| ns);
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
@ -1397,7 +1401,7 @@ fn make_assignment_stmt<'a>(
} }
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(x) => { Expr::Variable(x) => {
let (index, _, Ident { name, pos }) = x.as_ref(); let (index, _, Ident { name, pos, .. }) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
AccessMode::ReadWrite => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
@ -1418,7 +1422,7 @@ fn make_assignment_stmt<'a>(
} }
// var[???] (indexed) = rhs, var.??? (indexed) = rhs // var[???] (indexed) = rhs, var.??? (indexed) = rhs
Expr::Variable(x) => { Expr::Variable(x) => {
let (index, _, Ident { name, pos }) = x.as_ref(); let (index, _, Ident { name, pos, .. }) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { match state.stack[(state.stack.len() - index.unwrap().get())].1 {
AccessMode::ReadWrite => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
@ -1508,7 +1512,7 @@ fn make_dot_expr(
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
let hash_set = calc_fn_hash(empty(), &setter, 2); let hash_set = calc_fn_hash(empty(), &setter, 2);
let rhs = Expr::Property(Box::new((getter, hash_get, setter, hash_set, ident))); let rhs = Expr::Property(Box::new(((getter, hash_get), (setter, hash_set), ident)));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
@ -1602,139 +1606,6 @@ fn make_dot_expr(
}) })
} }
/// Make an 'in' expression.
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
match (&lhs, &rhs) {
(_, x @ Expr::IntegerConstant(_, _))
| (_, x @ Expr::And(_, _))
| (_, x @ Expr::Or(_, _))
| (_, x @ Expr::In(_, _))
| (_, x @ Expr::BoolConstant(_, _))
| (_, x @ Expr::Unit(_)) => {
return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(),
)
.into_err(x.position()))
}
#[cfg(not(feature = "no_float"))]
(_, x @ Expr::FloatConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(),
)
.into_err(x.position()))
}
// "xxx" in "xxxx", 'x' in "xxxx" - OK!
(Expr::StringConstant(_, _), Expr::StringConstant(_, _))
| (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (),
// 123.456 in "xxxx"
#[cfg(not(feature = "no_float"))]
(x @ Expr::FloatConstant(_, _), Expr::StringConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a float".into(),
)
.into_err(x.position()))
}
// 123 in "xxxx"
(x @ Expr::IntegerConstant(_, _), Expr::StringConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a number".into(),
)
.into_err(x.position()))
}
// (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx",
// true in "xxxx", false in "xxxx"
(x @ Expr::And(_, _), Expr::StringConstant(_, _))
| (x @ Expr::Or(_, _), Expr::StringConstant(_, _))
| (x @ Expr::In(_, _), Expr::StringConstant(_, _))
| (x @ Expr::BoolConstant(_, _), Expr::StringConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a boolean".into(),
)
.into_err(x.position()))
}
// [???, ???, ???] in "xxxx"
(x @ Expr::Array(_, _), Expr::StringConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an array".into(),
)
.into_err(x.position()))
}
// #{...} in "xxxx"
(x @ Expr::Map(_, _), Expr::StringConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an object map".into(),
)
.into_err(x.position()))
}
// () in "xxxx"
(x @ Expr::Unit(_), Expr::StringConstant(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not ()".into(),
)
.into_err(x.position()))
}
// "xxx" in #{...}, 'x' in #{...} - OK!
(Expr::StringConstant(_, _), Expr::Map(_, _))
| (Expr::CharConstant(_, _), Expr::Map(_, _)) => (),
// 123.456 in #{...}
#[cfg(not(feature = "no_float"))]
(x @ Expr::FloatConstant(_, _), Expr::Map(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a float".into(),
)
.into_err(x.position()))
}
// 123 in #{...}
(x @ Expr::IntegerConstant(_, _), Expr::Map(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a number".into(),
)
.into_err(x.position()))
}
// (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...},
// true in #{...}, false in #{...}
(x @ Expr::And(_, _), Expr::Map(_, _))
| (x @ Expr::Or(_, _), Expr::Map(_, _))
| (x @ Expr::In(_, _), Expr::Map(_, _))
| (x @ Expr::BoolConstant(_, _), Expr::Map(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a boolean".into(),
)
.into_err(x.position()))
}
// [???, ???, ???] in #{..}
(x @ Expr::Array(_, _), Expr::Map(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an array".into(),
)
.into_err(x.position()))
}
// #{...} in #{..}
(x @ Expr::Map(_, _), Expr::Map(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an object map".into(),
)
.into_err(x.position()))
}
// () in #{...}
(x @ Expr::Unit(_), Expr::Map(_, _)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not ()".into(),
)
.into_err(x.position()))
}
_ => (),
}
Ok(Expr::In(Box::new(BinaryExpr { lhs, rhs }), op_pos))
}
/// Parse a binary expression. /// Parse a binary expression.
fn parse_binary_op( fn parse_binary_op(
input: &mut TokenStream, input: &mut TokenStream,
@ -1880,9 +1751,21 @@ fn parse_binary_op(
) )
} }
Token::In => { Token::In => {
let rhs = args.pop().unwrap(); // Swap the arguments
let current_lhs = args.pop().unwrap(); let current_lhs = args.remove(0);
make_in_expr(current_lhs, rhs, pos)? args.push(current_lhs);
// Convert into a call to `contains`
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2);
Expr::FnCall(
Box::new(FnCallExpr {
hash: FnHash::from_script(hash),
args,
name: OP_CONTAINS.into(),
..op_base
}),
pos,
)
} }
Token::Custom(s) Token::Custom(s)
@ -1984,8 +1867,8 @@ fn parse_custom_syntax(
tokens.push(keyword); tokens.push(keyword);
} }
MARKER_BLOCK => match parse_block(input, state, lib, settings)? { MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
Stmt::Block(statements, pos) => { block @ Stmt::Block(_, _) => {
keywords.push(Expr::Stmt(Box::new(statements.into()), pos)); keywords.push(Expr::Stmt(Box::new(block.into())));
let keyword = state.get_interned_string(MARKER_BLOCK); let keyword = state.get_interned_string(MARKER_BLOCK);
segments.push(keyword.clone()); segments.push(keyword.clone());
tokens.push(keyword); tokens.push(keyword);
@ -2118,20 +2001,20 @@ fn parse_if(
// if guard { if_body } else ... // if guard { if_body } else ...
let else_body = if match_token(input, Token::Else).0 { let else_body = if match_token(input, Token::Else).0 {
Some(if let (Token::If, _) = input.peek().unwrap() { if let (Token::If, _) = input.peek().unwrap() {
// if guard { if_body } else if ... // if guard { if_body } else if ...
parse_if(input, state, lib, settings.level_up())? parse_if(input, state, lib, settings.level_up())?
} else { } else {
// if guard { if_body } else { else-body } // if guard { if_body } else { else-body }
parse_block(input, state, lib, settings.level_up())? parse_block(input, state, lib, settings.level_up())?
}) }
} else { } else {
None Stmt::Noop(Position::NONE)
}; };
Ok(Stmt::If( Ok(Stmt::If(
guard, guard,
Box::new((if_body, else_body)), Box::new((if_body.into(), else_body.into())),
settings.pos, settings.pos,
)) ))
} }
@ -2151,18 +2034,18 @@ fn parse_while_loop(
(Token::While, pos) => { (Token::While, pos) => {
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
(Some(expr), pos) (expr, pos)
} }
(Token::Loop, pos) => (None, pos), (Token::Loop, pos) => (Expr::Unit(Position::NONE), pos),
_ => unreachable!(), _ => unreachable!(),
}; };
settings.pos = token_pos; settings.pos = token_pos;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
settings.is_breakable = true; settings.is_breakable = true;
let body = Box::new(parse_block(input, state, lib, settings.level_up())?); let body = parse_block(input, state, lib, settings.level_up())?;
Ok(Stmt::While(guard, body, settings.pos)) Ok(Stmt::While(guard, Box::new(body.into()), settings.pos))
} }
/// Parse a do loop. /// Parse a do loop.
@ -2180,7 +2063,7 @@ fn parse_do(
// do { body } [while|until] guard // do { body } [while|until] guard
settings.is_breakable = true; settings.is_breakable = true;
let body = Box::new(parse_block(input, state, lib, settings.level_up())?); let body = parse_block(input, state, lib, settings.level_up())?;
let is_while = match input.next().unwrap() { let is_while = match input.next().unwrap() {
(Token::While, _) => true, (Token::While, _) => true,
@ -2198,7 +2081,12 @@ fn parse_do(
let guard = parse_expr(input, state, lib, settings.level_up())?; let guard = parse_expr(input, state, lib, settings.level_up())?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
Ok(Stmt::Do(body, guard, is_while, settings.pos)) Ok(Stmt::Do(
Box::new(body.into()),
guard,
is_while,
settings.pos,
))
} }
/// Parse a for loop. /// Parse a for loop.
@ -2253,7 +2141,7 @@ fn parse_for(
state.stack.truncate(prev_stack_len); state.stack.truncate(prev_stack_len);
Ok(Stmt::For(expr, Box::new((name, body)), settings.pos)) Ok(Stmt::For(expr, Box::new((name, body.into())), settings.pos))
} }
/// Parse a variable definition statement. /// Parse a variable definition statement.
@ -2290,18 +2178,18 @@ fn parse_let(
// let name = ... // let name = ...
let expr = if match_token(input, Token::Equals).0 { let expr = if match_token(input, Token::Equals).0 {
// let name = expr // let name = expr
Some(parse_expr(input, state, lib, settings.level_up())?) parse_expr(input, state, lib, settings.level_up())?
} else { } else {
None Expr::Unit(Position::NONE)
}; };
state.stack.push((name, var_type)); state.stack.push((name, var_type));
match var_type { match var_type {
// let name = expr // let name = expr
AccessMode::ReadWrite => Ok(Stmt::Let(Box::new(var_def), expr, export, settings.pos)), AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def, export, settings.pos)),
// const name = { expr:constant } // const name = { expr:constant }
AccessMode::ReadOnly => Ok(Stmt::Const(Box::new(var_def), expr, export, settings.pos)), AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def, export, settings.pos)),
} }
} }
@ -2342,10 +2230,10 @@ fn parse_import(
Ok(Stmt::Import( Ok(Stmt::Import(
expr, expr,
Some(Box::new(Ident { Some(Ident {
name, name,
pos: name_pos, pos: name_pos,
})), }),
settings.pos, settings.pos,
)) ))
} }
@ -2542,7 +2430,7 @@ fn parse_stmt(
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
use AccessMode::{ReadOnly, ReadWrite}; use AccessMode::{ReadOnly, ReadWrite};
let mut _comments: Vec<String> = Default::default(); let mut _comments: StaticVec<String> = Default::default();
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ {
@ -2688,16 +2576,13 @@ fn parse_stmt(
match input.peek().unwrap() { match input.peek().unwrap() {
// `return`/`throw` at <EOF> // `return`/`throw` at <EOF>
(Token::EOF, pos) => Ok(Stmt::Return((return_type, token_pos), None, *pos)), (Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)),
// `return;` or `throw;` // `return;` or `throw;`
(Token::SemiColon, _) => { (Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)),
Ok(Stmt::Return((return_type, token_pos), None, settings.pos))
}
// `return` or `throw` with expression // `return` or `throw` with expression
(_, _) => { (_, _) => {
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
let pos = expr.position(); Ok(Stmt::Return(return_type, Some(expr), token_pos))
Ok(Stmt::Return((return_type, token_pos), Some(expr), pos))
} }
} }
} }
@ -2775,7 +2660,7 @@ fn parse_try_catch(
let catch_body = parse_block(input, state, lib, settings.level_up())?; let catch_body = parse_block(input, state, lib, settings.level_up())?;
Ok(Stmt::TryCatch( Ok(Stmt::TryCatch(
Box::new((body, var_def, catch_body)), Box::new((body.into(), var_def, catch_body.into())),
settings.pos, settings.pos,
catch_pos, catch_pos,
)) ))
@ -2789,7 +2674,7 @@ fn parse_fn(
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
access: FnAccess, access: FnAccess,
mut settings: ParseSettings, mut settings: ParseSettings,
comments: Vec<String>, comments: StaticVec<String>,
) -> Result<ScriptFnDef, ParseError> { ) -> Result<ScriptFnDef, ParseError> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2852,7 +2737,8 @@ fn parse_fn(
parse_block(input, state, lib, settings.level_up())? parse_block(input, state, lib, settings.level_up())?
} }
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
}; }
.into();
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
@ -2914,7 +2800,7 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<Ident>, pos: Po
let mut statements: StaticVec<_> = Default::default(); let mut statements: StaticVec<_> = Default::default();
statements.extend(externals.into_iter().map(Stmt::Share)); statements.extend(externals.into_iter().map(Stmt::Share));
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr));
Expr::Stmt(Box::new(statements), pos) Expr::Stmt(Box::new(StmtBlock { statements, pos }))
} }
/// Parse an anonymous function definition. /// Parse an anonymous function definition.
@ -3016,7 +2902,7 @@ fn parse_anon_fn(
params, params,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), externals: Default::default(),
body, body: body.into(),
lib: None, lib: None,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
mods: Default::default(), mods: Default::default(),

View File

@ -58,8 +58,6 @@ pub enum EvalAltResult {
/// Trying to index into a type that is not an array, an object map, or a string, and has no /// Trying to index into a type that is not an array, an object map, or a string, and has no
/// indexer function defined. Wrapped value is the type name. /// indexer function defined. Wrapped value is the type name.
ErrorIndexingType(String, Position), ErrorIndexingType(String, Position),
/// Invalid arguments for `in` operator.
ErrorInExpr(Position),
/// The `for` statement encounters a type that is not an iterator. /// The `for` statement encounters a type that is not an iterator.
ErrorFor(Position), ErrorFor(Position),
/// Data race detected when accessing a variable. Wrapped value is the variable name. /// Data race detected when accessing a variable. Wrapped value is the variable name.
@ -122,7 +120,6 @@ impl EvalAltResult {
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable", Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
Self::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant", Self::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant",
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect", Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
Self::ErrorInExpr(_) => "Malformed 'in' expression",
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorTooManyOperations(_) => "Too many operations", Self::ErrorTooManyOperations(_) => "Too many operations",
@ -182,7 +179,6 @@ impl fmt::Display for EvalAltResult {
Self::ErrorUnboundThis(_) Self::ErrorUnboundThis(_)
| Self::ErrorFor(_) | Self::ErrorFor(_)
| Self::ErrorInExpr(_)
| Self::ErrorDotExpr(_, _) | Self::ErrorDotExpr(_, _)
| Self::ErrorTooManyOperations(_) | Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(_) | Self::ErrorTooManyModules(_)
@ -305,7 +301,6 @@ impl EvalAltResult {
| Self::ErrorDataRace(_, _) | Self::ErrorDataRace(_, _)
| Self::ErrorAssignmentToConstant(_, _) | Self::ErrorAssignmentToConstant(_, _)
| Self::ErrorMismatchOutputType(_, _, _) | Self::ErrorMismatchOutputType(_, _, _)
| Self::ErrorInExpr(_)
| Self::ErrorDotExpr(_, _) | Self::ErrorDotExpr(_, _)
| Self::ErrorArithmetic(_, _) | Self::ErrorArithmetic(_, _)
| Self::ErrorRuntime(_, _) => true, | Self::ErrorRuntime(_, _) => true,
@ -362,7 +357,6 @@ impl EvalAltResult {
| Self::ErrorParsing(_, _) | Self::ErrorParsing(_, _)
| Self::ErrorUnboundThis(_) | Self::ErrorUnboundThis(_)
| Self::ErrorFor(_) | Self::ErrorFor(_)
| Self::ErrorInExpr(_)
| Self::ErrorArithmetic(_, _) | Self::ErrorArithmetic(_, _)
| Self::ErrorTooManyOperations(_) | Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(_) | Self::ErrorTooManyModules(_)
@ -430,7 +424,6 @@ impl EvalAltResult {
| Self::ErrorDataRace(_, pos) | Self::ErrorDataRace(_, pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, _, pos) | Self::ErrorMismatchOutputType(_, _, pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)
@ -471,7 +464,6 @@ impl EvalAltResult {
| Self::ErrorDataRace(_, pos) | Self::ErrorDataRace(_, pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, _, pos) | Self::ErrorMismatchOutputType(_, _, pos)
| Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)

View File

@ -151,7 +151,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ {
info.func.get_fn_def().comments.clone() info.func.get_fn_def().comments.to_vec()
} }
} else { } else {
Default::default() Default::default()

View File

@ -116,16 +116,19 @@ pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 {
pub struct HashableHashMap<K, T, H: BuildHasher>(HashMap<K, T, H>); pub struct HashableHashMap<K, T, H: BuildHasher>(HashMap<K, T, H>);
impl<K, T, H: BuildHasher> From<HashMap<K, T, H>> for HashableHashMap<K, T, H> { impl<K, T, H: BuildHasher> From<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
#[inline(always)]
fn from(value: HashMap<K, T, H>) -> Self { fn from(value: HashMap<K, T, H>) -> Self {
Self(value) Self(value)
} }
} }
impl<K, T, H: BuildHasher> AsRef<HashMap<K, T, H>> for HashableHashMap<K, T, H> { impl<K, T, H: BuildHasher> AsRef<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
#[inline(always)]
fn as_ref(&self) -> &HashMap<K, T, H> { fn as_ref(&self) -> &HashMap<K, T, H> {
&self.0 &self.0
} }
} }
impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H> { impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
#[inline(always)]
fn as_mut(&mut self) -> &mut HashMap<K, T, H> { fn as_mut(&mut self) -> &mut HashMap<K, T, H> {
&mut self.0 &mut self.0
} }
@ -133,21 +136,25 @@ impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H>
impl<K, T, H: BuildHasher> Deref for HashableHashMap<K, T, H> { impl<K, T, H: BuildHasher> Deref for HashableHashMap<K, T, H> {
type Target = HashMap<K, T, H>; type Target = HashMap<K, T, H>;
#[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
} }
impl<K, T, H: BuildHasher> DerefMut for HashableHashMap<K, T, H> { impl<K, T, H: BuildHasher> DerefMut for HashableHashMap<K, T, H> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
} }
} }
impl<K: Debug, T: Debug, H: BuildHasher> Debug for HashableHashMap<K, T, H> { impl<K: Debug, T: Debug, H: BuildHasher> Debug for HashableHashMap<K, T, H> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f) self.0.fmt(f)
} }
} }
impl<K: Hash + Ord, T: Hash, H: BuildHasher> Hash for HashableHashMap<K, T, H> { impl<K: Hash + Ord, T: Hash, H: BuildHasher> Hash for HashableHashMap<K, T, H> {
#[inline(always)]
fn hash<B: Hasher>(&self, state: &mut B) { fn hash<B: Hasher>(&self, state: &mut B) {
let mut keys: Vec<_> = self.0.keys().collect(); let mut keys: Vec<_> = self.0.keys().collect();
keys.sort(); keys.sort();

View File

@ -38,7 +38,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?); assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
assert!(engine.eval::<bool>("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?); assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?);
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?); assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
assert_eq!( assert_eq!(

View File

@ -55,20 +55,18 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident("DECISION" @ 1:9), Some(BoolConstant(false, 1:20)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#)); assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Block([Const(BoolConstant(false, 1:20), Ident("DECISION" @ 1:9), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)], functions: Module("#));
let ast = engine.compile("if 1 == 2 { 42 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert!( assert!(format!("{:?}", ast).starts_with("AST { source: None, body: [], functions: Module("));
format!("{:?}", ast).starts_with("AST { source: None, statements: [], functions: Module(")
);
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("abs(-42)")?; let ast = engine.compile("abs(-42)")?;
assert!(format!("{:?}", ast) assert!(format!("{:?}", ast)
.starts_with(r"AST { source: None, statements: [Expr(IntegerConstant(42, 1:1))]")); .starts_with(r"AST { source: None, body: [Expr(IntegerConstant(42, 1:1))]"));
Ok(()) Ok(())
} }