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
|
Version 0.19.14
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
This version runs faster due to optimizations done on AST node structures. It also fixes a number of
|
||||||
|
panic bugs related to passing shared values as function call arguments.
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@ -27,18 +30,24 @@ Breaking changes
|
|||||||
* `Module::update_fn_metadata` input parameter is changed.
|
* `Module::update_fn_metadata` input parameter is changed.
|
||||||
* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`.
|
* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`.
|
||||||
* `is_def_var` and `is_def_fn` are now reserved keywords.
|
* `is_def_var` and `is_def_fn` are now reserved keywords.
|
||||||
* `Engine::id` field is removed.
|
* `Engine::id` field is removed because it is never used.
|
||||||
* `num-traits` is now a required dependency.
|
* `num-traits` is now a required dependency.
|
||||||
|
* The `in` operator is now implemented on top of the `contains` function and is no longer restricted to a few specific types.
|
||||||
|
* `EvalAltResult::ErrorInExpr` is removed because the `in` operator now calls `contains`.
|
||||||
|
* The methods `AST::walk`, `Expr::walk`, `Stmt::walk` and `ASTNode::walk` and the callbacks they take now return `bool` to optionally terminate the recursive walk.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* Layout of AST nodes is optimized to reduce redirections, so speed is improved.
|
||||||
* Function calls are more optimized and should now run faster.
|
* Function calls are more optimized and should now run faster.
|
||||||
* `range` function now supports negative step and decreasing streams (i.e. to < from).
|
* `range` function now supports negative step and decreasing streams (i.e. to < from).
|
||||||
* More information is provided to the error variable captured by the `catch` statement in an _object map_.
|
* More information is provided to the error variable captured by the `catch` statement in an _object map_.
|
||||||
* Previously, `private` functions in an `AST` cannot be called with `call_fn` etc. This is inconvenient when trying to call a function inside a script which also serves as a loadable module exporting part (but not all) of the functions. Now, all functions (`private` or not) can be called in an `AST`. The `private` keyword is relegated to preventing a function from being exported.
|
* Previously, `private` functions in an `AST` cannot be called with `call_fn` etc. This is inconvenient when trying to call a function inside a script which also serves as a loadable module exporting part (but not all) of the functions. Now, all functions (`private` or not) can be called in an `AST`. The `private` keyword is relegated to preventing a function from being exported.
|
||||||
* `Dynamic::as_unit` just for completeness sake.
|
* `Dynamic::as_unit` just for completeness sake.
|
||||||
* `bytes` method added for strings to get length quickly (if the string is ASCII-only).
|
* `bytes` method added for strings to get length quickly (if the string is ASCII-only).
|
||||||
|
* `FileModuleResolver` can now enable/disable caching.
|
||||||
|
* Recursively walking an `AST` can now be terminated in the middle.
|
||||||
|
|
||||||
|
|
||||||
Version 0.19.13
|
Version 0.19.13
|
||||||
|
547
src/ast.rs
547
src/ast.rs
@ -47,7 +47,7 @@ pub enum FnAccess {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ScriptFnDef {
|
pub struct ScriptFnDef {
|
||||||
/// Function body.
|
/// Function body.
|
||||||
pub body: Stmt,
|
pub body: StmtBlock,
|
||||||
/// Encapsulated running environment, if any.
|
/// Encapsulated running environment, if any.
|
||||||
pub lib: Option<Shared<Module>>,
|
pub lib: Option<Shared<Module>>,
|
||||||
/// Encapsulated imported modules.
|
/// Encapsulated imported modules.
|
||||||
@ -61,9 +61,9 @@ pub struct ScriptFnDef {
|
|||||||
pub params: StaticVec<ImmutableString>,
|
pub params: StaticVec<ImmutableString>,
|
||||||
/// Access to external variables.
|
/// Access to external variables.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub externals: Vec<ImmutableString>,
|
pub externals: StaticVec<ImmutableString>,
|
||||||
/// Function doc-comments (if any).
|
/// Function doc-comments (if any).
|
||||||
pub comments: Vec<String>,
|
pub comments: StaticVec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ScriptFnDef {
|
impl fmt::Display for ScriptFnDef {
|
||||||
@ -149,7 +149,7 @@ pub struct AST {
|
|||||||
/// Source of the [`AST`].
|
/// Source of the [`AST`].
|
||||||
source: Option<ImmutableString>,
|
source: Option<ImmutableString>,
|
||||||
/// Global statements.
|
/// Global statements.
|
||||||
statements: Vec<Stmt>,
|
body: StmtBlock,
|
||||||
/// Script-defined functions.
|
/// Script-defined functions.
|
||||||
functions: Shared<Module>,
|
functions: Shared<Module>,
|
||||||
/// Embedded module resolver, if any.
|
/// Embedded module resolver, if any.
|
||||||
@ -162,7 +162,7 @@ impl Default for AST {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
statements: Vec::with_capacity(16),
|
body: Default::default(),
|
||||||
functions: Default::default(),
|
functions: Default::default(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
@ -179,7 +179,10 @@ impl AST {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
statements: statements.into_iter().collect(),
|
body: StmtBlock {
|
||||||
|
statements: statements.into_iter().collect(),
|
||||||
|
pos: Position::NONE,
|
||||||
|
},
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
@ -194,7 +197,10 @@ impl AST {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Some(source.into()),
|
source: Some(source.into()),
|
||||||
statements: statements.into_iter().collect(),
|
body: StmtBlock {
|
||||||
|
statements: statements.into_iter().collect(),
|
||||||
|
pos: Position::NONE,
|
||||||
|
},
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: None,
|
resolver: None,
|
||||||
@ -231,7 +237,7 @@ impl AST {
|
|||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn statements(&self) -> &[Stmt] {
|
pub(crate) fn statements(&self) -> &[Stmt] {
|
||||||
&self.statements
|
&self.body.statements
|
||||||
}
|
}
|
||||||
/// _(INTERNALS)_ Get the statements.
|
/// _(INTERNALS)_ Get the statements.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
@ -239,13 +245,13 @@ impl AST {
|
|||||||
#[deprecated = "this method is volatile and may change"]
|
#[deprecated = "this method is volatile and may change"]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn statements(&self) -> &[Stmt] {
|
pub fn statements(&self) -> &[Stmt] {
|
||||||
&self.statements
|
&self.body.statements
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the statements.
|
/// Get a mutable reference to the statements.
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn statements_mut(&mut self) -> &mut Vec<Stmt> {
|
pub(crate) fn statements_mut(&mut self) -> &mut StaticVec<Stmt> {
|
||||||
&mut self.statements
|
&mut self.body.statements
|
||||||
}
|
}
|
||||||
/// Get the internal shared [`Module`] containing all script-defined functions.
|
/// Get the internal shared [`Module`] containing all script-defined functions.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
@ -333,7 +339,7 @@ impl AST {
|
|||||||
functions.merge_filtered(&self.functions, &filter);
|
functions.merge_filtered(&self.functions, &filter);
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
statements: Default::default(),
|
body: Default::default(),
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
@ -345,7 +351,7 @@ impl AST {
|
|||||||
pub fn clone_statements_only(&self) -> Self {
|
pub fn clone_statements_only(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
statements: self.statements.clone(),
|
body: self.body.clone(),
|
||||||
functions: Default::default(),
|
functions: Default::default(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
resolver: self.resolver.clone(),
|
resolver: self.resolver.clone(),
|
||||||
@ -515,20 +521,19 @@ impl AST {
|
|||||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let Self {
|
let Self {
|
||||||
statements,
|
body, functions, ..
|
||||||
functions,
|
|
||||||
..
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let ast = match (statements.is_empty(), other.statements.is_empty()) {
|
let merged = match (body.is_empty(), other.body.is_empty()) {
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
let mut statements = statements.clone();
|
let mut body = body.clone();
|
||||||
statements.extend(other.statements.iter().cloned());
|
body.statements
|
||||||
statements
|
.extend(other.body.statements.iter().cloned());
|
||||||
|
body
|
||||||
}
|
}
|
||||||
(false, true) => statements.clone(),
|
(false, true) => body.clone(),
|
||||||
(true, false) => other.statements.clone(),
|
(true, false) => other.body.clone(),
|
||||||
(true, true) => vec![],
|
(true, true) => Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = other.source.clone().or_else(|| self.source.clone());
|
let source = other.source.clone().or_else(|| self.source.clone());
|
||||||
@ -537,9 +542,9 @@ impl AST {
|
|||||||
functions.merge_filtered(&other.functions, &filter);
|
functions.merge_filtered(&other.functions, &filter);
|
||||||
|
|
||||||
if let Some(source) = source {
|
if let Some(source) = source {
|
||||||
Self::new_with_source(ast, functions, source)
|
Self::new_with_source(merged.statements, functions, source)
|
||||||
} else {
|
} else {
|
||||||
Self::new(ast, functions)
|
Self::new(merged.statements, functions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||||
@ -599,7 +604,10 @@ impl AST {
|
|||||||
other: Self,
|
other: Self,
|
||||||
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.statements.extend(other.statements.into_iter());
|
self.body
|
||||||
|
.statements
|
||||||
|
.extend(other.body.statements.into_iter());
|
||||||
|
|
||||||
if !other.functions.is_empty() {
|
if !other.functions.is_empty() {
|
||||||
shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter);
|
shared_make_mut(&mut self.functions).merge_filtered(&other.functions, &filter);
|
||||||
}
|
}
|
||||||
@ -673,45 +681,51 @@ impl AST {
|
|||||||
/// Clear all statements in the [`AST`], leaving only function definitions.
|
/// Clear all statements in the [`AST`], leaving only function definitions.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clear_statements(&mut self) {
|
pub fn clear_statements(&mut self) {
|
||||||
self.statements = vec![];
|
self.body = Default::default();
|
||||||
}
|
}
|
||||||
/// Recursively walk the [`AST`], including function bodies (if any).
|
/// Recursively walk the [`AST`], including function bodies (if any).
|
||||||
|
/// Return `false` from the callback to terminate the walk.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode])) {
|
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||||
self.statements()
|
let path = &mut Default::default();
|
||||||
.iter()
|
|
||||||
.chain({
|
for stmt in self.statements() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
if !stmt.walk(path, on_node) {
|
||||||
{
|
return false;
|
||||||
self.iter_fn_def().map(|f| &f.body)
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
|
||||||
crate::stdlib::iter::empty()
|
if !stmt.walk(path, on_node) {
|
||||||
}
|
return false;
|
||||||
})
|
}
|
||||||
.for_each(|stmt| stmt.walk(&mut Default::default(), on_node));
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
/// _(INTERNALS)_ Recursively walk the [`AST`], including function bodies (if any).
|
/// _(INTERNALS)_ Recursively walk the [`AST`], including function bodies (if any).
|
||||||
|
/// Return `false` from the callback to terminate the walk.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode])) {
|
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
|
||||||
self.statements()
|
let path = &mut Default::default();
|
||||||
.iter()
|
|
||||||
.chain({
|
for stmt in self.statements() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
if !stmt.walk(path, on_node) {
|
||||||
{
|
return false;
|
||||||
self.iter_fn_def().map(|f| &f.body)
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) {
|
||||||
crate::stdlib::iter::empty()
|
if !stmt.walk(path, on_node) {
|
||||||
}
|
return false;
|
||||||
})
|
}
|
||||||
.for_each(|stmt| stmt.walk(&mut Default::default(), on_node));
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -804,6 +818,41 @@ impl<'a> From<&'a Expr> for ASTNode<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// _(INTERNALS)_ A statements block.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// # Volatile Data Structure
|
||||||
|
///
|
||||||
|
/// This type is volatile and may change.
|
||||||
|
#[derive(Clone, Hash, Default)]
|
||||||
|
pub struct StmtBlock {
|
||||||
|
pub statements: StaticVec<Stmt>,
|
||||||
|
pub pos: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StmtBlock {
|
||||||
|
/// Is this statements block empty?
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.statements.is_empty()
|
||||||
|
}
|
||||||
|
/// Number of statements in this statements block.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.statements.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for StmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if !self.pos.is_none() {
|
||||||
|
write!(f, "{} @ ", self.pos)?;
|
||||||
|
}
|
||||||
|
fmt::Debug::fmt(&self.statements, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// _(INTERNALS)_ A statement.
|
/// _(INTERNALS)_ A statement.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
@ -815,32 +864,36 @@ pub enum Stmt {
|
|||||||
/// No-op.
|
/// No-op.
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
/// `if` expr `{` stmt `}` `else` `{` stmt `}`
|
/// `if` expr `{` stmt `}` `else` `{` stmt `}`
|
||||||
If(Expr, Box<(Stmt, Option<Stmt>)>, Position),
|
If(Expr, Box<(StmtBlock, StmtBlock)>, Position),
|
||||||
/// `switch` expr `{` literal or _ `=>` stmt `,` ... `}`
|
/// `switch` expr `{` literal or _ `=>` stmt `,` ... `}`
|
||||||
Switch(
|
Switch(
|
||||||
Expr,
|
Expr,
|
||||||
Box<(
|
Box<(
|
||||||
HashableHashMap<u64, Stmt, StraightHasherBuilder>,
|
HashableHashMap<u64, StmtBlock, StraightHasherBuilder>,
|
||||||
Option<Stmt>,
|
StmtBlock,
|
||||||
)>,
|
)>,
|
||||||
Position,
|
Position,
|
||||||
),
|
),
|
||||||
/// `while` expr `{` stmt `}`
|
/// `while` expr `{` stmt `}`
|
||||||
While(Option<Expr>, Box<Stmt>, Position),
|
While(Expr, Box<StmtBlock>, Position),
|
||||||
/// `do` `{` stmt `}` `while`|`until` expr
|
/// `do` `{` stmt `}` `while`|`until` expr
|
||||||
Do(Box<Stmt>, Expr, bool, Position),
|
Do(Box<StmtBlock>, Expr, bool, Position),
|
||||||
/// `for` id `in` expr `{` stmt `}`
|
/// `for` id `in` expr `{` stmt `}`
|
||||||
For(Expr, Box<(String, Stmt)>, Position),
|
For(Expr, Box<(String, StmtBlock)>, Position),
|
||||||
/// \[`export`\] `let` id `=` expr
|
/// \[`export`\] `let` id `=` expr
|
||||||
Let(Box<Ident>, Option<Expr>, bool, Position),
|
Let(Expr, Ident, bool, Position),
|
||||||
/// \[`export`\] `const` id `=` expr
|
/// \[`export`\] `const` id `=` expr
|
||||||
Const(Box<Ident>, Option<Expr>, bool, Position),
|
Const(Expr, Ident, bool, Position),
|
||||||
/// expr op`=` expr
|
/// expr op`=` expr
|
||||||
Assignment(Box<(Expr, Expr, Option<OpAssignment>)>, Position),
|
Assignment(Box<(Expr, Expr, Option<OpAssignment>)>, Position),
|
||||||
/// `{` stmt`;` ... `}`
|
/// `{` stmt`;` ... `}`
|
||||||
Block(Vec<Stmt>, Position),
|
Block(Vec<Stmt>, Position),
|
||||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||||
TryCatch(Box<(Stmt, Option<Ident>, Stmt)>, Position, Position),
|
TryCatch(
|
||||||
|
Box<(StmtBlock, Option<Ident>, StmtBlock)>,
|
||||||
|
Position,
|
||||||
|
Position,
|
||||||
|
),
|
||||||
/// [expression][Expr]
|
/// [expression][Expr]
|
||||||
Expr(Expr),
|
Expr(Expr),
|
||||||
/// `continue`
|
/// `continue`
|
||||||
@ -848,10 +901,10 @@ pub enum Stmt {
|
|||||||
/// `break`
|
/// `break`
|
||||||
Break(Position),
|
Break(Position),
|
||||||
/// `return`/`throw`
|
/// `return`/`throw`
|
||||||
Return((ReturnType, Position), Option<Expr>, Position),
|
Return(ReturnType, Option<Expr>, Position),
|
||||||
/// `import` expr `as` var
|
/// `import` expr `as` var
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import(Expr, Option<Box<Ident>>, Position),
|
Import(Expr, Option<Ident>, Position),
|
||||||
/// `export` var `as` var `,` ...
|
/// `export` var `as` var `,` ...
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export(Vec<(Ident, Option<Ident>)>, Position),
|
Export(Vec<(Ident, Option<Ident>)>, Position),
|
||||||
@ -867,6 +920,27 @@ impl Default for Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Stmt> for StmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(stmt: Stmt) -> Self {
|
||||||
|
match stmt {
|
||||||
|
Stmt::Block(block, pos) => Self {
|
||||||
|
statements: block.into(),
|
||||||
|
pos,
|
||||||
|
},
|
||||||
|
Stmt::Noop(pos) => Self {
|
||||||
|
statements: Default::default(),
|
||||||
|
pos,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let pos = stmt.position();
|
||||||
|
let statements = vec![stmt].into();
|
||||||
|
Self { statements, pos }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Stmt {
|
impl Stmt {
|
||||||
/// Is this statement [`Noop`][Stmt::Noop]?
|
/// Is this statement [`Noop`][Stmt::Noop]?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -889,7 +963,7 @@ impl Stmt {
|
|||||||
| Self::While(_, _, pos)
|
| Self::While(_, _, pos)
|
||||||
| Self::Do(_, _, _, pos)
|
| Self::Do(_, _, _, pos)
|
||||||
| Self::For(_, _, pos)
|
| Self::For(_, _, pos)
|
||||||
| Self::Return((_, pos), _, _)
|
| Self::Return(_, _, pos)
|
||||||
| Self::Let(_, _, _, pos)
|
| Self::Let(_, _, _, pos)
|
||||||
| Self::Const(_, _, _, pos)
|
| Self::Const(_, _, _, pos)
|
||||||
| Self::TryCatch(_, pos, _) => *pos,
|
| Self::TryCatch(_, pos, _) => *pos,
|
||||||
@ -918,7 +992,7 @@ impl Stmt {
|
|||||||
| Self::While(_, _, pos)
|
| Self::While(_, _, pos)
|
||||||
| Self::Do(_, _, _, pos)
|
| Self::Do(_, _, _, pos)
|
||||||
| Self::For(_, _, pos)
|
| Self::For(_, _, pos)
|
||||||
| Self::Return((_, pos), _, _)
|
| Self::Return(_, _, pos)
|
||||||
| Self::Let(_, _, _, pos)
|
| Self::Let(_, _, _, pos)
|
||||||
| Self::Const(_, _, _, pos)
|
| Self::Const(_, _, _, pos)
|
||||||
| Self::TryCatch(_, pos, _) => *pos = new_pos,
|
| Self::TryCatch(_, pos, _) => *pos = new_pos,
|
||||||
@ -938,6 +1012,31 @@ impl Stmt {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Does this statement return a value?
|
||||||
|
pub fn returns_value(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::If(_, _, _) | Self::Switch(_, _, _) | Self::Block(_, _) | Self::Expr(_) => true,
|
||||||
|
|
||||||
|
Self::Noop(_)
|
||||||
|
| Self::While(_, _, _)
|
||||||
|
| Self::Do(_, _, _, _)
|
||||||
|
| Self::For(_, _, _)
|
||||||
|
| Self::TryCatch(_, _, _) => false,
|
||||||
|
|
||||||
|
Self::Let(_, _, _, _)
|
||||||
|
| Self::Const(_, _, _, _)
|
||||||
|
| Self::Assignment(_, _)
|
||||||
|
| Self::Continue(_)
|
||||||
|
| Self::Break(_)
|
||||||
|
| Self::Return(_, _, _) => false,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Self::Import(_, _, _) | Self::Export(_, _) => false,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
Self::Share(_) => unreachable!("Stmt::Share should not be parsed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
||||||
pub fn is_self_terminated(&self) -> bool {
|
pub fn is_self_terminated(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -976,23 +1075,29 @@ impl Stmt {
|
|||||||
Self::Expr(expr) => expr.is_pure(),
|
Self::Expr(expr) => expr.is_pure(),
|
||||||
Self::If(condition, x, _) => {
|
Self::If(condition, x, _) => {
|
||||||
condition.is_pure()
|
condition.is_pure()
|
||||||
&& x.0.is_pure()
|
&& x.0.statements.iter().all(Stmt::is_pure)
|
||||||
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
|
&& x.1.statements.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
Self::Switch(expr, x, _) => {
|
Self::Switch(expr, x, _) => {
|
||||||
expr.is_pure()
|
expr.is_pure()
|
||||||
&& x.0.values().all(Stmt::is_pure)
|
&& x.0
|
||||||
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
|
.values()
|
||||||
|
.flat_map(|block| block.statements.iter())
|
||||||
|
.all(Stmt::is_pure)
|
||||||
|
&& x.1.statements.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
Self::While(Some(condition), block, _) | Self::Do(block, condition, _, _) => {
|
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
||||||
condition.is_pure() && block.is_pure()
|
condition.is_pure() && block.statements.iter().all(Stmt::is_pure)
|
||||||
|
}
|
||||||
|
Self::For(iterable, x, _) => {
|
||||||
|
iterable.is_pure() && x.1.statements.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
Self::While(None, block, _) => block.is_pure(),
|
|
||||||
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
|
|
||||||
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
|
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
|
||||||
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
|
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
|
||||||
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
|
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
|
||||||
Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(),
|
Self::TryCatch(x, _, _) => {
|
||||||
|
x.0.statements.iter().all(Stmt::is_pure) && x.2.statements.iter().all(Stmt::is_pure)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Import(_, _, _) => false,
|
Self::Import(_, _, _) => false,
|
||||||
@ -1003,53 +1108,150 @@ impl Stmt {
|
|||||||
Self::Share(_) => false,
|
Self::Share(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Recursively walk this statement.
|
/// Is this statement _pure_ within the containing block?
|
||||||
|
///
|
||||||
|
/// An internally pure statement only has side effects that disappear outside the block.
|
||||||
|
///
|
||||||
|
/// Only variable declarations (i.e. `let` and `const`) and `import`/`export` statements
|
||||||
|
/// are internally pure.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn walk<'a>(&'a self, path: &mut Vec<ASTNode<'a>>, on_node: &mut impl FnMut(&[ASTNode])) {
|
pub fn is_internally_pure(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Let(expr, _, _, _) | Self::Const(expr, _, _, _) => expr.is_pure(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Self::Import(expr, _, _) => expr.is_pure(),
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Self::Export(_, _) => true,
|
||||||
|
|
||||||
|
_ => self.is_pure(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Does this statement break the current control flow through the containing block?
|
||||||
|
///
|
||||||
|
/// Currently this is only true for `return`, `throw`, `break` and `continue`.
|
||||||
|
///
|
||||||
|
/// All statements following this statement will essentially be dead code.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_control_flow_break(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Recursively walk this statement.
|
||||||
|
/// Return `false` from the callback to terminate the walk.
|
||||||
|
pub fn walk<'a>(
|
||||||
|
&'a self,
|
||||||
|
path: &mut Vec<ASTNode<'a>>,
|
||||||
|
on_node: &mut impl FnMut(&[ASTNode]) -> bool,
|
||||||
|
) -> bool {
|
||||||
path.push(self.into());
|
path.push(self.into());
|
||||||
on_node(path);
|
|
||||||
|
if !on_node(path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Let(_, Some(e), _, _) | Self::Const(_, Some(e), _, _) => e.walk(path, on_node),
|
Self::Let(e, _, _, _) | Self::Const(e, _, _, _) => {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::If(e, x, _) => {
|
Self::If(e, x, _) => {
|
||||||
e.walk(path, on_node);
|
if !e.walk(path, on_node) {
|
||||||
x.0.walk(path, on_node);
|
return false;
|
||||||
if let Some(ref s) = x.1 {
|
}
|
||||||
s.walk(path, on_node);
|
for s in &x.0.statements {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s in &x.1.statements {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Switch(e, x, _) => {
|
Self::Switch(e, x, _) => {
|
||||||
e.walk(path, on_node);
|
if !e.walk(path, on_node) {
|
||||||
x.0.values().for_each(|s| s.walk(path, on_node));
|
return false;
|
||||||
if let Some(ref s) = x.1 {
|
}
|
||||||
s.walk(path, on_node);
|
for s in x.0.values().flat_map(|block| block.statements.iter()) {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s in &x.1.statements {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::While(Some(e), s, _) | Self::Do(s, e, _, _) => {
|
Self::While(e, s, _) | Self::Do(s, e, _, _) => {
|
||||||
e.walk(path, on_node);
|
if !e.walk(path, on_node) {
|
||||||
s.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, _) => {
|
Self::For(e, x, _) => {
|
||||||
e.walk(path, on_node);
|
if !e.walk(path, on_node) {
|
||||||
x.1.walk(path, on_node);
|
return false;
|
||||||
|
}
|
||||||
|
for s in &x.1.statements {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::Assignment(x, _) => {
|
Self::Assignment(x, _) => {
|
||||||
x.0.walk(path, on_node);
|
if !x.0.walk(path, on_node) {
|
||||||
x.1.walk(path, on_node);
|
return false;
|
||||||
|
}
|
||||||
|
if !x.1.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Block(x, _) => {
|
||||||
|
for s in x {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::Block(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
|
|
||||||
Self::TryCatch(x, _, _) => {
|
Self::TryCatch(x, _, _) => {
|
||||||
x.0.walk(path, on_node);
|
for s in &x.0.statements {
|
||||||
x.2.walk(path, on_node);
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s in &x.2.statements {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Expr(e) | Self::Return(_, Some(e), _) => {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::Expr(e) | Self::Return(_, Some(e), _) => e.walk(path, on_node),
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Import(e, _, _) => e.walk(path, on_node),
|
Self::Import(e, _, _) => {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
path.pop().unwrap();
|
path.pop().unwrap();
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1102,7 +1304,7 @@ pub struct OpAssignment {
|
|||||||
/// # Volatile Data Structure
|
/// # Volatile Data Structure
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
|
||||||
pub struct FnHash {
|
pub struct FnHash {
|
||||||
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
|
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
|
||||||
script: Option<u64>,
|
script: Option<u64>,
|
||||||
@ -1110,6 +1312,20 @@ pub struct FnHash {
|
|||||||
native: u64,
|
native: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for FnHash {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(script) = self.script {
|
||||||
|
if script == self.native {
|
||||||
|
write!(f, "({}=={})", script, self.native)
|
||||||
|
} else {
|
||||||
|
write!(f, "({}, {})", script, self.native)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.native)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FnHash {
|
impl FnHash {
|
||||||
/// Create a [`FnHash`] with only the native Rust hash.
|
/// Create a [`FnHash`] with only the native Rust hash.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -1185,6 +1401,7 @@ pub struct FloatWrapper(FLOAT);
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl Hash for FloatWrapper {
|
impl Hash for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.0.to_ne_bytes().hash(state);
|
self.0.to_ne_bytes().hash(state);
|
||||||
}
|
}
|
||||||
@ -1192,6 +1409,7 @@ impl Hash for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl AsRef<FLOAT> for FloatWrapper {
|
impl AsRef<FLOAT> for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn as_ref(&self) -> &FLOAT {
|
fn as_ref(&self) -> &FLOAT {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
@ -1199,6 +1417,7 @@ impl AsRef<FLOAT> for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl AsMut<FLOAT> for FloatWrapper {
|
impl AsMut<FLOAT> for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn as_mut(&mut self) -> &mut FLOAT {
|
fn as_mut(&mut self) -> &mut FLOAT {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
@ -1208,6 +1427,7 @@ impl AsMut<FLOAT> for FloatWrapper {
|
|||||||
impl crate::stdlib::ops::Deref for FloatWrapper {
|
impl crate::stdlib::ops::Deref for FloatWrapper {
|
||||||
type Target = FLOAT;
|
type Target = FLOAT;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
@ -1215,6 +1435,7 @@ impl crate::stdlib::ops::Deref for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
@ -1222,6 +1443,7 @@ impl crate::stdlib::ops::DerefMut for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl fmt::Debug for FloatWrapper {
|
impl fmt::Debug for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Display::fmt(self, f)
|
fmt::Display::fmt(self, f)
|
||||||
}
|
}
|
||||||
@ -1229,6 +1451,7 @@ impl fmt::Debug for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl fmt::Display for FloatWrapper {
|
impl fmt::Display for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use num_traits::Float;
|
use num_traits::Float;
|
||||||
@ -1244,6 +1467,7 @@ impl fmt::Display for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl From<FLOAT> for FloatWrapper {
|
impl From<FLOAT> for FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: FLOAT) -> Self {
|
fn from(value: FLOAT) -> Self {
|
||||||
Self::new(value)
|
Self::new(value)
|
||||||
}
|
}
|
||||||
@ -1253,6 +1477,7 @@ impl From<FLOAT> for FloatWrapper {
|
|||||||
impl FromStr for FloatWrapper {
|
impl FromStr for FloatWrapper {
|
||||||
type Err = <FLOAT as FromStr>::Err;
|
type Err = <FLOAT as FromStr>::Err;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
FLOAT::from_str(s).map(Into::<Self>::into)
|
FLOAT::from_str(s).map(Into::<Self>::into)
|
||||||
}
|
}
|
||||||
@ -1260,6 +1485,7 @@ impl FromStr for FloatWrapper {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl FloatWrapper {
|
impl FloatWrapper {
|
||||||
|
#[inline(always)]
|
||||||
pub const fn new(value: FLOAT) -> Self {
|
pub const fn new(value: FLOAT) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
@ -1298,18 +1524,16 @@ pub enum Expr {
|
|||||||
Unit(Position),
|
Unit(Position),
|
||||||
/// Variable access - (optional index, optional (hash, modules), variable name)
|
/// Variable access - (optional index, optional (hash, modules), variable name)
|
||||||
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
|
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
|
||||||
/// Property access - (getter, hash, setter, hash, prop)
|
/// Property access - ((getter, hash), (setter, hash), prop)
|
||||||
Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>),
|
Property(Box<((ImmutableString, u64), (ImmutableString, u64), Ident)>),
|
||||||
/// { [statement][Stmt] }
|
/// { [statement][Stmt] ... }
|
||||||
Stmt(Box<StaticVec<Stmt>>, Position),
|
Stmt(Box<StmtBlock>),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
FnCall(Box<FnCallExpr>, Position),
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
/// lhs `.` rhs
|
/// lhs `.` rhs
|
||||||
Dot(Box<BinaryExpr>, Position),
|
Dot(Box<BinaryExpr>, Position),
|
||||||
/// expr `[` expr `]`
|
/// expr `[` expr `]`
|
||||||
Index(Box<BinaryExpr>, Position),
|
Index(Box<BinaryExpr>, Position),
|
||||||
/// lhs `in` rhs
|
|
||||||
In(Box<BinaryExpr>, Position),
|
|
||||||
/// lhs `&&` rhs
|
/// lhs `&&` rhs
|
||||||
And(Box<BinaryExpr>, Position),
|
And(Box<BinaryExpr>, Position),
|
||||||
/// lhs `||` rhs
|
/// lhs `||` rhs
|
||||||
@ -1329,6 +1553,7 @@ impl Expr {
|
|||||||
/// Get the [`Dynamic`] value of a constant expression.
|
/// Get the [`Dynamic`] value of a constant expression.
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if the expression is not constant.
|
/// Returns [`None`] if the expression is not constant.
|
||||||
|
#[inline]
|
||||||
pub fn get_constant_value(&self) -> Option<Dynamic> {
|
pub fn get_constant_value(&self) -> Option<Dynamic> {
|
||||||
Some(match self {
|
Some(match self {
|
||||||
Self::DynamicConstant(x, _) => x.as_ref().clone(),
|
Self::DynamicConstant(x, _) => x.as_ref().clone(),
|
||||||
@ -1379,6 +1604,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the [position][Position] of the expression.
|
/// Get the [position][Position] of the expression.
|
||||||
|
#[inline]
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -1392,12 +1618,12 @@ impl Expr {
|
|||||||
Self::FnPointer(_, pos) => *pos,
|
Self::FnPointer(_, pos) => *pos,
|
||||||
Self::Array(_, pos) => *pos,
|
Self::Array(_, pos) => *pos,
|
||||||
Self::Map(_, pos) => *pos,
|
Self::Map(_, pos) => *pos,
|
||||||
Self::Property(x) => (x.4).pos,
|
Self::Property(x) => (x.2).pos,
|
||||||
Self::Stmt(_, pos) => *pos,
|
Self::Stmt(x) => x.pos,
|
||||||
Self::Variable(x) => (x.2).pos,
|
Self::Variable(x) => (x.2).pos,
|
||||||
Self::FnCall(_, pos) => *pos,
|
Self::FnCall(_, pos) => *pos,
|
||||||
|
|
||||||
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
Self::And(x, _) | Self::Or(x, _) => x.lhs.position(),
|
||||||
|
|
||||||
Self::Unit(pos) => *pos,
|
Self::Unit(pos) => *pos,
|
||||||
|
|
||||||
@ -1407,6 +1633,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Override the [position][Position] of the expression.
|
/// Override the [position][Position] of the expression.
|
||||||
|
#[inline]
|
||||||
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
|
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -1421,10 +1648,10 @@ impl Expr {
|
|||||||
Self::Array(_, pos) => *pos = new_pos,
|
Self::Array(_, pos) => *pos = new_pos,
|
||||||
Self::Map(_, pos) => *pos = new_pos,
|
Self::Map(_, pos) => *pos = new_pos,
|
||||||
Self::Variable(x) => (x.2).pos = new_pos,
|
Self::Variable(x) => (x.2).pos = new_pos,
|
||||||
Self::Property(x) => (x.4).pos = new_pos,
|
Self::Property(x) => (x.2).pos = new_pos,
|
||||||
Self::Stmt(_, pos) => *pos = new_pos,
|
Self::Stmt(x) => x.pos = new_pos,
|
||||||
Self::FnCall(_, pos) => *pos = new_pos,
|
Self::FnCall(_, pos) => *pos = new_pos,
|
||||||
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
Self::And(_, pos) | Self::Or(_, pos) => *pos = new_pos,
|
||||||
Self::Unit(pos) => *pos = new_pos,
|
Self::Unit(pos) => *pos = new_pos,
|
||||||
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
||||||
Self::Custom(_, pos) => *pos = new_pos,
|
Self::Custom(_, pos) => *pos = new_pos,
|
||||||
@ -1435,17 +1662,18 @@ impl Expr {
|
|||||||
/// Is the expression pure?
|
/// Is the expression pure?
|
||||||
///
|
///
|
||||||
/// A pure expression has no side effects.
|
/// A pure expression has no side effects.
|
||||||
|
#[inline]
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Array(x, _) => x.iter().all(Self::is_pure),
|
Self::Array(x, _) => x.iter().all(Self::is_pure),
|
||||||
|
|
||||||
Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure),
|
Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure),
|
||||||
|
|
||||||
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => {
|
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) => {
|
||||||
x.lhs.is_pure() && x.rhs.is_pure()
|
x.lhs.is_pure() && x.rhs.is_pure()
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Stmt(x, _) => x.iter().all(Stmt::is_pure),
|
Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
|
||||||
|
|
||||||
Self::Variable(_) => true,
|
Self::Variable(_) => true,
|
||||||
|
|
||||||
@ -1461,6 +1689,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the expression a constant?
|
/// Is the expression a constant?
|
||||||
|
#[inline]
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -1480,17 +1709,11 @@ impl Expr {
|
|||||||
// An map literal is constant if all items are constant
|
// An map literal is constant if all items are constant
|
||||||
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant),
|
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant),
|
||||||
|
|
||||||
// Check in expression
|
|
||||||
Self::In(x, _) => match (&x.lhs, &x.rhs) {
|
|
||||||
(Self::StringConstant(_, _), Self::StringConstant(_, _))
|
|
||||||
| (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is a particular [token][Token] allowed as a postfix operator to this expression?
|
/// Is a particular [token][Token] allowed as a postfix operator to this expression?
|
||||||
|
#[inline]
|
||||||
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||||
match token {
|
match token {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -1507,14 +1730,13 @@ impl Expr {
|
|||||||
| Self::IntegerConstant(_, _)
|
| Self::IntegerConstant(_, _)
|
||||||
| Self::CharConstant(_, _)
|
| Self::CharConstant(_, _)
|
||||||
| Self::FnPointer(_, _)
|
| Self::FnPointer(_, _)
|
||||||
| Self::In(_, _)
|
|
||||||
| Self::And(_, _)
|
| Self::And(_, _)
|
||||||
| Self::Or(_, _)
|
| Self::Or(_, _)
|
||||||
| Self::Unit(_) => false,
|
| Self::Unit(_) => false,
|
||||||
|
|
||||||
Self::StringConstant(_, _)
|
Self::StringConstant(_, _)
|
||||||
| Self::FnCall(_, _)
|
| Self::FnCall(_, _)
|
||||||
| Self::Stmt(_, _)
|
| Self::Stmt(_)
|
||||||
| Self::Dot(_, _)
|
| Self::Dot(_, _)
|
||||||
| Self::Index(_, _)
|
| Self::Index(_, _)
|
||||||
| Self::Array(_, _)
|
| Self::Array(_, _)
|
||||||
@ -1544,29 +1766,68 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Recursively walk this expression.
|
/// Recursively walk this expression.
|
||||||
#[inline(always)]
|
/// Return `false` from the callback to terminate the walk.
|
||||||
pub fn walk<'a>(&'a self, path: &mut Vec<ASTNode<'a>>, on_node: &mut impl FnMut(&[ASTNode])) {
|
pub fn walk<'a>(
|
||||||
|
&'a self,
|
||||||
|
path: &mut Vec<ASTNode<'a>>,
|
||||||
|
on_node: &mut impl FnMut(&[ASTNode]) -> bool,
|
||||||
|
) -> bool {
|
||||||
path.push(self.into());
|
path.push(self.into());
|
||||||
on_node(path);
|
|
||||||
|
if !on_node(path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
|
Self::Stmt(x) => {
|
||||||
Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)),
|
for s in &x.statements {
|
||||||
Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)),
|
if !s.walk(path, on_node) {
|
||||||
Self::Index(x, _)
|
return false;
|
||||||
| Self::Dot(x, _)
|
}
|
||||||
| Expr::In(x, _)
|
}
|
||||||
| Expr::And(x, _)
|
}
|
||||||
| Expr::Or(x, _) => {
|
Self::Array(x, _) => {
|
||||||
x.lhs.walk(path, on_node);
|
for e in x.as_ref() {
|
||||||
x.rhs.walk(path, on_node);
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Map(x, _) => {
|
||||||
|
for (_, e) in x.as_ref() {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Index(x, _) | Self::Dot(x, _) | Expr::And(x, _) | Expr::Or(x, _) => {
|
||||||
|
if !x.lhs.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !x.rhs.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::FnCall(x, _) => {
|
||||||
|
for e in &x.args {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Custom(x, _) => {
|
||||||
|
for e in &x.keywords {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::FnCall(x, _) => x.args.iter().for_each(|e| e.walk(path, on_node)),
|
|
||||||
Self::Custom(x, _) => x.keywords.iter().for_each(|e| e.walk(path, on_node)),
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
path.pop().unwrap();
|
path.pop().unwrap();
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1583,8 +1844,8 @@ mod tests {
|
|||||||
assert_eq!(size_of::<Position>(), 4);
|
assert_eq!(size_of::<Position>(), 4);
|
||||||
assert_eq!(size_of::<ast::Expr>(), 16);
|
assert_eq!(size_of::<ast::Expr>(), 16);
|
||||||
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
||||||
assert_eq!(size_of::<ast::Stmt>(), 32);
|
assert_eq!(size_of::<ast::Stmt>(), 40);
|
||||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
assert_eq!(size_of::<Option<ast::Stmt>>(), 40);
|
||||||
assert_eq!(size_of::<FnPtr>(), 32);
|
assert_eq!(size_of::<FnPtr>(), 32);
|
||||||
assert_eq!(size_of::<Scope>(), 48);
|
assert_eq!(size_of::<Scope>(), 48);
|
||||||
assert_eq!(size_of::<LexError>(), 56);
|
assert_eq!(size_of::<LexError>(), 56);
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST};
|
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
|
||||||
use rhai::OptimizationLevel;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -56,35 +53,38 @@ fn print_help() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
println!("Rhai REPL tool");
|
println!("Rhai REPL tool");
|
||||||
println!("==============");
|
println!("==============");
|
||||||
print_help();
|
print_help();
|
||||||
|
|
||||||
// Load init scripts
|
// Initialize scripting engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
|
// Set a file module resolver without caching
|
||||||
|
let mut resolver = rhai::module_resolvers::FileModuleResolver::new();
|
||||||
|
resolver.enable_cache(false);
|
||||||
|
engine.set_module_resolver(resolver);
|
||||||
|
|
||||||
|
// Load init scripts
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
let mut has_init_scripts = false;
|
let mut has_init_scripts = false;
|
||||||
|
|
||||||
for filename in env::args().skip(1) {
|
for filename in env::args().skip(1) {
|
||||||
{
|
contents.clear();
|
||||||
contents.clear();
|
|
||||||
|
|
||||||
let mut f = match File::open(&filename) {
|
let mut f = match File::open(&filename) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error reading script file: {}\n{}", filename, 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);
|
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
|
let module = match engine
|
||||||
@ -119,9 +119,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup Engine
|
// Setup Engine
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
engine.set_optimization_level(OptimizationLevel::None);
|
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
@ -130,8 +129,10 @@ fn main() {
|
|||||||
let mut ast_u: AST = Default::default();
|
let mut ast_u: AST = Default::default();
|
||||||
let mut ast: AST = Default::default();
|
let mut ast: AST = Default::default();
|
||||||
|
|
||||||
// REPL loop
|
// Make Engine immutable
|
||||||
|
let engine = engine;
|
||||||
|
|
||||||
|
// REPL loop
|
||||||
'main_loop: loop {
|
'main_loop: loop {
|
||||||
print!("rhai-repl> ");
|
print!("rhai-repl> ");
|
||||||
stdout().flush().expect("couldn't flush stdout");
|
stdout().flush().expect("couldn't flush stdout");
|
||||||
@ -233,7 +234,7 @@ fn main() {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
{
|
{
|
||||||
ast = engine.optimize_ast(&scope, r, OptimizationLevel::Simple);
|
ast = engine.optimize_ast(&scope, r, rhai::OptimizationLevel::Simple);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
|
421
src/engine.rs
421
src/engine.rs
@ -1,6 +1,6 @@
|
|||||||
//! Main module defining the script evaluation [`Engine`].
|
//! Main module defining the script evaluation [`Engine`].
|
||||||
|
|
||||||
use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt};
|
use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt, StmtBlock};
|
||||||
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
||||||
use crate::fn_native::{
|
use crate::fn_native::{
|
||||||
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
|
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
|
||||||
@ -17,7 +17,6 @@ use crate::stdlib::{
|
|||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fmt, format,
|
fmt, format,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
iter::empty,
|
|
||||||
num::{NonZeroU64, NonZeroU8, NonZeroUsize},
|
num::{NonZeroU64, NonZeroU8, NonZeroUsize},
|
||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -25,12 +24,12 @@ use crate::stdlib::{
|
|||||||
use crate::syntax::CustomSyntax;
|
use crate::syntax::CustomSyntax;
|
||||||
use crate::utils::{get_hasher, StraightHasherBuilder};
|
use crate::utils::{get_hasher, StraightHasherBuilder};
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult,
|
Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared,
|
||||||
Scope, Shared, StaticVec,
|
StaticVec,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::Array;
|
use crate::{calc_fn_hash, stdlib::iter::empty, Array};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub const TYPICAL_ARRAY_SIZE: usize = 8; // Small arrays are typical
|
pub const TYPICAL_ARRAY_SIZE: usize = 8; // Small arrays are typical
|
||||||
@ -203,9 +202,15 @@ pub const FN_IDX_GET: &str = "index$get$";
|
|||||||
pub const FN_IDX_SET: &str = "index$set$";
|
pub const FN_IDX_SET: &str = "index$set$";
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub const FN_ANONYMOUS: &str = "anon$";
|
pub const FN_ANONYMOUS: &str = "anon$";
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
|
||||||
|
/// Standard equality comparison operator.
|
||||||
pub const OP_EQUALS: &str = "==";
|
pub const OP_EQUALS: &str = "==";
|
||||||
|
|
||||||
|
/// Standard method function for containment testing.
|
||||||
|
///
|
||||||
|
/// The `in` operator is implemented as a call to this method.
|
||||||
|
pub const OP_CONTAINS: &str = "contains";
|
||||||
|
|
||||||
/// Method of chaining.
|
/// Method of chaining.
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
@ -223,11 +228,11 @@ pub enum ChainType {
|
|||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum ChainArgument {
|
pub enum ChainArgument {
|
||||||
/// Dot-property access.
|
/// Dot-property access.
|
||||||
Property,
|
Property(Position),
|
||||||
/// Arguments to a dot-function call.
|
/// Arguments to a dot-function call.
|
||||||
FnCallArgs(StaticVec<Dynamic>),
|
FnCallArgs(StaticVec<Dynamic>, StaticVec<Position>),
|
||||||
/// Index value.
|
/// Index value.
|
||||||
IndexValue(Dynamic),
|
IndexValue(Dynamic, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
@ -241,8 +246,10 @@ impl ChainArgument {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn as_index_value(self) -> Dynamic {
|
pub fn as_index_value(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Self::Property | Self::FnCallArgs(_) => panic!("expecting ChainArgument::IndexValue"),
|
Self::Property(_) | Self::FnCallArgs(_, _) => {
|
||||||
Self::IndexValue(value) => value,
|
panic!("expecting ChainArgument::IndexValue")
|
||||||
|
}
|
||||||
|
Self::IndexValue(value, _) => value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Return the `StaticVec<Dynamic>` value.
|
/// Return the `StaticVec<Dynamic>` value.
|
||||||
@ -252,27 +259,29 @@ impl ChainArgument {
|
|||||||
/// Panics if not `ChainArgument::FnCallArgs`.
|
/// Panics if not `ChainArgument::FnCallArgs`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn as_fn_call_args(self) -> StaticVec<Dynamic> {
|
pub fn as_fn_call_args(self) -> (StaticVec<Dynamic>, StaticVec<Position>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Property | Self::IndexValue(_) => panic!("expecting ChainArgument::FnCallArgs"),
|
Self::Property(_) | Self::IndexValue(_, _) => {
|
||||||
Self::FnCallArgs(value) => value,
|
panic!("expecting ChainArgument::FnCallArgs")
|
||||||
|
}
|
||||||
|
Self::FnCallArgs(values, positions) => (values, positions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl From<StaticVec<Dynamic>> for ChainArgument {
|
impl From<(StaticVec<Dynamic>, StaticVec<Position>)> for ChainArgument {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: StaticVec<Dynamic>) -> Self {
|
fn from((values, positions): (StaticVec<Dynamic>, StaticVec<Position>)) -> Self {
|
||||||
Self::FnCallArgs(value)
|
Self::FnCallArgs(values, positions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl From<Dynamic> for ChainArgument {
|
impl From<(Dynamic, Position)> for ChainArgument {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: Dynamic) -> Self {
|
fn from((value, pos): (Dynamic, Position)) -> Self {
|
||||||
Self::IndexValue(value)
|
Self::IndexValue(value, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +404,11 @@ impl<'a> Target<'a> {
|
|||||||
}
|
}
|
||||||
/// Update the value of the `Target`.
|
/// Update the value of the `Target`.
|
||||||
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
||||||
pub fn set_value(&mut self, new_val: Dynamic, pos: Position) -> Result<(), Box<EvalAltResult>> {
|
pub fn set_value(
|
||||||
|
&mut self,
|
||||||
|
new_val: Dynamic,
|
||||||
|
_pos: Position,
|
||||||
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => **r = new_val,
|
Self::Ref(r) => **r = new_val,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -411,7 +424,7 @@ impl<'a> Target<'a> {
|
|||||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||||
"char".to_string(),
|
"char".to_string(),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
pos,
|
_pos,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -893,6 +906,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a module within an imports stack.
|
/// Search for a module within an imports stack.
|
||||||
|
#[inline]
|
||||||
pub(crate) fn search_imports(
|
pub(crate) fn search_imports(
|
||||||
&self,
|
&self,
|
||||||
mods: &Imports,
|
mods: &Imports,
|
||||||
@ -932,7 +946,7 @@ impl Engine {
|
|||||||
match expr {
|
match expr {
|
||||||
Expr::Variable(v) => match v.as_ref() {
|
Expr::Variable(v) => match v.as_ref() {
|
||||||
// Qualified variable
|
// Qualified variable
|
||||||
(_, Some((hash_var, modules)), Ident { name, pos }) => {
|
(_, Some((hash_var, modules)), Ident { name, pos, .. }) => {
|
||||||
let module = self.search_imports(mods, state, modules).ok_or_else(|| {
|
let module = self.search_imports(mods, state, modules).ok_or_else(|| {
|
||||||
EvalAltResult::ErrorModuleNotFound(
|
EvalAltResult::ErrorModuleNotFound(
|
||||||
modules[0].name.to_string(),
|
modules[0].name.to_string(),
|
||||||
@ -971,7 +985,7 @@ impl Engine {
|
|||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
||||||
let (index, _, Ident { name, pos }) = match expr {
|
let (index, _, Ident { name, pos, .. }) = match expr {
|
||||||
Expr::Variable(v) => v.as_ref(),
|
Expr::Variable(v) => v.as_ref(),
|
||||||
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
||||||
};
|
};
|
||||||
@ -1152,9 +1166,9 @@ impl Engine {
|
|||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
|
||||||
let FnCallExpr { name, hash, .. } = x.as_ref();
|
let FnCallExpr { name, hash, .. } = x.as_ref();
|
||||||
let args = idx_val.as_fn_call_args();
|
let mut args = idx_val.as_fn_call_args();
|
||||||
self.make_method_call(
|
self.make_method_call(
|
||||||
mods, state, lib, name, *hash, target, args, *pos, level,
|
mods, state, lib, name, *hash, target, &mut args, *pos, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx.fn_name(...) = ???
|
// xxx.fn_name(...) = ???
|
||||||
@ -1167,7 +1181,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// {xxx:map}.id op= ???
|
// {xxx:map}.id op= ???
|
||||||
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
|
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
|
||||||
let Ident { name, pos } = &x.4;
|
let Ident { name, pos, .. } = &x.2;
|
||||||
let index = name.clone().into();
|
let index = name.clone().into();
|
||||||
let val = self.get_indexed_mut(
|
let val = self.get_indexed_mut(
|
||||||
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
|
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
|
||||||
@ -1180,7 +1194,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// {xxx:map}.id
|
// {xxx:map}.id
|
||||||
Expr::Property(x) if target_val.is::<Map>() => {
|
Expr::Property(x) if target_val.is::<Map>() => {
|
||||||
let Ident { name, pos } = &x.4;
|
let Ident { name, pos, .. } = &x.2;
|
||||||
let index = name.clone().into();
|
let index = name.clone().into();
|
||||||
let val = self.get_indexed_mut(
|
let val = self.get_indexed_mut(
|
||||||
mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
|
mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
|
||||||
@ -1190,7 +1204,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx.id = ???
|
// xxx.id = ???
|
||||||
Expr::Property(x) if new_val.is_some() => {
|
Expr::Property(x) if new_val.is_some() => {
|
||||||
let (_, _, setter, hash_set, Ident { pos, .. }) = x.as_ref();
|
let (_, (setter, hash_set), Ident { pos, .. }) = x.as_ref();
|
||||||
let hash = FnHash::from_native(*hash_set);
|
let hash = FnHash::from_native(*hash_set);
|
||||||
let mut new_val = new_val;
|
let mut new_val = new_val;
|
||||||
let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
|
let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0];
|
||||||
@ -1202,7 +1216,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Property(x) => {
|
Expr::Property(x) => {
|
||||||
let (getter, hash_get, _, _, Ident { pos, .. }) = x.as_ref();
|
let ((getter, hash_get), _, Ident { pos, .. }) = x.as_ref();
|
||||||
let hash = FnHash::from_native(*hash_get);
|
let hash = FnHash::from_native(*hash_get);
|
||||||
let mut args = [target_val];
|
let mut args = [target_val];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -1215,7 +1229,7 @@ impl Engine {
|
|||||||
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => {
|
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => {
|
||||||
let mut val = match &x.lhs {
|
let mut val = match &x.lhs {
|
||||||
Expr::Property(p) => {
|
Expr::Property(p) => {
|
||||||
let Ident { name, pos } = &p.4;
|
let Ident { name, pos, .. } = &p.2;
|
||||||
let index = name.clone().into();
|
let index = name.clone().into();
|
||||||
self.get_indexed_mut(
|
self.get_indexed_mut(
|
||||||
mods, state, lib, target_val, index, *pos, false, is_ref, true,
|
mods, state, lib, target_val, index, *pos, false, is_ref, true,
|
||||||
@ -1225,9 +1239,9 @@ impl Engine {
|
|||||||
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||||
let FnCallExpr { name, hash, .. } = x.as_ref();
|
let FnCallExpr { name, hash, .. } = x.as_ref();
|
||||||
let args = idx_val.as_fn_call_args();
|
let mut args = idx_val.as_fn_call_args();
|
||||||
let (val, _) = self.make_method_call(
|
let (val, _) = self.make_method_call(
|
||||||
mods, state, lib, name, *hash, target, args, *pos, level,
|
mods, state, lib, name, *hash, target, &mut args, *pos, level,
|
||||||
)?;
|
)?;
|
||||||
val.into()
|
val.into()
|
||||||
}
|
}
|
||||||
@ -1250,7 +1264,7 @@ impl Engine {
|
|||||||
match &x.lhs {
|
match &x.lhs {
|
||||||
// xxx.prop[expr] | xxx.prop.expr
|
// xxx.prop[expr] | xxx.prop.expr
|
||||||
Expr::Property(p) => {
|
Expr::Property(p) => {
|
||||||
let (getter, hash_get, setter, hash_set, Ident { pos, .. }) =
|
let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) =
|
||||||
p.as_ref();
|
p.as_ref();
|
||||||
let hash_get = FnHash::from_native(*hash_get);
|
let hash_get = FnHash::from_native(*hash_get);
|
||||||
let hash_set = FnHash::from_native(*hash_set);
|
let hash_set = FnHash::from_native(*hash_set);
|
||||||
@ -1303,9 +1317,9 @@ impl Engine {
|
|||||||
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
|
||||||
Expr::FnCall(f, pos) if f.namespace.is_none() => {
|
Expr::FnCall(f, pos) if f.namespace.is_none() => {
|
||||||
let FnCallExpr { name, hash, .. } = f.as_ref();
|
let FnCallExpr { name, hash, .. } = f.as_ref();
|
||||||
let args = idx_val.as_fn_call_args();
|
let mut args = idx_val.as_fn_call_args();
|
||||||
let (mut val, _) = self.make_method_call(
|
let (mut val, _) = self.make_method_call(
|
||||||
mods, state, lib, name, *hash, target, args, *pos, level,
|
mods, state, lib, name, *hash, target, &mut args, *pos, level,
|
||||||
)?;
|
)?;
|
||||||
let val = &mut val;
|
let val = &mut val;
|
||||||
let target = &mut val.into();
|
let target = &mut val.into();
|
||||||
@ -1364,6 +1378,7 @@ impl Engine {
|
|||||||
let Ident {
|
let Ident {
|
||||||
name: var_name,
|
name: var_name,
|
||||||
pos: var_pos,
|
pos: var_pos,
|
||||||
|
..
|
||||||
} = &x.2;
|
} = &x.2;
|
||||||
|
|
||||||
self.inc_operations(state, *var_pos)?;
|
self.inc_operations(state, *var_pos)?;
|
||||||
@ -1422,23 +1437,26 @@ impl Engine {
|
|||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => {
|
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => {
|
||||||
|
let mut arg_positions: StaticVec<_> = Default::default();
|
||||||
|
|
||||||
let arg_values = x
|
let arg_values = x
|
||||||
.args
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg_expr| {
|
.map(|arg_expr| {
|
||||||
|
arg_positions.push(arg_expr.position());
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
||||||
.map(Dynamic::flatten)
|
.map(Dynamic::flatten)
|
||||||
})
|
})
|
||||||
.collect::<Result<StaticVec<_>, _>>()?;
|
.collect::<Result<StaticVec<_>, _>>()?;
|
||||||
|
|
||||||
idx_values.push(arg_values.into());
|
idx_values.push((arg_values, arg_positions).into());
|
||||||
}
|
}
|
||||||
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
|
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
|
||||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::Property(_) if parent_chain_type == ChainType::Dot => {
|
Expr::Property(x) if parent_chain_type == ChainType::Dot => {
|
||||||
idx_values.push(ChainArgument::Property)
|
idx_values.push(ChainArgument::Property(x.2.pos))
|
||||||
}
|
}
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
|
|
||||||
@ -1447,29 +1465,33 @@ impl Engine {
|
|||||||
|
|
||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_val = match lhs {
|
let lhs_val = match lhs {
|
||||||
Expr::Property(_) if parent_chain_type == ChainType::Dot => {
|
Expr::Property(x) if parent_chain_type == ChainType::Dot => {
|
||||||
ChainArgument::Property
|
ChainArgument::Property(x.2.pos)
|
||||||
}
|
}
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
Expr::FnCall(x, _)
|
Expr::FnCall(x, _)
|
||||||
if parent_chain_type == ChainType::Dot && x.namespace.is_none() =>
|
if parent_chain_type == ChainType::Dot && x.namespace.is_none() =>
|
||||||
{
|
{
|
||||||
x.args
|
let mut arg_positions: StaticVec<_> = Default::default();
|
||||||
|
|
||||||
|
let arg_values = x
|
||||||
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg_expr| {
|
.map(|arg_expr| {
|
||||||
|
arg_positions.push(arg_expr.position());
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
self.eval_expr(scope, mods, state, lib, this_ptr, arg_expr, level)
|
||||||
.map(Dynamic::flatten)
|
.map(Dynamic::flatten)
|
||||||
})
|
})
|
||||||
.collect::<Result<StaticVec<Dynamic>, _>>()?
|
.collect::<Result<StaticVec<_>, _>>()?;
|
||||||
.into()
|
|
||||||
|
(arg_values, arg_positions).into()
|
||||||
}
|
}
|
||||||
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
|
Expr::FnCall(_, _) if parent_chain_type == ChainType::Dot => {
|
||||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||||
}
|
}
|
||||||
_ => self
|
_ => self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)
|
||||||
.flatten()
|
.map(|v| (v.flatten(), lhs.position()).into())?,
|
||||||
.into(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push in reverse order
|
// Push in reverse order
|
||||||
@ -1486,9 +1508,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => idx_values.push(
|
_ => idx_values.push(
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||||
.flatten()
|
.map(|v| (v.flatten(), expr.position()).into())?,
|
||||||
.into(),
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1603,63 +1624,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate an 'in' expression.
|
|
||||||
fn eval_in_expr(
|
|
||||||
&self,
|
|
||||||
scope: &mut Scope,
|
|
||||||
mods: &mut Imports,
|
|
||||||
state: &mut State,
|
|
||||||
lib: &[&Module],
|
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
|
||||||
lhs: &Expr,
|
|
||||||
rhs: &Expr,
|
|
||||||
level: usize,
|
|
||||||
) -> RhaiResult {
|
|
||||||
self.inc_operations(state, rhs.position())?;
|
|
||||||
|
|
||||||
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
|
|
||||||
let rhs_value = self
|
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
match rhs_value {
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
Dynamic(Union::Array(mut rhs_value, _)) => {
|
|
||||||
// Call the `==` operator to compare each value
|
|
||||||
let hash = calc_fn_hash(empty(), OP_EQUALS, 2);
|
|
||||||
for value in rhs_value.iter_mut() {
|
|
||||||
let args = &mut [&mut lhs_value.clone(), value];
|
|
||||||
let pos = rhs.position();
|
|
||||||
|
|
||||||
if self
|
|
||||||
.call_native_fn(mods, state, lib, OP_EQUALS, hash, args, false, false, pos)?
|
|
||||||
.0
|
|
||||||
.as_bool()
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Ok(true.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false.into())
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
Dynamic(Union::Map(rhs_value, _)) => match lhs_value {
|
|
||||||
// Only allows string or char
|
|
||||||
Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains_key(&s).into()),
|
|
||||||
Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
|
|
||||||
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
|
|
||||||
},
|
|
||||||
Dynamic(Union::Str(rhs_value, _)) => match lhs_value {
|
|
||||||
// Only allows string or char
|
|
||||||
Dynamic(Union::Str(s, _)) => Ok(rhs_value.contains(s.as_str()).into()),
|
|
||||||
Dynamic(Union::Char(c, _)) => Ok(rhs_value.contains(c).into()),
|
|
||||||
_ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
|
|
||||||
},
|
|
||||||
_ => EvalAltResult::ErrorInExpr(rhs.position()).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate an expression.
|
/// Evaluate an expression.
|
||||||
pub(crate) fn eval_expr(
|
pub(crate) fn eval_expr(
|
||||||
&self,
|
&self,
|
||||||
@ -1691,16 +1655,11 @@ impl Engine {
|
|||||||
.map(|(val, _)| val.take_or_clone()),
|
.map(|(val, _)| val.take_or_clone()),
|
||||||
|
|
||||||
// Statement block
|
// Statement block
|
||||||
Expr::Stmt(x, _) => self.eval_stmt_block(
|
Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT),
|
||||||
scope,
|
Expr::Stmt(x) => {
|
||||||
mods,
|
let statements = &x.statements;
|
||||||
state,
|
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
||||||
lib,
|
}
|
||||||
this_ptr,
|
|
||||||
x.as_ref().as_ref(),
|
|
||||||
true,
|
|
||||||
level,
|
|
||||||
),
|
|
||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -1745,13 +1704,13 @@ impl Engine {
|
|||||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||||
let FnCallExpr {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
capture: cap_scope,
|
capture,
|
||||||
hash,
|
hash,
|
||||||
args,
|
args,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
self.make_function_call(
|
self.make_function_call(
|
||||||
scope, mods, state, lib, this_ptr, name, args, *hash, *pos, *cap_scope, level,
|
scope, mods, state, lib, this_ptr, name, args, *hash, *pos, *capture, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1771,10 +1730,6 @@ impl Engine {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::In(x, _) => {
|
|
||||||
self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.lhs, &x.rhs, level)
|
|
||||||
}
|
|
||||||
|
|
||||||
Expr::And(x, _) => {
|
Expr::And(x, _) => {
|
||||||
Ok((self
|
Ok((self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
|
||||||
@ -1844,6 +1799,10 @@ impl Engine {
|
|||||||
restore_prev_state: bool,
|
restore_prev_state: bool,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
|
if statements.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
let mut _extra_fn_resolution_cache = false;
|
let mut _extra_fn_resolution_cache = false;
|
||||||
let prev_always_search = state.always_search;
|
let prev_always_search = state.always_search;
|
||||||
let prev_scope_len = scope.len();
|
let prev_scope_len = scope.len();
|
||||||
@ -2065,23 +2024,43 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Block scope
|
// Block scope
|
||||||
|
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
|
||||||
Stmt::Block(statements, _) => {
|
Stmt::Block(statements, _) => {
|
||||||
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
self.eval_stmt_block(scope, mods, state, lib, this_ptr, statements, true, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If statement
|
// If statement
|
||||||
Stmt::If(expr, x, _) => {
|
Stmt::If(expr, x, _) => {
|
||||||
let (if_block, else_block) = x.as_ref();
|
let (
|
||||||
|
StmtBlock {
|
||||||
|
statements: if_stmt,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
StmtBlock {
|
||||||
|
statements: else_stmt,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = x.as_ref();
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
||||||
.and_then(|guard_val| {
|
.and_then(|guard_val| {
|
||||||
if guard_val {
|
if guard_val {
|
||||||
self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level)
|
if !if_stmt.is_empty() {
|
||||||
} else if let Some(stmt) = else_block {
|
self.eval_stmt_block(
|
||||||
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
|
scope, mods, state, lib, this_ptr, if_stmt, true, level,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
}
|
||||||
} else {
|
} 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);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
table
|
table.get(&hash).map(|StmtBlock { statements, .. }| {
|
||||||
.get(&hash)
|
if !statements.is_empty() {
|
||||||
.map(|stmt| self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level))
|
self.eval_stmt_block(
|
||||||
|
scope, mods, state, lib, this_ptr, statements, true, level,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// Non-hashable values never match any specific clause
|
// Non-hashable values never match any specific clause
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// Default match clause
|
// Default match clause
|
||||||
def_stmt.as_ref().map_or_else(
|
let def_stmt = &def_stmt.statements;
|
||||||
|| Ok(Dynamic::UNIT),
|
if !def_stmt.is_empty() {
|
||||||
|def_stmt| {
|
self.eval_stmt_block(
|
||||||
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
|
scope, mods, state, lib, this_ptr, def_stmt, true, level,
|
||||||
},
|
)
|
||||||
)
|
} else {
|
||||||
|
Ok(Dynamic::UNIT)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// While loop
|
// While loop
|
||||||
Stmt::While(expr, body, _) => loop {
|
Stmt::While(expr, body, _) => {
|
||||||
let condition = if let Some(expr) = expr {
|
let body = &body.statements;
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
loop {
|
||||||
.as_bool()
|
let condition = if !expr.is_unit() {
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
|
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
} else {
|
.as_bool()
|
||||||
true
|
.map_err(|err| {
|
||||||
};
|
self.make_type_mismatch_err::<bool>(err, expr.position())
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
if condition {
|
if !condition {
|
||||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
if body.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||||
|
{
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
EvalAltResult::LoopBreak(false, _) => (),
|
EvalAltResult::LoopBreak(false, _) => (),
|
||||||
@ -2134,40 +2132,46 @@ impl Engine {
|
|||||||
_ => return Err(err),
|
_ => return Err(err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return Ok(Dynamic::UNIT);
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// Do loop
|
// Do loop
|
||||||
Stmt::Do(body, expr, is_while, _) => loop {
|
Stmt::Do(body, expr, is_while, _) => {
|
||||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
|
let body = &body.statements;
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => match *err {
|
|
||||||
EvalAltResult::LoopBreak(false, _) => continue,
|
|
||||||
EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
|
||||||
_ => return Err(err),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if self
|
loop {
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
if !body.is_empty() {
|
||||||
.as_bool()
|
match self
|
||||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
|
.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
|
||||||
{
|
{
|
||||||
if !*is_while {
|
Ok(_) => (),
|
||||||
return Ok(Dynamic::UNIT);
|
Err(err) => match *err {
|
||||||
|
EvalAltResult::LoopBreak(false, _) => continue,
|
||||||
|
EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
||||||
|
_ => return Err(err),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if *is_while {
|
if self
|
||||||
return Ok(Dynamic::UNIT);
|
.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
|
// For loop
|
||||||
Stmt::For(expr, x, _) => {
|
Stmt::For(expr, x, _) => {
|
||||||
let (name, stmt) = x.as_ref();
|
let (name, StmtBlock { statements, pos }) = x.as_ref();
|
||||||
let iter_obj = self
|
let iter_obj = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -2216,9 +2220,15 @@ impl Engine {
|
|||||||
*loop_var = value;
|
*loop_var = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inc_operations(state, stmt.position())?;
|
self.inc_operations(state, *pos)?;
|
||||||
|
|
||||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) {
|
if statements.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.eval_stmt_block(
|
||||||
|
scope, mods, state, lib, this_ptr, statements, true, level,
|
||||||
|
) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
EvalAltResult::LoopBreak(false, _) => (),
|
EvalAltResult::LoopBreak(false, _) => (),
|
||||||
@ -2244,10 +2254,20 @@ impl Engine {
|
|||||||
|
|
||||||
// Try/Catch statement
|
// Try/Catch statement
|
||||||
Stmt::TryCatch(x, _, _) => {
|
Stmt::TryCatch(x, _, _) => {
|
||||||
let (try_body, err_var, catch_body) = x.as_ref();
|
let (
|
||||||
|
StmtBlock {
|
||||||
|
statements: try_body,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
err_var,
|
||||||
|
StmtBlock {
|
||||||
|
statements: catch_body,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = x.as_ref();
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, mods, state, lib, this_ptr, try_body, level)
|
.eval_stmt_block(scope, mods, state, lib, this_ptr, try_body, true, level)
|
||||||
.map(|_| Dynamic::UNIT);
|
.map(|_| Dynamic::UNIT);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@ -2306,8 +2326,9 @@ impl Engine {
|
|||||||
scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value);
|
scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result =
|
let result = self.eval_stmt_block(
|
||||||
self.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level);
|
scope, mods, state, lib, this_ptr, catch_body, true, level,
|
||||||
|
);
|
||||||
|
|
||||||
state.scope_level -= 1;
|
state.scope_level -= 1;
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
@ -2328,7 +2349,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return value
|
// Return value
|
||||||
Stmt::Return((ReturnType::Return, pos), Some(expr), _) => {
|
Stmt::Return(ReturnType::Return, Some(expr), pos) => {
|
||||||
let value = self
|
let value = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -2336,12 +2357,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty return
|
// Empty return
|
||||||
Stmt::Return((ReturnType::Return, pos), None, _) => {
|
Stmt::Return(ReturnType::Return, None, pos) => {
|
||||||
EvalAltResult::Return(Default::default(), *pos).into()
|
EvalAltResult::Return(Default::default(), *pos).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw value
|
// Throw value
|
||||||
Stmt::Return((ReturnType::Exception, pos), Some(expr), _) => {
|
Stmt::Return(ReturnType::Exception, Some(expr), pos) => {
|
||||||
let value = self
|
let value = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -2349,38 +2370,34 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty throw
|
// Empty throw
|
||||||
Stmt::Return((ReturnType::Exception, pos), None, _) => {
|
Stmt::Return(ReturnType::Exception, None, pos) => {
|
||||||
EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()
|
EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let/const statement
|
// Let/const statement
|
||||||
Stmt::Let(var_def, expr, export, _) | Stmt::Const(var_def, expr, export, _) => {
|
Stmt::Let(expr, Ident { name, .. }, export, _)
|
||||||
|
| Stmt::Const(expr, Ident { name, .. }, export, _) => {
|
||||||
let entry_type = match stmt {
|
let entry_type = match stmt {
|
||||||
Stmt::Let(_, _, _, _) => AccessMode::ReadWrite,
|
Stmt::Let(_, _, _, _) => AccessMode::ReadWrite,
|
||||||
Stmt::Const(_, _, _, _) => AccessMode::ReadOnly,
|
Stmt::Const(_, _, _, _) => AccessMode::ReadOnly,
|
||||||
_ => unreachable!("should be Stmt::Let or Stmt::Const, but gets {:?}", stmt),
|
_ => unreachable!("should be Stmt::Let or Stmt::Const, but gets {:?}", stmt),
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = if let Some(expr) = expr {
|
let value = self
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten()
|
.flatten();
|
||||||
} else {
|
|
||||||
Dynamic::UNIT
|
|
||||||
};
|
|
||||||
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
|
let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() {
|
||||||
(
|
(
|
||||||
var_def.name.to_string().into(),
|
name.to_string().into(),
|
||||||
if *export {
|
if *export { Some(name.clone()) } else { None },
|
||||||
Some(var_def.name.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
} else if *export {
|
} else if *export {
|
||||||
unreachable!("exported variable not on global level");
|
unreachable!("exported variable not on global level");
|
||||||
} else {
|
} else {
|
||||||
(unsafe_cast_var_name_to_lifetime(&var_def.name).into(), None)
|
(unsafe_cast_var_name_to_lifetime(name).into(), None)
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.push_dynamic_value(var_name, entry_type, value);
|
scope.push_dynamic_value(var_name, entry_type, value);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -2392,7 +2409,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Import statement
|
// Import statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, alias, _pos) => {
|
Stmt::Import(expr, export, _pos) => {
|
||||||
// Guard against too many modules
|
// Guard against too many modules
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if state.modules >= self.max_modules() {
|
if state.modules >= self.max_modules() {
|
||||||
@ -2419,14 +2436,14 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
.unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?;
|
.unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?;
|
||||||
|
|
||||||
if let Some(name_def) = alias {
|
if let Some(name) = export.as_ref().map(|x| x.name.clone()) {
|
||||||
if !module.is_indexed() {
|
if !module.is_indexed() {
|
||||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||||
module.build_index();
|
module.build_index();
|
||||||
mods.push(name_def.name.clone(), module);
|
mods.push(name, module);
|
||||||
} else {
|
} else {
|
||||||
mods.push(name_def.name.clone(), module);
|
mods.push(name, module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2441,14 +2458,13 @@ impl Engine {
|
|||||||
// Export statement
|
// Export statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Export(list, _) => {
|
Stmt::Export(list, _) => {
|
||||||
for (Ident { name, pos: id_pos }, rename) in list.iter() {
|
for (Ident { name, pos, .. }, rename) in list.iter() {
|
||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
if let Some(index) = scope.get_index(name).map(|(i, _)| i) {
|
||||||
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name);
|
||||||
scope.add_entry_alias(index, alias.clone());
|
scope.add_entry_alias(index, alias.clone());
|
||||||
} else {
|
} else {
|
||||||
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *id_pos)
|
return EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos).into();
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
@ -2482,6 +2498,7 @@ impl Engine {
|
|||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
/// Check a result to ensure that the data size is within allowable limit.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[inline(always)]
|
||||||
fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult {
|
fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult {
|
||||||
// Simply return all errors
|
// Simply return all errors
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
|
@ -926,8 +926,9 @@ impl Engine {
|
|||||||
if !resolver.contains_path(s) && !imports.contains(s) =>
|
if !resolver.contains_path(s) && !imports.contains(s) =>
|
||||||
{
|
{
|
||||||
imports.insert(s.clone());
|
imports.insert(s.clone());
|
||||||
|
true
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1832,7 +1833,7 @@ impl Engine {
|
|||||||
let lib = Default::default();
|
let lib = Default::default();
|
||||||
|
|
||||||
let stmt = crate::stdlib::mem::take(ast.statements_mut());
|
let stmt = crate::stdlib::mem::take(ast.statements_mut());
|
||||||
crate::optimize::optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level)
|
||||||
}
|
}
|
||||||
/// Generate a list of all registered functions.
|
/// Generate a list of all registered functions.
|
||||||
///
|
///
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! Built-in implementations for common operators.
|
//! Built-in implementations for common operators.
|
||||||
|
|
||||||
|
use crate::engine::OP_CONTAINS;
|
||||||
use crate::fn_native::{FnCallArgs, NativeCallContext};
|
use crate::fn_native::{FnCallArgs, NativeCallContext};
|
||||||
use crate::stdlib::{any::TypeId, format, string::ToString};
|
use crate::stdlib::{any::TypeId, format, string::ToString};
|
||||||
use crate::{Dynamic, ImmutableString, RhaiResult, INT};
|
use crate::{Dynamic, ImmutableString, RhaiResult, INT};
|
||||||
@ -77,6 +78,13 @@ pub fn get_builtin_binary_op_fn(
|
|||||||
Ok((x $op y).into())
|
Ok((x $op y).into())
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
($xx:ident . $func:ident ( $yy:ty )) => {
|
||||||
|
return Some(|_, args| {
|
||||||
|
let x = &*args[0].read_lock::<$xx>().unwrap();
|
||||||
|
let y = &*args[1].read_lock::<$yy>().unwrap();
|
||||||
|
Ok(x.$func(y).into())
|
||||||
|
})
|
||||||
|
};
|
||||||
($func:ident ( $op:tt )) => {
|
($func:ident ( $op:tt )) => {
|
||||||
return Some(|_, args| {
|
return Some(|_, args| {
|
||||||
let (x, y) = $func(args);
|
let (x, y) = $func(args);
|
||||||
@ -259,6 +267,24 @@ pub fn get_builtin_binary_op_fn(
|
|||||||
">=" => impl_op!(get_s1s2(>=)),
|
">=" => impl_op!(get_s1s2(>=)),
|
||||||
"<" => impl_op!(get_s1s2(<)),
|
"<" => impl_op!(get_s1s2(<)),
|
||||||
"<=" => impl_op!(get_s1s2(<=)),
|
"<=" => impl_op!(get_s1s2(<=)),
|
||||||
|
OP_CONTAINS => {
|
||||||
|
return Some(|_, args| {
|
||||||
|
let s = &*args[0].read_lock::<ImmutableString>().unwrap();
|
||||||
|
let c = args[1].as_char().unwrap();
|
||||||
|
Ok((s.contains(c)).into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map op string
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
if types_pair == (TypeId::of::<crate::Map>(), TypeId::of::<ImmutableString>()) {
|
||||||
|
use crate::Map;
|
||||||
|
|
||||||
|
match op {
|
||||||
|
OP_CONTAINS => impl_op!(Map.contains_key(ImmutableString)),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,6 +368,13 @@ pub fn get_builtin_binary_op_fn(
|
|||||||
">=" => impl_op!(ImmutableString >= ImmutableString),
|
">=" => impl_op!(ImmutableString >= ImmutableString),
|
||||||
"<" => impl_op!(ImmutableString < ImmutableString),
|
"<" => impl_op!(ImmutableString < ImmutableString),
|
||||||
"<=" => impl_op!(ImmutableString <= ImmutableString),
|
"<=" => impl_op!(ImmutableString <= ImmutableString),
|
||||||
|
OP_CONTAINS => {
|
||||||
|
return Some(|_, args| {
|
||||||
|
let s1 = &*args[0].read_lock::<ImmutableString>().unwrap();
|
||||||
|
let s2 = &*args[1].read_lock::<ImmutableString>().unwrap();
|
||||||
|
Ok((s1.contains(s2.as_str())).into())
|
||||||
|
})
|
||||||
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
107
src/fn_call.rs
107
src/fn_call.rs
@ -493,6 +493,10 @@ impl Engine {
|
|||||||
|
|
||||||
self.inc_operations(state, pos)?;
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
|
if fn_def.body.is_empty() {
|
||||||
|
return Ok(Dynamic::UNIT);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -539,10 +543,10 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the function
|
// Evaluate the function
|
||||||
let stmt = &fn_def.body;
|
let body = &fn_def.body.statements;
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, mods, state, unified_lib, this_ptr, stmt, level)
|
.eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
EvalAltResult::Return(x, _) => Ok(x),
|
||||||
@ -722,6 +726,10 @@ impl Engine {
|
|||||||
|
|
||||||
let func = func.get_fn_def();
|
let func = func.get_fn_def();
|
||||||
|
|
||||||
|
if func.body.is_empty() {
|
||||||
|
return Ok((Dynamic::UNIT, false));
|
||||||
|
}
|
||||||
|
|
||||||
let scope: &mut Scope = &mut Default::default();
|
let scope: &mut Scope = &mut Default::default();
|
||||||
|
|
||||||
// Move captured variables into scope
|
// Move captured variables into scope
|
||||||
@ -831,6 +839,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a text script in place - used primarily for 'eval'.
|
/// Evaluate a text script in place - used primarily for 'eval'.
|
||||||
|
#[inline]
|
||||||
fn eval_script_expr_in_place(
|
fn eval_script_expr_in_place(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -884,7 +893,7 @@ impl Engine {
|
|||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
mut hash: FnHash,
|
mut hash: FnHash,
|
||||||
target: &mut crate::engine::Target,
|
target: &mut crate::engine::Target,
|
||||||
mut call_args: StaticVec<Dynamic>,
|
(call_args, call_arg_positions): &mut (StaticVec<Dynamic>, StaticVec<Position>),
|
||||||
pos: Position,
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
@ -902,7 +911,7 @@ impl Engine {
|
|||||||
let fn_name = fn_ptr.fn_name();
|
let fn_name = fn_ptr.fn_name();
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||||
// Recalculate hashes
|
// Recalculate hashes
|
||||||
let hash = FnHash::from_script(calc_fn_hash(empty(), fn_name, args_len));
|
let new_hash = FnHash::from_script(calc_fn_hash(empty(), fn_name, args_len));
|
||||||
// Arguments are passed as-is, adding the curried arguments
|
// Arguments are passed as-is, adding the curried arguments
|
||||||
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||||
let mut arg_values = curry
|
let mut arg_values = curry
|
||||||
@ -913,17 +922,32 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, fn_name, hash, args, false, false, pos, None, level,
|
mods, state, lib, fn_name, new_hash, args, false, false, pos, None, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KEYWORD_FN_PTR_CALL if call_args.len() > 0 && call_args[0].is::<FnPtr>() => {
|
KEYWORD_FN_PTR_CALL => {
|
||||||
|
if call_args.len() > 0 {
|
||||||
|
if !call_args[0].is::<FnPtr>() {
|
||||||
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
|
self.map_type_name(obj.type_name()),
|
||||||
|
call_arg_positions[0],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
|
self.map_type_name(obj.type_name()),
|
||||||
|
pos,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// FnPtr call on object
|
// FnPtr call on object
|
||||||
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
|
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
|
||||||
|
call_arg_positions.remove(0);
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
let fn_name = fn_ptr.fn_name();
|
let fn_name = fn_ptr.fn_name();
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
let hash = FnHash::from_script_and_native(
|
let new_hash = FnHash::from_script_and_native(
|
||||||
calc_fn_hash(empty(), fn_name, args_len),
|
calc_fn_hash(empty(), fn_name, args_len),
|
||||||
calc_fn_hash(empty(), fn_name, args_len + 1),
|
calc_fn_hash(empty(), fn_name, args_len + 1),
|
||||||
);
|
);
|
||||||
@ -937,22 +961,34 @@ impl Engine {
|
|||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level,
|
mods, state, lib, fn_name, new_hash, args, is_ref, true, pos, None, level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KEYWORD_FN_PTR_CURRY if obj.is::<FnPtr>() => {
|
KEYWORD_FN_PTR_CURRY => {
|
||||||
// Curry call
|
if !obj.is::<FnPtr>() {
|
||||||
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
|
self.map_type_name(obj.type_name()),
|
||||||
|
pos,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
||||||
|
|
||||||
|
// Curry call
|
||||||
Ok((
|
Ok((
|
||||||
FnPtr::new_unchecked(
|
if call_args.is_empty() {
|
||||||
fn_ptr.get_fn_name().clone(),
|
fn_ptr.clone()
|
||||||
fn_ptr
|
} else {
|
||||||
.curry()
|
FnPtr::new_unchecked(
|
||||||
.iter()
|
fn_ptr.get_fn_name().clone(),
|
||||||
.cloned()
|
fn_ptr
|
||||||
.chain(call_args.into_iter())
|
.curry()
|
||||||
.collect(),
|
.iter()
|
||||||
)
|
.cloned()
|
||||||
|
.chain(call_args.iter_mut().map(|v| mem::take(v)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
.into(),
|
.into(),
|
||||||
false,
|
false,
|
||||||
))
|
))
|
||||||
@ -981,7 +1017,10 @@ impl Engine {
|
|||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(i, v)| call_args.insert(i, v));
|
.for_each(|(i, v)| {
|
||||||
|
call_args.insert(i, v);
|
||||||
|
call_arg_positions.insert(i, Position::NONE);
|
||||||
|
});
|
||||||
// Recalculate the hash based on the new function name and new arguments
|
// Recalculate the hash based on the new function name and new arguments
|
||||||
hash = FnHash::from_script_and_native(
|
hash = FnHash::from_script_and_native(
|
||||||
calc_fn_hash(empty(), fn_name, call_args.len()),
|
calc_fn_hash(empty(), fn_name, call_args.len()),
|
||||||
@ -1065,7 +1104,6 @@ impl Engine {
|
|||||||
FnHash::from_native(calc_fn_hash(empty(), name, args_len))
|
FnHash::from_native(calc_fn_hash(empty(), name, args_len))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Fn()
|
// Handle Fn()
|
||||||
KEYWORD_FN_PTR if args_expr.len() == 1 => {
|
KEYWORD_FN_PTR if args_expr.len() == 1 => {
|
||||||
// Fn - only in function call style
|
// Fn - only in function call style
|
||||||
@ -1361,22 +1399,27 @@ impl Engine {
|
|||||||
match func {
|
match func {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Some(f) if f.is_script() => {
|
Some(f) if f.is_script() => {
|
||||||
let args = args.as_mut();
|
let fn_def = f.get_fn_def();
|
||||||
let new_scope = &mut Default::default();
|
|
||||||
let fn_def = f.get_fn_def().clone();
|
|
||||||
|
|
||||||
let mut source = module.id_raw().cloned();
|
if fn_def.body.is_empty() {
|
||||||
mem::swap(&mut state.source, &mut source);
|
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(
|
let level = level + 1;
|
||||||
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
Some(f) if f.is_plugin_fn() => f
|
||||||
|
@ -124,7 +124,7 @@ pub type FLOAT = f32;
|
|||||||
|
|
||||||
pub use ast::{FnAccess, AST};
|
pub use ast::{FnAccess, AST};
|
||||||
pub use dynamic::Dynamic;
|
pub use dynamic::Dynamic;
|
||||||
pub use engine::{Engine, EvalContext};
|
pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS};
|
||||||
pub use fn_native::{FnPtr, NativeCallContext};
|
pub use fn_native::{FnPtr, NativeCallContext};
|
||||||
pub use fn_register::{RegisterFn, RegisterResultFn};
|
pub use fn_register::{RegisterFn, RegisterResultFn};
|
||||||
pub use module::{FnNamespace, Module};
|
pub use module::{FnNamespace, Module};
|
||||||
@ -192,7 +192,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni
|
|||||||
#[deprecated = "this type is volatile and may change"]
|
#[deprecated = "this type is volatile and may change"]
|
||||||
pub use ast::{
|
pub use ast::{
|
||||||
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment,
|
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment,
|
||||||
ReturnType, ScriptFnDef, Stmt,
|
ReturnType, ScriptFnDef, Stmt, StmtBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
|
@ -39,6 +39,7 @@ pub enum FnNamespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FnNamespace {
|
impl Default for FnNamespace {
|
||||||
|
#[inline(always)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Internal
|
Self::Internal
|
||||||
}
|
}
|
||||||
@ -150,6 +151,7 @@ pub struct Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Module {
|
impl Default for Module {
|
||||||
|
#[inline(always)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: None,
|
id: None,
|
||||||
@ -226,6 +228,7 @@ impl AsRef<Module> for Module {
|
|||||||
impl<M: AsRef<Module>> Add<M> for &Module {
|
impl<M: AsRef<Module>> Add<M> for &Module {
|
||||||
type Output = Module;
|
type Output = Module;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn add(self, rhs: M) -> Self::Output {
|
fn add(self, rhs: M) -> Self::Output {
|
||||||
let mut module = self.clone();
|
let mut module = self.clone();
|
||||||
module.merge(rhs.as_ref());
|
module.merge(rhs.as_ref());
|
||||||
@ -236,6 +239,7 @@ impl<M: AsRef<Module>> Add<M> for &Module {
|
|||||||
impl<M: AsRef<Module>> Add<M> for Module {
|
impl<M: AsRef<Module>> Add<M> for Module {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn add(mut self, rhs: M) -> Self::Output {
|
fn add(mut self, rhs: M) -> Self::Output {
|
||||||
self.merge(rhs.as_ref());
|
self.merge(rhs.as_ref());
|
||||||
self
|
self
|
||||||
@ -243,6 +247,7 @@ impl<M: AsRef<Module>> Add<M> for Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Into<Module>> AddAssign<M> for Module {
|
impl<M: Into<Module>> AddAssign<M> for Module {
|
||||||
|
#[inline(always)]
|
||||||
fn add_assign(&mut self, rhs: M) {
|
fn add_assign(&mut self, rhs: M) {
|
||||||
self.combine(rhs.into());
|
self.combine(rhs.into());
|
||||||
}
|
}
|
||||||
@ -1953,16 +1958,19 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Does a type iterator exist in the entire module tree?
|
/// Does a type iterator exist in the entire module tree?
|
||||||
|
#[inline(always)]
|
||||||
pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
|
pub fn contains_qualified_iter(&self, id: TypeId) -> bool {
|
||||||
self.all_type_iterators.contains_key(&id)
|
self.all_type_iterators.contains_key(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does a type iterator exist in the module?
|
/// Does a type iterator exist in the module?
|
||||||
|
#[inline(always)]
|
||||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||||
self.type_iterators.contains_key(&id)
|
self.type_iterators.contains_key(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a type iterator into the [`Module`].
|
/// Set a type iterator into the [`Module`].
|
||||||
|
#[inline(always)]
|
||||||
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) -> &mut Self {
|
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) -> &mut Self {
|
||||||
self.type_iterators.insert(typ, func);
|
self.type_iterators.insert(typ, func);
|
||||||
self.indexed = false;
|
self.indexed = false;
|
||||||
@ -1971,6 +1979,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set a type iterator into the [`Module`].
|
/// Set a type iterator into the [`Module`].
|
||||||
|
#[inline(always)]
|
||||||
pub fn set_iterable<T>(&mut self) -> &mut Self
|
pub fn set_iterable<T>(&mut self) -> &mut Self
|
||||||
where
|
where
|
||||||
T: Variant + Clone + IntoIterator,
|
T: Variant + Clone + IntoIterator,
|
||||||
@ -1982,6 +1991,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set an iterator type into the [`Module`] as a type iterator.
|
/// Set an iterator type into the [`Module`] as a type iterator.
|
||||||
|
#[inline(always)]
|
||||||
pub fn set_iterator<T>(&mut self) -> &mut Self
|
pub fn set_iterator<T>(&mut self) -> &mut Self
|
||||||
where
|
where
|
||||||
T: Variant + Clone + Iterator,
|
T: Variant + Clone + Iterator,
|
||||||
@ -1993,11 +2003,13 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the specified type iterator.
|
/// Get the specified type iterator.
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||||
self.all_type_iterators.get(&id).cloned()
|
self.all_type_iterators.get(&id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the specified type iterator.
|
/// Get the specified type iterator.
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||||
self.type_iterators.get(&id).cloned()
|
self.type_iterators.get(&id).cloned()
|
||||||
}
|
}
|
||||||
@ -2021,32 +2033,41 @@ pub struct NamespaceRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for NamespaceRef {
|
impl fmt::Debug for NamespaceRef {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Debug::fmt(&self.path, f)?;
|
|
||||||
|
|
||||||
if let Some(index) = self.index {
|
if let Some(index) = self.index {
|
||||||
write!(f, " -> {}", index)
|
write!(f, "{} -> ", index)?;
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.write_str(
|
||||||
|
&self
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.map(|Ident { name, .. }| name.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("::"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for NamespaceRef {
|
impl Deref for NamespaceRef {
|
||||||
type Target = StaticVec<Ident>;
|
type Target = StaticVec<Ident>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for NamespaceRef {
|
impl DerefMut for NamespaceRef {
|
||||||
|
#[inline(always)]
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.path
|
&mut self.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for NamespaceRef {
|
impl fmt::Display for NamespaceRef {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
for Ident { name, .. } in self.path.iter() {
|
for Ident { name, .. } in self.path.iter() {
|
||||||
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
||||||
@ -2056,6 +2077,7 @@ impl fmt::Display for NamespaceRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<StaticVec<Ident>> for NamespaceRef {
|
impl From<StaticVec<Ident>> for NamespaceRef {
|
||||||
|
#[inline(always)]
|
||||||
fn from(path: StaticVec<Ident>) -> Self {
|
fn from(path: StaticVec<Ident>) -> Self {
|
||||||
Self { index: None, path }
|
Self { index: None, path }
|
||||||
}
|
}
|
||||||
@ -2063,11 +2085,13 @@ impl From<StaticVec<Ident>> for NamespaceRef {
|
|||||||
|
|
||||||
impl NamespaceRef {
|
impl NamespaceRef {
|
||||||
/// Get the [`Scope`][crate::Scope] index offset.
|
/// Get the [`Scope`][crate::Scope] index offset.
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
||||||
self.index
|
self.index
|
||||||
}
|
}
|
||||||
/// Set the [`Scope`][crate::Scope] index offset.
|
/// Set the [`Scope`][crate::Scope] index offset.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||||
self.index = index
|
self.index = index
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
|||||||
pub struct FileModuleResolver {
|
pub struct FileModuleResolver {
|
||||||
base_path: PathBuf,
|
base_path: PathBuf,
|
||||||
extension: String,
|
extension: String,
|
||||||
|
cache_enabled: bool,
|
||||||
|
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>,
|
cache: crate::stdlib::cell::RefCell<HashMap<PathBuf, Shared<Module>>>,
|
||||||
@ -101,6 +102,7 @@ impl FileModuleResolver {
|
|||||||
Self {
|
Self {
|
||||||
base_path: path.into(),
|
base_path: path.into(),
|
||||||
extension: extension.into(),
|
extension: extension.into(),
|
||||||
|
cache_enabled: true,
|
||||||
cache: Default::default(),
|
cache: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,9 +154,25 @@ impl FileModuleResolver {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enable/disable the cache.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_cache(&mut self, enable: bool) -> &mut Self {
|
||||||
|
self.cache_enabled = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Is the cache enabled?
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_cache_enabled(&self) -> bool {
|
||||||
|
self.cache_enabled
|
||||||
|
}
|
||||||
|
|
||||||
/// Is a particular path cached?
|
/// Is a particular path cached?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_cached(&self, path: &str) -> bool {
|
pub fn is_cached(&self, path: &str) -> bool {
|
||||||
|
if !self.cache_enabled {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let file_path = self.get_file_path(path);
|
let file_path = self.get_file_path(path);
|
||||||
|
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
@ -211,7 +229,7 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
let file_path = self.get_file_path(path);
|
let file_path = self.get_file_path(path);
|
||||||
|
|
||||||
// See if it is cached
|
// See if it is cached
|
||||||
{
|
if self.is_cache_enabled() {
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
let c = self.cache.borrow();
|
let c = self.cache.borrow();
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
@ -242,10 +260,12 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
.into();
|
.into();
|
||||||
|
|
||||||
// Put it into the cache
|
// Put it into the cache
|
||||||
#[cfg(not(feature = "sync"))]
|
if self.is_cache_enabled() {
|
||||||
self.cache.borrow_mut().insert(file_path, m.clone());
|
#[cfg(not(feature = "sync"))]
|
||||||
#[cfg(feature = "sync")]
|
self.cache.borrow_mut().insert(file_path, m.clone());
|
||||||
self.cache.write().unwrap().insert(file_path, m.clone());
|
#[cfg(feature = "sync")]
|
||||||
|
self.cache.write().unwrap().insert(file_path, m.clone());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(m)
|
Ok(m)
|
||||||
}
|
}
|
||||||
|
579
src/optimize.rs
579
src/optimize.rs
@ -1,6 +1,6 @@
|
|||||||
//! Module implementing the [`AST`] optimizer.
|
//! Module implementing the [`AST`] optimizer.
|
||||||
|
|
||||||
use crate::ast::{Expr, Stmt};
|
use crate::ast::{Expr, Ident, Stmt, StmtBlock};
|
||||||
use crate::dynamic::AccessMode;
|
use crate::dynamic::AccessMode;
|
||||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||||
use crate::fn_builtin::get_builtin_binary_op_fn;
|
use crate::fn_builtin::get_builtin_binary_op_fn;
|
||||||
@ -15,7 +15,6 @@ use crate::stdlib::{
|
|||||||
vec,
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use crate::token::is_valid_identifier;
|
|
||||||
use crate::utils::get_hasher;
|
use crate::utils::get_hasher;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope,
|
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope,
|
||||||
@ -75,14 +74,18 @@ struct State<'a> {
|
|||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
/// Create a new State.
|
/// Create a new State.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self {
|
pub fn new(
|
||||||
|
engine: &'a Engine,
|
||||||
|
lib: &'a [&'a Module],
|
||||||
|
optimization_level: OptimizationLevel,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
changed: false,
|
changed: false,
|
||||||
variables: vec![],
|
variables: vec![],
|
||||||
propagate_constants: true,
|
propagate_constants: true,
|
||||||
engine,
|
engine,
|
||||||
lib,
|
lib,
|
||||||
optimization_level: level,
|
optimization_level,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Reset the state from dirty to clean.
|
/// Reset the state from dirty to clean.
|
||||||
@ -95,6 +98,11 @@ impl<'a> State<'a> {
|
|||||||
pub fn set_dirty(&mut self) {
|
pub fn set_dirty(&mut self) {
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
}
|
}
|
||||||
|
/// Set the [`AST`] state to be not dirty (i.e. unchanged).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn clear_dirty(&mut self) {
|
||||||
|
self.changed = false;
|
||||||
|
}
|
||||||
/// Is the [`AST`] dirty (i.e. changed)?
|
/// Is the [`AST`] dirty (i.e. changed)?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
@ -169,139 +177,112 @@ fn call_fn_with_constant_arguments(
|
|||||||
/// Optimize a block of [statements][Stmt].
|
/// Optimize a block of [statements][Stmt].
|
||||||
fn optimize_stmt_block(
|
fn optimize_stmt_block(
|
||||||
mut statements: Vec<Stmt>,
|
mut statements: Vec<Stmt>,
|
||||||
pos: Position,
|
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
preserve_result: bool,
|
preserve_result: bool,
|
||||||
count_promote_as_dirty: bool,
|
) -> Vec<Stmt> {
|
||||||
) -> Stmt {
|
if statements.is_empty() {
|
||||||
let orig_len = statements.len(); // Original number of statements in the block, for change detection
|
return statements;
|
||||||
let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later
|
}
|
||||||
let orig_propagate_constants = state.propagate_constants;
|
|
||||||
|
|
||||||
// Optimize each statement in the block
|
let mut is_dirty = state.is_dirty();
|
||||||
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);
|
|
||||||
|
|
||||||
if value_expr.is_constant() {
|
loop {
|
||||||
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone());
|
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));
|
|
||||||
}
|
// Optimize each statement in the block
|
||||||
// Add variables into the state
|
statements.iter_mut().for_each(|stmt| {
|
||||||
Stmt::Let(var_def, expr, _, _) => {
|
match stmt {
|
||||||
if let Some(value_expr) = expr {
|
// Add constant literals into the state
|
||||||
|
Stmt::Const(value_expr, Ident { name, .. }, _, _) => {
|
||||||
optimize_expr(value_expr, state);
|
optimize_expr(value_expr, state);
|
||||||
|
|
||||||
|
if value_expr.is_constant() {
|
||||||
|
state.push_var(name, AccessMode::ReadOnly, value_expr.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Add variables into the state
|
||||||
state.push_var(
|
Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => {
|
||||||
&var_def.name,
|
optimize_expr(value_expr, state);
|
||||||
AccessMode::ReadWrite,
|
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos));
|
||||||
Expr::Unit(var_def.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
|
// Remove all pure statements that do not return values at the end of a block.
|
||||||
let last_stmt = if preserve_result {
|
// We cannot remove anything for non-pure statements due to potential side-effects.
|
||||||
statements.pop()
|
if preserve_result {
|
||||||
} else {
|
loop {
|
||||||
None
|
match &statements[..] {
|
||||||
};
|
[stmt] if !stmt.returns_value() && stmt.is_internally_pure() => {
|
||||||
|
state.set_dirty();
|
||||||
statements.retain(|stmt| !stmt.is_pure());
|
statements.clear();
|
||||||
|
}
|
||||||
if let Some(stmt) = last_stmt {
|
[.., second_last_stmt, Stmt::Noop(_)] if second_last_stmt.returns_value() => {}
|
||||||
statements.push(stmt);
|
[.., second_last_stmt, last_stmt]
|
||||||
}
|
if !last_stmt.returns_value() && last_stmt.is_internally_pure() =>
|
||||||
|
{
|
||||||
// Remove all let/import statements at the end of a block - the new variables will go away anyway.
|
state.set_dirty();
|
||||||
// But be careful only remove ones that have no initial values or have values that are pure expressions,
|
if second_last_stmt.returns_value() {
|
||||||
// otherwise there may be side effects.
|
*statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
|
||||||
let mut removed = false;
|
} else {
|
||||||
|
statements.pop().unwrap();
|
||||||
while let Some(expr) = statements.pop() {
|
}
|
||||||
match expr {
|
}
|
||||||
Stmt::Let(_, expr, _, _) | Stmt::Const(_, expr, _, _) => {
|
_ => break,
|
||||||
removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true)
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
} else {
|
||||||
Stmt::Import(expr, _, _) => removed = expr.is_pure(),
|
loop {
|
||||||
_ => {
|
match &statements[..] {
|
||||||
statements.push(expr);
|
[stmt] if stmt.is_internally_pure() => {
|
||||||
break;
|
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 is_dirty {
|
||||||
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() {
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop the stack and remove all the local constants
|
statements
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize a [statement][Stmt].
|
/// Optimize a [statement][Stmt].
|
||||||
@ -316,18 +297,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// if false { if_block } -> Noop
|
// if expr {}
|
||||||
Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_none() => {
|
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
|
||||||
state.set_dirty();
|
|
||||||
*stmt = Stmt::Noop(*pos);
|
|
||||||
}
|
|
||||||
// if true { if_block } -> if_block
|
|
||||||
Stmt::If(Expr::BoolConstant(true, _), x, _) if x.1.is_none() => {
|
|
||||||
*stmt = mem::take(&mut x.0);
|
|
||||||
optimize_stmt(stmt, state, true);
|
|
||||||
}
|
|
||||||
// if expr { Noop }
|
|
||||||
Stmt::If(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = condition.position();
|
let pos = condition.position();
|
||||||
@ -336,38 +307,56 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
|
|
||||||
*stmt = if preserve_result {
|
*stmt = if preserve_result {
|
||||||
// -> { expr, Noop }
|
// -> { expr, Noop }
|
||||||
Stmt::Block(vec![Stmt::Expr(expr), mem::take(&mut x.0)], pos)
|
Stmt::Block(vec![Stmt::Expr(expr), Stmt::Noop(pos)], pos)
|
||||||
} else {
|
} else {
|
||||||
// -> expr
|
// -> expr
|
||||||
Stmt::Expr(expr)
|
Stmt::Expr(expr)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// if expr { if_block }
|
// if false { if_block } -> Noop
|
||||||
Stmt::If(condition, x, _) if x.1.is_none() => {
|
Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_empty() => {
|
||||||
optimize_expr(condition, state);
|
state.set_dirty();
|
||||||
optimize_stmt(&mut x.0, state, true);
|
*stmt = Stmt::Noop(*pos);
|
||||||
}
|
}
|
||||||
// if false { if_block } else { else_block } -> else_block
|
// if false { if_block } else { else_block } -> else_block
|
||||||
Stmt::If(Expr::BoolConstant(false, _), x, _) if x.1.is_some() => {
|
Stmt::If(Expr::BoolConstant(false, _), x, _) => {
|
||||||
*stmt = mem::take(x.1.as_mut().unwrap());
|
state.set_dirty();
|
||||||
optimize_stmt(stmt, state, true);
|
*stmt = match optimize_stmt_block(
|
||||||
|
mem::take(&mut x.1.statements).into_vec(),
|
||||||
|
state,
|
||||||
|
preserve_result,
|
||||||
|
) {
|
||||||
|
statements if statements.is_empty() => Stmt::Noop(x.1.pos),
|
||||||
|
statements => Stmt::Block(statements, x.1.pos),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if true { if_block } else { else_block } -> if_block
|
// if true { if_block } else { else_block } -> if_block
|
||||||
Stmt::If(Expr::BoolConstant(true, _), x, _) => {
|
Stmt::If(Expr::BoolConstant(true, _), x, _) => {
|
||||||
*stmt = mem::take(&mut x.0);
|
state.set_dirty();
|
||||||
optimize_stmt(stmt, state, true);
|
*stmt = match optimize_stmt_block(
|
||||||
|
mem::take(&mut x.0.statements).into_vec(),
|
||||||
|
state,
|
||||||
|
preserve_result,
|
||||||
|
) {
|
||||||
|
statements if statements.is_empty() => Stmt::Noop(x.0.pos),
|
||||||
|
statements => Stmt::Block(statements, x.0.pos),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
Stmt::If(condition, x, _) => {
|
Stmt::If(condition, x, _) => {
|
||||||
optimize_expr(condition, state);
|
optimize_expr(condition, state);
|
||||||
optimize_stmt(&mut x.0, state, true);
|
x.0.statements = optimize_stmt_block(
|
||||||
if let Some(else_block) = x.1.as_mut() {
|
mem::take(&mut x.0.statements).into_vec(),
|
||||||
optimize_stmt(else_block, state, true);
|
state,
|
||||||
match else_block {
|
preserve_result,
|
||||||
Stmt::Noop(_) => x.1 = None, // Noop -> no else block
|
)
|
||||||
_ => (),
|
.into();
|
||||||
}
|
x.1.statements = optimize_stmt_block(
|
||||||
}
|
mem::take(&mut x.1.statements).into_vec(),
|
||||||
|
state,
|
||||||
|
preserve_result,
|
||||||
|
)
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch const { ... }
|
// switch const { ... }
|
||||||
@ -381,117 +370,144 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
|
|
||||||
let table = &mut x.0;
|
let table = &mut x.0;
|
||||||
|
|
||||||
if let Some(stmt) = table.get_mut(&hash) {
|
let (statements, new_pos) = if let Some(block) = table.get_mut(&hash) {
|
||||||
optimize_stmt(stmt, state, true);
|
(
|
||||||
*expr = Expr::Stmt(Box::new(vec![mem::take(stmt)].into()), *pos);
|
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, true)
|
||||||
} else if let Some(def_stmt) = x.1.as_mut() {
|
.into(),
|
||||||
optimize_stmt(def_stmt, state, true);
|
block.pos,
|
||||||
*expr = Expr::Stmt(Box::new(vec![mem::take(def_stmt)].into()), *pos);
|
)
|
||||||
} else {
|
} else {
|
||||||
*expr = Expr::Unit(*pos);
|
(
|
||||||
}
|
optimize_stmt_block(mem::take(&mut x.1.statements).into_vec(), state, true)
|
||||||
|
.into(),
|
||||||
|
if x.1.pos.is_none() { *pos } else { x.1.pos },
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
*expr = Expr::Stmt(Box::new(StmtBlock {
|
||||||
|
statements,
|
||||||
|
pos: new_pos,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
// switch
|
// switch
|
||||||
Stmt::Switch(expr, x, _) => {
|
Stmt::Switch(expr, x, _) => {
|
||||||
optimize_expr(expr, state);
|
optimize_expr(expr, state);
|
||||||
x.0.values_mut()
|
x.0.values_mut().for_each(|block| {
|
||||||
.for_each(|stmt| optimize_stmt(stmt, state, true));
|
block.statements = optimize_stmt_block(
|
||||||
if let Some(def_stmt) = x.1.as_mut() {
|
mem::take(&mut block.statements).into_vec(),
|
||||||
optimize_stmt(def_stmt, state, true);
|
state,
|
||||||
|
preserve_result,
|
||||||
match def_stmt {
|
)
|
||||||
Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.1 = None,
|
.into()
|
||||||
_ => (),
|
});
|
||||||
}
|
x.1.statements = optimize_stmt_block(
|
||||||
}
|
mem::take(&mut x.1.statements).into_vec(),
|
||||||
|
state,
|
||||||
|
preserve_result,
|
||||||
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// while false { block } -> Noop
|
// while false { block } -> Noop
|
||||||
Stmt::While(Some(Expr::BoolConstant(false, pos)), _, _) => {
|
Stmt::While(Expr::BoolConstant(false, pos), _, _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Noop(*pos)
|
*stmt = Stmt::Noop(*pos)
|
||||||
}
|
}
|
||||||
// while expr { block }
|
// while expr { block }
|
||||||
Stmt::While(condition, block, _) => {
|
Stmt::While(condition, block, _) => {
|
||||||
optimize_stmt(block, state, false);
|
optimize_expr(condition, state);
|
||||||
|
|
||||||
if let Some(condition) = condition {
|
block.statements =
|
||||||
optimize_expr(condition, state);
|
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false)
|
||||||
}
|
.into();
|
||||||
|
|
||||||
match **block {
|
if block.len() == 1 {
|
||||||
// while expr { break; } -> { expr; }
|
match block.statements[0] {
|
||||||
Stmt::Break(pos) => {
|
// while expr { break; } -> { expr; }
|
||||||
// Only a single break statement - turn into running the guard expression once
|
Stmt::Break(pos) => {
|
||||||
state.set_dirty();
|
// Only a single break statement - turn into running the guard expression once
|
||||||
if let Some(condition) = condition {
|
state.set_dirty();
|
||||||
let mut statements = vec![Stmt::Expr(mem::take(condition))];
|
if !condition.is_unit() {
|
||||||
if preserve_result {
|
let mut statements = vec![Stmt::Expr(mem::take(condition))];
|
||||||
statements.push(Stmt::Noop(pos))
|
if preserve_result {
|
||||||
}
|
statements.push(Stmt::Noop(pos))
|
||||||
*stmt = Stmt::Block(statements, pos);
|
}
|
||||||
} else {
|
*stmt = Stmt::Block(statements, pos);
|
||||||
*stmt = Stmt::Noop(pos);
|
} else {
|
||||||
};
|
*stmt = Stmt::Noop(pos);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// do { block } while false | do { block } until true -> { block }
|
// do { block } while false | do { block } until true -> { block }
|
||||||
Stmt::Do(block, Expr::BoolConstant(true, _), false, _)
|
Stmt::Do(block, Expr::BoolConstant(true, _), false, _)
|
||||||
| Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => {
|
| Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
optimize_stmt(block.as_mut(), state, false);
|
*stmt = Stmt::Block(
|
||||||
*stmt = mem::take(block.as_mut());
|
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false),
|
||||||
|
block.pos,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// do { block } while|until expr
|
// do { block } while|until expr
|
||||||
Stmt::Do(block, condition, _, _) => {
|
Stmt::Do(block, condition, _, _) => {
|
||||||
optimize_stmt(block.as_mut(), state, false);
|
|
||||||
optimize_expr(condition, state);
|
optimize_expr(condition, state);
|
||||||
|
block.statements =
|
||||||
|
optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false)
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
// for id in expr { block }
|
// for id in expr { block }
|
||||||
Stmt::For(iterable, x, _) => {
|
Stmt::For(iterable, x, _) => {
|
||||||
optimize_expr(iterable, state);
|
optimize_expr(iterable, state);
|
||||||
optimize_stmt(&mut x.1, state, false);
|
x.1.statements =
|
||||||
|
optimize_stmt_block(mem::take(&mut x.1.statements).into_vec(), state, false).into();
|
||||||
}
|
}
|
||||||
// let id = expr;
|
// let id = expr;
|
||||||
Stmt::Let(_, Some(expr), _, _) => optimize_expr(expr, state),
|
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state),
|
||||||
// let id;
|
|
||||||
Stmt::Let(_, None, _, _) => (),
|
|
||||||
// import expr as var;
|
// import expr as var;
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, _, _) => optimize_expr(expr, state),
|
Stmt::Import(expr, _, _) => optimize_expr(expr, state),
|
||||||
// { block }
|
// { block }
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, pos) => {
|
||||||
*stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true);
|
*stmt = match optimize_stmt_block(mem::take(statements), state, preserve_result) {
|
||||||
|
statements if statements.is_empty() => {
|
||||||
|
state.set_dirty();
|
||||||
|
Stmt::Noop(*pos)
|
||||||
|
}
|
||||||
|
// 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 }
|
// try { pure block } catch ( var ) { block }
|
||||||
Stmt::TryCatch(x, _, _) if x.0.is_pure() => {
|
Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => {
|
||||||
// If try block is pure, there will never be any exceptions
|
// If try block is pure, there will never be any exceptions
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let pos = x.0.position();
|
*stmt = Stmt::Block(
|
||||||
optimize_stmt(&mut x.0, state, preserve_result);
|
optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false),
|
||||||
let mut statements = match mem::take(&mut x.0) {
|
x.0.pos,
|
||||||
Stmt::Block(statements, _) => statements,
|
);
|
||||||
stmt => vec![stmt],
|
|
||||||
};
|
|
||||||
statements.push(Stmt::Noop(pos));
|
|
||||||
*stmt = Stmt::Block(statements, pos);
|
|
||||||
}
|
}
|
||||||
// try { block } catch ( var ) { block }
|
// try { block } catch ( var ) { block }
|
||||||
Stmt::TryCatch(x, _, _) => {
|
Stmt::TryCatch(x, _, _) => {
|
||||||
optimize_stmt(&mut x.0, state, false);
|
x.0.statements =
|
||||||
optimize_stmt(&mut x.2, state, false);
|
optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false).into();
|
||||||
|
x.2.statements =
|
||||||
|
optimize_stmt_block(mem::take(&mut x.2.statements).into_vec(), state, false).into();
|
||||||
}
|
}
|
||||||
// {}
|
// {}
|
||||||
Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => {
|
Stmt::Expr(Expr::Stmt(x)) if x.statements.is_empty() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Noop(*pos);
|
*stmt = Stmt::Noop(x.pos);
|
||||||
}
|
}
|
||||||
// {...};
|
// {...};
|
||||||
Stmt::Expr(Expr::Stmt(x, pos)) => {
|
Stmt::Expr(Expr::Stmt(x)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Block(mem::take(x).into_vec(), *pos);
|
*stmt = Stmt::Block(mem::take(&mut x.statements).into_vec(), x.pos);
|
||||||
}
|
}
|
||||||
// expr;
|
// expr;
|
||||||
Stmt::Expr(expr) => optimize_expr(expr, state),
|
Stmt::Expr(expr) => optimize_expr(expr, state),
|
||||||
@ -514,25 +530,15 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
// {}
|
// {}
|
||||||
Expr::Stmt(x, pos) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(*pos) }
|
Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) }
|
||||||
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
|
||||||
Expr::Stmt(x, pos) => match optimize_stmt_block(mem::take(x).into_vec(), *pos, state, true, false) {
|
Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true).into(),
|
||||||
// {}
|
|
||||||
Stmt::Noop(_) => { state.set_dirty(); *expr = Expr::Unit(*pos); }
|
|
||||||
// { stmt, .. }
|
|
||||||
Stmt::Block(statements, _) => *x = Box::new(statements.into()),
|
|
||||||
// { expr }
|
|
||||||
Stmt::Expr(inner) => { state.set_dirty(); *expr = inner; }
|
|
||||||
// { stmt }
|
|
||||||
stmt => x.push(stmt),
|
|
||||||
}
|
|
||||||
|
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// map.string
|
// map.string
|
||||||
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
||||||
let prop = &p.4.name;
|
let prop = &p.2.name;
|
||||||
// Map literal where everything is pure - promote the indexed item.
|
// Map literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -598,32 +604,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
// #{ key:value, .. }
|
// #{ key:value, .. }
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)),
|
Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)),
|
||||||
// lhs in rhs
|
|
||||||
Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
|
||||||
// "xxx" in "xxxxx"
|
|
||||||
(Expr::StringConstant(a, pos), Expr::StringConstant(b, _)) => {
|
|
||||||
state.set_dirty();
|
|
||||||
*expr = Expr::BoolConstant( b.contains(a.as_str()), *pos);
|
|
||||||
}
|
|
||||||
// 'x' in "xxxxx"
|
|
||||||
(Expr::CharConstant(a, pos), Expr::StringConstant(b, _)) => {
|
|
||||||
state.set_dirty();
|
|
||||||
*expr = Expr::BoolConstant(b.contains(*a), *pos);
|
|
||||||
}
|
|
||||||
// "xxx" in #{...}
|
|
||||||
(Expr::StringConstant(a, pos), Expr::Map(b, _)) => {
|
|
||||||
state.set_dirty();
|
|
||||||
*expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == *a).is_some(), *pos);
|
|
||||||
}
|
|
||||||
// 'x' in #{...}
|
|
||||||
(Expr::CharConstant(a, pos), Expr::Map(b, _)) => {
|
|
||||||
state.set_dirty();
|
|
||||||
let ch = a.to_string();
|
|
||||||
*expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == &ch).is_some(), *pos);
|
|
||||||
}
|
|
||||||
// lhs in rhs
|
|
||||||
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
|
||||||
},
|
|
||||||
// lhs && rhs
|
// lhs && rhs
|
||||||
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// true && rhs -> rhs
|
// true && rhs -> rhs
|
||||||
@ -684,7 +664,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
|||||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||||
&& x.args.len() == 2 // binary call
|
&& x.args.len() == 2 // binary call
|
||||||
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
||||||
&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
||||||
=> {
|
=> {
|
||||||
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
||||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||||
@ -783,16 +763,16 @@ fn optimize_top_level(
|
|||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Vec<Stmt> {
|
) -> Vec<Stmt> {
|
||||||
// If optimization level is None then skip optimizing
|
// If optimization level is None then skip optimizing
|
||||||
if level == OptimizationLevel::None {
|
if optimization_level == OptimizationLevel::None {
|
||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
return statements;
|
return statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the state
|
// Set up the state
|
||||||
let mut state = State::new(engine, lib, level);
|
let mut state = State::new(engine, lib, optimization_level);
|
||||||
|
|
||||||
// Add constants and variables from the scope
|
// Add constants and variables from the scope
|
||||||
scope.iter().for_each(|(name, constant, value)| {
|
scope.iter().for_each(|(name, constant, value)| {
|
||||||
@ -816,34 +796,17 @@ fn optimize_top_level(
|
|||||||
|
|
||||||
statements.iter_mut().enumerate().for_each(|(i, stmt)| {
|
statements.iter_mut().enumerate().for_each(|(i, stmt)| {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Const(var_def, expr, _, _) if expr.is_some() => {
|
Stmt::Const(value_expr, Ident { name, .. }, _, _) => {
|
||||||
// Load constants
|
// Load constants
|
||||||
let value_expr = expr.as_mut().unwrap();
|
|
||||||
optimize_expr(value_expr, &mut state);
|
optimize_expr(value_expr, &mut state);
|
||||||
|
|
||||||
if value_expr.is_constant() {
|
if value_expr.is_constant() {
|
||||||
state.push_var(&var_def.name, AccessMode::ReadOnly, value_expr.clone());
|
state.push_var(name, AccessMode::ReadOnly, value_expr.clone());
|
||||||
}
|
|
||||||
|
|
||||||
// Keep it in the global scope
|
|
||||||
if value_expr.is_unit() {
|
|
||||||
state.set_dirty();
|
|
||||||
*expr = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::Const(var_def, None, _, _) => {
|
Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => {
|
||||||
state.push_var(&var_def.name, AccessMode::ReadOnly, Expr::Unit(var_def.pos));
|
optimize_expr(value_expr, &mut state);
|
||||||
}
|
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*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),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Keep all variable declarations at this level
|
// Keep all variable declarations at this level
|
||||||
@ -852,7 +815,7 @@ fn optimize_top_level(
|
|||||||
Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true,
|
Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(_, _, _) => true,
|
Stmt::Import(_, _, _) => true,
|
||||||
_ => i == num_statements - 1,
|
_ => i >= num_statements - 1,
|
||||||
};
|
};
|
||||||
optimize_stmt(stmt, &mut state, keep);
|
optimize_stmt(stmt, &mut state, keep);
|
||||||
}
|
}
|
||||||
@ -887,12 +850,12 @@ pub fn optimize_into_ast(
|
|||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
mut statements: Vec<Stmt>,
|
mut statements: Vec<Stmt>,
|
||||||
_functions: Vec<crate::ast::ScriptFnDef>,
|
_functions: Vec<crate::ast::ScriptFnDef>,
|
||||||
level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> AST {
|
) -> AST {
|
||||||
let level = if cfg!(feature = "no_optimize") {
|
let level = if cfg!(feature = "no_optimize") {
|
||||||
OptimizationLevel::None
|
OptimizationLevel::None
|
||||||
} else {
|
} else {
|
||||||
level
|
optimization_level
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -921,33 +884,49 @@ pub fn optimize_into_ast(
|
|||||||
lib2.set_script_fn(fn_def);
|
lib2.set_script_fn(fn_def);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lib2 = &[&lib2];
|
||||||
|
|
||||||
_functions
|
_functions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut fn_def| {
|
.map(|mut fn_def| {
|
||||||
let pos = fn_def.body.position();
|
let pos = fn_def.body.pos;
|
||||||
|
|
||||||
// Optimize the function body
|
let mut body = fn_def.body.statements.into_vec();
|
||||||
let mut body = optimize_top_level(
|
|
||||||
vec![fn_def.body],
|
|
||||||
engine,
|
|
||||||
&Scope::new(),
|
|
||||||
&[&lib2],
|
|
||||||
level,
|
|
||||||
);
|
|
||||||
|
|
||||||
// {} -> Noop
|
loop {
|
||||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
// Optimize the function body
|
||||||
// { return val; } -> val
|
let state = &mut State::new(engine, lib2, level);
|
||||||
Stmt::Return((crate::ast::ReturnType::Return, _), Some(expr), _) => {
|
|
||||||
Stmt::Expr(expr)
|
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))
|
fn_def.body = StmtBlock {
|
||||||
}
|
statements: body.into(),
|
||||||
// All others
|
pos,
|
||||||
stmt => stmt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn_def
|
fn_def
|
||||||
})
|
})
|
||||||
.for_each(|fn_def| {
|
.for_each(|fn_def| {
|
||||||
|
@ -164,7 +164,7 @@ mod array_functions {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn map(
|
pub fn map(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -197,7 +197,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(ar.into())
|
Ok(ar.into())
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn filter(
|
pub fn filter(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -233,8 +233,70 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(ar.into())
|
Ok(ar.into())
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
|
pub fn contains(
|
||||||
|
ctx: NativeCallContext,
|
||||||
|
array: &mut Array,
|
||||||
|
value: Dynamic,
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
for item in array.iter_mut() {
|
||||||
|
if ctx
|
||||||
|
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
|
||||||
|
if fn_sig.starts_with(OP_EQUALS) =>
|
||||||
|
{
|
||||||
|
if item.type_id() == value.type_id() {
|
||||||
|
// No default when comparing same type
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(Dynamic::FALSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})?
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok(Dynamic::TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Dynamic::FALSE)
|
||||||
|
}
|
||||||
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn index_of(
|
pub fn index_of(
|
||||||
|
ctx: NativeCallContext,
|
||||||
|
array: &mut Array,
|
||||||
|
value: Dynamic,
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
for (i, item) in array.iter_mut().enumerate() {
|
||||||
|
if ctx
|
||||||
|
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
|
||||||
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
|
||||||
|
if fn_sig.starts_with(OP_EQUALS) =>
|
||||||
|
{
|
||||||
|
if item.type_id() == value.type_id() {
|
||||||
|
// No default when comparing same type
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(Dynamic::FALSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})?
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok((i as INT).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((-1 as INT).into())
|
||||||
|
}
|
||||||
|
#[rhai_fn(name = "index_of", return_raw, pure)]
|
||||||
|
pub fn index_of_filter(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
filter: FnPtr,
|
filter: FnPtr,
|
||||||
@ -267,7 +329,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok((-1 as INT).into())
|
Ok((-1 as INT).into())
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn some(
|
pub fn some(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -301,7 +363,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(false.into())
|
Ok(false.into())
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn all(
|
pub fn all(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -335,7 +397,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(true.into())
|
Ok(true.into())
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn reduce(
|
pub fn reduce(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -366,7 +428,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "reduce", return_raw)]
|
#[rhai_fn(name = "reduce", return_raw, pure)]
|
||||||
pub fn reduce_with_initial(
|
pub fn reduce_with_initial(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -405,7 +467,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw, pure)]
|
||||||
pub fn reduce_rev(
|
pub fn reduce_rev(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -436,7 +498,7 @@ mod array_functions {
|
|||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "reduce_rev", return_raw)]
|
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
|
||||||
pub fn reduce_rev_with_initial(
|
pub fn reduce_rev_with_initial(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -634,7 +696,7 @@ mod array_functions {
|
|||||||
|
|
||||||
drained
|
drained
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "==", return_raw)]
|
#[rhai_fn(name = "==", return_raw, pure)]
|
||||||
pub fn equals(
|
pub fn equals(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
@ -648,18 +710,31 @@ mod array_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) {
|
for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) {
|
||||||
let equals = ctx
|
if !ctx
|
||||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [a1, a2])
|
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [a1, a2])
|
||||||
.map(|v| v.as_bool().unwrap_or(false))?;
|
.or_else(|err| match *err {
|
||||||
|
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
|
||||||
if !equals {
|
if fn_sig.starts_with(OP_EQUALS) =>
|
||||||
return Ok(false.into());
|
{
|
||||||
|
if a1.type_id() == a2.type_id() {
|
||||||
|
// No default when comparing same type
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(Dynamic::FALSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(err),
|
||||||
|
})?
|
||||||
|
.as_bool()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return Ok(Dynamic::FALSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true.into())
|
Ok(Dynamic::TRUE)
|
||||||
}
|
}
|
||||||
#[rhai_fn(name = "!=", return_raw)]
|
#[rhai_fn(name = "!=", return_raw, pure)]
|
||||||
pub fn not_equals(
|
pub fn not_equals(
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
array: &mut Array,
|
array: &mut Array,
|
||||||
|
@ -13,8 +13,8 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
|||||||
|
|
||||||
#[export_module]
|
#[export_module]
|
||||||
mod map_functions {
|
mod map_functions {
|
||||||
#[rhai_fn(pure)]
|
#[rhai_fn(name = "has", pure)]
|
||||||
pub fn has(map: &mut Map, prop: ImmutableString) -> bool {
|
pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
|
||||||
map.contains_key(&prop)
|
map.contains_key(&prop)
|
||||||
}
|
}
|
||||||
#[rhai_fn(pure)]
|
#[rhai_fn(pure)]
|
||||||
|
@ -74,14 +74,6 @@ mod string_functions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rhai_fn(name = "contains")]
|
|
||||||
pub fn contains_char(string: &str, character: char) -> bool {
|
|
||||||
string.contains(character)
|
|
||||||
}
|
|
||||||
pub fn contains(string: &str, find_string: &str) -> bool {
|
|
||||||
string.contains(find_string)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rhai_fn(name = "index_of")]
|
#[rhai_fn(name = "index_of")]
|
||||||
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
|
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
|
||||||
let start = if start < 0 {
|
let start = if start < 0 {
|
||||||
|
@ -38,6 +38,7 @@ pub enum LexError {
|
|||||||
impl Error for LexError {}
|
impl Error for LexError {}
|
||||||
|
|
||||||
impl fmt::Display for LexError {
|
impl fmt::Display for LexError {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
|
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
|
||||||
@ -293,7 +294,7 @@ pub struct ParseError(pub Box<ParseErrorType>, pub Position);
|
|||||||
impl Error for ParseError {}
|
impl Error for ParseError {}
|
||||||
|
|
||||||
impl fmt::Display for ParseError {
|
impl fmt::Display for ParseError {
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Display::fmt(&self.0, f)?;
|
fmt::Display::fmt(&self.0, f)?;
|
||||||
|
|
||||||
|
270
src/parser.rs
270
src/parser.rs
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef,
|
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef,
|
||||||
Stmt,
|
Stmt, StmtBlock,
|
||||||
};
|
};
|
||||||
use crate::dynamic::{AccessMode, Union};
|
use crate::dynamic::{AccessMode, Union};
|
||||||
use crate::engine::KEYWORD_THIS;
|
use crate::engine::{KEYWORD_THIS, OP_CONTAINS};
|
||||||
use crate::module::NamespaceRef;
|
use crate::module::NamespaceRef;
|
||||||
use crate::optimize::optimize_into_ast;
|
use crate::optimize::optimize_into_ast;
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
@ -243,7 +243,11 @@ impl Expr {
|
|||||||
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
||||||
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
||||||
|
|
||||||
Self::Property(Box::new((getter, hash_get, setter, hash_set, ident.into())))
|
Self::Property(Box::new((
|
||||||
|
(getter, hash_get),
|
||||||
|
(setter, hash_set),
|
||||||
|
ident.into(),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
@ -480,7 +484,6 @@ fn parse_index_chain(
|
|||||||
Expr::CharConstant(_, _)
|
Expr::CharConstant(_, _)
|
||||||
| Expr::And(_, _)
|
| Expr::And(_, _)
|
||||||
| Expr::Or(_, _)
|
| Expr::Or(_, _)
|
||||||
| Expr::In(_, _)
|
|
||||||
| Expr::BoolConstant(_, _)
|
| Expr::BoolConstant(_, _)
|
||||||
| Expr::Unit(_) => {
|
| Expr::Unit(_) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
@ -514,7 +517,6 @@ fn parse_index_chain(
|
|||||||
Expr::CharConstant(_, _)
|
Expr::CharConstant(_, _)
|
||||||
| Expr::And(_, _)
|
| Expr::And(_, _)
|
||||||
| Expr::Or(_, _)
|
| Expr::Or(_, _)
|
||||||
| Expr::In(_, _)
|
|
||||||
| Expr::BoolConstant(_, _)
|
| Expr::BoolConstant(_, _)
|
||||||
| Expr::Unit(_) => {
|
| Expr::Unit(_) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
@ -548,8 +550,8 @@ fn parse_index_chain(
|
|||||||
)
|
)
|
||||||
.into_err(x.position()))
|
.into_err(x.position()))
|
||||||
}
|
}
|
||||||
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???]
|
// lhs[??? && ???], lhs[??? || ???]
|
||||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) | x @ Expr::In(_, _) => {
|
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Array access expects integer index, not a boolean".into(),
|
"Array access expects integer index, not a boolean".into(),
|
||||||
)
|
)
|
||||||
@ -811,7 +813,7 @@ fn parse_switch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = HashMap::new();
|
let mut table = HashMap::<u64, StmtBlock>::new();
|
||||||
let mut def_stmt = None;
|
let mut def_stmt = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -871,10 +873,10 @@ fn parse_switch(
|
|||||||
let need_comma = !stmt.is_self_terminated();
|
let need_comma = !stmt.is_self_terminated();
|
||||||
|
|
||||||
def_stmt = if let Some(hash) = hash {
|
def_stmt = if let Some(hash) = hash {
|
||||||
table.insert(hash, stmt);
|
table.insert(hash, stmt.into());
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(stmt)
|
Some(stmt.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
@ -905,7 +907,10 @@ fn parse_switch(
|
|||||||
|
|
||||||
Ok(Stmt::Switch(
|
Ok(Stmt::Switch(
|
||||||
item,
|
item,
|
||||||
Box::new((final_table.into(), def_stmt)),
|
Box::new((
|
||||||
|
final_table.into(),
|
||||||
|
def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()),
|
||||||
|
)),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -956,7 +961,7 @@ fn parse_primary(
|
|||||||
// { - block statement as expression
|
// { - block statement as expression
|
||||||
Token::LeftBrace if settings.allow_stmt_expr => {
|
Token::LeftBrace if settings.allow_stmt_expr => {
|
||||||
match parse_block(input, state, lib, settings.level_up())? {
|
match parse_block(input, state, lib, settings.level_up())? {
|
||||||
Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
|
block @ Stmt::Block(_, _) => Expr::Stmt(Box::new(block.into())),
|
||||||
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
|
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -964,15 +969,14 @@ fn parse_primary(
|
|||||||
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
||||||
|
|
||||||
// If statement is allowed to act as expressions
|
// If statement is allowed to act as expressions
|
||||||
Token::If if settings.allow_if_expr => Expr::Stmt(
|
Token::If if settings.allow_if_expr => Expr::Stmt(Box::new(
|
||||||
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
|
parse_if(input, state, lib, settings.level_up())?.into(),
|
||||||
settings.pos,
|
)),
|
||||||
),
|
|
||||||
// Switch statement is allowed to act as expressions
|
// Switch statement is allowed to act as expressions
|
||||||
Token::Switch if settings.allow_switch_expr => Expr::Stmt(
|
Token::Switch if settings.allow_switch_expr => Expr::Stmt(Box::new(
|
||||||
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
|
parse_switch(input, state, lib, settings.level_up())?.into(),
|
||||||
settings.pos,
|
)),
|
||||||
),
|
|
||||||
// | ...
|
// | ...
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
||||||
@ -1149,14 +1153,14 @@ fn parse_primary(
|
|||||||
.into_err(pos));
|
.into_err(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, namespace, Ident { name, pos }) = *x;
|
let (_, namespace, Ident { name, pos, .. }) = *x;
|
||||||
settings.pos = pos;
|
settings.pos = pos;
|
||||||
let ns = namespace.map(|(_, ns)| ns);
|
let ns = namespace.map(|(_, ns)| ns);
|
||||||
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
||||||
}
|
}
|
||||||
// Function call
|
// Function call
|
||||||
(Expr::Variable(x), Token::LeftParen) => {
|
(Expr::Variable(x), Token::LeftParen) => {
|
||||||
let (_, namespace, Ident { name, pos }) = *x;
|
let (_, namespace, Ident { name, pos, .. }) = *x;
|
||||||
settings.pos = pos;
|
settings.pos = pos;
|
||||||
let ns = namespace.map(|(_, ns)| ns);
|
let ns = namespace.map(|(_, ns)| ns);
|
||||||
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
||||||
@ -1397,7 +1401,7 @@ fn make_assignment_stmt<'a>(
|
|||||||
}
|
}
|
||||||
// var (indexed) = rhs
|
// var (indexed) = rhs
|
||||||
Expr::Variable(x) => {
|
Expr::Variable(x) => {
|
||||||
let (index, _, Ident { name, pos }) = x.as_ref();
|
let (index, _, Ident { name, pos, .. }) = x.as_ref();
|
||||||
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
||||||
AccessMode::ReadWrite => {
|
AccessMode::ReadWrite => {
|
||||||
Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
|
Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
|
||||||
@ -1418,7 +1422,7 @@ fn make_assignment_stmt<'a>(
|
|||||||
}
|
}
|
||||||
// var[???] (indexed) = rhs, var.??? (indexed) = rhs
|
// var[???] (indexed) = rhs, var.??? (indexed) = rhs
|
||||||
Expr::Variable(x) => {
|
Expr::Variable(x) => {
|
||||||
let (index, _, Ident { name, pos }) = x.as_ref();
|
let (index, _, Ident { name, pos, .. }) = x.as_ref();
|
||||||
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
||||||
AccessMode::ReadWrite => {
|
AccessMode::ReadWrite => {
|
||||||
Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
|
Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos))
|
||||||
@ -1508,7 +1512,7 @@ fn make_dot_expr(
|
|||||||
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
||||||
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
let hash_set = calc_fn_hash(empty(), &setter, 2);
|
||||||
|
|
||||||
let rhs = Expr::Property(Box::new((getter, hash_get, setter, hash_set, ident)));
|
let rhs = Expr::Property(Box::new(((getter, hash_get), (setter, hash_set), ident)));
|
||||||
|
|
||||||
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
||||||
}
|
}
|
||||||
@ -1602,139 +1606,6 @@ fn make_dot_expr(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make an 'in' expression.
|
|
||||||
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
|
|
||||||
match (&lhs, &rhs) {
|
|
||||||
(_, x @ Expr::IntegerConstant(_, _))
|
|
||||||
| (_, x @ Expr::And(_, _))
|
|
||||||
| (_, x @ Expr::Or(_, _))
|
|
||||||
| (_, x @ Expr::In(_, _))
|
|
||||||
| (_, x @ Expr::BoolConstant(_, _))
|
|
||||||
| (_, x @ Expr::Unit(_)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression expects a string, array or object map".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
(_, x @ Expr::FloatConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression expects a string, array or object map".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// "xxx" in "xxxx", 'x' in "xxxx" - OK!
|
|
||||||
(Expr::StringConstant(_, _), Expr::StringConstant(_, _))
|
|
||||||
| (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (),
|
|
||||||
|
|
||||||
// 123.456 in "xxxx"
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
(x @ Expr::FloatConstant(_, _), Expr::StringConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for a string expects a string, not a float".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// 123 in "xxxx"
|
|
||||||
(x @ Expr::IntegerConstant(_, _), Expr::StringConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for a string expects a string, not a number".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx",
|
|
||||||
// true in "xxxx", false in "xxxx"
|
|
||||||
(x @ Expr::And(_, _), Expr::StringConstant(_, _))
|
|
||||||
| (x @ Expr::Or(_, _), Expr::StringConstant(_, _))
|
|
||||||
| (x @ Expr::In(_, _), Expr::StringConstant(_, _))
|
|
||||||
| (x @ Expr::BoolConstant(_, _), Expr::StringConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for a string expects a string, not a boolean".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// [???, ???, ???] in "xxxx"
|
|
||||||
(x @ Expr::Array(_, _), Expr::StringConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for a string expects a string, not an array".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// #{...} in "xxxx"
|
|
||||||
(x @ Expr::Map(_, _), Expr::StringConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for a string expects a string, not an object map".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// () in "xxxx"
|
|
||||||
(x @ Expr::Unit(_), Expr::StringConstant(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for a string expects a string, not ()".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// "xxx" in #{...}, 'x' in #{...} - OK!
|
|
||||||
(Expr::StringConstant(_, _), Expr::Map(_, _))
|
|
||||||
| (Expr::CharConstant(_, _), Expr::Map(_, _)) => (),
|
|
||||||
|
|
||||||
// 123.456 in #{...}
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
(x @ Expr::FloatConstant(_, _), Expr::Map(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for an object map expects a string, not a float".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// 123 in #{...}
|
|
||||||
(x @ Expr::IntegerConstant(_, _), Expr::Map(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for an object map expects a string, not a number".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...},
|
|
||||||
// true in #{...}, false in #{...}
|
|
||||||
(x @ Expr::And(_, _), Expr::Map(_, _))
|
|
||||||
| (x @ Expr::Or(_, _), Expr::Map(_, _))
|
|
||||||
| (x @ Expr::In(_, _), Expr::Map(_, _))
|
|
||||||
| (x @ Expr::BoolConstant(_, _), Expr::Map(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for an object map expects a string, not a boolean".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// [???, ???, ???] in #{..}
|
|
||||||
(x @ Expr::Array(_, _), Expr::Map(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for an object map expects a string, not an array".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// #{...} in #{..}
|
|
||||||
(x @ Expr::Map(_, _), Expr::Map(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for an object map expects a string, not an object map".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
// () in #{...}
|
|
||||||
(x @ Expr::Unit(_), Expr::Map(_, _)) => {
|
|
||||||
return Err(PERR::MalformedInExpr(
|
|
||||||
"'in' expression for an object map expects a string, not ()".into(),
|
|
||||||
)
|
|
||||||
.into_err(x.position()))
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Expr::In(Box::new(BinaryExpr { lhs, rhs }), op_pos))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a binary expression.
|
/// Parse a binary expression.
|
||||||
fn parse_binary_op(
|
fn parse_binary_op(
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
@ -1880,9 +1751,21 @@ fn parse_binary_op(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Token::In => {
|
Token::In => {
|
||||||
let rhs = args.pop().unwrap();
|
// Swap the arguments
|
||||||
let current_lhs = args.pop().unwrap();
|
let current_lhs = args.remove(0);
|
||||||
make_in_expr(current_lhs, rhs, pos)?
|
args.push(current_lhs);
|
||||||
|
|
||||||
|
// Convert into a call to `contains`
|
||||||
|
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2);
|
||||||
|
Expr::FnCall(
|
||||||
|
Box::new(FnCallExpr {
|
||||||
|
hash: FnHash::from_script(hash),
|
||||||
|
args,
|
||||||
|
name: OP_CONTAINS.into(),
|
||||||
|
..op_base
|
||||||
|
}),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Custom(s)
|
Token::Custom(s)
|
||||||
@ -1984,8 +1867,8 @@ fn parse_custom_syntax(
|
|||||||
tokens.push(keyword);
|
tokens.push(keyword);
|
||||||
}
|
}
|
||||||
MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
|
MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
|
||||||
Stmt::Block(statements, pos) => {
|
block @ Stmt::Block(_, _) => {
|
||||||
keywords.push(Expr::Stmt(Box::new(statements.into()), pos));
|
keywords.push(Expr::Stmt(Box::new(block.into())));
|
||||||
let keyword = state.get_interned_string(MARKER_BLOCK);
|
let keyword = state.get_interned_string(MARKER_BLOCK);
|
||||||
segments.push(keyword.clone());
|
segments.push(keyword.clone());
|
||||||
tokens.push(keyword);
|
tokens.push(keyword);
|
||||||
@ -2118,20 +2001,20 @@ fn parse_if(
|
|||||||
|
|
||||||
// if guard { if_body } else ...
|
// if guard { if_body } else ...
|
||||||
let else_body = if match_token(input, Token::Else).0 {
|
let else_body = if match_token(input, Token::Else).0 {
|
||||||
Some(if let (Token::If, _) = input.peek().unwrap() {
|
if let (Token::If, _) = input.peek().unwrap() {
|
||||||
// if guard { if_body } else if ...
|
// if guard { if_body } else if ...
|
||||||
parse_if(input, state, lib, settings.level_up())?
|
parse_if(input, state, lib, settings.level_up())?
|
||||||
} else {
|
} else {
|
||||||
// if guard { if_body } else { else-body }
|
// if guard { if_body } else { else-body }
|
||||||
parse_block(input, state, lib, settings.level_up())?
|
parse_block(input, state, lib, settings.level_up())?
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
Stmt::Noop(Position::NONE)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Stmt::If(
|
Ok(Stmt::If(
|
||||||
guard,
|
guard,
|
||||||
Box::new((if_body, else_body)),
|
Box::new((if_body.into(), else_body.into())),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -2151,18 +2034,18 @@ fn parse_while_loop(
|
|||||||
(Token::While, pos) => {
|
(Token::While, pos) => {
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
(Some(expr), pos)
|
(expr, pos)
|
||||||
}
|
}
|
||||||
(Token::Loop, pos) => (None, pos),
|
(Token::Loop, pos) => (Expr::Unit(Position::NONE), pos),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
settings.pos = token_pos;
|
settings.pos = token_pos;
|
||||||
|
|
||||||
ensure_not_assignment(input)?;
|
ensure_not_assignment(input)?;
|
||||||
settings.is_breakable = true;
|
settings.is_breakable = true;
|
||||||
let body = Box::new(parse_block(input, state, lib, settings.level_up())?);
|
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
Ok(Stmt::While(guard, body, settings.pos))
|
Ok(Stmt::While(guard, Box::new(body.into()), settings.pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a do loop.
|
/// Parse a do loop.
|
||||||
@ -2180,7 +2063,7 @@ fn parse_do(
|
|||||||
|
|
||||||
// do { body } [while|until] guard
|
// do { body } [while|until] guard
|
||||||
settings.is_breakable = true;
|
settings.is_breakable = true;
|
||||||
let body = Box::new(parse_block(input, state, lib, settings.level_up())?);
|
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
let is_while = match input.next().unwrap() {
|
let is_while = match input.next().unwrap() {
|
||||||
(Token::While, _) => true,
|
(Token::While, _) => true,
|
||||||
@ -2198,7 +2081,12 @@ fn parse_do(
|
|||||||
let guard = parse_expr(input, state, lib, settings.level_up())?;
|
let guard = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
ensure_not_assignment(input)?;
|
ensure_not_assignment(input)?;
|
||||||
|
|
||||||
Ok(Stmt::Do(body, guard, is_while, settings.pos))
|
Ok(Stmt::Do(
|
||||||
|
Box::new(body.into()),
|
||||||
|
guard,
|
||||||
|
is_while,
|
||||||
|
settings.pos,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a for loop.
|
/// Parse a for loop.
|
||||||
@ -2253,7 +2141,7 @@ fn parse_for(
|
|||||||
|
|
||||||
state.stack.truncate(prev_stack_len);
|
state.stack.truncate(prev_stack_len);
|
||||||
|
|
||||||
Ok(Stmt::For(expr, Box::new((name, body)), settings.pos))
|
Ok(Stmt::For(expr, Box::new((name, body.into())), settings.pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a variable definition statement.
|
/// Parse a variable definition statement.
|
||||||
@ -2290,18 +2178,18 @@ fn parse_let(
|
|||||||
// let name = ...
|
// let name = ...
|
||||||
let expr = if match_token(input, Token::Equals).0 {
|
let expr = if match_token(input, Token::Equals).0 {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
Some(parse_expr(input, state, lib, settings.level_up())?)
|
parse_expr(input, state, lib, settings.level_up())?
|
||||||
} else {
|
} else {
|
||||||
None
|
Expr::Unit(Position::NONE)
|
||||||
};
|
};
|
||||||
|
|
||||||
state.stack.push((name, var_type));
|
state.stack.push((name, var_type));
|
||||||
|
|
||||||
match var_type {
|
match var_type {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
AccessMode::ReadWrite => Ok(Stmt::Let(Box::new(var_def), expr, export, settings.pos)),
|
AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def, export, settings.pos)),
|
||||||
// const name = { expr:constant }
|
// const name = { expr:constant }
|
||||||
AccessMode::ReadOnly => Ok(Stmt::Const(Box::new(var_def), expr, export, settings.pos)),
|
AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def, export, settings.pos)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2342,10 +2230,10 @@ fn parse_import(
|
|||||||
|
|
||||||
Ok(Stmt::Import(
|
Ok(Stmt::Import(
|
||||||
expr,
|
expr,
|
||||||
Some(Box::new(Ident {
|
Some(Ident {
|
||||||
name,
|
name,
|
||||||
pos: name_pos,
|
pos: name_pos,
|
||||||
})),
|
}),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -2542,7 +2430,7 @@ fn parse_stmt(
|
|||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
use AccessMode::{ReadOnly, ReadWrite};
|
use AccessMode::{ReadOnly, ReadWrite};
|
||||||
|
|
||||||
let mut _comments: Vec<String> = Default::default();
|
let mut _comments: StaticVec<String> = Default::default();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
@ -2688,16 +2576,13 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
// `return`/`throw` at <EOF>
|
// `return`/`throw` at <EOF>
|
||||||
(Token::EOF, pos) => Ok(Stmt::Return((return_type, token_pos), None, *pos)),
|
(Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
||||||
// `return;` or `throw;`
|
// `return;` or `throw;`
|
||||||
(Token::SemiColon, _) => {
|
(Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
||||||
Ok(Stmt::Return((return_type, token_pos), None, settings.pos))
|
|
||||||
}
|
|
||||||
// `return` or `throw` with expression
|
// `return` or `throw` with expression
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
let pos = expr.position();
|
Ok(Stmt::Return(return_type, Some(expr), token_pos))
|
||||||
Ok(Stmt::Return((return_type, token_pos), Some(expr), pos))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2775,7 +2660,7 @@ fn parse_try_catch(
|
|||||||
let catch_body = parse_block(input, state, lib, settings.level_up())?;
|
let catch_body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
Ok(Stmt::TryCatch(
|
Ok(Stmt::TryCatch(
|
||||||
Box::new((body, var_def, catch_body)),
|
Box::new((body.into(), var_def, catch_body.into())),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
catch_pos,
|
catch_pos,
|
||||||
))
|
))
|
||||||
@ -2789,7 +2674,7 @@ fn parse_fn(
|
|||||||
lib: &mut FunctionsLib,
|
lib: &mut FunctionsLib,
|
||||||
access: FnAccess,
|
access: FnAccess,
|
||||||
mut settings: ParseSettings,
|
mut settings: ParseSettings,
|
||||||
comments: Vec<String>,
|
comments: StaticVec<String>,
|
||||||
) -> Result<ScriptFnDef, ParseError> {
|
) -> Result<ScriptFnDef, ParseError> {
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
@ -2852,7 +2737,8 @@ fn parse_fn(
|
|||||||
parse_block(input, state, lib, settings.level_up())?
|
parse_block(input, state, lib, settings.level_up())?
|
||||||
}
|
}
|
||||||
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
|
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
|
||||||
};
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
||||||
|
|
||||||
@ -2914,7 +2800,7 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<Ident>, pos: Po
|
|||||||
let mut statements: StaticVec<_> = Default::default();
|
let mut statements: StaticVec<_> = Default::default();
|
||||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
statements.extend(externals.into_iter().map(Stmt::Share));
|
||||||
statements.push(Stmt::Expr(expr));
|
statements.push(Stmt::Expr(expr));
|
||||||
Expr::Stmt(Box::new(statements), pos)
|
Expr::Stmt(Box::new(StmtBlock { statements, pos }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an anonymous function definition.
|
/// Parse an anonymous function definition.
|
||||||
@ -3016,7 +2902,7 @@ fn parse_anon_fn(
|
|||||||
params,
|
params,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
externals: Default::default(),
|
externals: Default::default(),
|
||||||
body,
|
body: body.into(),
|
||||||
lib: None,
|
lib: None,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
mods: Default::default(),
|
mods: Default::default(),
|
||||||
|
@ -58,8 +58,6 @@ pub enum EvalAltResult {
|
|||||||
/// Trying to index into a type that is not an array, an object map, or a string, and has no
|
/// Trying to index into a type that is not an array, an object map, or a string, and has no
|
||||||
/// indexer function defined. Wrapped value is the type name.
|
/// indexer function defined. Wrapped value is the type name.
|
||||||
ErrorIndexingType(String, Position),
|
ErrorIndexingType(String, Position),
|
||||||
/// Invalid arguments for `in` operator.
|
|
||||||
ErrorInExpr(Position),
|
|
||||||
/// The `for` statement encounters a type that is not an iterator.
|
/// The `for` statement encounters a type that is not an iterator.
|
||||||
ErrorFor(Position),
|
ErrorFor(Position),
|
||||||
/// Data race detected when accessing a variable. Wrapped value is the variable name.
|
/// Data race detected when accessing a variable. Wrapped value is the variable name.
|
||||||
@ -122,7 +120,6 @@ impl EvalAltResult {
|
|||||||
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
|
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
|
||||||
Self::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant",
|
Self::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant",
|
||||||
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
|
||||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||||
Self::ErrorTooManyOperations(_) => "Too many operations",
|
Self::ErrorTooManyOperations(_) => "Too many operations",
|
||||||
@ -182,7 +179,6 @@ impl fmt::Display for EvalAltResult {
|
|||||||
|
|
||||||
Self::ErrorUnboundThis(_)
|
Self::ErrorUnboundThis(_)
|
||||||
| Self::ErrorFor(_)
|
| Self::ErrorFor(_)
|
||||||
| Self::ErrorInExpr(_)
|
|
||||||
| Self::ErrorDotExpr(_, _)
|
| Self::ErrorDotExpr(_, _)
|
||||||
| Self::ErrorTooManyOperations(_)
|
| Self::ErrorTooManyOperations(_)
|
||||||
| Self::ErrorTooManyModules(_)
|
| Self::ErrorTooManyModules(_)
|
||||||
@ -305,7 +301,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorDataRace(_, _)
|
| Self::ErrorDataRace(_, _)
|
||||||
| Self::ErrorAssignmentToConstant(_, _)
|
| Self::ErrorAssignmentToConstant(_, _)
|
||||||
| Self::ErrorMismatchOutputType(_, _, _)
|
| Self::ErrorMismatchOutputType(_, _, _)
|
||||||
| Self::ErrorInExpr(_)
|
|
||||||
| Self::ErrorDotExpr(_, _)
|
| Self::ErrorDotExpr(_, _)
|
||||||
| Self::ErrorArithmetic(_, _)
|
| Self::ErrorArithmetic(_, _)
|
||||||
| Self::ErrorRuntime(_, _) => true,
|
| Self::ErrorRuntime(_, _) => true,
|
||||||
@ -362,7 +357,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorParsing(_, _)
|
| Self::ErrorParsing(_, _)
|
||||||
| Self::ErrorUnboundThis(_)
|
| Self::ErrorUnboundThis(_)
|
||||||
| Self::ErrorFor(_)
|
| Self::ErrorFor(_)
|
||||||
| Self::ErrorInExpr(_)
|
|
||||||
| Self::ErrorArithmetic(_, _)
|
| Self::ErrorArithmetic(_, _)
|
||||||
| Self::ErrorTooManyOperations(_)
|
| Self::ErrorTooManyOperations(_)
|
||||||
| Self::ErrorTooManyModules(_)
|
| Self::ErrorTooManyModules(_)
|
||||||
@ -430,7 +424,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||||
| Self::ErrorInExpr(pos)
|
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
| Self::ErrorTooManyOperations(pos)
|
| Self::ErrorTooManyOperations(pos)
|
||||||
@ -471,7 +464,6 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, _, pos)
|
| Self::ErrorMismatchOutputType(_, _, pos)
|
||||||
| Self::ErrorInExpr(pos)
|
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
| Self::ErrorTooManyOperations(pos)
|
| Self::ErrorTooManyOperations(pos)
|
||||||
|
@ -151,7 +151,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
info.func.get_fn_def().comments.clone()
|
info.func.get_fn_def().comments.to_vec()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
|
@ -116,16 +116,19 @@ pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 {
|
|||||||
pub struct HashableHashMap<K, T, H: BuildHasher>(HashMap<K, T, H>);
|
pub struct HashableHashMap<K, T, H: BuildHasher>(HashMap<K, T, H>);
|
||||||
|
|
||||||
impl<K, T, H: BuildHasher> From<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
|
impl<K, T, H: BuildHasher> From<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: HashMap<K, T, H>) -> Self {
|
fn from(value: HashMap<K, T, H>) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K, T, H: BuildHasher> AsRef<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
|
impl<K, T, H: BuildHasher> AsRef<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
|
||||||
|
#[inline(always)]
|
||||||
fn as_ref(&self) -> &HashMap<K, T, H> {
|
fn as_ref(&self) -> &HashMap<K, T, H> {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
|
impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H> {
|
||||||
|
#[inline(always)]
|
||||||
fn as_mut(&mut self) -> &mut HashMap<K, T, H> {
|
fn as_mut(&mut self) -> &mut HashMap<K, T, H> {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
@ -133,21 +136,25 @@ impl<K, T, H: BuildHasher> AsMut<HashMap<K, T, H>> for HashableHashMap<K, T, H>
|
|||||||
impl<K, T, H: BuildHasher> Deref for HashableHashMap<K, T, H> {
|
impl<K, T, H: BuildHasher> Deref for HashableHashMap<K, T, H> {
|
||||||
type Target = HashMap<K, T, H>;
|
type Target = HashMap<K, T, H>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K, T, H: BuildHasher> DerefMut for HashableHashMap<K, T, H> {
|
impl<K, T, H: BuildHasher> DerefMut for HashableHashMap<K, T, H> {
|
||||||
|
#[inline(always)]
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K: Debug, T: Debug, H: BuildHasher> Debug for HashableHashMap<K, T, H> {
|
impl<K: Debug, T: Debug, H: BuildHasher> Debug for HashableHashMap<K, T, H> {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K: Hash + Ord, T: Hash, H: BuildHasher> Hash for HashableHashMap<K, T, H> {
|
impl<K: Hash + Ord, T: Hash, H: BuildHasher> Hash for HashableHashMap<K, T, H> {
|
||||||
|
#[inline(always)]
|
||||||
fn hash<B: Hasher>(&self, state: &mut B) {
|
fn hash<B: Hasher>(&self, state: &mut B) {
|
||||||
let mut keys: Vec<_> = self.0.keys().collect();
|
let mut keys: Vec<_> = self.0.keys().collect();
|
||||||
keys.sort();
|
keys.sort();
|
||||||
|
@ -38,7 +38,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
|
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
|
||||||
assert!(engine.eval::<bool>("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?);
|
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?);
|
||||||
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
|
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -55,20 +55,18 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(Ident("DECISION" @ 1:9), Some(BoolConstant(false, 1:20)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
|
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Block([Const(BoolConstant(false, 1:20), Ident("DECISION" @ 1:9), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)], functions: Module("#));
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert!(
|
assert!(format!("{:?}", ast).starts_with("AST { source: None, body: [], functions: Module("));
|
||||||
format!("{:?}", ast).starts_with("AST { source: None, statements: [], functions: Module(")
|
|
||||||
);
|
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let ast = engine.compile("abs(-42)")?;
|
let ast = engine.compile("abs(-42)")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast)
|
assert!(format!("{:?}", ast)
|
||||||
.starts_with(r"AST { source: None, statements: [Expr(IntegerConstant(42, 1:1))]"));
|
.starts_with(r"AST { source: None, body: [Expr(IntegerConstant(42, 1:1))]"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user