Merge pull request #529 from schungx/master

General housekeeping and bump minimum Rust version.
This commit is contained in:
Stephen Chung 2022-02-24 11:26:59 +08:00 committed by GitHub
commit 53fffad726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 940 additions and 684 deletions

View File

@ -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
--------- ---------

View File

@ -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"

View File

@ -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).

View File

@ -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(())
} }

View File

@ -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
/// ///

View File

@ -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,
}); });
} }

View File

@ -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].
/// ///

View File

@ -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))
}
}
}

View File

@ -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
/// ///

View File

@ -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,

View File

@ -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> {

View File

@ -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.

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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;
} }
} }

View File

@ -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` &ndash; support for decimal numbers
* `metadata` &ndash; access functions metadata
* `serde` &ndash; export functions metadata to JSON
* `debugging` &ndash; required by `rhai-dbg`
* `rustyline` &ndash; 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
``` ```

View File

@ -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(),

View File

@ -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))

View File

@ -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);

View File

@ -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())?;
} }
} }

View File

@ -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"))]

View File

@ -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() {

View File

@ -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,)*)

View File

@ -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)?;

View File

@ -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)); )*

View File

@ -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(

View File

@ -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>,
>, >,

View File

@ -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() {

View File

@ -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")]
{ {

View File

@ -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]

View File

@ -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
/// ///

View File

@ -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

View File

@ -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 }")?;

View File

@ -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;")?;

View File

@ -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