Merge pull request #517 from schungx/master

Add debugging interface.
This commit is contained in:
Stephen Chung 2022-01-30 11:47:17 +08:00 committed by GitHub
commit bbca1c472b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 3634 additions and 1498 deletions

View File

@ -18,24 +18,25 @@ jobs:
os: [ubuntu-latest] os: [ubuntu-latest]
flags: flags:
- "" - ""
- "--features debugging"
- "--features metadata,serde,internals" - "--features metadata,serde,internals"
- "--features unchecked,serde,metadata,internals" - "--features unchecked,serde,metadata,internals,debugging"
- "--features sync,serde,metadata,internals" - "--features sync,serde,metadata,internals,debugging"
- "--features no_position,serde,metadata,internals" - "--features no_position,serde,metadata,internals,debugging"
- "--features no_optimize,serde,metadata,internals" - "--features no_optimize,serde,metadata,internals,debugging"
- "--features no_float,serde,metadata,internals" - "--features no_float,serde,metadata,internals,debugging"
- "--features f32_float,serde,metadata,internals" - "--features f32_float,serde,metadata,internals,debugging"
- "--features decimal,serde,metadata,internals" - "--features decimal,serde,metadata,internals,debugging"
- "--features no_float,decimal" - "--features no_float,decimal"
- "--tests --features only_i32,serde,metadata,internals" - "--tests --features only_i32,serde,metadata,internals,debugging"
- "--features only_i64,serde,metadata,internals" - "--features only_i64,serde,metadata,internals,debugging"
- "--features no_index,serde,metadata,internals" - "--features no_index,serde,metadata,internals,debugging"
- "--features no_object,serde,metadata,internals" - "--features no_object,serde,metadata,internals,debugging"
- "--features no_function,serde,metadata,internals" - "--features no_function,serde,metadata,internals,debugging"
- "--features no_module,serde,metadata,internals" - "--features no_module,serde,metadata,internals,debugging"
- "--features no_closure,serde,metadata,internals" - "--features no_closure,serde,metadata,internals,debugging"
- "--features unicode-xid-ident,serde,metadata,internals" - "--features unicode-xid-ident,serde,metadata,internals,debugging"
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked" - "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked,debugging"
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked" - "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
toolchain: [stable] toolchain: [stable]
experimental: [false] experimental: [false]

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ Cargo.lock
benches/results benches/results
before* before*
after* after*
.rhai-repl-history.txt

View File

@ -1,20 +1,36 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 1.4.2 Version 1.5.0
============= =============
This version adds a debugging interface, which can be used to integrate a debugger.
Bug fixes Bug fixes
--------- ---------
* In `Scope::clone_visible`, constants are now properly cloned as constants.
* Variables introduced inside `try` blocks are now properly cleaned up upon an exception. * Variables introduced inside `try` blocks are now properly cleaned up upon an exception.
* Off-by-one error in character positions after a comment line is now fixed. * Off-by-one error in character positions after a comment line is now fixed.
Script-breaking changes
-----------------------
* For consistency, the `export` statement no longer exports multiple variables.
New features
------------
* A debugging interface is added.
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
* A new package, `DebuggingPackage`, is added which contains the `stack_trace` function to get the current call stack anywhere in a script.
Enhancements Enhancements
------------ ------------
* `rhai-repl` tool has a few more commands, such as `strict` to turn on/off _Strict Variables Mode_ and `optimize` to turn on/off script optimization. * `rhai-repl` tool has a few more commands, such as `strict` to turn on/off _Strict Variables Mode_ and `optimize` to turn on/off script optimization.
* Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required. * Default features for dependencies (such as `ahash/std` and `num-traits/std`) are no longer required.
* The `no_module` feature now eliminates large sections of code via feature gates.
Version 1.4.1 Version 1.4.1

View File

@ -22,6 +22,15 @@ num-traits = { version = "0.2", default-features = false }
smartstring = { version = "0.2.8", default-features = false } smartstring = { version = "0.2.8", default-features = false }
rhai_codegen = { version = "1.2", path = "codegen", default-features = false } rhai_codegen = { version = "1.2", path = "codegen", default-features = false }
no-std-compat = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
libm = { version = "0.2", default-features = false, optional = true }
core-error = { version = "0.0", default-features = false, features = ["alloc"], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true }
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
unicode-xid = { version = "0.2", default-features = false, optional = true }
rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true }
rustyline = { version = "9", optional = true }
[dev-dependencies] [dev-dependencies]
serde_bytes = "0.11" serde_bytes = "0.11"
@ -44,6 +53,7 @@ no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata
debugging = ["internals"] # enable debugging
no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"] no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng"]
@ -51,54 +61,25 @@ no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compi
wasm-bindgen = ["instant/wasm-bindgen"] wasm-bindgen = ["instant/wasm-bindgen"]
stdweb = ["instant/stdweb"] stdweb = ["instant/stdweb"]
[[bin]]
name = "rhai-repl"
required-features = ["rustyline"]
[[bin]]
name = "rhai-run"
[[bin]]
name = "rhai-dbg"
required-features = ["debugging"]
[profile.release] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1
#opt-level = "z" # optimize for size #opt-level = "z" # optimize for size
#panic = 'abort' # remove stack backtrace for no-std #panic = 'abort' # remove stack backtrace for no-std
[dependencies.no-std-compat]
version = "0.4"
default-features = false
features = ["alloc"]
optional = true
[dependencies.libm]
version = "0.2"
default-features = false
optional = true
[dependencies.core-error]
version = "0.0"
default-features = false
features = ["alloc"]
optional = true
[dependencies.serde]
version = "1.0"
default-features = false
features = ["derive", "alloc"]
optional = true
[dependencies.serde_json]
version = "1.0"
default-features = false
features = ["alloc"]
optional = true
[dependencies.unicode-xid]
version = "0.2"
default-features = false
optional = true
[dependencies.rust_decimal]
version = "1.16"
default-features = false
features = ["maths"]
optional = true
[target.'cfg(target_family = "wasm")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
instant = { version = "0.1.10" } # WASM implementation of std::time::Instant instant = { version = "0.1.10" } # WASM implementation of std::time::Instant
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["metadata", "serde", "internals", "decimal"] # compiling for no-std features = ["metadata", "serde", "internals", "decimal", "debugging"]

View File

@ -43,7 +43,7 @@ Standard features
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations.
* Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts).
* Easy custom API development via [plugins](https://rhai.rs/book/plugins/index.html) system powered by procedural macros. * Easy custom API development via [plugins](https://rhai.rs/book/plugins) system powered by procedural macros.
* [Function overloading](https://rhai.rs/book/language/overload.html) and [operator overloading](https://rhai.rs/book/rust/operators.html). * [Function overloading](https://rhai.rs/book/language/overload.html) and [operator overloading](https://rhai.rs/book/rust/operators.html).
* Dynamic dispatch via [function pointers](https://rhai.rs/book/language/fn-ptr.html) with additional support for [currying](https://rhai.rs/book/language/fn-curry.html). * Dynamic dispatch via [function pointers](https://rhai.rs/book/language/fn-ptr.html) with additional support for [currying](https://rhai.rs/book/language/fn-curry.html).
* [Closures](https://rhai.rs/book/language/fn-closure.html) (anonymous functions) that can capture shared values. * [Closures](https://rhai.rs/book/language/fn-closure.html) (anonymous functions) that can capture shared values.
@ -51,6 +51,7 @@ Standard features
* Organize code base with dynamically-loadable [modules](https://rhai.rs/book/language/modules.html), optionally [overriding the resolution process](https://rhai.rs/book/rust/modules/resolvers.html). * Organize code base with dynamically-loadable [modules](https://rhai.rs/book/language/modules.html), optionally [overriding the resolution process](https://rhai.rs/book/rust/modules/resolvers.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
* Support for [minimal builds](https://rhai.rs/book/start/builds/minimal.html) by excluding unneeded language [features](https://rhai.rs/book/start/features.html). * Support for [minimal builds](https://rhai.rs/book/start/builds/minimal.html) by excluding unneeded language [features](https://rhai.rs/book/start/features.html).
* A [debugging](https://rhai.rs/book/engine/debugging) interface.
Protected against attacks Protected against attacks

View File

@ -17,10 +17,7 @@ struct Handler {
} }
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -34,7 +31,7 @@ fn print_scope(scope: &Scope) {
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }

View File

@ -13,10 +13,7 @@ struct Handler {
} }
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -30,7 +27,7 @@ fn print_scope(scope: &Scope) {
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }

View File

@ -16,10 +16,7 @@ struct Handler {
} }
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -33,7 +30,7 @@ fn print_scope(scope: &Scope) {
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }

View File

@ -154,7 +154,8 @@ impl Engine {
arg_values: impl AsMut<[Dynamic]>, arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult { ) -> RhaiResult {
let state = &mut EvalState::new(); let state = &mut EvalState::new();
let global = &mut GlobalRuntimeState::new(); let global = &mut GlobalRuntimeState::new(self);
let statements = ast.statements(); let statements = ast.statements();
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();

View File

@ -72,7 +72,9 @@ impl Expression<'_> {
#[must_use] #[must_use]
pub fn get_string_value(&self) -> Option<&str> { pub fn get_string_value(&self) -> Option<&str> {
match self.0 { match self.0 {
Expr::Variable(_, _, x) if x.1.is_none() => Some(x.2.as_str()), #[cfg(not(feature = "no_module"))]
Expr::Variable(_, _, x) if x.1.is_some() => None,
Expr::Variable(_, _, x) => Some(x.2.as_str()),
Expr::StringConstant(x, _) => Some(x.as_str()), Expr::StringConstant(x, _) => Some(x.as_str()),
_ => None, _ => None,
} }

View File

@ -184,7 +184,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> RhaiResultOf<T> { ) -> RhaiResultOf<T> {
let global = &mut GlobalRuntimeState::new(); let global = &mut GlobalRuntimeState::new(self);
let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?; let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?;

View File

@ -12,8 +12,7 @@ impl Engine {
/// ///
/// The callback function signature takes the following form: /// The callback function signature takes the following form:
/// ///
/// > `Fn(name: &str, index: usize, context: &EvalContext)` /// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
/// > ` -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
/// ///
/// where: /// where:
/// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the /// * `index`: an offset from the bottom of the current [`Scope`][crate::Scope] that the
@ -64,7 +63,7 @@ impl Engine {
self.resolve_var = Some(Box::new(callback)); self.resolve_var = Some(Box::new(callback));
self self
} }
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens. /// _(internals)_ Register a callback that will be invoked during parsing to remap certain tokens.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// # Callback Function Signature /// # Callback Function Signature
@ -261,4 +260,23 @@ impl Engine {
self.debug = Some(Box::new(callback)); self.debug = Some(Box::new(callback));
self self
} }
/// _(debugging)_ Register callbacks for debugging.
/// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")]
#[inline(always)]
pub fn on_debugger(
&mut self,
init: impl Fn() -> Dynamic + SendSync + 'static,
callback: impl Fn(
&mut EvalContext,
crate::ast::ASTNode,
Option<&str>,
Position,
) -> RhaiResultOf<crate::eval::DebuggerCommand>
+ SendSync
+ 'static,
) -> &mut Self {
self.debugger = Some((Box::new(init), Box::new(callback)));
self
}
} }

View File

@ -1037,9 +1037,10 @@ impl Engine {
signatures.extend(self.global_namespace().gen_fn_signatures()); signatures.extend(self.global_namespace().gen_fn_signatures());
self.global_sub_modules.iter().for_each(|(name, m)| { #[cfg(not(feature = "no_module"))]
for (name, m) in &self.global_sub_modules {
signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f)))
}); }
signatures.extend( signatures.extend(
self.global_modules self.global_modules

View File

@ -44,8 +44,8 @@ impl Engine {
/// Evaluate an [`AST`] with own scope, returning any error (if any). /// Evaluate an [`AST`] with own scope, returning any error (if any).
#[inline] #[inline]
pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> {
let global = &mut GlobalRuntimeState::new(); let state = &mut EvalState::new();
let mut state = EvalState::new(); let global = &mut GlobalRuntimeState::new(self);
global.source = ast.source_raw().clone(); global.source = ast.source_raw().clone();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -64,7 +64,7 @@ impl Engine {
} else { } else {
&lib &lib
}; };
self.eval_global_statements(scope, global, &mut state, statements, lib, 0)?; self.eval_global_statements(scope, global, state, statements, lib, 0)?;
} }
Ok(()) Ok(())
} }

View File

@ -811,7 +811,7 @@ impl AsRef<crate::Shared<crate::Module>> for AST {
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Copy, Hash)]
pub enum ASTNode<'a> { pub enum ASTNode<'a> {
/// A statement ([`Stmt`]). /// A statement ([`Stmt`]).
Stmt(&'a Stmt), Stmt(&'a Stmt),
@ -831,6 +831,19 @@ impl<'a> From<&'a Expr> for ASTNode<'a> {
} }
} }
impl PartialEq for ASTNode<'_> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Stmt(x), Self::Stmt(y)) => std::ptr::eq(*x, *y),
(Self::Expr(x), Self::Expr(y)) => std::ptr::eq(*x, *y),
_ => false,
}
}
}
impl Eq for ASTNode<'_> {}
impl ASTNode<'_> { impl ASTNode<'_> {
/// Get the [`Position`] of this [`ASTNode`]. /// Get the [`Position`] of this [`ASTNode`].
pub const fn position(&self) -> Position { pub const fn position(&self) -> Position {

View File

@ -3,7 +3,6 @@
use super::{ASTNode, Ident, Stmt, StmtBlock}; use super::{ASTNode, Ident, Stmt, StmtBlock};
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
use crate::func::hashing::ALT_ZERO_HASH; use crate::func::hashing::ALT_ZERO_HASH;
use crate::module::Namespace;
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::types::dynamic::Union; use crate::types::dynamic::Union;
use crate::{calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, StaticVec, INT}; use crate::{calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, StaticVec, INT};
@ -33,6 +32,16 @@ pub struct BinaryExpr {
pub rhs: Expr, pub rhs: Expr,
} }
impl From<(Expr, Expr)> for BinaryExpr {
#[inline(always)]
fn from(value: (Expr, Expr)) -> Self {
Self {
lhs: value.0,
rhs: value.1,
}
}
}
/// _(internals)_ A custom syntax expression. /// _(internals)_ A custom syntax expression.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
@ -159,7 +168,10 @@ impl FnCallHashes {
#[derive(Debug, Clone, Default, Hash)] #[derive(Debug, Clone, Default, Hash)]
pub struct FnCallExpr { pub struct FnCallExpr {
/// Namespace of the function, if any. /// Namespace of the function, if any.
pub namespace: Option<Namespace>, ///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
pub namespace: Option<crate::module::Namespace>,
/// Function name. /// Function name.
pub name: Identifier, pub name: Identifier,
/// Pre-calculated hashes. /// Pre-calculated hashes.
@ -183,10 +195,15 @@ pub struct FnCallExpr {
impl FnCallExpr { impl FnCallExpr {
/// Does this function call contain a qualified namespace? /// Does this function call contain a qualified namespace?
///
/// Always `false` under `no_module`.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn is_qualified(&self) -> bool { pub const fn is_qualified(&self) -> bool {
self.namespace.is_some() #[cfg(not(feature = "no_module"))]
return self.namespace.is_some();
#[cfg(feature = "no_module")]
return false;
} }
/// Convert this into an [`Expr::FnCall`]. /// Convert this into an [`Expr::FnCall`].
#[inline(always)] #[inline(always)]
@ -358,15 +375,18 @@ pub enum Expr {
Variable( Variable(
Option<NonZeroU8>, Option<NonZeroU8>,
Position, Position,
Box<(Option<NonZeroUsize>, Option<(Namespace, u64)>, Identifier)>, #[cfg(not(feature = "no_module"))]
Box<(
Option<NonZeroUsize>,
Option<(crate::module::Namespace, u64)>,
Identifier,
)>,
#[cfg(feature = "no_module")] Box<(Option<NonZeroUsize>, (), Identifier)>,
), ),
/// Property access - ((getter, hash), (setter, hash), prop) /// Property access - ((getter, hash), (setter, hash), prop)
Property( Property(
Box<( Box<((Identifier, u64), (Identifier, u64), ImmutableString)>,
(Identifier, u64), Position,
(Identifier, u64),
(ImmutableString, Position),
)>,
), ),
/// Stack slot for function calls. See [`FnCallExpr`] for more details. /// Stack slot for function calls. See [`FnCallExpr`] for more details.
/// ///
@ -378,9 +398,9 @@ pub enum Expr {
Stmt(Box<StmtBlock>), Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
/// lhs `.` rhs - bool variable is a dummy /// lhs `.` rhs - boolean variable is a dummy
Dot(Box<BinaryExpr>, bool, Position), Dot(Box<BinaryExpr>, bool, Position),
/// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops /// lhs `[` rhs `]` - boolean indicates whether the dotting/indexing chain stops
Index(Box<BinaryExpr>, bool, Position), Index(Box<BinaryExpr>, bool, Position),
/// lhs `&&` rhs /// lhs `&&` rhs
And(Box<BinaryExpr>, Position), And(Box<BinaryExpr>, Position),
@ -427,6 +447,8 @@ impl fmt::Debug for Expr {
} }
Self::Variable(i, _, x) => { Self::Variable(i, _, x) => {
f.write_str("Variable(")?; f.write_str("Variable(")?;
#[cfg(not(feature = "no_module"))]
if let Some((_, ref namespace)) = x.1 { if let Some((_, ref namespace)) = x.1 {
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())? write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?
} }
@ -436,7 +458,7 @@ impl fmt::Debug for Expr {
} }
f.write_str(")") f.write_str(")")
} }
Self::Property(x) => write!(f, "Property({})", (x.2).0), Self::Property(x, _) => write!(f, "Property({})", x.2),
Self::Stack(x, _) => write!(f, "StackSlot({})", x), Self::Stack(x, _) => write!(f, "StackSlot({})", x),
Self::Stmt(x) => { Self::Stmt(x) => {
f.write_str("ExprStmtBlock")?; f.write_str("ExprStmtBlock")?;
@ -444,6 +466,7 @@ impl fmt::Debug for Expr {
} }
Self::FnCall(x, _) => { Self::FnCall(x, _) => {
let mut ff = f.debug_struct("FnCall"); let mut ff = f.debug_struct("FnCall");
#[cfg(not(feature = "no_module"))]
x.namespace.as_ref().map(|ns| ff.field("namespace", ns)); x.namespace.as_ref().map(|ns| ff.field("namespace", ns));
ff.field("name", &x.name) ff.field("name", &x.name)
.field("hash", &x.hashes) .field("hash", &x.hashes)
@ -595,6 +618,7 @@ impl Expr {
Union::FnPtr(f, _, _) if !f.is_curried() => Self::FnCall( Union::FnPtr(f, _, _) if !f.is_curried() => Self::FnCall(
FnCallExpr { FnCallExpr {
#[cfg(not(feature = "no_module"))]
namespace: None, namespace: None,
name: KEYWORD_FN_PTR.into(), name: KEYWORD_FN_PTR.into(),
hashes: calc_fn_hash(f.fn_name(), 1).into(), hashes: calc_fn_hash(f.fn_name(), 1).into(),
@ -610,20 +634,32 @@ impl Expr {
} }
} }
/// Is the expression a simple variable access? /// Is the expression a simple variable access?
///
/// `non_qualified` is ignored under `no_module`.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool { pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool {
let _non_qualified = non_qualified;
match self { match self {
Self::Variable(_, _, x) => !non_qualified || x.1.is_none(), #[cfg(not(feature = "no_module"))]
Self::Variable(_, _, x) if _non_qualified && x.1.is_some() => false,
Self::Variable(_, _, _) => true,
_ => false, _ => false,
} }
} }
/// Return the variable name if the expression a simple variable access. /// Return the variable name if the expression a simple variable access.
///
/// `non_qualified` is ignored under `no_module`.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> { pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> {
let _non_qualified = non_qualified;
match self { match self {
Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()), #[cfg(not(feature = "no_module"))]
Self::Variable(_, _, x) if _non_qualified && x.1.is_some() => None,
Self::Variable(_, _, x) => Some(x.2.as_str()),
_ => None, _ => None,
} }
} }
@ -648,9 +684,9 @@ impl Expr {
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::Index(_, _, pos) | Self::Index(_, _, pos)
| Self::Custom(_, pos) | Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos, | Self::InterpolatedString(_, pos)
| Self::Property(_, pos) => *pos,
Self::Property(x) => (x.2).1,
Self::Stmt(x) => x.position(), Self::Stmt(x) => x.position(),
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(), Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(),
@ -679,9 +715,9 @@ impl Expr {
| Self::Stack(_, pos) | Self::Stack(_, pos)
| Self::FnCall(_, pos) | Self::FnCall(_, pos)
| Self::Custom(_, pos) | Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos = new_pos, | Self::InterpolatedString(_, pos)
| Self::Property(_, pos) => *pos = new_pos,
Self::Property(x) => (x.2).1 = new_pos,
Self::Stmt(x) => x.set_position(new_pos), Self::Stmt(x) => x.set_position(new_pos),
} }
@ -781,7 +817,7 @@ impl Expr {
_ => false, _ => false,
}, },
Self::Property(_) => match token { Self::Property(_, _) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
Token::LeftParen => true, Token::LeftParen => true,

View File

@ -13,8 +13,12 @@ pub enum FnAccess {
Private, Private,
} }
/// A type that holds a configuration option with bit-flags. /// A type that holds a configuration option with bit-flags. Exported under the `internals` feature
/// Exported under the `internals` feature only. /// only.
///
/// 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
/// one more dependency.
#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] #[derive(PartialEq, Eq, Copy, Clone, Hash, Default)]
pub struct OptionFlags(u8); pub struct OptionFlags(u8);
@ -120,19 +124,20 @@ pub mod AST_OPTION_FLAGS {
/// _(internals)_ The [`AST`][crate::AST] node is constant. /// _(internals)_ The [`AST`][crate::AST] node is constant.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001);
/// _(internals)_ The [`AST`][crate::AST] node is public. /// _(internals)_ The [`AST`][crate::AST] node is exported to the outside (i.e. public).
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010); pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010);
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode. /// _(internals)_ The [`AST`][crate::AST] node is in negated mode
/// (meaning whatever information is the opposite).
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100);
/// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow. /// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000); pub const AST_OPTION_BREAK: OptionFlags = OptionFlags(0b0000_1000);
/// _(internals)_ Mask of all options. /// _(internals)_ Mask of all options.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags( pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags(
AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0, AST_OPTION_CONSTANT.0 | AST_OPTION_EXPORTED.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK.0,
); );
impl std::fmt::Debug for OptionFlags { impl std::fmt::Debug for OptionFlags {
@ -158,9 +163,9 @@ pub mod AST_OPTION_FLAGS {
f.write_str("(")?; f.write_str("(")?;
write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?;
write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?; write_option(self, f, num_flags, AST_OPTION_EXPORTED, "Exported")?;
write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?;
write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?; write_option(self, f, num_flags, AST_OPTION_BREAK, "Break")?;
f.write_str(")")?; f.write_str(")")?;
Ok(()) Ok(())

View File

@ -13,7 +13,7 @@ pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
pub use ident::Ident; pub use ident::Ident;
#[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::{OpAssignment, Stmt, StmtBlock}; pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub use expr::FloatWrapper; pub use expr::FloatWrapper;

View File

@ -1,6 +1,6 @@
//! Module defining script statements. //! Module defining script statements.
use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS}; use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
use crate::engine::KEYWORD_EVAL; use crate::engine::KEYWORD_EVAL;
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::{calc_fn_hash, Position, StaticVec, INT}; use crate::{calc_fn_hash, Position, StaticVec, INT};
@ -23,6 +23,8 @@ pub struct OpAssignment<'a> {
/// Hash of the underlying operator call (for fallback). /// Hash of the underlying operator call (for fallback).
pub hash_op: u64, pub hash_op: u64,
/// Op-assignment operator. /// Op-assignment operator.
pub op_assign: &'a str,
/// Underlying operator.
pub op: &'a str, pub op: &'a str,
} }
@ -51,7 +53,8 @@ impl OpAssignment<'_> {
Self { Self {
hash_op_assign: calc_fn_hash(op.literal_syntax(), 2), hash_op_assign: calc_fn_hash(op.literal_syntax(), 2),
hash_op: calc_fn_hash(op_raw, 2), hash_op: calc_fn_hash(op_raw, 2),
op: op.literal_syntax(), op_assign: op.literal_syntax(),
op: op_raw,
} }
} }
/// Create a new [`OpAssignment`] from a base operator. /// Create a new [`OpAssignment`] from a base operator.
@ -76,6 +79,58 @@ impl OpAssignment<'_> {
} }
} }
/// A statements block with an optional condition.
#[derive(Debug, Clone, Hash)]
pub struct ConditionalStmtBlock {
/// Optional condition.
pub condition: Option<Expr>,
/// Statements block.
pub statements: StmtBlock,
}
impl<B: Into<StmtBlock>> From<(Option<Expr>, B)> for ConditionalStmtBlock {
#[inline(always)]
fn from(value: (Option<Expr>, B)) -> Self {
Self {
condition: value.0,
statements: value.1.into(),
}
}
}
impl ConditionalStmtBlock {
/// Does the condition exist?
#[inline(always)]
#[must_use]
pub const fn has_condition(&self) -> bool {
self.condition.is_some()
}
}
/// _(internals)_ A type containing all cases for a `switch` statement.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub struct SwitchCases {
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
/// Statements block for the default case (there can be no condition for the default case).
pub def_case: StmtBlock,
/// List of range cases.
pub ranges: StaticVec<(INT, INT, bool, ConditionalStmtBlock)>,
}
/// _(internals)_ A `try-catch` block.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub struct TryCatchBlock {
/// `try` block.
pub try_block: StmtBlock,
/// `catch` variable, if any.
pub catch_var: Option<Ident>,
/// `catch` block.
pub catch_block: StmtBlock,
}
/// _(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)]
@ -157,6 +212,20 @@ impl DerefMut for StmtBlock {
} }
} }
impl AsRef<[Stmt]> for StmtBlock {
#[inline(always)]
fn as_ref(&self) -> &[Stmt] {
&self.0
}
}
impl AsMut<[Stmt]> for StmtBlock {
#[inline(always)]
fn as_mut(&mut self) -> &mut [Stmt] {
&mut self.0
}
}
impl fmt::Debug for StmtBlock { impl fmt::Debug for StmtBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Block")?; f.write_str("Block")?;
@ -211,15 +280,7 @@ 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( Switch(Expr, Box<SwitchCases>, Position),
Expr,
Box<(
BTreeMap<u64, Box<(Option<Expr>, StmtBlock)>>,
StmtBlock,
StaticVec<(INT, INT, bool, Option<Expr>, StmtBlock)>,
)>,
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.
@ -228,8 +289,8 @@ pub enum Stmt {
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while` /// * [`AST_OPTION_NONE`] = `while`
/// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until` /// * [`AST_OPTION_NEGATED`] = `until`
Do(Box<StmtBlock>, Expr, OptionFlags, Position), Do(Box<StmtBlock>, Expr, OptionFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position), For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
@ -237,11 +298,11 @@ pub enum Stmt {
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export` /// * [`AST_OPTION_EXPORTED`] = `export`
/// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const` /// * [`AST_OPTION_CONSTANT`] = `const`
Var(Expr, Box<Ident>, OptionFlags, Position), Var(Expr, Box<Ident>, OptionFlags, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position), Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
/// ///
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single /// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
@ -250,33 +311,33 @@ pub enum Stmt {
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Box<[Stmt]>, Position), Block(Box<[Stmt]>, Position),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position), TryCatch(Box<TryCatchBlock>, Position),
/// [expression][Expr] /// [expression][Expr]
Expr(Expr), Expr(Expr),
/// `continue`/`break` /// `continue`/`break`
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue` /// * [`AST_OPTION_NONE`] = `continue`
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break` /// * [`AST_OPTION_BREAK`] = `break`
BreakLoop(OptionFlags, Position), BreakLoop(OptionFlags, Position),
/// `return`/`throw` /// `return`/`throw`
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return` /// * [`AST_OPTION_NONE`] = `return`
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw` /// * [`AST_OPTION_BREAK`] = `throw`
Return(OptionFlags, Option<Expr>, Position), Return(OptionFlags, Option<Expr>, Position),
/// `import` expr `as` var /// `import` expr `as` var
/// ///
/// 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(Expr, Option<Box<Ident>>, Position),
/// `export` var `as` var `,` ... /// `export` var `as` var
/// ///
/// Not available under `no_module`. /// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export(Box<[(Ident, Ident)]>, Position), Export(Box<(Ident, Ident)>, Position),
/// Convert a variable to shared. /// Convert a variable to shared.
/// ///
/// Not available under `no_closure`. /// Not available under `no_closure`.
@ -440,26 +501,26 @@ impl Stmt {
Self::Expr(expr) => expr.is_pure(), Self::Expr(expr) => expr.is_pure(),
Self::If(condition, x, _) => { Self::If(condition, x, _) => {
condition.is_pure() condition.is_pure()
&& (x.0).0.iter().all(Stmt::is_pure) && x.0.iter().all(Stmt::is_pure)
&& (x.1).0.iter().all(Stmt::is_pure) && x.1.iter().all(Stmt::is_pure)
} }
Self::Switch(expr, x, _) => { Self::Switch(expr, x, _) => {
expr.is_pure() expr.is_pure()
&& x.0.values().all(|block| { && x.cases.values().all(|block| {
block.0.as_ref().map(Expr::is_pure).unwrap_or(true) block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
&& (block.1).0.iter().all(Stmt::is_pure) && block.statements.iter().all(Stmt::is_pure)
}) })
&& (x.2).iter().all(|(_, _, _, condition, stmt)| { && x.ranges.iter().all(|(_, _, _, block)| {
condition.as_ref().map(Expr::is_pure).unwrap_or(true) block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
&& stmt.0.iter().all(Stmt::is_pure) && block.statements.iter().all(Stmt::is_pure)
}) })
&& (x.1).0.iter().all(Stmt::is_pure) && x.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(Expr::BoolConstant(false, _), _, _) => true,
Self::Do(body, Expr::BoolConstant(x, _), options, _) Self::Do(body, Expr::BoolConstant(x, _), options, _)
if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => if *x == options.contains(AST_OPTION_NEGATED) =>
{ {
body.iter().all(Stmt::is_pure) body.iter().all(Stmt::is_pure)
} }
@ -469,13 +530,13 @@ impl Stmt {
// 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).0.iter().all(Stmt::is_pure), Self::For(iterable, x, _) => iterable.is_pure() && x.2.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()),
Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, Self::BreakLoop(_, _) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _) => { Self::TryCatch(x, _) => {
(x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) x.try_block.iter().all(Stmt::is_pure) && x.catch_block.iter().all(Stmt::is_pure)
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -571,12 +632,12 @@ impl Stmt {
if !e.walk(path, on_node) { if !e.walk(path, on_node) {
return false; return false;
} }
for s in &(x.0).0 { for s in x.0.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
for s in &(x.1).0 { for s in x.1.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
@ -586,27 +647,37 @@ impl Stmt {
if !e.walk(path, on_node) { if !e.walk(path, on_node) {
return false; return false;
} }
for b in x.0.values() { for b in x.cases.values() {
if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { if !b
.condition
.as_ref()
.map(|e| e.walk(path, on_node))
.unwrap_or(true)
{
return false; return false;
} }
for s in &(b.1).0 { for s in b.statements.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
} }
for (_, _, _, c, stmt) in &x.2 { for (_, _, _, b) in &x.ranges {
if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { if !b
.condition
.as_ref()
.map(|e| e.walk(path, on_node))
.unwrap_or(true)
{
return false; return false;
} }
for s in &stmt.0 { for s in b.statements.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
} }
for s in &(x.1).0 { for s in x.def_case.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
@ -626,17 +697,17 @@ impl Stmt {
if !e.walk(path, on_node) { if !e.walk(path, on_node) {
return false; return false;
} }
for s in &(x.2).0 { for s in x.2.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
} }
Self::Assignment(x, _) => { Self::Assignment(x, _) => {
if !x.0.walk(path, on_node) { if !x.1.lhs.walk(path, on_node) {
return false; return false;
} }
if !x.2.walk(path, on_node) { if !x.1.rhs.walk(path, on_node) {
return false; return false;
} }
} }
@ -655,12 +726,12 @@ impl Stmt {
} }
} }
Self::TryCatch(x, _) => { Self::TryCatch(x, _) => {
for s in &(x.0).0 { for s in x.try_block.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
} }
for s in &(x.2).0 { for s in x.catch_block.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }

View File

@ -3,10 +3,38 @@ Rhai Tools
Tools for running Rhai scripts. Tools for running Rhai scripts.
| 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-repl`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-repl.rs) | `rustyline` | simple REPL that interactively evaluates statements |
| [`rhai-dbg`](https://github.com/rhaiscript/rhai/blob/main/src/bin/rhai-dbg.rs) | `debugging` | the _Rhai Debugger_ |
How to Run How to Run
---------- ----------
```bash ```sh
cargo run --bin sample_app_to_run cargo run --bin sample_app_to_run
``` ```
or with required features
```sh
cargo run --bin sample_app_to_run --features feature1,feature2,feature3
```
How to Install
--------------
To install these all tools (with [`decimal`] and [`metadata`] support), use the following command:
```sh
cargo install --path . --bins --features decimal,metadata,debugging,rustyline
```
or specifically:
```sh
cargo install --path . --bin rhai-run --features decimal,metadata,debugging,rustyline
```

543
src/bin/rhai-dbg.rs Normal file
View File

@ -0,0 +1,543 @@
use rhai::debugger::DebuggerCommand;
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, Position, Scope};
use std::{
env,
fs::File,
io::{stdin, stdout, Read, Write},
path::Path,
process::exit,
};
/// Pretty-print source line.
fn print_source(lines: &[String], pos: Position, offset: usize) {
let line_no = if lines.len() > 1 {
if pos.is_none() {
"".to_string()
} else {
format!("{}: ", pos.line().unwrap())
}
} else {
"".to_string()
};
// Print error position
if pos.is_none() {
// No position
println!();
} else {
// Specific position - print line text
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
// Display position marker
if let Some(pos) = pos.position() {
println!("{0:>1$}", "^", line_no.len() + pos + offset);
}
}
}
/// Pretty-print error.
fn print_error(input: &str, mut err: EvalAltResult) {
let lines: Vec<_> = input.trim().split('\n').collect();
let pos = err.take_position();
let line_no = if lines.len() > 1 {
if pos.is_none() {
"".to_string()
} else {
format!("{}: ", pos.line().unwrap())
}
} else {
"".to_string()
};
// Print error position
if pos.is_none() {
// No position
println!("{}", err);
} else {
// Specific position - print line text
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
// Display position marker
println!(
"{0:>1$} {2}",
"^",
line_no.len() + pos.position().unwrap(),
err
);
}
}
/// Print debug help.
fn print_debug_help() {
println!("help => print this help");
println!("quit, exit, kill => quit");
println!("scope => print the scope");
println!("print => print all variables de-duplicated");
println!("print <variable> => print the current value of a variable");
#[cfg(not(feature = "no_module"))]
println!("imports => print all imported modules");
println!("node => print the current AST node");
println!("backtrace => print the current call-stack");
println!("breakpoints => print all break-points");
println!("enable <bp#> => enable a break-point");
println!("disable <bp#> => disable a break-point");
println!("delete <bp#> => delete a break-point");
println!("clear => delete all break-points");
#[cfg(not(feature = "no_position"))]
println!("break => set a new break-point at the current position");
#[cfg(not(feature = "no_position"))]
println!("break <line#> => set a new break-point at a line number");
#[cfg(not(feature = "no_object"))]
println!("break .<prop> => set a new break-point for a property access");
println!("break <func> => set a new break-point for a function call");
println!(
"break <func> <#args> => set a new break-point for a function call with #args arguments"
);
println!("throw [message] => throw an exception (message optional)");
println!("run => restart the script evaluation from beginning");
println!("step => go to the next expression, diving into functions");
println!("over => go to the next expression, skipping oer functions");
println!("next => go to the next statement, skipping over functions");
println!("continue => continue normal execution");
println!();
}
/// Display the scope.
fn print_scope(scope: &Scope, dedup: bool) {
let flattened_clone;
let scope = if dedup {
flattened_clone = scope.clone_visible();
&flattened_clone
} else {
scope
};
for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
#[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
if dedup {
println!(
"{}{}{} = {:?}",
if constant { "const " } else { "" },
name,
value_is_shared,
*value.read_lock::<Dynamic>().unwrap(),
);
} else {
println!(
"[{}] {}{}{} = {:?}",
i + 1,
if constant { "const " } else { "" },
name,
value_is_shared,
*value.read_lock::<Dynamic>().unwrap(),
);
}
}
println!();
}
fn main() {
let title = format!("Rhai Debugger (version {})", env!("CARGO_PKG_VERSION"));
println!("{}", title);
println!("{0:=<1$}", "", title.len());
// Initialize scripting engine
let mut engine = Engine::new();
let mut script = String::new();
let main_ast;
{
// Load init scripts
if let Some(filename) = env::args().skip(1).next() {
let filename = match Path::new(&filename).canonicalize() {
Err(err) => {
eprintln!("Error script file path: {}\n{}", filename, err);
exit(1);
}
Ok(f) => {
match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap()) {
Ok(f) => f.into(),
_ => f,
}
}
};
let mut f = match File::open(&filename) {
Err(err) => {
eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1);
}
Ok(f) => f,
};
if let Err(err) = f.read_to_string(&mut script) {
println!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1);
}
let script = if script.starts_with("#!") {
// Skip shebang
&script[script.find('\n').unwrap_or(0)..]
} else {
&script[..]
};
main_ast = match engine
.compile(&script)
.map_err(Into::<Box<EvalAltResult>>::into)
{
Err(err) => {
print_error(&script, *err);
exit(1);
}
Ok(ast) => ast,
};
println!("Script '{}' loaded.", filename.to_string_lossy());
println!();
} else {
eprintln!("No script file specified.");
exit(1);
}
}
// Hook up debugger
let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect();
engine.on_debugger(
// Store the current source in the debugger state
|| "".into(),
// Main debugging interface
move |context, node, source, pos| {
{
let current_source = &mut *context
.global_runtime_state_mut()
.debugger
.state_mut()
.write_lock::<ImmutableString>()
.unwrap();
let src = source.unwrap_or("");
// Check source
if src != current_source {
println!(">>> Source => {}", source.unwrap_or("main script"));
*current_source = src.into();
}
if !src.is_empty() {
// Print just a line number for imported modules
println!("{} @ {:?}", src, pos);
} else {
// Print the current source line
print_source(&lines, pos, 0);
}
}
// Read stdin for commands
let mut input = String::new();
loop {
print!("rhai-dbg> ");
stdout().flush().expect("couldn't flush stdout");
input.clear();
match stdin().read_line(&mut input) {
Ok(0) => break Ok(DebuggerCommand::Continue),
Ok(_) => match input
.trim()
.split_whitespace()
.collect::<Vec<_>>()
.as_slice()
{
["help", ..] => print_debug_help(),
["exit", ..] | ["quit", ..] | ["kill", ..] => {
println!("Script terminated. Bye!");
exit(0);
}
["node", ..] => {
println!("{:?} {}@{:?}", node, source.unwrap_or_default(), pos);
println!();
}
["continue", ..] => break Ok(DebuggerCommand::Continue),
[] | ["step", ..] => break Ok(DebuggerCommand::StepInto),
["over", ..] => break Ok(DebuggerCommand::StepOver),
["next", ..] => break Ok(DebuggerCommand::Next),
["scope", ..] => print_scope(context.scope(), false),
["print", var_name, ..] => {
if let Some(value) = context.scope().get_value::<Dynamic>(var_name) {
if value.is::<()>() {
println!("=> ()");
} else {
println!("=> {}", value);
}
} else {
eprintln!("Variable not found: {}", var_name);
}
}
["print", ..] => print_scope(context.scope(), true),
#[cfg(not(feature = "no_module"))]
["imports", ..] => {
for (i, (name, module)) in context
.global_runtime_state()
.scan_imports_raw()
.enumerate()
{
println!(
"[{}] {} = {}",
i + 1,
name,
module.id().unwrap_or("<unknown>")
);
}
println!();
}
#[cfg(not(feature = "no_function"))]
["backtrace", ..] => {
for frame in context
.global_runtime_state()
.debugger
.call_stack()
.iter()
.rev()
{
println!("{}", frame)
}
}
["clear", ..] => {
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.clear();
println!("All break-points cleared.");
}
["breakpoints", ..] => Iterator::for_each(
context
.global_runtime_state()
.debugger
.break_points()
.iter()
.enumerate(),
|(i, bp)| match bp {
#[cfg(not(feature = "no_position"))]
rhai::debugger::BreakPoint::AtPosition { pos, .. } => {
let line_num = format!("[{}] line ", i + 1);
print!("{}", line_num);
print_source(&lines, *pos, line_num.len());
}
_ => println!("[{}] {}", i + 1, bp),
},
),
["enable", n, ..] => {
if let Ok(n) = n.parse::<usize>() {
let range = 1..=context
.global_runtime_state_mut()
.debugger
.break_points()
.len();
if range.contains(&n) {
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.get_mut(n - 1)
.unwrap()
.enable(true);
println!("Break-point #{} enabled.", n)
} else {
eprintln!("Invalid break-point: {}", n);
}
} else {
eprintln!("Invalid break-point: '{}'", n);
}
}
["disable", n, ..] => {
if let Ok(n) = n.parse::<usize>() {
let range = 1..=context
.global_runtime_state_mut()
.debugger
.break_points()
.len();
if range.contains(&n) {
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.get_mut(n - 1)
.unwrap()
.enable(false);
println!("Break-point #{} disabled.", n)
} else {
eprintln!("Invalid break-point: {}", n);
}
} else {
eprintln!("Invalid break-point: '{}'", n);
}
}
["delete", n, ..] => {
if let Ok(n) = n.parse::<usize>() {
let range = 1..=context
.global_runtime_state_mut()
.debugger
.break_points()
.len();
if range.contains(&n) {
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.remove(n - 1);
println!("Break-point #{} deleted.", n)
} else {
eprintln!("Invalid break-point: {}", n);
}
} else {
eprintln!("Invalid break-point: '{}'", n);
}
}
["break", fn_name, args, ..] => {
if let Ok(args) = args.parse::<usize>() {
let bp = rhai::debugger::BreakPoint::AtFunctionCall {
name: fn_name.trim().into(),
args,
enabled: true,
};
println!("Break-point added for {}", bp);
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.push(bp);
} else {
eprintln!("Invalid number of arguments: '{}'", args);
}
}
// Property name
#[cfg(not(feature = "no_object"))]
["break", param] if param.starts_with('.') && param.len() > 1 => {
let bp = rhai::debugger::BreakPoint::AtProperty {
name: param[1..].into(),
enabled: true,
};
println!("Break-point added for {}", bp);
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.push(bp);
}
// Numeric parameter
#[cfg(not(feature = "no_position"))]
["break", param] if param.parse::<usize>().is_ok() => {
let n = param.parse::<usize>().unwrap();
let range = if source.is_none() {
1..=lines.len()
} else {
1..=(u16::MAX as usize)
};
if range.contains(&n) {
let bp = rhai::debugger::BreakPoint::AtPosition {
source: source.unwrap_or("").into(),
pos: Position::new(n as u16, 0),
enabled: true,
};
println!("Break-point added {}", bp);
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.push(bp);
} else {
eprintln!("Invalid line number: {}", n);
}
}
// Function name parameter
["break", param] => {
let bp = rhai::debugger::BreakPoint::AtFunctionName {
name: param.trim().into(),
enabled: true,
};
println!("Break-point added for {}", bp);
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.push(bp);
}
#[cfg(not(feature = "no_position"))]
["break", ..] => {
let bp = rhai::debugger::BreakPoint::AtPosition {
source: source.unwrap_or("").into(),
pos,
enabled: true,
};
println!("Break-point added {}", bp);
context
.global_runtime_state_mut()
.debugger
.break_points_mut()
.push(bp);
}
["throw"] => {
break Err(EvalAltResult::ErrorRuntime(Dynamic::UNIT, pos).into())
}
["throw", _msg, ..] => {
let msg = input.trim().splitn(2, ' ').skip(1).next().unwrap_or("");
break Err(EvalAltResult::ErrorRuntime(msg.trim().into(), pos).into());
}
["run", ..] => {
println!("Restarting script...");
break Err(EvalAltResult::ErrorTerminated(Dynamic::UNIT, pos).into());
}
[cmd, ..] => eprintln!("Invalid debugger command: '{}'", cmd),
},
Err(err) => panic!("input error: {}", err),
}
}
},
);
// Set a file module resolver without caching
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
{
let mut resolver = rhai::module_resolvers::FileModuleResolver::new();
resolver.enable_cache(false);
engine.set_module_resolver(resolver);
}
print_debug_help();
// Evaluate
while let Err(err) = engine.run_ast_with_scope(&mut Scope::new(), &main_ast) {
match *err {
// Loop back to restart
EvalAltResult::ErrorTerminated(_, _) => (),
// Break evaluation
_ => {
print_error(&script, *err);
break;
}
}
}
}

View File

@ -1,12 +1,12 @@
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT}; use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
use rustyline::config::Builder;
use rustyline::error::ReadlineError;
use rustyline::{Cmd, Editor, Event, EventHandler, KeyCode, KeyEvent, Modifiers, Movement};
use smallvec::smallvec;
use std::{ use std::{env, fs::File, io::Read, path::Path, process::exit};
env,
fs::File, const HISTORY_FILE: &str = ".rhai-repl-history";
io::{stdin, stdout, Read, Write},
path::Path,
process::exit,
};
/// Pretty-print error. /// Pretty-print error.
fn print_error(input: &str, mut err: EvalAltResult) { fn print_error(input: &str, mut err: EvalAltResult) {
@ -44,7 +44,9 @@ fn print_error(input: &str, mut err: EvalAltResult) {
/// Print help text. /// Print help text.
fn print_help() { fn print_help() {
println!("help => print this help"); println!("help => print this help");
println!("keys => print list of key bindings");
println!("quit, exit => quit"); println!("quit, exit => quit");
println!("history => print lines history");
println!("scope => print all variables in the scope"); println!("scope => print all variables in the scope");
println!("strict => toggle on/off Strict Variables Mode"); println!("strict => toggle on/off Strict Variables Mode");
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
@ -55,16 +57,51 @@ fn print_help() {
println!("json => output all functions in JSON format"); println!("json => output all functions in JSON format");
println!("ast => print the last AST (optimized)"); println!("ast => print the last AST (optimized)");
println!("astu => print the last raw, un-optimized AST"); println!("astu => print the last raw, un-optimized AST");
println!(r"end a line with '\' to continue to the next line."); println!();
println!("press Shift-Enter to continue to the next line,");
println!(r"or end a line with '\' (e.g. when pasting code).");
println!();
}
/// Print key bindings.
fn print_keys() {
println!("Home => move to beginning of line");
println!("Ctrl-Home => move to beginning of input");
println!("End => move to end of line");
println!("Ctrl-End => move to end of input");
println!("Left => move left");
println!("Ctrl-Left => move left by one word");
println!("Right => move right by one word");
println!("Ctrl-Right => move right");
println!("Up => previous line or history");
println!("Ctrl-Up => previous history");
println!("Down => next line or history");
println!("Ctrl-Down => next history");
println!("Ctrl-R => reverse search history");
println!(" (Ctrl-S forward, Ctrl-G cancel)");
println!("Ctrl-L => clear screen");
println!("Escape => clear all input");
println!("Ctrl-C => exit");
println!("Ctrl-D => EOF (when line empty)");
println!("Ctrl-H, Backspace => backspace");
println!("Ctrl-D, Del => delete character");
println!("Ctrl-U => delete from start");
println!("Ctrl-W => delete previous word");
println!("Ctrl-T => transpose characters");
println!("Ctrl-V => insert special character");
println!("Ctrl-Y => paste yank");
println!("Ctrl-Z => suspend (Unix), undo (Windows)");
println!("Ctrl-_ => undo");
println!("Enter => run code");
println!("Shift-Ctrl-Enter => continue to next line");
println!();
println!("Plus all standard Emacs key bindings");
println!(); println!();
} }
/// Display the scope. /// Display the scope.
fn print_scope(scope: &Scope) { fn print_scope(scope: &Scope) {
scope for (i, (name, constant, value)) in scope.iter_raw().enumerate() {
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" }; let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
@ -78,25 +115,15 @@ fn print_scope(scope: &Scope) {
value_is_shared, value_is_shared,
*value.read_lock::<Dynamic>().unwrap(), *value.read_lock::<Dynamic>().unwrap(),
) )
}); }
println!(); println!();
} }
fn main() { // Load script files specified in the command line.
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION")); #[cfg(not(feature = "no_module"))]
println!("{}", title); #[cfg(not(feature = "no_std"))]
println!("{0:=<1$}", "", title.len()); fn load_script_files(engine: &mut Engine) {
#[cfg(not(feature = "no_optimize"))]
let mut optimize_level = rhai::OptimizationLevel::Simple;
// Initialize scripting engine
let mut engine = Engine::new();
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
{
// Load init scripts // Load init scripts
let mut contents = String::new(); let mut contents = String::new();
let mut has_init_scripts = false; let mut has_init_scripts = false;
@ -169,8 +196,92 @@ fn main() {
if has_init_scripts { if has_init_scripts {
println!(); println!();
} }
}
// Setup the Rustyline editor.
fn setup_editor() -> Editor<()> {
let config = Builder::new()
.tab_stop(4)
.indent_size(4)
.bracketed_paste(true)
.build();
let mut rl = Editor::<()>::with_config(config);
// Bind more keys
// On Windows, Esc clears the input buffer
#[cfg(target_family = "windows")]
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(KeyCode::Esc, Modifiers::empty())]),
EventHandler::Simple(Cmd::Kill(Movement::WholeBuffer)),
);
// On Windows, Ctrl-Z is undo
#[cfg(target_family = "windows")]
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent::ctrl('z')]),
EventHandler::Simple(Cmd::Undo(1)),
);
// Map Shift-Return to insert a new line - bypass need for `\` continuation
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(
KeyCode::Char('m'),
Modifiers::CTRL_SHIFT
)]),
EventHandler::Simple(Cmd::Newline),
);
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(
KeyCode::Char('j'),
Modifiers::CTRL_SHIFT
)]),
EventHandler::Simple(Cmd::Newline),
);
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(KeyCode::Enter, Modifiers::SHIFT)]),
EventHandler::Simple(Cmd::Newline),
);
// Map Ctrl-Home and Ctrl-End for beginning/end of input
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(KeyCode::Home, Modifiers::CTRL)]),
EventHandler::Simple(Cmd::Move(Movement::BeginningOfBuffer)),
);
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(KeyCode::End, Modifiers::CTRL)]),
EventHandler::Simple(Cmd::Move(Movement::EndOfBuffer)),
);
// Map Ctrl-Up and Ctrl-Down to skip up/down the history, even through multi-line histories
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(KeyCode::Down, Modifiers::CTRL)]),
EventHandler::Simple(Cmd::NextHistory),
);
rl.bind_sequence(
Event::KeySeq(smallvec![KeyEvent(KeyCode::Up, Modifiers::CTRL)]),
EventHandler::Simple(Cmd::PreviousHistory),
);
// Load the history file
if rl.load_history(HISTORY_FILE).is_err() {
eprintln!("! No previous lines history!");
} }
rl
}
fn main() {
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION"));
println!("{}", title);
println!("{0:=<1$}", "", title.len());
#[cfg(not(feature = "no_optimize"))]
let mut optimize_level = rhai::OptimizationLevel::Simple;
// Initialize scripting engine
let mut engine = Engine::new();
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
load_script_files(&mut engine);
// Setup Engine // Setup Engine
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(rhai::OptimizationLevel::None); engine.set_optimization_level(rhai::OptimizationLevel::None);
@ -194,6 +305,9 @@ fn main() {
// Create scope // Create scope
let mut scope = Scope::new(); let mut scope = Scope::new();
// REPL line editor setup
let mut rl = setup_editor();
// REPL loop // REPL loop
let mut input = String::new(); let mut input = String::new();
let mut main_ast = AST::empty(); let mut main_ast = AST::empty();
@ -203,31 +317,37 @@ fn main() {
print_help(); print_help();
'main_loop: loop { 'main_loop: loop {
print!("rhai-repl> ");
stdout().flush().expect("couldn't flush stdout");
input.clear(); input.clear();
loop { loop {
match stdin().read_line(&mut input) { let prompt = if input.is_empty() {
Ok(0) => break 'main_loop, "rhai-repl> "
Ok(_) => (),
Err(err) => panic!("input error: {}", err),
}
let line = input.as_str().trim_end();
// Allow line continuation
if line.ends_with('\\') {
let len = line.len();
input.truncate(len - 1);
input.push('\n');
} else { } else {
" > "
};
match rl.readline(prompt) {
// Line continuation
Ok(mut line) if line.ends_with("\\") => {
line.pop();
input += line.trim_end();
input.push('\n');
}
Ok(line) => {
input += line.trim_end();
if !input.is_empty() {
rl.add_history_entry(input.clone());
}
break; break;
} }
print!("> "); Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop,
stdout().flush().expect("couldn't flush stdout");
Err(err) => {
eprintln!("Error: {:?}", err);
break 'main_loop;
}
}
} }
let script = input.trim(); let script = input.trim();
@ -242,7 +362,29 @@ fn main() {
print_help(); print_help();
continue; continue;
} }
"keys" => {
print_keys();
continue;
}
"exit" | "quit" => break, // quit "exit" | "quit" => break, // quit
"history" => {
for (i, h) in rl.history().iter().enumerate() {
match &h.split('\n').collect::<Vec<_>>()[..] {
[line] => println!("[{}] {}", i + 1, line),
lines => {
for (x, line) in lines.iter().enumerate() {
let number = format!("[{}]", i + 1);
if x == 0 {
println!("{} {}", number, line.trim_end());
} else {
println!("{0:>1$} {2}", "", number.len(), line.trim_end());
}
}
}
}
}
continue;
}
"strict" if engine.strict_variables() => { "strict" if engine.strict_variables() => {
engine.set_strict_variables(false); engine.set_strict_variables(false);
println!("Strict Variables Mode turned OFF."); println!("Strict Variables Mode turned OFF.");
@ -282,13 +424,14 @@ fn main() {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
"functions" => { "functions" => {
// print a list of all registered functions // print a list of all registered functions
engine for f in engine.gen_fn_signatures(false) {
.gen_fn_signatures(false) println!("{}", f)
.into_iter() }
.for_each(|f| println!("{}", f));
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
main_ast.iter_functions().for_each(|f| println!("{}", f)); for f in main_ast.iter_functions() {
println!("{}", f)
}
println!(); println!();
continue; continue;
@ -343,4 +486,8 @@ fn main() {
// Throw away all the statements, leaving only the functions // Throw away all the statements, leaving only the functions
main_ast.clear_statements(); main_ast.clear_statements();
} }
rl.save_history(HISTORY_FILE).unwrap();
println!("Bye!");
} }

View File

@ -96,6 +96,7 @@ pub struct Engine {
/// A collection of all modules loaded into the global namespace of the Engine. /// A collection of all modules loaded into the global namespace of the Engine.
pub(crate) global_modules: StaticVec<Shared<Module>>, pub(crate) global_modules: StaticVec<Shared<Module>>,
/// A collection of all sub-modules directly loaded into the Engine. /// A collection of all sub-modules directly loaded into the Engine.
#[cfg(not(feature = "no_module"))]
pub(crate) global_sub_modules: BTreeMap<Identifier, Shared<Module>>, pub(crate) global_sub_modules: BTreeMap<Identifier, Shared<Module>>,
/// A module resolution service. /// A module resolution service.
@ -115,17 +116,17 @@ pub struct Engine {
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>, pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
pub(crate) resolve_var: Option<OnVarCallback>, pub(crate) resolve_var: Option<Box<OnVarCallback>>,
/// Callback closure to remap tokens during parsing. /// Callback closure to remap tokens during parsing.
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>, pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
/// Callback closure for implementing the `print` command. /// Callback closure for implementing the `print` command.
pub(crate) print: Option<OnPrintCallback>, pub(crate) print: Option<Box<OnPrintCallback>>,
/// Callback closure for implementing the `debug` command. /// Callback closure for implementing the `debug` command.
pub(crate) debug: Option<OnDebugCallback>, pub(crate) debug: Option<Box<OnDebugCallback>>,
/// Callback closure for progress reporting. /// Callback closure for progress reporting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub(crate) progress: Option<crate::func::native::OnProgressCallback>, pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
/// Optimize the [`AST`][crate::AST] after compilation. /// Optimize the [`AST`][crate::AST] after compilation.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
@ -137,6 +138,13 @@ pub struct Engine {
/// Max limits. /// Max limits.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub(crate) limits: crate::api::limits::Limits, pub(crate) limits: crate::api::limits::Limits,
/// Callback closure for debugging.
#[cfg(feature = "debugging")]
pub(crate) debugger: Option<(
Box<crate::eval::OnDebuggingInit>,
Box<crate::eval::OnDebuggerCallback>,
)>,
} }
impl fmt::Debug for Engine { impl fmt::Debug for Engine {
@ -144,11 +152,11 @@ impl fmt::Debug for Engine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("Engine"); let mut f = f.debug_struct("Engine");
f.field("global_modules", &self.global_modules) f.field("global_modules", &self.global_modules);
.field("global_sub_modules", &self.global_sub_modules);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
f.field("module_resolver", &self.module_resolver.is_some()); f.field("global_sub_modules", &self.global_sub_modules)
.field("module_resolver", &self.module_resolver.is_some());
f.field("type_names", &self.type_names) f.field("type_names", &self.type_names)
.field("disabled_symbols", &self.disabled_symbols) .field("disabled_symbols", &self.disabled_symbols)
@ -226,7 +234,7 @@ impl Engine {
engine.print = Some(Box::new(|s| println!("{}", s))); engine.print = Some(Box::new(|s| println!("{}", s)));
engine.debug = Some(Box::new(|s, source, pos| { engine.debug = Some(Box::new(|s, source, pos| {
if let Some(source) = source { if let Some(source) = source {
println!("{}{:?} | {}", source, pos, s); println!("{} @ {:?} | {}", source, pos, s);
} else if pos.is_none() { } else if pos.is_none() {
println!("{}", s); println!("{}", s);
} else { } else {
@ -253,6 +261,8 @@ impl Engine {
pub fn new_raw() -> Self { pub fn new_raw() -> Self {
let mut engine = Self { let mut engine = Self {
global_modules: StaticVec::new_const(), global_modules: StaticVec::new_const(),
#[cfg(not(feature = "no_module"))]
global_sub_modules: BTreeMap::new(), global_sub_modules: BTreeMap::new(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -280,6 +290,9 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: crate::api::limits::Limits::new(), limits: crate::api::limits::Limits::new(),
#[cfg(feature = "debugging")]
debugger: None,
}; };
// Add the global namespace module // Add the global namespace module

View File

@ -125,6 +125,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
target: &mut Target, target: &mut Target,
root: (&str, Position), root: (&str, Position),
parent: &Expr,
rhs: &Expr, rhs: &Expr,
terminate_chaining: bool, terminate_chaining: bool,
idx_values: &mut StaticVec<super::ChainArgument>, idx_values: &mut StaticVec<super::ChainArgument>,
@ -132,12 +133,16 @@ impl Engine {
level: usize, level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>, new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
let _parent = parent;
let is_ref_mut = target.is_ref(); let is_ref_mut = target.is_ref();
let _terminate_chaining = terminate_chaining; let _terminate_chaining = terminate_chaining;
// Pop the last index value // Pop the last index value
let idx_val = idx_values.pop().unwrap(); let idx_val = idx_values.pop().unwrap();
#[cfg(feature = "debugging")]
let scope = &mut Scope::new();
match chain_type { match chain_type {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ChainType::Indexing => { ChainType::Indexing => {
@ -150,6 +155,9 @@ impl Engine {
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos) Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
if !_terminate_chaining => if !_terminate_chaining =>
{ {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
let mut idx_val_for_setter = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
let idx_pos = x.lhs.position(); let idx_pos = x.lhs.position();
let rhs_chain = rhs.into(); let rhs_chain = rhs.into();
@ -162,7 +170,7 @@ impl Engine {
let obj_ptr = &mut obj; let obj_ptr = &mut obj;
match self.eval_dot_index_chain_helper( match self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *term,
idx_values, rhs_chain, level, new_val, idx_values, rhs_chain, level, new_val,
) { ) {
Ok((result, true)) if is_obj_temp_val => { Ok((result, true)) if is_obj_temp_val => {
@ -195,6 +203,9 @@ impl Engine {
} }
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
let mut idx_val_for_setter = idx_val.clone(); let mut idx_val_for_setter = idx_val.clone();
@ -236,11 +247,15 @@ impl Engine {
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// xxx[rhs] // xxx[rhs]
_ => self _ => {
.get_indexed_mut( #[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?;
self.get_indexed_mut(
global, state, lib, target, idx_val, pos, false, true, level, global, state, lib, target, idx_val, pos, false, true, level,
) )
.map(|v| (v.take_or_clone(), false)), .map(|v| (v.take_or_clone(), false))
}
} }
} }
@ -251,9 +266,20 @@ impl Engine {
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => { Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref(); let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val.into_fn_call_args(); let call_args = &mut idx_val.into_fn_call_args();
self.make_method_call(
#[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, rhs, level,
)?;
let result = self.make_method_call(
global, state, lib, name, *hashes, target, call_args, *pos, level, global, state, lib, name, *hashes, target, call_args, *pos, level,
) );
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
result
} }
// xxx.fn_name(...) = ??? // xxx.fn_name(...) = ???
Expr::FnCall(_, _) if new_val.is_some() => { Expr::FnCall(_, _) if new_val.is_some() => {
@ -264,10 +290,12 @@ impl Engine {
unreachable!("function call in dot chain should not be namespace-qualified") unreachable!("function call in dot chain should not be namespace-qualified")
} }
// {xxx:map}.id op= ??? // {xxx:map}.id op= ???
Expr::Property(x) if target.is::<crate::Map>() && new_val.is_some() => { Expr::Property(x, pos) if target.is::<crate::Map>() && new_val.is_some() => {
let (name, pos) = &x.2; #[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
let index = x.2.clone().into();
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
let index = name.into();
{ {
let val_target = &mut self.get_indexed_mut( let val_target = &mut self.get_indexed_mut(
global, state, lib, target, index, *pos, true, false, level, global, state, lib, target, index, *pos, true, false, level,
@ -282,17 +310,22 @@ impl Engine {
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// {xxx:map}.id // {xxx:map}.id
Expr::Property(x) if target.is::<crate::Map>() => { Expr::Property(x, pos) if target.is::<crate::Map>() => {
let (name, pos) = &x.2; #[cfg(feature = "debugging")]
let index = name.into(); self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
let index = x.2.clone().into();
let val = self.get_indexed_mut( let val = self.get_indexed_mut(
global, state, lib, target, index, *pos, false, false, level, global, state, lib, target, index, *pos, false, false, level,
)?; )?;
Ok((val.take_or_clone(), false)) Ok((val.take_or_clone(), false))
} }
// xxx.id op= ??? // xxx.id op= ???
Expr::Property(x) if new_val.is_some() => { Expr::Property(x, pos) if new_val.is_some() => {
let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref(); #[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
let ((getter, hash_get), (setter, hash_set), name) = x.as_ref();
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
if op_info.is_some() { if op_info.is_some() {
@ -367,8 +400,11 @@ impl Engine {
}) })
} }
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x, pos) => {
let ((getter, hash_get), _, (name, pos)) = x.as_ref(); #[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
let ((getter, hash_get), _, name) = x.as_ref();
let hash = crate::ast::FnCallHashes::from_native(*hash_get); let hash = crate::ast::FnCallHashes::from_native(*hash_get);
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
self.exec_fn_call( self.exec_fn_call(
@ -401,23 +437,39 @@ impl Engine {
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos)
if target.is::<crate::Map>() => if target.is::<crate::Map>() =>
{ {
let _node = &x.lhs;
let val_target = &mut match x.lhs { let val_target = &mut match x.lhs {
Expr::Property(ref p) => { Expr::Property(ref p, pos) => {
let (name, pos) = &p.2; #[cfg(feature = "debugging")]
let index = name.into(); self.run_debugger(
scope, global, state, lib, this_ptr, _node, level,
)?;
let index = p.2.clone().into();
self.get_indexed_mut( self.get_indexed_mut(
global, state, lib, target, index, *pos, false, true, level, global, state, lib, target, index, pos, false, true, level,
)? )?
} }
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(ref x, pos) if !x.is_qualified() => { Expr::FnCall(ref x, pos) if !x.is_qualified() => {
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref(); let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
let call_args = &mut idx_val.into_fn_call_args(); let call_args = &mut idx_val.into_fn_call_args();
let (val, _) = self.make_method_call(
#[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, _node, level,
)?;
let result = self.make_method_call(
global, state, lib, name, *hashes, target, call_args, pos, global, state, lib, name, *hashes, target, call_args, pos,
level, level,
)?; );
val.into()
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
result?.0.into()
} }
// {xxx:map}.module::fn_name(...) - syntax error // {xxx:map}.module::fn_name(...) - syntax error
Expr::FnCall(_, _) => unreachable!( Expr::FnCall(_, _) => unreachable!(
@ -429,18 +481,24 @@ impl Engine {
let rhs_chain = rhs.into(); let rhs_chain = rhs.into();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, val_target, root, &x.rhs, *term, global, state, lib, this_ptr, val_target, 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(*x_pos)) .map_err(|err| err.fill_position(*x_pos))
} }
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr // xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => { Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => {
let _node = &x.lhs;
match x.lhs { match x.lhs {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(ref p) => { Expr::Property(ref p, pos) => {
let ((getter, hash_get), (setter, hash_set), (name, pos)) = #[cfg(feature = "debugging")]
p.as_ref(); self.run_debugger(
scope, global, state, lib, this_ptr, _node, level,
)?;
let ((getter, hash_get), (setter, hash_set), name) = p.as_ref();
let rhs_chain = rhs.into(); let rhs_chain = rhs.into();
let hash_get = crate::ast::FnCallHashes::from_native(*hash_get); let hash_get = crate::ast::FnCallHashes::from_native(*hash_get);
let hash_set = crate::ast::FnCallHashes::from_native(*hash_set); let hash_set = crate::ast::FnCallHashes::from_native(*hash_set);
@ -451,15 +509,15 @@ impl Engine {
let (mut val, _) = self let (mut val, _) = self
.exec_fn_call( .exec_fn_call(
global, state, lib, getter, hash_get, args, is_ref_mut, global, state, lib, getter, hash_get, args, is_ref_mut,
true, *pos, None, level, true, pos, None, level,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
ERR::ErrorDotExpr(_, _) => { ERR::ErrorDotExpr(_, _) => {
let prop = name.into(); let prop = name.into();
self.get_indexed_mut( self.get_indexed_mut(
global, state, lib, target, prop, *pos, false, global, state, lib, target, prop, pos, false, true,
true, level, level,
) )
.map(|v| (v.take_or_clone(), false)) .map(|v| (v.take_or_clone(), false))
.map_err( .map_err(
@ -482,6 +540,7 @@ impl Engine {
this_ptr, this_ptr,
&mut val.into(), &mut val.into(),
root, root,
rhs,
&x.rhs, &x.rhs,
*term, *term,
idx_values, idx_values,
@ -498,7 +557,7 @@ impl Engine {
let args = &mut arg_values; let args = &mut arg_values;
self.exec_fn_call( self.exec_fn_call(
global, state, lib, setter, hash_set, args, is_ref_mut, global, state, lib, setter, hash_set, args, is_ref_mut,
true, *pos, None, level, true, pos, None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -513,7 +572,7 @@ impl Engine {
); );
self.exec_fn_call( self.exec_fn_call(
global, state, lib, fn_name, hash_set, args, global, state, lib, fn_name, hash_set, args,
is_ref_mut, true, *pos, None, level, is_ref_mut, true, pos, None, level,
) )
.or_else(|idx_err| match *idx_err { .or_else(|idx_err| match *idx_err {
ERR::ErrorIndexingType(_, _) => { ERR::ErrorIndexingType(_, _) => {
@ -536,14 +595,24 @@ impl Engine {
let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref(); let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = rhs.into(); let rhs_chain = rhs.into();
let args = &mut idx_val.into_fn_call_args(); let args = &mut idx_val.into_fn_call_args();
let (mut val, _) = self.make_method_call(
global, state, lib, name, *hashes, target, args, pos, level, #[cfg(feature = "debugging")]
let reset_debugger = self.run_debugger_with_reset(
scope, global, state, lib, this_ptr, _node, level,
)?; )?;
let val = &mut val;
let result = self.make_method_call(
global, state, lib, name, *hashes, target, args, pos, level,
);
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
let val = &mut result?.0;
let target = &mut val.into(); let target = &mut val.into();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, target, root, &x.rhs, *term, global, state, lib, this_ptr, target, 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))
@ -594,6 +663,9 @@ impl Engine {
match lhs { match lhs {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(_, var_pos, x) => { Expr::Variable(_, var_pos, x) => {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, *var_pos)?; self.inc_operations(&mut global.num_operations, *var_pos)?;
@ -604,7 +676,7 @@ impl Engine {
let root = (x.2.as_str(), *var_pos); let root = (x.2.as_str(), *var_pos);
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
global, state, lib, &mut None, obj_ptr, root, rhs, term, idx_values, global, state, lib, &mut None, obj_ptr, root, expr, rhs, term, idx_values,
chain_type, level, new_val, chain_type, level, new_val,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
@ -618,8 +690,8 @@ impl Engine {
let obj_ptr = &mut value.into(); let obj_ptr = &mut value.into();
let root = ("", expr.position()); let root = ("", expr.position());
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
global, state, lib, this_ptr, obj_ptr, root, rhs, term, idx_values, chain_type, global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
level, new_val, chain_type, level, new_val,
) )
.map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v }) .map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v })
.map_err(|err| err.fill_position(op_pos)) .map_err(|err| err.fill_position(op_pos))
@ -678,10 +750,10 @@ impl Engine {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => {
idx_values.push(super::ChainArgument::Property((x.2).1)) idx_values.push(super::ChainArgument::Property(*pos))
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_, _) => unreachable!("unexpected Expr::Property for indexing"),
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if !terminate_chaining => { Expr::Index(x, term, _) | Expr::Dot(x, term, _) if !terminate_chaining => {
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref(); let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
@ -689,10 +761,10 @@ impl Engine {
// Evaluate in left-to-right order // Evaluate in left-to-right order
let lhs_arg_val = match lhs { let lhs_arg_val = match lhs {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => {
super::ChainArgument::Property((x.2).1) super::ChainArgument::Property(*pos)
} }
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), Expr::Property(_, _) => unreachable!("unexpected Expr::Property for indexing"),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::FnCall(x, _) Expr::FnCall(x, _)

455
src/eval/debugger.rs Normal file
View File

@ -0,0 +1,455 @@
//! Module defining the debugging interface.
#![cfg(feature = "debugging")]
use super::{EvalContext, EvalState, GlobalRuntimeState};
use crate::ast::{ASTNode, Expr, Stmt};
use crate::{Dynamic, Engine, Identifier, Module, Position, RhaiResultOf, Scope};
use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// Callback function to initialize the debugger.
#[cfg(not(feature = "sync"))]
pub type OnDebuggingInit = dyn Fn() -> Dynamic;
/// Callback function to initialize the debugger.
#[cfg(feature = "sync")]
pub type OnDebuggingInit = dyn Fn() -> Dynamic + Send + Sync;
/// Callback function for debugging.
#[cfg(not(feature = "sync"))]
pub type OnDebuggerCallback =
dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>;
/// Callback function for debugging.
#[cfg(feature = "sync")]
pub type OnDebuggerCallback = dyn Fn(&mut EvalContext, ASTNode, Option<&str>, Position) -> RhaiResultOf<DebuggerCommand>
+ Send
+ Sync;
/// A command for the debugger on the next iteration.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum DebuggerCommand {
// Continue normal execution.
Continue,
// Step into the next expression, diving into functions.
StepInto,
// Run to the next expression or statement, stepping over functions.
StepOver,
// Run to the next statement, skipping over functions.
Next,
}
/// A break-point for debugging.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BreakPoint {
/// Break at a particular position under a particular source.
///
/// Not available under `no_position`.
///
/// Source is empty if not available.
#[cfg(not(feature = "no_position"))]
AtPosition {
source: Identifier,
pos: Position,
enabled: bool,
},
/// Break at a particular function call.
AtFunctionName { name: Identifier, enabled: bool },
/// Break at a particular function call with a particular number of arguments.
AtFunctionCall {
name: Identifier,
args: usize,
enabled: bool,
},
/// Break at a particular property .
///
/// Not available under `no_object`.
#[cfg(not(feature = "no_object"))]
AtProperty { name: Identifier, enabled: bool },
}
impl fmt::Display for BreakPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(not(feature = "no_position"))]
Self::AtPosition {
source,
pos,
enabled,
} => {
if !source.is_empty() {
write!(f, "{} @ {:?}", source, pos)?;
} else {
write!(f, "@ {:?}", pos)?;
}
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
Self::AtFunctionName {
name: fn_name,
enabled,
} => {
write!(f, "{} (...)", fn_name)?;
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
Self::AtFunctionCall {
name: fn_name,
args,
enabled,
} => {
write!(
f,
"{} ({})",
fn_name,
std::iter::repeat("_")
.take(*args)
.collect::<Vec<_>>()
.join(", ")
)?;
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
#[cfg(not(feature = "no_object"))]
Self::AtProperty {
name: prop,
enabled,
} => {
write!(f, ".{}", prop)?;
if !*enabled {
f.write_str(" (disabled)")?;
}
Ok(())
}
}
}
}
impl BreakPoint {
/// Is this [`BreakPoint`] enabled?
#[inline(always)]
pub fn is_enabled(&self) -> bool {
match self {
#[cfg(not(feature = "no_position"))]
Self::AtPosition { enabled, .. } => *enabled,
Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => *enabled,
#[cfg(not(feature = "no_object"))]
Self::AtProperty { enabled, .. } => *enabled,
}
}
/// Enable/disable this [`BreakPoint`].
#[inline(always)]
pub fn enable(&mut self, value: bool) {
match self {
#[cfg(not(feature = "no_position"))]
Self::AtPosition { enabled, .. } => *enabled = value,
Self::AtFunctionName { enabled, .. } | Self::AtFunctionCall { enabled, .. } => {
*enabled = value
}
#[cfg(not(feature = "no_object"))]
Self::AtProperty { enabled, .. } => *enabled = value,
}
}
}
/// A function call.
#[cfg(not(feature = "no_function"))]
#[derive(Debug, Clone, Hash)]
pub struct CallStackFrame {
/// Function name.
pub fn_name: Identifier,
/// Copies of function call arguments, if any.
pub args: crate::StaticVec<Dynamic>,
/// Source of the function, empty if none.
pub source: Identifier,
/// [Position][`Position`] of the function call.
pub pos: Position,
}
#[cfg(not(feature = "no_function"))]
impl fmt::Display for CallStackFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut fp = f.debug_tuple(&self.fn_name);
for arg in &self.args {
fp.field(arg);
}
fp.finish()?;
if !self.pos.is_none() {
if self.source.is_empty() {
write!(f, " @ {:?}", self.pos)?;
} else {
write!(f, ": {} @ {:?}", self.source, self.pos)?;
}
}
Ok(())
}
}
/// A type providing debugging facilities.
#[derive(Debug, Clone, Hash)]
pub struct Debugger {
/// The current status command.
status: DebuggerCommand,
/// The current state.
state: Dynamic,
/// The current set of break-points.
break_points: Vec<BreakPoint>,
/// The current function call stack.
#[cfg(not(feature = "no_function"))]
call_stack: Vec<CallStackFrame>,
}
impl Debugger {
/// Create a new [`Debugger`] based on an [`Engine`].
#[inline(always)]
#[must_use]
pub fn new(engine: &Engine) -> Self {
Self {
status: if engine.debugger.is_some() {
DebuggerCommand::StepInto
} else {
DebuggerCommand::Continue
},
state: if let Some((ref init, _)) = engine.debugger {
init()
} else {
Dynamic::UNIT
},
break_points: Vec::new(),
#[cfg(not(feature = "no_function"))]
call_stack: Vec::new(),
}
}
/// Get a reference to the current state.
#[inline(always)]
#[must_use]
pub fn state(&self) -> &Dynamic {
&self.state
}
/// Get a mutable reference to the current state.
#[inline(always)]
#[must_use]
pub fn state_mut(&mut self) -> &mut Dynamic {
&mut self.state
}
/// Get the current call stack.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn call_stack(&self) -> &[CallStackFrame] {
&self.call_stack
}
/// Rewind the function call stack to a particular depth.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub(crate) fn rewind_call_stack(&mut self, len: usize) {
self.call_stack.truncate(len);
}
/// Add a new frame to the function call stack.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub(crate) fn push_call_stack_frame(
&mut self,
fn_name: impl Into<Identifier>,
args: crate::StaticVec<Dynamic>,
source: impl Into<Identifier>,
pos: Position,
) {
self.call_stack.push(CallStackFrame {
fn_name: fn_name.into(),
args,
source: source.into(),
pos,
});
}
/// Get the current status of this [`Debugger`].
#[inline(always)]
#[must_use]
pub fn status(&self) -> DebuggerCommand {
self.status
}
/// Get a mutable reference to the current status of this [`Debugger`].
#[inline(always)]
#[must_use]
pub fn status_mut(&mut self) -> &mut DebuggerCommand {
&mut self.status
}
/// Set the status of this [`Debugger`].
#[inline(always)]
pub fn reset_status(&mut self, status: Option<DebuggerCommand>) {
if let Some(cmd) = status {
self.status = cmd;
}
}
/// Does a particular [`AST` Node][ASTNode] trigger a break-point?
#[must_use]
pub fn is_break_point(&self, src: &str, node: ASTNode) -> bool {
let _src = src;
self.break_points()
.iter()
.filter(|&bp| bp.is_enabled())
.any(|bp| match bp {
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { pos, .. } if pos.is_none() => false,
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => {
node.position().line().unwrap_or(0) == pos.line().unwrap() && _src == source
}
#[cfg(not(feature = "no_position"))]
BreakPoint::AtPosition { source, pos, .. } => {
node.position() == *pos && _src == source
}
BreakPoint::AtFunctionName { name, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
x.name == *name
}
_ => false,
},
BreakPoint::AtFunctionCall { name, args, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, _)) | ASTNode::Stmt(Stmt::FnCall(x, _)) => {
x.args.len() == *args && x.name == *name
}
_ => false,
},
#[cfg(not(feature = "no_object"))]
BreakPoint::AtProperty { name, .. } => match node {
ASTNode::Expr(Expr::Property(x, _)) => x.2 == *name,
_ => false,
},
})
}
/// Get a slice of all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
pub fn break_points(&self) -> &[BreakPoint] {
&self.break_points
}
/// Get the underlying [`Vec`] holding all [`BreakPoint`]'s.
#[inline(always)]
#[must_use]
pub fn break_points_mut(&mut self) -> &mut Vec<BreakPoint> {
&mut self.break_points
}
}
impl Engine {
/// Run the debugger callback.
#[inline(always)]
pub(crate) fn run_debugger<'a>(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
level: usize,
) -> RhaiResultOf<()> {
if let Some(cmd) =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, node, level)?
{
global.debugger.status = cmd;
}
Ok(())
}
/// Run the debugger callback.
///
/// Returns `true` if the debugger needs to be reactivated at the end of the block, statement or
/// function call.
///
/// # Note
///
/// When the debugger callback return [`DebuggerCommand::StepOver`], the debugger if temporarily
/// disabled and `true` is returned.
///
/// It is up to the [`Engine`] to reactivate the debugger.
#[inline]
#[must_use]
pub(crate) fn run_debugger_with_reset<'a>(
&self,
scope: &mut Scope,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
node: impl Into<ASTNode<'a>>,
level: usize,
) -> RhaiResultOf<Option<DebuggerCommand>> {
if let Some((_, ref on_debugger)) = self.debugger {
let node = node.into();
// Skip transitive nodes
match node {
ASTNode::Expr(Expr::Stmt(_)) | ASTNode::Stmt(Stmt::Expr(_)) => return Ok(None),
_ => (),
}
let stop = match global.debugger.status {
DebuggerCommand::Continue => false,
DebuggerCommand::Next => matches!(node, ASTNode::Stmt(_)),
DebuggerCommand::StepInto | DebuggerCommand::StepOver => true,
};
if !stop && !global.debugger.is_break_point(&global.source, node) {
return Ok(None);
}
let source = global.source.clone();
let source = if source.is_empty() {
None
} else {
Some(source.as_str())
};
let mut context = crate::EvalContext {
engine: self,
scope,
global,
state,
lib,
this_ptr,
level,
};
let command = on_debugger(&mut context, node, source, node.position())?;
match command {
DebuggerCommand::Continue => {
global.debugger.status = DebuggerCommand::Continue;
Ok(None)
}
DebuggerCommand::Next => {
global.debugger.status = DebuggerCommand::Continue;
Ok(Some(DebuggerCommand::Next))
}
DebuggerCommand::StepInto => {
global.debugger.status = DebuggerCommand::StepInto;
Ok(None)
}
DebuggerCommand::StepOver => {
global.debugger.status = DebuggerCommand::Continue;
Ok(Some(DebuggerCommand::StepOver))
}
}
} else {
Ok(None)
}
}
}

View File

@ -24,7 +24,7 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 's, 'ps, 'b, 't, 'pt> {
pub(crate) level: usize, pub(crate) level: usize,
} }
impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> { impl<'x, 'px, 'm, 'pm, 'pt> EvalContext<'_, 'x, 'px, 'm, 'pm, '_, '_, '_, '_, 'pt> {
/// The current [`Engine`]. /// The current [`Engine`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
@ -46,13 +46,14 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> {
pub const fn scope(&self) -> &Scope<'px> { pub const fn scope(&self) -> &Scope<'px> {
self.scope self.scope
} }
/// Mutable reference to the current [`Scope`]. /// Get a mutable reference to the current [`Scope`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn scope_mut(&mut self) -> &mut &'x mut Scope<'px> { pub fn scope_mut(&mut self) -> &mut &'x mut Scope<'px> {
&mut self.scope &mut self.scope
} }
/// Get an iterator over the current set of modules imported via `import` statements. /// Get an iterator over the current set of modules imported via `import` statements,
/// in reverse order (i.e. modules imported last come first).
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
@ -61,12 +62,19 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> {
/// _(internals)_ The current [`GlobalRuntimeState`]. /// _(internals)_ The current [`GlobalRuntimeState`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn global_runtime_state(&self) -> &GlobalRuntimeState { pub const fn global_runtime_state(&self) -> &GlobalRuntimeState {
self.global self.global
} }
/// _(internals)_ Get a mutable reference to the current [`GlobalRuntimeState`].
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn global_runtime_state_mut(&mut self) -> &mut &'m mut GlobalRuntimeState<'pm> {
&mut self.global
}
/// Get an iterator over the namespaces containing definition of all script-defined functions. /// Get an iterator over the namespaces containing definition of all script-defined functions.
#[inline] #[inline]
pub fn iter_namespaces(&self) -> impl Iterator<Item = &Module> { pub fn iter_namespaces(&self) -> impl Iterator<Item = &Module> {

View File

@ -7,16 +7,17 @@ use std::marker::PhantomData;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// _(internals)_ A type that holds all the current states of the [`Engine`]. /// _(internals)_ A type that holds all the current states of the [`Engine`][crate::Engine].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EvalState<'a> { pub struct EvalState<'a> {
/// Force a [`Scope`] search by name. /// Force a [`Scope`][crate::Scope] search by name.
/// ///
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. /// Normally, access to variables are parsed with a relative offset into the
/// [`Scope`][crate::Scope] to avoid a lookup.
/// ///
/// In some situation, e.g. after running an `eval` statement, or after a custom syntax statement, /// In some situation, e.g. after running an `eval` statement, or after a custom syntax
/// subsequent offsets may become mis-aligned. /// statement, subsequent offsets may become mis-aligned.
/// ///
/// When that happens, this flag is turned on. /// When that happens, this flag is turned on.
pub always_search_scope: bool, pub always_search_scope: bool,

View File

@ -3,25 +3,23 @@
use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
use crate::ast::{Expr, FnCallExpr, OpAssignment}; use crate::ast::{Expr, FnCallExpr, OpAssignment};
use crate::engine::{KEYWORD_THIS, OP_CONCAT}; use crate::engine::{KEYWORD_THIS, OP_CONCAT};
use crate::module::Namespace;
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::{ use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, StaticVec, ERR,
};
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
impl Engine { impl Engine {
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
#[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn search_imports( pub(crate) fn search_imports(
&self, &self,
global: &GlobalRuntimeState, global: &GlobalRuntimeState,
state: &mut EvalState, state: &mut EvalState,
namespace: &Namespace, namespace: &crate::module::Namespace,
) -> Option<Shared<Module>> { ) -> Option<crate::Shared<Module>> {
let root = &namespace[0].name; let root = &namespace[0].name;
// Qualified - check if the root module is directly indexed // Qualified - check if the root module is directly indexed
@ -59,13 +57,16 @@ impl Engine {
} }
Expr::Variable(None, _var_pos, v) => match v.as_ref() { Expr::Variable(None, _var_pos, v) => match v.as_ref() {
// Normal variable access // Normal variable access
#[cfg(not(feature = "no_module"))]
(_, None, _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr), (_, None, _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr),
#[cfg(feature = "no_module")]
(_, (), _) => self.search_scope_only(scope, global, state, lib, this_ptr, expr),
// Qualified variable access // Qualified variable access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
(_, Some((namespace, hash_var)), var_name) => { (_, Some((namespace, hash_var)), var_name) => {
if let Some(module) = self.search_imports(global, state, namespace) {
// foo:bar::baz::VARIABLE // foo:bar::baz::VARIABLE
if let Some(module) = self.search_imports(global, state, namespace) {
return match module.get_qualified_var(*hash_var) { return match module.get_qualified_var(*hash_var) {
Ok(target) => { Ok(target) => {
let mut target = target.clone(); let mut target = target.clone();
@ -89,13 +90,13 @@ impl Engine {
}; };
} }
// global::VARIABLE
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL { if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL {
// global::VARIABLE if let Some(ref constants) = global.constants {
let global_constants = global.constants_mut(); if let Some(value) =
crate::func::locked_write(constants).get_mut(var_name)
if let Some(mut guard) = global_constants { {
if let Some(value) = guard.get_mut(var_name) {
let mut target: Target = value.clone().into(); let mut target: Target = value.clone().into();
// Module variables are constant // Module variables are constant
target.set_access_mode(AccessMode::ReadOnly); target.set_access_mode(AccessMode::ReadOnly);
@ -117,9 +118,6 @@ impl Engine {
Err(ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos).into()) Err(ERR::ErrorModuleNotFound(namespace.to_string(), namespace[0].pos).into())
} }
#[cfg(feature = "no_module")]
(_, Some((_, _)), _) => unreachable!("qualified access under no_module"),
}, },
_ => unreachable!("Expr::Variable expected but gets {:?}", expr), _ => unreachable!("Expr::Variable expected but gets {:?}", expr),
} }
@ -211,6 +209,7 @@ impl Engine {
) -> RhaiResult { ) -> RhaiResult {
let FnCallExpr { let FnCallExpr {
name, name,
#[cfg(not(feature = "no_module"))]
namespace, namespace,
capture_parent_scope: capture, capture_parent_scope: capture,
hashes, hashes,
@ -219,15 +218,17 @@ impl Engine {
.. ..
} = expr; } = expr;
#[cfg(not(feature = "no_module"))]
if let Some(namespace) = namespace.as_ref() { if let Some(namespace) = namespace.as_ref() {
// Qualified function call // Qualified function call
let hash = hashes.native; let hash = hashes.native;
self.make_qualified_function_call( return self.make_qualified_function_call(
scope, global, state, lib, this_ptr, namespace, name, args, constants, hash, pos, scope, global, state, lib, this_ptr, namespace, name, args, constants, hash, pos,
level, level,
) );
} else { }
// Normal function call // Normal function call
let (first_arg, args) = args.split_first().map_or_else( let (first_arg, args) = args.split_first().map_or_else(
|| (None, args.as_ref()), || (None, args.as_ref()),
@ -235,13 +236,19 @@ impl Engine {
); );
self.make_function_call( self.make_function_call(
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, pos,
pos, *capture, level, *capture, level,
) )
} }
}
/// Evaluate an expression. /// Evaluate an expression.
//
// # Implementation Notes
//
// Do not use the `?` operator within the main body as it makes this function return early,
// possibly by-passing important cleanup tasks at the end.
//
// Errors that are not recoverable, such as system errors or safety errors, can use `?`.
pub(crate) fn eval_expr( pub(crate) fn eval_expr(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -258,16 +265,29 @@ impl Engine {
// Function calls should account for a relatively larger portion of expressions because // Function calls should account for a relatively larger portion of expressions because
// binary operators are also function calls. // binary operators are also function calls.
if let Expr::FnCall(x, pos) = expr { if let Expr::FnCall(x, pos) = expr {
#[cfg(feature = "debugging")]
let reset_debugger =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?; self.inc_operations(&mut global.num_operations, expr.position())?;
return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level); let result =
self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level);
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
return result;
} }
// Then variable access. // Then variable access.
// We shouldn't do this for too many variants because, soon or later, the added comparisons // We shouldn't do this for too many variants because, soon or later, the added comparisons
// will cost more than the mis-predicted `match` branch. // will cost more than the mis-predicted `match` branch.
if let Expr::Variable(index, var_pos, x) = expr { if let Expr::Variable(index, var_pos, x) = expr {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, expr, level)?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?; self.inc_operations(&mut global.num_operations, expr.position())?;
@ -282,10 +302,14 @@ impl Engine {
}; };
} }
#[cfg(feature = "debugging")]
let reset_debugger =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, expr.position())?; self.inc_operations(&mut global.num_operations, expr.position())?;
match expr { let result = match expr {
// Constants // Constants
Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()), Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()),
Expr::IntegerConstant(x, _) => Ok((*x).into()), Expr::IntegerConstant(x, _) => Ok((*x).into()),
@ -299,47 +323,62 @@ impl Engine {
// `... ${...} ...` // `... ${...} ...`
Expr::InterpolatedString(x, pos) => { Expr::InterpolatedString(x, pos) => {
let mut pos = *pos; let mut pos = *pos;
let mut result: Dynamic = self.const_empty_string().into(); let mut concat: Dynamic = self.const_empty_string().into();
let mut result = Ok(Dynamic::UNIT);
for expr in x.iter() { for expr in x.iter() {
let item = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?; let item =
match self.eval_expr(scope, global, state, lib, this_ptr, expr, level) {
Ok(r) => r,
err => {
result = err;
break;
}
};
self.eval_op_assignment( if let Err(err) = self.eval_op_assignment(
global, global,
state, state,
lib, lib,
Some(OpAssignment::new(OP_CONCAT)), Some(OpAssignment::new(OP_CONCAT)),
pos, pos,
&mut (&mut result).into(), &mut (&mut concat).into(),
("", Position::NONE), ("", Position::NONE),
item, item,
) ) {
.map_err(|err| err.fill_position(expr.position()))?; result = Err(err.fill_position(expr.position()));
break;
}
pos = expr.position(); pos = expr.position();
} }
Ok(result) result.map(|_| concat)
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(x, _) => { Expr::Array(x, _) => {
let mut arr = Dynamic::from_array(crate::Array::with_capacity(x.len())); let mut arr = crate::Array::with_capacity(x.len());
let mut result = Ok(Dynamic::UNIT);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let mut sizes = (0, 0, 0); let mut sizes = (0, 0, 0);
for item_expr in x.iter() { for item_expr in x.iter() {
let value = self let value = match self
.eval_expr(scope, global, state, lib, this_ptr, item_expr, level)? .eval_expr(scope, global, state, lib, this_ptr, item_expr, level)
.flatten(); {
Ok(r) => r.flatten(),
err => {
result = err;
break;
}
};
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let val_sizes = Self::calc_data_sizes(&value, true); let val_sizes = Self::calc_data_sizes(&value, true);
arr.write_lock::<crate::Array>() arr.push(value);
.expect("`Array`")
.push(value);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if self.has_data_size_limit() { if self.has_data_size_limit() {
@ -352,29 +391,33 @@ impl Engine {
} }
} }
Ok(arr) result.map(|_| arr.into())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, _) => { Expr::Map(x, _) => {
let mut map = Dynamic::from_map(x.1.clone()); let mut map = x.1.clone();
let mut result = Ok(Dynamic::UNIT);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let mut sizes = (0, 0, 0); let mut sizes = (0, 0, 0);
for (crate::ast::Ident { name, .. }, value_expr) in x.0.iter() { for (crate::ast::Ident { name, .. }, value_expr) in x.0.iter() {
let key = name.as_str(); let key = name.as_str();
let value = self let value = match self
.eval_expr(scope, global, state, lib, this_ptr, value_expr, level)? .eval_expr(scope, global, state, lib, this_ptr, value_expr, level)
.flatten(); {
Ok(r) => r.flatten(),
err => {
result = err;
break;
}
};
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let val_sizes = Self::calc_data_sizes(&value, true); let val_sizes = Self::calc_data_sizes(&value, true);
*map.write_lock::<crate::Map>() *map.get_mut(key).unwrap() = value;
.expect("`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() {
@ -387,33 +430,53 @@ impl Engine {
} }
} }
Ok(map) result.map(|_| map.into())
} }
Expr::And(x, _) => { Expr::And(x, _) => {
Ok((self let lhs = self
.eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)? .eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)
.as_bool() .and_then(|v| {
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))? v.as_bool().map_err(|typ| {
&& // Short-circuit using && self.make_type_mismatch_err::<bool>(typ, x.lhs.position())
self })
.eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level)? });
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.rhs.position()))?) if let Ok(true) = lhs {
.into()) self.eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level)
.and_then(|v| {
v.as_bool()
.map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, x.rhs.position())
})
.map(Into::into)
})
} else {
lhs.map(Into::into)
}
} }
Expr::Or(x, _) => { Expr::Or(x, _) => {
Ok((self let lhs = self
.eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)? .eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)
.as_bool() .and_then(|v| {
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))? v.as_bool().map_err(|typ| {
|| // Short-circuit using || self.make_type_mismatch_err::<bool>(typ, x.lhs.position())
self })
.eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level)? });
.as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.rhs.position()))?) if let Ok(false) = lhs {
.into()) self.eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level)
.and_then(|v| {
v.as_bool()
.map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, x.rhs.position())
})
.map(Into::into)
})
} else {
lhs.map(Into::into)
}
} }
Expr::Custom(custom, pos) => { Expr::Custom(custom, pos) => {
@ -459,6 +522,11 @@ impl Engine {
} }
_ => unreachable!("expression cannot be evaluated: {:?}", expr), _ => unreachable!("expression cannot be evaluated: {:?}", expr),
} };
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
return result;
} }
} }

View File

@ -1,33 +1,30 @@
//! Global runtime state. //! Global runtime state.
use crate::func::{CallableFunction, IteratorFn}; use crate::{Engine, Identifier};
use crate::{Identifier, Module, Shared, StaticVec};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{fmt, marker::PhantomData};
any::TypeId,
fmt,
iter::{FromIterator, Rev, Zip},
marker::PhantomData,
};
/// _(internals)_ A stack of imported [modules][Module] plus mutable global runtime states. /// _(internals)_ Global runtime states.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
// //
// # Implementation Notes // # Implementation Notes
// //
// This implementation splits the module names from the shared modules to improve data locality. // This implementation for imported [modules][crate::Module] splits the module names from the shared
// Most usage will be looking up a particular key from the list and then getting the module that // modules to improve data locality. Most usage will be looking up a particular key from the list
// corresponds to that key. // 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.
// ///
// We cannot use Cow<str> here because `eval` may load a [module][Module] and /// Not available under `no_module`.
// the module name will live beyond the AST of the eval script text. #[cfg(not(feature = "no_module"))]
keys: StaticVec<Identifier>, keys: crate::StaticVec<Identifier>,
/// Stack of imported [modules][Module]. /// Stack of imported [modules][crate::Module].
modules: StaticVec<Shared<Module>>, ///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
modules: crate::StaticVec<crate::Shared<crate::Module>>,
/// Source of the current context. /// Source of the current context.
/// No source if the string is empty. /// No source if the string is empty.
pub source: Identifier, pub source: Identifier,
@ -38,33 +35,37 @@ pub struct GlobalRuntimeState<'a> {
/// Function call hashes to index getters and setters. /// Function call hashes to index getters and setters.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
fn_hash_indexing: (u64, u64), fn_hash_indexing: (u64, u64),
/// Embedded [module][Module] resolver. /// Embedded [crate::Module][crate::Module] resolver.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub embedded_module_resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>, pub embedded_module_resolver:
Option<crate::Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Cache of globally-defined constants. /// Cache of globally-defined constants.
///
/// Interior mutability is needed because it is shared in order to aid in cloning.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: pub(crate) constants: Option<
Option<Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>>, crate::Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>,
>,
/// Debugging interface.
#[cfg(feature = "debugging")]
pub debugger: super::Debugger,
/// Take care of the lifetime parameter. /// Take care of the lifetime parameter.
dummy: PhantomData<&'a ()>, dummy: PhantomData<&'a ()>,
} }
impl Default for GlobalRuntimeState<'_> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl GlobalRuntimeState<'_> { impl GlobalRuntimeState<'_> {
/// Create a new [`GlobalRuntimeState`]. /// Create a new [`GlobalRuntimeState`] based on an [`Engine`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new(engine: &Engine) -> Self {
let _engine = engine;
Self { Self {
keys: StaticVec::new_const(), #[cfg(not(feature = "no_module"))]
modules: StaticVec::new_const(), keys: crate::StaticVec::new_const(),
#[cfg(not(feature = "no_module"))]
modules: crate::StaticVec::new_const(),
source: Identifier::new_const(), source: Identifier::new_const(),
num_operations: 0, num_operations: 0,
num_modules_loaded: 0, num_modules_loaded: 0,
@ -75,29 +76,46 @@ impl GlobalRuntimeState<'_> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: None, constants: None,
#[cfg(feature = "debugging")]
debugger: crate::eval::Debugger::new(_engine),
dummy: PhantomData::default(), dummy: PhantomData::default(),
} }
} }
/// Get the length of the stack of globally-imported [modules][Module]. /// Get the length of the stack of globally-imported [modules][crate::Module].
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn num_imports(&self) -> usize { pub fn num_imports(&self) -> usize {
self.keys.len() self.keys.len()
} }
/// Get the globally-imported [module][Module] at a particular index. /// Get the globally-imported [crate::Module][crate::Module] at a particular index.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_shared_import(&self, index: usize) -> Option<Shared<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 [module][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`.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn get_shared_import_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> { pub(crate) fn get_shared_import_mut(
&mut self,
index: usize,
) -> Option<&mut crate::Shared<crate::Module>> {
self.modules.get_mut(index) self.modules.get_mut(index)
} }
/// Get the index of a globally-imported [module][Module] by name. /// Get the index of a globally-imported [crate::Module][crate::Module] by name.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn find_import(&self, name: &str) -> Option<usize> { pub fn find_import(&self, name: &str) -> Option<usize> {
@ -111,101 +129,121 @@ impl GlobalRuntimeState<'_> {
} }
}) })
} }
/// Push an imported [module][Module] onto the stack. /// Push an imported [crate::Module][crate::Module] onto the stack.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub fn push_import(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) { pub fn push_import(
&mut self,
name: impl Into<Identifier>,
module: impl Into<crate::Shared<crate::Module>>,
) {
self.keys.push(name.into()); self.keys.push(name.into());
self.modules.push(module.into()); self.modules.push(module.into());
} }
/// Truncate the stack of globally-imported [modules][Module] to a particular length. /// Truncate the stack of globally-imported [modules][crate::Module] to a particular length.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub fn truncate_imports(&mut self, size: usize) { pub fn truncate_imports(&mut self, size: usize) {
self.keys.truncate(size); self.keys.truncate(size);
self.modules.truncate(size); self.modules.truncate(size);
} }
/// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> {
self.keys self.keys
.iter() .iter()
.rev() .rev()
.zip(self.modules.iter().rev()) .zip(self.modules.iter().rev())
.map(|(name, module)| (name.as_str(), module.as_ref())) .map(|(name, module)| (name.as_str(), module.as_ref()))
} }
/// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub(crate) fn iter_imports_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> { pub(crate) fn iter_imports_raw(
&self,
) -> impl Iterator<Item = (&Identifier, &crate::Shared<crate::Module>)> {
self.keys.iter().rev().zip(self.modules.iter().rev()) self.keys.iter().rev().zip(self.modules.iter().rev())
} }
/// Get an iterator to the stack of globally-imported [modules][Module] in forward order. /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub(crate) fn scan_imports_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> { pub fn scan_imports_raw(
&self,
) -> 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][Module]? /// Does the specified function hash key exist in the stack of globally-imported [modules][crate::Module]?
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
#[must_use] #[must_use]
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][Module]. /// Get the specified function via its hash key from the stack of globally-imported [modules][crate::Module].
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn get_qualified_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&str>)> { pub fn get_qualified_fn(
&self,
hash: u64,
) -> Option<(&crate::func::CallableFunction, Option<&str>)> {
self.modules self.modules
.iter() .iter()
.rev() .rev()
.find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id()))) .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id())))
} }
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of
/// globally-imported [modules][Module]? /// globally-imported [modules][crate::Module]?
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn contains_iter(&self, id: TypeId) -> bool { pub fn contains_iter(&self, id: std::any::TypeId) -> bool {
self.modules.iter().any(|m| m.contains_qualified_iter(id)) self.modules.iter().any(|m| m.contains_qualified_iter(id))
} }
/// Get the specified [`TypeId`][std::any::TypeId] iterator from the stack of globally-imported /// Get the specified [`TypeId`][std::any::TypeId] iterator from the stack of globally-imported
/// [modules][Module]. /// [modules][crate::Module].
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub fn get_iter(&self, id: std::any::TypeId) -> Option<&crate::func::IteratorFn> {
self.modules self.modules
.iter() .iter()
.rev() .rev()
.find_map(|m| m.get_qualified_iter(id)) .find_map(|m| m.get_qualified_iter(id))
} }
/// Get a mutable reference to the cache of globally-defined constants. /// Get the current source.
#[cfg(not(feature = "no_module"))] #[inline]
#[cfg(not(feature = "no_function"))]
#[must_use] #[must_use]
pub(crate) fn constants_mut<'a>( pub fn source(&self) -> Option<&str> {
&'a mut self, match self.source.as_str() {
) -> Option< "" => None,
impl std::ops::DerefMut<Target = std::collections::BTreeMap<Identifier, crate::Dynamic>> + 'a, s => Some(s),
> {
if let Some(ref global_constants) = self.constants {
Some(crate::func::native::shared_write_lock(global_constants))
} else {
None
} }
} }
/// Set a constant into the cache of globally-defined constants.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
pub(crate) fn set_constant(&mut self, name: impl Into<Identifier>, value: crate::Dynamic) {
if self.constants.is_none() {
let dict: crate::Locked<_> = std::collections::BTreeMap::new().into();
self.constants = Some(dict.into());
}
crate::func::native::shared_write_lock(self.constants.as_mut().expect("`Some`"))
.insert(name.into(), value);
}
/// Get the pre-calculated index getter hash. /// Get the pre-calculated index getter hash.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[must_use] #[must_use]
@ -234,10 +272,13 @@ impl GlobalRuntimeState<'_> {
} }
} }
#[cfg(not(feature = "no_module"))]
impl IntoIterator for GlobalRuntimeState<'_> { impl IntoIterator for GlobalRuntimeState<'_> {
type Item = (Identifier, Shared<Module>); type Item = (Identifier, crate::Shared<crate::Module>);
type IntoIter = type IntoIter = std::iter::Zip<
Zip<Rev<smallvec::IntoIter<[Identifier; 3]>>, Rev<smallvec::IntoIter<[Shared<Module>; 3]>>>; std::iter::Rev<smallvec::IntoIter<[Identifier; 3]>>,
std::iter::Rev<smallvec::IntoIter<[crate::Shared<crate::Module>; 3]>>,
>;
#[inline] #[inline]
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
@ -248,22 +289,16 @@ impl IntoIterator for GlobalRuntimeState<'_> {
} }
} }
impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for GlobalRuntimeState<'_> { #[cfg(not(feature = "no_module"))]
#[inline] impl<K: Into<Identifier>, M: Into<crate::Shared<crate::Module>>> Extend<(K, M)>
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self { for GlobalRuntimeState<'_>
let mut lib = Self::new(); {
lib.extend(iter);
lib
}
}
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(k, m)| { for (k, m) in iter {
self.keys.push(k.into()); self.keys.push(k.into());
self.modules.push(m.into()); self.modules.push(m.into());
}) }
} }
} }
@ -272,8 +307,10 @@ impl fmt::Debug for GlobalRuntimeState<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("GlobalRuntimeState"); let mut f = f.debug_struct("GlobalRuntimeState");
f.field("imports", &self.keys.iter().zip(self.modules.iter())) #[cfg(not(feature = "no_module"))]
.field("source", &self.source) f.field("imports", &self.keys.iter().zip(self.modules.iter()));
f.field("source", &self.source)
.field("num_operations", &self.num_operations) .field("num_operations", &self.num_operations)
.field("num_modules_loaded", &self.num_modules_loaded); .field("num_modules_loaded", &self.num_modules_loaded);

View File

@ -1,5 +1,6 @@
mod chaining; mod chaining;
mod data_check; mod data_check;
mod debugger;
mod eval_context; mod eval_context;
mod eval_state; mod eval_state;
mod expr; mod expr;
@ -9,6 +10,11 @@ mod target;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
pub use chaining::{ChainArgument, ChainType}; pub use chaining::{ChainArgument, ChainType};
#[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))]
pub use debugger::CallStackFrame;
#[cfg(feature = "debugging")]
pub use debugger::{BreakPoint, Debugger, DebuggerCommand, OnDebuggerCallback, OnDebuggingInit};
pub use eval_context::EvalContext; pub use eval_context::EvalContext;
pub use eval_state::EvalState; pub use eval_state::EvalState;
pub use global_state::GlobalRuntimeState; pub use global_state::GlobalRuntimeState;

View File

@ -1,7 +1,9 @@
//! Module defining functions for evaluating a statement. //! Module defining functions for evaluating a statement.
use super::{EvalState, GlobalRuntimeState, Target}; use super::{EvalState, GlobalRuntimeState, Target};
use crate::ast::{Expr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::ast::{
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
};
use crate::func::get_hasher; use crate::func::get_hasher;
use crate::types::dynamic::{AccessMode, Union}; use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT}; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT};
@ -11,6 +13,13 @@ use std::prelude::v1::*;
impl Engine { impl Engine {
/// Evaluate a statements block. /// Evaluate a statements block.
//
// # Implementation Notes
//
// Do not use the `?` operator within the main body as it makes this function return early,
// possibly by-passing important cleanup tasks at the end.
//
// Errors that are not recoverable, such as system errors or safety errors, can use `?`.
pub(crate) fn eval_stmt_block( pub(crate) fn eval_stmt_block(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -28,7 +37,8 @@ impl Engine {
let orig_always_search_scope = state.always_search_scope; let orig_always_search_scope = state.always_search_scope;
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let orig_mods_len = global.num_imports(); #[cfg(not(feature = "no_module"))]
let orig_imports_len = global.num_imports();
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
if restore_orig_state { if restore_orig_state {
@ -38,7 +48,8 @@ impl Engine {
let mut result = Ok(Dynamic::UNIT); let mut result = Ok(Dynamic::UNIT);
for stmt in statements { for stmt in statements {
let _mods_len = global.num_imports(); #[cfg(not(feature = "no_module"))]
let imports_len = global.num_imports();
result = self.eval_stmt( result = self.eval_stmt(
scope, scope,
@ -61,7 +72,7 @@ impl Engine {
// Without global functions, the extra modules never affect function resolution. // Without global functions, the extra modules never affect function resolution.
if global if global
.scan_imports_raw() .scan_imports_raw()
.skip(_mods_len) .skip(imports_len)
.any(|(_, m)| m.contains_indexed_global_functions()) .any(|(_, m)| m.contains_indexed_global_functions())
{ {
if state.fn_resolution_caches_len() > orig_fn_resolution_caches_len { if state.fn_resolution_caches_len() > orig_fn_resolution_caches_len {
@ -86,7 +97,8 @@ impl Engine {
if restore_orig_state { if restore_orig_state {
scope.rewind(orig_scope_len); scope.rewind(orig_scope_len);
state.scope_level -= 1; state.scope_level -= 1;
global.truncate_imports(orig_mods_len); #[cfg(not(feature = "no_module"))]
global.truncate_imports(orig_imports_len);
// The impact of new local variables goes away at the end of a block // The impact of new local variables goes away at the end of a block
// because any new variables introduced will go out of scope // because any new variables introduced will go out of scope
@ -119,6 +131,7 @@ impl Engine {
if let Some(OpAssignment { if let Some(OpAssignment {
hash_op_assign, hash_op_assign,
hash_op, hash_op,
op_assign,
op, op,
}) = op_info }) = op_info
{ {
@ -140,21 +153,23 @@ impl Engine {
let hash = hash_op_assign; let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_val]; let args = &mut [lhs_ptr_inner, &mut new_val];
match self.call_native_fn(global, state, lib, op, hash, args, true, true, op_pos) { match self.call_native_fn(
global, state, lib, op_assign, hash, args, true, true, op_pos,
) {
Ok(_) => { Ok(_) => {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.check_data_size(&mut args[0], root.1)?; self.check_data_size(&args[0], root.1)?;
} }
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) => Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, _) if f.starts_with(op_assign)) =>
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
// Run function
let (value, _) = self.call_native_fn( let (value, _) = self.call_native_fn(
global, state, lib, op, hash_op, args, true, false, op_pos, global, state, lib, op, hash_op, args, true, false, op_pos,
)?; )?;
#[cfg(not(feature = "unchecked"))]
self.check_data_size(&value, root.1)?;
*args[0] = value.flatten(); *args[0] = value.flatten();
} }
Err(err) => return Err(err), Err(err) => return Err(err),
@ -168,11 +183,13 @@ impl Engine {
} }
/// Evaluate a statement. /// Evaluate a statement.
/// //
/// # Safety // # Implementation Notes
/// //
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via // Do not use the `?` operator within the main body as it makes this function return early,
/// direct lifetime casting. // possibly by-passing important cleanup tasks at the end.
//
// Errors that are not recoverable, such as system errors or safety errors, can use `?`.
pub(crate) fn eval_stmt( pub(crate) fn eval_stmt(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -184,6 +201,10 @@ impl Engine {
rewind_scope: bool, rewind_scope: bool,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(feature = "debugging")]
let reset_debugger =
self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?;
// Coded this way for better branch prediction. // Coded this way for better branch prediction.
// Popular branches are lifted out of the `match` statement into their own branches. // Popular branches are lifted out of the `match` statement into their own branches.
@ -192,7 +213,13 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, stmt.position())?; self.inc_operations(&mut global.num_operations, stmt.position())?;
return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level); let result =
self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level);
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
return result;
} }
// Then assignments. // Then assignments.
@ -202,18 +229,26 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, stmt.position())?; self.inc_operations(&mut global.num_operations, stmt.position())?;
return if x.0.is_variable_access(false) { let result = if x.1.lhs.is_variable_access(false) {
let (lhs_expr, op_info, rhs_expr) = x.as_ref(); let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
let rhs_val = self
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)?
.flatten();
let (mut lhs_ptr, pos) =
self.search_namespace(scope, global, state, lib, this_ptr, lhs_expr)?;
let var_name = lhs_expr.get_variable_name(false).expect("`Expr::Variable`"); let rhs_result = self
.eval_expr(scope, global, state, lib, this_ptr, rhs, level)
.map(Dynamic::flatten);
if let Ok(rhs_val) = rhs_result {
let search_result =
self.search_namespace(scope, global, state, lib, this_ptr, lhs);
if let Ok(search_val) = search_result {
let (mut lhs_ptr, pos) = search_val;
let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`");
if !lhs_ptr.is_ref() { if !lhs_ptr.is_ref() {
return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()); return Err(
ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()
);
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -229,54 +264,68 @@ impl Engine {
(var_name, pos), (var_name, pos),
rhs_val, rhs_val,
) )
.map_err(|err| err.fill_position(rhs_expr.position()))?; .map_err(|err| err.fill_position(rhs.position()))
.map(|_| Dynamic::UNIT)
Ok(Dynamic::UNIT)
} else { } else {
let (lhs_expr, op_info, rhs_expr) = x.as_ref(); search_result.map(|_| Dynamic::UNIT)
let rhs_val = self }
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)? } else {
.flatten(); rhs_result
let _new_val = Some(((rhs_val, rhs_expr.position()), (*op_info, *op_pos))); }
} else {
let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
let rhs_result = self
.eval_expr(scope, global, state, lib, this_ptr, rhs, level)
.map(Dynamic::flatten);
if let Ok(rhs_val) = rhs_result {
let _new_val = Some(((rhs_val, rhs.position()), (*op_info, *op_pos)));
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr { match lhs {
// name op= rhs (handled above) // name op= rhs (handled above)
Expr::Variable(_, _, _) => { Expr::Variable(_, _, _) => {
unreachable!("Expr::Variable case is already handled") unreachable!("Expr::Variable case is already handled")
} }
// idx_lhs[idx_expr] op= rhs // idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(_, _, _) => { Expr::Index(_, _, _) => self
self.eval_dot_index_chain( .eval_dot_index_chain(
scope, global, state, lib, this_ptr, lhs_expr, level, _new_val, scope, global, state, lib, this_ptr, lhs, level, _new_val,
)?; )
Ok(Dynamic::UNIT) .map(|_| Dynamic::UNIT),
}
// dot_lhs.dot_rhs op= rhs // dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(_, _, _) => { Expr::Dot(_, _, _) => self
self.eval_dot_index_chain( .eval_dot_index_chain(
scope, global, state, lib, this_ptr, lhs_expr, level, _new_val, scope, global, state, lib, this_ptr, lhs, level, _new_val,
)?; )
Ok(Dynamic::UNIT) .map(|_| Dynamic::UNIT),
_ => unreachable!("cannot assign to expression: {:?}", lhs),
} }
_ => unreachable!("cannot assign to expression: {:?}", lhs_expr), } else {
rhs_result
} }
}; };
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
return result;
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, stmt.position())?; self.inc_operations(&mut global.num_operations, stmt.position())?;
match stmt { let result = match stmt {
// No-op // No-op
Stmt::Noop(_) => Ok(Dynamic::UNIT), Stmt::Noop(_) => Ok(Dynamic::UNIT),
// Expression as statement // Expression as statement
Stmt::Expr(expr) => Ok(self Stmt::Expr(expr) => self
.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.flatten()), .map(Dynamic::flatten),
// Block scope // Block scope
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT), Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
@ -287,92 +336,125 @@ impl Engine {
// If statement // If statement
Stmt::If(expr, x, _) => { Stmt::If(expr, x, _) => {
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)
.as_bool() .and_then(|v| {
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?; v.as_bool().map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, expr.position())
})
});
if guard_val { match guard_val {
Ok(true) => {
if !x.0.is_empty() { if !x.0.is_empty() {
self.eval_stmt_block(scope, global, state, lib, this_ptr, &x.0, true, level) self.eval_stmt_block(
scope, global, state, lib, this_ptr, &x.0, true, level,
)
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} else { }
Ok(false) => {
if !x.1.is_empty() { if !x.1.is_empty() {
self.eval_stmt_block(scope, global, state, lib, this_ptr, &x.1, true, level) self.eval_stmt_block(
scope, global, state, lib, this_ptr, &x.1, true, level,
)
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} }
err => err.map(Into::into),
}
} }
// Switch statement // Switch statement
Stmt::Switch(match_expr, x, _) => { Stmt::Switch(match_expr, x, _) => {
let (table, def_stmt, ranges) = x.as_ref(); let SwitchCases {
cases,
def_case,
ranges,
} = x.as_ref();
let value = let value_result =
self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level)?; self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level);
let stmt_block = if value.is_hashable() { if let Ok(value) = value_result {
let stmt_block_result = if value.is_hashable() {
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
// First check hashes // First check hashes
if let Some(t) = table.get(&hash) { if let Some(t) = cases.get(&hash) {
if let Some(ref c) = t.0 { let cond_result = t
if self .condition
.eval_expr(scope, global, state, lib, this_ptr, &c, level)? .as_ref()
.as_bool() .map(|cond| {
.map_err(|typ| { self.eval_expr(scope, global, state, lib, this_ptr, cond, level)
self.make_type_mismatch_err::<bool>(typ, c.position()) .and_then(|v| {
})? v.as_bool().map_err(|typ| {
{ self.make_type_mismatch_err::<bool>(
Some(&t.1) typ,
} else { cond.position(),
None )
} })
} else { })
Some(&t.1) })
.unwrap_or(Ok(true));
match cond_result {
Ok(true) => Ok(Some(&t.statements)),
Ok(false) => Ok(None),
_ => cond_result.map(|_| None),
} }
} else if value.is::<INT>() && !ranges.is_empty() { } else if value.is::<INT>() && !ranges.is_empty() {
// Then check integer ranges // Then check integer ranges
let value = value.as_int().expect("`INT`"); let value = value.as_int().expect("`INT`");
let mut result = None; let mut result = Ok(None);
for (_, _, _, condition, stmt_block) in for (_, _, _, block) in
ranges.iter().filter(|&&(start, end, inclusive, _, _)| { ranges.iter().filter(|&&(start, end, inclusive, _)| {
(!inclusive && (start..end).contains(&value)) (!inclusive && (start..end).contains(&value))
|| (inclusive && (start..=end).contains(&value)) || (inclusive && (start..=end).contains(&value))
}) })
{ {
if let Some(c) = condition { let cond_result = block
if !self .condition
.eval_expr(scope, global, state, lib, this_ptr, &c, level)? .as_ref()
.as_bool() .map(|cond| {
.map_err(|typ| { self.eval_expr(
self.make_type_mismatch_err::<bool>(typ, c.position()) scope, global, state, lib, this_ptr, cond, level,
})? )
{ .and_then(|v| {
continue; v.as_bool().map_err(|typ| {
} self.make_type_mismatch_err::<bool>(
typ,
cond.position(),
)
})
})
})
.unwrap_or(Ok(true));
match cond_result {
Ok(true) => result = Ok(Some(&block.statements)),
Ok(false) => continue,
_ => result = cond_result.map(|_| None),
} }
result = Some(stmt_block);
break; break;
} }
result result
} else { } else {
// Nothing matches // Nothing matches
None Ok(None)
} }
} else { } else {
// Non-hashable // Non-hashable
None Ok(None)
}; };
if let Some(statements) = stmt_block { if let Ok(Some(statements)) = stmt_block_result {
if !statements.is_empty() { if !statements.is_empty() {
self.eval_stmt_block( self.eval_stmt_block(
scope, global, state, lib, this_ptr, statements, true, level, scope, global, state, lib, this_ptr, statements, true, level,
@ -380,15 +462,20 @@ impl Engine {
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} else { } else if let Ok(None) = stmt_block_result {
// Default match clause // Default match clause
if !def_stmt.is_empty() { if !def_case.is_empty() {
self.eval_stmt_block( self.eval_stmt_block(
scope, global, state, lib, this_ptr, def_stmt, true, level, scope, global, state, lib, this_ptr, def_case, true, level,
) )
} else { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} else {
stmt_block_result.map(|_| Dynamic::UNIT)
}
} else {
value_result
} }
} }
@ -401,8 +488,8 @@ impl Engine {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
ERR::LoopBreak(false, _) => (), ERR::LoopBreak(false, _) => (),
ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT), ERR::LoopBreak(true, _) => break Ok(Dynamic::UNIT),
_ => return Err(err), _ => break Err(err),
}, },
} }
} else { } else {
@ -414,25 +501,30 @@ impl Engine {
// While loop // While loop
Stmt::While(expr, body, _) => loop { Stmt::While(expr, body, _) => loop {
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)
.as_bool() .and_then(|v| {
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?; v.as_bool().map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, expr.position())
})
});
if !condition { match condition {
return Ok(Dynamic::UNIT); Ok(false) => break Ok(Dynamic::UNIT),
} Ok(true) if body.is_empty() => (),
if !body.is_empty() { Ok(true) => {
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)
{ {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
ERR::LoopBreak(false, _) => (), ERR::LoopBreak(false, _) => (),
ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT), ERR::LoopBreak(true, _) => break Ok(Dynamic::UNIT),
_ => return Err(err), _ => break Err(err),
}, },
} }
} }
err => break err.map(|_| Dynamic::UNIT),
}
}, },
// Do loop // Do loop
@ -446,28 +538,36 @@ impl Engine {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
ERR::LoopBreak(false, _) => continue, ERR::LoopBreak(false, _) => continue,
ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT), ERR::LoopBreak(true, _) => break Ok(Dynamic::UNIT),
_ => return Err(err), _ => break Err(err),
}, },
} }
} }
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)
.as_bool() .and_then(|v| {
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?; v.as_bool().map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, expr.position())
})
});
if condition ^ is_while { match condition {
return Ok(Dynamic::UNIT); Ok(condition) if condition ^ is_while => break Ok(Dynamic::UNIT),
Ok(_) => (),
err => break err.map(|_| Dynamic::UNIT),
} }
}, },
// For loop // For loop
Stmt::For(expr, x, _) => { Stmt::For(expr, x, _) => {
let (Ident { name: var_name, .. }, counter, statements) = x.as_ref(); let (Ident { name: var_name, .. }, counter, statements) = x.as_ref();
let iter_obj = self
.eval_expr(scope, global, state, lib, this_ptr, expr, level)? let iter_result = self
.flatten(); .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten);
if let Ok(iter_obj) = iter_result {
let iter_type = iter_obj.type_id(); let iter_type = iter_obj.type_id();
// lib should only contain scripts, so technically they cannot have iterators // lib should only contain scripts, so technically they cannot have iterators
@ -480,9 +580,10 @@ impl Engine {
let func = self let func = self
.global_modules .global_modules
.iter() .iter()
.find_map(|m| m.get_iter(iter_type)) .find_map(|m| m.get_iter(iter_type));
.or_else(|| global.get_iter(iter_type))
.or_else(|| { #[cfg(not(feature = "no_module"))]
let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| {
self.global_sub_modules self.global_sub_modules
.values() .values()
.find_map(|m| m.get_qualified_iter(iter_type)) .find_map(|m| m.get_qualified_iter(iter_type))
@ -550,8 +651,8 @@ impl Engine {
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if let Err(err) = if let Err(err) = self
self.inc_operations(&mut global.num_operations, statements.position()) .inc_operations(&mut global.num_operations, statements.position())
{ {
loop_result = Err(err); loop_result = Err(err);
break; break;
@ -584,19 +685,26 @@ impl Engine {
} else { } else {
Err(ERR::ErrorFor(expr.position()).into()) Err(ERR::ErrorFor(expr.position()).into())
} }
} else {
iter_result
}
} }
// Continue/Break statement // Continue/Break statement
Stmt::BreakLoop(options, pos) => { Stmt::BreakLoop(options, pos) => {
Err(ERR::LoopBreak(options.contains(AST_OPTION_BREAK_OUT), *pos).into()) Err(ERR::LoopBreak(options.contains(AST_OPTION_BREAK), *pos).into())
} }
// Try/Catch statement // Try/Catch statement
Stmt::TryCatch(x, _) => { Stmt::TryCatch(x, _) => {
let (try_stmt, err_var_name, catch_stmt) = x.as_ref(); let TryCatchBlock {
try_block,
catch_var,
catch_block,
} = x.as_ref();
let result = self let result = self
.eval_stmt_block(scope, global, state, lib, this_ptr, try_stmt, true, level) .eval_stmt_block(scope, global, state, lib, this_ptr, try_block, true, level)
.map(|_| Dynamic::UNIT); .map(|_| Dynamic::UNIT);
match result { match result {
@ -623,17 +731,15 @@ impl Engine {
err_map.insert("source".into(), global.source.clone().into()); err_map.insert("source".into(), global.source.clone().into());
} }
if err_pos.is_none() { if !err_pos.is_none() {
// No position info err_map.insert(
} else { "line".into(),
let line = err_pos.line().unwrap() as INT; (err_pos.line().unwrap() as INT).into(),
let position = if err_pos.is_beginning_of_line() { );
0 err_map.insert(
} else { "position".into(),
err_pos.position().unwrap() (err_pos.position().unwrap_or(0) as INT).into(),
} as INT; );
err_map.insert("line".into(), line.into());
err_map.insert("position".into(), position.into());
} }
err.dump_fields(&mut err_map); err.dump_fields(&mut err_map);
@ -643,12 +749,19 @@ impl Engine {
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
err_var_name catch_var
.as_ref() .as_ref()
.map(|Ident { name, .. }| scope.push(name.clone(), err_value)); .map(|Ident { name, .. }| scope.push(name.clone(), err_value));
let result = self.eval_stmt_block( let result = self.eval_stmt_block(
scope, global, state, lib, this_ptr, catch_stmt, true, level, scope,
global,
state,
lib,
this_ptr,
catch_block,
true,
level,
); );
scope.rewind(orig_scope_len); scope.rewind(orig_scope_len);
@ -669,27 +782,19 @@ impl Engine {
} }
// Throw value // Throw value
Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK_OUT) => { Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK) => self
Err(ERR::ErrorRuntime( .eval_expr(scope, global, state, lib, this_ptr, expr, level)
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
.flatten(),
*pos,
)
.into())
}
// Empty throw // Empty throw
Stmt::Return(options, None, pos) if options.contains(AST_OPTION_BREAK_OUT) => { Stmt::Return(options, None, 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) => Err(ERR::Return( Stmt::Return(_, Some(expr), pos) => self
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)? .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.flatten(), .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())),
*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()),
@ -702,12 +807,13 @@ impl Engine {
} else { } else {
AccessMode::ReadWrite AccessMode::ReadWrite
}; };
let export = options.contains(AST_OPTION_PUBLIC); let export = options.contains(AST_OPTION_EXPORTED);
let value = 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)
.flatten(); .map(Dynamic::flatten);
if let Ok(value) = value_result {
let _alias = if !rewind_scope { let _alias = if !rewind_scope {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -715,8 +821,13 @@ impl Engine {
&& entry_type == AccessMode::ReadOnly && entry_type == AccessMode::ReadOnly
&& lib.iter().any(|&m| !m.is_empty()) && lib.iter().any(|&m| !m.is_empty())
{ {
// Add a global constant if at top level and there are functions if global.constants.is_none() {
global.set_constant(var_name.clone(), value.clone()); global.constants = Some(crate::Shared::new(crate::Locked::new(
std::collections::BTreeMap::new(),
)));
}
crate::func::locked_write(global.constants.as_ref().unwrap())
.insert(var_name.clone(), value.clone());
} }
if export { if export {
@ -738,6 +849,9 @@ impl Engine {
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} else {
value_result
}
} }
// Import statement // Import statement
@ -749,34 +863,40 @@ impl Engine {
return Err(ERR::ErrorTooManyModules(*_pos).into()); return Err(ERR::ErrorTooManyModules(*_pos).into());
} }
if let Some(path) = self let path_result = self
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)? .eval_expr(scope, global, state, lib, this_ptr, &expr, level)
.try_cast::<crate::ImmutableString>() .and_then(|v| {
{ v.try_cast::<crate::ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<crate::ImmutableString>(
"",
expr.position(),
)
})
});
if let Ok(path) = path_result {
use crate::ModuleResolver; use crate::ModuleResolver;
let source = match global.source.as_str() {
"" => None,
s => Some(s),
};
let path_pos = expr.position(); let path_pos = expr.position();
let module = global let resolver = global.embedded_module_resolver.clone();
.embedded_module_resolver
let module_result = resolver
.as_ref() .as_ref()
.and_then(|r| match r.resolve(self, source, &path, path_pos) { .and_then(|r| match r.resolve_raw(self, global, &path, path_pos) {
Err(err) if matches!(*err, ERR::ErrorModuleNotFound(_, _)) => None, Err(err) if matches!(*err, ERR::ErrorModuleNotFound(_, _)) => None,
result => Some(result), result => Some(result),
}) })
.or_else(|| { .or_else(|| {
self.module_resolver self.module_resolver
.as_ref() .as_ref()
.map(|r| r.resolve(self, source, &path, path_pos)) .map(|r| r.resolve_raw(self, global, &path, path_pos))
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())
})?; });
if let Ok(module) = module_result {
if let Some(name) = export.as_ref().map(|x| x.name.clone()) { if let Some(name) = export.as_ref().map(|x| x.name.clone()) {
if !module.is_indexed() { if !module.is_indexed() {
// Index the module (making a clone copy if necessary) if it is not indexed // Index the module (making a clone copy if necessary) if it is not indexed
@ -792,28 +912,27 @@ impl Engine {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} else { } else {
Err(self.make_type_mismatch_err::<crate::ImmutableString>("", expr.position())) module_result.map(|_| Dynamic::UNIT)
}
} else {
path_result.map(|_| Dynamic::UNIT)
} }
} }
// Export statement // Export statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(list, _) => { Stmt::Export(x, _) => {
list.iter().try_for_each( let (Ident { name, pos, .. }, Ident { name: alias, .. }) = x.as_ref();
|(Ident { name, pos, .. }, Ident { name: rename, .. })| {
// Mark scope variables as public // Mark scope variables as public
if let Some((index, _)) = scope.get_index(name) { if let Some((index, _)) = scope.get_index(name) {
scope.add_entry_alias( scope.add_entry_alias(
index, index,
if rename.is_empty() { name } else { rename }.clone(), if alias.is_empty() { name } else { alias }.clone(),
); );
Ok(()) as RhaiResultOf<_> Ok(Dynamic::UNIT)
} else { } else {
Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into())
} }
},
)?;
Ok(Dynamic::UNIT)
} }
// Share statement // Share statement
@ -831,6 +950,11 @@ impl Engine {
} }
_ => unreachable!("statement cannot be evaluated: {:?}", stmt), _ => unreachable!("statement cannot be evaluated: {:?}", stmt),
} };
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
return result;
} }
} }

View File

@ -10,8 +10,6 @@ use crate::engine::{
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::eval::{EvalState, GlobalRuntimeState}; use crate::eval::{EvalState, GlobalRuntimeState};
use crate::module::Namespace;
use crate::tokenizer::Token;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr,
Identifier, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, Identifier, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR,
@ -150,18 +148,26 @@ impl Engine {
#[must_use] #[must_use]
fn gen_call_signature( fn gen_call_signature(
&self, &self,
namespace: Option<&Namespace>, #[cfg(not(feature = "no_module"))] namespace: Option<&crate::module::Namespace>,
fn_name: &str, fn_name: &str,
args: &[&mut Dynamic], args: &[&mut Dynamic],
) -> String { ) -> String {
format!( #[cfg(not(feature = "no_module"))]
"{}{}{} ({})", let (ns, sep) = (
namespace.map_or_else(|| String::new(), |ns| ns.to_string()), namespace.map_or_else(|| String::new(), |ns| ns.to_string()),
if namespace.is_some() { if namespace.is_some() {
Token::DoubleColon.literal_syntax() crate::tokenizer::Token::DoubleColon.literal_syntax()
} else { } else {
"" ""
}, },
);
#[cfg(feature = "no_module")]
let (ns, sep) = ("", "");
format!(
"{}{}{} ({})",
ns,
sep,
fn_name, fn_name,
args.iter() args.iter()
.map(|a| if a.is::<ImmutableString>() { .map(|a| if a.is::<ImmutableString>() {
@ -194,6 +200,8 @@ impl Engine {
allow_dynamic: bool, allow_dynamic: bool,
is_op_assignment: bool, is_op_assignment: bool,
) -> Option<&'s FnResolutionCacheEntry> { ) -> Option<&'s FnResolutionCacheEntry> {
let _global = global;
if hash_script == 0 { if hash_script == 0 {
return None; return None;
} }
@ -233,9 +241,12 @@ impl Engine {
source: m.id_raw().clone(), source: m.id_raw().clone(),
}) })
}) })
}) });
#[cfg(not(feature = "no_module"))]
let func = func
.or_else(|| { .or_else(|| {
global.get_qualified_fn(hash).map(|(func, source)| { _global.get_qualified_fn(hash).map(|(func, source)| {
FnResolutionCacheEntry { FnResolutionCacheEntry {
func: func.clone(), func: func.clone(),
source: source source: source
@ -506,9 +517,16 @@ impl Engine {
} }
// Raise error // Raise error
_ => Err( _ => Err(ERR::ErrorFunctionNotFound(
ERR::ErrorFunctionNotFound(self.gen_call_signature(None, name, args), pos).into(), self.gen_call_signature(
#[cfg(not(feature = "no_module"))]
None,
name,
args,
), ),
pos,
)
.into()),
} }
} }
@ -889,8 +907,12 @@ impl Engine {
) -> RhaiResultOf<(Dynamic, Position)> { ) -> RhaiResultOf<(Dynamic, Position)> {
Ok(( Ok((
if let Expr::Stack(slot, _) = arg_expr { if let Expr::Stack(slot, _) = arg_expr {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
constants[*slot].clone() constants[*slot].clone()
} else if let Some(value) = arg_expr.get_literal_value() { } else if let Some(value) = arg_expr.get_literal_value() {
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?;
value value
} else { } else {
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)? self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
@ -1133,9 +1155,12 @@ impl Engine {
// convert to method-call style in order to leverage potential &mut first argument and // convert to method-call style in order to leverage potential &mut first argument and
// avoid cloning the value // avoid cloning the value
if curry.is_empty() && first_arg.map_or(false, |expr| expr.is_variable_access(false)) { if curry.is_empty() && first_arg.map_or(false, |expr| expr.is_variable_access(false)) {
// func(x, ...) -> x.func(...)
let first_expr = first_arg.unwrap(); let first_expr = first_arg.unwrap();
#[cfg(feature = "debugging")]
self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level)?;
// func(x, ...) -> x.func(...)
a_expr.iter().try_for_each(|expr| { a_expr.iter().try_for_each(|expr| {
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants) self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
.map(|(value, _)| arg_values.push(value.flatten())) .map(|(value, _)| arg_values.push(value.flatten()))
@ -1189,6 +1214,7 @@ impl Engine {
} }
/// Call a namespace-qualified function in normal function-call style. /// Call a namespace-qualified function in normal function-call style.
#[cfg(not(feature = "no_module"))]
pub(crate) fn make_qualified_function_call( pub(crate) fn make_qualified_function_call(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1196,7 +1222,7 @@ impl Engine {
state: &mut EvalState, state: &mut EvalState,
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
namespace: &Namespace, namespace: &crate::module::Namespace,
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
constants: &[Dynamic], constants: &[Dynamic],
@ -1215,6 +1241,9 @@ impl Engine {
// 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 and avoid cloning the value // &mut first argument 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")]
self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level)?;
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
arg_values.push(Dynamic::UNIT); arg_values.push(Dynamic::UNIT);

View File

@ -17,7 +17,7 @@ pub enum CallableFunction {
/// and the rest passed by value. /// and the rest passed by value.
Method(Shared<FnAny>), Method(Shared<FnAny>),
/// An iterator function. /// An iterator function.
Iterator(IteratorFn), Iterator(Shared<IteratorFn>),
/// A plugin function, /// A plugin function,
Plugin(Shared<FnPlugin>), Plugin(Shared<FnPlugin>),
/// A script-defined function. /// A script-defined function.
@ -177,9 +177,9 @@ impl CallableFunction {
/// Get a reference to an iterator function. /// Get a reference to an iterator function.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn get_iter_fn(&self) -> Option<IteratorFn> { pub fn get_iter_fn(&self) -> Option<&IteratorFn> {
match self { match self {
Self::Iterator(f) => Some(*f), Self::Iterator(f) => Some(f.as_ref()),
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None, Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -218,13 +218,6 @@ impl CallableFunction {
} }
} }
impl From<IteratorFn> for CallableFunction {
#[inline(always)]
fn from(func: IteratorFn) -> Self {
Self::Iterator(func)
}
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
impl From<crate::ast::ScriptFnDef> for CallableFunction { impl From<crate::ast::ScriptFnDef> for CallableFunction {
#[inline(always)] #[inline(always)]

View File

@ -22,7 +22,7 @@ pub use hashing::{
combine_hashes, get_hasher, combine_hashes, get_hasher,
}; };
pub use native::{ pub use native::{
shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock, FnAny, locked_write, shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, FnAny,
FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared,
}; };
pub use plugin::PluginFunction; pub use plugin::PluginFunction;

View File

@ -215,7 +215,6 @@ impl<'a> NativeCallContext<'a> {
/// ///
/// Not available under `no_module`. /// Not available under `no_module`.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn global_runtime_state(&self) -> Option<&GlobalRuntimeState> { pub const fn global_runtime_state(&self) -> Option<&GlobalRuntimeState> {
@ -286,26 +285,29 @@ impl<'a> NativeCallContext<'a> {
is_method_call: bool, is_method_call: bool,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> RhaiResult { ) -> RhaiResult {
let mut global = self
.global
.cloned()
.unwrap_or_else(|| GlobalRuntimeState::new(self.engine()));
let mut state = EvalState::new();
let fn_name = fn_name.as_ref(); let fn_name = fn_name.as_ref();
let len = args.len(); let args_len = args.len();
let hash = if is_method_call { let hash = if is_method_call {
FnCallHashes::from_all( FnCallHashes::from_all(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
calc_fn_hash(fn_name, len - 1), calc_fn_hash(fn_name, args_len - 1),
calc_fn_hash(fn_name, len), calc_fn_hash(fn_name, args_len),
) )
} else { } else {
calc_fn_hash(fn_name, len).into() calc_fn_hash(fn_name, args_len).into()
}; };
self.engine() self.engine()
.exec_fn_call( .exec_fn_call(
&mut self &mut global,
.global &mut state,
.cloned()
.unwrap_or_else(|| GlobalRuntimeState::new()),
&mut EvalState::new(),
self.lib, self.lib,
fn_name, fn_name,
hash, hash,
@ -356,11 +358,11 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
shared_try_take(value).ok().expect("not shared") shared_try_take(value).ok().expect("not shared")
} }
/// Lock a [`Shared`] resource. /// Lock a [`Locked`] resource.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
#[allow(dead_code)] #[allow(dead_code)]
pub fn shared_write_lock<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> { pub fn locked_write<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
return value.borrow_mut(); return value.borrow_mut();
@ -368,60 +370,62 @@ pub fn shared_write_lock<'a, T>(value: &'a Locked<T>) -> LockGuard<'a, T> {
return value.write().unwrap(); return value.write().unwrap();
} }
/// A general function trail object. /// General function trail object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
/// A general function trail object. /// General function trail object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync; pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync;
/// A trail object for built-in functions. /// Trail object for built-in functions.
pub type FnBuiltin = fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; pub type FnBuiltin = fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
/// A standard function that gets an iterator from a type. /// Function that gets an iterator from a type.
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; #[cfg(not(feature = "sync"))]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
/// Function that gets an iterator from a type.
#[cfg(feature = "sync")]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync;
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnPlugin = dyn PluginFunction; pub type FnPlugin = dyn PluginFunction;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnPlugin = dyn PluginFunction + Send + Sync; pub type FnPlugin = dyn PluginFunction + Send + Sync;
/// A standard callback function for progress reporting. /// Callback function for progress reporting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + 'static>; pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic>;
/// A standard callback function for progress reporting. /// Callback function for progress reporting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + Send + Sync + 'static>; pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic> + Send + Sync;
/// A standard callback function for printing. /// Callback function for printing.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnPrintCallback = Box<dyn Fn(&str) + 'static>; pub type OnPrintCallback = dyn Fn(&str);
/// A standard callback function for printing. /// Callback function for printing.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnPrintCallback = Box<dyn Fn(&str) + Send + Sync + 'static>; pub type OnPrintCallback = dyn Fn(&str) + Send + Sync;
/// A standard callback function for debugging. /// Callback function for debugging.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + 'static>; pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position);
/// A standard callback function for debugging. /// Callback function for debugging.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + Send + Sync + 'static>; pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position) + Send + Sync;
/// A standard callback function for mapping tokens during parsing. /// Callback function for mapping tokens during parsing.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token; pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token;
/// A standard callback function for mapping tokens during parsing. /// Callback function for mapping tokens during parsing.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnParseTokenCallback = pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync;
dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync + 'static;
/// A standard callback function for variable access. /// Callback function for variable access.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type OnVarCallback = pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>;
Box<dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + 'static>; /// Callback function for variable access.
/// A standard callback function for variable access.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type OnVarCallback = pub type OnVarCallback =
Box<dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync + 'static>; dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync;

View File

@ -70,7 +70,12 @@ impl Engine {
} }
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let orig_mods_len = global.num_imports(); #[cfg(not(feature = "no_module"))]
let orig_imports_len = global.num_imports();
#[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))]
let orig_call_stack_len = global.debugger.call_stack().len();
// Put arguments into scope as variables // Put arguments into scope as variables
scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| { scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| {
@ -78,6 +83,20 @@ impl Engine {
mem::take(*v) mem::take(*v)
}))); })));
// Push a new call stack frame
#[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))]
global.debugger.push_call_stack_frame(
fn_def.name.clone(),
scope
.iter()
.skip(orig_scope_len)
.map(|(_, _, v)| v.clone())
.collect(),
global.source.clone(),
pos,
);
// Merge in encapsulated environment, if any // Merge in encapsulated environment, if any
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1); let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
@ -97,10 +116,9 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
if let Some(ref modules) = fn_def.global { if let Some(ref modules) = fn_def.global {
modules for (n, m) in modules.iter().cloned() {
.iter() global.push_import(n, m)
.cloned() }
.for_each(|(n, m)| global.push_import(n, m));
} }
// Evaluate the function // Evaluate the function
@ -144,11 +162,17 @@ impl Engine {
// Remove arguments only, leaving new variables in the scope // Remove arguments only, leaving new variables in the scope
scope.remove_range(orig_scope_len, args.len()) scope.remove_range(orig_scope_len, args.len())
} }
global.truncate_imports(orig_mods_len); #[cfg(not(feature = "no_module"))]
global.truncate_imports(orig_imports_len);
// Restore state // Restore state
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
// Pop the call stack
#[cfg(feature = "debugging")]
#[cfg(not(feature = "no_function"))]
global.debugger.rewind_call_stack(orig_call_stack_len);
result result
} }
@ -161,6 +185,8 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
hash_script: u64, hash_script: u64,
) -> bool { ) -> bool {
let _global = global;
let cache = state.fn_resolution_cache_mut(); let cache = state.fn_resolution_cache_mut();
if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) { if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) {
@ -170,9 +196,12 @@ impl Engine {
// First check script-defined functions // First check script-defined functions
let result = lib.iter().any(|&m| m.contains_fn(hash_script)) let result = lib.iter().any(|&m| m.contains_fn(hash_script))
// Then check the global namespace and packages // Then check the global namespace and packages
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script)) || self.global_modules.iter().any(|m| m.contains_fn(hash_script));
#[cfg(not(feature = "no_module"))]
let result = result ||
// Then check imported modules // Then check imported modules
|| global.map_or(false, |m| m.contains_qualified_fn(hash_script)) _global.map_or(false, |m| m.contains_qualified_fn(hash_script))
// Then check sub-modules // Then check sub-modules
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));

View File

@ -123,6 +123,7 @@ type UNSIGNED_INT = u64;
type UNSIGNED_INT = u32; type UNSIGNED_INT = u32;
/// The system floating-point type. It is defined as [`f64`]. /// The system floating-point type. It is defined as [`f64`].
///
/// Not available under `no_float`. /// Not available under `no_float`.
/// ///
/// If the `f32_float` feature is enabled, this will be [`f32`] instead. /// If the `f32_float` feature is enabled, this will be [`f32`] instead.
@ -132,6 +133,7 @@ pub type FLOAT = f64;
/// The system floating-point type. /// The system floating-point type.
/// It is defined as [`f32`] since the `f32_float` feature is used. /// It is defined as [`f32`] since the `f32_float` feature is used.
///
/// Not available under `no_float`. /// Not available under `no_float`.
/// ///
/// If the `f32_float` feature is not used, this will be `f64` instead. /// If the `f32_float` feature is not used, this will be `f64` instead.
@ -156,6 +158,15 @@ pub use types::{
Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope, Dynamic, EvalAltResult, FnPtr, ImmutableString, LexError, ParseError, ParseErrorType, Scope,
}; };
/// _(debugging)_ Module containing types for debugging.
/// Exported under the `debugging` feature only.
#[cfg(feature = "debugging")]
pub mod debugger {
#[cfg(not(feature = "no_function"))]
pub use super::eval::CallStackFrame;
pub use super::eval::{BreakPoint, Debugger, DebuggerCommand};
}
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline. /// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
@ -188,16 +199,19 @@ pub use func::Func;
pub use ast::ScriptFnMetadata; pub use ast::ScriptFnMetadata;
/// Variable-sized array of [`Dynamic`] values. /// Variable-sized array of [`Dynamic`] values.
///
/// Not available under `no_index`. /// Not available under `no_index`.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>; pub type Array = Vec<Dynamic>;
/// Variable-sized array of [`u8`] values (byte array). /// Variable-sized array of [`u8`] values (byte array).
///
/// Not available under `no_index`. /// Not available under `no_index`.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Blob = Vec<u8>; pub type Blob = Vec<u8>;
/// A dictionary of [`Dynamic`] values with string keys. /// A dictionary of [`Dynamic`] values with string keys.
///
/// Not available under `no_object`. /// Not available under `no_object`.
/// ///
/// [`SmartString`](https://crates.io/crates/smartstring) is used as the key type because most /// [`SmartString`](https://crates.io/crates/smartstring) is used as the key type because most
@ -240,8 +254,9 @@ pub use parser::ParseState;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub use ast::{ pub use ast::{
ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
AST_OPTION_FLAGS::*,
}; };
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
@ -255,6 +270,7 @@ pub use eval::{EvalState, GlobalRuntimeState};
pub use func::call::{FnResolutionCache, FnResolutionCacheEntry}; pub use func::call::{FnResolutionCache, FnResolutionCacheEntry};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
pub use module::Namespace; pub use module::Namespace;
/// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a /// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a

View File

@ -1,11 +1,10 @@
//! Module defining external-loaded modules for Rhai. //! Module defining external-loaded modules for Rhai.
use crate::ast::{FnAccess, Ident}; use crate::ast::FnAccess;
use crate::func::{ use crate::func::{
shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction,
SendSync, SendSync,
}; };
use crate::tokenizer::Token;
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier,
@ -19,8 +18,7 @@ use std::{
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
fmt, fmt,
iter::{empty, once}, iter::{empty, once},
num::NonZeroUsize, ops::{Add, AddAssign},
ops::{Add, AddAssign, Deref, DerefMut},
}; };
/// A type representing the namespace of a function. /// A type representing the namespace of a function.
@ -29,6 +27,8 @@ pub enum FnNamespace {
/// Expose to global namespace. /// Expose to global namespace.
Global, Global,
/// Module namespace only. /// Module namespace only.
///
/// Ignored under `no_module`.
Internal, Internal,
} }
@ -151,7 +151,7 @@ impl FuncInfo {
ty => ty.into(), ty => ty.into(),
} }
} }
/// Generate a signature of the function. /// _(metadata)_ Generate a signature of the function.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[must_use] #[must_use]
@ -244,9 +244,9 @@ pub struct Module {
/// including those in sub-modules. /// including those in sub-modules.
all_functions: BTreeMap<u64, Shared<CallableFunction>>, all_functions: BTreeMap<u64, Shared<CallableFunction>>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: BTreeMap<TypeId, IteratorFn>, type_iterators: BTreeMap<TypeId, Shared<IteratorFn>>,
/// Flattened collection of iterator functions, including those in sub-modules. /// Flattened collection of iterator functions, including those in sub-modules.
all_type_iterators: BTreeMap<TypeId, IteratorFn>, all_type_iterators: BTreeMap<TypeId, Shared<IteratorFn>>,
/// Is the [`Module`] indexed? /// Is the [`Module`] indexed?
indexed: bool, indexed: bool,
/// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace?
@ -462,7 +462,7 @@ impl Module {
self.indexed self.indexed
} }
/// Generate signatures for all the non-private functions in the [`Module`]. /// _(metadata)_ Generate signatures for all the non-private functions in the [`Module`].
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline] #[inline]
@ -759,8 +759,7 @@ impl Module {
self self
} }
/// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a /// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a registered function.
/// registered function.
/// Exported under the `metadata` feature only. /// Exported under the `metadata` feature only.
/// ///
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
@ -1373,6 +1372,7 @@ impl Module {
/// Get a namespace-qualified function. /// Get a namespace-qualified function.
/// ///
/// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. /// The [`u64`] hash is calculated by [`build_index`][Module::build_index].
#[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
@ -1402,9 +1402,9 @@ impl Module {
/// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level. /// Sub-modules are flattened onto the root [`Module`], with higher level overriding lower level.
#[inline] #[inline]
pub fn combine_flatten(&mut self, other: Self) -> &mut Self { pub fn combine_flatten(&mut self, other: Self) -> &mut Self {
other.modules.into_iter().for_each(|(_, m)| { for (_, m) in other.modules.into_iter() {
self.combine_flatten(shared_take_or_clone(m)); self.combine_flatten(shared_take_or_clone(m));
}); }
self.variables.extend(other.variables.into_iter()); self.variables.extend(other.variables.into_iter());
self.functions.extend(other.functions.into_iter()); self.functions.extend(other.functions.into_iter());
self.type_iterators.extend(other.type_iterators.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter());
@ -1420,22 +1420,22 @@ impl Module {
/// Only items not existing in this [`Module`] are added. /// Only items not existing in this [`Module`] are added.
#[inline] #[inline]
pub fn fill_with(&mut self, other: &Self) -> &mut Self { pub fn fill_with(&mut self, other: &Self) -> &mut Self {
other.modules.iter().for_each(|(k, v)| { for (k, v) in &other.modules {
if !self.modules.contains_key(k) { if !self.modules.contains_key(k) {
self.modules.insert(k.clone(), v.clone()); self.modules.insert(k.clone(), v.clone());
} }
}); }
other.variables.iter().for_each(|(k, v)| { for (k, v) in &other.variables {
if !self.variables.contains_key(k) { if !self.variables.contains_key(k) {
self.variables.insert(k.clone(), v.clone()); self.variables.insert(k.clone(), v.clone());
} }
}); }
other.functions.iter().for_each(|(&k, v)| { for (&k, v) in &other.functions {
self.functions.entry(k).or_insert_with(|| v.clone()); self.functions.entry(k).or_insert_with(|| v.clone());
}); }
other.type_iterators.iter().for_each(|(&k, &v)| { for (&k, v) in &other.type_iterators {
self.type_iterators.entry(k).or_insert(v); self.type_iterators.entry(k).or_insert_with(|| v.clone());
}); }
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear(); self.all_type_iterators.clear();
@ -1456,12 +1456,11 @@ impl Module {
other: &Self, other: &Self,
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_function"))] for (k, v) in &other.modules {
other.modules.iter().for_each(|(k, v)| {
let mut m = Self::new(); let mut m = Self::new();
m.merge_filtered(v, _filter); m.merge_filtered(v, _filter);
self.set_sub_module(k.clone(), m); self.set_sub_module(k.clone(), m);
}); }
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
self.modules self.modules
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone()))); .extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
@ -1484,7 +1483,8 @@ impl Module {
.map(|(&k, v)| (k, v.clone())), .map(|(&k, v)| (k, v.clone())),
); );
self.type_iterators.extend(other.type_iterators.iter()); self.type_iterators
.extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone())));
self.all_functions.clear(); self.all_functions.clear();
self.all_variables.clear(); self.all_variables.clear();
self.all_type_iterators.clear(); self.all_type_iterators.clear();
@ -1658,24 +1658,78 @@ impl Module {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn eval_ast_as_new( pub fn eval_ast_as_new(
scope: crate::Scope, scope: crate::Scope,
ast: &crate::AST, ast: &crate::AST,
engine: &crate::Engine, engine: &crate::Engine,
) -> RhaiResultOf<Self> {
let global = &mut crate::eval::GlobalRuntimeState::new(engine);
Self::eval_ast_as_new_raw(engine, scope, global, ast)
}
/// Create a new [`Module`] by evaluating an [`AST`][crate::AST].
///
/// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions
/// to cross-call each other. 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.
#[cfg(not(feature = "no_module"))]
pub(crate) fn eval_ast_as_new_raw(
engine: &crate::Engine,
scope: crate::Scope,
global: &mut crate::eval::GlobalRuntimeState,
ast: &crate::AST,
) -> RhaiResultOf<Self> { ) -> RhaiResultOf<Self> {
let mut scope = scope; let mut scope = scope;
let mut global = crate::eval::GlobalRuntimeState::new();
let orig_mods_len = global.num_imports(); // Save global state
let orig_imports_len = global.num_imports();
let orig_source = global.source.clone();
#[cfg(not(feature = "no_function"))]
let orig_constants = std::mem::take(&mut global.constants);
// Run the script // Run the script
engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?; let result = engine.eval_ast_with_scope_raw(&mut scope, global, &ast, 0);
// Create new module // Create new module
let mut module = let mut module = Module::new();
scope
.into_iter() // Extra modules left become sub-modules
.fold(Module::new(), |mut module, (_, value, mut aliases)| { #[cfg(not(feature = "no_function"))]
let mut func_global = None;
if result.is_ok() {
global
.scan_imports_raw()
.skip(orig_imports_len)
.for_each(|(k, m)| {
#[cfg(not(feature = "no_function"))]
if func_global.is_none() {
func_global = Some(StaticVec::new());
}
#[cfg(not(feature = "no_function"))]
func_global
.as_mut()
.expect("`Some`")
.push((k.clone(), m.clone()));
module.set_sub_module(k.clone(), m.clone());
});
}
// Restore global state
#[cfg(not(feature = "no_function"))]
{
global.constants = orig_constants;
}
global.truncate_imports(orig_imports_len);
global.source = orig_source;
result?;
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
for (_, value, mut aliases) in scope {
match aliases.len() { match aliases.len() {
0 => (), 0 => (),
1 => { 1 => {
@ -1684,30 +1738,14 @@ impl Module {
} }
_ => { _ => {
let last_alias = aliases.pop().unwrap(); let last_alias = aliases.pop().unwrap();
aliases.into_iter().for_each(|alias| { for alias in aliases {
module.set_var(alias, value.clone()); module.set_var(alias, value.clone());
}); }
// Avoid cloning the last value // Avoid cloning the last value
module.set_var(last_alias, value); module.set_var(last_alias, value);
} }
} }
module
});
// Extra modules left in the scope become sub-modules
#[cfg(not(feature = "no_function"))]
let mut func_global = None;
global.into_iter().skip(orig_mods_len).for_each(|kv| {
#[cfg(not(feature = "no_function"))]
if func_global.is_none() {
func_global = Some(StaticVec::new());
} }
#[cfg(not(feature = "no_function"))]
func_global.as_mut().expect("`Some`").push(kv.clone());
module.set_sub_module(kv.0, kv.1);
});
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let func_global = func_global.map(|v| v.into_boxed_slice()); let func_global = func_global.map(|v| v.into_boxed_slice());
@ -1765,33 +1803,33 @@ impl Module {
path: &mut Vec<&'a str>, path: &mut Vec<&'a str>,
variables: &mut BTreeMap<u64, Dynamic>, variables: &mut BTreeMap<u64, Dynamic>,
functions: &mut BTreeMap<u64, Shared<CallableFunction>>, functions: &mut BTreeMap<u64, Shared<CallableFunction>>,
type_iterators: &mut BTreeMap<TypeId, IteratorFn>, type_iterators: &mut BTreeMap<TypeId, Shared<IteratorFn>>,
) -> bool { ) -> bool {
let mut contains_indexed_global_functions = false; let mut contains_indexed_global_functions = false;
module.modules.iter().for_each(|(name, m)| { for (name, m) in &module.modules {
// Index all the sub-modules first. // Index all the sub-modules first.
path.push(name); path.push(name);
if index_module(m, path, variables, functions, type_iterators) { if index_module(m, path, variables, functions, type_iterators) {
contains_indexed_global_functions = true; contains_indexed_global_functions = true;
} }
path.pop(); path.pop();
}); }
// Index all variables // Index all variables
module.variables.iter().for_each(|(var_name, value)| { for (var_name, value) in &module.variables {
let hash_var = crate::calc_qualified_var_hash(path.iter().copied(), var_name); let hash_var = crate::calc_qualified_var_hash(path.iter().copied(), var_name);
variables.insert(hash_var, value.clone()); variables.insert(hash_var, value.clone());
}); }
// Index type iterators // Index type iterators
module.type_iterators.iter().for_each(|(&type_id, func)| { for (&type_id, func) in &module.type_iterators {
type_iterators.insert(type_id, *func); type_iterators.insert(type_id, func.clone());
contains_indexed_global_functions = true; contains_indexed_global_functions = true;
}); }
// Index all Rust functions // Index all Rust functions
module.functions.iter().for_each(|(&hash, f)| { for (&hash, f) in &module.functions {
match f.metadata.namespace { match f.metadata.namespace {
FnNamespace::Global => { FnNamespace::Global => {
// Flatten all functions with global namespace // Flatten all functions with global namespace
@ -1802,7 +1840,7 @@ impl Module {
} }
match f.metadata.access { match f.metadata.access {
FnAccess::Public => (), FnAccess::Public => (),
FnAccess::Private => return, // Do not index private functions FnAccess::Private => continue, // Do not index private functions
} }
if !f.func.is_script() { if !f.func.is_script() {
@ -1820,7 +1858,7 @@ impl Module {
); );
functions.insert(hash_qualified_script, f.func.clone()); functions.insert(hash_qualified_script, f.func.clone());
} }
}); }
contains_indexed_global_functions contains_indexed_global_functions
} }
@ -1865,10 +1903,33 @@ impl Module {
} }
/// Set a type iterator into the [`Module`]. /// Set a type iterator into the [`Module`].
#[cfg(not(feature = "sync"))]
#[inline] #[inline]
pub fn set_iter(&mut self, type_id: TypeId, func: IteratorFn) -> &mut Self { pub fn set_iter(
&mut self,
type_id: TypeId,
func: impl Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
) -> &mut Self {
let func = Shared::new(func);
if self.indexed { if self.indexed {
self.all_type_iterators.insert(type_id, func); self.all_type_iterators.insert(type_id, func.clone());
self.contains_indexed_global_functions = true;
}
self.type_iterators.insert(type_id, func);
self
}
/// Set a type iterator into the [`Module`].
#[cfg(feature = "sync")]
#[inline]
pub fn set_iter(
&mut self,
type_id: TypeId,
func: impl Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + SendSync + 'static,
) -> &mut Self {
let func = Shared::new(func);
if self.indexed {
self.all_type_iterators.insert(type_id, func.clone());
self.contains_indexed_global_functions = true; self.contains_indexed_global_functions = true;
} }
self.type_iterators.insert(type_id, func); self.type_iterators.insert(type_id, func);
@ -1900,126 +1961,29 @@ impl Module {
} }
/// Get the specified type iterator. /// Get the specified type iterator.
#[cfg(not(feature = "no_module"))]
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> { pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> {
self.all_type_iterators.get(&id).cloned() self.all_type_iterators.get(&id).map(|f| f.as_ref())
} }
/// Get the specified type iterator. /// Get the specified type iterator.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> {
self.type_iterators.get(&id).cloned() self.type_iterators.get(&id).map(|f| f.as_ref())
} }
} }
/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function mod namespace;
/// call. Exported under the `internals` feature only.
///
/// A [`u64`] offset to the current [stack of imported modules][crate::GlobalRuntimeState] is
/// cached for quick search purposes.
///
/// A [`StaticVec`] is used because the vast majority of namespace-qualified access contains only
/// one level, and it is wasteful to always allocate a [`Vec`] with one element.
#[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct Namespace {
index: Option<NonZeroUsize>,
path: StaticVec<Ident>,
}
impl fmt::Debug for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(index) = self.index {
write!(f, "{} -> ", index)?;
}
f.write_str(
&self
.path
.iter()
.map(|Ident { name, .. }| name.as_str())
.collect::<StaticVec<_>>()
.join(Token::DoubleColon.literal_syntax()),
)
}
}
impl fmt::Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
&self
.path
.iter()
.map(|Ident { name, .. }| name.as_str())
.collect::<StaticVec<_>>()
.join(Token::DoubleColon.literal_syntax()),
)
}
}
impl Deref for Namespace {
type Target = StaticVec<Ident>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.path
}
}
impl DerefMut for Namespace {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.path
}
}
impl From<Vec<Ident>> for Namespace {
#[inline(always)]
fn from(mut path: Vec<Ident>) -> Self {
path.shrink_to_fit();
Self {
index: None,
path: path.into(),
}
}
}
impl From<StaticVec<Ident>> for Namespace {
#[inline(always)]
fn from(mut path: StaticVec<Ident>) -> Self {
path.shrink_to_fit();
Self { index: None, path }
}
}
impl Namespace {
/// Create a new [`Namespace`].
#[inline(always)]
#[must_use]
pub const fn new() -> Self {
Self {
index: None,
path: StaticVec::new_const(),
}
}
/// Get the [`Scope`][crate::Scope] index offset.
#[inline(always)]
#[must_use]
pub(crate) const fn index(&self) -> Option<NonZeroUsize> {
self.index
}
/// Set the [`Scope`][crate::Scope] index offset.
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.index = index
}
}
#[cfg(not(feature = "no_module"))]
pub use resolvers::ModuleResolver;
/// Module containing all built-in [module resolvers][ModuleResolver]. /// Module containing all built-in [module resolvers][ModuleResolver].
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod resolvers; pub mod resolvers;
#[cfg(not(feature = "no_module"))]
pub use namespace::Namespace;
#[cfg(not(feature = "no_module"))]
pub use resolvers::ModuleResolver;

117
src/module/namespace.rs Normal file
View File

@ -0,0 +1,117 @@
//! Namespace reference type.
#![cfg(not(feature = "no_module"))]
use crate::ast::Ident;
use crate::tokenizer::Token;
use crate::StaticVec;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
fmt,
num::NonZeroUsize,
ops::{Deref, DerefMut},
};
/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function call.
/// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
///
/// A [`u64`] offset to the current [stack of imported modules][crate::GlobalRuntimeState] is
/// cached for quick search purposes.
///
/// A [`StaticVec`] is used because the vast majority of namespace-qualified access contains only
/// one level, and it is wasteful to always allocate a [`Vec`] with one element.
#[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct Namespace {
index: Option<NonZeroUsize>,
path: StaticVec<Ident>,
}
impl fmt::Debug for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(index) = self.index {
write!(f, "{} -> ", index)?;
}
f.write_str(
&self
.path
.iter()
.map(|Ident { name, .. }| name.as_str())
.collect::<StaticVec<_>>()
.join(Token::DoubleColon.literal_syntax()),
)
}
}
impl fmt::Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
&self
.path
.iter()
.map(|Ident { name, .. }| name.as_str())
.collect::<StaticVec<_>>()
.join(Token::DoubleColon.literal_syntax()),
)
}
}
impl Deref for Namespace {
type Target = StaticVec<Ident>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.path
}
}
impl DerefMut for Namespace {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.path
}
}
impl From<Vec<Ident>> for Namespace {
#[inline(always)]
fn from(mut path: Vec<Ident>) -> Self {
path.shrink_to_fit();
Self {
index: None,
path: path.into(),
}
}
}
impl From<StaticVec<Ident>> for Namespace {
#[inline(always)]
fn from(mut path: StaticVec<Ident>) -> Self {
path.shrink_to_fit();
Self { index: None, path }
}
}
impl Namespace {
/// Create a new [`Namespace`].
#[inline(always)]
#[must_use]
pub const fn new() -> Self {
Self {
index: None,
path: StaticVec::new_const(),
}
}
/// Get the [`Scope`][crate::Scope] index offset.
#[inline(always)]
#[must_use]
pub(crate) const fn index(&self) -> Option<NonZeroUsize> {
self.index
}
/// Set the [`Scope`][crate::Scope] index offset.
#[inline(always)]
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.index = index
}
}

View File

@ -124,7 +124,7 @@ impl ModuleResolver for ModuleResolversCollection {
path: &str, path: &str,
pos: Position, pos: Position,
) -> RhaiResultOf<Shared<Module>> { ) -> RhaiResultOf<Shared<Module>> {
for resolver in self.0.iter() { for resolver in &self.0 {
match resolver.resolve(engine, source_path, path, pos) { match resolver.resolve(engine, source_path, path, pos) {
Ok(module) => return Ok(module), Ok(module) => return Ok(module),
Err(err) => match *err { Err(err) => match *err {

View File

@ -1,7 +1,8 @@
#![cfg(not(feature = "no_std"))] #![cfg(not(feature = "no_std"))]
#![cfg(not(target_family = "wasm"))] #![cfg(not(target_family = "wasm"))]
use crate::func::native::shared_write_lock; use crate::eval::GlobalRuntimeState;
use crate::func::native::locked_write;
use crate::{ use crate::{
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR,
}; };
@ -207,12 +208,12 @@ impl FileModuleResolver {
let file_path = self.get_file_path(path.as_ref(), source_path); let file_path = self.get_file_path(path.as_ref(), source_path);
shared_write_lock(&self.cache).contains_key(&file_path) locked_write(&self.cache).contains_key(&file_path)
} }
/// Empty the internal cache. /// Empty the internal cache.
#[inline] #[inline]
pub fn clear_cache(&mut self) -> &mut Self { pub fn clear_cache(&mut self) -> &mut Self {
shared_write_lock(&self.cache).clear(); locked_write(&self.cache).clear();
self self
} }
/// Remove the specified path from internal cache. /// Remove the specified path from internal cache.
@ -227,7 +228,7 @@ impl FileModuleResolver {
) -> Option<Shared<Module>> { ) -> Option<Shared<Module>> {
let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref)); let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref));
shared_write_lock(&self.cache) locked_write(&self.cache)
.remove_entry(&file_path) .remove_entry(&file_path)
.map(|(_, v)| v) .map(|(_, v)| v)
} }
@ -252,24 +253,25 @@ impl FileModuleResolver {
file_path.set_extension(self.extension.as_str()); // Force extension file_path.set_extension(self.extension.as_str()); // Force extension
file_path file_path
} }
}
impl ModuleResolver for FileModuleResolver { /// Resolve a module based on a path.
fn resolve( fn impl_resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>, global: Option<&mut GlobalRuntimeState>,
source: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> RhaiResultOf<Shared<Module>> { ) -> Result<Shared<Module>, Box<crate::EvalAltResult>> {
// Load relative paths from source if there is no base path specified // Load relative paths from source if there is no base path specified
let source_path = let source_path = global
source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); .as_ref()
.and_then(|g| g.source())
.or(source)
.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy()));
// Construct the script file path
let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref())); let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref()));
// See if it is cached
if self.is_cache_enabled() { if self.is_cache_enabled() {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let c = self.cache.borrow(); let c = self.cache.borrow();
@ -281,7 +283,6 @@ impl ModuleResolver for FileModuleResolver {
} }
} }
// Load the script file and compile it
let scope = Scope::new(); let scope = Scope::new();
let mut ast = engine let mut ast = engine
@ -295,18 +296,43 @@ impl ModuleResolver for FileModuleResolver {
ast.set_source(path); ast.set_source(path);
// Make a module from the AST let m: Shared<Module> = if let Some(global) = global {
let m: Shared<Module> = Module::eval_ast_as_new(scope, &ast, engine) Module::eval_ast_as_new_raw(engine, scope, global, &ast)
} else {
Module::eval_ast_as_new(scope, &ast, engine)
}
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
.into(); .into();
// Put it into the cache
if self.is_cache_enabled() { if self.is_cache_enabled() {
shared_write_lock(&self.cache).insert(file_path, m.clone()); locked_write(&self.cache).insert(file_path, m.clone());
} }
Ok(m) Ok(m)
} }
}
impl ModuleResolver for FileModuleResolver {
fn resolve_raw(
&self,
engine: &Engine,
global: &mut GlobalRuntimeState,
path: &str,
pos: Position,
) -> RhaiResultOf<Shared<Module>> {
self.impl_resolve(engine, Some(global), None, path, pos)
}
#[inline(always)]
fn resolve(
&self,
engine: &Engine,
source: Option<&str>,
path: &str,
pos: Position,
) -> RhaiResultOf<Shared<Module>> {
self.impl_resolve(engine, None, source, path, pos)
}
/// Resolve an `AST` based on a path string. /// Resolve an `AST` based on a path string.
/// ///

View File

@ -1,3 +1,4 @@
use crate::eval::GlobalRuntimeState;
use crate::func::native::SendSync; use crate::func::native::SendSync;
use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST}; use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -21,11 +22,26 @@ pub trait ModuleResolver: SendSync {
fn resolve( fn resolve(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>, source: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> RhaiResultOf<Shared<Module>>; ) -> RhaiResultOf<Shared<Module>>;
/// Resolve a module based on a path string, given a [`GlobalRuntimeState`].
///
/// # WARNING - Low Level API
///
/// This function is very low level.
fn resolve_raw(
&self,
engine: &Engine,
global: &mut GlobalRuntimeState,
path: &str,
pos: Position,
) -> RhaiResultOf<Shared<Module>> {
self.resolve(engine, global.source(), path, pos)
}
/// Resolve an `AST` based on a path string. /// Resolve an `AST` based on a path string.
/// ///
/// Returns [`None`] (default) if such resolution is not supported /// Returns [`None`] (default) if such resolution is not supported
@ -40,7 +56,7 @@ pub trait ModuleResolver: SendSync {
fn resolve_ast( fn resolve_ast(
&self, &self,
engine: &Engine, engine: &Engine,
source_path: Option<&str>, source: Option<&str>,
path: &str, path: &str,
pos: Position, pos: Position,
) -> Option<RhaiResultOf<AST>> { ) -> Option<RhaiResultOf<AST>> {

View File

@ -138,7 +138,7 @@ impl<'a> OptimizerState<'a> {
self.engine self.engine
.call_native_fn( .call_native_fn(
&mut GlobalRuntimeState::new(), &mut GlobalRuntimeState::new(&self.engine),
&mut EvalState::new(), &mut EvalState::new(),
lib, lib,
fn_name, fn_name,
@ -164,9 +164,21 @@ fn has_native_fn_override(
// First check the global namespace and packages, but skip modules that are standard because // First check the global namespace and packages, but skip modules that are standard because
// they should never conflict with system functions. // they should never conflict with system functions.
engine.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash)) let result = engine
.global_modules
.iter()
.filter(|m| !m.standard)
.any(|m| m.contains_fn(hash));
#[cfg(not(feature = "no_module"))]
// Then check sub-modules // Then check sub-modules
|| engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash)) let result = result
|| engine
.global_sub_modules
.values()
.any(|m| m.contains_qualified_fn(hash));
result
} }
/// Optimize a block of [statements][Stmt]. /// Optimize a block of [statements][Stmt].
@ -237,7 +249,7 @@ fn optimize_stmt_block(
}); });
// Optimize each statement in the block // Optimize each statement in the block
statements.iter_mut().for_each(|stmt| { for stmt in statements.iter_mut() {
match stmt { match stmt {
Stmt::Var(value_expr, x, options, _) => { Stmt::Var(value_expr, x, options, _) => {
if options.contains(AST_OPTION_CONSTANT) { if options.contains(AST_OPTION_CONSTANT) {
@ -260,7 +272,7 @@ fn optimize_stmt_block(
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
} }
}); }
// Remove all pure statements except the last one // Remove all pure statements except the last one
let mut index = 0; let mut index = 0;
@ -308,7 +320,7 @@ fn optimize_stmt_block(
match statements[..] { match statements[..] {
// { return; } -> {} // { return; } -> {}
[Stmt::Return(options, None, _)] [Stmt::Return(options, None, _)]
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{ {
state.set_dirty(); state.set_dirty();
statements.clear(); statements.clear();
@ -320,7 +332,7 @@ fn optimize_stmt_block(
// { ...; return; } -> { ... } // { ...; return; } -> { ... }
[.., ref last_stmt, Stmt::Return(options, None, _)] [.., ref last_stmt, Stmt::Return(options, None, _)]
if reduce_return if reduce_return
&& !options.contains(AST_OPTION_BREAK_OUT) && !options.contains(AST_OPTION_BREAK)
&& !last_stmt.returns_value() => && !last_stmt.returns_value() =>
{ {
state.set_dirty(); state.set_dirty();
@ -328,7 +340,7 @@ fn optimize_stmt_block(
} }
// { ...; return val; } -> { ...; val } // { ...; return val; } -> { ...; val }
[.., Stmt::Return(options, ref mut expr, pos)] [.., Stmt::Return(options, ref mut expr, pos)]
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{ {
state.set_dirty(); state.set_dirty();
*statements.last_mut().unwrap() = expr *statements.last_mut().unwrap() = expr
@ -365,7 +377,7 @@ fn optimize_stmt_block(
} }
// { ...; return; } -> { ... } // { ...; return; } -> { ... }
[.., Stmt::Return(options, None, _)] [.., Stmt::Return(options, None, _)]
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{ {
state.set_dirty(); state.set_dirty();
statements.pop().unwrap(); statements.pop().unwrap();
@ -373,7 +385,7 @@ fn optimize_stmt_block(
// { ...; return pure_val; } -> { ... } // { ...; return pure_val; } -> { ... }
[.., Stmt::Return(options, Some(ref expr), _)] [.., Stmt::Return(options, Some(ref expr), _)]
if reduce_return if reduce_return
&& !options.contains(AST_OPTION_BREAK_OUT) && !options.contains(AST_OPTION_BREAK)
&& expr.is_pure() => && expr.is_pure() =>
{ {
state.set_dirty(); state.set_dirty();
@ -412,26 +424,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match stmt { match stmt {
// var = var op expr => var op= expr // var = var op expr => var op= expr
Stmt::Assignment(x, _) Stmt::Assignment(x, _)
if x.1.is_none() if x.0.is_none()
&& x.0.is_variable_access(true) && x.1.lhs.is_variable_access(true)
&& matches!(&x.2, Expr::FnCall(x2, _) && matches!(&x.1.rhs, Expr::FnCall(x2, _)
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false) if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
&& x2.args.len() == 2 && x2.args.len() == 2
&& x2.args[0].get_variable_name(true) == x.0.get_variable_name(true) && x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true)
) => ) =>
{ {
match x.2 { match x.1.rhs {
Expr::FnCall(ref mut x2, _) => { Expr::FnCall(ref mut x2, _) => {
state.set_dirty(); state.set_dirty();
x.1 = Some(OpAssignment::new_from_base(&x2.name)); x.0 = Some(OpAssignment::new_from_base(&x2.name));
let value = mem::take(&mut x2.args[1]); let value = mem::take(&mut x2.args[1]);
if let Expr::Stack(slot, pos) = value { if let Expr::Stack(slot, pos) = value {
x.2 = x.1.rhs =
Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos); Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos);
} else { } else {
x.2 = value; x.1.rhs = value;
} }
} }
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
@ -439,13 +451,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
// expr op= expr // expr op= expr
Stmt::Assignment(x, _) => match x.0 { Stmt::Assignment(x, _) => {
Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state, false), if !x.1.lhs.is_variable_access(false) {
_ => { optimize_expr(&mut x.1.lhs, state, false);
optimize_expr(&mut x.0, state, false); }
optimize_expr(&mut x.2, state, false); optimize_expr(&mut x.1.rhs, state, false);
} }
},
// if expr {} // if expr {}
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => { Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
@ -502,66 +513,24 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
let table = &mut x.0; let cases = &mut x.cases;
// First check hashes // First check hashes
if let Some(block) = table.get_mut(&hash) { if let Some(block) = cases.get_mut(&hash) {
if let Some(mut condition) = mem::take(&mut block.0) { if let Some(mut condition) = mem::take(&mut block.condition) {
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
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.1), state, true, true, false); optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
*stmt = Stmt::If( *stmt = Stmt::If(
condition, condition,
Box::new(( Box::new((
mem::take(&mut block.1), mem::take(&mut block.statements),
Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos))
.into(),
)),
match_expr.position(),
);
} else {
// Promote the matched case
let statements =
optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false);
*stmt = Stmt::Block(statements.into_boxed_slice(), block.1.position());
}
state.set_dirty();
return;
}
// Then check ranges
let ranges = &mut x.2;
if value.is::<INT>() && !ranges.is_empty() {
let value = value.as_int().expect("`INT`");
// Only one range or all ranges without conditions
if ranges.len() == 1 || ranges.iter().all(|(_, _, _, c, _)| c.is_none()) {
for (_, _, _, condition, stmt_block) in
ranges
.iter_mut()
.filter(|&&mut (start, end, inclusive, _, _)| {
(!inclusive && (start..end).contains(&value))
|| (inclusive && (start..=end).contains(&value))
})
{
if let Some(mut condition) = mem::take(condition) {
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false);
let def_stmt =
optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
*stmt = Stmt::If(
condition,
Box::new((
mem::take(stmt_block),
Stmt::Block( Stmt::Block(
def_stmt.into_boxed_slice(), def_stmt.into_boxed_slice(),
x.1.position().or_else(*pos), x.def_case.position().or_else(*pos),
) )
.into(), .into(),
)), )),
@ -569,11 +538,68 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
); );
} else { } else {
// Promote the matched case // Promote the matched case
let statements = mem::take(&mut **stmt_block); let statements = optimize_stmt_block(
mem::take(&mut block.statements),
state,
true,
true,
false,
);
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.position());
}
state.set_dirty();
return;
}
// Then check ranges
let ranges = &mut x.ranges;
if value.is::<INT>() && !ranges.is_empty() {
let value = value.as_int().expect("`INT`");
// Only one range or all ranges without conditions
if ranges.len() == 1 || ranges.iter().all(|(_, _, _, c)| !c.has_condition()) {
for (_, _, _, block) in
ranges
.iter_mut()
.filter(|&&mut (start, end, inclusive, _)| {
(!inclusive && (start..end).contains(&value))
|| (inclusive && (start..=end).contains(&value))
})
{
if let Some(mut condition) = mem::take(&mut block.condition) {
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false);
let def_stmt = optimize_stmt_block(
mem::take(&mut x.def_case),
state,
true,
true,
false,
);
*stmt = Stmt::If(
condition,
Box::new((
mem::take(&mut block.statements),
Stmt::Block(
def_stmt.into_boxed_slice(),
x.def_case.position().or_else(*pos),
)
.into(),
)),
match_expr.position(),
);
} else {
// Promote the matched case
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 = Stmt::Block(
Stmt::Block(statements.into_boxed_slice(), stmt_block.position()); statements.into_boxed_slice(),
block.statements.position(),
);
} }
state.set_dirty(); state.set_dirty();
@ -581,14 +607,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
} else { } else {
// Multiple ranges - clear the table and just keep the right ranges // Multiple ranges - clear the table and just keep the right ranges
if !table.is_empty() { if !cases.is_empty() {
state.set_dirty(); state.set_dirty();
table.clear(); cases.clear();
} }
let old_ranges_len = ranges.len(); let old_ranges_len = ranges.len();
ranges.retain(|&mut (start, end, inclusive, _, _)| { ranges.retain(|&mut (start, end, inclusive, _)| {
(!inclusive && (start..end).contains(&value)) (!inclusive && (start..end).contains(&value))
|| (inclusive && (start..=end).contains(&value)) || (inclusive && (start..=end).contains(&value))
}); });
@ -597,16 +623,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
state.set_dirty(); state.set_dirty();
} }
for (_, _, _, condition, stmt_block) in ranges.iter_mut() { for (_, _, _, block) in ranges.iter_mut() {
let statements = mem::take(&mut **stmt_block); let statements = mem::take(&mut *block.statements);
**stmt_block = *block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false); optimize_stmt_block(statements, state, preserve_result, true, false);
if let Some(mut c) = mem::take(condition) { if let Some(mut condition) = mem::take(&mut block.condition) {
optimize_expr(&mut c, state, false); optimize_expr(&mut condition, state, false);
match c { match condition {
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(), Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
_ => *condition = Some(c), _ => block.condition = Some(condition),
} }
} }
} }
@ -616,35 +642,41 @@ 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 = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); let def_stmt =
*stmt = Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos)); optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
*stmt = Stmt::Block(
def_stmt.into_boxed_slice(),
x.def_case.position().or_else(*pos),
);
} }
// switch // switch
Stmt::Switch(match_expr, x, _) => { Stmt::Switch(match_expr, x, _) => {
optimize_expr(match_expr, state, false); optimize_expr(match_expr, state, false);
x.0.values_mut().for_each(|block| { for block in x.cases.values_mut() {
let statements = mem::take(&mut *block.1); let statements = mem::take(&mut *block.statements);
*block.1 = optimize_stmt_block(statements, state, preserve_result, true, false); *block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false);
if let Some(mut condition) = mem::take(&mut block.0) { if let Some(mut condition) = mem::take(&mut block.condition) {
optimize_expr(&mut condition, state, false); optimize_expr(&mut condition, state, false);
match condition { match condition {
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(), Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
_ => block.0 = Some(condition), _ => block.condition = Some(condition),
}
} }
} }
});
// Remove false cases // Remove false cases
while let Some((&key, _)) = x.0.iter().find(|(_, block)| match block.0 { while let Some((&key, _)) = x.cases.iter().find(|(_, block)| match block.condition {
Some(Expr::BoolConstant(false, _)) => true, Some(Expr::BoolConstant(false, _)) => true,
_ => false, _ => false,
}) { }) {
state.set_dirty(); state.set_dirty();
x.0.remove(&key); x.cases.remove(&key);
} }
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false); let def_block = mem::take(&mut *x.def_case);
*x.def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
} }
// while false { block } -> Noop // while false { block } -> Noop
@ -663,7 +695,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if body.len() == 1 { if body.len() == 1 {
match body[0] { match body[0] {
// while expr { break; } -> { expr; } // while expr { break; } -> { expr; }
Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK_OUT) => { Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK) => {
// 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() {
@ -727,19 +759,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
} }
// 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.0.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 = Stmt::Block(
optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false) optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
.into_boxed_slice(), .into_boxed_slice(),
x.0.position(), x.try_block.position(),
); );
} }
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, _) => { Stmt::TryCatch(x, _) => {
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false); *x.try_block =
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false); optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false);
*x.catch_block =
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
} }
// func(...) // func(...)
Stmt::Expr(expr @ Expr::FnCall(_, _)) => { Stmt::Expr(expr @ Expr::FnCall(_, _)) => {
@ -800,8 +834,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(x,_, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) { Expr::Dot(x,_, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
// map.string // map.string
(Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { (Expr::Map(m, pos), Expr::Property(p, _)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
let prop = p.2.0.as_str(); let prop = p.2.as_str();
// Map literal where everything is pure - promote the indexed item. // Map literal where everything is pure - promote the indexed item.
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
@ -1091,7 +1125,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
}, },
// constant-name // constant-name
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => { #[cfg(not(feature = "no_module"))]
Expr::Variable(_, _, x) if x.1.is_some() => (),
Expr::Variable(_, pos, x) if state.find_constant(&x.2).is_some() => {
// Replace constant with value // Replace constant with value
*expr = Expr::from_dynamic(state.find_constant(&x.2).unwrap().clone(), *pos); *expr = Expr::from_dynamic(state.find_constant(&x.2).unwrap().clone(), *pos);
state.set_dirty(); state.set_dirty();
@ -1137,13 +1173,13 @@ fn optimize_top_level(
); );
// Add constants and variables from the scope // Add constants and variables from the scope
scope.iter().for_each(|(name, constant, value)| { for (name, constant, value) in scope.iter() {
if !constant { if !constant {
state.push_var(name, AccessMode::ReadWrite, None); state.push_var(name, AccessMode::ReadWrite, None);
} else { } else {
state.push_var(name, AccessMode::ReadOnly, Some(value)); state.push_var(name, AccessMode::ReadOnly, Some(value));
} }
}); }
statements = optimize_stmt_block(statements, &mut state, true, false, true); statements = optimize_stmt_block(statements, &mut state, true, false, true);
statements statements
@ -1169,9 +1205,8 @@ pub fn optimize_into_ast(
// We only need the script library's signatures for optimization purposes // We only need the script library's signatures for optimization purposes
let mut lib2 = crate::Module::new(); let mut lib2 = crate::Module::new();
functions for fn_def in &functions {
.iter() lib2.set_script_fn(crate::ast::ScriptFnDef {
.map(|fn_def| crate::ast::ScriptFnDef {
name: fn_def.name.clone(), name: fn_def.name.clone(),
access: fn_def.access, access: fn_def.access,
body: crate::ast::StmtBlock::NONE, body: crate::ast::StmtBlock::NONE,
@ -1182,33 +1217,25 @@ pub fn optimize_into_ast(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,
})
.for_each(|fn_def| {
lib2.set_script_fn(fn_def);
}); });
}
let lib2 = &[&lib2]; let lib2 = &[&lib2];
functions for fn_def in functions {
.into_iter()
.map(|fn_def| {
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
// Optimize the function body // Optimize the function body
let body = mem::take(&mut *fn_def.body); let body = mem::take(&mut *fn_def.body);
*fn_def.body = *fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level);
optimize_top_level(body, engine, scope, lib2, optimization_level);
fn_def
})
.for_each(|fn_def| {
module.set_script_fn(fn_def); module.set_script_fn(fn_def);
}); }
} else { } else {
functions.into_iter().for_each(|fn_def| { for fn_def in functions {
module.set_script_fn(fn_def); module.set_script_fn(fn_def);
}); }
} }
module module

View File

@ -1,5 +1,4 @@
#![cfg(not(feature = "no_index"))] #![cfg(not(feature = "no_index"))]
#![allow(non_snake_case)]
use crate::engine::OP_EQUALS; use crate::engine::OP_EQUALS;
use crate::eval::{calc_index, calc_offset_len}; use crate::eval::{calc_index, calc_offset_len};

View File

@ -1,5 +1,3 @@
#![allow(non_snake_case)]
use crate::eval::calc_index; use crate::eval::calc_index;
use crate::plugin::*; use crate::plugin::*;
use crate::{ use crate::{

View File

@ -1,5 +1,4 @@
#![cfg(not(feature = "no_index"))] #![cfg(not(feature = "no_index"))]
#![allow(non_snake_case)]
use crate::eval::{calc_index, calc_offset_len}; use crate::eval::{calc_index, calc_offset_len};
use crate::plugin::*; use crate::plugin::*;

83
src/packages/debugging.rs Normal file
View File

@ -0,0 +1,83 @@
#![cfg(feature = "debugging")]
use crate::def_package;
use crate::plugin::*;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
#[cfg(not(feature = "no_function"))]
use crate::{Dynamic, NativeCallContext};
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
use crate::Array;
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_object"))]
use crate::Map;
def_package! {
/// Package of basic debugging utilities.
crate::DebuggingPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "debugging", debugging_functions);
}
}
#[export_module]
mod debugging_functions {
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
pub fn stack_trace(ctx: NativeCallContext) -> Array {
if let Some(global) = ctx.global_runtime_state() {
global
.debugger
.call_stack()
.iter()
.rev()
.map(
|frame @ crate::debugger::CallStackFrame {
fn_name: _fn_name,
args: _args,
source: _source,
pos: _pos,
}| {
let display = frame.to_string();
#[cfg(not(feature = "no_object"))]
{
let mut map = Map::new();
map.insert("display".into(), display.into());
map.insert("fn_name".into(), _fn_name.into());
if !_args.is_empty() {
map.insert(
"args".into(),
Dynamic::from_array(_args.clone().to_vec()),
);
}
if !_source.is_empty() {
map.insert("source".into(), _source.into());
}
if !_pos.is_none() {
map.insert(
"line".into(),
(_pos.line().unwrap() as crate::INT).into(),
);
map.insert(
"position".into(),
(_pos.position().unwrap_or(0) as crate::INT).into(),
);
}
Dynamic::from_map(map)
}
#[cfg(feature = "no_object")]
display.into()
},
)
.collect()
} else {
Array::new()
}
}
}

View File

@ -92,13 +92,14 @@ fn collect_fn_metadata(
// Create a metadata record for a function. // Create a metadata record for a function.
fn make_metadata( fn make_metadata(
dict: &BTreeSet<Identifier>, dict: &BTreeSet<Identifier>,
namespace: Option<Identifier>, #[cfg(not(feature = "no_module"))] namespace: Option<Identifier>,
func: &ScriptFnDef, func: &ScriptFnDef,
) -> Map { ) -> Map {
const DICT: &str = "key exists"; const DICT: &str = "key exists";
let mut map = Map::new(); let mut map = Map::new();
#[cfg(not(feature = "no_module"))]
if let Some(ns) = namespace { if let Some(ns) = namespace {
map.insert(dict.get("namespace").expect(DICT).clone(), ns.into()); map.insert(dict.get("namespace").expect(DICT).clone(), ns.into());
} }
@ -133,6 +134,7 @@ fn collect_fn_metadata(
// Intern strings // Intern strings
let dict: BTreeSet<Identifier> = [ let dict: BTreeSet<Identifier> = [
#[cfg(not(feature = "no_module"))]
"namespace", "namespace",
"name", "name",
"access", "access",
@ -150,21 +152,52 @@ fn collect_fn_metadata(
ctx.iter_namespaces() ctx.iter_namespaces()
.flat_map(Module::iter_script_fn) .flat_map(Module::iter_script_fn)
.filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f)) .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); .for_each(|(_, _, _, _, f)| {
list.push(
make_metadata(
&dict,
#[cfg(not(feature = "no_module"))]
None,
f,
)
.into(),
)
});
ctx.engine() ctx.engine()
.global_modules .global_modules
.iter() .iter()
.flat_map(|m| m.iter_script_fn()) .flat_map(|m| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f)) .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); .for_each(|(_, _, _, _, f)| {
list.push(
make_metadata(
&dict,
#[cfg(not(feature = "no_module"))]
None,
f,
)
.into(),
)
});
#[cfg(not(feature = "no_module"))]
ctx.engine() ctx.engine()
.global_sub_modules .global_sub_modules
.values() .values()
.flat_map(|m| m.iter_script_fn()) .flat_map(|m| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f)) .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); .for_each(|(_, _, _, _, f)| {
list.push(
make_metadata(
&dict,
#[cfg(not(feature = "no_module"))]
None,
f,
)
.into(),
)
});
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -189,7 +222,7 @@ fn collect_fn_metadata(
.for_each(|(_, _, _, _, f)| { .for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into()) list.push(make_metadata(dict, Some(namespace.clone()), f).into())
}); });
module.iter_sub_modules().for_each(|(ns, m)| { for (ns, m) in module.iter_sub_modules() {
let ns = format!( let ns = format!(
"{}{}{}", "{}{}{}",
namespace, namespace,
@ -197,11 +230,12 @@ fn collect_fn_metadata(
ns ns
); );
scan_module(list, dict, ns.into(), m.as_ref(), filter) scan_module(list, dict, ns.into(), m.as_ref(), filter)
}); }
} }
ctx.iter_imports_raw() for (ns, m) in ctx.iter_imports_raw() {
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)); scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)
}
} }
list list

View File

@ -1,5 +1,3 @@
#![allow(non_snake_case)]
use crate::def_package; use crate::def_package;
use crate::plugin::*; use crate::plugin::*;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]

View File

@ -6,6 +6,7 @@ pub(crate) mod arithmetic;
pub(crate) mod array_basic; pub(crate) mod array_basic;
mod bit_field; mod bit_field;
pub(crate) mod blob_basic; pub(crate) mod blob_basic;
mod debugging;
mod fn_basic; mod fn_basic;
mod iter_basic; mod iter_basic;
mod lang_core; mod lang_core;
@ -24,6 +25,8 @@ pub use array_basic::BasicArrayPackage;
pub use bit_field::BitFieldPackage; pub use bit_field::BitFieldPackage;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub use blob_basic::BasicBlobPackage; pub use blob_basic::BasicBlobPackage;
#[cfg(feature = "debugging")]
pub use debugging::DebuggingPackage;
pub use fn_basic::BasicFnPackage; pub use fn_basic::BasicFnPackage;
pub use iter_basic::BasicIteratorPackage; pub use iter_basic::BasicIteratorPackage;
pub use lang_core::LanguageCorePackage; pub use lang_core::LanguageCorePackage;

View File

@ -13,6 +13,7 @@ def_package! {
/// * [`BasicStringPackage`][super::BasicStringPackage] /// * [`BasicStringPackage`][super::BasicStringPackage]
/// * [`BasicIteratorPackage`][super::BasicIteratorPackage] /// * [`BasicIteratorPackage`][super::BasicIteratorPackage]
/// * [`BasicFnPackage`][super::BasicFnPackage] /// * [`BasicFnPackage`][super::BasicFnPackage]
/// * [`DebuggingPackage`][super::DebuggingPackage]
crate::CorePackage => |lib| { crate::CorePackage => |lib| {
lib.standard = true; lib.standard = true;
@ -21,5 +22,7 @@ def_package! {
super::BasicStringPackage::init(lib); super::BasicStringPackage::init(lib);
super::BasicIteratorPackage::init(lib); super::BasicIteratorPackage::init(lib);
super::BasicFnPackage::init(lib); super::BasicFnPackage::init(lib);
#[cfg(feature = "debugging")]
super::DebuggingPackage::init(lib);
} }
} }

View File

@ -1,5 +1,3 @@
#![allow(non_snake_case)]
use crate::plugin::*; use crate::plugin::*;
use crate::{def_package, FnPtr, INT}; use crate::{def_package, FnPtr, INT};
use std::fmt::{Binary, LowerHex, Octal}; use std::fmt::{Binary, LowerHex, Octal};

View File

@ -1,5 +1,3 @@
#![allow(non_snake_case)]
use crate::plugin::*; use crate::plugin::*;
use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, RhaiResultOf, StaticVec, INT}; use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, RhaiResultOf, StaticVec, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]

View File

@ -3,12 +3,11 @@
use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::custom_syntax::{markers::*, CustomSyntax};
use crate::api::options::LanguageOptions; use crate::api::options::LanguageOptions;
use crate::ast::{ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
StmtBlock, AST_OPTION_FLAGS::*, OpAssignment, ScriptFnDef, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
}; };
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::func::hashing::get_hasher; use crate::func::hashing::get_hasher;
use crate::module::Namespace;
use crate::tokenizer::{ use crate::tokenizer::{
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl, TokenizerControl,
@ -16,9 +15,8 @@ use crate::tokenizer::{
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::types::StringsInterner; use crate::types::StringsInterner;
use crate::{ use crate::{
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Dynamic, Engine, ExclusiveRange, calc_fn_hash, Dynamic, Engine, ExclusiveRange, Identifier, ImmutableString, InclusiveRange,
Identifier, ImmutableString, InclusiveRange, LexError, ParseError, Position, Scope, Shared, LexError, ParseError, Position, Scope, Shared, StaticVec, AST, INT, PERR,
StaticVec, AST, INT, PERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -63,7 +61,7 @@ pub struct ParseState<'e> {
pub allow_capture: bool, pub allow_capture: bool,
/// Encapsulates a local stack with imported [module][crate::Module] names. /// Encapsulates a local stack with imported [module][crate::Module] names.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub modules: StaticVec<Identifier>, pub imports: StaticVec<Identifier>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub max_expr_depth: Option<NonZeroUsize>, pub max_expr_depth: Option<NonZeroUsize>,
@ -94,7 +92,7 @@ impl<'e> ParseState<'e> {
stack: StaticVec::new_const(), stack: StaticVec::new_const(),
entry_stack_len: 0, entry_stack_len: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: StaticVec::new_const(), imports: StaticVec::new_const(),
} }
} }
@ -158,7 +156,7 @@ impl<'e> ParseState<'e> {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> { pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> {
self.modules self.imports
.iter() .iter()
.rev() .rev()
.enumerate() .enumerate()
@ -266,18 +264,23 @@ impl Expr {
#[must_use] #[must_use]
fn into_property(self, state: &mut ParseState) -> Self { fn into_property(self, state: &mut ParseState) -> Self {
match self { match self {
Self::Variable(_, pos, x) if x.1.is_none() => { #[cfg(not(feature = "no_module"))]
Self::Variable(_, _, ref x) if x.1.is_some() => self,
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);
let hash_get = calc_fn_hash(&getter, 1); let hash_get = calc_fn_hash(&getter, 1);
let setter = state.get_identifier(crate::engine::FN_SET, &ident); let setter = state.get_identifier(crate::engine::FN_SET, &ident);
let hash_set = calc_fn_hash(&setter, 2); let hash_set = calc_fn_hash(&setter, 2);
Self::Property(Box::new(( Self::Property(
Box::new((
(getter, hash_get), (getter, hash_get),
(setter, hash_set), (setter, hash_set),
(state.get_interned_string("", &ident), pos), state.get_interned_string("", &ident),
))) )),
pos,
)
} }
_ => self, _ => self,
} }
@ -449,7 +452,7 @@ fn parse_fn_call(
lib: &mut FnLib, lib: &mut FnLib,
id: Identifier, id: Identifier,
capture_parent_scope: bool, capture_parent_scope: bool,
namespace: Option<Namespace>, #[cfg(not(feature = "no_module"))] namespace: Option<crate::module::Namespace>,
settings: ParseSettings, settings: ParseSettings,
) -> ParseResult<Expr> { ) -> ParseResult<Expr> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -457,6 +460,7 @@ fn parse_fn_call(
let (token, token_pos) = input.peek().expect(NEVER_ENDS); let (token, token_pos) = input.peek().expect(NEVER_ENDS);
#[cfg(not(feature = "no_module"))]
let mut namespace = namespace; let mut namespace = namespace;
let mut args = StaticVec::new_const(); let mut args = StaticVec::new_const();
@ -475,9 +479,8 @@ fn parse_fn_call(
Token::RightParen => { Token::RightParen => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
let hash = if let Some(modules) = namespace.as_mut() {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ let hash = if let Some(modules) = namespace.as_mut() {
let index = state.find_module(&modules[0].name); let index = state.find_module(&modules[0].name);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -486,17 +489,19 @@ fn parse_fn_call(
let relax = false; let relax = false;
if !relax && settings.strict_var && index.is_none() { if !relax && settings.strict_var && index.is_none() {
return Err(PERR::ModuleUndefined(modules[0].name.to_string()) return Err(
.into_err(modules[0].pos)); PERR::ModuleUndefined(modules[0].name.to_string()).into_err(modules[0].pos)
);
} }
modules.set_index(index); modules.set_index(index);
}
calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) crate::calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0)
} else { } else {
calc_fn_hash(&id, 0) calc_fn_hash(&id, 0)
}; };
#[cfg(feature = "no_module")]
let hash = calc_fn_hash(&id, 0);
let hashes = if is_valid_function_name(&id) { let hashes = if is_valid_function_name(&id) {
hash.into() hash.into()
@ -509,6 +514,7 @@ fn parse_fn_call(
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_identifier("", id), name: state.get_identifier("", id),
capture_parent_scope, capture_parent_scope,
#[cfg(not(feature = "no_module"))]
namespace, namespace,
hashes, hashes,
args, args,
@ -534,9 +540,8 @@ fn parse_fn_call(
(Token::RightParen, _) => { (Token::RightParen, _) => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
let hash = if let Some(modules) = namespace.as_mut() {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ let hash = if let Some(modules) = namespace.as_mut() {
let index = state.find_module(&modules[0].name); let index = state.find_module(&modules[0].name);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -550,12 +555,17 @@ fn parse_fn_call(
} }
modules.set_index(index); modules.set_index(index);
}
calc_qualified_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len()) crate::calc_qualified_fn_hash(
modules.iter().map(|m| m.name.as_str()),
&id,
args.len(),
)
} else { } else {
calc_fn_hash(&id, args.len()) calc_fn_hash(&id, args.len())
}; };
#[cfg(feature = "no_module")]
let hash = calc_fn_hash(&id, args.len());
let hashes = if is_valid_function_name(&id) { let hashes = if is_valid_function_name(&id) {
hash.into() hash.into()
@ -568,6 +578,7 @@ fn parse_fn_call(
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_identifier("", id), name: state.get_identifier("", id),
capture_parent_scope, capture_parent_scope,
#[cfg(not(feature = "no_module"))]
namespace, namespace,
hashes, hashes,
args, args,
@ -985,8 +996,8 @@ fn parse_switch(
} }
} }
let mut table = BTreeMap::<u64, Box<(Option<Expr>, StmtBlock)>>::new(); let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
let mut ranges = StaticVec::<(INT, INT, bool, Option<Expr>, StmtBlock)>::new(); let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new();
let mut def_pos = Position::NONE; let mut def_pos = Position::NONE;
let mut def_stmt = None; let mut def_stmt = None;
@ -1050,7 +1061,7 @@ fn parse_switch(
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
if table.contains_key(&hash) { if cases.contains_key(&hash) {
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position())); return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
} }
(Some(hash), None) (Some(hash), None)
@ -1092,18 +1103,20 @@ fn parse_switch(
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
table cases.entry(hash).or_insert_with(|| {
.entry(hash) let block: ConditionalStmtBlock = (condition, stmt).into();
.or_insert_with(|| (condition.clone(), stmt.into()).into()); block.into()
});
} }
// Other range // Other range
_ => ranges.push((range.0, range.1, range.2, condition, stmt.into())), _ => ranges.push((range.0, range.1, range.2, (condition, stmt).into())),
} }
} }
None None
} }
(Some(hash), None) => { (Some(hash), None) => {
table.insert(hash, (condition, stmt.into()).into()); let block: ConditionalStmtBlock = (condition, stmt).into();
cases.insert(hash, block.into());
None None
} }
(None, None) => Some(stmt.into()), (None, None) => Some(stmt.into()),
@ -1133,11 +1146,16 @@ fn parse_switch(
} }
} }
let def_stmt_block = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()); let def_case = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into());
Ok(Stmt::Switch( Ok(Stmt::Switch(
item, item,
(table, def_stmt_block, ranges).into(), SwitchCases {
cases,
def_case,
ranges,
}
.into(),
settings.pos, settings.pos,
)) ))
} }
@ -1328,6 +1346,11 @@ fn parse_primary(
// Identifier // Identifier
Token::Identifier(_) => { Token::Identifier(_) => {
#[cfg(not(feature = "no_module"))]
let none = None;
#[cfg(feature = "no_module")]
let none = ();
let s = match input.next().expect(NEVER_ENDS) { let s = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(s), _) => s, (Token::Identifier(s), _) => s,
token => unreachable!("Token::Identifier expected but gets {:?}", token), token => unreachable!("Token::Identifier expected but gets {:?}", token),
@ -1344,7 +1367,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
None, None,
settings.pos, settings.pos,
(None, None, state.get_identifier("", s)).into(), (None, none, state.get_identifier("", s)).into(),
) )
} }
// Namespace qualification // Namespace qualification
@ -1358,7 +1381,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
None, None,
settings.pos, settings.pos,
(None, None, state.get_identifier("", s)).into(), (None, none, state.get_identifier("", s)).into(),
) )
} }
// Normal variable access // Normal variable access
@ -1379,7 +1402,7 @@ fn parse_primary(
Expr::Variable( Expr::Variable(
short_index, short_index,
settings.pos, settings.pos,
(index, None, state.get_identifier("", s)).into(), (index, none, state.get_identifier("", s)).into(),
) )
} }
} }
@ -1387,6 +1410,11 @@ fn parse_primary(
// Reserved keyword or symbol // Reserved keyword or symbol
Token::Reserved(_) => { Token::Reserved(_) => {
#[cfg(not(feature = "no_module"))]
let none = None;
#[cfg(feature = "no_module")]
let none = ();
let s = match input.next().expect(NEVER_ENDS) { let s = match input.next().expect(NEVER_ENDS) {
(Token::Reserved(s), _) => s, (Token::Reserved(s), _) => s,
token => unreachable!("Token::Reserved expected but gets {:?}", token), token => unreachable!("Token::Reserved expected but gets {:?}", token),
@ -1397,14 +1425,14 @@ fn parse_primary(
Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable(
None, None,
settings.pos, settings.pos,
(None, None, state.get_identifier("", s)).into(), (None, none, state.get_identifier("", s)).into(),
), ),
// Access to `this` as a variable is OK within a function scope // Access to `this` as a variable is OK within a function scope
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( _ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
None, None,
settings.pos, settings.pos,
(None, None, state.get_identifier("", s)).into(), (None, none, state.get_identifier("", s)).into(),
), ),
// Cannot access to `this` as a variable not in a function scope // Cannot access to `this` as a variable not in a function scope
_ if &*s == KEYWORD_THIS => { _ if &*s == KEYWORD_THIS => {
@ -1451,6 +1479,7 @@ fn parse_postfix(
lhs = match (lhs, tail_token) { lhs = match (lhs, tail_token) {
// Qualified function call with ! // Qualified function call with !
#[cfg(not(feature = "no_module"))]
(Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => { (Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => {
return if !match_token(input, Token::LeftParen).0 { return if !match_token(input, Token::LeftParen).0 {
Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string())
@ -1476,17 +1505,37 @@ fn parse_postfix(
_ => (), _ => (),
} }
let (_, namespace, name) = *x; let (_, _ns, name) = *x;
settings.pos = pos; settings.pos = pos;
let ns = namespace.map(|(ns, _)| ns); #[cfg(not(feature = "no_module"))]
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())? let _ns = _ns.map(|(ns, _)| ns);
parse_fn_call(
input,
state,
lib,
name,
true,
#[cfg(not(feature = "no_module"))]
_ns,
settings.level_up(),
)?
} }
// Function call // Function call
(Expr::Variable(_, pos, x), Token::LeftParen) => { (Expr::Variable(_, pos, x), Token::LeftParen) => {
let (_, namespace, name) = *x; let (_, _ns, name) = *x;
let ns = namespace.map(|(ns, _)| ns); #[cfg(not(feature = "no_module"))]
let _ns = _ns.map(|(ns, _)| ns);
settings.pos = pos; settings.pos = pos;
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? parse_fn_call(
input,
state,
lib,
name,
false,
#[cfg(not(feature = "no_module"))]
_ns,
settings.level_up(),
)?
} }
// module access // module access
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1498,7 +1547,7 @@ fn parse_postfix(
if let Some((ref mut namespace, _)) = namespace { if let Some((ref mut namespace, _)) = namespace {
namespace.push(var_name_def); namespace.push(var_name_def);
} else { } else {
let mut ns = Namespace::new(); let mut ns = crate::module::Namespace::new();
ns.push(var_name_def); ns.push(var_name_def);
namespace = Some((ns, 42)); namespace = Some((ns, 42));
} }
@ -1543,6 +1592,7 @@ fn parse_postfix(
} }
// Cache the hash key for namespace-qualified variables // Cache the hash key for namespace-qualified variables
#[cfg(not(feature = "no_module"))]
let namespaced_variable = match lhs { let namespaced_variable = match lhs {
Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x.as_mut()), Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x.as_mut()),
Expr::Index(ref mut x, _, _) | Expr::Dot(ref mut x, _, _) => match x.lhs { Expr::Index(ref mut x, _, _) | Expr::Dot(ref mut x, _, _) => match x.lhs {
@ -1552,8 +1602,9 @@ fn parse_postfix(
_ => None, _ => None,
}; };
#[cfg(not(feature = "no_module"))]
if let Some((_, Some((namespace, hash)), name)) = namespaced_variable { if let Some((_, Some((namespace, hash)), name)) = namespaced_variable {
*hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name); *hash = crate::calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -1692,20 +1743,20 @@ fn make_assignment_stmt(
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> { fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
match expr { match expr {
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs { Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs {
Expr::Property(_) if !term => { Expr::Property(_, _) if !term => {
check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))) check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _)))
} }
Expr::Property(_) => None, Expr::Property(_, _) => None,
// Anything other than a property after dotting (e.g. a method call) is not an l-value // Anything other than a property after dotting (e.g. a method call) is not an l-value
ref e => Some(e.position()), ref e => Some(e.position()),
}, },
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs { Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
_ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), _ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
_ => None, _ => None,
}, },
Expr::Property(_) if parent_is_dot => None, Expr::Property(_, _) if parent_is_dot => None,
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
e if parent_is_dot => Some(e.position()), e if parent_is_dot => Some(e.position()),
_ => None, _ => None,
} }
@ -1719,9 +1770,10 @@ fn make_assignment_stmt(
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
} }
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(None, _, ref x) if x.0.is_none() => { Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) (op_info, (lhs, rhs).into()).into(),
} op_pos,
)),
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(i, var_pos, ref x) => { Expr::Variable(i, var_pos, ref x) => {
let (index, _, name) = x.as_ref(); let (index, _, name) = x.as_ref();
@ -1730,7 +1782,10 @@ fn make_assignment_stmt(
|n| n.get() as usize, |n| n.get() as usize,
); );
match state.stack[state.stack.len() - index].1 { match state.stack[state.stack.len() - index].1 {
AccessMode::ReadWrite => Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)), AccessMode::ReadWrite => Ok(Stmt::Assignment(
(op_info, (lhs, rhs).into()).into(),
op_pos,
)),
// Constant values cannot be assigned to // Constant values cannot be assigned to
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
@ -1749,9 +1804,10 @@ fn make_assignment_stmt(
None => { None => {
match x.lhs { match x.lhs {
// var[???] = rhs, var.??? = rhs // var[???] = rhs, var.??? = rhs
Expr::Variable(_, _, _) => { Expr::Variable(_, _, _) => Ok(Stmt::Assignment(
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) (op_info, (lhs, rhs).into()).into(),
} op_pos,
)),
// expr[???] = rhs, expr.??? = rhs // expr[???] = rhs, expr.??? = rhs
ref expr => { ref expr => {
Err(PERR::AssignmentToInvalidLHS("".to_string()) Err(PERR::AssignmentToInvalidLHS("".to_string())
@ -1826,11 +1882,14 @@ 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.module::id - syntax error // lhs.module::id - syntax error
#[cfg(not(feature = "no_module"))]
(_, Expr::Variable(_, _, x)) => { (_, Expr::Variable(_, _, x)) => {
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos)) 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,
@ -1844,7 +1903,7 @@ fn make_dot_expr(
}; };
match x.lhs { match x.lhs {
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),
rhs: x.rhs, rhs: x.rhs,
@ -2142,7 +2201,19 @@ fn parse_custom_syntax(
let name = state.get_identifier("", name); let name = state.get_identifier("", name);
segments.push(name.clone().into()); segments.push(name.clone().into());
tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_IDENT)); tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_IDENT));
inputs.push(Expr::Variable(None, pos, (None, None, name).into())); inputs.push(Expr::Variable(
None,
pos,
(
None,
#[cfg(not(feature = "no_module"))]
None,
#[cfg(feature = "no_module")]
(),
name,
)
.into(),
));
} }
CUSTOM_SYNTAX_MARKER_SYMBOL => { CUSTOM_SYNTAX_MARKER_SYMBOL => {
let (symbol, pos) = parse_symbol(input)?; let (symbol, pos) = parse_symbol(input)?;
@ -2520,7 +2591,7 @@ fn parse_let(
state.stack.push((name, var_type)); state.stack.push((name, var_type));
let export = if is_export { let export = if is_export {
AST_OPTION_PUBLIC AST_OPTION_EXPORTED
} else { } else {
AST_OPTION_NONE AST_OPTION_NONE
}; };
@ -2564,7 +2635,7 @@ fn parse_import(
// import expr as name ... // import expr as name ...
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
let name = state.get_identifier("", name); let name = state.get_identifier("", name);
state.modules.push(name.clone()); state.imports.push(name.clone());
Ok(Stmt::Import( Ok(Stmt::Import(
expr, expr,
@ -2603,48 +2674,27 @@ fn parse_export(
_ => (), _ => (),
} }
let mut exports = Vec::<(Ident, Ident)>::with_capacity(4);
loop {
let (id, id_pos) = parse_var_name(input)?; let (id, id_pos) = parse_var_name(input)?;
let (rename, rename_pos) = if match_token(input, Token::As).0 { let (alias, alias_pos) = if match_token(input, Token::As).0 {
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
if exports.iter().any(|(_, alias)| alias.name == name.as_ref()) {
return Err(PERR::DuplicatedVariable(name.to_string()).into_err(pos));
}
(Some(name), pos) (Some(name), pos)
} else { } else {
(None, Position::NONE) (None, Position::NONE)
}; };
exports.push(( let export = (
Ident { Ident {
name: state.get_identifier("", id), name: state.get_identifier("", id),
pos: id_pos, pos: id_pos,
}, },
Ident { Ident {
name: state.get_identifier("", rename.as_ref().map_or("", <_>::as_ref)), name: state.get_identifier("", alias.as_ref().map_or("", <_>::as_ref)),
pos: rename_pos, pos: alias_pos,
}, },
)); );
match input.peek().expect(NEVER_ENDS) { Ok(Stmt::Export(export.into(), settings.pos))
(Token::Comma, _) => {
eat_token(input, Token::Comma);
}
(Token::Identifier(_), pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
"to separate the list of exports".into(),
)
.into_err(*pos))
}
_ => break,
}
}
Ok(Stmt::Export(exports.into_boxed_slice(), settings.pos))
} }
/// Parse a statement block. /// Parse a statement block.
@ -2677,7 +2727,7 @@ fn parse_block(
state.entry_stack_len = state.stack.len(); state.entry_stack_len = state.stack.len();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
let prev_mods_len = state.modules.len(); let orig_imports_len = state.imports.len();
loop { loop {
// Terminated? // Terminated?
@ -2744,7 +2794,7 @@ fn parse_block(
state.entry_stack_len = prev_entry_stack_len; state.entry_stack_len = prev_entry_stack_len;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
state.modules.truncate(prev_mods_len); state.imports.truncate(orig_imports_len);
Ok(Stmt::Block(statements.into_boxed_slice(), settings.pos)) Ok(Stmt::Block(statements.into_boxed_slice(), settings.pos))
} }
@ -2925,7 +2975,7 @@ fn parse_stmt(
} }
Token::Break if settings.default_options.allow_loop && settings.is_breakable => { Token::Break if settings.default_options.allow_loop && settings.is_breakable => {
let pos = eat_token(input, Token::Break); let pos = eat_token(input, Token::Break);
Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos)) Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos))
} }
Token::Continue | Token::Break if settings.default_options.allow_loop => { Token::Continue | Token::Break if settings.default_options.allow_loop => {
Err(PERR::LoopBreak.into_err(token_pos)) Err(PERR::LoopBreak.into_err(token_pos))
@ -2937,7 +2987,7 @@ fn parse_stmt(
.map(|(token, pos)| { .map(|(token, pos)| {
let flags = match token { let flags = match token {
Token::Return => AST_OPTION_NONE, Token::Return => AST_OPTION_NONE,
Token::Throw => AST_OPTION_BREAK_OUT, Token::Throw => AST_OPTION_BREAK,
token => unreachable!( token => unreachable!(
"Token::Return or Token::Throw expected but gets {:?}", "Token::Return or Token::Throw expected but gets {:?}",
token token
@ -2996,10 +3046,10 @@ fn parse_try_catch(
let mut settings = settings; let mut settings = settings;
settings.pos = eat_token(input, Token::Try); settings.pos = eat_token(input, Token::Try);
// try { body } // try { try_block }
let body = parse_block(input, state, lib, settings.level_up())?; let try_block = parse_block(input, state, lib, settings.level_up())?;
// try { body } catch // try { try_block } catch
let (matched, catch_pos) = match_token(input, Token::Catch); let (matched, catch_pos) = match_token(input, Token::Catch);
if !matched { if !matched {
@ -3009,8 +3059,8 @@ fn parse_try_catch(
); );
} }
// try { body } catch ( // try { try_block } catch (
let err_var = if match_token(input, Token::LeftParen).0 { let catch_var = if match_token(input, Token::LeftParen).0 {
let (name, pos) = parse_var_name(input)?; let (name, pos) = parse_var_name(input)?;
let (matched, err_pos) = match_token(input, Token::RightParen); let (matched, err_pos) = match_token(input, Token::RightParen);
@ -3029,16 +3079,21 @@ fn parse_try_catch(
None None
}; };
// try { body } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
let catch_body = parse_block(input, state, lib, settings.level_up())?; let catch_block = parse_block(input, state, lib, settings.level_up())?;
if err_var.is_some() { if catch_var.is_some() {
// Remove the error variable from the stack // Remove the error variable from the stack
state.stack.pop().unwrap(); state.stack.pop().unwrap();
} }
Ok(Stmt::TryCatch( Ok(Stmt::TryCatch(
(body.into(), err_var, catch_body.into()).into(), TryCatchBlock {
try_block: try_block.into(),
catch_var,
catch_block: catch_block.into(),
}
.into(),
settings.pos, settings.pos,
)) ))
} }
@ -3162,12 +3217,21 @@ fn make_curry_from_externals(
args.push(fn_expr); args.push(fn_expr);
args.extend( args.extend(externals.iter().cloned().map(|x| {
externals Expr::Variable(
.iter() None,
.cloned() Position::NONE,
.map(|x| Expr::Variable(None, Position::NONE, (None, None, x).into())), (
); None,
#[cfg(not(feature = "no_module"))]
None,
#[cfg(feature = "no_module")]
(),
x,
)
.into(),
)
}));
let expr = FnCallExpr { let expr = FnCallExpr {
name: state.get_identifier("", crate::engine::KEYWORD_FN_PTR_CURRY), name: state.get_identifier("", crate::engine::KEYWORD_FN_PTR_CURRY),
@ -3185,7 +3249,7 @@ fn make_curry_from_externals(
let mut statements = StaticVec::with_capacity(externals.len() + 1); let mut statements = StaticVec::with_capacity(externals.len() + 1);
statements.extend(externals.into_iter().map(Stmt::Share)); statements.extend(externals.into_iter().map(Stmt::Share));
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr));
Expr::Stmt(StmtBlock::new(statements, pos).into()) Expr::Stmt(crate::ast::StmtBlock::new(statements, pos).into())
} }
/// Parse an anonymous function definition. /// Parse an anonymous function definition.
@ -3451,9 +3515,9 @@ impl Engine {
{ {
let mut m = crate::Module::new(); let mut m = crate::Module::new();
_lib.into_iter().for_each(|fn_def| { for fn_def in _lib {
m.set_script_fn(fn_def); m.set_script_fn(fn_def);
}); }
return Ok(AST::new(statements, m)); return Ok(AST::new(statements, m));
} }

View File

@ -16,6 +16,7 @@ enum FnType {
Native, Native,
} }
#[cfg(not(feature = "no_module"))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
enum FnNamespace { enum FnNamespace {
@ -23,6 +24,7 @@ enum FnNamespace {
Internal, Internal,
} }
#[cfg(not(feature = "no_module"))]
impl From<crate::FnNamespace> for FnNamespace { impl From<crate::FnNamespace> for FnNamespace {
fn from(value: crate::FnNamespace) -> Self { fn from(value: crate::FnNamespace) -> Self {
match value { match value {
@ -62,6 +64,7 @@ struct FnParam<'a> {
struct FnMetadata<'a> { struct FnMetadata<'a> {
pub base_hash: u64, pub base_hash: u64,
pub full_hash: u64, pub full_hash: u64,
#[cfg(not(feature = "no_module"))]
pub namespace: FnNamespace, pub namespace: FnNamespace,
pub access: FnAccess, pub access: FnAccess,
pub name: String, pub name: String,
@ -110,6 +113,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
Self { Self {
base_hash, base_hash,
full_hash, full_hash,
#[cfg(not(feature = "no_module"))]
namespace: info.metadata.namespace.into(), namespace: info.metadata.namespace.into(),
access: info.metadata.access.into(), access: info.metadata.access.into(),
name: info.metadata.name.to_string(), name: info.metadata.name.to_string(),
@ -206,26 +210,35 @@ impl Engine {
let _ast = ast; let _ast = ast;
let mut global = ModuleMetadata::new(); let mut global = ModuleMetadata::new();
self.global_sub_modules.iter().for_each(|(name, m)| { #[cfg(not(feature = "no_module"))]
for (name, m) in &self.global_sub_modules {
global.modules.insert(name, m.as_ref().into()); global.modules.insert(name, m.as_ref().into());
}); }
self.global_modules self.global_modules
.iter() .iter()
.filter(|m| include_packages || !m.standard) .filter(|m| include_packages || !m.standard)
.flat_map(|m| m.iter_fn()) .flat_map(|m| m.iter_fn())
.for_each(|f| { .for_each(|f| {
#[allow(unused_mut)]
let mut meta: FnMetadata = f.into(); let mut meta: FnMetadata = f.into();
#[cfg(not(feature = "no_module"))]
{
meta.namespace = FnNamespace::Global; meta.namespace = FnNamespace::Global;
}
global.functions.push(meta); global.functions.push(meta);
}); });
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ast.shared_lib().iter_fn().for_each(|f| { for f in _ast.shared_lib().iter_fn() {
#[allow(unused_mut)]
let mut meta: FnMetadata = f.into(); let mut meta: FnMetadata = f.into();
#[cfg(not(feature = "no_module"))]
{
meta.namespace = FnNamespace::Global; meta.namespace = FnNamespace::Global;
}
global.functions.push(meta); global.functions.push(meta);
}); }
global.functions.sort(); global.functions.sort();

View File

@ -260,7 +260,11 @@ impl fmt::Display for Position {
impl fmt::Debug for Position { impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "no_position"))] #[cfg(not(feature = "no_position"))]
if self.is_beginning_of_line() {
write!(f, "{}", self.line)?;
} else {
write!(f, "{}:{}", self.line, self.pos)?; write!(f, "{}:{}", self.line, self.pos)?;
}
#[cfg(feature = "no_position")] #[cfg(feature = "no_position")]
f.write_str("none")?; f.write_str("none")?;

View File

@ -1119,18 +1119,18 @@ impl Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(ref mut a, _, ref mut access) => { Union::Array(ref mut a, _, ref mut access) => {
*access = typ; *access = typ;
a.iter_mut().for_each(|v| { for v in a.iter_mut() {
v.set_access_mode(typ); v.set_access_mode(typ);
}); }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Blob(_, _, ref mut access) => *access = typ, Union::Blob(_, _, ref mut access) => *access = typ,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref mut m, _, ref mut access) => { Union::Map(ref mut m, _, ref mut access) => {
*access = typ; *access = typ;
m.values_mut().for_each(|v| { for v in m.values_mut() {
v.set_access_mode(typ); v.set_access_mode(typ);
}); }
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, _, ref mut access) => *access = typ, Union::TimeStamp(_, _, ref mut access) => *access = typ,
@ -1708,14 +1708,14 @@ impl Dynamic {
match self.0 { match self.0 {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell, _, _) => { Union::Shared(ref cell, _, _) => {
let value = crate::func::native::shared_write_lock(cell); let guard = crate::func::native::locked_write(cell);
if (*value).type_id() != TypeId::of::<T>() if (*guard).type_id() != TypeId::of::<T>()
&& TypeId::of::<Dynamic>() != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>()
{ {
return None; return None;
} else { } else {
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value))); return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard)));
} }
} }
_ => (), _ => (),

View File

@ -534,8 +534,12 @@ impl Scope<'_> {
Self::new(), Self::new(),
|mut entries, (index, (name, alias))| { |mut entries, (index, (name, alias))| {
if !entries.names.iter().any(|(key, _)| key == name) { if !entries.names.iter().any(|(key, _)| key == name) {
let orig_value = &self.values[len - 1 - index];
let mut value = orig_value.clone();
value.set_access_mode(orig_value.access_mode());
entries.names.push((name.clone(), alias.clone())); entries.names.push((name.clone(), alias.clone()));
entries.values.push(self.values[len - 1 - index].clone()); entries.values.push(value);
} }
entries entries
}, },
@ -607,9 +611,9 @@ impl Scope<'_> {
impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> { 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) {
iter.into_iter().for_each(|(name, value)| { for (name, value) in iter {
self.push_dynamic_value(name, AccessMode::ReadWrite, value); self.push_dynamic_value(name, AccessMode::ReadWrite, value);
}); }
} }
} }
@ -625,7 +629,7 @@ impl<K: Into<Identifier>> FromIterator<(K, Dynamic)> for Scope<'_> {
impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> { 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) {
iter.into_iter().for_each(|(name, is_constant, value)| { for (name, is_constant, value) in iter {
self.push_dynamic_value( self.push_dynamic_value(
name, name,
if is_constant { if is_constant {
@ -635,7 +639,7 @@ impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
}, },
value, value,
); );
}); }
} }
} }

78
tests/debugging.rs Normal file
View File

@ -0,0 +1,78 @@
#![cfg(feature = "debugging")]
use rhai::{Dynamic, Engine, EvalAltResult, INT};
#[cfg(not(feature = "no_index"))]
use rhai::Array;
#[cfg(not(feature = "no_object"))]
use rhai::Map;
#[test]
fn test_debugging() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
{
let r = engine.eval::<Array>(
"
fn foo(x) {
if x >= 5 {
stack_trace()
} else {
foo(x+1)
}
}
foo(0)
",
)?;
assert_eq!(r.len(), 6);
assert_eq!(engine.eval::<INT>("len(stack_trace())")?, 0);
}
Ok(())
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_debugger_state() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.on_debugger(
|| {
// Say, use an object map for the debugger state
let mut state = Map::new();
// Initialize properties
state.insert("hello".into(), (42 as INT).into());
state.insert("foo".into(), false.into());
Dynamic::from_map(state)
},
|context, _, _, _| {
// Get global runtime state
let global = context.global_runtime_state_mut();
// Get debugger
let debugger = &mut global.debugger;
// Print debugger state - which is an object map
println!("Current state = {}", debugger.state());
// Modify state
let mut state = debugger.state_mut().write_lock::<Map>().unwrap();
let hello = state.get("hello").unwrap().as_int().unwrap();
state.insert("hello".into(), (hello + 1).into());
state.insert("foo".into(), true.into());
state.insert("something_new".into(), "hello, world!".into());
// Continue with debugging
Ok(rhai::debugger::DebuggerCommand::StepInto)
},
);
engine.run("let x = 42;")?;
Ok(())
}

View File

@ -317,11 +317,10 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
foo = calc(foo); foo = calc(foo);
hello = `hello, ${foo} worlds!`; hello = `hello, ${foo} worlds!`;
export export x as abc;
x as abc, export x as xxx;
x as xxx, export foo;
foo, export hello;
hello;
"#, "#,
)?; )?;