Merge pull request #529 from schungx/master
General housekeeping and bump minimum Rust version.
This commit is contained in:
commit
53fffad726
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,16 +1,34 @@
|
|||||||
Rhai Release Notes
|
Rhai Release Notes
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
Version 1.6.0
|
||||||
|
=============
|
||||||
|
|
||||||
|
Compiler version
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* Minimum compiler version is now `1.57` due to [`smartstring`](https://crates.io/crates/smartstring) dependency.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Invalid property or method access such as `a.b::c.d` or `a.b::func()` no longer panics but properly returns a syntax error.
|
||||||
|
* `Scope::is_constant` now returns the correct value.
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Variable definitions are optimized so that shadowed variables are reused as much as possible to reduce memory consumption.
|
||||||
|
|
||||||
|
|
||||||
Version 1.5.0
|
Version 1.5.0
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This version adds a debugging interface, which can be used to integrate a debugger.
|
This version adds a debugging interface, which can be used to integrate a debugger.
|
||||||
|
|
||||||
Based on popular demand, an option is added to throw exceptions when invalid properties are accessed
|
Based on popular demand, an option is added to throw exceptions when invalid properties are accessed on object maps (default is to return `()`).
|
||||||
on object maps (default is to return `()`).
|
|
||||||
|
|
||||||
Also based on popular demand, the `REPL` tool now uses
|
Also based on popular demand, the `REPL` tool now uses a slightly-enhanced version of [`rustyline`](https://crates.io/crates/rustyline) for line editing and history.
|
||||||
[`rustyline`](https://crates.io/crates/rustyline) for line editing and history.
|
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
|
@ -3,7 +3,8 @@ members = [".", "codegen"]
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rhai"
|
name = "rhai"
|
||||||
version = "1.5.0"
|
version = "1.6.0"
|
||||||
|
rust-version = "1.57"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||||
description = "Embedded scripting for Rust"
|
description = "Embedded scripting for Rust"
|
||||||
|
@ -26,7 +26,7 @@ Targets and builds
|
|||||||
* All CPU and O/S targets supported by Rust, including:
|
* All CPU and O/S targets supported by Rust, including:
|
||||||
* WebAssembly (WASM)
|
* WebAssembly (WASM)
|
||||||
* `no-std`
|
* `no-std`
|
||||||
* Minimum Rust version 1.51
|
* Minimum Rust version 1.57
|
||||||
|
|
||||||
|
|
||||||
Standard features
|
Standard features
|
||||||
@ -36,7 +36,7 @@ Standard features
|
|||||||
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single-core, 2.3 GHz Linux VM).
|
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single-core, 2.3 GHz Linux VM).
|
||||||
* Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types]([#custom-types-and-methods](https://rhai.rs/book/rust/custom.html)), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/custom.html) and [indexers](https://rhai.rs/book/rust/indexers.html).
|
* Tight integration with native Rust [functions](https://rhai.rs/book/rust/functions.html) and [types]([#custom-types-and-methods](https://rhai.rs/book/rust/custom.html)), including [getters/setters](https://rhai.rs/book/rust/getters-setters.html), [methods](https://rhai.rs/book/rust/custom.html) and [indexers](https://rhai.rs/book/rust/indexers.html).
|
||||||
* Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.html).
|
* Freely pass Rust values into a script as [variables](https://rhai.rs/book/language/variables.html)/[constants](https://rhai.rs/book/language/constants.html) via an external [`Scope`](https://rhai.rs/book/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. Or tap directly into the [variable resolution process](https://rhai.rs/book/engine/var.html).
|
||||||
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) and [object maps](https://rhai.rs/book/language/object-maps.html).
|
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).
|
||||||
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
|
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
|
||||||
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
||||||
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring).
|
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring).
|
||||||
|
@ -2,33 +2,41 @@
|
|||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, FnPtr};
|
use rhai::{Engine, EvalAltResult, FnPtr};
|
||||||
|
|
||||||
|
// To call a Rhai closure at a later time, you'd need three things:
|
||||||
|
// 1) an `Engine` (with all needed functions registered),
|
||||||
|
// 2) a compiled `AST`,
|
||||||
|
// 3) the closure (of type `FnPtr`).
|
||||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||||
// This script creates a closure which may capture variables.
|
|
||||||
let script = "
|
|
||||||
let x = 20;
|
|
||||||
|
|
||||||
// The following closure captures 'x'
|
|
||||||
return |a, b| (x + a) * b;
|
|
||||||
";
|
|
||||||
|
|
||||||
// To call a Rhai closure at a later time, you'd need three things:
|
|
||||||
// 1) an `Engine` (with all needed functions registered),
|
|
||||||
// 2) a compiled `AST`,
|
|
||||||
// 3) the closure (of type `FnPtr`).
|
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
let ast = engine.compile(script)?;
|
// This script creates a closure which captures a variable and returns it.
|
||||||
|
let ast = engine.compile(
|
||||||
|
"
|
||||||
|
let x = 18;
|
||||||
|
|
||||||
|
// The following closure captures 'x'
|
||||||
|
return |a, b| {
|
||||||
|
x += 1; // x is incremented each time
|
||||||
|
(x + a) * b
|
||||||
|
};
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
|
||||||
let closure = engine.eval_ast::<FnPtr>(&ast)?;
|
let closure = engine.eval_ast::<FnPtr>(&ast)?;
|
||||||
|
|
||||||
// Create a closure that we can call any time, encapsulating the
|
// Create a closure by encapsulating the `Engine`, `AST` and `FnPtr`.
|
||||||
// `Engine`, `AST` and `FnPtr`.
|
// In a real application, you'd be handling errors.
|
||||||
let func = move |x: i64, y: i64| -> Result<i64, _> { closure.call(&engine, &ast, (x, y)) };
|
let func = move |x: i64, y: i64| -> i64 { closure.call(&engine, &ast, (x, y)).unwrap() };
|
||||||
|
|
||||||
// Now we can call `func` anywhere just like a normal function!
|
// Now we can call `func` anywhere just like a normal function!
|
||||||
let result = func(1, 2)?;
|
let r1 = func(1, 2);
|
||||||
|
|
||||||
println!("The Answer: {}", result); // prints 42
|
// Notice that each call to `func` returns a different value
|
||||||
|
// because the captured `x` is always changing!
|
||||||
|
let r2 = func(1, 2);
|
||||||
|
let r3 = func(1, 2);
|
||||||
|
|
||||||
|
println!("The Answers: {}, {}, {}", r1, r2, r3); // prints 40, 42, 44
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,8 @@ impl Engine {
|
|||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
|
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments and the
|
||||||
/// and the following options:
|
/// following options:
|
||||||
///
|
///
|
||||||
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
|
/// * whether to evaluate the [`AST`] to load necessary modules before calling the function
|
||||||
/// * whether to rewind the [`Scope`] after the function call
|
/// * whether to rewind the [`Scope`] after the function call
|
||||||
@ -95,8 +95,8 @@ impl Engine {
|
|||||||
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
||||||
/// This is to avoid unnecessarily cloning the arguments.
|
/// This is to avoid unnecessarily cloning the arguments.
|
||||||
///
|
///
|
||||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
/// Do not use the arguments after this call. If they are needed afterwards, clone them _before_
|
||||||
/// clone them _before_ calling this function.
|
/// calling this function.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -99,12 +99,15 @@ impl Engine {
|
|||||||
) {
|
) {
|
||||||
ast.walk(&mut |path| match path.last().unwrap() {
|
ast.walk(&mut |path| match path.last().unwrap() {
|
||||||
// Collect all `import` statements with a string constant path
|
// Collect all `import` statements with a string constant path
|
||||||
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, ..), ..))
|
ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
|
||||||
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
|
Expr::StringConstant(ref s, ..)
|
||||||
{
|
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
|
||||||
imports.insert(s.clone().into());
|
{
|
||||||
true
|
imports.insert(s.clone().into());
|
||||||
}
|
true
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
_ => true,
|
_ => true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,7 @@ impl Expression<'_> {
|
|||||||
self.0.position()
|
self.0.position()
|
||||||
}
|
}
|
||||||
/// Get the value of this expression if it is a literal constant.
|
/// Get the value of this expression if it is a literal constant.
|
||||||
|
///
|
||||||
/// Supports [`INT`][crate::INT], [`FLOAT`][crate::FLOAT], `()`, `char`, `bool` and
|
/// Supports [`INT`][crate::INT], [`FLOAT`][crate::FLOAT], `()`, `char`, `bool` and
|
||||||
/// [`ImmutableString`][crate::ImmutableString].
|
/// [`ImmutableString`][crate::ImmutableString].
|
||||||
///
|
///
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext,
|
Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext,
|
||||||
RhaiResult, RhaiResultOf, Scope, AST,
|
Position, RhaiResult, RhaiResultOf, Scope, AST,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -314,3 +314,27 @@ impl Expression<'_> {
|
|||||||
self.get_string_value()
|
self.get_string_value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
/// Create a new [`Position`].
|
||||||
|
///
|
||||||
|
/// If `line` is zero, then [`None`] is returned.
|
||||||
|
///
|
||||||
|
/// If `position` is zero, then it is at the beginning of a line.
|
||||||
|
///
|
||||||
|
/// # Deprecated
|
||||||
|
///
|
||||||
|
/// This function is deprecated. Use [`new`][Position::new] (which panics when `line` is zero) instead.
|
||||||
|
///
|
||||||
|
/// This method will be removed in the next major version.
|
||||||
|
#[deprecated(since = "1.6.0", note = "use `new` instead")]
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new_const(line: u16, position: u16) -> Option<Self> {
|
||||||
|
if line == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self::new(line, position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,8 +34,9 @@ impl Engine {
|
|||||||
/// ## Constants Propagation
|
/// ## Constants Propagation
|
||||||
///
|
///
|
||||||
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
||||||
/// the scope are propagated throughout the script _including_ functions. This allows functions
|
/// the scope are propagated throughout the script _including_ functions.
|
||||||
/// to be optimized based on dynamic global constants.
|
///
|
||||||
|
/// This allows functions to be optimized based on dynamic global constants.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -72,8 +72,9 @@ impl Engine {
|
|||||||
/// ## Constants Propagation
|
/// ## Constants Propagation
|
||||||
///
|
///
|
||||||
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
||||||
/// the scope are propagated throughout the script _including_ functions. This allows functions
|
/// the scope are propagated throughout the script _including_ functions.
|
||||||
/// to be optimized based on dynamic global constants.
|
///
|
||||||
|
/// This allows functions to be optimized based on dynamic global constants.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -134,8 +135,9 @@ impl Engine {
|
|||||||
/// ## Constants Propagation
|
/// ## Constants Propagation
|
||||||
///
|
///
|
||||||
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
||||||
/// the scope are propagated throughout the script _including_ functions. This allows functions
|
/// the scope are propagated throughout the script _including_ functions.
|
||||||
/// to be optimized based on dynamic global constants.
|
///
|
||||||
|
/// This allows functions to be optimized based on dynamic global constants.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -176,8 +178,9 @@ impl Engine {
|
|||||||
/// ## Constants Propagation
|
/// ## Constants Propagation
|
||||||
///
|
///
|
||||||
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
|
||||||
/// the scope are propagated throughout the script _including_ functions. This allows functions
|
/// the scope are propagated throughout the script _including_ functions.
|
||||||
/// to be optimized based on dynamic global constants.
|
///
|
||||||
|
/// This allows functions to be optimized based on dynamic global constants.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn run_file_with_scope(
|
pub fn run_file_with_scope(
|
||||||
&self,
|
&self,
|
||||||
|
@ -82,21 +82,26 @@ impl Engine {
|
|||||||
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
|
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
|
||||||
self.optimization_level
|
self.optimization_level
|
||||||
}
|
}
|
||||||
/// Optimize the [`AST`][crate::AST] with constants defined in an external Scope. An optimized
|
/// Optimize the [`AST`][crate::AST] with constants defined in an external Scope.
|
||||||
/// copy of the [`AST`][crate::AST] is returned while the original [`AST`][crate::AST] is consumed.
|
/// An optimized copy of the [`AST`][crate::AST] is returned while the original [`AST`][crate::AST]
|
||||||
|
/// is consumed.
|
||||||
///
|
///
|
||||||
/// Not available under `no_optimize`.
|
/// Not available under `no_optimize`.
|
||||||
///
|
///
|
||||||
/// Although optimization is performed by default during compilation, sometimes it is necessary
|
/// Although optimization is performed by default during compilation, sometimes it is necessary
|
||||||
/// to _re_-optimize an [`AST`][crate::AST]. For example, when working with constants that are
|
/// to _re_-optimize an [`AST`][crate::AST].
|
||||||
/// passed in via an external scope, it will be more efficient to optimize the
|
///
|
||||||
/// [`AST`][crate::AST] once again to take advantage of the new constants.
|
/// For example, when working with constants that are passed in via an external scope, it will
|
||||||
|
/// be more efficient to optimize the [`AST`][crate::AST] once again to take advantage of the
|
||||||
|
/// new constants.
|
||||||
///
|
///
|
||||||
/// With this method, it is no longer necessary to recompile a large script. The script
|
/// With this method, it is no longer necessary to recompile a large script. The script
|
||||||
/// [`AST`][crate::AST] can be compiled just once. Before evaluation, constants are passed into
|
/// [`AST`][crate::AST] can be compiled just once.
|
||||||
/// the [`Engine`] via an external scope (i.e. with
|
///
|
||||||
/// [`Scope::push_constant`][crate::Scope::push_constant]). Then, the [`AST`][crate::AST] is
|
/// Before evaluation, constants are passed into the [`Engine`] via an external scope (i.e. with
|
||||||
/// cloned and the copy re-optimized before running.
|
/// [`Scope::push_constant`][crate::Scope::push_constant]).
|
||||||
|
///
|
||||||
|
/// Then, the [`AST`][crate::AST] is cloned and the copy re-optimized before running.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn optimize_ast(
|
pub fn optimize_ast(
|
||||||
@ -148,8 +153,8 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// The following will raise an error during parsing because the `if` keyword is disabled
|
/// The following will raise an error during parsing because the `if` keyword is disabled and is
|
||||||
/// and is recognized as a reserved symbol!
|
/// recognized as a reserved symbol!
|
||||||
///
|
///
|
||||||
/// ```rust,should_panic
|
/// ```rust,should_panic
|
||||||
/// # fn main() -> Result<(), rhai::ParseError> {
|
/// # fn main() -> Result<(), rhai::ParseError> {
|
||||||
|
@ -57,7 +57,7 @@ impl Engine {
|
|||||||
/// Is `if`-expression allowed?
|
/// Is `if`-expression allowed?
|
||||||
/// Default is `true`.
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_if_expression(&self) -> bool {
|
pub const fn allow_if_expression(&self) -> bool {
|
||||||
self.options.allow_if_expr
|
self.options.allow_if_expr
|
||||||
}
|
}
|
||||||
/// Set whether `if`-expression is allowed.
|
/// Set whether `if`-expression is allowed.
|
||||||
@ -68,7 +68,7 @@ impl Engine {
|
|||||||
/// Is `switch` expression allowed?
|
/// Is `switch` expression allowed?
|
||||||
/// Default is `true`.
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_switch_expression(&self) -> bool {
|
pub const fn allow_switch_expression(&self) -> bool {
|
||||||
self.options.allow_switch_expr
|
self.options.allow_switch_expr
|
||||||
}
|
}
|
||||||
/// Set whether `switch` expression is allowed.
|
/// Set whether `switch` expression is allowed.
|
||||||
@ -79,7 +79,7 @@ impl Engine {
|
|||||||
/// Is statement-expression allowed?
|
/// Is statement-expression allowed?
|
||||||
/// Default is `true`.
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_statement_expression(&self) -> bool {
|
pub const fn allow_statement_expression(&self) -> bool {
|
||||||
self.options.allow_stmt_expr
|
self.options.allow_stmt_expr
|
||||||
}
|
}
|
||||||
/// Set whether statement-expression is allowed.
|
/// Set whether statement-expression is allowed.
|
||||||
@ -93,7 +93,7 @@ impl Engine {
|
|||||||
/// Not available under `no_function`.
|
/// Not available under `no_function`.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_anonymous_fn(&self) -> bool {
|
pub const fn allow_anonymous_fn(&self) -> bool {
|
||||||
self.options.allow_anonymous_fn
|
self.options.allow_anonymous_fn
|
||||||
}
|
}
|
||||||
/// Set whether anonymous function is allowed.
|
/// Set whether anonymous function is allowed.
|
||||||
@ -107,7 +107,7 @@ impl Engine {
|
|||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
/// Default is `true`.
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_looping(&self) -> bool {
|
pub const fn allow_looping(&self) -> bool {
|
||||||
self.options.allow_looping
|
self.options.allow_looping
|
||||||
}
|
}
|
||||||
/// Set whether looping is allowed.
|
/// Set whether looping is allowed.
|
||||||
@ -118,7 +118,7 @@ impl Engine {
|
|||||||
/// Is variables shadowing allowed?
|
/// Is variables shadowing allowed?
|
||||||
/// Default is `true`.
|
/// Default is `true`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_shadowing(&self) -> bool {
|
pub const fn allow_shadowing(&self) -> bool {
|
||||||
self.options.allow_shadowing
|
self.options.allow_shadowing
|
||||||
}
|
}
|
||||||
/// Set whether variables shadowing is allowed.
|
/// Set whether variables shadowing is allowed.
|
||||||
@ -129,7 +129,7 @@ impl Engine {
|
|||||||
/// Is strict variables mode enabled?
|
/// Is strict variables mode enabled?
|
||||||
/// Default is `false`.
|
/// Default is `false`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn strict_variables(&self) -> bool {
|
pub const fn strict_variables(&self) -> bool {
|
||||||
self.options.strict_var
|
self.options.strict_var
|
||||||
}
|
}
|
||||||
/// Set whether strict variables mode is enabled.
|
/// Set whether strict variables mode is enabled.
|
||||||
@ -143,7 +143,7 @@ impl Engine {
|
|||||||
/// Not available under `no_object`.
|
/// Not available under `no_object`.
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn fail_on_invalid_map_property(&self) -> bool {
|
pub const fn fail_on_invalid_map_property(&self) -> bool {
|
||||||
self.options.fail_on_invalid_map_property
|
self.options.fail_on_invalid_map_property
|
||||||
}
|
}
|
||||||
/// Set whether to raise error if an object map property does not exist.
|
/// Set whether to raise error if an object map property does not exist.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Module defining the AST (abstract syntax tree).
|
//! Module defining the AST (abstract syntax tree).
|
||||||
|
|
||||||
use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS::*};
|
use super::{Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer, AST_OPTION_FLAGS::*};
|
||||||
use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec};
|
use crate::{Dynamic, FnNamespace, Identifier, Position};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -197,7 +197,7 @@ impl AST {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn take_statements(&mut self) -> StaticVec<Stmt> {
|
pub(crate) fn take_statements(&mut self) -> StmtBlockContainer {
|
||||||
self.body.take_statements()
|
self.body.take_statements()
|
||||||
}
|
}
|
||||||
/// Does this [`AST`] contain script-defined functions?
|
/// Does this [`AST`] contain script-defined functions?
|
||||||
@ -744,10 +744,11 @@ impl AST {
|
|||||||
include_variables: bool,
|
include_variables: bool,
|
||||||
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
|
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
|
||||||
self.statements().iter().filter_map(move |stmt| match stmt {
|
self.statements().iter().filter_map(move |stmt| match stmt {
|
||||||
Stmt::Var(expr, name, options, ..)
|
Stmt::Var(x, options, ..)
|
||||||
if options.contains(AST_OPTION_CONSTANT) && include_constants
|
if options.contains(AST_OPTION_CONSTANT) && include_constants
|
||||||
|| !options.contains(AST_OPTION_CONSTANT) && include_variables =>
|
|| !options.contains(AST_OPTION_CONSTANT) && include_variables =>
|
||||||
{
|
{
|
||||||
|
let (name, expr, ..) = x.as_ref();
|
||||||
if let Some(value) = expr.get_literal_value() {
|
if let Some(value) = expr.get_literal_value() {
|
||||||
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
|
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,8 +13,8 @@ pub enum FnAccess {
|
|||||||
Private,
|
Private,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that holds a configuration option with bit-flags. Exported under the `internals` feature
|
/// _(internals)_ A type that holds a configuration option with bit-flags.
|
||||||
/// only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
/// Functionality-wise, this type is a naive and simplistic implementation of
|
/// Functionality-wise, this type is a naive and simplistic implementation of
|
||||||
/// [`bit_flags`](https://crates.io/crates/bitflags). It is re-implemented to avoid pulling in yet
|
/// [`bit_flags`](https://crates.io/crates/bitflags). It is re-implemented to avoid pulling in yet
|
||||||
@ -113,7 +113,8 @@ impl BitAndAssign for OptionFlags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Option bit-flags for [`AST`][super::AST] nodes.
|
/// _(internals)_ Option bit-flags for [`AST`][super::AST] nodes.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub mod AST_OPTION_FLAGS {
|
pub mod AST_OPTION_FLAGS {
|
||||||
use super::OptionFlags;
|
use super::OptionFlags;
|
||||||
|
@ -16,7 +16,10 @@ pub use ident::Ident;
|
|||||||
pub use script_fn::EncapsulatedEnviron;
|
pub use script_fn::EncapsulatedEnviron;
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||||
pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};
|
pub use stmt::{
|
||||||
|
ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases,
|
||||||
|
TryCatchBlock,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub use expr::FloatWrapper;
|
pub use expr::FloatWrapper;
|
||||||
|
@ -84,8 +84,8 @@ pub struct ScriptFnMetadata<'a> {
|
|||||||
///
|
///
|
||||||
/// Line doc-comments are kept in one string slice per line without the termination line-break.
|
/// Line doc-comments are kept in one string slice per line without the termination line-break.
|
||||||
///
|
///
|
||||||
/// Leading white-spaces are stripped, and each string slice always starts with the corresponding
|
/// Leading white-spaces are stripped, and each string slice always starts with the
|
||||||
/// doc-comment leader: `///` or `/**`.
|
/// corresponding doc-comment leader: `///` or `/**`.
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
pub comments: Vec<&'a str>,
|
pub comments: Vec<&'a str>,
|
||||||
/// Function access mode.
|
/// Function access mode.
|
||||||
|
295
src/ast/stmt.rs
295
src/ast/stmt.rs
@ -11,6 +11,7 @@ use std::{
|
|||||||
fmt,
|
fmt,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
mem,
|
mem,
|
||||||
|
num::NonZeroUsize,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,9 +115,9 @@ pub struct SwitchCases {
|
|||||||
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
||||||
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
|
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
|
||||||
/// Statements block for the default case (there can be no condition for the default case).
|
/// Statements block for the default case (there can be no condition for the default case).
|
||||||
pub def_case: StmtBlock,
|
pub def_case: Box<StmtBlock>,
|
||||||
/// List of range cases.
|
/// List of range cases.
|
||||||
pub ranges: StaticVec<(INT, INT, bool, ConditionalStmtBlock)>,
|
pub ranges: StaticVec<(INT, INT, bool, Box<ConditionalStmtBlock>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ A `try-catch` block.
|
/// _(internals)_ A `try-catch` block.
|
||||||
@ -131,31 +132,51 @@ pub struct TryCatchBlock {
|
|||||||
pub catch_block: StmtBlock,
|
pub catch_block: StmtBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// _(internals)_ The underlying container type for [`StmtBlock`].
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// A [`SmallVec`](https://crates.io/crates/smallvec) containing up to 8 items inline is used to
|
||||||
|
/// hold a statements block, with the assumption that most program blocks would container fewer than
|
||||||
|
/// 8 statements, and those that do have a lot more statements.
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
pub type StmtBlockContainer = smallvec::SmallVec<[Stmt; 8]>;
|
||||||
|
|
||||||
|
/// _(internals)_ The underlying container type for [`StmtBlock`].
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
|
pub type StmtBlockContainer = StaticVec<Stmt>;
|
||||||
|
|
||||||
/// _(internals)_ A scoped block of statements.
|
/// _(internals)_ A scoped block of statements.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Clone, Hash, Default)]
|
#[derive(Clone, Hash, Default)]
|
||||||
pub struct StmtBlock(StaticVec<Stmt>, Span);
|
pub struct StmtBlock(StmtBlockContainer, Span);
|
||||||
|
|
||||||
impl StmtBlock {
|
impl StmtBlock {
|
||||||
/// A [`StmtBlock`] that does not exist.
|
/// A [`StmtBlock`] that does not exist.
|
||||||
pub const NONE: Self = Self::empty(Position::NONE);
|
pub const NONE: Self = Self::empty(Position::NONE);
|
||||||
|
|
||||||
/// Create a new [`StmtBlock`].
|
/// Create a new [`StmtBlock`].
|
||||||
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
statements: impl IntoIterator<Item = Stmt>,
|
statements: impl IntoIterator<Item = Stmt>,
|
||||||
start_pos: Position,
|
start_pos: Position,
|
||||||
end_pos: Position,
|
end_pos: Position,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut statements: StaticVec<_> = statements.into_iter().collect();
|
Self::new_with_span(statements, Span::new(start_pos, end_pos))
|
||||||
|
}
|
||||||
|
/// Create a new [`StmtBlock`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn new_with_span(statements: impl IntoIterator<Item = Stmt>, span: Span) -> Self {
|
||||||
|
let mut statements: smallvec::SmallVec<_> = statements.into_iter().collect();
|
||||||
statements.shrink_to_fit();
|
statements.shrink_to_fit();
|
||||||
Self(statements, Span::new(start_pos, end_pos))
|
Self(statements, span)
|
||||||
}
|
}
|
||||||
/// Create an empty [`StmtBlock`].
|
/// Create an empty [`StmtBlock`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn empty(pos: Position) -> Self {
|
pub const fn empty(pos: Position) -> Self {
|
||||||
Self(StaticVec::new_const(), Span::new(pos, pos))
|
Self(StmtBlockContainer::new_const(), Span::new(pos, pos))
|
||||||
}
|
}
|
||||||
/// Is this statements block empty?
|
/// Is this statements block empty?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -178,7 +199,7 @@ impl StmtBlock {
|
|||||||
/// Extract the statements.
|
/// Extract the statements.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn take_statements(&mut self) -> StaticVec<Stmt> {
|
pub(crate) fn take_statements(&mut self) -> StmtBlockContainer {
|
||||||
mem::take(&mut self.0)
|
mem::take(&mut self.0)
|
||||||
}
|
}
|
||||||
/// Get an iterator over the statements of this statements block.
|
/// Get an iterator over the statements of this statements block.
|
||||||
@ -223,7 +244,7 @@ impl StmtBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for StmtBlock {
|
impl Deref for StmtBlock {
|
||||||
type Target = StaticVec<Stmt>;
|
type Target = StmtBlockContainer;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@ -267,8 +288,8 @@ impl From<Stmt> for StmtBlock {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn from(stmt: Stmt) -> Self {
|
fn from(stmt: Stmt) -> Self {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Block(mut block, span) => Self(block.iter_mut().map(mem::take).collect(), span),
|
Stmt::Block(block) => *block,
|
||||||
Stmt::Noop(pos) => Self(StaticVec::new_const(), Span::new(pos, pos)),
|
Stmt::Noop(pos) => Self(StmtBlockContainer::new_const(), Span::new(pos, pos)),
|
||||||
_ => {
|
_ => {
|
||||||
let pos = stmt.position();
|
let pos = stmt.position();
|
||||||
Self(vec![stmt].into(), Span::new(pos, Position::NONE))
|
Self(vec![stmt].into(), Span::new(pos, Position::NONE))
|
||||||
@ -279,6 +300,9 @@ impl From<Stmt> for StmtBlock {
|
|||||||
|
|
||||||
impl IntoIterator for StmtBlock {
|
impl IntoIterator for StmtBlock {
|
||||||
type Item = Stmt;
|
type Item = Stmt;
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
type IntoIter = smallvec::IntoIter<[Stmt; 8]>;
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
type IntoIter = smallvec::IntoIter<[Stmt; 3]>;
|
type IntoIter = smallvec::IntoIter<[Stmt; 3]>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -301,7 +325,7 @@ pub enum Stmt {
|
|||||||
/// No-op.
|
/// No-op.
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
/// `if` expr `{` stmt `}` `else` `{` stmt `}`
|
/// `if` expr `{` stmt `}` `else` `{` stmt `}`
|
||||||
If(Expr, Box<(StmtBlock, StmtBlock)>, Position),
|
If(Box<(Expr, StmtBlock, StmtBlock)>, Position),
|
||||||
/// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}`
|
/// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}`
|
||||||
///
|
///
|
||||||
/// ### Data Structure
|
/// ### Data Structure
|
||||||
@ -309,27 +333,31 @@ pub enum Stmt {
|
|||||||
/// 0) Hash table for (condition, block)
|
/// 0) Hash table for (condition, block)
|
||||||
/// 1) Default block
|
/// 1) Default block
|
||||||
/// 2) List of ranges: (start, end, inclusive, condition, statement)
|
/// 2) List of ranges: (start, end, inclusive, condition, statement)
|
||||||
Switch(Expr, Box<SwitchCases>, Position),
|
Switch(Box<(Expr, SwitchCases)>, Position),
|
||||||
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
|
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
|
||||||
///
|
///
|
||||||
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
|
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
|
||||||
While(Expr, Box<StmtBlock>, Position),
|
While(Box<(Expr, StmtBlock)>, Position),
|
||||||
/// `do` `{` stmt `}` `while`|`until` expr
|
/// `do` `{` stmt `}` `while`|`until` expr
|
||||||
///
|
///
|
||||||
/// ### Option Flags
|
/// ### Option Flags
|
||||||
///
|
///
|
||||||
/// * [`AST_OPTION_NONE`] = `while`
|
/// * [`AST_OPTION_NONE`] = `while`
|
||||||
/// * [`AST_OPTION_NEGATED`] = `until`
|
/// * [`AST_OPTION_NEGATED`] = `until`
|
||||||
Do(Box<StmtBlock>, Expr, OptionFlags, Position),
|
Do(Box<(Expr, StmtBlock)>, OptionFlags, Position),
|
||||||
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
||||||
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
|
For(Box<(Ident, Option<Ident>, Expr, StmtBlock)>, Position),
|
||||||
/// \[`export`\] `let`|`const` id `=` expr
|
/// \[`export`\] `let`|`const` id `=` expr
|
||||||
///
|
///
|
||||||
/// ### Option Flags
|
/// ### Option Flags
|
||||||
///
|
///
|
||||||
/// * [`AST_OPTION_EXPORTED`] = `export`
|
/// * [`AST_OPTION_EXPORTED`] = `export`
|
||||||
/// * [`AST_OPTION_CONSTANT`] = `const`
|
/// * [`AST_OPTION_CONSTANT`] = `const`
|
||||||
Var(Expr, Box<Ident>, OptionFlags, Position),
|
Var(
|
||||||
|
Box<(Ident, Expr, Option<NonZeroUsize>)>,
|
||||||
|
OptionFlags,
|
||||||
|
Position,
|
||||||
|
),
|
||||||
/// expr op`=` expr
|
/// expr op`=` expr
|
||||||
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
|
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
@ -338,11 +366,11 @@ pub enum Stmt {
|
|||||||
/// function call forming one statement.
|
/// function call forming one statement.
|
||||||
FnCall(Box<FnCallExpr>, Position),
|
FnCall(Box<FnCallExpr>, Position),
|
||||||
/// `{` stmt`;` ... `}`
|
/// `{` stmt`;` ... `}`
|
||||||
Block(Box<[Stmt]>, Span),
|
Block(Box<StmtBlock>),
|
||||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||||
TryCatch(Box<TryCatchBlock>, Position),
|
TryCatch(Box<TryCatchBlock>, Position),
|
||||||
/// [expression][Expr]
|
/// [expression][Expr]
|
||||||
Expr(Expr),
|
Expr(Box<Expr>),
|
||||||
/// `continue`/`break`
|
/// `continue`/`break`
|
||||||
///
|
///
|
||||||
/// ### Option Flags
|
/// ### Option Flags
|
||||||
@ -356,13 +384,13 @@ pub enum Stmt {
|
|||||||
///
|
///
|
||||||
/// * [`AST_OPTION_NONE`] = `return`
|
/// * [`AST_OPTION_NONE`] = `return`
|
||||||
/// * [`AST_OPTION_BREAK`] = `throw`
|
/// * [`AST_OPTION_BREAK`] = `throw`
|
||||||
Return(OptionFlags, Option<Expr>, Position),
|
Return(Option<Box<Expr>>, OptionFlags, Position),
|
||||||
/// `import` expr `as` var
|
/// `import` expr `as` alias
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import(Expr, Option<Box<Ident>>, Position),
|
Import(Box<(Expr, Option<Ident>)>, Position),
|
||||||
/// `export` var `as` var
|
/// `export` var `as` alias
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -376,7 +404,7 @@ pub enum Stmt {
|
|||||||
/// This variant does not map to any language structure. It is currently only used only to
|
/// This variant does not map to any language structure. It is currently only used only to
|
||||||
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
|
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Share(crate::Identifier, Position),
|
Share(Box<crate::Identifier>, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Stmt {
|
impl Default for Stmt {
|
||||||
@ -389,7 +417,21 @@ impl Default for Stmt {
|
|||||||
impl From<StmtBlock> for Stmt {
|
impl From<StmtBlock> for Stmt {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(block: StmtBlock) -> Self {
|
fn from(block: StmtBlock) -> Self {
|
||||||
Self::Block(block.0.into_boxed_slice(), block.1)
|
Self::Block(block.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IntoIterator<Item = Stmt>> From<(T, Position, Position)> for Stmt {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: (T, Position, Position)) -> Self {
|
||||||
|
StmtBlock::new(value.0, value.1, value.2).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IntoIterator<Item = Stmt>> From<(T, Span)> for Stmt {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: (T, Span)) -> Self {
|
||||||
|
StmtBlock::new_with_span(value.0, value.1).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +459,7 @@ impl Stmt {
|
|||||||
| Self::Var(.., pos)
|
| Self::Var(.., pos)
|
||||||
| Self::TryCatch(.., pos) => *pos,
|
| Self::TryCatch(.., pos) => *pos,
|
||||||
|
|
||||||
Self::Block(.., span) => span.start(),
|
Self::Block(x) => x.position(),
|
||||||
|
|
||||||
Self::Expr(x) => x.start_position(),
|
Self::Expr(x) => x.start_position(),
|
||||||
|
|
||||||
@ -446,7 +488,7 @@ impl Stmt {
|
|||||||
| Self::Var(.., pos)
|
| Self::Var(.., pos)
|
||||||
| Self::TryCatch(.., pos) => *pos = new_pos,
|
| Self::TryCatch(.., pos) => *pos = new_pos,
|
||||||
|
|
||||||
Self::Block(.., span) => *span = Span::new(new_pos, span.end()),
|
Self::Block(x) => x.set_position(new_pos, x.end_position()),
|
||||||
|
|
||||||
Self::Expr(x) => {
|
Self::Expr(x) => {
|
||||||
x.set_position(new_pos);
|
x.set_position(new_pos);
|
||||||
@ -502,11 +544,13 @@ impl Stmt {
|
|||||||
// A No-op requires a semicolon in order to know it is an empty statement!
|
// A No-op requires a semicolon in order to know it is an empty statement!
|
||||||
Self::Noop(..) => false,
|
Self::Noop(..) => false,
|
||||||
|
|
||||||
Self::Expr(Expr::Custom(x, ..)) if x.is_self_terminated() => true,
|
Self::Expr(e) => match &**e {
|
||||||
|
Expr::Custom(x, ..) if x.is_self_terminated() => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
|
||||||
Self::Var(..)
|
Self::Var(..)
|
||||||
| Self::Assignment(..)
|
| Self::Assignment(..)
|
||||||
| Self::Expr(..)
|
|
||||||
| Self::FnCall(..)
|
| Self::FnCall(..)
|
||||||
| Self::Do(..)
|
| Self::Do(..)
|
||||||
| Self::BreakLoop(..)
|
| Self::BreakLoop(..)
|
||||||
@ -527,38 +571,37 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Self::Noop(..) => true,
|
Self::Noop(..) => true,
|
||||||
Self::Expr(expr) => expr.is_pure(),
|
Self::Expr(expr) => expr.is_pure(),
|
||||||
Self::If(condition, x, ..) => {
|
Self::If(x, ..) => {
|
||||||
condition.is_pure()
|
x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure)
|
||||||
&& x.0.iter().all(Stmt::is_pure)
|
|
||||||
&& x.1.iter().all(Stmt::is_pure)
|
|
||||||
}
|
}
|
||||||
Self::Switch(expr, x, ..) => {
|
Self::Switch(x, ..) => {
|
||||||
expr.is_pure()
|
x.0.is_pure()
|
||||||
&& x.cases.values().all(|block| {
|
&& x.1.cases.values().all(|block| {
|
||||||
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||||
&& block.statements.iter().all(Stmt::is_pure)
|
&& block.statements.iter().all(Stmt::is_pure)
|
||||||
})
|
})
|
||||||
&& x.ranges.iter().all(|(.., block)| {
|
&& x.1.ranges.iter().all(|(.., block)| {
|
||||||
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||||
&& block.statements.iter().all(Stmt::is_pure)
|
&& block.statements.iter().all(Stmt::is_pure)
|
||||||
})
|
})
|
||||||
&& x.def_case.iter().all(Stmt::is_pure)
|
&& x.1.def_case.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loops that exit can be pure because it can never be infinite.
|
// Loops that exit can be pure because it can never be infinite.
|
||||||
Self::While(Expr::BoolConstant(false, ..), ..) => true,
|
Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true,
|
||||||
Self::Do(body, Expr::BoolConstant(x, ..), options, ..)
|
Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 {
|
||||||
if *x == options.contains(AST_OPTION_NEGATED) =>
|
Expr::BoolConstant(cond, ..) if cond == options.contains(AST_OPTION_NEGATED) => {
|
||||||
{
|
x.1.iter().all(Stmt::is_pure)
|
||||||
body.iter().all(Stmt::is_pure)
|
}
|
||||||
}
|
_ => false,
|
||||||
|
},
|
||||||
|
|
||||||
// Loops are never pure since they can be infinite - and that's a side effect.
|
// Loops are never pure since they can be infinite - and that's a side effect.
|
||||||
Self::While(..) | Self::Do(..) => false,
|
Self::While(..) | Self::Do(..) => false,
|
||||||
|
|
||||||
// For loops can be pure because if the iterable is pure, it is finite,
|
// For loops can be pure because if the iterable is pure, it is finite,
|
||||||
// so infinite loops can never occur.
|
// so infinite loops can never occur.
|
||||||
Self::For(iterable, x, ..) => iterable.is_pure() && x.2.iter().all(Stmt::is_pure),
|
Self::For(x, ..) => x.2.is_pure() && x.3.iter().all(Stmt::is_pure),
|
||||||
|
|
||||||
Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false,
|
Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false,
|
||||||
Self::Block(block, ..) => block.iter().all(|stmt| stmt.is_pure()),
|
Self::Block(block, ..) => block.iter().all(|stmt| stmt.is_pure()),
|
||||||
@ -578,8 +621,8 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
/// Does this statement's behavior depend on its containing block?
|
/// Does this statement's behavior depend on its containing block?
|
||||||
///
|
///
|
||||||
/// A statement that depends on its containing block behaves differently when promoted
|
/// A statement that depends on its containing block behaves differently when promoted to an
|
||||||
/// to an upper block.
|
/// upper block.
|
||||||
///
|
///
|
||||||
/// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements,
|
/// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements,
|
||||||
/// and `eval` calls (which may in turn call define variables) fall under this category.
|
/// and `eval` calls (which may in turn call define variables) fall under this category.
|
||||||
@ -589,11 +632,13 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Self::Var(..) => true,
|
Self::Var(..) => true,
|
||||||
|
|
||||||
Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_block_dependent),
|
Self::Expr(e) => match &**e {
|
||||||
|
Expr::Stmt(s) => s.iter().all(Stmt::is_block_dependent),
|
||||||
|
Expr::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
|
||||||
Self::FnCall(x, ..) | Self::Expr(Expr::FnCall(x, ..)) => {
|
Self::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL,
|
||||||
!x.is_qualified() && x.name == KEYWORD_EVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Import(..) | Self::Export(..) => true,
|
Self::Import(..) | Self::Export(..) => true,
|
||||||
@ -611,12 +656,15 @@ impl Stmt {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_internally_pure(&self) -> bool {
|
pub fn is_internally_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Var(expr, _, ..) => expr.is_pure(),
|
Self::Var(x, ..) => x.1.is_pure(),
|
||||||
|
|
||||||
Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_internally_pure),
|
Self::Expr(e) => match e.as_ref() {
|
||||||
|
Expr::Stmt(s) => s.iter().all(Stmt::is_internally_pure),
|
||||||
|
_ => self.is_pure(),
|
||||||
|
},
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Import(expr, ..) => expr.is_pure(),
|
Self::Import(x, ..) => x.0.is_pure(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Export(..) => true,
|
Self::Export(..) => true,
|
||||||
|
|
||||||
@ -651,86 +699,86 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Var(e, _, ..) => {
|
Self::Var(x, ..) => {
|
||||||
if !e.walk(path, on_node) {
|
if !x.1.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::If(e, x, ..) => {
|
Self::If(x, ..) => {
|
||||||
if !e.walk(path, on_node) {
|
if !x.0.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in x.0.iter() {
|
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s in x.1.iter() {
|
for s in x.1.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Self::Switch(e, x, ..) => {
|
|
||||||
if !e.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for b in x.cases.values() {
|
|
||||||
if !b
|
|
||||||
.condition
|
|
||||||
.as_ref()
|
|
||||||
.map(|e| e.walk(path, on_node))
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for s in b.statements.iter() {
|
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (.., b) in &x.ranges {
|
|
||||||
if !b
|
|
||||||
.condition
|
|
||||||
.as_ref()
|
|
||||||
.map(|e| e.walk(path, on_node))
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for s in b.statements.iter() {
|
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s in x.def_case.iter() {
|
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::While(e, s, ..) | Self::Do(s, e, ..) => {
|
|
||||||
if !e.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for s in &s.0 {
|
|
||||||
if !s.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::For(e, x, ..) => {
|
|
||||||
if !e.walk(path, on_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for s in x.2.iter() {
|
for s in x.2.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self::Switch(x, ..) => {
|
||||||
|
if !x.0.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for b in x.1.cases.values() {
|
||||||
|
if !b
|
||||||
|
.condition
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.walk(path, on_node))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for s in b.statements.iter() {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (.., b) in &x.1.ranges {
|
||||||
|
if !b
|
||||||
|
.condition
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.walk(path, on_node))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for s in b.statements.iter() {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s in x.1.def_case.iter() {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::While(x, ..) | Self::Do(x, ..) => {
|
||||||
|
if !x.0.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for s in x.1.statements() {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::For(x, ..) => {
|
||||||
|
if !x.2.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for s in x.3.iter() {
|
||||||
|
if !s.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Assignment(x, ..) => {
|
Self::Assignment(x, ..) => {
|
||||||
if !x.1.lhs.walk(path, on_node) {
|
if !x.1.lhs.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
@ -747,7 +795,7 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Block(x, ..) => {
|
Self::Block(x, ..) => {
|
||||||
for s in x.iter() {
|
for s in x.statements() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -765,14 +813,19 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Expr(e) | Self::Return(_, Some(e), _) => {
|
Self::Expr(e) => {
|
||||||
|
if !e.walk(path, on_node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Return(Some(e), ..) => {
|
||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Import(e, ..) => {
|
Self::Import(x, ..) => {
|
||||||
if !e.walk(path, on_node) {
|
if !x.0.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
Rhai Tools
|
Rhai Tools
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Tools for running Rhai scripts.
|
Tools for working with Rhai scripts.
|
||||||
|
|
||||||
| Tool | Required feature(s) | Description |
|
| Tool | Required feature(s) | Description |
|
||||||
| -------------------------------------------------------------------------------- | :-----------------: | --------------------------------------------------- |
|
| -------------------------------------------------------------------------------- | :-----------------: | ----------------------------------------------------- |
|
||||||
| [`rhai-run`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-run.rs) | | runs each filename passed to it as a Rhai script |
|
| [`rhai-run`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-run.rs) | | runs each filename passed to it as a Rhai script |
|
||||||
| [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | simple REPL that interactively evaluates statements |
|
| [`rhai-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | a simple REPL that interactively evaluates statements |
|
||||||
| [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ |
|
| [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ |
|
||||||
|
|
||||||
There is a feature called `bin-features` which automatically includes all the necessary features
|
For convenience, a feature named `bin-features` is available which is a combination of the following:
|
||||||
required for building these tools.
|
|
||||||
|
* `decimal` – support for decimal numbers
|
||||||
|
* `metadata` – access functions metadata
|
||||||
|
* `serde` – export functions metadata to JSON
|
||||||
|
* `debugging` – required by `rhai-dbg`
|
||||||
|
* `rustyline` – required by `rhai-repl`
|
||||||
|
|
||||||
|
|
||||||
How to Run
|
How to Run
|
||||||
@ -33,5 +38,5 @@ cargo install --path . --bins --features bin-features
|
|||||||
or specifically:
|
or specifically:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install --path . --bin rhai-run --features bin-features
|
cargo install --path . --bin sample_app_to_run --features bin-features
|
||||||
```
|
```
|
||||||
|
@ -635,6 +635,7 @@ fn main() {
|
|||||||
// Hook up debugger
|
// Hook up debugger
|
||||||
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
engine.register_debugger(
|
engine.register_debugger(
|
||||||
// Store the current source in the debugger state
|
// Store the current source in the debugger state
|
||||||
|| "".into(),
|
|| "".into(),
|
||||||
|
@ -187,9 +187,9 @@ impl Engine {
|
|||||||
self.call_indexer_set(
|
self.call_indexer_set(
|
||||||
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||||
)
|
)
|
||||||
.or_else(|idx_err| match *idx_err {
|
.or_else(|e| match *e {
|
||||||
ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)),
|
ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)),
|
||||||
_ => Err(idx_err),
|
_ => Err(e),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,28 +333,25 @@ impl Engine {
|
|||||||
self.call_indexer_get(
|
self.call_indexer_get(
|
||||||
global, state, lib, target, &mut prop, level,
|
global, state, lib, target, &mut prop, level,
|
||||||
)
|
)
|
||||||
.map_err(
|
.map_err(|e| {
|
||||||
|idx_err| match *idx_err {
|
match *e {
|
||||||
ERR::ErrorIndexingType(..) => err,
|
ERR::ErrorIndexingType(..) => err,
|
||||||
_ => idx_err,
|
_ => e,
|
||||||
},
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.eval_op_assignment(
|
{
|
||||||
global,
|
let orig_val = &mut (&mut orig_val).into();
|
||||||
state,
|
|
||||||
lib,
|
self.eval_op_assignment(
|
||||||
op_info,
|
global, state, lib, op_info, op_pos, orig_val, root, new_val,
|
||||||
op_pos,
|
level,
|
||||||
&mut (&mut orig_val).into(),
|
)
|
||||||
root,
|
.map_err(|err| err.fill_position(new_pos))?;
|
||||||
new_val,
|
}
|
||||||
level,
|
|
||||||
)
|
|
||||||
.map_err(|err| err.fill_position(new_pos))?;
|
|
||||||
|
|
||||||
new_val = orig_val;
|
new_val = orig_val;
|
||||||
}
|
}
|
||||||
@ -373,12 +370,10 @@ impl Engine {
|
|||||||
self.call_indexer_set(
|
self.call_indexer_set(
|
||||||
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
global, state, lib, target, idx, new_val, is_ref_mut, level,
|
||||||
)
|
)
|
||||||
.map_err(
|
.map_err(|e| match *e {
|
||||||
|idx_err| match *idx_err {
|
ERR::ErrorIndexingType(..) => err,
|
||||||
ERR::ErrorIndexingType(..) => err,
|
_ => e,
|
||||||
_ => idx_err,
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})
|
})
|
||||||
@ -403,11 +398,9 @@ impl Engine {
|
|||||||
self.call_indexer_get(
|
self.call_indexer_get(
|
||||||
global, state, lib, target, &mut prop, level,
|
global, state, lib, target, &mut prop, level,
|
||||||
)
|
)
|
||||||
.map_err(|idx_err| {
|
.map_err(|e| match *e {
|
||||||
match *idx_err {
|
ERR::ErrorIndexingType(..) => err,
|
||||||
ERR::ErrorIndexingType(..) => err,
|
_ => e,
|
||||||
_ => idx_err,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
@ -502,39 +495,28 @@ impl Engine {
|
|||||||
global, state, lib, target, &mut prop, level,
|
global, state, lib, target, &mut prop, level,
|
||||||
)
|
)
|
||||||
.map_err(
|
.map_err(
|
||||||
|idx_err| match *idx_err {
|
|e| match *e {
|
||||||
ERR::ErrorIndexingType(..) => err,
|
ERR::ErrorIndexingType(..) => err,
|
||||||
_ => idx_err,
|
_ => e,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let val = &mut val;
|
let val = &mut (&mut val).into();
|
||||||
|
|
||||||
let (result, may_be_changed) = self
|
let (result, may_be_changed) = self
|
||||||
.eval_dot_index_chain_helper(
|
.eval_dot_index_chain_helper(
|
||||||
global,
|
global, state, lib, this_ptr, val, root, rhs, &x.rhs,
|
||||||
state,
|
*term, idx_values, rhs_chain, level, new_val,
|
||||||
lib,
|
|
||||||
this_ptr,
|
|
||||||
&mut val.into(),
|
|
||||||
root,
|
|
||||||
rhs,
|
|
||||||
&x.rhs,
|
|
||||||
*term,
|
|
||||||
idx_values,
|
|
||||||
rhs_chain,
|
|
||||||
level,
|
|
||||||
new_val,
|
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(*x_pos))?;
|
.map_err(|err| err.fill_position(*x_pos))?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
if may_be_changed {
|
if may_be_changed {
|
||||||
// Re-use args because the first &mut parameter will not be consumed
|
// Re-use args because the first &mut parameter will not be consumed
|
||||||
let mut arg_values = [target.as_mut(), val];
|
let mut arg_values = [target.as_mut(), val.as_mut()];
|
||||||
let args = &mut arg_values;
|
let args = &mut arg_values;
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
None, global, state, lib, setter, hash_set, args,
|
None, global, state, lib, setter, hash_set, args,
|
||||||
@ -550,13 +532,13 @@ impl Engine {
|
|||||||
global, state, lib, target, idx, new_val,
|
global, state, lib, target, idx, new_val,
|
||||||
is_ref_mut, level,
|
is_ref_mut, level,
|
||||||
)
|
)
|
||||||
.or_else(|idx_err| match *idx_err {
|
.or_else(|e| match *e {
|
||||||
// If there is no setter, no need to feed it
|
// If there is no setter, no need to feed it
|
||||||
// back because the property is read-only
|
// back because the property is read-only
|
||||||
ERR::ErrorIndexingType(..) => {
|
ERR::ErrorIndexingType(..) => {
|
||||||
Ok((Dynamic::UNIT, false))
|
Ok((Dynamic::UNIT, false))
|
||||||
}
|
}
|
||||||
_ => Err(idx_err),
|
_ => Err(e),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
@ -584,11 +566,11 @@ impl Engine {
|
|||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
global.debugger.reset_status(reset_debugger);
|
global.debugger.reset_status(reset_debugger);
|
||||||
|
|
||||||
let val = &mut result?.0;
|
let (val, _) = &mut result?;
|
||||||
let target = &mut val.into();
|
let val = &mut val.into();
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
global, state, lib, this_ptr, target, root, rhs, &x.rhs, *term,
|
global, state, lib, this_ptr, val, root, rhs, &x.rhs, *term,
|
||||||
idx_values, rhs_chain, level, new_val,
|
idx_values, rhs_chain, level, new_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(pos))
|
.map_err(|err| err.fill_position(pos))
|
||||||
|
@ -361,17 +361,23 @@ impl Debugger {
|
|||||||
node.position() == *pos && _src == source
|
node.position() == *pos && _src == source
|
||||||
}
|
}
|
||||||
BreakPoint::AtFunctionName { name, .. } => match node {
|
BreakPoint::AtFunctionName { name, .. } => match node {
|
||||||
ASTNode::Expr(Expr::FnCall(x, ..))
|
ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
|
||||||
| ASTNode::Stmt(Stmt::FnCall(x, ..))
|
x.name == *name
|
||||||
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, ..))) => x.name == *name,
|
}
|
||||||
|
ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() {
|
||||||
|
Expr::FnCall(x, ..) => x.name == *name,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
BreakPoint::AtFunctionCall { name, args, .. } => match node {
|
BreakPoint::AtFunctionCall { name, args, .. } => match node {
|
||||||
ASTNode::Expr(Expr::FnCall(x, ..))
|
ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
|
||||||
| ASTNode::Stmt(Stmt::FnCall(x, ..))
|
|
||||||
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, ..))) => {
|
|
||||||
x.args.len() == *args && x.name == *name
|
x.args.len() == *args && x.name == *name
|
||||||
}
|
}
|
||||||
|
ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() {
|
||||||
|
Expr::FnCall(x, ..) => x.args.len() == *args && x.name == *name,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -548,9 +554,12 @@ impl Engine {
|
|||||||
DebuggerCommand::FunctionExit => {
|
DebuggerCommand::FunctionExit => {
|
||||||
// Bump a level if it is a function call
|
// Bump a level if it is a function call
|
||||||
let level = match node {
|
let level = match node {
|
||||||
ASTNode::Expr(Expr::FnCall(..))
|
ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => {
|
||||||
| ASTNode::Stmt(Stmt::FnCall(..))
|
context.call_level() + 1
|
||||||
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(..))) => context.call_level() + 1,
|
}
|
||||||
|
ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => {
|
||||||
|
context.call_level() + 1
|
||||||
|
}
|
||||||
_ => context.call_level(),
|
_ => context.call_level(),
|
||||||
};
|
};
|
||||||
global.debugger.status = DebuggerStatus::FunctionExit(level);
|
global.debugger.status = DebuggerStatus::FunctionExit(level);
|
||||||
|
@ -419,17 +419,13 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
let val_sizes = Self::calc_data_sizes(&value, true);
|
let delta = Self::calc_data_sizes(&value, true);
|
||||||
|
|
||||||
*map.get_mut(key).unwrap() = value;
|
*map.get_mut(key).unwrap() = value;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if self.has_data_size_limit() {
|
if self.has_data_size_limit() {
|
||||||
sizes = (
|
sizes = (sizes.0 + delta.0, sizes.1 + delta.1, sizes.2 + delta.2);
|
||||||
sizes.0 + val_sizes.0,
|
|
||||||
sizes.1 + val_sizes.1,
|
|
||||||
sizes.2 + val_sizes.2,
|
|
||||||
);
|
|
||||||
self.raise_err_if_over_data_size_limit(sizes, value_expr.position())?;
|
self.raise_err_if_over_data_size_limit(sizes, value_expr.position())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,10 @@ pub type GlobalConstants =
|
|||||||
// # Implementation Notes
|
// # Implementation Notes
|
||||||
//
|
//
|
||||||
// This implementation for imported [modules][crate::Module] splits the module names from the shared
|
// This implementation for imported [modules][crate::Module] splits the module names from the shared
|
||||||
// modules to improve data locality. Most usage will be looking up a particular key from the list
|
// modules to improve data locality.
|
||||||
// and then getting the module that corresponds to that key.
|
//
|
||||||
|
// Most usage will be looking up a particular key from the list and then getting the module that
|
||||||
|
// corresponds to that key.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GlobalRuntimeState<'a> {
|
pub struct GlobalRuntimeState<'a> {
|
||||||
/// Stack of module names.
|
/// Stack of module names.
|
||||||
@ -103,7 +105,8 @@ impl GlobalRuntimeState<'_> {
|
|||||||
pub fn get_shared_import(&self, index: usize) -> Option<crate::Shared<crate::Module>> {
|
pub fn get_shared_import(&self, index: usize) -> Option<crate::Shared<crate::Module>> {
|
||||||
self.modules.get(index).cloned()
|
self.modules.get(index).cloned()
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the globally-imported [crate::Module][crate::Module] at a particular index.
|
/// Get a mutable reference to the globally-imported [crate::Module][crate::Module] at a
|
||||||
|
/// particular index.
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -190,7 +193,8 @@ impl GlobalRuntimeState<'_> {
|
|||||||
) -> impl Iterator<Item = (&Identifier, &crate::Shared<crate::Module>)> {
|
) -> impl Iterator<Item = (&Identifier, &crate::Shared<crate::Module>)> {
|
||||||
self.keys.iter().zip(self.modules.iter())
|
self.keys.iter().zip(self.modules.iter())
|
||||||
}
|
}
|
||||||
/// Does the specified function hash key exist in the stack of globally-imported [modules][crate::Module]?
|
/// Does the specified function hash key exist in the stack of globally-imported
|
||||||
|
/// [modules][crate::Module]?
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -200,7 +204,8 @@ impl GlobalRuntimeState<'_> {
|
|||||||
pub fn contains_qualified_fn(&self, hash: u64) -> bool {
|
pub fn contains_qualified_fn(&self, hash: u64) -> bool {
|
||||||
self.modules.iter().any(|m| m.contains_qualified_fn(hash))
|
self.modules.iter().any(|m| m.contains_qualified_fn(hash))
|
||||||
}
|
}
|
||||||
/// Get the specified function via its hash key from the stack of globally-imported [modules][crate::Module].
|
/// Get the specified function via its hash key from the stack of globally-imported
|
||||||
|
/// [modules][crate::Module].
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
104
src/eval/stmt.rs
104
src/eval/stmt.rs
@ -257,16 +257,11 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, pos)?;
|
self.inc_operations(&mut global.num_operations, pos)?;
|
||||||
|
|
||||||
|
let root = (var_name, pos);
|
||||||
|
let lhs_ptr = &mut lhs_ptr;
|
||||||
|
|
||||||
self.eval_op_assignment(
|
self.eval_op_assignment(
|
||||||
global,
|
global, state, lib, *op_info, *op_pos, lhs_ptr, root, rhs_val, level,
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
*op_info,
|
|
||||||
*op_pos,
|
|
||||||
&mut lhs_ptr,
|
|
||||||
(var_name, pos),
|
|
||||||
rhs_val,
|
|
||||||
level,
|
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(rhs.start_position()))
|
.map_err(|err| err.fill_position(rhs.start_position()))
|
||||||
.map(|_| Dynamic::UNIT)
|
.map(|_| Dynamic::UNIT)
|
||||||
@ -338,7 +333,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If statement
|
// If statement
|
||||||
Stmt::If(expr, x, ..) => {
|
Stmt::If(x, ..) => {
|
||||||
|
let (expr, if_block, else_block) = x.as_ref();
|
||||||
|
|
||||||
let guard_val = self
|
let guard_val = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
@ -349,18 +346,18 @@ impl Engine {
|
|||||||
|
|
||||||
match guard_val {
|
match guard_val {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
if !x.0.is_empty() {
|
if !if_block.is_empty() {
|
||||||
self.eval_stmt_block(
|
self.eval_stmt_block(
|
||||||
scope, global, state, lib, this_ptr, &x.0, true, level,
|
scope, global, state, lib, this_ptr, if_block, true, level,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
if !x.1.is_empty() {
|
if !else_block.is_empty() {
|
||||||
self.eval_stmt_block(
|
self.eval_stmt_block(
|
||||||
scope, global, state, lib, this_ptr, &x.1, true, level,
|
scope, global, state, lib, this_ptr, else_block, true, level,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
@ -371,15 +368,17 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Switch statement
|
// Switch statement
|
||||||
Stmt::Switch(match_expr, x, ..) => {
|
Stmt::Switch(x, ..) => {
|
||||||
let SwitchCases {
|
let (
|
||||||
cases,
|
expr,
|
||||||
def_case,
|
SwitchCases {
|
||||||
ranges,
|
cases,
|
||||||
} = x.as_ref();
|
def_case,
|
||||||
|
ranges,
|
||||||
|
},
|
||||||
|
) = x.as_ref();
|
||||||
|
|
||||||
let value_result =
|
let value_result = self.eval_expr(scope, global, state, lib, this_ptr, expr, level);
|
||||||
self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level);
|
|
||||||
|
|
||||||
if let Ok(value) = value_result {
|
if let Ok(value) = value_result {
|
||||||
let stmt_block_result = if value.is_hashable() {
|
let stmt_block_result = if value.is_hashable() {
|
||||||
@ -388,8 +387,8 @@ impl Engine {
|
|||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
// First check hashes
|
// First check hashes
|
||||||
if let Some(t) = cases.get(&hash) {
|
if let Some(case_block) = cases.get(&hash) {
|
||||||
let cond_result = t
|
let cond_result = case_block
|
||||||
.condition
|
.condition
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|cond| {
|
.map(|cond| {
|
||||||
@ -406,7 +405,7 @@ impl Engine {
|
|||||||
.unwrap_or(Ok(true));
|
.unwrap_or(Ok(true));
|
||||||
|
|
||||||
match cond_result {
|
match cond_result {
|
||||||
Ok(true) => Ok(Some(&t.statements)),
|
Ok(true) => Ok(Some(&case_block.statements)),
|
||||||
Ok(false) => Ok(None),
|
Ok(false) => Ok(None),
|
||||||
_ => cond_result.map(|_| None),
|
_ => cond_result.map(|_| None),
|
||||||
}
|
}
|
||||||
@ -484,7 +483,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
Stmt::While(Expr::Unit(..), body, ..) => loop {
|
Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..)) => loop {
|
||||||
|
let (.., body) = x.as_ref();
|
||||||
|
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
match self
|
match self
|
||||||
.eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level)
|
.eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level)
|
||||||
@ -503,7 +504,9 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// While loop
|
// While loop
|
||||||
Stmt::While(expr, body, ..) => loop {
|
Stmt::While(x, ..) => loop {
|
||||||
|
let (expr, body) = x.as_ref();
|
||||||
|
|
||||||
let condition = self
|
let condition = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
@ -532,7 +535,8 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Do loop
|
// Do loop
|
||||||
Stmt::Do(body, expr, options, ..) => loop {
|
Stmt::Do(x, options, ..) => loop {
|
||||||
|
let (expr, body) = x.as_ref();
|
||||||
let is_while = !options.contains(AST_OPTION_NEGATED);
|
let is_while = !options.contains(AST_OPTION_NEGATED);
|
||||||
|
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
@ -549,7 +553,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let condition = self
|
let condition = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
v.as_bool().map_err(|typ| {
|
v.as_bool().map_err(|typ| {
|
||||||
self.make_type_mismatch_err::<bool>(typ, expr.position())
|
self.make_type_mismatch_err::<bool>(typ, expr.position())
|
||||||
@ -564,8 +568,8 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// For loop
|
// For loop
|
||||||
Stmt::For(expr, x, ..) => {
|
Stmt::For(x, ..) => {
|
||||||
let (Ident { name: var_name, .. }, counter, statements) = x.as_ref();
|
let (Ident { name: var_name, .. }, counter, expr, statements) = x.as_ref();
|
||||||
|
|
||||||
let iter_result = self
|
let iter_result = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
@ -786,42 +790,43 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Throw value
|
// Throw value
|
||||||
Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK) => self
|
Stmt::Return(Some(expr), options, pos) if options.contains(AST_OPTION_BREAK) => self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
|
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
|
||||||
|
|
||||||
// Empty throw
|
// Empty throw
|
||||||
Stmt::Return(options, None, pos) if options.contains(AST_OPTION_BREAK) => {
|
Stmt::Return(None, options, pos) if options.contains(AST_OPTION_BREAK) => {
|
||||||
Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into())
|
Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return value
|
// Return value
|
||||||
Stmt::Return(.., Some(expr), pos) => self
|
Stmt::Return(Some(expr), .., pos) => self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())),
|
.and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())),
|
||||||
|
|
||||||
// Empty return
|
// Empty return
|
||||||
Stmt::Return(.., None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
Stmt::Return(None, .., pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
||||||
|
|
||||||
// Let/const statement - shadowing disallowed
|
// Let/const statement - shadowing disallowed
|
||||||
Stmt::Var(.., x, _, pos) if !self.allow_shadowing() && scope.contains(&x.name) => {
|
Stmt::Var(x, .., pos) if !self.allow_shadowing() && scope.contains(&x.0.name) => {
|
||||||
Err(ERR::ErrorVariableExists(x.name.to_string(), *pos).into())
|
Err(ERR::ErrorVariableExists(x.0.name.to_string(), *pos).into())
|
||||||
}
|
}
|
||||||
// Let/const statement
|
// Let/const statement
|
||||||
Stmt::Var(expr, x, options, pos) => {
|
Stmt::Var(x, options, pos) => {
|
||||||
let var_name = &x.name;
|
let (Ident { name: var_name, .. }, expr, index) = x.as_ref();
|
||||||
|
|
||||||
let entry_type = if options.contains(AST_OPTION_CONSTANT) {
|
let access = if options.contains(AST_OPTION_CONSTANT) {
|
||||||
AccessMode::ReadOnly
|
AccessMode::ReadOnly
|
||||||
} else {
|
} else {
|
||||||
AccessMode::ReadWrite
|
AccessMode::ReadWrite
|
||||||
};
|
};
|
||||||
let export = options.contains(AST_OPTION_EXPORTED);
|
let export = options.contains(AST_OPTION_EXPORTED);
|
||||||
|
|
||||||
|
// Check variable definition filter
|
||||||
let result = if let Some(ref filter) = self.def_var_filter {
|
let result = if let Some(ref filter) = self.def_var_filter {
|
||||||
let will_shadow = scope.contains(var_name);
|
let will_shadow = scope.contains(var_name);
|
||||||
let nesting_level = state.scope_level;
|
let nesting_level = state.scope_level;
|
||||||
let is_const = entry_type == AccessMode::ReadOnly;
|
let is_const = access == AccessMode::ReadOnly;
|
||||||
let info = VarDefInfo {
|
let info = VarDefInfo {
|
||||||
name: var_name,
|
name: var_name,
|
||||||
is_const,
|
is_const,
|
||||||
@ -854,16 +859,18 @@ impl Engine {
|
|||||||
if let Some(result) = result {
|
if let Some(result) = result {
|
||||||
result.map(|_| Dynamic::UNIT)
|
result.map(|_| Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
|
// Evaluate initial value
|
||||||
let value_result = self
|
let value_result = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||||
.map(Dynamic::flatten);
|
.map(Dynamic::flatten);
|
||||||
|
|
||||||
if let Ok(value) = value_result {
|
if let Ok(mut value) = value_result {
|
||||||
let _alias = if !rewind_scope {
|
let _alias = if !rewind_scope {
|
||||||
|
// Put global constants into global module
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if state.scope_level == 0
|
if state.scope_level == 0
|
||||||
&& entry_type == AccessMode::ReadOnly
|
&& access == AccessMode::ReadOnly
|
||||||
&& lib.iter().any(|&m| !m.is_empty())
|
&& lib.iter().any(|&m| !m.is_empty())
|
||||||
{
|
{
|
||||||
if global.constants.is_none() {
|
if global.constants.is_none() {
|
||||||
@ -886,7 +893,12 @@ impl Engine {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.push_dynamic_value(var_name.clone(), entry_type, value);
|
if let Some(index) = index {
|
||||||
|
value.set_access_mode(access);
|
||||||
|
*scope.get_mut_by_index(scope.len() - index.get()) = value;
|
||||||
|
} else {
|
||||||
|
scope.push_entry(var_name.clone(), access, value);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if let Some(alias) = _alias {
|
if let Some(alias) = _alias {
|
||||||
@ -902,7 +914,9 @@ impl Engine {
|
|||||||
|
|
||||||
// Import statement
|
// Import statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, export, _pos) => {
|
Stmt::Import(x, _pos) => {
|
||||||
|
let (expr, export) = x.as_ref();
|
||||||
|
|
||||||
// Guard against too many modules
|
// Guard against too many modules
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if global.num_modules_loaded >= self.max_modules() {
|
if global.num_modules_loaded >= self.max_modules() {
|
||||||
|
@ -9,7 +9,8 @@ use std::prelude::v1::*;
|
|||||||
|
|
||||||
/// Trait that parses arguments to a function call.
|
/// Trait that parses arguments to a function call.
|
||||||
///
|
///
|
||||||
/// Any data type can implement this trait in order to pass arguments to [`Engine::call_fn`][crate::Engine::call_fn].
|
/// Any data type can implement this trait in order to pass arguments to
|
||||||
|
/// [`Engine::call_fn`][crate::Engine::call_fn].
|
||||||
pub trait FuncArgs {
|
pub trait FuncArgs {
|
||||||
/// Parse function call arguments into a container.
|
/// Parse function call arguments into a container.
|
||||||
///
|
///
|
||||||
@ -65,8 +66,7 @@ impl<T: Variant + Clone> FuncArgs for Vec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro to implement [`FuncArgs`] for tuples of standard types (each can be
|
/// Macro to implement [`FuncArgs`] for tuples of standard types (each can be converted into a [`Dynamic`]).
|
||||||
/// converted into a [`Dynamic`]).
|
|
||||||
macro_rules! impl_args {
|
macro_rules! impl_args {
|
||||||
($($p:ident),*) => {
|
($($p:ident),*) => {
|
||||||
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
|
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
|
||||||
|
@ -340,7 +340,8 @@ impl Engine {
|
|||||||
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
|
/// Function call arguments be _consumed_ when the function requires them to be passed by value.
|
||||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
/// All function arguments not in the first position are always passed by value and thus consumed.
|
||||||
///
|
///
|
||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument -
|
||||||
|
/// all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_native_fn(
|
pub(crate) fn call_native_fn(
|
||||||
&self,
|
&self,
|
||||||
global: &mut GlobalRuntimeState,
|
global: &mut GlobalRuntimeState,
|
||||||
@ -591,10 +592,11 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// # WARNING
|
/// # WARNING
|
||||||
///
|
///
|
||||||
/// Function call arguments may be _consumed_ when the function requires them to be passed by value.
|
/// Function call arguments may be _consumed_ when the function requires them to be passed by
|
||||||
/// All function arguments not in the first position are always passed by value and thus consumed.
|
/// value. All function arguments not in the first position are always passed by value and thus consumed.
|
||||||
///
|
///
|
||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument -
|
||||||
|
/// all others are silently replaced by `()`!
|
||||||
pub(crate) fn exec_fn_call(
|
pub(crate) fn exec_fn_call(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope>,
|
scope: Option<&mut Scope>,
|
||||||
@ -1301,8 +1303,8 @@ impl Engine {
|
|||||||
// No arguments
|
// No arguments
|
||||||
} else {
|
} else {
|
||||||
// See if the first argument is a variable (not namespace-qualified).
|
// See if the first argument is a variable (not namespace-qualified).
|
||||||
// If so, convert to method-call style in order to leverage potential
|
// If so, convert to method-call style in order to leverage potential &mut first argument
|
||||||
// &mut first argument and avoid cloning the value
|
// and avoid cloning the value
|
||||||
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
@ -85,22 +85,32 @@ pub trait RegisterNativeFunction<Args, Result> {
|
|||||||
fn return_type_name() -> &'static str;
|
fn return_type_name() -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
fn is_setter(_fn_name: &str) -> bool {
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
if _fn_name.starts_with(crate::engine::FN_SET) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
if _fn_name.starts_with(crate::engine::FN_IDX_SET) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXPECT_ARGS: &str = "arguments";
|
const EXPECT_ARGS: &str = "arguments";
|
||||||
|
|
||||||
|
macro_rules! check_constant {
|
||||||
|
($ctx:ident, $args:ident) => {
|
||||||
|
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
||||||
|
{
|
||||||
|
let args_len = $args.len();
|
||||||
|
|
||||||
|
if args_len > 0 && $args[0].is_read_only() {
|
||||||
|
let deny = match $ctx.fn_name() {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
f if args_len == 2 && f.starts_with(crate::engine::FN_SET) => true,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
crate::engine::FN_IDX_SET if args_len == 3 => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if deny {
|
||||||
|
return Err(
|
||||||
|
ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! def_register {
|
macro_rules! def_register {
|
||||||
() => {
|
() => {
|
||||||
def_register!(imp from_pure :);
|
def_register!(imp from_pure :);
|
||||||
@ -124,11 +134,9 @@ macro_rules! def_register {
|
|||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
|
||||||
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
|
check_constant!(ctx, args);
|
||||||
|
|
||||||
let mut _drain = args.iter_mut();
|
let mut _drain = args.iter_mut();
|
||||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||||
|
|
||||||
@ -152,11 +160,9 @@ macro_rules! def_register {
|
|||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
|
||||||
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
|
check_constant!(ctx, args);
|
||||||
|
|
||||||
let mut _drain = args.iter_mut();
|
let mut _drain = args.iter_mut();
|
||||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||||
|
|
||||||
@ -180,11 +186,9 @@ macro_rules! def_register {
|
|||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
|
||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
|
||||||
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
|
check_constant!(ctx, args);
|
||||||
|
|
||||||
let mut _drain = args.iter_mut();
|
let mut _drain = args.iter_mut();
|
||||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||||
|
|
||||||
@ -205,11 +209,9 @@ macro_rules! def_register {
|
|||||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
|
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
|
||||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
|
||||||
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The arguments are assumed to be of the correct number and types!
|
// The arguments are assumed to be of the correct number and types!
|
||||||
|
check_constant!(ctx, args);
|
||||||
|
|
||||||
let mut _drain = args.iter_mut();
|
let mut _drain = args.iter_mut();
|
||||||
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
$($let $par = ($clone)(_drain.next().expect(EXPECT_ARGS)); )*
|
||||||
|
|
||||||
|
@ -1670,9 +1670,12 @@ impl Module {
|
|||||||
}
|
}
|
||||||
/// Create a new [`Module`] by evaluating an [`AST`][crate::AST].
|
/// Create a new [`Module`] by evaluating an [`AST`][crate::AST].
|
||||||
///
|
///
|
||||||
/// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions
|
/// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions to
|
||||||
/// to cross-call each other. Functions in the global namespace, plus all functions
|
/// cross-call each other.
|
||||||
/// defined in the [`Module`], are _merged_ into a _unified_ namespace before each call.
|
///
|
||||||
|
/// Functions in the global namespace, plus all functions defined in the [`Module`], are
|
||||||
|
/// _merged_ into a _unified_ namespace before each call.
|
||||||
|
///
|
||||||
/// Therefore, all functions will be found.
|
/// Therefore, all functions will be found.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub(crate) fn eval_ast_as_new_raw(
|
pub(crate) fn eval_ast_as_new_raw(
|
||||||
|
344
src/optimizer.rs
344
src/optimizer.rs
@ -1,7 +1,9 @@
|
|||||||
//! Module implementing the [`AST`] optimizer.
|
//! Module implementing the [`AST`] optimizer.
|
||||||
#![cfg(not(feature = "no_optimize"))]
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
|
|
||||||
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
use crate::ast::{
|
||||||
|
Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, AST_OPTION_FLAGS::*,
|
||||||
|
};
|
||||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||||
use crate::func::builtin::get_builtin_binary_op_fn;
|
use crate::func::builtin::get_builtin_binary_op_fn;
|
||||||
@ -184,12 +186,12 @@ fn has_native_fn_override(
|
|||||||
|
|
||||||
/// Optimize a block of [statements][Stmt].
|
/// Optimize a block of [statements][Stmt].
|
||||||
fn optimize_stmt_block(
|
fn optimize_stmt_block(
|
||||||
mut statements: StaticVec<Stmt>,
|
mut statements: StmtBlockContainer,
|
||||||
state: &mut OptimizerState,
|
state: &mut OptimizerState,
|
||||||
preserve_result: bool,
|
preserve_result: bool,
|
||||||
is_internal: bool,
|
is_internal: bool,
|
||||||
reduce_return: bool,
|
reduce_return: bool,
|
||||||
) -> StaticVec<Stmt> {
|
) -> StmtBlockContainer {
|
||||||
if statements.is_empty() {
|
if statements.is_empty() {
|
||||||
return statements;
|
return statements;
|
||||||
}
|
}
|
||||||
@ -252,22 +254,22 @@ fn optimize_stmt_block(
|
|||||||
// Optimize each statement in the block
|
// Optimize each statement in the block
|
||||||
for stmt in statements.iter_mut() {
|
for stmt in statements.iter_mut() {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Var(value_expr, x, options, ..) => {
|
Stmt::Var(x, options, ..) => {
|
||||||
if options.contains(AST_OPTION_CONSTANT) {
|
if options.contains(AST_OPTION_CONSTANT) {
|
||||||
// Add constant literals into the state
|
// Add constant literals into the state
|
||||||
optimize_expr(value_expr, state, false);
|
optimize_expr(&mut x.1, state, false);
|
||||||
|
|
||||||
if value_expr.is_constant() {
|
if x.1.is_constant() {
|
||||||
state.push_var(
|
state.push_var(
|
||||||
x.name.as_str(),
|
x.0.name.as_str(),
|
||||||
AccessMode::ReadOnly,
|
AccessMode::ReadOnly,
|
||||||
value_expr.get_literal_value(),
|
x.1.get_literal_value(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add variables into the state
|
// Add variables into the state
|
||||||
optimize_expr(value_expr, state, false);
|
optimize_expr(&mut x.1, state, false);
|
||||||
state.push_var(x.name.as_str(), AccessMode::ReadWrite, None);
|
state.push_var(x.0.name.as_str(), AccessMode::ReadWrite, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Optimize the statement
|
// Optimize the statement
|
||||||
@ -284,10 +286,11 @@ fn optimize_stmt_block(
|
|||||||
.find_map(|(i, stmt)| match stmt {
|
.find_map(|(i, stmt)| match stmt {
|
||||||
stmt if !is_pure(stmt) => Some(i),
|
stmt if !is_pure(stmt) => Some(i),
|
||||||
|
|
||||||
Stmt::Var(e, _, ..) | Stmt::Expr(e) if !e.is_constant() => Some(i),
|
Stmt::Var(x, ..) if x.1.is_constant() => Some(i),
|
||||||
|
Stmt::Expr(e) if !e.is_constant() => Some(i),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(e, ..) if !e.is_constant() => Some(i),
|
Stmt::Import(x, ..) if !x.0.is_constant() => Some(i),
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
@ -320,7 +323,7 @@ fn optimize_stmt_block(
|
|||||||
loop {
|
loop {
|
||||||
match statements[..] {
|
match statements[..] {
|
||||||
// { return; } -> {}
|
// { return; } -> {}
|
||||||
[Stmt::Return(options, None, ..)]
|
[Stmt::Return(None, options, ..)]
|
||||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -331,7 +334,7 @@ fn optimize_stmt_block(
|
|||||||
statements.clear();
|
statements.clear();
|
||||||
}
|
}
|
||||||
// { ...; return; } -> { ... }
|
// { ...; return; } -> { ... }
|
||||||
[.., ref last_stmt, Stmt::Return(options, None, ..)]
|
[.., ref last_stmt, Stmt::Return(None, options, ..)]
|
||||||
if reduce_return
|
if reduce_return
|
||||||
&& !options.contains(AST_OPTION_BREAK)
|
&& !options.contains(AST_OPTION_BREAK)
|
||||||
&& !last_stmt.returns_value() =>
|
&& !last_stmt.returns_value() =>
|
||||||
@ -340,7 +343,7 @@ fn optimize_stmt_block(
|
|||||||
statements.pop().unwrap();
|
statements.pop().unwrap();
|
||||||
}
|
}
|
||||||
// { ...; return val; } -> { ...; val }
|
// { ...; return val; } -> { ...; val }
|
||||||
[.., Stmt::Return(options, ref mut expr, pos)]
|
[.., Stmt::Return(ref mut expr, options, pos)]
|
||||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -377,14 +380,14 @@ fn optimize_stmt_block(
|
|||||||
statements.clear();
|
statements.clear();
|
||||||
}
|
}
|
||||||
// { ...; return; } -> { ... }
|
// { ...; return; } -> { ... }
|
||||||
[.., Stmt::Return(options, None, ..)]
|
[.., Stmt::Return(None, options, ..)]
|
||||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
statements.pop().unwrap();
|
statements.pop().unwrap();
|
||||||
}
|
}
|
||||||
// { ...; return pure_val; } -> { ... }
|
// { ...; return pure_val; } -> { ... }
|
||||||
[.., Stmt::Return(options, Some(ref expr), ..)]
|
[.., Stmt::Return(Some(ref expr), options, ..)]
|
||||||
if reduce_return
|
if reduce_return
|
||||||
&& !options.contains(AST_OPTION_BREAK)
|
&& !options.contains(AST_OPTION_BREAK)
|
||||||
&& expr.is_pure() =>
|
&& expr.is_pure() =>
|
||||||
@ -460,7 +463,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if expr {}
|
// if expr {}
|
||||||
Stmt::If(condition, x, ..) if x.0.is_empty() && x.1.is_empty() => {
|
Stmt::If(x, ..) if x.1.is_empty() && x.2.is_empty() => {
|
||||||
|
let condition = &mut x.0;
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = condition.start_position();
|
let pos = condition.start_position();
|
||||||
@ -469,56 +473,72 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
*stmt = if preserve_result {
|
*stmt = if preserve_result {
|
||||||
// -> { expr, Noop }
|
// -> { expr, Noop }
|
||||||
Stmt::Block(
|
(
|
||||||
[Stmt::Expr(expr), Stmt::Noop(pos)].into(),
|
[Stmt::Expr(expr.into()), Stmt::Noop(pos)],
|
||||||
Span::new(pos, Position::NONE),
|
pos,
|
||||||
|
Position::NONE,
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
// -> expr
|
// -> expr
|
||||||
Stmt::Expr(expr)
|
Stmt::Expr(expr.into())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// if false { if_block } -> Noop
|
// if false { if_block } -> Noop
|
||||||
Stmt::If(Expr::BoolConstant(false, pos), x, ..) if x.1.is_empty() => {
|
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) && x.2.is_empty() => {
|
||||||
state.set_dirty();
|
if let Expr::BoolConstant(false, pos) = x.0 {
|
||||||
*stmt = Stmt::Noop(*pos);
|
state.set_dirty();
|
||||||
|
*stmt = Stmt::Noop(pos);
|
||||||
|
} else {
|
||||||
|
unreachable!("`Expr::BoolConstant`");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if false { if_block } else { else_block } -> else_block
|
// if false { if_block } else { else_block } -> else_block
|
||||||
Stmt::If(Expr::BoolConstant(false, ..), x, ..) => {
|
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
*stmt =
|
||||||
|
match optimize_stmt_block(mem::take(&mut *x.2), state, preserve_result, true, false)
|
||||||
|
{
|
||||||
|
statements if statements.is_empty() => Stmt::Noop(x.2.position()),
|
||||||
|
statements => (statements, x.2.span()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if true { if_block } else { else_block } -> if_block
|
||||||
|
Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(true, ..)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt =
|
*stmt =
|
||||||
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
|
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
|
||||||
{
|
{
|
||||||
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
||||||
statements => Stmt::Block(statements.into_boxed_slice(), x.1.span()),
|
statements => (statements, x.1.span()).into(),
|
||||||
}
|
|
||||||
}
|
|
||||||
// if true { if_block } else { else_block } -> if_block
|
|
||||||
Stmt::If(Expr::BoolConstant(true, ..), x, ..) => {
|
|
||||||
state.set_dirty();
|
|
||||||
*stmt =
|
|
||||||
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
|
|
||||||
{
|
|
||||||
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
|
||||||
statements => Stmt::Block(statements.into_boxed_slice(), x.0.span()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
Stmt::If(condition, x, ..) => {
|
Stmt::If(x, ..) => {
|
||||||
|
let (condition, body, other) = x.as_mut();
|
||||||
optimize_expr(condition, state, false);
|
optimize_expr(condition, state, false);
|
||||||
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false);
|
**body =
|
||||||
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false);
|
optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false);
|
||||||
|
**other =
|
||||||
|
optimize_stmt_block(mem::take(&mut **other), state, preserve_result, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch const { ... }
|
// switch const { ... }
|
||||||
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
|
Stmt::Switch(x, pos) if x.0.is_constant() => {
|
||||||
|
let (
|
||||||
|
match_expr,
|
||||||
|
SwitchCases {
|
||||||
|
cases,
|
||||||
|
ranges,
|
||||||
|
def_case,
|
||||||
|
},
|
||||||
|
) = x.as_mut();
|
||||||
|
|
||||||
let value = match_expr.get_literal_value().unwrap();
|
let value = match_expr.get_literal_value().unwrap();
|
||||||
let hasher = &mut get_hasher();
|
let hasher = &mut get_hasher();
|
||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
let cases = &mut x.cases;
|
|
||||||
|
|
||||||
// First check hashes
|
// First check hashes
|
||||||
if let Some(block) = cases.get_mut(&hash) {
|
if let Some(block) = cases.get_mut(&hash) {
|
||||||
if let Some(mut condition) = mem::take(&mut block.condition) {
|
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||||
@ -526,18 +546,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
optimize_expr(&mut condition, state, false);
|
optimize_expr(&mut condition, state, false);
|
||||||
|
|
||||||
let def_stmt =
|
let def_stmt =
|
||||||
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
optimize_stmt_block(mem::take(def_case), state, true, true, false);
|
||||||
|
|
||||||
*stmt = Stmt::If(
|
*stmt = Stmt::If(
|
||||||
condition,
|
(
|
||||||
Box::new((
|
condition,
|
||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(
|
StmtBlock::new_with_span(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt,
|
||||||
x.def_case.span_or_else(*pos, Position::NONE),
|
def_case.span_or_else(*pos, Position::NONE),
|
||||||
)
|
),
|
||||||
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
|
||||||
match_expr.start_position(),
|
match_expr.start_position(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -549,7 +569,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span());
|
*stmt = (statements, block.statements.span()).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -557,8 +577,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then check ranges
|
// Then check ranges
|
||||||
let ranges = &mut x.ranges;
|
|
||||||
|
|
||||||
if value.is::<INT>() && !ranges.is_empty() {
|
if value.is::<INT>() && !ranges.is_empty() {
|
||||||
let value = value.as_int().expect("`INT`");
|
let value = value.as_int().expect("`INT`");
|
||||||
|
|
||||||
@ -576,23 +594,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
optimize_expr(&mut condition, state, false);
|
optimize_expr(&mut condition, state, false);
|
||||||
|
|
||||||
let def_stmt = optimize_stmt_block(
|
let def_stmt =
|
||||||
mem::take(&mut x.def_case),
|
optimize_stmt_block(mem::take(def_case), state, true, true, false);
|
||||||
state,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
*stmt = Stmt::If(
|
*stmt = Stmt::If(
|
||||||
condition,
|
(
|
||||||
Box::new((
|
condition,
|
||||||
mem::take(&mut block.statements),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(
|
StmtBlock::new_with_span(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt,
|
||||||
x.def_case.span_or_else(*pos, Position::NONE),
|
def_case.span_or_else(*pos, Position::NONE),
|
||||||
)
|
),
|
||||||
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
|
||||||
match_expr.start_position(),
|
match_expr.start_position(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -600,8 +613,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
let statements = mem::take(&mut *block.statements);
|
let statements = mem::take(&mut *block.statements);
|
||||||
let statements =
|
let statements =
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
optimize_stmt_block(statements, state, true, true, false);
|
||||||
*stmt =
|
*stmt = (statements, block.statements.span()).into();
|
||||||
Stmt::Block(statements.into_boxed_slice(), block.statements.span());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -644,17 +656,25 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
// Promote the default case
|
// Promote the default case
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let def_stmt =
|
let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false);
|
||||||
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
*stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into();
|
||||||
*stmt = Stmt::Block(
|
|
||||||
def_stmt.into_boxed_slice(),
|
|
||||||
x.def_case.span_or_else(*pos, Position::NONE),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// switch
|
// switch
|
||||||
Stmt::Switch(match_expr, x, ..) => {
|
Stmt::Switch(x, ..) => {
|
||||||
|
let (
|
||||||
|
match_expr,
|
||||||
|
SwitchCases {
|
||||||
|
cases,
|
||||||
|
ranges,
|
||||||
|
def_case,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = x.as_mut();
|
||||||
|
|
||||||
optimize_expr(match_expr, state, false);
|
optimize_expr(match_expr, state, false);
|
||||||
for block in x.cases.values_mut() {
|
|
||||||
|
// Optimize cases
|
||||||
|
for block in cases.values_mut() {
|
||||||
let statements = mem::take(&mut *block.statements);
|
let statements = mem::take(&mut *block.statements);
|
||||||
*block.statements =
|
*block.statements =
|
||||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||||
@ -669,30 +689,58 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove false cases
|
// Remove false cases
|
||||||
while let Some((&key, ..)) = x.cases.iter().find(|(.., block)| match block.condition {
|
cases.retain(|_, block| match block.condition {
|
||||||
Some(Expr::BoolConstant(false, ..)) => true,
|
Some(Expr::BoolConstant(false, ..)) => {
|
||||||
_ => false,
|
state.set_dirty();
|
||||||
}) {
|
false
|
||||||
state.set_dirty();
|
}
|
||||||
x.cases.remove(&key);
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optimize ranges
|
||||||
|
for (.., block) in ranges.iter_mut() {
|
||||||
|
let statements = mem::take(&mut *block.statements);
|
||||||
|
*block.statements =
|
||||||
|
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||||
|
|
||||||
|
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||||
|
optimize_expr(&mut condition, state, false);
|
||||||
|
match condition {
|
||||||
|
Expr::Unit(..) | Expr::BoolConstant(true, ..) => state.set_dirty(),
|
||||||
|
_ => block.condition = Some(condition),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let def_block = mem::take(&mut *x.def_case);
|
// Remove false ranges
|
||||||
*x.def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
ranges.retain(|(.., block)| match block.condition {
|
||||||
|
Some(Expr::BoolConstant(false, ..)) => {
|
||||||
|
state.set_dirty();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let def_block = mem::take(&mut ***def_case);
|
||||||
|
***def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// while false { block } -> Noop
|
// while false { block } -> Noop
|
||||||
Stmt::While(Expr::BoolConstant(false, pos), ..) => {
|
Stmt::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => match x.0 {
|
||||||
state.set_dirty();
|
Expr::BoolConstant(false, pos) => {
|
||||||
*stmt = Stmt::Noop(*pos)
|
state.set_dirty();
|
||||||
}
|
*stmt = Stmt::Noop(pos)
|
||||||
|
}
|
||||||
|
_ => unreachable!("`Expr::BoolConstant"),
|
||||||
|
},
|
||||||
// while expr { block }
|
// while expr { block }
|
||||||
Stmt::While(condition, body, ..) => {
|
Stmt::While(x, ..) => {
|
||||||
|
let (condition, body) = x.as_mut();
|
||||||
optimize_expr(condition, state, false);
|
optimize_expr(condition, state, false);
|
||||||
if let Expr::BoolConstant(true, pos) = condition {
|
if let Expr::BoolConstant(true, pos) = condition {
|
||||||
*condition = Expr::Unit(*pos);
|
*condition = Expr::Unit(*pos);
|
||||||
}
|
}
|
||||||
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
|
**body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
|
||||||
|
|
||||||
if body.len() == 1 {
|
if body.len() == 1 {
|
||||||
match body[0] {
|
match body[0] {
|
||||||
@ -701,14 +749,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
// Only a single break statement - turn into running the guard expression once
|
// Only a single break statement - turn into running the guard expression once
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
if !condition.is_unit() {
|
if !condition.is_unit() {
|
||||||
let mut statements = vec![Stmt::Expr(mem::take(condition))];
|
let mut statements = vec![Stmt::Expr(mem::take(condition).into())];
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
statements.push(Stmt::Noop(pos))
|
statements.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
*stmt = Stmt::Block(
|
*stmt = (statements, Span::new(pos, Position::NONE)).into();
|
||||||
statements.into_boxed_slice(),
|
|
||||||
Span::new(pos, Position::NONE),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
*stmt = Stmt::Noop(pos);
|
*stmt = Stmt::Noop(pos);
|
||||||
};
|
};
|
||||||
@ -717,37 +762,51 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// do { block } while false | do { block } until true -> { block }
|
// do { block } until true -> { block }
|
||||||
Stmt::Do(body, Expr::BoolConstant(x, ..), options, ..)
|
Stmt::Do(x, options, ..)
|
||||||
if *x == options.contains(AST_OPTION_NEGATED) =>
|
if matches!(x.0, Expr::BoolConstant(true, ..))
|
||||||
|
&& options.contains(AST_OPTION_NEGATED) =>
|
||||||
{
|
{
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Block(
|
*stmt = (
|
||||||
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false),
|
||||||
.into_boxed_slice(),
|
x.1.span(),
|
||||||
body.span(),
|
)
|
||||||
);
|
.into();
|
||||||
|
}
|
||||||
|
// do { block } while false -> { block }
|
||||||
|
Stmt::Do(x, options, ..)
|
||||||
|
if matches!(x.0, Expr::BoolConstant(false, ..))
|
||||||
|
&& !options.contains(AST_OPTION_NEGATED) =>
|
||||||
|
{
|
||||||
|
state.set_dirty();
|
||||||
|
*stmt = (
|
||||||
|
optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false),
|
||||||
|
x.1.span(),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
// do { block } while|until expr
|
// do { block } while|until expr
|
||||||
Stmt::Do(body, condition, ..) => {
|
Stmt::Do(x, ..) => {
|
||||||
optimize_expr(condition, state, false);
|
optimize_expr(&mut x.0, state, false);
|
||||||
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
|
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false);
|
||||||
}
|
}
|
||||||
// for id in expr { block }
|
// for id in expr { block }
|
||||||
Stmt::For(iterable, x, ..) => {
|
Stmt::For(x, ..) => {
|
||||||
optimize_expr(iterable, state, false);
|
optimize_expr(&mut x.2, state, false);
|
||||||
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false);
|
*x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false);
|
||||||
}
|
}
|
||||||
// let id = expr;
|
// let id = expr;
|
||||||
Stmt::Var(expr, _, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
|
Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
|
||||||
optimize_expr(expr, state, false)
|
optimize_expr(&mut x.1, state, false)
|
||||||
}
|
}
|
||||||
// import expr as var;
|
// import expr as var;
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(expr, ..) => optimize_expr(expr, state, false),
|
Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
|
||||||
// { block }
|
// { block }
|
||||||
Stmt::Block(statements, span) => {
|
Stmt::Block(block) => {
|
||||||
let statements = mem::take(statements).into_vec().into();
|
let span = block.span();
|
||||||
|
let statements = block.take_statements().into_vec().into();
|
||||||
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
|
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||||
|
|
||||||
match block.as_mut_slice() {
|
match block.as_mut_slice() {
|
||||||
@ -760,18 +819,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = mem::take(s);
|
*stmt = mem::take(s);
|
||||||
}
|
}
|
||||||
_ => *stmt = Stmt::Block(block.into_boxed_slice(), *span),
|
_ => *stmt = (block, span).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
||||||
Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => {
|
Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => {
|
||||||
// If try block is pure, there will never be any exceptions
|
// If try block is pure, there will never be any exceptions
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Block(
|
*stmt = (
|
||||||
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false),
|
||||||
.into_boxed_slice(),
|
|
||||||
x.try_block.span(),
|
x.try_block.span(),
|
||||||
);
|
)
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
// try { try_block } catch ( var ) { catch_block }
|
// try { try_block } catch ( var ) { catch_block }
|
||||||
Stmt::TryCatch(x, ..) => {
|
Stmt::TryCatch(x, ..) => {
|
||||||
@ -780,31 +839,33 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
*x.catch_block =
|
*x.catch_block =
|
||||||
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
|
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
|
||||||
}
|
}
|
||||||
// func(...)
|
|
||||||
Stmt::Expr(expr @ Expr::FnCall(..)) => {
|
Stmt::Expr(expr) => {
|
||||||
optimize_expr(expr, state, false);
|
optimize_expr(expr, state, false);
|
||||||
match expr {
|
|
||||||
|
match expr.as_mut() {
|
||||||
|
// func(...)
|
||||||
Expr::FnCall(x, pos) => {
|
Expr::FnCall(x, pos) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::FnCall(mem::take(x), *pos);
|
*stmt = Stmt::FnCall(mem::take(x), *pos);
|
||||||
}
|
}
|
||||||
|
// {...};
|
||||||
|
Expr::Stmt(x) => {
|
||||||
|
if x.is_empty() {
|
||||||
|
state.set_dirty();
|
||||||
|
*stmt = Stmt::Noop(x.position());
|
||||||
|
} else {
|
||||||
|
state.set_dirty();
|
||||||
|
*stmt = mem::take(&mut **x).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// expr;
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// {}
|
|
||||||
Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => {
|
|
||||||
state.set_dirty();
|
|
||||||
*stmt = Stmt::Noop(x.position());
|
|
||||||
}
|
|
||||||
// {...};
|
|
||||||
Stmt::Expr(Expr::Stmt(x)) => {
|
|
||||||
state.set_dirty();
|
|
||||||
*stmt = mem::take(&mut **x).into();
|
|
||||||
}
|
|
||||||
// expr;
|
|
||||||
Stmt::Expr(expr) => optimize_expr(expr, state, false),
|
|
||||||
// return expr;
|
// return expr;
|
||||||
Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state, false),
|
Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false),
|
||||||
|
|
||||||
// All other statements - skip
|
// All other statements - skip
|
||||||
_ => (),
|
_ => (),
|
||||||
@ -1155,12 +1216,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
///
|
///
|
||||||
/// Constants and variables from the scope are added.
|
/// Constants and variables from the scope are added.
|
||||||
fn optimize_top_level(
|
fn optimize_top_level(
|
||||||
statements: StaticVec<Stmt>,
|
statements: StmtBlockContainer,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
#[cfg(not(feature = "no_function"))] lib: &[&crate::Module],
|
#[cfg(not(feature = "no_function"))] lib: &[&crate::Module],
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> StaticVec<Stmt> {
|
) -> StmtBlockContainer {
|
||||||
let mut statements = statements;
|
let mut statements = statements;
|
||||||
|
|
||||||
// If optimization level is None then skip optimizing
|
// If optimization level is None then skip optimizing
|
||||||
@ -1186,15 +1247,14 @@ fn optimize_top_level(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statements = optimize_stmt_block(statements, &mut state, true, false, true);
|
optimize_stmt_block(statements, &mut state, true, false, true)
|
||||||
statements
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize an [`AST`].
|
/// Optimize an [`AST`].
|
||||||
pub fn optimize_into_ast(
|
pub fn optimize_into_ast(
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
statements: StaticVec<Stmt>,
|
statements: StmtBlockContainer,
|
||||||
#[cfg(not(feature = "no_function"))] functions: StaticVec<
|
#[cfg(not(feature = "no_function"))] functions: StaticVec<
|
||||||
crate::Shared<crate::ast::ScriptFnDef>,
|
crate::Shared<crate::ast::ScriptFnDef>,
|
||||||
>,
|
>,
|
||||||
|
236
src/parser.rs
236
src/parser.rs
@ -5,13 +5,14 @@ use crate::api::events::VarDefInfo;
|
|||||||
use crate::api::options::LanguageOptions;
|
use crate::api::options::LanguageOptions;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
OpAssignment, ScriptFnDef, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock,
|
||||||
|
AST_OPTION_FLAGS::*,
|
||||||
};
|
};
|
||||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||||
use crate::func::hashing::get_hasher;
|
use crate::func::hashing::get_hasher;
|
||||||
use crate::tokenizer::{
|
use crate::tokenizer::{
|
||||||
is_keyword_function, is_valid_function_name, is_valid_identifier, Span, Token, TokenStream,
|
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
||||||
TokenizerControl,
|
TokenizerControl,
|
||||||
};
|
};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
@ -52,7 +53,7 @@ pub struct ParseState<'e> {
|
|||||||
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
/// Encapsulates a local stack with variable names to simulate an actual runtime scope.
|
||||||
pub stack: Scope<'e>,
|
pub stack: Scope<'e>,
|
||||||
/// Size of the local variables stack upon entry of the current block scope.
|
/// Size of the local variables stack upon entry of the current block scope.
|
||||||
pub entry_stack_len: usize,
|
pub block_stack_len: usize,
|
||||||
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
/// Tracks a list of external variables (variables that are not explicitly declared in the scope).
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub external_vars: Vec<crate::ast::Ident>,
|
pub external_vars: Vec<crate::ast::Ident>,
|
||||||
@ -93,7 +94,7 @@ impl<'e> ParseState<'e> {
|
|||||||
allow_capture: true,
|
allow_capture: true,
|
||||||
interned_strings: StringsInterner::new(),
|
interned_strings: StringsInterner::new(),
|
||||||
stack: Scope::new(),
|
stack: Scope::new(),
|
||||||
entry_stack_len: 0,
|
block_stack_len: 0,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
imports: StaticVec::new_const(),
|
imports: StaticVec::new_const(),
|
||||||
}
|
}
|
||||||
@ -270,7 +271,7 @@ impl Expr {
|
|||||||
fn into_property(self, state: &mut ParseState) -> Self {
|
fn into_property(self, state: &mut ParseState) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Self::Variable(.., ref x) if x.1.is_some() => self,
|
Self::Variable(.., x) if x.1.is_some() => unreachable!("qualified property"),
|
||||||
Self::Variable(.., pos, x) => {
|
Self::Variable(.., pos, x) => {
|
||||||
let ident = x.2;
|
let ident = x.2;
|
||||||
let getter = state.get_identifier(crate::engine::FN_GET, &ident);
|
let getter = state.get_identifier(crate::engine::FN_GET, &ident);
|
||||||
@ -1006,7 +1007,7 @@ fn parse_switch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
|
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
|
||||||
let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new();
|
let mut ranges = StaticVec::<(INT, INT, bool, Box<ConditionalStmtBlock>)>::new();
|
||||||
let mut def_pos = Position::NONE;
|
let mut def_pos = Position::NONE;
|
||||||
let mut def_stmt = None;
|
let mut def_stmt = None;
|
||||||
|
|
||||||
@ -1118,7 +1119,10 @@ fn parse_switch(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Other range
|
// Other range
|
||||||
_ => ranges.push((range.0, range.1, range.2, (condition, stmt).into())),
|
_ => {
|
||||||
|
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||||
|
ranges.push((range.0, range.1, range.2, block.into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -1128,7 +1132,7 @@ fn parse_switch(
|
|||||||
cases.insert(hash, block.into());
|
cases.insert(hash, block.into());
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
(None, None) => Some(stmt.into()),
|
(None, None) => Some(Box::new(stmt.into())),
|
||||||
_ => unreachable!("both hash and range in switch statement case"),
|
_ => unreachable!("both hash and range in switch statement case"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1155,18 +1159,13 @@ fn parse_switch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let def_case = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into());
|
let cases = SwitchCases {
|
||||||
|
cases,
|
||||||
|
def_case: def_stmt.unwrap_or_else(|| StmtBlock::NONE.into()),
|
||||||
|
ranges,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Stmt::Switch(
|
Ok(Stmt::Switch((item, cases).into(), settings.pos))
|
||||||
item,
|
|
||||||
SwitchCases {
|
|
||||||
cases,
|
|
||||||
def_case,
|
|
||||||
ranges,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
settings.pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a primary expression.
|
/// Parse a primary expression.
|
||||||
@ -1876,7 +1875,7 @@ fn parse_op_assignment_stmt(
|
|||||||
.map(|(op, pos)| (Some(op), pos))
|
.map(|(op, pos)| (Some(op), pos))
|
||||||
.expect(NEVER_ENDS),
|
.expect(NEVER_ENDS),
|
||||||
// Not op-assignment
|
// Not op-assignment
|
||||||
_ => return Ok(Stmt::Expr(lhs)),
|
_ => return Ok(Stmt::Expr(lhs.into())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut settings = settings;
|
let mut settings = settings;
|
||||||
@ -1901,24 +1900,58 @@ fn make_dot_expr(
|
|||||||
x.rhs = make_dot_expr(state, x.rhs, term || terminate_chaining, rhs, op_pos)?;
|
x.rhs = make_dot_expr(state, x.rhs, term || terminate_chaining, rhs, op_pos)?;
|
||||||
Ok(Expr::Index(x, false, pos))
|
Ok(Expr::Index(x, false, pos))
|
||||||
}
|
}
|
||||||
|
// lhs.module::id - syntax error
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
(.., Expr::Variable(.., x)) if x.1.is_some() => {
|
||||||
|
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0.position()))
|
||||||
|
}
|
||||||
// lhs.id
|
// lhs.id
|
||||||
(lhs, var_expr @ Expr::Variable(..)) if var_expr.is_variable_access(true) => {
|
(lhs, var_expr @ Expr::Variable(..)) => {
|
||||||
let rhs = var_expr.into_property(state);
|
let rhs = var_expr.into_property(state);
|
||||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
||||||
}
|
}
|
||||||
// lhs.module::id - syntax error
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
(.., Expr::Variable(.., x)) => {
|
|
||||||
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
(.., Expr::Variable(..)) => unreachable!("qualified property name"),
|
|
||||||
// lhs.prop
|
// lhs.prop
|
||||||
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
(lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot(
|
||||||
BinaryExpr { lhs, rhs: prop }.into(),
|
BinaryExpr { lhs, rhs: prop }.into(),
|
||||||
false,
|
false,
|
||||||
op_pos,
|
op_pos,
|
||||||
)),
|
)),
|
||||||
|
// lhs.nnn::func(...) - syntax error
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
(.., Expr::FnCall(func, ..)) if func.is_qualified() => {
|
||||||
|
Err(PERR::PropertyExpected.into_err(func.namespace.expect("`Some`").position()))
|
||||||
|
}
|
||||||
|
// lhs.Fn() or lhs.eval()
|
||||||
|
(.., Expr::FnCall(func, func_pos))
|
||||||
|
if func.args.is_empty()
|
||||||
|
&& [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL]
|
||||||
|
.contains(&func.name.as_ref()) =>
|
||||||
|
{
|
||||||
|
let err_msg = format!(
|
||||||
|
"'{}' should not be called in method style. Try {}(...);",
|
||||||
|
func.name, func.name
|
||||||
|
);
|
||||||
|
Err(LexError::ImproperSymbol(func.name.to_string(), err_msg).into_err(func_pos))
|
||||||
|
}
|
||||||
|
// lhs.func!(...)
|
||||||
|
(.., Expr::FnCall(func, func_pos)) if func.capture_parent_scope => {
|
||||||
|
Err(PERR::MalformedCapture(
|
||||||
|
"method-call style does not support running within the caller's scope".into(),
|
||||||
|
)
|
||||||
|
.into_err(func_pos))
|
||||||
|
}
|
||||||
|
// lhs.func(...)
|
||||||
|
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
||||||
|
// Recalculate hash
|
||||||
|
func.hashes = FnCallHashes::from_all(
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
calc_fn_hash(&func.name, func.args.len()),
|
||||||
|
calc_fn_hash(&func.name, func.args.len() + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let rhs = Expr::FnCall(func, func_pos);
|
||||||
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
||||||
|
}
|
||||||
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
// lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs]
|
||||||
(lhs, rhs @ Expr::Dot(..)) | (lhs, rhs @ Expr::Index(..)) => {
|
(lhs, rhs @ Expr::Dot(..)) | (lhs, rhs @ Expr::Index(..)) => {
|
||||||
let (x, term, pos, is_dot) = match rhs {
|
let (x, term, pos, is_dot) = match rhs {
|
||||||
@ -1928,6 +1961,17 @@ fn make_dot_expr(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match x.lhs {
|
match x.lhs {
|
||||||
|
// lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Expr::Variable(.., x) if x.1.is_some() => {
|
||||||
|
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0.position()))
|
||||||
|
}
|
||||||
|
// lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
Expr::FnCall(func, ..) if func.is_qualified() => {
|
||||||
|
Err(PERR::PropertyExpected.into_err(func.namespace.expect("`Some`").position()))
|
||||||
|
}
|
||||||
|
// lhs.id.dot_rhs or lhs.id[idx_rhs]
|
||||||
Expr::Variable(..) | Expr::Property(..) => {
|
Expr::Variable(..) | Expr::Property(..) => {
|
||||||
let new_lhs = BinaryExpr {
|
let new_lhs = BinaryExpr {
|
||||||
lhs: x.lhs.into_property(state),
|
lhs: x.lhs.into_property(state),
|
||||||
@ -1942,6 +1986,7 @@ fn make_dot_expr(
|
|||||||
};
|
};
|
||||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
||||||
}
|
}
|
||||||
|
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
||||||
Expr::FnCall(mut func, func_pos) => {
|
Expr::FnCall(mut func, func_pos) => {
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
func.hashes = FnCallHashes::from_all(
|
func.hashes = FnCallHashes::from_all(
|
||||||
@ -1966,42 +2011,6 @@ fn make_dot_expr(
|
|||||||
expr => unreachable!("invalid dot expression: {:?}", expr),
|
expr => unreachable!("invalid dot expression: {:?}", expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// lhs.nnn::func(...)
|
|
||||||
(.., Expr::FnCall(x, ..)) if x.is_qualified() => {
|
|
||||||
unreachable!("method call should not be namespace-qualified")
|
|
||||||
}
|
|
||||||
// lhs.Fn() or lhs.eval()
|
|
||||||
(.., Expr::FnCall(x, pos))
|
|
||||||
if x.args.is_empty()
|
|
||||||
&& [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL]
|
|
||||||
.contains(&x.name.as_ref()) =>
|
|
||||||
{
|
|
||||||
Err(LexError::ImproperSymbol(
|
|
||||||
x.name.to_string(),
|
|
||||||
format!(
|
|
||||||
"'{}' should not be called in method style. Try {}(...);",
|
|
||||||
x.name, x.name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_err(pos))
|
|
||||||
}
|
|
||||||
// lhs.func!(...)
|
|
||||||
(.., Expr::FnCall(x, pos)) if x.capture_parent_scope => Err(PERR::MalformedCapture(
|
|
||||||
"method-call style does not support running within the caller's scope".into(),
|
|
||||||
)
|
|
||||||
.into_err(pos)),
|
|
||||||
// lhs.func(...)
|
|
||||||
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
|
||||||
// Recalculate hash
|
|
||||||
func.hashes = FnCallHashes::from_all(
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
calc_fn_hash(&func.name, func.args.len()),
|
|
||||||
calc_fn_hash(&func.name, func.args.len() + 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
let rhs = Expr::FnCall(func, func_pos);
|
|
||||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
|
||||||
}
|
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
(.., rhs) => Err(PERR::PropertyExpected.into_err(rhs.start_position())),
|
(.., rhs) => Err(PERR::PropertyExpected.into_err(rhs.start_position())),
|
||||||
}
|
}
|
||||||
@ -2418,8 +2427,7 @@ fn parse_if(
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Stmt::If(
|
Ok(Stmt::If(
|
||||||
guard,
|
(guard, if_body.into(), else_body.into()).into(),
|
||||||
(if_body.into(), else_body.into()).into(),
|
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -2452,7 +2460,7 @@ fn parse_while_loop(
|
|||||||
|
|
||||||
let body = parse_block(input, state, lib, settings.level_up())?;
|
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
Ok(Stmt::While(guard, Box::new(body.into()), settings.pos))
|
Ok(Stmt::While((guard, body.into()).into(), settings.pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a do loop.
|
/// Parse a do loop.
|
||||||
@ -2490,12 +2498,7 @@ fn parse_do(
|
|||||||
let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
|
let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
|
||||||
ensure_not_assignment(input)?;
|
ensure_not_assignment(input)?;
|
||||||
|
|
||||||
Ok(Stmt::Do(
|
Ok(Stmt::Do((guard, body.into()).into(), negated, settings.pos))
|
||||||
Box::new(body.into()),
|
|
||||||
guard,
|
|
||||||
negated,
|
|
||||||
settings.pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a for loop.
|
/// Parse a for loop.
|
||||||
@ -2583,8 +2586,7 @@ fn parse_for(
|
|||||||
state.stack.rewind(prev_stack_len);
|
state.stack.rewind(prev_stack_len);
|
||||||
|
|
||||||
Ok(Stmt::For(
|
Ok(Stmt::For(
|
||||||
expr,
|
Box::new((loop_var, counter_var, expr, body.into())),
|
||||||
Box::new((loop_var, counter_var, body.into())),
|
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -2594,7 +2596,7 @@ fn parse_let(
|
|||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
lib: &mut FnLib,
|
lib: &mut FnLib,
|
||||||
var_type: AccessMode,
|
access: AccessMode,
|
||||||
is_export: bool,
|
is_export: bool,
|
||||||
settings: ParseSettings,
|
settings: ParseSettings,
|
||||||
) -> ParseResult<Stmt> {
|
) -> ParseResult<Stmt> {
|
||||||
@ -2617,7 +2619,7 @@ fn parse_let(
|
|||||||
if let Some(ref filter) = state.engine.def_var_filter {
|
if let Some(ref filter) = state.engine.def_var_filter {
|
||||||
let will_shadow = state.stack.iter().any(|(v, ..)| v == name.as_ref());
|
let will_shadow = state.stack.iter().any(|(v, ..)| v == name.as_ref());
|
||||||
let level = settings.level;
|
let level = settings.level;
|
||||||
let is_const = var_type == AccessMode::ReadOnly;
|
let is_const = access == AccessMode::ReadOnly;
|
||||||
let info = VarDefInfo {
|
let info = VarDefInfo {
|
||||||
name: &name,
|
name: &name,
|
||||||
is_const,
|
is_const,
|
||||||
@ -2645,10 +2647,6 @@ fn parse_let(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = state.get_identifier("", name);
|
let name = state.get_identifier("", name);
|
||||||
let var_def = Ident {
|
|
||||||
name: name.clone(),
|
|
||||||
pos,
|
|
||||||
};
|
|
||||||
|
|
||||||
// let name = ...
|
// let name = ...
|
||||||
let expr = if match_token(input, Token::Equals).0 {
|
let expr = if match_token(input, Token::Equals).0 {
|
||||||
@ -2664,23 +2662,31 @@ fn parse_let(
|
|||||||
AST_OPTION_NONE
|
AST_OPTION_NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
match var_type {
|
let existing = state.stack.get_index(&name).and_then(|(n, ..)| {
|
||||||
|
if n < state.block_stack_len {
|
||||||
|
// Defined in parent block
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(n)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let idx = if let Some(n) = existing {
|
||||||
|
state.stack.get_mut_by_index(n).set_access_mode(access);
|
||||||
|
Some(NonZeroUsize::new(state.stack.len() - n).unwrap())
|
||||||
|
} else {
|
||||||
|
state.stack.push_entry(name.as_str(), access, Dynamic::UNIT);
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let var_def = (Ident { name, pos }, expr, idx).into();
|
||||||
|
|
||||||
|
Ok(match access {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
AccessMode::ReadWrite => {
|
AccessMode::ReadWrite => Stmt::Var(var_def, export, settings.pos),
|
||||||
state.stack.push(name, ());
|
|
||||||
Ok(Stmt::Var(expr, var_def.into(), export, settings.pos))
|
|
||||||
}
|
|
||||||
// const name = { expr:constant }
|
// const name = { expr:constant }
|
||||||
AccessMode::ReadOnly => {
|
AccessMode::ReadOnly => Stmt::Var(var_def, AST_OPTION_CONSTANT + export, settings.pos),
|
||||||
state.stack.push_constant(name, ());
|
})
|
||||||
Ok(Stmt::Var(
|
|
||||||
expr,
|
|
||||||
var_def.into(),
|
|
||||||
AST_OPTION_CONSTANT + export,
|
|
||||||
settings.pos,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an import statement.
|
/// Parse an import statement.
|
||||||
@ -2703,7 +2709,7 @@ fn parse_import(
|
|||||||
|
|
||||||
// import expr as ...
|
// import expr as ...
|
||||||
if !match_token(input, Token::As).0 {
|
if !match_token(input, Token::As).0 {
|
||||||
return Ok(Stmt::Import(expr, None, settings.pos));
|
return Ok(Stmt::Import((expr, None).into(), settings.pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
// import expr as name ...
|
// import expr as name ...
|
||||||
@ -2712,8 +2718,7 @@ fn parse_import(
|
|||||||
state.imports.push(name.clone());
|
state.imports.push(name.clone());
|
||||||
|
|
||||||
Ok(Stmt::Import(
|
Ok(Stmt::Import(
|
||||||
expr,
|
(expr, Some(Ident { name, pos })).into(),
|
||||||
Some(Ident { name, pos }.into()),
|
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -2797,8 +2802,8 @@ fn parse_block(
|
|||||||
|
|
||||||
let mut statements = Vec::with_capacity(8);
|
let mut statements = Vec::with_capacity(8);
|
||||||
|
|
||||||
let prev_entry_stack_len = state.entry_stack_len;
|
let prev_entry_stack_len = state.block_stack_len;
|
||||||
state.entry_stack_len = state.stack.len();
|
state.block_stack_len = state.stack.len();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let orig_imports_len = state.imports.len();
|
let orig_imports_len = state.imports.len();
|
||||||
@ -2858,16 +2863,13 @@ fn parse_block(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
state.stack.rewind(state.entry_stack_len);
|
state.stack.rewind(state.block_stack_len);
|
||||||
state.entry_stack_len = prev_entry_stack_len;
|
state.block_stack_len = prev_entry_stack_len;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
state.imports.truncate(orig_imports_len);
|
state.imports.truncate(orig_imports_len);
|
||||||
|
|
||||||
Ok(Stmt::Block(
|
Ok((statements, settings.pos, end_pos).into())
|
||||||
statements.into_boxed_slice(),
|
|
||||||
Span::new(settings.pos, end_pos),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression as a statement.
|
/// Parse an expression as a statement.
|
||||||
@ -3070,17 +3072,17 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
// `return`/`throw` at <EOF>
|
// `return`/`throw` at <EOF>
|
||||||
(Token::EOF, ..) => Ok(Stmt::Return(return_type, None, token_pos)),
|
(Token::EOF, ..) => Ok(Stmt::Return(None, return_type, token_pos)),
|
||||||
// `return`/`throw` at end of block
|
// `return`/`throw` at end of block
|
||||||
(Token::RightBrace, ..) if !settings.is_global => {
|
(Token::RightBrace, ..) if !settings.is_global => {
|
||||||
Ok(Stmt::Return(return_type, None, token_pos))
|
Ok(Stmt::Return(None, return_type, token_pos))
|
||||||
}
|
}
|
||||||
// `return;` or `throw;`
|
// `return;` or `throw;`
|
||||||
(Token::SemiColon, ..) => Ok(Stmt::Return(return_type, None, token_pos)),
|
(Token::SemiColon, ..) => Ok(Stmt::Return(None, return_type, token_pos)),
|
||||||
// `return` or `throw` with expression
|
// `return` or `throw` with expression
|
||||||
_ => {
|
_ => {
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
Ok(Stmt::Return(return_type, Some(expr), token_pos))
|
Ok(Stmt::Return(Some(expr.into()), return_type, token_pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3326,9 +3328,9 @@ fn make_curry_from_externals(
|
|||||||
statements.extend(
|
statements.extend(
|
||||||
externals
|
externals
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|crate::ast::Ident { name, pos }| Stmt::Share(name, pos)),
|
.map(|crate::ast::Ident { name, pos }| Stmt::Share(name.into(), pos)),
|
||||||
);
|
);
|
||||||
statements.push(Stmt::Expr(expr));
|
statements.push(Stmt::Expr(expr.into()));
|
||||||
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
|
Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3481,8 +3483,8 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut statements = StaticVec::new_const();
|
let mut statements = StmtBlockContainer::new_const();
|
||||||
statements.push(Stmt::Expr(expr));
|
statements.push(Stmt::Expr(expr.into()));
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
return Ok(crate::optimizer::optimize_into_ast(
|
return Ok(crate::optimizer::optimize_into_ast(
|
||||||
@ -3507,8 +3509,8 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
) -> ParseResult<(StaticVec<Stmt>, StaticVec<Shared<ScriptFnDef>>)> {
|
) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> {
|
||||||
let mut statements = StaticVec::new_const();
|
let mut statements = StmtBlockContainer::new_const();
|
||||||
let mut functions = BTreeMap::new();
|
let mut functions = BTreeMap::new();
|
||||||
|
|
||||||
while !input.peek().expect(NEVER_ENDS).0.is_eof() {
|
while !input.peek().expect(NEVER_ENDS).0.is_eof() {
|
||||||
|
@ -21,8 +21,8 @@ fn check_struct_sizes() {
|
|||||||
);
|
);
|
||||||
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
|
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
|
||||||
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
|
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
|
||||||
assert_eq!(size_of::<ast::Stmt>(), if PACKED { 24 } else { 32 });
|
assert_eq!(size_of::<ast::Stmt>(), if PACKED { 12 } else { 16 });
|
||||||
assert_eq!(size_of::<Option<ast::Stmt>>(), if PACKED { 24 } else { 32 });
|
assert_eq!(size_of::<Option<ast::Stmt>>(), if PACKED { 12 } else { 16 });
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@ impl Position {
|
|||||||
/// Panics if `line` is zero.
|
/// Panics if `line` is zero.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(line: u16, position: u16) -> Self {
|
pub const fn new(line: u16, position: u16) -> Self {
|
||||||
assert!(line != 0, "line cannot be zero");
|
assert!(line != 0, "line cannot be zero");
|
||||||
|
|
||||||
let _pos = position;
|
let _pos = position;
|
||||||
@ -106,26 +106,6 @@ impl Position {
|
|||||||
pos: _pos,
|
pos: _pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Create a new [`Position`].
|
|
||||||
///
|
|
||||||
/// If `line` is zero, then [`None`] is returned.
|
|
||||||
///
|
|
||||||
/// If `position` is zero, then it is at the beginning of a line.
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub const fn new_const(line: u16, position: u16) -> Option<Self> {
|
|
||||||
if line == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let _pos = position;
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
line,
|
|
||||||
#[cfg(not(feature = "no_position"))]
|
|
||||||
pos: _pos,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Get the line number (1-based), or [`None`] if there is no position.
|
/// Get the line number (1-based), or [`None`] if there is no position.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -339,8 +339,8 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Does this [`Dynamic`] hold a variant data type
|
/// Does this [`Dynamic`] hold a variant data type instead of one of the supported system
|
||||||
/// instead of one of the supported system primitive types?
|
/// primitive types?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn is_variant(&self) -> bool {
|
pub const fn is_variant(&self) -> bool {
|
||||||
@ -360,8 +360,7 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
/// Is the value held by this [`Dynamic`] a particular type?
|
/// Is the value held by this [`Dynamic`] a particular type?
|
||||||
///
|
///
|
||||||
/// If the [`Dynamic`] is a shared variant checking is performed on
|
/// If the [`Dynamic`] is a shared variant checking is performed on top of its internal value.
|
||||||
/// top of its internal value.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is<T: Any + Clone>(&self) -> bool {
|
pub fn is<T: Any + Clone>(&self) -> bool {
|
||||||
@ -1072,11 +1071,14 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
/// Is this [`Dynamic`] read-only?
|
/// Is this [`Dynamic`] read-only?
|
||||||
///
|
///
|
||||||
/// Constant [`Dynamic`] values are read-only. If a [`&mut Dynamic`][Dynamic] to such a constant
|
/// Constant [`Dynamic`] values are read-only.
|
||||||
/// is passed to a Rust function, the function can use this information to return an error of
|
///
|
||||||
/// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant]
|
/// If a [`&mut Dynamic`][Dynamic] to such a constant is passed to a Rust function, the function
|
||||||
/// if its value is going to be modified. This safe-guards constant values from being modified
|
/// can use this information to return an error of
|
||||||
/// from within Rust functions.
|
/// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant] if its value
|
||||||
|
/// is going to be modified.
|
||||||
|
///
|
||||||
|
/// This safe-guards constant values from being modified from within Rust functions.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_read_only(&self) -> bool {
|
pub fn is_read_only(&self) -> bool {
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -1138,15 +1140,16 @@ impl Dynamic {
|
|||||||
///
|
///
|
||||||
/// # Notes
|
/// # Notes
|
||||||
///
|
///
|
||||||
/// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as an [`Array`][crate::Array].
|
/// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as
|
||||||
/// A [`Vec<T>`][Vec] does not get automatically converted to an [`Array`][crate::Array], but
|
/// an [`Array`][crate::Array]. A [`Vec<T>`][Vec] does not get automatically converted to an
|
||||||
/// will be a custom type instead (stored as a trait object). Use `Into<Dynamic>` to convert a
|
/// [`Array`][crate::Array], but will be a custom type instead (stored as a trait object). Use
|
||||||
/// [`Vec<T>`][Vec] into a [`Dynamic`] as an [`Array`][crate::Array] value.
|
/// `Into<Dynamic>` to convert a [`Vec<T>`][Vec] into a [`Dynamic`] as an
|
||||||
|
/// [`Array`][crate::Array] value.
|
||||||
///
|
///
|
||||||
/// Similarly, passing in a [`HashMap<String, T>`][std::collections::HashMap] or
|
/// Similarly, passing in a [`HashMap<String, T>`][std::collections::HashMap] or
|
||||||
/// [`BTreeMap<String, T>`][std::collections::BTreeMap] will not get a [`Map`][crate::Map]
|
/// [`BTreeMap<String, T>`][std::collections::BTreeMap] will not get a [`Map`][crate::Map] but a
|
||||||
/// but a custom type. Again, use `Into<Dynamic>` to get a [`Dynamic`] with a
|
/// custom type. Again, use `Into<Dynamic>` to get a [`Dynamic`] with a [`Map`][crate::Map]
|
||||||
/// [`Map`][crate::Map] value.
|
/// value.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -37,10 +37,11 @@ const SCOPE_ENTRIES_INLINED: usize = 8;
|
|||||||
///
|
///
|
||||||
/// my_scope.push("z", 40_i64);
|
/// my_scope.push("z", 40_i64);
|
||||||
///
|
///
|
||||||
/// engine.eval_with_scope::<()>(&mut my_scope, "let x = z + 1; z = 0;")?;
|
/// engine.run_with_scope(&mut my_scope, "let x = z + 1; z = 0;")?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 42);
|
/// let result: i64 = engine.eval_with_scope(&mut my_scope, "x + 1")?;
|
||||||
///
|
///
|
||||||
|
/// assert_eq!(result, 42);
|
||||||
/// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 41);
|
/// assert_eq!(my_scope.get_value::<i64>("x").expect("x should exist"), 41);
|
||||||
/// assert_eq!(my_scope.get_value::<i64>("z").expect("z should exist"), 0);
|
/// assert_eq!(my_scope.get_value::<i64>("z").expect("z should exist"), 0);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -52,11 +53,11 @@ const SCOPE_ENTRIES_INLINED: usize = 8;
|
|||||||
//
|
//
|
||||||
// # Implementation Notes
|
// # Implementation Notes
|
||||||
//
|
//
|
||||||
// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name,
|
// [`Scope`] is implemented as two arrays of exactly the same length. Variables data (name, type,
|
||||||
// type, etc.) is manually split into two equal-length arrays. That's because variable names take
|
// etc.) is manually split into two equal-length arrays. That's because variable names take up the
|
||||||
// up the most space, with [`Identifier`] being four words long, but in the vast majority of
|
// most space, with [`Identifier`] being three words long, but in the vast majority of cases the
|
||||||
// cases the name is NOT used to look up a variable. Variable lookup is usually via direct
|
// name is NOT used to look up a variable. Variable lookup is usually via direct indexing,
|
||||||
// indexing, by-passing the name altogether.
|
// by-passing the name altogether.
|
||||||
//
|
//
|
||||||
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables
|
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables
|
||||||
// are accessed.
|
// are accessed.
|
||||||
@ -185,7 +186,7 @@ impl Scope<'_> {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn push(&mut self, name: impl Into<Identifier>, value: impl Variant + Clone) -> &mut Self {
|
pub fn push(&mut self, name: impl Into<Identifier>, value: impl Variant + Clone) -> &mut Self {
|
||||||
self.push_dynamic_value(name, AccessMode::ReadWrite, Dynamic::from(value))
|
self.push_entry(name, AccessMode::ReadWrite, Dynamic::from(value))
|
||||||
}
|
}
|
||||||
/// Add (push) a new [`Dynamic`] entry to the [`Scope`].
|
/// Add (push) a new [`Dynamic`] entry to the [`Scope`].
|
||||||
///
|
///
|
||||||
@ -201,7 +202,7 @@ impl Scope<'_> {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn push_dynamic(&mut self, name: impl Into<Identifier>, value: Dynamic) -> &mut Self {
|
pub fn push_dynamic(&mut self, name: impl Into<Identifier>, value: Dynamic) -> &mut Self {
|
||||||
self.push_dynamic_value(name, value.access_mode(), value)
|
self.push_entry(name, value.access_mode(), value)
|
||||||
}
|
}
|
||||||
/// Add (push) a new constant to the [`Scope`].
|
/// Add (push) a new constant to the [`Scope`].
|
||||||
///
|
///
|
||||||
@ -224,7 +225,7 @@ impl Scope<'_> {
|
|||||||
name: impl Into<Identifier>,
|
name: impl Into<Identifier>,
|
||||||
value: impl Variant + Clone,
|
value: impl Variant + Clone,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.push_dynamic_value(name, AccessMode::ReadOnly, Dynamic::from(value))
|
self.push_entry(name, AccessMode::ReadOnly, Dynamic::from(value))
|
||||||
}
|
}
|
||||||
/// Add (push) a new constant with a [`Dynamic`] value to the Scope.
|
/// Add (push) a new constant with a [`Dynamic`] value to the Scope.
|
||||||
///
|
///
|
||||||
@ -247,11 +248,11 @@ impl Scope<'_> {
|
|||||||
name: impl Into<Identifier>,
|
name: impl Into<Identifier>,
|
||||||
value: Dynamic,
|
value: Dynamic,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.push_dynamic_value(name, AccessMode::ReadOnly, value)
|
self.push_entry(name, AccessMode::ReadOnly, value)
|
||||||
}
|
}
|
||||||
/// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`].
|
/// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn push_dynamic_value(
|
pub(crate) fn push_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Into<Identifier>,
|
name: impl Into<Identifier>,
|
||||||
access: AccessMode,
|
access: AccessMode,
|
||||||
@ -374,9 +375,11 @@ impl Scope<'_> {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_constant(&self, name: &str) -> Option<bool> {
|
pub fn is_constant(&self, name: &str) -> Option<bool> {
|
||||||
self.get_index(name).and_then(|(.., access)| match access {
|
self.get_index(name).and_then(|(.., access)| {
|
||||||
AccessMode::ReadWrite => None,
|
Some(match access {
|
||||||
AccessMode::ReadOnly => Some(true),
|
AccessMode::ReadWrite => false,
|
||||||
|
AccessMode::ReadOnly => true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Update the value of the named entry in the [`Scope`] if it already exists and is not constant.
|
/// Update the value of the named entry in the [`Scope`] if it already exists and is not constant.
|
||||||
@ -622,7 +625,7 @@ impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
||||||
for (name, value) in iter {
|
for (name, value) in iter {
|
||||||
self.push_dynamic_value(name, AccessMode::ReadWrite, value);
|
self.push_entry(name, AccessMode::ReadWrite, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,7 +643,7 @@ impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (K, bool, Dynamic)>>(&mut self, iter: T) {
|
||||||
for (name, is_constant, value) in iter {
|
for (name, is_constant, value) in iter {
|
||||||
self.push_dynamic_value(
|
self.push_entry(
|
||||||
name,
|
name,
|
||||||
if is_constant {
|
if is_constant {
|
||||||
AccessMode::ReadOnly
|
AccessMode::ReadOnly
|
||||||
|
@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", ast),
|
format!("{:?}", ast),
|
||||||
r#"AST { body: [Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)] }"#
|
r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), (Constant), 1:1), Expr(123 @ 1:51)] }"#
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
@ -25,11 +25,15 @@ fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert!(engine.compile("let x = || 42;").is_err());
|
assert!(engine.compile("let x = || 42;").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.compile("while x > y { foo(z); }")?;
|
let ast = engine.compile("let x = 0; while x < 10 { x += 1; }")?;
|
||||||
|
|
||||||
engine.set_allow_looping(false);
|
engine.set_allow_looping(false);
|
||||||
|
|
||||||
assert!(engine.compile("while x > y { foo(z); }").is_err());
|
engine.run_ast(&ast)?;
|
||||||
|
|
||||||
|
assert!(engine
|
||||||
|
.compile("let x = 0; while x < 10 { x += 1; }")
|
||||||
|
.is_err());
|
||||||
|
|
||||||
engine.compile("let x = 42; let x = 123;")?;
|
engine.compile("let x = 42; let x = 123;")?;
|
||||||
|
|
||||||
|
@ -5,17 +5,80 @@ fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
engine.run_with_scope(&mut scope, "let x = 4 + 5")?;
|
||||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
|
||||||
engine.eval_with_scope::<()>(&mut scope, "x += 1; x += 2;")?;
|
engine.run_with_scope(&mut scope, "x += 1; x += 2;")?;
|
||||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
|
||||||
|
|
||||||
scope.set_value("x", 42 as INT);
|
scope.set_value("x", 42 as INT);
|
||||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 42);
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 42);
|
||||||
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?;
|
engine.run_with_scope(&mut scope, "{ let x = 3 }")?;
|
||||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 42);
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 42);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
if engine.optimization_level() != rhai::OptimizationLevel::None {
|
||||||
|
scope.clear();
|
||||||
|
engine.run_with_scope(&mut scope, "let x = 3; let x = 42; let x = 123;")?;
|
||||||
|
assert_eq!(scope.len(), 1);
|
||||||
|
assert_eq!(scope.get_value::<INT>("x").unwrap(), 123);
|
||||||
|
|
||||||
|
scope.clear();
|
||||||
|
engine.run_with_scope(
|
||||||
|
&mut scope,
|
||||||
|
"let x = 3; let y = 0; let x = 42; let y = 999; let x = 123;",
|
||||||
|
)?;
|
||||||
|
assert_eq!(scope.len(), 2);
|
||||||
|
assert_eq!(scope.get_value::<INT>("x").unwrap(), 123);
|
||||||
|
assert_eq!(scope.get_value::<INT>("y").unwrap(), 999);
|
||||||
|
|
||||||
|
scope.clear();
|
||||||
|
engine.run_with_scope(
|
||||||
|
&mut scope,
|
||||||
|
"const x = 3; let y = 0; let x = 42; let y = 999;",
|
||||||
|
)?;
|
||||||
|
assert_eq!(scope.len(), 2);
|
||||||
|
assert_eq!(scope.get_value::<INT>("x").unwrap(), 42);
|
||||||
|
assert_eq!(scope.get_value::<INT>("y").unwrap(), 999);
|
||||||
|
assert!(!scope.is_constant("x").unwrap());
|
||||||
|
assert!(!scope.is_constant("y").unwrap());
|
||||||
|
|
||||||
|
scope.clear();
|
||||||
|
engine.run_with_scope(
|
||||||
|
&mut scope,
|
||||||
|
"const x = 3; let y = 0; let x = 42; let y = 999; const x = 123;",
|
||||||
|
)?;
|
||||||
|
assert_eq!(scope.len(), 2);
|
||||||
|
assert_eq!(scope.get_value::<INT>("x").unwrap(), 123);
|
||||||
|
assert_eq!(scope.get_value::<INT>("y").unwrap(), 999);
|
||||||
|
assert!(scope.is_constant("x").unwrap());
|
||||||
|
assert!(!scope.is_constant("y").unwrap());
|
||||||
|
|
||||||
|
scope.clear();
|
||||||
|
engine.run_with_scope(
|
||||||
|
&mut scope,
|
||||||
|
"let x = 3; let y = 0; { let x = 42; let y = 999; } let x = 123;",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(scope.len(), 2);
|
||||||
|
assert_eq!(scope.get_value::<INT>("x").unwrap(), 123);
|
||||||
|
assert_eq!(scope.get_value::<INT>("y").unwrap(), 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
"
|
||||||
|
let sum = 0;
|
||||||
|
for x in 0..10 {
|
||||||
|
let x = 42;
|
||||||
|
sum += x;
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
",
|
||||||
|
)?,
|
||||||
|
420
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +123,7 @@ fn test_scope_eval() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
// First invocation
|
// First invocation
|
||||||
engine
|
engine
|
||||||
.eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;")
|
.run_with_scope(&mut scope, " let x = 4 + 5 - y + z; y = 1;")
|
||||||
.expect("variables y and z should exist");
|
.expect("variables y and z should exist");
|
||||||
|
|
||||||
// Second invocation using the same state
|
// Second invocation using the same state
|
||||||
|
Loading…
Reference in New Issue
Block a user