Merge pull request #372 from schungx/master

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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