Merge pull request #372 from schungx/master
Improve speed by optimizing AST layout.
This commit is contained in:
commit
7a30030647
11
CHANGELOG.md
11
CHANGELOG.md
@ -4,6 +4,9 @@ Rhai Release Notes
|
||||
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
|
||||
---------
|
||||
|
||||
@ -27,18 +30,24 @@ Breaking changes
|
||||
* `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`.
|
||||
* `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.
|
||||
* 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
|
||||
------------
|
||||
|
||||
* Layout of AST nodes is optimized to reduce redirections, so speed is improved.
|
||||
* Function calls are more optimized and should now run faster.
|
||||
* `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_.
|
||||
* 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.
|
||||
* `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
|
||||
|
547
src/ast.rs
547
src/ast.rs
@ -47,7 +47,7 @@ pub enum FnAccess {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScriptFnDef {
|
||||
/// Function body.
|
||||
pub body: Stmt,
|
||||
pub body: StmtBlock,
|
||||
/// Encapsulated running environment, if any.
|
||||
pub lib: Option<Shared<Module>>,
|
||||
/// Encapsulated imported modules.
|
||||
@ -61,9 +61,9 @@ pub struct ScriptFnDef {
|
||||
pub params: StaticVec<ImmutableString>,
|
||||
/// Access to external variables.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub externals: Vec<ImmutableString>,
|
||||
pub externals: StaticVec<ImmutableString>,
|
||||
/// Function doc-comments (if any).
|
||||
pub comments: Vec<String>,
|
||||
pub comments: StaticVec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ScriptFnDef {
|
||||
@ -149,7 +149,7 @@ pub struct AST {
|
||||
/// Source of the [`AST`].
|
||||
source: Option<ImmutableString>,
|
||||
/// Global statements.
|
||||
statements: Vec<Stmt>,
|
||||
body: StmtBlock,
|
||||
/// Script-defined functions.
|
||||
functions: Shared<Module>,
|
||||
/// Embedded module resolver, if any.
|
||||
@ -162,7 +162,7 @@ impl Default for AST {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
statements: Vec::with_capacity(16),
|
||||
body: Default::default(),
|
||||
functions: Default::default(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -179,7 +179,10 @@ impl AST {
|
||||
) -> Self {
|
||||
Self {
|
||||
source: None,
|
||||
statements: statements.into_iter().collect(),
|
||||
body: StmtBlock {
|
||||
statements: statements.into_iter().collect(),
|
||||
pos: Position::NONE,
|
||||
},
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -194,7 +197,10 @@ impl AST {
|
||||
) -> Self {
|
||||
Self {
|
||||
source: Some(source.into()),
|
||||
statements: statements.into_iter().collect(),
|
||||
body: StmtBlock {
|
||||
statements: statements.into_iter().collect(),
|
||||
pos: Position::NONE,
|
||||
},
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -231,7 +237,7 @@ impl AST {
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn statements(&self) -> &[Stmt] {
|
||||
&self.statements
|
||||
&self.body.statements
|
||||
}
|
||||
/// _(INTERNALS)_ Get the statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
@ -239,13 +245,13 @@ impl AST {
|
||||
#[deprecated = "this method is volatile and may change"]
|
||||
#[inline(always)]
|
||||
pub fn statements(&self) -> &[Stmt] {
|
||||
&self.statements
|
||||
&self.body.statements
|
||||
}
|
||||
/// Get a mutable reference to the statements.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> {
|
||||
&mut self.statements
|
||||
pub(crate) fn statements_mut(&mut self) -> &mut StaticVec<Stmt> {
|
||||
&mut self.body.statements
|
||||
}
|
||||
/// Get the internal shared [`Module`] containing all script-defined functions.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -333,7 +339,7 @@ impl AST {
|
||||
functions.merge_filtered(&self.functions, &filter);
|
||||
Self {
|
||||
source: self.source.clone(),
|
||||
statements: Default::default(),
|
||||
body: Default::default(),
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
@ -345,7 +351,7 @@ impl AST {
|
||||
pub fn clone_statements_only(&self) -> Self {
|
||||
Self {
|
||||
source: self.source.clone(),
|
||||
statements: self.statements.clone(),
|
||||
body: self.body.clone(),
|
||||
functions: Default::default(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: self.resolver.clone(),
|
||||
@ -515,20 +521,19 @@ impl AST {
|
||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let Self {
|
||||
statements,
|
||||
functions,
|
||||
..
|
||||
body, functions, ..
|
||||
} = self;
|
||||
|
||||
let ast = match (statements.is_empty(), other.statements.is_empty()) {
|
||||
let merged = match (body.is_empty(), other.body.is_empty()) {
|
||||
(false, false) => {
|
||||
let mut statements = statements.clone();
|
||||
statements.extend(other.statements.iter().cloned());
|
||||
statements
|
||||
let mut body = body.clone();
|
||||
body.statements
|
||||
.extend(other.body.statements.iter().cloned());
|
||||
body
|
||||
}
|
||||
(false, true) => statements.clone(),
|
||||
(true, false) => other.statements.clone(),
|
||||
(true, true) => vec![],
|
||||
(false, true) => body.clone(),
|
||||
(true, false) => other.body.clone(),
|
||||
(true, true) => Default::default(),
|
||||
};
|
||||
|
||||
let source = other.source.clone().or_else(|| self.source.clone());
|
||||
@ -537,9 +542,9 @@ impl AST {
|
||||
functions.merge_filtered(&other.functions, &filter);
|
||||
|
||||
if let Some(source) = source {
|
||||
Self::new_with_source(ast, functions, source)
|
||||
Self::new_with_source(merged.statements, functions, source)
|
||||
} else {
|
||||
Self::new(ast, functions)
|
||||
Self::new(merged.statements, functions)
|
||||
}
|
||||
}
|
||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||
@ -599,7 +604,10 @@ impl AST {
|
||||
other: Self,
|
||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
self.statements.extend(other.statements.into_iter());
|
||||
self.body
|
||||
.statements
|
||||
.extend(other.body.statements.into_iter());
|
||||
|
||||
if !other.functions.is_empty() {
|
||||
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.
|
||||
#[inline(always)]
|
||||
pub fn clear_statements(&mut self) {
|
||||
self.statements = vec![];
|
||||
self.body = Default::default();
|
||||
}
|
||||
/// 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 = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode])) {
|
||||
self.statements()
|
||||
.iter()
|
||||
.chain({
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
self.iter_fn_def().map(|f| &f.body)
|
||||
}
|
||||
#[cfg(feature = "no_function")]
|
||||
{
|
||||
crate::stdlib::iter::empty()
|
||||
}
|
||||
})
|
||||
.for_each(|stmt| stmt.walk(&mut Default::default(), on_node));
|
||||
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||
let path = &mut Default::default();
|
||||
|
||||
for stmt in self.statements() {
|
||||
if !stmt.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
|
||||
if !stmt.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
/// _(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.
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode])) {
|
||||
self.statements()
|
||||
.iter()
|
||||
.chain({
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
self.iter_fn_def().map(|f| &f.body)
|
||||
}
|
||||
#[cfg(feature = "no_function")]
|
||||
{
|
||||
crate::stdlib::iter::empty()
|
||||
}
|
||||
})
|
||||
.for_each(|stmt| stmt.walk(&mut Default::default(), on_node));
|
||||
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||
let path = &mut Default::default();
|
||||
|
||||
for stmt in self.statements() {
|
||||
if !stmt.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
|
||||
if !stmt.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
@ -815,32 +864,36 @@ pub enum Stmt {
|
||||
/// No-op.
|
||||
Noop(Position),
|
||||
/// `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,
|
||||
Box<(
|
||||
HashableHashMap<u64, Stmt, StraightHasherBuilder>,
|
||||
Option<Stmt>,
|
||||
HashableHashMap<u64, StmtBlock, StraightHasherBuilder>,
|
||||
StmtBlock,
|
||||
)>,
|
||||
Position,
|
||||
),
|
||||
/// `while` expr `{` stmt `}`
|
||||
While(Option<Expr>, Box<Stmt>, Position),
|
||||
While(Expr, Box<StmtBlock>, Position),
|
||||
/// `do` `{` stmt `}` `while`|`until` expr
|
||||
Do(Box<Stmt>, Expr, bool, Position),
|
||||
Do(Box<StmtBlock>, Expr, bool, Position),
|
||||
/// `for` id `in` expr `{` stmt `}`
|
||||
For(Expr, Box<(String, Stmt)>, Position),
|
||||
For(Expr, Box<(String, StmtBlock)>, Position),
|
||||
/// \[`export`\] `let` id `=` expr
|
||||
Let(Box<Ident>, Option<Expr>, bool, Position),
|
||||
Let(Expr, Ident, bool, Position),
|
||||
/// \[`export`\] `const` id `=` expr
|
||||
Const(Box<Ident>, Option<Expr>, bool, Position),
|
||||
Const(Expr, Ident, bool, Position),
|
||||
/// expr op`=` expr
|
||||
Assignment(Box<(Expr, Expr, Option<OpAssignment>)>, Position),
|
||||
/// `{` stmt`;` ... `}`
|
||||
Block(Vec<Stmt>, Position),
|
||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||
TryCatch(Box<(Stmt, Option<Ident>, Stmt)>, Position, Position),
|
||||
TryCatch(
|
||||
Box<(StmtBlock, Option<Ident>, StmtBlock)>,
|
||||
Position,
|
||||
Position,
|
||||
),
|
||||
/// [expression][Expr]
|
||||
Expr(Expr),
|
||||
/// `continue`
|
||||
@ -848,10 +901,10 @@ pub enum Stmt {
|
||||
/// `break`
|
||||
Break(Position),
|
||||
/// `return`/`throw`
|
||||
Return((ReturnType, Position), Option<Expr>, Position),
|
||||
Return(ReturnType, Option<Expr>, Position),
|
||||
/// `import` expr `as` var
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Import(Expr, Option<Box<Ident>>, Position),
|
||||
Import(Expr, Option<Ident>, Position),
|
||||
/// `export` var `as` var `,` ...
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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 {
|
||||
/// Is this statement [`Noop`][Stmt::Noop]?
|
||||
#[inline(always)]
|
||||
@ -889,7 +963,7 @@ impl Stmt {
|
||||
| Self::While(_, _, pos)
|
||||
| Self::Do(_, _, _, pos)
|
||||
| Self::For(_, _, pos)
|
||||
| Self::Return((_, pos), _, _)
|
||||
| Self::Return(_, _, pos)
|
||||
| Self::Let(_, _, _, pos)
|
||||
| Self::Const(_, _, _, pos)
|
||||
| Self::TryCatch(_, pos, _) => *pos,
|
||||
@ -918,7 +992,7 @@ impl Stmt {
|
||||
| Self::While(_, _, pos)
|
||||
| Self::Do(_, _, _, pos)
|
||||
| Self::For(_, _, pos)
|
||||
| Self::Return((_, pos), _, _)
|
||||
| Self::Return(_, _, pos)
|
||||
| Self::Let(_, _, _, pos)
|
||||
| Self::Const(_, _, _, pos)
|
||||
| Self::TryCatch(_, pos, _) => *pos = new_pos,
|
||||
@ -938,6 +1012,31 @@ impl Stmt {
|
||||
|
||||
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)?
|
||||
pub fn is_self_terminated(&self) -> bool {
|
||||
match self {
|
||||
@ -976,23 +1075,29 @@ impl Stmt {
|
||||
Self::Expr(expr) => expr.is_pure(),
|
||||
Self::If(condition, x, _) => {
|
||||
condition.is_pure()
|
||||
&& x.0.is_pure()
|
||||
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
|
||||
&& x.0.statements.iter().all(Stmt::is_pure)
|
||||
&& x.1.statements.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::Switch(expr, x, _) => {
|
||||
expr.is_pure()
|
||||
&& x.0.values().all(Stmt::is_pure)
|
||||
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
|
||||
&& x.0
|
||||
.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, _, _) => {
|
||||
condition.is_pure() && block.is_pure()
|
||||
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
||||
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::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
|
||||
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"))]
|
||||
Self::Import(_, _, _) => false,
|
||||
@ -1003,53 +1108,150 @@ impl Stmt {
|
||||
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)]
|
||||
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());
|
||||
on_node(path);
|
||||
|
||||
if !on_node(path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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, _) => {
|
||||
e.walk(path, on_node);
|
||||
x.0.walk(path, on_node);
|
||||
if let Some(ref s) = x.1 {
|
||||
s.walk(path, on_node);
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
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, _) => {
|
||||
e.walk(path, on_node);
|
||||
x.0.values().for_each(|s| s.walk(path, on_node));
|
||||
if let Some(ref s) = x.1 {
|
||||
s.walk(path, on_node);
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
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(Some(e), s, _) | Self::Do(s, e, _, _) => {
|
||||
e.walk(path, on_node);
|
||||
s.walk(path, on_node);
|
||||
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(None, s, _) => s.walk(path, on_node),
|
||||
Self::For(e, x, _) => {
|
||||
e.walk(path, on_node);
|
||||
x.1.walk(path, on_node);
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &x.1.statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Assignment(x, _) => {
|
||||
x.0.walk(path, on_node);
|
||||
x.1.walk(path, on_node);
|
||||
if !x.0.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, _, _) => {
|
||||
x.0.walk(path, on_node);
|
||||
x.2.walk(path, on_node);
|
||||
for s in &x.0.statements {
|
||||
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"))]
|
||||
Self::Import(e, _, _) => e.walk(path, on_node),
|
||||
Self::Import(e, _, _) => {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
path.pop().unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1102,7 +1304,7 @@ pub struct OpAssignment {
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// 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 {
|
||||
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
|
||||
script: Option<u64>,
|
||||
@ -1110,6 +1312,20 @@ pub struct FnHash {
|
||||
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 {
|
||||
/// Create a [`FnHash`] with only the native Rust hash.
|
||||
#[inline(always)]
|
||||
@ -1185,6 +1401,7 @@ pub struct FloatWrapper(FLOAT);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl Hash for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.to_ne_bytes().hash(state);
|
||||
}
|
||||
@ -1192,6 +1409,7 @@ impl Hash for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl AsRef<FLOAT> for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &FLOAT {
|
||||
&self.0
|
||||
}
|
||||
@ -1199,6 +1417,7 @@ impl AsRef<FLOAT> for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl AsMut<FLOAT> for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn as_mut(&mut self) -> &mut FLOAT {
|
||||
&mut self.0
|
||||
}
|
||||
@ -1208,6 +1427,7 @@ impl AsMut<FLOAT> for FloatWrapper {
|
||||
impl crate::stdlib::ops::Deref for FloatWrapper {
|
||||
type Target = FLOAT;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
@ -1215,6 +1435,7 @@ impl crate::stdlib::ops::Deref for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
@ -1222,6 +1443,7 @@ impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl fmt::Debug for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
@ -1229,6 +1451,7 @@ impl fmt::Debug for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl fmt::Display for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(feature = "no_std")]
|
||||
use num_traits::Float;
|
||||
@ -1244,6 +1467,7 @@ impl fmt::Display for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl From<FLOAT> for FloatWrapper {
|
||||
#[inline(always)]
|
||||
fn from(value: FLOAT) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
@ -1253,6 +1477,7 @@ impl From<FLOAT> for FloatWrapper {
|
||||
impl FromStr for FloatWrapper {
|
||||
type Err = <FLOAT as FromStr>::Err;
|
||||
|
||||
#[inline(always)]
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
FLOAT::from_str(s).map(Into::<Self>::into)
|
||||
}
|
||||
@ -1260,6 +1485,7 @@ impl FromStr for FloatWrapper {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
impl FloatWrapper {
|
||||
#[inline(always)]
|
||||
pub const fn new(value: FLOAT) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
@ -1298,18 +1524,16 @@ pub enum Expr {
|
||||
Unit(Position),
|
||||
/// Variable access - (optional index, optional (hash, modules), variable name)
|
||||
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
|
||||
/// Property access - (getter, hash, setter, hash, prop)
|
||||
Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>),
|
||||
/// { [statement][Stmt] }
|
||||
Stmt(Box<StaticVec<Stmt>>, Position),
|
||||
/// Property access - ((getter, hash), (setter, hash), prop)
|
||||
Property(Box<((ImmutableString, u64), (ImmutableString, u64), Ident)>),
|
||||
/// { [statement][Stmt] ... }
|
||||
Stmt(Box<StmtBlock>),
|
||||
/// func `(` expr `,` ... `)`
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// lhs `.` rhs
|
||||
Dot(Box<BinaryExpr>, Position),
|
||||
/// expr `[` expr `]`
|
||||
Index(Box<BinaryExpr>, Position),
|
||||
/// lhs `in` rhs
|
||||
In(Box<BinaryExpr>, Position),
|
||||
/// lhs `&&` rhs
|
||||
And(Box<BinaryExpr>, Position),
|
||||
/// lhs `||` rhs
|
||||
@ -1329,6 +1553,7 @@ impl Expr {
|
||||
/// Get the [`Dynamic`] value of a constant expression.
|
||||
///
|
||||
/// Returns [`None`] if the expression is not constant.
|
||||
#[inline]
|
||||
pub fn get_constant_value(&self) -> Option<Dynamic> {
|
||||
Some(match self {
|
||||
Self::DynamicConstant(x, _) => x.as_ref().clone(),
|
||||
@ -1379,6 +1604,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
/// Get the [position][Position] of the expression.
|
||||
#[inline]
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -1392,12 +1618,12 @@ impl Expr {
|
||||
Self::FnPointer(_, pos) => *pos,
|
||||
Self::Array(_, pos) => *pos,
|
||||
Self::Map(_, pos) => *pos,
|
||||
Self::Property(x) => (x.4).pos,
|
||||
Self::Stmt(_, pos) => *pos,
|
||||
Self::Property(x) => (x.2).pos,
|
||||
Self::Stmt(x) => x.pos,
|
||||
Self::Variable(x) => (x.2).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,
|
||||
|
||||
@ -1407,6 +1633,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
/// Override the [position][Position] of the expression.
|
||||
#[inline]
|
||||
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -1421,10 +1648,10 @@ impl Expr {
|
||||
Self::Array(_, pos) => *pos = new_pos,
|
||||
Self::Map(_, pos) => *pos = new_pos,
|
||||
Self::Variable(x) => (x.2).pos = new_pos,
|
||||
Self::Property(x) => (x.4).pos = new_pos,
|
||||
Self::Stmt(_, pos) => *pos = new_pos,
|
||||
Self::Property(x) => (x.2).pos = new_pos,
|
||||
Self::Stmt(x) => x.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::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
||||
Self::Custom(_, pos) => *pos = new_pos,
|
||||
@ -1435,17 +1662,18 @@ impl Expr {
|
||||
/// Is the expression pure?
|
||||
///
|
||||
/// A pure expression has no side effects.
|
||||
#[inline]
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Self::Array(x, _) => x.iter().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()
|
||||
}
|
||||
|
||||
Self::Stmt(x, _) => x.iter().all(Stmt::is_pure),
|
||||
Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
|
||||
|
||||
Self::Variable(_) => true,
|
||||
|
||||
@ -1461,6 +1689,7 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
/// Is the expression a constant?
|
||||
#[inline]
|
||||
pub fn is_constant(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -1480,17 +1709,11 @@ impl Expr {
|
||||
// An map literal is constant if all items are 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,
|
||||
}
|
||||
}
|
||||
/// Is a particular [token][Token] allowed as a postfix operator to this expression?
|
||||
#[inline]
|
||||
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||
match token {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -1507,14 +1730,13 @@ impl Expr {
|
||||
| Self::IntegerConstant(_, _)
|
||||
| Self::CharConstant(_, _)
|
||||
| Self::FnPointer(_, _)
|
||||
| Self::In(_, _)
|
||||
| Self::And(_, _)
|
||||
| Self::Or(_, _)
|
||||
| Self::Unit(_) => false,
|
||||
|
||||
Self::StringConstant(_, _)
|
||||
| Self::FnCall(_, _)
|
||||
| Self::Stmt(_, _)
|
||||
| Self::Stmt(_)
|
||||
| Self::Dot(_, _)
|
||||
| Self::Index(_, _)
|
||||
| Self::Array(_, _)
|
||||
@ -1544,29 +1766,68 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
/// Recursively walk this expression.
|
||||
#[inline(always)]
|
||||
pub fn walk<'a>(&'a self, path: &mut Vec<ASTNode<'a>>, on_node: &mut impl FnMut(&[ASTNode])) {
|
||||
/// 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());
|
||||
on_node(path);
|
||||
|
||||
if !on_node(path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
|
||||
Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)),
|
||||
Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)),
|
||||
Self::Index(x, _)
|
||||
| Self::Dot(x, _)
|
||||
| Expr::In(x, _)
|
||||
| Expr::And(x, _)
|
||||
| Expr::Or(x, _) => {
|
||||
x.lhs.walk(path, on_node);
|
||||
x.rhs.walk(path, on_node);
|
||||
Self::Stmt(x) => {
|
||||
for s in &x.statements {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Array(x, _) => {
|
||||
for e in x.as_ref() {
|
||||
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();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1583,8 +1844,8 @@ mod tests {
|
||||
assert_eq!(size_of::<Position>(), 4);
|
||||
assert_eq!(size_of::<ast::Expr>(), 16);
|
||||
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
||||
assert_eq!(size_of::<ast::Stmt>(), 32);
|
||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
||||
assert_eq!(size_of::<ast::Stmt>(), 40);
|
||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 40);
|
||||
assert_eq!(size_of::<FnPtr>(), 32);
|
||||
assert_eq!(size_of::<Scope>(), 48);
|
||||
assert_eq!(size_of::<LexError>(), 56);
|
||||
|
@ -1,8 +1,5 @@
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
@ -56,35 +53,38 @@ fn print_help() {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
println!("Rhai REPL tool");
|
||||
println!("==============");
|
||||
print_help();
|
||||
|
||||
// Load init scripts
|
||||
// Initialize scripting engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[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 has_init_scripts = false;
|
||||
|
||||
for filename in env::args().skip(1) {
|
||||
{
|
||||
contents.clear();
|
||||
contents.clear();
|
||||
|
||||
let mut f = match File::open(&filename) {
|
||||
Err(err) => {
|
||||
eprintln!("Error reading script file: {}\n{}", filename, err);
|
||||
exit(1);
|
||||
}
|
||||
Ok(f) => f,
|
||||
};
|
||||
|
||||
if let Err(err) = f.read_to_string(&mut contents) {
|
||||
println!("Error reading script file: {}\n{}", filename, err);
|
||||
let mut f = match File::open(&filename) {
|
||||
Err(err) => {
|
||||
eprintln!("Error reading script file: {}\n{}", filename, err);
|
||||
exit(1);
|
||||
}
|
||||
Ok(f) => f,
|
||||
};
|
||||
|
||||
if let Err(err) = f.read_to_string(&mut contents) {
|
||||
println!("Error reading script file: {}\n{}", filename, err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let module = match engine
|
||||
@ -119,9 +119,8 @@ fn main() {
|
||||
}
|
||||
|
||||
// Setup Engine
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
@ -130,8 +129,10 @@ fn main() {
|
||||
let mut ast_u: AST = Default::default();
|
||||
let mut ast: AST = Default::default();
|
||||
|
||||
// REPL loop
|
||||
// Make Engine immutable
|
||||
let engine = engine;
|
||||
|
||||
// REPL loop
|
||||
'main_loop: loop {
|
||||
print!("rhai-repl> ");
|
||||
stdout().flush().expect("couldn't flush stdout");
|
||||
@ -233,7 +234,7 @@ fn main() {
|
||||
|
||||
#[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")]
|
||||
|
421
src/engine.rs
421
src/engine.rs
@ -1,6 +1,6 @@
|
||||
//! 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::fn_native::{
|
||||
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
|
||||
@ -17,7 +17,6 @@ use crate::stdlib::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt, format,
|
||||
hash::{Hash, Hasher},
|
||||
iter::empty,
|
||||
num::{NonZeroU64, NonZeroU8, NonZeroUsize},
|
||||
ops::DerefMut,
|
||||
string::{String, ToString},
|
||||
@ -25,12 +24,12 @@ use crate::stdlib::{
|
||||
use crate::syntax::CustomSyntax;
|
||||
use crate::utils::{get_hasher, StraightHasherBuilder};
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult,
|
||||
Scope, Shared, StaticVec,
|
||||
Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared,
|
||||
StaticVec,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::Array;
|
||||
use crate::{calc_fn_hash, stdlib::iter::empty, Array};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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$";
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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 = "==";
|
||||
|
||||
/// 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.
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
@ -223,11 +228,11 @@ pub enum ChainType {
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum ChainArgument {
|
||||
/// Dot-property access.
|
||||
Property,
|
||||
Property(Position),
|
||||
/// Arguments to a dot-function call.
|
||||
FnCallArgs(StaticVec<Dynamic>),
|
||||
FnCallArgs(StaticVec<Dynamic>, StaticVec<Position>),
|
||||
/// Index value.
|
||||
IndexValue(Dynamic),
|
||||
IndexValue(Dynamic, Position),
|
||||
}
|
||||
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
@ -241,8 +246,10 @@ impl ChainArgument {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub fn as_index_value(self) -> Dynamic {
|
||||
match self {
|
||||
Self::Property | Self::FnCallArgs(_) => panic!("expecting ChainArgument::IndexValue"),
|
||||
Self::IndexValue(value) => value,
|
||||
Self::Property(_) | Self::FnCallArgs(_, _) => {
|
||||
panic!("expecting ChainArgument::IndexValue")
|
||||
}
|
||||
Self::IndexValue(value, _) => value,
|
||||
}
|
||||
}
|
||||
/// Return the `StaticVec<Dynamic>` value.
|
||||
@ -252,27 +259,29 @@ impl ChainArgument {
|
||||
/// Panics if not `ChainArgument::FnCallArgs`.
|
||||
#[inline(always)]
|
||||
#[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 {
|
||||
Self::Property | Self::IndexValue(_) => panic!("expecting ChainArgument::FnCallArgs"),
|
||||
Self::FnCallArgs(value) => value,
|
||||
Self::Property(_) | Self::IndexValue(_, _) => {
|
||||
panic!("expecting ChainArgument::FnCallArgs")
|
||||
}
|
||||
Self::FnCallArgs(values, positions) => (values, positions),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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)]
|
||||
fn from(value: StaticVec<Dynamic>) -> Self {
|
||||
Self::FnCallArgs(value)
|
||||
fn from((values, positions): (StaticVec<Dynamic>, StaticVec<Position>)) -> Self {
|
||||
Self::FnCallArgs(values, positions)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
impl From<Dynamic> for ChainArgument {
|
||||
impl From<(Dynamic, Position)> for ChainArgument {
|
||||
#[inline(always)]
|
||||
fn from(value: Dynamic) -> Self {
|
||||
Self::IndexValue(value)
|
||||
fn from((value, pos): (Dynamic, Position)) -> Self {
|
||||
Self::IndexValue(value, pos)
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,7 +404,11 @@ impl<'a> Target<'a> {
|
||||
}
|
||||
/// Update the value of the `Target`.
|
||||
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
||||
pub fn set_value(&mut self, new_val: Dynamic, pos: Position) -> Result<(), Box<EvalAltResult>> {
|
||||
pub fn set_value(
|
||||
&mut self,
|
||||
new_val: Dynamic,
|
||||
_pos: Position,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
match self {
|
||||
Self::Ref(r) => **r = new_val,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -411,7 +424,7 @@ impl<'a> Target<'a> {
|
||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||
"char".to_string(),
|
||||
err.to_string(),
|
||||
pos,
|
||||
_pos,
|
||||
))
|
||||
})?;
|
||||
|
||||
@ -893,6 +906,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Search for a module within an imports stack.
|
||||
#[inline]
|
||||
pub(crate) fn search_imports(
|
||||
&self,
|
||||
mods: &Imports,
|
||||
@ -932,7 +946,7 @@ impl Engine {
|
||||
match expr {
|
||||
Expr::Variable(v) => match v.as_ref() {
|
||||
// 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(|| {
|
||||
EvalAltResult::ErrorModuleNotFound(
|
||||
modules[0].name.to_string(),
|
||||
@ -971,7 +985,7 @@ impl Engine {
|
||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||
expr: &Expr,
|
||||
) -> 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(),
|
||||
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
||||
};
|
||||
@ -1152,9 +1166,9 @@ impl Engine {
|
||||
// xxx.fn_name(arg_expr_list)
|
||||
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
|
||||
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(
|
||||
mods, state, lib, name, *hash, target, args, *pos, level,
|
||||
mods, state, lib, name, *hash, target, &mut args, *pos, level,
|
||||
)
|
||||
}
|
||||
// xxx.fn_name(...) = ???
|
||||
@ -1167,7 +1181,7 @@ impl Engine {
|
||||
}
|
||||
// {xxx:map}.id op= ???
|
||||
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 val = self.get_indexed_mut(
|
||||
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
|
||||
@ -1180,7 +1194,7 @@ impl Engine {
|
||||
}
|
||||
// {xxx:map}.id
|
||||
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 val = self.get_indexed_mut(
|
||||
mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
|
||||
@ -1190,7 +1204,7 @@ impl Engine {
|
||||
}
|
||||
// xxx.id = ???
|
||||
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 mut new_val = new_val;
|
||||
let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
|
||||
@ -1202,7 +1216,7 @@ impl Engine {
|
||||
}
|
||||
// xxx.id
|
||||
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 mut args = [target_val];
|
||||
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>() => {
|
||||
let mut val = match &x.lhs {
|
||||
Expr::Property(p) => {
|
||||
let Ident { name, pos } = &p.4;
|
||||
let Ident { name, pos, .. } = &p.2;
|
||||
let index = name.clone().into();
|
||||
self.get_indexed_mut(
|
||||
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
|
||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||
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(
|
||||
mods, state, lib, name, *hash, target, args, *pos, level,
|
||||
mods, state, lib, name, *hash, target, &mut args, *pos, level,
|
||||
)?;
|
||||
val.into()
|
||||
}
|
||||
@ -1250,7 +1264,7 @@ impl Engine {
|
||||
match &x.lhs {
|
||||
// xxx.prop[expr] | xxx.prop.expr
|
||||
Expr::Property(p) => {
|
||||
let (getter, hash_get, setter, hash_set, Ident { pos, .. }) =
|
||||
let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) =
|
||||
p.as_ref();
|
||||
let hash_get = FnHash::from_native(*hash_get);
|
||||
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
|
||||
Expr::FnCall(f, pos) if f.namespace.is_none() => {
|
||||
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(
|
||||
mods, state, lib, name, *hash, target, args, *pos, level,
|
||||
mods, state, lib, name, *hash, target, &mut args, *pos, level,
|
||||
)?;
|
||||
let val = &mut val;
|
||||
let target = &mut val.into();
|
||||
@ -1364,6 +1378,7 @@ impl Engine {
|
||||
let Ident {
|
||||
name: var_name,
|
||||
pos: var_pos,
|
||||
..
|
||||
} = &x.2;
|
||||
|
||||
self.inc_operations(state, *var_pos)?;
|
||||
@ -1422,23 +1437,26 @@ impl Engine {
|
||||
|
||||
match expr {
|
||||
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => {
|
||||
let mut arg_positions: StaticVec<_> = Default::default();
|
||||
|
||||
let arg_values = x
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg_expr| {
|
||||
arg_positions.push(arg_expr.position());
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
||||
.map(Dynamic::flatten)
|
||||
})
|
||||
.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 => {
|
||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||
}
|
||||
|
||||
Expr::Property(_) if parent_chain_type == ChainType::Dot => {
|
||||
idx_values.push(ChainArgument::Property)
|
||||
Expr::Property(x) if parent_chain_type == ChainType::Dot => {
|
||||
idx_values.push(ChainArgument::Property(x.2.pos))
|
||||
}
|
||||
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
||||
|
||||
@ -1447,29 +1465,33 @@ impl Engine {
|
||||
|
||||
// Evaluate in left-to-right order
|
||||
let lhs_val = match lhs {
|
||||
Expr::Property(_) if parent_chain_type == ChainType::Dot => {
|
||||
ChainArgument::Property
|
||||
Expr::Property(x) if parent_chain_type == ChainType::Dot => {
|
||||
ChainArgument::Property(x.2.pos)
|
||||
}
|
||||
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
||||
Expr::FnCall(x, _)
|
||||
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()
|
||||
.map(|arg_expr| {
|
||||
arg_positions.push(arg_expr.position());
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
||||
.map(Dynamic::flatten)
|
||||
})
|
||||
.collect::<Result<StaticVec<Dynamic>, _>>()?
|
||||
.into()
|
||||
.collect::<Result<StaticVec<_>, _>>()?;
|
||||
|
||||
(arg_values, arg_positions).into()
|
||||
}
|
||||
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
|
||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||
}
|
||||
_ => self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
|
||||
.flatten()
|
||||
.into(),
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)
|
||||
.map(|v| (v.flatten(), lhs.position()).into())?,
|
||||
};
|
||||
|
||||
// Push in reverse order
|
||||
@ -1486,9 +1508,8 @@ impl Engine {
|
||||
}
|
||||
|
||||
_ => idx_values.push(
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten()
|
||||
.into(),
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||
.map(|v| (v.flatten(), expr.position()).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.
|
||||
pub(crate) fn eval_expr(
|
||||
&self,
|
||||
@ -1691,16 +1655,11 @@ impl Engine {
|
||||
.map(|(val, _)| val.take_or_clone()),
|
||||
|
||||
// Statement block
|
||||
Expr::Stmt(x, _) => self.eval_stmt_block(
|
||||
scope,
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
this_ptr,
|
||||
x.as_ref().as_ref(),
|
||||
true,
|
||||
level,
|
||||
),
|
||||
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
||||
Expr::Stmt(x) => {
|
||||
let statements = &x.statements;
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
||||
}
|
||||
|
||||
// lhs[idx_expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -1745,13 +1704,13 @@ impl Engine {
|
||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
capture: cap_scope,
|
||||
capture,
|
||||
hash,
|
||||
args,
|
||||
..
|
||||
} = x.as_ref();
|
||||
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, _) => {
|
||||
Ok((self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
|
||||
@ -1844,6 +1799,10 @@ impl Engine {
|
||||
restore_prev_state: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
if statements.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
let mut _extra_fn_resolution_cache = false;
|
||||
let prev_always_search = state.always_search;
|
||||
let prev_scope_len = scope.len();
|
||||
@ -2065,23 +2024,43 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Block scope
|
||||
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
|
||||
Stmt::Block(statements, _) => {
|
||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
||||
}
|
||||
|
||||
// If statement
|
||||
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)?
|
||||
.as_bool()
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
||||
.and_then(|guard_val| {
|
||||
if guard_val {
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level)
|
||||
} else if let Some(stmt) = else_block {
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
|
||||
if !if_stmt.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, if_stmt, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
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);
|
||||
let hash = hasher.finish();
|
||||
|
||||
table
|
||||
.get(&hash)
|
||||
.map(|stmt| self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level))
|
||||
table.get(&hash).map(|StmtBlock { statements, .. }| {
|
||||
if !statements.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, statements, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Non-hashable values never match any specific clause
|
||||
None
|
||||
}
|
||||
.unwrap_or_else(|| {
|
||||
// Default match clause
|
||||
def_stmt.as_ref().map_or_else(
|
||||
|| Ok(Dynamic::UNIT),
|
||||
|def_stmt| {
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
|
||||
},
|
||||
)
|
||||
let def_stmt = &def_stmt.statements;
|
||||
if !def_stmt.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, def_stmt, true, level,
|
||||
)
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// While loop
|
||||
Stmt::While(expr, body, _) => loop {
|
||||
let condition = if let Some(expr) = expr {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
|
||||
} else {
|
||||
true
|
||||
};
|
||||
Stmt::While(expr, body, _) => {
|
||||
let body = &body.statements;
|
||||
loop {
|
||||
let condition = if !expr.is_unit() {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|err| {
|
||||
self.make_type_mismatch_err::<bool>(err, expr.position())
|
||||
})?
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if condition {
|
||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
|
||||
if !condition {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
if body.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::LoopBreak(false, _) => (),
|
||||
@ -2134,40 +2132,46 @@ impl Engine {
|
||||
_ => return Err(err),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Do loop
|
||||
Stmt::Do(body, expr, is_while, _) => loop {
|
||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::LoopBreak(false, _) => continue,
|
||||
EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
||||
_ => return Err(err),
|
||||
},
|
||||
}
|
||||
Stmt::Do(body, expr, is_while, _) => {
|
||||
let body = &body.statements;
|
||||
|
||||
if self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
|
||||
{
|
||||
if !*is_while {
|
||||
return Ok(Dynamic::UNIT);
|
||||
loop {
|
||||
if !body.is_empty() {
|
||||
match self
|
||||
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::LoopBreak(false, _) => continue,
|
||||
EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
||||
_ => return Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if *is_while {
|
||||
return Ok(Dynamic::UNIT);
|
||||
|
||||
if self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
|
||||
{
|
||||
if !*is_while {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
} else {
|
||||
if *is_while {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// For loop
|
||||
Stmt::For(expr, x, _) => {
|
||||
let (name, stmt) = x.as_ref();
|
||||
let (name, StmtBlock { statements, pos }) = x.as_ref();
|
||||
let iter_obj = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
@ -2216,9 +2220,15 @@ impl Engine {
|
||||
*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(_) => (),
|
||||
Err(err) => match *err {
|
||||
EvalAltResult::LoopBreak(false, _) => (),
|
||||
@ -2244,10 +2254,20 @@ impl Engine {
|
||||
|
||||
// Try/Catch statement
|
||||
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
|
||||
.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);
|
||||
|
||||
match result {
|
||||
@ -2306,8 +2326,9 @@ impl Engine {
|
||||
scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value);
|
||||
}
|
||||
|
||||
let result =
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level);
|
||||
let result = self.eval_stmt_block(
|
||||
scope, mods, state, lib, this_ptr, catch_body, true, level,
|
||||
);
|
||||
|
||||
state.scope_level -= 1;
|
||||
scope.rewind(orig_scope_len);
|
||||
@ -2328,7 +2349,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Return value
|
||||
Stmt::Return((ReturnType::Return, pos), Some(expr), _) => {
|
||||
Stmt::Return(ReturnType::Return, Some(expr), pos) => {
|
||||
let value = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
@ -2336,12 +2357,12 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Empty return
|
||||
Stmt::Return((ReturnType::Return, pos), None, _) => {
|
||||
Stmt::Return(ReturnType::Return, None, pos) => {
|
||||
EvalAltResult::Return(Default::default(), *pos).into()
|
||||
}
|
||||
|
||||
// Throw value
|
||||
Stmt::Return((ReturnType::Exception, pos), Some(expr), _) => {
|
||||
Stmt::Return(ReturnType::Exception, Some(expr), pos) => {
|
||||
let value = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
@ -2349,38 +2370,34 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Empty throw
|
||||
Stmt::Return((ReturnType::Exception, pos), None, _) => {
|
||||
Stmt::Return(ReturnType::Exception, None, pos) => {
|
||||
EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Stmt::Let(_, _, _, _) => AccessMode::ReadWrite,
|
||||
Stmt::Const(_, _, _, _) => AccessMode::ReadOnly,
|
||||
_ => unreachable!("should be Stmt::Let or Stmt::Const, but gets {:?}", stmt),
|
||||
};
|
||||
|
||||
let value = if let Some(expr) = expr {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten()
|
||||
} else {
|
||||
Dynamic::UNIT
|
||||
};
|
||||
let value = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
|
||||
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
|
||||
(
|
||||
var_def.name.to_string().into(),
|
||||
if *export {
|
||||
Some(var_def.name.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
name.to_string().into(),
|
||||
if *export { Some(name.clone()) } else { None },
|
||||
)
|
||||
} else if *export {
|
||||
unreachable!("exported variable not on global level");
|
||||
} 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);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -2392,7 +2409,7 @@ impl Engine {
|
||||
|
||||
// Import statement
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(expr, alias, _pos) => {
|
||||
Stmt::Import(expr, export, _pos) => {
|
||||
// Guard against too many modules
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if state.modules >= self.max_modules() {
|
||||
@ -2419,14 +2436,14 @@ impl Engine {
|
||||
})
|
||||
.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() {
|
||||
// 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);
|
||||
module.build_index();
|
||||
mods.push(name_def.name.clone(), module);
|
||||
mods.push(name, module);
|
||||
} else {
|
||||
mods.push(name_def.name.clone(), module);
|
||||
mods.push(name, module);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2441,14 +2458,13 @@ impl Engine {
|
||||
// Export statement
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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
|
||||
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
||||
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
||||
scope.add_entry_alias(index, alias.clone());
|
||||
} else {
|
||||
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos)
|
||||
.into();
|
||||
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into();
|
||||
}
|
||||
}
|
||||
Ok(Dynamic::UNIT)
|
||||
@ -2482,6 +2498,7 @@ impl Engine {
|
||||
|
||||
/// Check a result to ensure that the data size is within allowable limit.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[inline(always)]
|
||||
fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult {
|
||||
// Simply return all errors
|
||||
if result.is_err() {
|
||||
|
@ -926,8 +926,9 @@ impl Engine {
|
||||
if !resolver.contains_path(s) && !imports.contains(s) =>
|
||||
{
|
||||
imports.insert(s.clone());
|
||||
true
|
||||
}
|
||||
_ => (),
|
||||
_ => true,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1832,7 +1833,7 @@ impl Engine {
|
||||
let lib = Default::default();
|
||||
|
||||
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.
|
||||
///
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! Built-in implementations for common operators.
|
||||
|
||||
use crate::engine::OP_CONTAINS;
|
||||
use crate::fn_native::{FnCallArgs, NativeCallContext};
|
||||
use crate::stdlib::{any::TypeId, format, string::ToString};
|
||||
use crate::{Dynamic, ImmutableString, RhaiResult, INT};
|
||||
@ -77,6 +78,13 @@ pub fn get_builtin_binary_op_fn(
|
||||
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 )) => {
|
||||
return Some(|_, 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(<=)),
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -342,6 +368,13 @@ pub fn get_builtin_binary_op_fn(
|
||||
">=" => 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,
|
||||
}
|
||||
}
|
||||
|
107
src/fn_call.rs
107
src/fn_call.rs
@ -493,6 +493,10 @@ impl Engine {
|
||||
|
||||
self.inc_operations(state, pos)?;
|
||||
|
||||
if fn_def.body.is_empty() {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
|
||||
// Check for stack overflow
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -539,10 +543,10 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Evaluate the function
|
||||
let stmt = &fn_def.body;
|
||||
let body = &fn_def.body.statements;
|
||||
|
||||
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 {
|
||||
// Convert return statement to return value
|
||||
EvalAltResult::Return(x, _) => Ok(x),
|
||||
@ -722,6 +726,10 @@ impl Engine {
|
||||
|
||||
let func = func.get_fn_def();
|
||||
|
||||
if func.body.is_empty() {
|
||||
return Ok((Dynamic::UNIT, false));
|
||||
}
|
||||
|
||||
let scope: &mut Scope = &mut Default::default();
|
||||
|
||||
// Move captured variables into scope
|
||||
@ -831,6 +839,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Evaluate a text script in place - used primarily for 'eval'.
|
||||
#[inline]
|
||||
fn eval_script_expr_in_place(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -884,7 +893,7 @@ impl Engine {
|
||||
fn_name: &str,
|
||||
mut hash: FnHash,
|
||||
target: &mut crate::engine::Target,
|
||||
mut call_args: StaticVec<Dynamic>,
|
||||
(call_args, call_arg_positions): &mut (StaticVec<Dynamic>, StaticVec<Position>),
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
@ -902,7 +911,7 @@ impl Engine {
|
||||
let fn_name = fn_ptr.fn_name();
|
||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||
// 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
|
||||
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||
let mut arg_values = curry
|
||||
@ -913,17 +922,32 @@ impl Engine {
|
||||
|
||||
// Map it to name(args) in function-call style
|
||||
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
|
||||
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
|
||||
call_arg_positions.remove(0);
|
||||
// Redirect function name
|
||||
let fn_name = fn_ptr.fn_name();
|
||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||
// 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 + 1),
|
||||
);
|
||||
@ -937,22 +961,34 @@ impl Engine {
|
||||
|
||||
// Map it to name(args) in function-call style
|
||||
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>() => {
|
||||
// Curry call
|
||||
KEYWORD_FN_PTR_CURRY => {
|
||||
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();
|
||||
|
||||
// Curry call
|
||||
Ok((
|
||||
FnPtr::new_unchecked(
|
||||
fn_ptr.get_fn_name().clone(),
|
||||
fn_ptr
|
||||
.curry()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(call_args.into_iter())
|
||||
.collect(),
|
||||
)
|
||||
if call_args.is_empty() {
|
||||
fn_ptr.clone()
|
||||
} else {
|
||||
FnPtr::new_unchecked(
|
||||
fn_ptr.get_fn_name().clone(),
|
||||
fn_ptr
|
||||
.curry()
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(call_args.iter_mut().map(|v| mem::take(v)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
.into(),
|
||||
false,
|
||||
))
|
||||
@ -981,7 +1017,10 @@ impl Engine {
|
||||
.iter()
|
||||
.cloned()
|
||||
.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
|
||||
hash = FnHash::from_script_and_native(
|
||||
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))
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Fn()
|
||||
KEYWORD_FN_PTR if args_expr.len() == 1 => {
|
||||
// Fn - only in function call style
|
||||
@ -1361,22 +1399,27 @@ impl Engine {
|
||||
match func {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Some(f) if f.is_script() => {
|
||||
let args = args.as_mut();
|
||||
let new_scope = &mut Default::default();
|
||||
let fn_def = f.get_fn_def().clone();
|
||||
let fn_def = f.get_fn_def();
|
||||
|
||||
let mut source = module.id_raw().cloned();
|
||||
mem::swap(&mut state.source, &mut source);
|
||||
if fn_def.body.is_empty() {
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
let args = args.as_mut();
|
||||
let new_scope = &mut Default::default();
|
||||
|
||||
let level = level + 1;
|
||||
let mut source = module.id_raw().cloned();
|
||||
mem::swap(&mut state.source, &mut source);
|
||||
|
||||
let result = self.call_script_fn(
|
||||
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
|
||||
);
|
||||
let level = level + 1;
|
||||
|
||||
state.source = source;
|
||||
let result = self.call_script_fn(
|
||||
new_scope, mods, state, lib, &mut None, fn_def, args, pos, level,
|
||||
);
|
||||
|
||||
result
|
||||
state.source = source;
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
Some(f) if f.is_plugin_fn() => f
|
||||
|
@ -124,7 +124,7 @@ pub type FLOAT = f32;
|
||||
|
||||
pub use ast::{FnAccess, AST};
|
||||
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_register::{RegisterFn, RegisterResultFn};
|
||||
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"]
|
||||
pub use ast::{
|
||||
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment,
|
||||
ReturnType, ScriptFnDef, Stmt,
|
||||
ReturnType, ScriptFnDef, Stmt, StmtBlock,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
|
@ -39,6 +39,7 @@ pub enum FnNamespace {
|
||||
}
|
||||
|
||||
impl Default for FnNamespace {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::Internal
|
||||
}
|
||||
@ -150,6 +151,7 @@ pub struct Module {
|
||||
}
|
||||
|
||||
impl Default for Module {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
@ -226,6 +228,7 @@ impl AsRef<Module> for Module {
|
||||
impl<M: AsRef<Module>> Add<M> for &Module {
|
||||
type Output = Module;
|
||||
|
||||
#[inline(always)]
|
||||
fn add(self, rhs: M) -> Self::Output {
|
||||
let mut module = self.clone();
|
||||
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 {
|
||||
type Output = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn add(mut self, rhs: M) -> Self::Output {
|
||||
self.merge(rhs.as_ref());
|
||||
self
|
||||
@ -243,6 +247,7 @@ impl<M: AsRef<Module>> Add<M> for Module {
|
||||
}
|
||||
|
||||
impl<M: Into<Module>> AddAssign<M> for Module {
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, rhs: M) {
|
||||
self.combine(rhs.into());
|
||||
}
|
||||
@ -1953,16 +1958,19 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Does a type iterator exist in the entire module tree?
|
||||
#[inline(always)]
|
||||
pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
|
||||
self.all_type_iterators.contains_key(&id)
|
||||
}
|
||||
|
||||
/// Does a type iterator exist in the module?
|
||||
#[inline(always)]
|
||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||
self.type_iterators.contains_key(&id)
|
||||
}
|
||||
|
||||
/// Set a type iterator into the [`Module`].
|
||||
#[inline(always)]
|
||||
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) -> &mut Self {
|
||||
self.type_iterators.insert(typ, func);
|
||||
self.indexed = false;
|
||||
@ -1971,6 +1979,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Set a type iterator into the [`Module`].
|
||||
#[inline(always)]
|
||||
pub fn set_iterable<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Variant + Clone + IntoIterator,
|
||||
@ -1982,6 +1991,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Set an iterator type into the [`Module`] as a type iterator.
|
||||
#[inline(always)]
|
||||
pub fn set_iterator<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Variant + Clone + Iterator,
|
||||
@ -1993,11 +2003,13 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||
self.all_type_iterators.get(&id).cloned()
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||
self.type_iterators.get(&id).cloned()
|
||||
}
|
||||
@ -2021,32 +2033,41 @@ pub struct NamespaceRef {
|
||||
}
|
||||
|
||||
impl fmt::Debug for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.path, f)?;
|
||||
|
||||
if let Some(index) = self.index {
|
||||
write!(f, " -> {}", index)
|
||||
} else {
|
||||
Ok(())
|
||||
write!(f, "{} -> ", index)?;
|
||||
}
|
||||
|
||||
f.write_str(
|
||||
&self
|
||||
.path
|
||||
.iter()
|
||||
.map(|Ident { name, .. }| name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("::"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for NamespaceRef {
|
||||
type Target = StaticVec<Ident>;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for Ident { name, .. } in self.path.iter() {
|
||||
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
||||
@ -2056,6 +2077,7 @@ impl fmt::Display for NamespaceRef {
|
||||
}
|
||||
|
||||
impl From<StaticVec<Ident>> for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn from(path: StaticVec<Ident>) -> Self {
|
||||
Self { index: None, path }
|
||||
}
|
||||
@ -2063,11 +2085,13 @@ impl From<StaticVec<Ident>> for NamespaceRef {
|
||||
|
||||
impl NamespaceRef {
|
||||
/// Get the [`Scope`][crate::Scope] index offset.
|
||||
#[inline(always)]
|
||||
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
||||
self.index
|
||||
}
|
||||
/// Set the [`Scope`][crate::Scope] index offset.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||
self.index = index
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
||||
pub struct FileModuleResolver {
|
||||
base_path: PathBuf,
|
||||
extension: String,
|
||||
cache_enabled: bool,
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>,
|
||||
@ -101,6 +102,7 @@ impl FileModuleResolver {
|
||||
Self {
|
||||
base_path: path.into(),
|
||||
extension: extension.into(),
|
||||
cache_enabled: true,
|
||||
cache: Default::default(),
|
||||
}
|
||||
}
|
||||
@ -152,9 +154,25 @@ impl FileModuleResolver {
|
||||
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?
|
||||
#[inline(always)]
|
||||
pub fn is_cached(&self, path: &str) -> bool {
|
||||
if !self.cache_enabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let file_path = self.get_file_path(path);
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
@ -211,7 +229,7 @@ impl ModuleResolver for FileModuleResolver {
|
||||
let file_path = self.get_file_path(path);
|
||||
|
||||
// See if it is cached
|
||||
{
|
||||
if self.is_cache_enabled() {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
let c = self.cache.borrow();
|
||||
#[cfg(feature = "sync")]
|
||||
@ -242,10 +260,12 @@ impl ModuleResolver for FileModuleResolver {
|
||||
.into();
|
||||
|
||||
// Put it into the cache
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.cache.borrow_mut().insert(file_path, m.clone());
|
||||
#[cfg(feature = "sync")]
|
||||
self.cache.write().unwrap().insert(file_path, m.clone());
|
||||
if self.is_cache_enabled() {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.cache.borrow_mut().insert(file_path, m.clone());
|
||||
#[cfg(feature = "sync")]
|
||||
self.cache.write().unwrap().insert(file_path, m.clone());
|
||||
}
|
||||
|
||||
Ok(m)
|
||||
}
|
||||
|
579
src/optimize.rs
579
src/optimize.rs
@ -1,6 +1,6 @@
|
||||
//! Module implementing the [`AST`] optimizer.
|
||||
|
||||
use crate::ast::{Expr, Stmt};
|
||||
use crate::ast::{Expr, Ident, Stmt, StmtBlock};
|
||||
use crate::dynamic::AccessMode;
|
||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::fn_builtin::get_builtin_binary_op_fn;
|
||||
@ -15,7 +15,6 @@ use crate::stdlib::{
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
use crate::token::is_valid_identifier;
|
||||
use crate::utils::get_hasher;
|
||||
use crate::{
|
||||
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> {
|
||||
/// Create a new State.
|
||||
#[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 {
|
||||
changed: false,
|
||||
variables: vec![],
|
||||
propagate_constants: true,
|
||||
engine,
|
||||
lib,
|
||||
optimization_level: level,
|
||||
optimization_level,
|
||||
}
|
||||
}
|
||||
/// Reset the state from dirty to clean.
|
||||
@ -95,6 +98,11 @@ impl<'a> State<'a> {
|
||||
pub fn set_dirty(&mut self) {
|
||||
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)?
|
||||
#[inline(always)]
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
@ -169,139 +177,112 @@ fn call_fn_with_constant_arguments(
|
||||
/// Optimize a block of [statements][Stmt].
|
||||
fn optimize_stmt_block(
|
||||
mut statements: Vec<Stmt>,
|
||||
pos: Position,
|
||||
state: &mut State,
|
||||
preserve_result: bool,
|
||||
count_promote_as_dirty: bool,
|
||||
) -> Stmt {
|
||||
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
||||
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
|
||||
let orig_propagate_constants = state.propagate_constants;
|
||||
) -> Vec<Stmt> {
|
||||
if statements.is_empty() {
|
||||
return statements;
|
||||
}
|
||||
|
||||
// Optimize each statement in the block
|
||||
statements.iter_mut().for_each(|stmt| {
|
||||
match stmt {
|
||||
// Add constant literals into the state
|
||||
Stmt::Const(var_def, Some(value_expr), _, _) => {
|
||||
optimize_expr(value_expr, state);
|
||||
let mut is_dirty = state.is_dirty();
|
||||
|
||||
if value_expr.is_constant() {
|
||||
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone());
|
||||
}
|
||||
loop {
|
||||
state.clear_dirty();
|
||||
|
||||
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
|
||||
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
|
||||
}
|
||||
Stmt::Const(var_def, None, _, _) => {
|
||||
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
|
||||
}
|
||||
// Add variables into the state
|
||||
Stmt::Let(var_def, expr, _, _) => {
|
||||
if let Some(value_expr) = expr {
|
||||
});
|
||||
|
||||
// Optimize each statement in the block
|
||||
statements.iter_mut().for_each(|stmt| {
|
||||
match stmt {
|
||||
// Add constant literals into the state
|
||||
Stmt::Const(value_expr, Ident { name, .. }, _, _) => {
|
||||
optimize_expr(value_expr, state);
|
||||
|
||||
if value_expr.is_constant() {
|
||||
state.push_var(name, AccessMode::ReadOnly, value_expr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
state.push_var(
|
||||
&var_def.name,
|
||||
AccessMode::ReadWrite,
|
||||
Expr::Unit(var_def.pos),
|
||||
);
|
||||
// Add variables into the state
|
||||
Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => {
|
||||
optimize_expr(value_expr, state);
|
||||
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos));
|
||||
}
|
||||
// Optimize the statement
|
||||
_ => optimize_stmt(stmt, state, preserve_result),
|
||||
}
|
||||
// Optimize the statement
|
||||
_ => optimize_stmt(stmt, state, preserve_result),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove all raw expression statements that are pure except for the very last statement
|
||||
let last_stmt = if preserve_result {
|
||||
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)
|
||||
// Remove all pure statements that do not return values at the end of a block.
|
||||
// We cannot remove anything for non-pure statements due to potential side-effects.
|
||||
if preserve_result {
|
||||
loop {
|
||||
match &statements[..] {
|
||||
[stmt] if !stmt.returns_value() && stmt.is_internally_pure() => {
|
||||
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,
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
||||
_ => {
|
||||
statements.push(expr);
|
||||
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
|
||||
state.restore_var(orig_constants_len);
|
||||
state.propagate_constants = orig_propagate_constants;
|
||||
|
||||
if !state.is_dirty() {
|
||||
break;
|
||||
}
|
||||
|
||||
is_dirty = true;
|
||||
}
|
||||
|
||||
if preserve_result {
|
||||
if removed {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
|
||||
// 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() {
|
||||
if is_dirty {
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
// Pop the stack and remove all the local constants
|
||||
state.restore_var(orig_constants_len);
|
||||
|
||||
state.propagate_constants = orig_propagate_constants;
|
||||
|
||||
match &statements[..] {
|
||||
// No statements in block - change to No-op
|
||||
[] => {
|
||||
state.set_dirty();
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// Only one let statement - leave it alone
|
||||
[x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos),
|
||||
// Only one const statement - leave it alone
|
||||
[x] if matches!(x, Stmt::Const(_, _, _, _)) => Stmt::Block(statements, pos),
|
||||
// Only one import statement - leave it alone
|
||||
#[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();
|
||||
}
|
||||
statements.remove(0)
|
||||
}
|
||||
_ => Stmt::Block(statements, pos),
|
||||
}
|
||||
statements
|
||||
}
|
||||
|
||||
/// 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
|
||||
Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_none() => {
|
||||
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(_)) => {
|
||||
// if expr {}
|
||||
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
|
||||
state.set_dirty();
|
||||
|
||||
let pos = condition.position();
|
||||
@ -336,38 +307,56 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
|
||||
*stmt = if preserve_result {
|
||||
// -> { 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 {
|
||||
// -> expr
|
||||
Stmt::Expr(expr)
|
||||
};
|
||||
}
|
||||
// if expr { if_block }
|
||||
Stmt::If(condition, x, _) if x.1.is_none() => {
|
||||
optimize_expr(condition, state);
|
||||
optimize_stmt(&mut x.0, state, true);
|
||||
// if false { if_block } -> Noop
|
||||
Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_empty() => {
|
||||
state.set_dirty();
|
||||
*stmt = Stmt::Noop(*pos);
|
||||
}
|
||||
// if false { if_block } else { else_block } -> else_block
|
||||
Stmt::If(Expr::BoolConstant(false, _), x, _) if x.1.is_some() => {
|
||||
*stmt = mem::take(x.1.as_mut().unwrap());
|
||||
optimize_stmt(stmt, state, true);
|
||||
Stmt::If(Expr::BoolConstant(false, _), x, _) => {
|
||||
state.set_dirty();
|
||||
*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
|
||||
Stmt::If(Expr::BoolConstant(true, _), x, _) => {
|
||||
*stmt = mem::take(&mut x.0);
|
||||
optimize_stmt(stmt, state, true);
|
||||
state.set_dirty();
|
||||
*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 }
|
||||
Stmt::If(condition, x, _) => {
|
||||
optimize_expr(condition, state);
|
||||
optimize_stmt(&mut x.0, state, true);
|
||||
if let Some(else_block) = x.1.as_mut() {
|
||||
optimize_stmt(else_block, state, true);
|
||||
match else_block {
|
||||
Stmt::Noop(_) => x.1 = None, // Noop -> no else block
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
x.0.statements = optimize_stmt_block(
|
||||
mem::take(&mut x.0.statements).into_vec(),
|
||||
state,
|
||||
preserve_result,
|
||||
)
|
||||
.into();
|
||||
x.1.statements = optimize_stmt_block(
|
||||
mem::take(&mut x.1.statements).into_vec(),
|
||||
state,
|
||||
preserve_result,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
|
||||
// switch const { ... }
|
||||
@ -381,117 +370,144 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
|
||||
let table = &mut x.0;
|
||||
|
||||
if let Some(stmt) = table.get_mut(&hash) {
|
||||
optimize_stmt(stmt, state, true);
|
||||
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos);
|
||||
} else if let Some(def_stmt) = x.1.as_mut() {
|
||||
optimize_stmt(def_stmt, state, true);
|
||||
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos);
|
||||
let (statements, new_pos) = if let Some(block) = table.get_mut(&hash) {
|
||||
(
|
||||
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, true)
|
||||
.into(),
|
||||
block.pos,
|
||||
)
|
||||
} 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
|
||||
Stmt::Switch(expr, x, _) => {
|
||||
optimize_expr(expr, state);
|
||||
x.0.values_mut()
|
||||
.for_each(|stmt| optimize_stmt(stmt, state, true));
|
||||
if let Some(def_stmt) = x.1.as_mut() {
|
||||
optimize_stmt(def_stmt, state, true);
|
||||
|
||||
match def_stmt {
|
||||
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.1 = None,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
x.0.values_mut().for_each(|block| {
|
||||
block.statements = optimize_stmt_block(
|
||||
mem::take(&mut block.statements).into_vec(),
|
||||
state,
|
||||
preserve_result,
|
||||
)
|
||||
.into()
|
||||
});
|
||||
x.1.statements = optimize_stmt_block(
|
||||
mem::take(&mut x.1.statements).into_vec(),
|
||||
state,
|
||||
preserve_result,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
// while false { block } -> Noop
|
||||
Stmt::While(Some(Expr::BoolConstant(false, pos)), _, _) => {
|
||||
Stmt::While(Expr::BoolConstant(false, pos), _, _) => {
|
||||
state.set_dirty();
|
||||
*stmt = Stmt::Noop(*pos)
|
||||
}
|
||||
// while expr { block }
|
||||
Stmt::While(condition, block, _) => {
|
||||
optimize_stmt(block, state, false);
|
||||
optimize_expr(condition, state);
|
||||
|
||||
if let Some(condition) = condition {
|
||||
optimize_expr(condition, state);
|
||||
}
|
||||
block.statements =
|
||||
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false)
|
||||
.into();
|
||||
|
||||
match **block {
|
||||
// while expr { break; } -> { expr; }
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement - turn into running the guard expression once
|
||||
state.set_dirty();
|
||||
if let Some(condition) = condition {
|
||||
let mut statements = vec![Stmt::Expr(mem::take(condition))];
|
||||
if preserve_result {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
*stmt = Stmt::Block(statements, pos);
|
||||
} else {
|
||||
*stmt = Stmt::Noop(pos);
|
||||
};
|
||||
if block.len() == 1 {
|
||||
match block.statements[0] {
|
||||
// while expr { break; } -> { expr; }
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement - turn into running the guard expression once
|
||||
state.set_dirty();
|
||||
if !condition.is_unit() {
|
||||
let mut statements = vec![Stmt::Expr(mem::take(condition))];
|
||||
if preserve_result {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
*stmt = Stmt::Block(statements, pos);
|
||||
} else {
|
||||
*stmt = Stmt::Noop(pos);
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
// do { block } while false | do { block } until true -> { block }
|
||||
Stmt::Do(block, Expr::BoolConstant(true, _), false, _)
|
||||
| Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => {
|
||||
state.set_dirty();
|
||||
optimize_stmt(block.as_mut(), state, false);
|
||||
*stmt = mem::take(block.as_mut());
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false),
|
||||
block.pos,
|
||||
);
|
||||
}
|
||||
// do { block } while|until expr
|
||||
Stmt::Do(block, condition, _, _) => {
|
||||
optimize_stmt(block.as_mut(), state, false);
|
||||
optimize_expr(condition, state);
|
||||
block.statements =
|
||||
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false)
|
||||
.into();
|
||||
}
|
||||
// for id in expr { block }
|
||||
Stmt::For(iterable, x, _) => {
|
||||
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;
|
||||
Stmt::Let(_, Some(expr), _, _) => optimize_expr(expr, state),
|
||||
// let id;
|
||||
Stmt::Let(_, None, _, _) => (),
|
||||
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state),
|
||||
// import expr as var;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(expr, _, _) => optimize_expr(expr, state),
|
||||
// { block }
|
||||
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)
|
||||
}
|
||||
// Only one statement - promote
|
||||
mut statements if statements.len() == 1 => {
|
||||
state.set_dirty();
|
||||
statements.pop().unwrap()
|
||||
}
|
||||
statements => Stmt::Block(statements, *pos),
|
||||
};
|
||||
}
|
||||
// try { block } catch ( var ) { block }
|
||||
Stmt::TryCatch(x, _, _) if x.0.is_pure() => {
|
||||
// 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
|
||||
state.set_dirty();
|
||||
let pos = x.0.position();
|
||||
optimize_stmt(&mut x.0, state, preserve_result);
|
||||
let mut statements = match mem::take(&mut x.0) {
|
||||
Stmt::Block(statements, _) => statements,
|
||||
stmt => vec![stmt],
|
||||
};
|
||||
statements.push(Stmt::Noop(pos));
|
||||
*stmt = Stmt::Block(statements, pos);
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false),
|
||||
x.0.pos,
|
||||
);
|
||||
}
|
||||
// try { block } catch ( var ) { block }
|
||||
Stmt::TryCatch(x, _, _) => {
|
||||
optimize_stmt(&mut x.0, state, false);
|
||||
optimize_stmt(&mut x.2, state, false);
|
||||
x.0.statements =
|
||||
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();
|
||||
*stmt = Stmt::Noop(*pos);
|
||||
*stmt = Stmt::Noop(x.pos);
|
||||
}
|
||||
// {...};
|
||||
Stmt::Expr(Expr::Stmt(x, pos)) => {
|
||||
Stmt::Expr(Expr::Stmt(x)) => {
|
||||
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;
|
||||
Stmt::Expr(expr) => optimize_expr(expr, state),
|
||||
@ -514,25 +530,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
|
||||
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
|
||||
Expr::Stmt(x, pos) => match optimize_stmt_block(mem::take(x).into_vec(), *pos, state, true, false) {
|
||||
// {}
|
||||
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),
|
||||
}
|
||||
|
||||
Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true).into(),
|
||||
// lhs.rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||
// map.string
|
||||
(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.
|
||||
// All other items can be thrown away.
|
||||
state.set_dirty();
|
||||
@ -598,32 +604,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
// #{ key:value, .. }
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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
|
||||
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||
// true && rhs -> rhs
|
||||
@ -684,7 +664,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||
&& x.args.len() == 2 // binary call
|
||||
&& 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 arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||
@ -783,16 +763,16 @@ fn optimize_top_level(
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
lib: &[&Module],
|
||||
level: OptimizationLevel,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Vec<Stmt> {
|
||||
// If optimization level is None then skip optimizing
|
||||
if level == OptimizationLevel::None {
|
||||
if optimization_level == OptimizationLevel::None {
|
||||
statements.shrink_to_fit();
|
||||
return statements;
|
||||
}
|
||||
|
||||
// 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
|
||||
scope.iter().for_each(|(name, constant, value)| {
|
||||
@ -816,34 +796,17 @@ fn optimize_top_level(
|
||||
|
||||
statements.iter_mut().enumerate().for_each(|(i, stmt)| {
|
||||
match stmt {
|
||||
Stmt::Const(var_def, expr, _, _) if expr.is_some() => {
|
||||
Stmt::Const(value_expr, Ident { name, .. }, _, _) => {
|
||||
// Load constants
|
||||
let value_expr = expr.as_mut().unwrap();
|
||||
optimize_expr(value_expr, &mut state);
|
||||
|
||||
if value_expr.is_constant() {
|
||||
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone());
|
||||
}
|
||||
|
||||
// Keep it in the global scope
|
||||
if value_expr.is_unit() {
|
||||
state.set_dirty();
|
||||
*expr = None;
|
||||
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));
|
||||
}
|
||||
Stmt::Let(var_def, expr, _, _) => {
|
||||
if let Some(value_expr) = expr {
|
||||
optimize_expr(value_expr, &mut state);
|
||||
}
|
||||
|
||||
state.push_var(
|
||||
&var_def.name,
|
||||
AccessMode::ReadWrite,
|
||||
Expr::Unit(var_def.pos),
|
||||
);
|
||||
Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => {
|
||||
optimize_expr(value_expr, &mut state);
|
||||
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos));
|
||||
}
|
||||
_ => {
|
||||
// Keep all variable declarations at this level
|
||||
@ -852,7 +815,7 @@ fn optimize_top_level(
|
||||
Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Import(_, _, _) => true,
|
||||
_ => i == num_statements - 1,
|
||||
_ => i >= num_statements - 1,
|
||||
};
|
||||
optimize_stmt(stmt, &mut state, keep);
|
||||
}
|
||||
@ -887,12 +850,12 @@ pub fn optimize_into_ast(
|
||||
scope: &Scope,
|
||||
mut statements: Vec<Stmt>,
|
||||
_functions: Vec<crate::ast::ScriptFnDef>,
|
||||
level: OptimizationLevel,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> AST {
|
||||
let level = if cfg!(feature = "no_optimize") {
|
||||
OptimizationLevel::None
|
||||
} else {
|
||||
level
|
||||
optimization_level
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -921,33 +884,49 @@ pub fn optimize_into_ast(
|
||||
lib2.set_script_fn(fn_def);
|
||||
});
|
||||
|
||||
let lib2 = &[&lib2];
|
||||
|
||||
_functions
|
||||
.into_iter()
|
||||
.map(|mut fn_def| {
|
||||
let pos = fn_def.body.position();
|
||||
let pos = fn_def.body.pos;
|
||||
|
||||
// Optimize the function body
|
||||
let mut body = optimize_top_level(
|
||||
vec![fn_def.body],
|
||||
engine,
|
||||
&Scope::new(),
|
||||
&[&lib2],
|
||||
level,
|
||||
);
|
||||
let mut body = fn_def.body.statements.into_vec();
|
||||
|
||||
// {} -> Noop
|
||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||
// { return val; } -> val
|
||||
Stmt::Return((crate::ast::ReturnType::Return, _), Some(expr), _) => {
|
||||
Stmt::Expr(expr)
|
||||
loop {
|
||||
// Optimize the function body
|
||||
let state = &mut State::new(engine, lib2, level);
|
||||
|
||||
body = optimize_stmt_block(body, state, true);
|
||||
|
||||
match &mut body[..] {
|
||||
// { return; } -> {}
|
||||
[Stmt::Return(crate::ast::ReturnType::Return, None, _)] => {
|
||||
body.clear();
|
||||
}
|
||||
// { ...; return; } -> { ... }
|
||||
[.., last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)]
|
||||
if !last_stmt.returns_value() =>
|
||||
{
|
||||
body.pop().unwrap();
|
||||
}
|
||||
// { ...; return val; } -> { ...; val }
|
||||
[.., 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,
|
||||
}
|
||||
// { return; } -> ()
|
||||
Stmt::Return((crate::ast::ReturnType::Return, pos), None, _) => {
|
||||
Stmt::Expr(Expr::Unit(pos))
|
||||
}
|
||||
// All others
|
||||
stmt => stmt,
|
||||
}
|
||||
|
||||
fn_def.body = StmtBlock {
|
||||
statements: body.into(),
|
||||
pos,
|
||||
};
|
||||
|
||||
fn_def
|
||||
})
|
||||
.for_each(|fn_def| {
|
||||
|
@ -164,7 +164,7 @@ mod array_functions {
|
||||
result
|
||||
}
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn map(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -197,7 +197,7 @@ mod array_functions {
|
||||
|
||||
Ok(ar.into())
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn filter(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -233,8 +233,70 @@ mod array_functions {
|
||||
|
||||
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(
|
||||
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,
|
||||
array: &mut Array,
|
||||
filter: FnPtr,
|
||||
@ -267,7 +329,7 @@ mod array_functions {
|
||||
|
||||
Ok((-1 as INT).into())
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn some(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -301,7 +363,7 @@ mod array_functions {
|
||||
|
||||
Ok(false.into())
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn all(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -335,7 +397,7 @@ mod array_functions {
|
||||
|
||||
Ok(true.into())
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn reduce(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -366,7 +428,7 @@ mod array_functions {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
#[rhai_fn(name = "reduce", return_raw)]
|
||||
#[rhai_fn(name = "reduce", return_raw, pure)]
|
||||
pub fn reduce_with_initial(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -405,7 +467,7 @@ mod array_functions {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
#[rhai_fn(return_raw, pure)]
|
||||
pub fn reduce_rev(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -436,7 +498,7 @@ mod array_functions {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
#[rhai_fn(name = "reduce_rev", return_raw)]
|
||||
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
|
||||
pub fn reduce_rev_with_initial(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -634,7 +696,7 @@ mod array_functions {
|
||||
|
||||
drained
|
||||
}
|
||||
#[rhai_fn(name = "==", return_raw)]
|
||||
#[rhai_fn(name = "==", return_raw, pure)]
|
||||
pub fn equals(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
@ -648,18 +710,31 @@ mod array_functions {
|
||||
}
|
||||
|
||||
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])
|
||||
.map(|v| v.as_bool().unwrap_or(false))?;
|
||||
|
||||
if !equals {
|
||||
return Ok(false.into());
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
|
||||
if fn_sig.starts_with(OP_EQUALS) =>
|
||||
{
|
||||
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(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
|
@ -13,8 +13,8 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
||||
|
||||
#[export_module]
|
||||
mod map_functions {
|
||||
#[rhai_fn(pure)]
|
||||
pub fn has(map: &mut Map, prop: ImmutableString) -> bool {
|
||||
#[rhai_fn(name = "has", pure)]
|
||||
pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
|
||||
map.contains_key(&prop)
|
||||
}
|
||||
#[rhai_fn(pure)]
|
||||
|
@ -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")]
|
||||
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
|
||||
let start = if start < 0 {
|
||||
|
@ -38,6 +38,7 @@ pub enum LexError {
|
||||
impl Error for LexError {}
|
||||
|
||||
impl fmt::Display for LexError {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
|
||||
@ -293,7 +294,7 @@ pub struct ParseError(pub Box<ParseErrorType>, pub Position);
|
||||
impl Error for ParseError {}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)?;
|
||||
|
||||
|
270
src/parser.rs
270
src/parser.rs
@ -2,10 +2,10 @@
|
||||
|
||||
use crate::ast::{
|
||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef,
|
||||
Stmt,
|
||||
Stmt, StmtBlock,
|
||||
};
|
||||
use crate::dynamic::{AccessMode, Union};
|
||||
use crate::engine::KEYWORD_THIS;
|
||||
use crate::engine::{KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::optimize::optimize_into_ast;
|
||||
use crate::optimize::OptimizationLevel;
|
||||
@ -243,7 +243,11 @@ impl Expr {
|
||||
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
||||
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,
|
||||
}
|
||||
@ -480,7 +484,6 @@ fn parse_index_chain(
|
||||
Expr::CharConstant(_, _)
|
||||
| Expr::And(_, _)
|
||||
| Expr::Or(_, _)
|
||||
| Expr::In(_, _)
|
||||
| Expr::BoolConstant(_, _)
|
||||
| Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
@ -514,7 +517,6 @@ fn parse_index_chain(
|
||||
Expr::CharConstant(_, _)
|
||||
| Expr::And(_, _)
|
||||
| Expr::Or(_, _)
|
||||
| Expr::In(_, _)
|
||||
| Expr::BoolConstant(_, _)
|
||||
| Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
@ -548,8 +550,8 @@ fn parse_index_chain(
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???]
|
||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) | x @ Expr::In(_, _) => {
|
||||
// lhs[??? && ???], lhs[??? || ???]
|
||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"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;
|
||||
|
||||
loop {
|
||||
@ -871,10 +873,10 @@ fn parse_switch(
|
||||
let need_comma = !stmt.is_self_terminated();
|
||||
|
||||
def_stmt = if let Some(hash) = hash {
|
||||
table.insert(hash, stmt);
|
||||
table.insert(hash, stmt.into());
|
||||
None
|
||||
} else {
|
||||
Some(stmt)
|
||||
Some(stmt.into())
|
||||
};
|
||||
|
||||
match input.peek().unwrap() {
|
||||
@ -905,7 +907,10 @@ fn parse_switch(
|
||||
|
||||
Ok(Stmt::Switch(
|
||||
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,
|
||||
))
|
||||
}
|
||||
@ -956,7 +961,7 @@ fn parse_primary(
|
||||
// { - block statement as expression
|
||||
Token::LeftBrace if settings.allow_stmt_expr => {
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -964,15 +969,14 @@ fn parse_primary(
|
||||
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
||||
|
||||
// If statement is allowed to act as expressions
|
||||
Token::If if settings.allow_if_expr => Expr::Stmt(
|
||||
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
|
||||
settings.pos,
|
||||
),
|
||||
Token::If if settings.allow_if_expr => Expr::Stmt(Box::new(
|
||||
parse_if(input, state, lib, settings.level_up())?.into(),
|
||||
)),
|
||||
// Switch statement is allowed to act as expressions
|
||||
Token::Switch if settings.allow_switch_expr => Expr::Stmt(
|
||||
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
|
||||
settings.pos,
|
||||
),
|
||||
Token::Switch if settings.allow_switch_expr => Expr::Stmt(Box::new(
|
||||
parse_switch(input, state, lib, settings.level_up())?.into(),
|
||||
)),
|
||||
|
||||
// | ...
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
||||
@ -1149,14 +1153,14 @@ fn parse_primary(
|
||||
.into_err(pos));
|
||||
}
|
||||
|
||||
let (_, namespace, Ident { name, pos }) = *x;
|
||||
let (_, namespace, Ident { name, pos, .. }) = *x;
|
||||
settings.pos = pos;
|
||||
let ns = namespace.map(|(_, ns)| ns);
|
||||
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
||||
}
|
||||
// Function call
|
||||
(Expr::Variable(x), Token::LeftParen) => {
|
||||
let (_, namespace, Ident { name, pos }) = *x;
|
||||
let (_, namespace, Ident { name, pos, .. }) = *x;
|
||||
settings.pos = pos;
|
||||
let ns = namespace.map(|(_, ns)| ns);
|
||||
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
||||
@ -1397,7 +1401,7 @@ fn make_assignment_stmt<'a>(
|
||||
}
|
||||
// var (indexed) = rhs
|
||||
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 {
|
||||
AccessMode::ReadWrite => {
|
||||
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
|
||||
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 {
|
||||
AccessMode::ReadWrite => {
|
||||
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 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)
|
||||
}
|
||||
@ -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.
|
||||
fn parse_binary_op(
|
||||
input: &mut TokenStream,
|
||||
@ -1880,9 +1751,21 @@ fn parse_binary_op(
|
||||
)
|
||||
}
|
||||
Token::In => {
|
||||
let rhs = args.pop().unwrap();
|
||||
let current_lhs = args.pop().unwrap();
|
||||
make_in_expr(current_lhs, rhs, pos)?
|
||||
// Swap the arguments
|
||||
let current_lhs = args.remove(0);
|
||||
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)
|
||||
@ -1984,8 +1867,8 @@ fn parse_custom_syntax(
|
||||
tokens.push(keyword);
|
||||
}
|
||||
MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
|
||||
Stmt::Block(statements, pos) => {
|
||||
keywords.push(Expr::Stmt(Box::new(statements.into()), pos));
|
||||
block @ Stmt::Block(_, _) => {
|
||||
keywords.push(Expr::Stmt(Box::new(block.into())));
|
||||
let keyword = state.get_interned_string(MARKER_BLOCK);
|
||||
segments.push(keyword.clone());
|
||||
tokens.push(keyword);
|
||||
@ -2118,20 +2001,20 @@ fn parse_if(
|
||||
|
||||
// if guard { if_body } else ...
|
||||
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 ...
|
||||
parse_if(input, state, lib, settings.level_up())?
|
||||
} else {
|
||||
// if guard { if_body } else { else-body }
|
||||
parse_block(input, state, lib, settings.level_up())?
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
Stmt::Noop(Position::NONE)
|
||||
};
|
||||
|
||||
Ok(Stmt::If(
|
||||
guard,
|
||||
Box::new((if_body, else_body)),
|
||||
Box::new((if_body.into(), else_body.into())),
|
||||
settings.pos,
|
||||
))
|
||||
}
|
||||
@ -2151,18 +2034,18 @@ fn parse_while_loop(
|
||||
(Token::While, pos) => {
|
||||
ensure_not_statement_expr(input, "a boolean")?;
|
||||
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!(),
|
||||
};
|
||||
settings.pos = token_pos;
|
||||
|
||||
ensure_not_assignment(input)?;
|
||||
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.
|
||||
@ -2180,7 +2063,7 @@ fn parse_do(
|
||||
|
||||
// do { body } [while|until] guard
|
||||
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() {
|
||||
(Token::While, _) => true,
|
||||
@ -2198,7 +2081,12 @@ fn parse_do(
|
||||
let guard = parse_expr(input, state, lib, settings.level_up())?;
|
||||
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.
|
||||
@ -2253,7 +2141,7 @@ fn parse_for(
|
||||
|
||||
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.
|
||||
@ -2290,18 +2178,18 @@ fn parse_let(
|
||||
// let name = ...
|
||||
let expr = if match_token(input, Token::Equals).0 {
|
||||
// let name = expr
|
||||
Some(parse_expr(input, state, lib, settings.level_up())?)
|
||||
parse_expr(input, state, lib, settings.level_up())?
|
||||
} else {
|
||||
None
|
||||
Expr::Unit(Position::NONE)
|
||||
};
|
||||
|
||||
state.stack.push((name, var_type));
|
||||
|
||||
match var_type {
|
||||
// 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 }
|
||||
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(
|
||||
expr,
|
||||
Some(Box::new(Ident {
|
||||
Some(Ident {
|
||||
name,
|
||||
pos: name_pos,
|
||||
})),
|
||||
}),
|
||||
settings.pos,
|
||||
))
|
||||
}
|
||||
@ -2542,7 +2430,7 @@ fn parse_stmt(
|
||||
) -> Result<Stmt, ParseError> {
|
||||
use AccessMode::{ReadOnly, ReadWrite};
|
||||
|
||||
let mut _comments: Vec<String> = Default::default();
|
||||
let mut _comments: StaticVec<String> = Default::default();
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
@ -2688,16 +2576,13 @@ fn parse_stmt(
|
||||
|
||||
match input.peek().unwrap() {
|
||||
// `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;`
|
||||
(Token::SemiColon, _) => {
|
||||
Ok(Stmt::Return((return_type, token_pos), None, settings.pos))
|
||||
}
|
||||
(Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
||||
// `return` or `throw` with expression
|
||||
(_, _) => {
|
||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||
let pos = expr.position();
|
||||
Ok(Stmt::Return((return_type, token_pos), Some(expr), pos))
|
||||
Ok(Stmt::Return(return_type, Some(expr), token_pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2775,7 +2660,7 @@ fn parse_try_catch(
|
||||
let catch_body = parse_block(input, state, lib, settings.level_up())?;
|
||||
|
||||
Ok(Stmt::TryCatch(
|
||||
Box::new((body, var_def, catch_body)),
|
||||
Box::new((body.into(), var_def, catch_body.into())),
|
||||
settings.pos,
|
||||
catch_pos,
|
||||
))
|
||||
@ -2789,7 +2674,7 @@ fn parse_fn(
|
||||
lib: &mut FunctionsLib,
|
||||
access: FnAccess,
|
||||
mut settings: ParseSettings,
|
||||
comments: Vec<String>,
|
||||
comments: StaticVec<String>,
|
||||
) -> Result<ScriptFnDef, ParseError> {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
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())?
|
||||
}
|
||||
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
|
||||
};
|
||||
}
|
||||
.into();
|
||||
|
||||
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();
|
||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
||||
statements.push(Stmt::Expr(expr));
|
||||
Expr::Stmt(Box::new(statements), pos)
|
||||
Expr::Stmt(Box::new(StmtBlock { statements, pos }))
|
||||
}
|
||||
|
||||
/// Parse an anonymous function definition.
|
||||
@ -3016,7 +2902,7 @@ fn parse_anon_fn(
|
||||
params,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals: Default::default(),
|
||||
body,
|
||||
body: body.into(),
|
||||
lib: None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
mods: Default::default(),
|
||||
|
@ -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
|
||||
/// indexer function defined. Wrapped value is the type name.
|
||||
ErrorIndexingType(String, Position),
|
||||
/// Invalid arguments for `in` operator.
|
||||
ErrorInExpr(Position),
|
||||
/// The `for` statement encounters a type that is not an iterator.
|
||||
ErrorFor(Position),
|
||||
/// 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::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant",
|
||||
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
|
||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||
Self::ErrorTooManyOperations(_) => "Too many operations",
|
||||
@ -182,7 +179,6 @@ impl fmt::Display for EvalAltResult {
|
||||
|
||||
Self::ErrorUnboundThis(_)
|
||||
| Self::ErrorFor(_)
|
||||
| Self::ErrorInExpr(_)
|
||||
| Self::ErrorDotExpr(_, _)
|
||||
| Self::ErrorTooManyOperations(_)
|
||||
| Self::ErrorTooManyModules(_)
|
||||
@ -305,7 +301,6 @@ impl EvalAltResult {
|
||||
| Self::ErrorDataRace(_, _)
|
||||
| Self::ErrorAssignmentToConstant(_, _)
|
||||
| Self::ErrorMismatchOutputType(_, _, _)
|
||||
| Self::ErrorInExpr(_)
|
||||
| Self::ErrorDotExpr(_, _)
|
||||
| Self::ErrorArithmetic(_, _)
|
||||
| Self::ErrorRuntime(_, _) => true,
|
||||
@ -362,7 +357,6 @@ impl EvalAltResult {
|
||||
| Self::ErrorParsing(_, _)
|
||||
| Self::ErrorUnboundThis(_)
|
||||
| Self::ErrorFor(_)
|
||||
| Self::ErrorInExpr(_)
|
||||
| Self::ErrorArithmetic(_, _)
|
||||
| Self::ErrorTooManyOperations(_)
|
||||
| Self::ErrorTooManyModules(_)
|
||||
@ -430,7 +424,6 @@ impl EvalAltResult {
|
||||
| Self::ErrorDataRace(_, pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorTooManyOperations(pos)
|
||||
@ -471,7 +464,6 @@ impl EvalAltResult {
|
||||
| Self::ErrorDataRace(_, pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorTooManyOperations(pos)
|
||||
|
@ -151,7 +151,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
|
||||
}
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
info.func.get_fn_def().comments.clone()
|
||||
info.func.get_fn_def().comments.to_vec()
|
||||
}
|
||||
} else {
|
||||
Default::default()
|
||||
|
@ -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>);
|
||||
|
||||
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 {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
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> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
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> {
|
||||
&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> {
|
||||
type Target = HashMap<K, T, H>;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<K, T, H: BuildHasher> DerefMut for HashableHashMap<K, T, H> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
impl<K: Debug, T: Debug, H: BuildHasher> Debug for HashableHashMap<K, T, H> {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl<K: Hash + Ord, T: Hash, H: BuildHasher> Hash for HashableHashMap<K, T, H> {
|
||||
#[inline(always)]
|
||||
fn hash<B: Hasher>(&self, state: &mut B) {
|
||||
let mut keys: Vec<_> = self.0.keys().collect();
|
||||
keys.sort();
|
||||
|
@ -38,7 +38,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
||||
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>("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_eq!(
|
||||
|
@ -55,20 +55,18 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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 }")?;
|
||||
|
||||
assert!(
|
||||
format!("{:?}", ast).starts_with("AST { source: None, statements: [], functions: Module(")
|
||||
);
|
||||
assert!(format!("{:?}", ast).starts_with("AST { source: None, body: [], functions: Module("));
|
||||
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
|
||||
let ast = engine.compile("abs(-42)")?;
|
||||
|
||||
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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user