commit
bbca1c472b
33
.github/workflows/build.yml
vendored
33
.github/workflows/build.yml
vendored
@ -18,24 +18,25 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
flags:
|
||||
- ""
|
||||
- "--features debugging"
|
||||
- "--features metadata,serde,internals"
|
||||
- "--features unchecked,serde,metadata,internals"
|
||||
- "--features sync,serde,metadata,internals"
|
||||
- "--features no_position,serde,metadata,internals"
|
||||
- "--features no_optimize,serde,metadata,internals"
|
||||
- "--features no_float,serde,metadata,internals"
|
||||
- "--features f32_float,serde,metadata,internals"
|
||||
- "--features decimal,serde,metadata,internals"
|
||||
- "--features unchecked,serde,metadata,internals,debugging"
|
||||
- "--features sync,serde,metadata,internals,debugging"
|
||||
- "--features no_position,serde,metadata,internals,debugging"
|
||||
- "--features no_optimize,serde,metadata,internals,debugging"
|
||||
- "--features no_float,serde,metadata,internals,debugging"
|
||||
- "--features f32_float,serde,metadata,internals,debugging"
|
||||
- "--features decimal,serde,metadata,internals,debugging"
|
||||
- "--features no_float,decimal"
|
||||
- "--tests --features only_i32,serde,metadata,internals"
|
||||
- "--features only_i64,serde,metadata,internals"
|
||||
- "--features no_index,serde,metadata,internals"
|
||||
- "--features no_object,serde,metadata,internals"
|
||||
- "--features no_function,serde,metadata,internals"
|
||||
- "--features no_module,serde,metadata,internals"
|
||||
- "--features no_closure,serde,metadata,internals"
|
||||
- "--features unicode-xid-ident,serde,metadata,internals"
|
||||
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked"
|
||||
- "--tests --features only_i32,serde,metadata,internals,debugging"
|
||||
- "--features only_i64,serde,metadata,internals,debugging"
|
||||
- "--features no_index,serde,metadata,internals,debugging"
|
||||
- "--features no_object,serde,metadata,internals,debugging"
|
||||
- "--features no_function,serde,metadata,internals,debugging"
|
||||
- "--features no_module,serde,metadata,internals,debugging"
|
||||
- "--features no_closure,serde,metadata,internals,debugging"
|
||||
- "--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,debugging"
|
||||
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
|
||||
toolchain: [stable]
|
||||
experimental: [false]
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ Cargo.lock
|
||||
benches/results
|
||||
before*
|
||||
after*
|
||||
.rhai-repl-history.txt
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,20 +1,36 @@
|
||||
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
|
||||
---------
|
||||
|
||||
* In `Scope::clone_visible`, constants are now properly cloned as constants.
|
||||
* 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.
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
* `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.
|
||||
* The `no_module` feature now eliminates large sections of code via feature gates.
|
||||
|
||||
|
||||
Version 1.4.1
|
||||
|
63
Cargo.toml
63
Cargo.toml
@ -22,6 +22,15 @@ num-traits = { version = "0.2", default-features = false }
|
||||
smartstring = { version = "0.2.8", 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]
|
||||
serde_bytes = "0.11"
|
||||
|
||||
@ -44,6 +53,7 @@ no_module = [] # no modules
|
||||
internals = [] # expose internal data structures
|
||||
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
|
||||
debugging = ["internals"] # enable debugging
|
||||
|
||||
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"]
|
||||
stdweb = ["instant/stdweb"]
|
||||
|
||||
[[bin]]
|
||||
name = "rhai-repl"
|
||||
required-features = ["rustyline"]
|
||||
|
||||
[[bin]]
|
||||
name = "rhai-run"
|
||||
|
||||
[[bin]]
|
||||
name = "rhai-dbg"
|
||||
required-features = ["debugging"]
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
#opt-level = "z" # optimize for size
|
||||
#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]
|
||||
instant = { version = "0.1.10" } # WASM implementation of std::time::Instant
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["metadata", "serde", "internals", "decimal"] # compiling for no-std
|
||||
features = ["metadata", "serde", "internals", "decimal", "debugging"]
|
||||
|
@ -43,7 +43,7 @@ Standard features
|
||||
* 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.
|
||||
* 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).
|
||||
* 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.
|
||||
@ -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).
|
||||
* 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).
|
||||
* A [debugging](https://rhai.rs/book/engine/debugging) interface.
|
||||
|
||||
|
||||
Protected against attacks
|
||||
|
@ -17,10 +17,7 @@ struct Handler {
|
||||
}
|
||||
|
||||
fn print_scope(scope: &Scope) {
|
||||
scope
|
||||
.iter_raw()
|
||||
.enumerate()
|
||||
.for_each(|(i, (name, constant, value))| {
|
||||
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")]
|
||||
@ -34,7 +31,7 @@ fn print_scope(scope: &Scope) {
|
||||
value_is_shared,
|
||||
*value.read_lock::<Dynamic>().unwrap(),
|
||||
)
|
||||
});
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,7 @@ struct Handler {
|
||||
}
|
||||
|
||||
fn print_scope(scope: &Scope) {
|
||||
scope
|
||||
.iter_raw()
|
||||
.enumerate()
|
||||
.for_each(|(i, (name, constant, value))| {
|
||||
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")]
|
||||
@ -30,7 +27,7 @@ fn print_scope(scope: &Scope) {
|
||||
value_is_shared,
|
||||
*value.read_lock::<Dynamic>().unwrap(),
|
||||
)
|
||||
});
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,7 @@ struct Handler {
|
||||
}
|
||||
|
||||
fn print_scope(scope: &Scope) {
|
||||
scope
|
||||
.iter_raw()
|
||||
.enumerate()
|
||||
.for_each(|(i, (name, constant, value))| {
|
||||
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")]
|
||||
@ -33,7 +30,7 @@ fn print_scope(scope: &Scope) {
|
||||
value_is_shared,
|
||||
*value.read_lock::<Dynamic>().unwrap(),
|
||||
)
|
||||
});
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,8 @@ impl Engine {
|
||||
arg_values: impl AsMut<[Dynamic]>,
|
||||
) -> RhaiResult {
|
||||
let state = &mut EvalState::new();
|
||||
let global = &mut GlobalRuntimeState::new();
|
||||
let global = &mut GlobalRuntimeState::new(self);
|
||||
|
||||
let statements = ast.statements();
|
||||
|
||||
let orig_scope_len = scope.len();
|
||||
|
@ -72,7 +72,9 @@ impl Expression<'_> {
|
||||
#[must_use]
|
||||
pub fn get_string_value(&self) -> Option<&str> {
|
||||
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()),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> 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)?;
|
||||
|
||||
|
@ -12,8 +12,7 @@ impl Engine {
|
||||
///
|
||||
/// The callback function signature takes the following form:
|
||||
///
|
||||
/// > `Fn(name: &str, index: usize, context: &EvalContext)`
|
||||
/// > ` -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
|
||||
/// > `Fn(name: &str, index: usize, context: &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>`
|
||||
///
|
||||
/// where:
|
||||
/// * `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
|
||||
}
|
||||
/// _(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.
|
||||
///
|
||||
/// # Callback Function Signature
|
||||
@ -261,4 +260,23 @@ impl Engine {
|
||||
self.debug = Some(Box::new(callback));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1037,9 +1037,10 @@ impl Engine {
|
||||
|
||||
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(
|
||||
self.global_modules
|
||||
|
@ -44,8 +44,8 @@ impl Engine {
|
||||
/// Evaluate an [`AST`] with own scope, returning any error (if any).
|
||||
#[inline]
|
||||
pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> {
|
||||
let global = &mut GlobalRuntimeState::new();
|
||||
let mut state = EvalState::new();
|
||||
let state = &mut EvalState::new();
|
||||
let global = &mut GlobalRuntimeState::new(self);
|
||||
global.source = ast.source_raw().clone();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -64,7 +64,7 @@ impl Engine {
|
||||
} else {
|
||||
&lib
|
||||
};
|
||||
self.eval_global_statements(scope, global, &mut state, statements, lib, 0)?;
|
||||
self.eval_global_statements(scope, global, state, statements, lib, 0)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -811,7 +811,7 @@ impl AsRef<crate::Shared<crate::Module>> for AST {
|
||||
|
||||
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Hash)]
|
||||
pub enum ASTNode<'a> {
|
||||
/// A statement ([`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<'_> {
|
||||
/// Get the [`Position`] of this [`ASTNode`].
|
||||
pub const fn position(&self) -> Position {
|
||||
|
@ -3,7 +3,6 @@
|
||||
use super::{ASTNode, Ident, Stmt, StmtBlock};
|
||||
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
|
||||
use crate::func::hashing::ALT_ZERO_HASH;
|
||||
use crate::module::Namespace;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, StaticVec, INT};
|
||||
@ -33,6 +32,16 @@ pub struct BinaryExpr {
|
||||
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.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
@ -159,7 +168,10 @@ impl FnCallHashes {
|
||||
#[derive(Debug, Clone, Default, Hash)]
|
||||
pub struct FnCallExpr {
|
||||
/// 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.
|
||||
pub name: Identifier,
|
||||
/// Pre-calculated hashes.
|
||||
@ -183,10 +195,15 @@ pub struct FnCallExpr {
|
||||
|
||||
impl FnCallExpr {
|
||||
/// Does this function call contain a qualified namespace?
|
||||
///
|
||||
/// Always `false` under `no_module`.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
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`].
|
||||
#[inline(always)]
|
||||
@ -358,15 +375,18 @@ pub enum Expr {
|
||||
Variable(
|
||||
Option<NonZeroU8>,
|
||||
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(
|
||||
Box<(
|
||||
(Identifier, u64),
|
||||
(Identifier, u64),
|
||||
(ImmutableString, Position),
|
||||
)>,
|
||||
Box<((Identifier, u64), (Identifier, u64), ImmutableString)>,
|
||||
Position,
|
||||
),
|
||||
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
|
||||
///
|
||||
@ -378,9 +398,9 @@ pub enum Expr {
|
||||
Stmt(Box<StmtBlock>),
|
||||
/// func `(` expr `,` ... `)`
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// lhs `.` rhs - bool variable is a dummy
|
||||
/// lhs `.` rhs - boolean variable is a dummy
|
||||
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),
|
||||
/// lhs `&&` rhs
|
||||
And(Box<BinaryExpr>, Position),
|
||||
@ -427,6 +447,8 @@ impl fmt::Debug for Expr {
|
||||
}
|
||||
Self::Variable(i, _, x) => {
|
||||
f.write_str("Variable(")?;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some((_, ref namespace)) = x.1 {
|
||||
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?
|
||||
}
|
||||
@ -436,7 +458,7 @@ impl fmt::Debug for Expr {
|
||||
}
|
||||
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::Stmt(x) => {
|
||||
f.write_str("ExprStmtBlock")?;
|
||||
@ -444,6 +466,7 @@ impl fmt::Debug for Expr {
|
||||
}
|
||||
Self::FnCall(x, _) => {
|
||||
let mut ff = f.debug_struct("FnCall");
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
x.namespace.as_ref().map(|ns| ff.field("namespace", ns));
|
||||
ff.field("name", &x.name)
|
||||
.field("hash", &x.hashes)
|
||||
@ -595,6 +618,7 @@ impl Expr {
|
||||
|
||||
Union::FnPtr(f, _, _) if !f.is_curried() => Self::FnCall(
|
||||
FnCallExpr {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace: None,
|
||||
name: KEYWORD_FN_PTR.into(),
|
||||
hashes: calc_fn_hash(f.fn_name(), 1).into(),
|
||||
@ -610,20 +634,32 @@ impl Expr {
|
||||
}
|
||||
}
|
||||
/// Is the expression a simple variable access?
|
||||
///
|
||||
/// `non_qualified` is ignored under `no_module`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool {
|
||||
let _non_qualified = non_qualified;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
/// Return the variable name if the expression a simple variable access.
|
||||
///
|
||||
/// `non_qualified` is ignored under `no_module`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> {
|
||||
let _non_qualified = non_qualified;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -648,9 +684,9 @@ impl Expr {
|
||||
| Self::FnCall(_, pos)
|
||||
| Self::Index(_, _, 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::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(),
|
||||
@ -679,9 +715,9 @@ impl Expr {
|
||||
| Self::Stack(_, pos)
|
||||
| Self::FnCall(_, 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),
|
||||
}
|
||||
|
||||
@ -781,7 +817,7 @@ impl Expr {
|
||||
_ => false,
|
||||
},
|
||||
|
||||
Self::Property(_) => match token {
|
||||
Self::Property(_, _) => match token {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => true,
|
||||
Token::LeftParen => true,
|
||||
|
@ -13,8 +13,12 @@ pub enum FnAccess {
|
||||
Private,
|
||||
}
|
||||
|
||||
/// A type that holds a configuration option with bit-flags.
|
||||
/// Exported under the `internals` feature only.
|
||||
/// A type that holds a configuration option with bit-flags. Exported under the `internals` feature
|
||||
/// 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)]
|
||||
pub struct OptionFlags(u8);
|
||||
|
||||
@ -120,19 +124,20 @@ pub mod AST_OPTION_FLAGS {
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is constant.
|
||||
/// Exported under the `internals` feature only.
|
||||
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.
|
||||
pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode.
|
||||
pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode
|
||||
/// (meaning whatever information is the opposite).
|
||||
/// Exported under the `internals` feature only.
|
||||
pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100);
|
||||
/// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow.
|
||||
/// 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.
|
||||
/// Exported under the `internals` feature only.
|
||||
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 {
|
||||
@ -158,9 +163,9 @@ pub mod AST_OPTION_FLAGS {
|
||||
|
||||
f.write_str("(")?;
|
||||
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_BREAK_OUT, "Break")?;
|
||||
write_option(self, f, num_flags, AST_OPTION_BREAK, "Break")?;
|
||||
f.write_str(")")?;
|
||||
|
||||
Ok(())
|
||||
|
@ -13,7 +13,7 @@ pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
|
||||
pub use ident::Ident;
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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"))]
|
||||
pub use expr::FloatWrapper;
|
||||
|
169
src/ast/stmt.rs
169
src/ast/stmt.rs
@ -1,6 +1,6 @@
|
||||
//! 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::tokenizer::Token;
|
||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
||||
@ -23,6 +23,8 @@ pub struct OpAssignment<'a> {
|
||||
/// Hash of the underlying operator call (for fallback).
|
||||
pub hash_op: u64,
|
||||
/// Op-assignment operator.
|
||||
pub op_assign: &'a str,
|
||||
/// Underlying operator.
|
||||
pub op: &'a str,
|
||||
}
|
||||
|
||||
@ -51,7 +53,8 @@ impl OpAssignment<'_> {
|
||||
Self {
|
||||
hash_op_assign: calc_fn_hash(op.literal_syntax(), 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.
|
||||
@ -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.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Block")?;
|
||||
@ -211,15 +280,7 @@ pub enum Stmt {
|
||||
/// 0) Hash table for (condition, block)
|
||||
/// 1) Default block
|
||||
/// 2) List of ranges: (start, end, inclusive, condition, statement)
|
||||
Switch(
|
||||
Expr,
|
||||
Box<(
|
||||
BTreeMap<u64, Box<(Option<Expr>, StmtBlock)>>,
|
||||
StmtBlock,
|
||||
StaticVec<(INT, INT, bool, Option<Expr>, StmtBlock)>,
|
||||
)>,
|
||||
Position,
|
||||
),
|
||||
Switch(Expr, Box<SwitchCases>, Position),
|
||||
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
|
||||
///
|
||||
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
|
||||
@ -228,8 +289,8 @@ pub enum Stmt {
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while`
|
||||
/// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until`
|
||||
/// * [`AST_OPTION_NONE`] = `while`
|
||||
/// * [`AST_OPTION_NEGATED`] = `until`
|
||||
Do(Box<StmtBlock>, Expr, OptionFlags, Position),
|
||||
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
||||
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
|
||||
@ -237,11 +298,11 @@ pub enum Stmt {
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export`
|
||||
/// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const`
|
||||
/// * [`AST_OPTION_EXPORTED`] = `export`
|
||||
/// * [`AST_OPTION_CONSTANT`] = `const`
|
||||
Var(Expr, Box<Ident>, OptionFlags, Position),
|
||||
/// expr op`=` expr
|
||||
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
|
||||
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
|
||||
/// func `(` expr `,` ... `)`
|
||||
///
|
||||
/// 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`;` ... `}`
|
||||
Block(Box<[Stmt]>, Position),
|
||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||
TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position),
|
||||
TryCatch(Box<TryCatchBlock>, Position),
|
||||
/// [expression][Expr]
|
||||
Expr(Expr),
|
||||
/// `continue`/`break`
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue`
|
||||
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break`
|
||||
/// * [`AST_OPTION_NONE`] = `continue`
|
||||
/// * [`AST_OPTION_BREAK`] = `break`
|
||||
BreakLoop(OptionFlags, Position),
|
||||
/// `return`/`throw`
|
||||
///
|
||||
/// ### Option Flags
|
||||
///
|
||||
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return`
|
||||
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw`
|
||||
/// * [`AST_OPTION_NONE`] = `return`
|
||||
/// * [`AST_OPTION_BREAK`] = `throw`
|
||||
Return(OptionFlags, Option<Expr>, Position),
|
||||
/// `import` expr `as` var
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Import(Expr, Option<Box<Ident>>, Position),
|
||||
/// `export` var `as` var `,` ...
|
||||
/// `export` var `as` var
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Export(Box<[(Ident, Ident)]>, Position),
|
||||
Export(Box<(Ident, Ident)>, Position),
|
||||
/// Convert a variable to shared.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
@ -440,26 +501,26 @@ impl Stmt {
|
||||
Self::Expr(expr) => expr.is_pure(),
|
||||
Self::If(condition, x, _) => {
|
||||
condition.is_pure()
|
||||
&& (x.0).0.iter().all(Stmt::is_pure)
|
||||
&& (x.1).0.iter().all(Stmt::is_pure)
|
||||
&& x.0.iter().all(Stmt::is_pure)
|
||||
&& x.1.iter().all(Stmt::is_pure)
|
||||
}
|
||||
Self::Switch(expr, x, _) => {
|
||||
expr.is_pure()
|
||||
&& x.0.values().all(|block| {
|
||||
block.0.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||
&& (block.1).0.iter().all(Stmt::is_pure)
|
||||
&& x.cases.values().all(|block| {
|
||||
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||
&& block.statements.iter().all(Stmt::is_pure)
|
||||
})
|
||||
&& (x.2).iter().all(|(_, _, _, condition, stmt)| {
|
||||
condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||
&& stmt.0.iter().all(Stmt::is_pure)
|
||||
&& x.ranges.iter().all(|(_, _, _, block)| {
|
||||
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||
&& 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.
|
||||
Self::While(Expr::BoolConstant(false, _), _, _) => true,
|
||||
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)
|
||||
}
|
||||
@ -469,13 +530,13 @@ impl Stmt {
|
||||
|
||||
// For loops can be pure because if the iterable is pure, it is finite,
|
||||
// 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::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
|
||||
Self::BreakLoop(_, _) | Self::Return(_, _, _) => false,
|
||||
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"))]
|
||||
@ -571,12 +632,12 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &(x.0).0 {
|
||||
for s in x.0.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in &(x.1).0 {
|
||||
for s in x.1.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -586,27 +647,37 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for b in x.0.values() {
|
||||
if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
|
||||
for b in x.cases.values() {
|
||||
if !b
|
||||
.condition
|
||||
.as_ref()
|
||||
.map(|e| e.walk(path, on_node))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for s in &(b.1).0 {
|
||||
for s in b.statements.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (_, _, _, c, stmt) in &x.2 {
|
||||
if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
|
||||
for (_, _, _, b) in &x.ranges {
|
||||
if !b
|
||||
.condition
|
||||
.as_ref()
|
||||
.map(|e| e.walk(path, on_node))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for s in &stmt.0 {
|
||||
for s in b.statements.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for s in &(x.1).0 {
|
||||
for s in x.def_case.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -626,17 +697,17 @@ impl Stmt {
|
||||
if !e.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
for s in &(x.2).0 {
|
||||
for s in x.2.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Assignment(x, _) => {
|
||||
if !x.0.walk(path, on_node) {
|
||||
if !x.1.lhs.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
if !x.2.walk(path, on_node) {
|
||||
if !x.1.rhs.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -655,12 +726,12 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
Self::TryCatch(x, _) => {
|
||||
for s in &(x.0).0 {
|
||||
for s in x.try_block.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for s in &(x.2).0 {
|
||||
for s in x.catch_block.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
|
@ -3,10 +3,38 @@ Rhai Tools
|
||||
|
||||
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
|
||||
----------
|
||||
|
||||
```bash
|
||||
```sh
|
||||
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
543
src/bin/rhai-dbg.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
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::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{stdin, stdout, Read, Write},
|
||||
path::Path,
|
||||
process::exit,
|
||||
};
|
||||
use std::{env, fs::File, io::Read, path::Path, process::exit};
|
||||
|
||||
const HISTORY_FILE: &str = ".rhai-repl-history";
|
||||
|
||||
/// Pretty-print error.
|
||||
fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
@ -44,7 +44,9 @@ fn print_error(input: &str, mut err: EvalAltResult) {
|
||||
/// Print help text.
|
||||
fn print_help() {
|
||||
println!("help => print this help");
|
||||
println!("keys => print list of key bindings");
|
||||
println!("quit, exit => quit");
|
||||
println!("history => print lines history");
|
||||
println!("scope => print all variables in the scope");
|
||||
println!("strict => toggle on/off Strict Variables Mode");
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -55,16 +57,51 @@ fn print_help() {
|
||||
println!("json => output all functions in JSON format");
|
||||
println!("ast => print the last AST (optimized)");
|
||||
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!();
|
||||
}
|
||||
|
||||
/// Display the scope.
|
||||
fn print_scope(scope: &Scope) {
|
||||
scope
|
||||
.iter_raw()
|
||||
.enumerate()
|
||||
.for_each(|(i, (name, constant, value))| {
|
||||
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")]
|
||||
@ -78,25 +115,15 @@ fn print_scope(scope: &Scope) {
|
||||
value_is_shared,
|
||||
*value.read_lock::<Dynamic>().unwrap(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
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 specified in the command line.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
fn load_script_files(engine: &mut Engine) {
|
||||
// Load init scripts
|
||||
let mut contents = String::new();
|
||||
let mut has_init_scripts = false;
|
||||
@ -169,8 +196,92 @@ fn main() {
|
||||
if has_init_scripts {
|
||||
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
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
@ -194,6 +305,9 @@ fn main() {
|
||||
// Create scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// REPL line editor setup
|
||||
let mut rl = setup_editor();
|
||||
|
||||
// REPL loop
|
||||
let mut input = String::new();
|
||||
let mut main_ast = AST::empty();
|
||||
@ -203,31 +317,37 @@ fn main() {
|
||||
print_help();
|
||||
|
||||
'main_loop: loop {
|
||||
print!("rhai-repl> ");
|
||||
stdout().flush().expect("couldn't flush stdout");
|
||||
|
||||
input.clear();
|
||||
|
||||
loop {
|
||||
match stdin().read_line(&mut input) {
|
||||
Ok(0) => break 'main_loop,
|
||||
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');
|
||||
let prompt = if input.is_empty() {
|
||||
"rhai-repl> "
|
||||
} 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;
|
||||
}
|
||||
|
||||
print!("> ");
|
||||
stdout().flush().expect("couldn't flush stdout");
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break 'main_loop,
|
||||
|
||||
Err(err) => {
|
||||
eprintln!("Error: {:?}", err);
|
||||
break 'main_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let script = input.trim();
|
||||
@ -242,7 +362,29 @@ fn main() {
|
||||
print_help();
|
||||
continue;
|
||||
}
|
||||
"keys" => {
|
||||
print_keys();
|
||||
continue;
|
||||
}
|
||||
"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() => {
|
||||
engine.set_strict_variables(false);
|
||||
println!("Strict Variables Mode turned OFF.");
|
||||
@ -282,13 +424,14 @@ fn main() {
|
||||
#[cfg(feature = "metadata")]
|
||||
"functions" => {
|
||||
// print a list of all registered functions
|
||||
engine
|
||||
.gen_fn_signatures(false)
|
||||
.into_iter()
|
||||
.for_each(|f| println!("{}", f));
|
||||
for f in engine.gen_fn_signatures(false) {
|
||||
println!("{}", f)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
main_ast.iter_functions().for_each(|f| println!("{}", f));
|
||||
for f in main_ast.iter_functions() {
|
||||
println!("{}", f)
|
||||
}
|
||||
|
||||
println!();
|
||||
continue;
|
||||
@ -343,4 +486,8 @@ fn main() {
|
||||
// Throw away all the statements, leaving only the functions
|
||||
main_ast.clear_statements();
|
||||
}
|
||||
|
||||
rl.save_history(HISTORY_FILE).unwrap();
|
||||
|
||||
println!("Bye!");
|
||||
}
|
||||
|
@ -96,6 +96,7 @@ pub struct Engine {
|
||||
/// A collection of all modules loaded into the global namespace of the Engine.
|
||||
pub(crate) global_modules: StaticVec<Shared<Module>>,
|
||||
/// 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>>,
|
||||
|
||||
/// A module resolution service.
|
||||
@ -115,17 +116,17 @@ pub struct Engine {
|
||||
/// Custom syntax.
|
||||
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
|
||||
/// 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.
|
||||
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
|
||||
|
||||
/// 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.
|
||||
pub(crate) debug: Option<OnDebugCallback>,
|
||||
pub(crate) debug: Option<Box<OnDebugCallback>>,
|
||||
/// Callback closure for progress reporting.
|
||||
#[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.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
@ -137,6 +138,13 @@ pub struct Engine {
|
||||
/// Max limits.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
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 {
|
||||
@ -144,11 +152,11 @@ impl fmt::Debug for Engine {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut f = f.debug_struct("Engine");
|
||||
|
||||
f.field("global_modules", &self.global_modules)
|
||||
.field("global_sub_modules", &self.global_sub_modules);
|
||||
f.field("global_modules", &self.global_modules);
|
||||
|
||||
#[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)
|
||||
.field("disabled_symbols", &self.disabled_symbols)
|
||||
@ -226,7 +234,7 @@ impl Engine {
|
||||
engine.print = Some(Box::new(|s| println!("{}", s)));
|
||||
engine.debug = Some(Box::new(|s, source, pos| {
|
||||
if let Some(source) = source {
|
||||
println!("{}{:?} | {}", source, pos, s);
|
||||
println!("{} @ {:?} | {}", source, pos, s);
|
||||
} else if pos.is_none() {
|
||||
println!("{}", s);
|
||||
} else {
|
||||
@ -253,6 +261,8 @@ impl Engine {
|
||||
pub fn new_raw() -> Self {
|
||||
let mut engine = Self {
|
||||
global_modules: StaticVec::new_const(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
global_sub_modules: BTreeMap::new(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -280,6 +290,9 @@ impl Engine {
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
limits: crate::api::limits::Limits::new(),
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
debugger: None,
|
||||
};
|
||||
|
||||
// Add the global namespace module
|
||||
|
@ -125,6 +125,7 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
target: &mut Target,
|
||||
root: (&str, Position),
|
||||
parent: &Expr,
|
||||
rhs: &Expr,
|
||||
terminate_chaining: bool,
|
||||
idx_values: &mut StaticVec<super::ChainArgument>,
|
||||
@ -132,12 +133,16 @@ impl Engine {
|
||||
level: usize,
|
||||
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
|
||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||
let _parent = parent;
|
||||
let is_ref_mut = target.is_ref();
|
||||
let _terminate_chaining = terminate_chaining;
|
||||
|
||||
// Pop the last index value
|
||||
let idx_val = idx_values.pop().unwrap();
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
let scope = &mut Scope::new();
|
||||
|
||||
match chain_type {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ChainType::Indexing => {
|
||||
@ -150,6 +155,9 @@ impl Engine {
|
||||
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
|
||||
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 idx_pos = x.lhs.position();
|
||||
let rhs_chain = rhs.into();
|
||||
@ -162,7 +170,7 @@ impl Engine {
|
||||
let obj_ptr = &mut obj;
|
||||
|
||||
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,
|
||||
) {
|
||||
Ok((result, true)) if is_obj_temp_val => {
|
||||
@ -195,6 +203,9 @@ impl Engine {
|
||||
}
|
||||
// xxx[rhs] op= new_val
|
||||
_ 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 mut idx_val_for_setter = idx_val.clone();
|
||||
|
||||
@ -236,11 +247,15 @@ impl Engine {
|
||||
Ok((Dynamic::UNIT, true))
|
||||
}
|
||||
// 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,
|
||||
)
|
||||
.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() => {
|
||||
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
|
||||
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,
|
||||
)
|
||||
);
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
global.debugger.reset_status(reset_debugger);
|
||||
|
||||
result
|
||||
}
|
||||
// xxx.fn_name(...) = ???
|
||||
Expr::FnCall(_, _) if new_val.is_some() => {
|
||||
@ -264,10 +290,12 @@ impl Engine {
|
||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||
}
|
||||
// {xxx:map}.id op= ???
|
||||
Expr::Property(x) if target.is::<crate::Map>() && new_val.is_some() => {
|
||||
let (name, pos) = &x.2;
|
||||
Expr::Property(x, pos) if target.is::<crate::Map>() && new_val.is_some() => {
|
||||
#[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 index = name.into();
|
||||
{
|
||||
let val_target = &mut self.get_indexed_mut(
|
||||
global, state, lib, target, index, *pos, true, false, level,
|
||||
@ -282,17 +310,22 @@ impl Engine {
|
||||
Ok((Dynamic::UNIT, true))
|
||||
}
|
||||
// {xxx:map}.id
|
||||
Expr::Property(x) if target.is::<crate::Map>() => {
|
||||
let (name, pos) = &x.2;
|
||||
let index = name.into();
|
||||
Expr::Property(x, pos) if target.is::<crate::Map>() => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||
|
||||
let index = x.2.clone().into();
|
||||
let val = self.get_indexed_mut(
|
||||
global, state, lib, target, index, *pos, false, false, level,
|
||||
)?;
|
||||
Ok((val.take_or_clone(), false))
|
||||
}
|
||||
// xxx.id op= ???
|
||||
Expr::Property(x) if new_val.is_some() => {
|
||||
let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref();
|
||||
Expr::Property(x, pos) if new_val.is_some() => {
|
||||
#[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`");
|
||||
|
||||
if op_info.is_some() {
|
||||
@ -367,8 +400,11 @@ impl Engine {
|
||||
})
|
||||
}
|
||||
// xxx.id
|
||||
Expr::Property(x) => {
|
||||
let ((getter, hash_get), _, (name, pos)) = x.as_ref();
|
||||
Expr::Property(x, pos) => {
|
||||
#[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 args = &mut [target.as_mut()];
|
||||
self.exec_fn_call(
|
||||
@ -401,23 +437,39 @@ impl Engine {
|
||||
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos)
|
||||
if target.is::<crate::Map>() =>
|
||||
{
|
||||
let _node = &x.lhs;
|
||||
|
||||
let val_target = &mut match x.lhs {
|
||||
Expr::Property(ref p) => {
|
||||
let (name, pos) = &p.2;
|
||||
let index = name.into();
|
||||
Expr::Property(ref p, pos) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(
|
||||
scope, global, state, lib, this_ptr, _node, level,
|
||||
)?;
|
||||
|
||||
let index = p.2.clone().into();
|
||||
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
|
||||
Expr::FnCall(ref x, pos) if !x.is_qualified() => {
|
||||
let crate::ast::FnCallExpr { name, hashes, .. } = x.as_ref();
|
||||
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,
|
||||
level,
|
||||
)?;
|
||||
val.into()
|
||||
);
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
global.debugger.reset_status(reset_debugger);
|
||||
|
||||
result?.0.into()
|
||||
}
|
||||
// {xxx:map}.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(_, _) => unreachable!(
|
||||
@ -429,18 +481,24 @@ impl Engine {
|
||||
let rhs_chain = rhs.into();
|
||||
|
||||
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,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*x_pos))
|
||||
}
|
||||
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
|
||||
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => {
|
||||
let _node = &x.lhs;
|
||||
|
||||
match x.lhs {
|
||||
// xxx.prop[expr] | xxx.prop.expr
|
||||
Expr::Property(ref p) => {
|
||||
let ((getter, hash_get), (setter, hash_set), (name, pos)) =
|
||||
p.as_ref();
|
||||
Expr::Property(ref p, pos) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
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 hash_get = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||
let hash_set = crate::ast::FnCallHashes::from_native(*hash_set);
|
||||
@ -451,15 +509,15 @@ impl Engine {
|
||||
let (mut val, _) = self
|
||||
.exec_fn_call(
|
||||
global, state, lib, getter, hash_get, args, is_ref_mut,
|
||||
true, *pos, None, level,
|
||||
true, pos, None, level,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
ERR::ErrorDotExpr(_, _) => {
|
||||
let prop = name.into();
|
||||
self.get_indexed_mut(
|
||||
global, state, lib, target, prop, *pos, false,
|
||||
true, level,
|
||||
global, state, lib, target, prop, pos, false, true,
|
||||
level,
|
||||
)
|
||||
.map(|v| (v.take_or_clone(), false))
|
||||
.map_err(
|
||||
@ -482,6 +540,7 @@ impl Engine {
|
||||
this_ptr,
|
||||
&mut val.into(),
|
||||
root,
|
||||
rhs,
|
||||
&x.rhs,
|
||||
*term,
|
||||
idx_values,
|
||||
@ -498,7 +557,7 @@ impl Engine {
|
||||
let args = &mut arg_values;
|
||||
self.exec_fn_call(
|
||||
global, state, lib, setter, hash_set, args, is_ref_mut,
|
||||
true, *pos, None, level,
|
||||
true, pos, None, level,
|
||||
)
|
||||
.or_else(
|
||||
|err| match *err {
|
||||
@ -513,7 +572,7 @@ impl Engine {
|
||||
);
|
||||
self.exec_fn_call(
|
||||
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 {
|
||||
ERR::ErrorIndexingType(_, _) => {
|
||||
@ -536,14 +595,24 @@ impl Engine {
|
||||
let crate::ast::FnCallExpr { name, hashes, .. } = f.as_ref();
|
||||
let rhs_chain = rhs.into();
|
||||
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();
|
||||
|
||||
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,
|
||||
)
|
||||
.map_err(|err| err.fill_position(pos))
|
||||
@ -594,6 +663,9 @@ impl Engine {
|
||||
match lhs {
|
||||
// id.??? or id[???]
|
||||
Expr::Variable(_, var_pos, x) => {
|
||||
#[cfg(feature = "debugging")]
|
||||
self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, *var_pos)?;
|
||||
|
||||
@ -604,7 +676,7 @@ impl Engine {
|
||||
let root = (x.2.as_str(), *var_pos);
|
||||
|
||||
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,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
@ -618,8 +690,8 @@ impl Engine {
|
||||
let obj_ptr = &mut value.into();
|
||||
let root = ("", expr.position());
|
||||
self.eval_dot_index_chain_helper(
|
||||
global, state, lib, this_ptr, obj_ptr, root, rhs, term, idx_values, chain_type,
|
||||
level, new_val,
|
||||
global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values,
|
||||
chain_type, level, new_val,
|
||||
)
|
||||
.map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v })
|
||||
.map_err(|err| err.fill_position(op_pos))
|
||||
@ -678,10 +750,10 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Property(x) if _parent_chain_type == ChainType::Dotting => {
|
||||
idx_values.push(super::ChainArgument::Property((x.2).1))
|
||||
Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => {
|
||||
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 => {
|
||||
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
||||
@ -689,10 +761,10 @@ impl Engine {
|
||||
// Evaluate in left-to-right order
|
||||
let lhs_arg_val = match lhs {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Property(x) if _parent_chain_type == ChainType::Dotting => {
|
||||
super::ChainArgument::Property((x.2).1)
|
||||
Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => {
|
||||
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"))]
|
||||
Expr::FnCall(x, _)
|
||||
|
455
src/eval/debugger.rs
Normal file
455
src/eval/debugger.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 's, 'ps, 'b, 't, 'pt> {
|
||||
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`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
@ -46,13 +46,14 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> {
|
||||
pub const fn scope(&self) -> &Scope<'px> {
|
||||
self.scope
|
||||
}
|
||||
/// Mutable reference to the current [`Scope`].
|
||||
/// Get a mutable reference to the current [`Scope`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn scope_mut(&mut self) -> &mut &'x mut Scope<'px> {
|
||||
&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"))]
|
||||
#[inline(always)]
|
||||
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`].
|
||||
/// Exported under the `internals` feature only.
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn global_runtime_state(&self) -> &GlobalRuntimeState {
|
||||
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.
|
||||
#[inline]
|
||||
pub fn iter_namespaces(&self) -> impl Iterator<Item = &Module> {
|
||||
|
@ -7,16 +7,17 @@ use std::marker::PhantomData;
|
||||
#[cfg(feature = "no_std")]
|
||||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
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,
|
||||
/// subsequent offsets may become mis-aligned.
|
||||
/// In some situation, e.g. after running an `eval` statement, or after a custom syntax
|
||||
/// statement, subsequent offsets may become mis-aligned.
|
||||
///
|
||||
/// When that happens, this flag is turned on.
|
||||
pub always_search_scope: bool,
|
||||
|
204
src/eval/expr.rs
204
src/eval/expr.rs
@ -3,25 +3,23 @@
|
||||
use super::{EvalContext, EvalState, GlobalRuntimeState, Target};
|
||||
use crate::ast::{Expr, FnCallExpr, OpAssignment};
|
||||
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
||||
use crate::module::Namespace;
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{
|
||||
Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, Shared, StaticVec, ERR,
|
||||
};
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR};
|
||||
use std::num::NonZeroUsize;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// Search for a module within an imports stack.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn search_imports(
|
||||
&self,
|
||||
global: &GlobalRuntimeState,
|
||||
state: &mut EvalState,
|
||||
namespace: &Namespace,
|
||||
) -> Option<Shared<Module>> {
|
||||
namespace: &crate::module::Namespace,
|
||||
) -> Option<crate::Shared<Module>> {
|
||||
let root = &namespace[0].name;
|
||||
|
||||
// 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() {
|
||||
// Normal variable access
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(_, 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
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(_, Some((namespace, hash_var)), var_name) => {
|
||||
if let Some(module) = self.search_imports(global, state, namespace) {
|
||||
// foo:bar::baz::VARIABLE
|
||||
if let Some(module) = self.search_imports(global, state, namespace) {
|
||||
return match module.get_qualified_var(*hash_var) {
|
||||
Ok(target) => {
|
||||
let mut target = target.clone();
|
||||
@ -89,13 +90,13 @@ impl Engine {
|
||||
};
|
||||
}
|
||||
|
||||
// global::VARIABLE
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if namespace.len() == 1 && namespace[0].name == crate::engine::KEYWORD_GLOBAL {
|
||||
// global::VARIABLE
|
||||
let global_constants = global.constants_mut();
|
||||
|
||||
if let Some(mut guard) = global_constants {
|
||||
if let Some(value) = guard.get_mut(var_name) {
|
||||
if let Some(ref constants) = global.constants {
|
||||
if let Some(value) =
|
||||
crate::func::locked_write(constants).get_mut(var_name)
|
||||
{
|
||||
let mut target: Target = value.clone().into();
|
||||
// Module variables are constant
|
||||
target.set_access_mode(AccessMode::ReadOnly);
|
||||
@ -117,9 +118,6 @@ impl Engine {
|
||||
|
||||
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),
|
||||
}
|
||||
@ -211,6 +209,7 @@ impl Engine {
|
||||
) -> RhaiResult {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace,
|
||||
capture_parent_scope: capture,
|
||||
hashes,
|
||||
@ -219,15 +218,17 @@ impl Engine {
|
||||
..
|
||||
} = expr;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some(namespace) = namespace.as_ref() {
|
||||
// Qualified function call
|
||||
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,
|
||||
level,
|
||||
)
|
||||
} else {
|
||||
);
|
||||
}
|
||||
|
||||
// Normal function call
|
||||
let (first_arg, args) = args.split_first().map_or_else(
|
||||
|| (None, args.as_ref()),
|
||||
@ -235,13 +236,19 @@ impl Engine {
|
||||
);
|
||||
|
||||
self.make_function_call(
|
||||
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
|
||||
pos, *capture, level,
|
||||
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes, pos,
|
||||
*capture, level,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -258,16 +265,29 @@ impl Engine {
|
||||
// Function calls should account for a relatively larger portion of expressions because
|
||||
// binary operators are also function calls.
|
||||
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"))]
|
||||
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.
|
||||
// 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.
|
||||
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"))]
|
||||
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"))]
|
||||
self.inc_operations(&mut global.num_operations, expr.position())?;
|
||||
|
||||
match expr {
|
||||
let result = match expr {
|
||||
// Constants
|
||||
Expr::DynamicConstant(x, _) => Ok(x.as_ref().clone()),
|
||||
Expr::IntegerConstant(x, _) => Ok((*x).into()),
|
||||
@ -299,47 +323,62 @@ impl Engine {
|
||||
// `... ${...} ...`
|
||||
Expr::InterpolatedString(x, 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() {
|
||||
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,
|
||||
state,
|
||||
lib,
|
||||
Some(OpAssignment::new(OP_CONCAT)),
|
||||
pos,
|
||||
&mut (&mut result).into(),
|
||||
&mut (&mut concat).into(),
|
||||
("", Position::NONE),
|
||||
item,
|
||||
)
|
||||
.map_err(|err| err.fill_position(expr.position()))?;
|
||||
) {
|
||||
result = Err(err.fill_position(expr.position()));
|
||||
break;
|
||||
}
|
||||
|
||||
pos = expr.position();
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
result.map(|_| concat)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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"))]
|
||||
let mut sizes = (0, 0, 0);
|
||||
|
||||
for item_expr in x.iter() {
|
||||
let value = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, item_expr, level)?
|
||||
.flatten();
|
||||
let value = match self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, item_expr, level)
|
||||
{
|
||||
Ok(r) => r.flatten(),
|
||||
err => {
|
||||
result = err;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
let val_sizes = Self::calc_data_sizes(&value, true);
|
||||
|
||||
arr.write_lock::<crate::Array>()
|
||||
.expect("`Array`")
|
||||
.push(value);
|
||||
arr.push(value);
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if self.has_data_size_limit() {
|
||||
@ -352,29 +391,33 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(arr)
|
||||
result.map(|_| arr.into())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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"))]
|
||||
let mut sizes = (0, 0, 0);
|
||||
|
||||
for (crate::ast::Ident { name, .. }, value_expr) in x.0.iter() {
|
||||
let key = name.as_str();
|
||||
let value = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, value_expr, level)?
|
||||
.flatten();
|
||||
let value = match self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, value_expr, level)
|
||||
{
|
||||
Ok(r) => r.flatten(),
|
||||
err => {
|
||||
result = err;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
let val_sizes = Self::calc_data_sizes(&value, true);
|
||||
|
||||
*map.write_lock::<crate::Map>()
|
||||
.expect("`Map`")
|
||||
.get_mut(key)
|
||||
.unwrap() = value;
|
||||
*map.get_mut(key).unwrap() = value;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if self.has_data_size_limit() {
|
||||
@ -387,33 +430,53 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
result.map(|_| map.into())
|
||||
}
|
||||
|
||||
Expr::And(x, _) => {
|
||||
Ok((self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
|
||||
&& // Short-circuit using &&
|
||||
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()))?)
|
||||
.into())
|
||||
let lhs = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, x.lhs.position())
|
||||
})
|
||||
});
|
||||
|
||||
if let Ok(true) = lhs {
|
||||
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, _) => {
|
||||
Ok((self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, x.lhs.position()))?
|
||||
|| // Short-circuit using ||
|
||||
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()))?)
|
||||
.into())
|
||||
let lhs = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, x.lhs.position())
|
||||
})
|
||||
});
|
||||
|
||||
if let Ok(false) = lhs {
|
||||
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) => {
|
||||
@ -459,6 +522,11 @@ impl Engine {
|
||||
}
|
||||
|
||||
_ => unreachable!("expression cannot be evaluated: {:?}", expr),
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
global.debugger.reset_status(reset_debugger);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,30 @@
|
||||
//! Global runtime state.
|
||||
|
||||
use crate::func::{CallableFunction, IteratorFn};
|
||||
use crate::{Identifier, Module, Shared, StaticVec};
|
||||
use crate::{Engine, Identifier};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
fmt,
|
||||
iter::{FromIterator, Rev, Zip},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use std::{fmt, marker::PhantomData};
|
||||
|
||||
/// _(internals)_ A stack of imported [modules][Module] plus mutable global runtime states.
|
||||
/// _(internals)_ Global runtime states.
|
||||
/// Exported under the `internals` feature only.
|
||||
//
|
||||
// # Implementation Notes
|
||||
//
|
||||
// This implementation splits the module names from the shared modules to improve data locality.
|
||||
// Most usage will be looking up a particular key from the list and then getting the module that
|
||||
// corresponds to that key.
|
||||
// This implementation for imported [modules][crate::Module] splits the module names from the shared
|
||||
// modules to improve data locality. Most usage will be looking up a particular key from the list
|
||||
// and then getting the module that corresponds to that key.
|
||||
#[derive(Clone)]
|
||||
pub struct GlobalRuntimeState<'a> {
|
||||
/// Stack of module names.
|
||||
//
|
||||
// We cannot use Cow<str> here because `eval` may load a [module][Module] and
|
||||
// the module name will live beyond the AST of the eval script text.
|
||||
keys: StaticVec<Identifier>,
|
||||
/// Stack of imported [modules][Module].
|
||||
modules: StaticVec<Shared<Module>>,
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
keys: crate::StaticVec<Identifier>,
|
||||
/// Stack of imported [modules][crate::Module].
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules: crate::StaticVec<crate::Shared<crate::Module>>,
|
||||
/// Source of the current context.
|
||||
/// No source if the string is empty.
|
||||
pub source: Identifier,
|
||||
@ -38,33 +35,37 @@ pub struct GlobalRuntimeState<'a> {
|
||||
/// Function call hashes to index getters and setters.
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
fn_hash_indexing: (u64, u64),
|
||||
/// Embedded [module][Module] resolver.
|
||||
/// Embedded [crate::Module][crate::Module] resolver.
|
||||
#[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.
|
||||
///
|
||||
/// Interior mutability is needed because it is shared in order to aid in cloning.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
constants:
|
||||
Option<Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>>,
|
||||
pub(crate) constants: Option<
|
||||
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.
|
||||
dummy: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl Default for GlobalRuntimeState<'_> {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalRuntimeState<'_> {
|
||||
/// Create a new [`GlobalRuntimeState`].
|
||||
/// Create a new [`GlobalRuntimeState`] based on an [`Engine`].
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
pub fn new(engine: &Engine) -> Self {
|
||||
let _engine = engine;
|
||||
|
||||
Self {
|
||||
keys: StaticVec::new_const(),
|
||||
modules: StaticVec::new_const(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
keys: crate::StaticVec::new_const(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules: crate::StaticVec::new_const(),
|
||||
source: Identifier::new_const(),
|
||||
num_operations: 0,
|
||||
num_modules_loaded: 0,
|
||||
@ -75,29 +76,46 @@ impl GlobalRuntimeState<'_> {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
constants: None,
|
||||
#[cfg(feature = "debugging")]
|
||||
debugger: crate::eval::Debugger::new(_engine),
|
||||
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)]
|
||||
#[must_use]
|
||||
pub fn num_imports(&self) -> usize {
|
||||
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)]
|
||||
#[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()
|
||||
}
|
||||
/// 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)]
|
||||
#[inline(always)]
|
||||
#[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)
|
||||
}
|
||||
/// 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]
|
||||
#[must_use]
|
||||
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)]
|
||||
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.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)]
|
||||
pub fn truncate_imports(&mut self, size: usize) {
|
||||
self.keys.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)]
|
||||
#[inline]
|
||||
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
|
||||
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &crate::Module)> {
|
||||
self.keys
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(self.modules.iter().rev())
|
||||
.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)]
|
||||
#[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())
|
||||
}
|
||||
/// 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)]
|
||||
#[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())
|
||||
}
|
||||
/// 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)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains_qualified_fn(&self, hash: u64) -> bool {
|
||||
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]
|
||||
#[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
|
||||
.iter()
|
||||
.rev()
|
||||
.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
|
||||
/// globally-imported [modules][Module]?
|
||||
/// globally-imported [modules][crate::Module]?
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
#[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))
|
||||
}
|
||||
/// 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]
|
||||
#[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
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|m| m.get_qualified_iter(id))
|
||||
}
|
||||
/// Get a mutable reference to the cache of globally-defined constants.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
/// Get the current source.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn constants_mut<'a>(
|
||||
&'a mut self,
|
||||
) -> Option<
|
||||
impl std::ops::DerefMut<Target = std::collections::BTreeMap<Identifier, crate::Dynamic>> + 'a,
|
||||
> {
|
||||
if let Some(ref global_constants) = self.constants {
|
||||
Some(crate::func::native::shared_write_lock(global_constants))
|
||||
} else {
|
||||
None
|
||||
pub fn source(&self) -> Option<&str> {
|
||||
match self.source.as_str() {
|
||||
"" => None,
|
||||
s => Some(s),
|
||||
}
|
||||
}
|
||||
/// 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.
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
#[must_use]
|
||||
@ -234,10 +272,13 @@ impl GlobalRuntimeState<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
impl IntoIterator for GlobalRuntimeState<'_> {
|
||||
type Item = (Identifier, Shared<Module>);
|
||||
type IntoIter =
|
||||
Zip<Rev<smallvec::IntoIter<[Identifier; 3]>>, Rev<smallvec::IntoIter<[Shared<Module>; 3]>>>;
|
||||
type Item = (Identifier, crate::Shared<crate::Module>);
|
||||
type IntoIter = std::iter::Zip<
|
||||
std::iter::Rev<smallvec::IntoIter<[Identifier; 3]>>,
|
||||
std::iter::Rev<smallvec::IntoIter<[crate::Shared<crate::Module>; 3]>>,
|
||||
>;
|
||||
|
||||
#[inline]
|
||||
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<'_> {
|
||||
#[inline]
|
||||
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
|
||||
let mut lib = Self::new();
|
||||
lib.extend(iter);
|
||||
lib
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState<'_> {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
impl<K: Into<Identifier>, M: Into<crate::Shared<crate::Module>>> Extend<(K, M)>
|
||||
for GlobalRuntimeState<'_>
|
||||
{
|
||||
#[inline]
|
||||
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.modules.push(m.into());
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,8 +307,10 @@ impl fmt::Debug for GlobalRuntimeState<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut f = f.debug_struct("GlobalRuntimeState");
|
||||
|
||||
f.field("imports", &self.keys.iter().zip(self.modules.iter()))
|
||||
.field("source", &self.source)
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
f.field("imports", &self.keys.iter().zip(self.modules.iter()));
|
||||
|
||||
f.field("source", &self.source)
|
||||
.field("num_operations", &self.num_operations)
|
||||
.field("num_modules_loaded", &self.num_modules_loaded);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
mod chaining;
|
||||
mod data_check;
|
||||
mod debugger;
|
||||
mod eval_context;
|
||||
mod eval_state;
|
||||
mod expr;
|
||||
@ -9,6 +10,11 @@ mod target;
|
||||
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
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_state::EvalState;
|
||||
pub use global_state::GlobalRuntimeState;
|
||||
|
490
src/eval/stmt.rs
490
src/eval/stmt.rs
@ -1,7 +1,9 @@
|
||||
//! Module defining functions for evaluating a statement.
|
||||
|
||||
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::types::dynamic::{AccessMode, Union};
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT};
|
||||
@ -11,6 +13,13 @@ use std::prelude::v1::*;
|
||||
|
||||
impl Engine {
|
||||
/// 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(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -28,7 +37,8 @@ impl Engine {
|
||||
|
||||
let orig_always_search_scope = state.always_search_scope;
|
||||
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();
|
||||
|
||||
if restore_orig_state {
|
||||
@ -38,7 +48,8 @@ impl Engine {
|
||||
let mut result = Ok(Dynamic::UNIT);
|
||||
|
||||
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(
|
||||
scope,
|
||||
@ -61,7 +72,7 @@ impl Engine {
|
||||
// Without global functions, the extra modules never affect function resolution.
|
||||
if global
|
||||
.scan_imports_raw()
|
||||
.skip(_mods_len)
|
||||
.skip(imports_len)
|
||||
.any(|(_, m)| m.contains_indexed_global_functions())
|
||||
{
|
||||
if state.fn_resolution_caches_len() > orig_fn_resolution_caches_len {
|
||||
@ -86,7 +97,8 @@ impl Engine {
|
||||
if restore_orig_state {
|
||||
scope.rewind(orig_scope_len);
|
||||
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
|
||||
// because any new variables introduced will go out of scope
|
||||
@ -119,6 +131,7 @@ impl Engine {
|
||||
if let Some(OpAssignment {
|
||||
hash_op_assign,
|
||||
hash_op,
|
||||
op_assign,
|
||||
op,
|
||||
}) = op_info
|
||||
{
|
||||
@ -140,21 +153,23 @@ impl Engine {
|
||||
let hash = hash_op_assign;
|
||||
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(_) => {
|
||||
#[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`
|
||||
let op = &op[..op.len() - 1]; // extract operator without =
|
||||
|
||||
// Run function
|
||||
let (value, _) = self.call_native_fn(
|
||||
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();
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
@ -168,11 +183,13 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Evaluate a statement.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via
|
||||
/// direct lifetime casting.
|
||||
//
|
||||
// # 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(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -184,6 +201,10 @@ impl Engine {
|
||||
rewind_scope: bool,
|
||||
level: usize,
|
||||
) -> 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.
|
||||
// Popular branches are lifted out of the `match` statement into their own branches.
|
||||
|
||||
@ -192,7 +213,13 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
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.
|
||||
@ -202,18 +229,26 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
||||
|
||||
return if x.0.is_variable_access(false) {
|
||||
let (lhs_expr, op_info, rhs_expr) = 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 result = if x.1.lhs.is_variable_access(false) {
|
||||
let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
|
||||
|
||||
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() {
|
||||
return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into());
|
||||
return Err(
|
||||
ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -229,54 +264,68 @@ impl Engine {
|
||||
(var_name, pos),
|
||||
rhs_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(rhs_expr.position()))?;
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
.map_err(|err| err.fill_position(rhs.position()))
|
||||
.map(|_| Dynamic::UNIT)
|
||||
} else {
|
||||
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
|
||||
let rhs_val = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)?
|
||||
.flatten();
|
||||
let _new_val = Some(((rhs_val, rhs_expr.position()), (*op_info, *op_pos)));
|
||||
search_result.map(|_| Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
rhs_result
|
||||
}
|
||||
} 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`
|
||||
match lhs_expr {
|
||||
match lhs {
|
||||
// name op= rhs (handled above)
|
||||
Expr::Variable(_, _, _) => {
|
||||
unreachable!("Expr::Variable case is already handled")
|
||||
}
|
||||
// idx_lhs[idx_expr] op= rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(_, _, _) => {
|
||||
self.eval_dot_index_chain(
|
||||
scope, global, state, lib, this_ptr, lhs_expr, level, _new_val,
|
||||
)?;
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
Expr::Index(_, _, _) => self
|
||||
.eval_dot_index_chain(
|
||||
scope, global, state, lib, this_ptr, lhs, level, _new_val,
|
||||
)
|
||||
.map(|_| Dynamic::UNIT),
|
||||
// dot_lhs.dot_rhs op= rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(_, _, _) => {
|
||||
self.eval_dot_index_chain(
|
||||
scope, global, state, lib, this_ptr, lhs_expr, level, _new_val,
|
||||
)?;
|
||||
Ok(Dynamic::UNIT)
|
||||
Expr::Dot(_, _, _) => self
|
||||
.eval_dot_index_chain(
|
||||
scope, global, state, lib, this_ptr, lhs, level, _new_val,
|
||||
)
|
||||
.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"))]
|
||||
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
||||
|
||||
match stmt {
|
||||
let result = match stmt {
|
||||
// No-op
|
||||
Stmt::Noop(_) => Ok(Dynamic::UNIT),
|
||||
|
||||
// Expression as statement
|
||||
Stmt::Expr(expr) => Ok(self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.flatten()),
|
||||
Stmt::Expr(expr) => self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.map(Dynamic::flatten),
|
||||
|
||||
// Block scope
|
||||
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
|
||||
@ -287,92 +336,125 @@ impl Engine {
|
||||
// If statement
|
||||
Stmt::If(expr, x, _) => {
|
||||
let guard_val = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.and_then(|v| {
|
||||
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() {
|
||||
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 {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
Ok(false) => {
|
||||
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 {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
}
|
||||
err => err.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
// Switch statement
|
||||
Stmt::Switch(match_expr, x, _) => {
|
||||
let (table, def_stmt, ranges) = x.as_ref();
|
||||
let SwitchCases {
|
||||
cases,
|
||||
def_case,
|
||||
ranges,
|
||||
} = x.as_ref();
|
||||
|
||||
let value =
|
||||
self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level)?;
|
||||
let value_result =
|
||||
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();
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
// First check hashes
|
||||
if let Some(t) = table.get(&hash) {
|
||||
if let Some(ref c) = t.0 {
|
||||
if self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &c, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, c.position())
|
||||
})?
|
||||
{
|
||||
Some(&t.1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(&t.1)
|
||||
if let Some(t) = cases.get(&hash) {
|
||||
let cond_result = t
|
||||
.condition
|
||||
.as_ref()
|
||||
.map(|cond| {
|
||||
self.eval_expr(scope, global, state, lib, this_ptr, cond, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(
|
||||
typ,
|
||||
cond.position(),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.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() {
|
||||
// Then check integer ranges
|
||||
let value = value.as_int().expect("`INT`");
|
||||
let mut result = None;
|
||||
let mut result = Ok(None);
|
||||
|
||||
for (_, _, _, condition, stmt_block) in
|
||||
ranges.iter().filter(|&&(start, end, inclusive, _, _)| {
|
||||
for (_, _, _, block) in
|
||||
ranges.iter().filter(|&&(start, end, inclusive, _)| {
|
||||
(!inclusive && (start..end).contains(&value))
|
||||
|| (inclusive && (start..=end).contains(&value))
|
||||
})
|
||||
{
|
||||
if let Some(c) = condition {
|
||||
if !self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &c, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, c.position())
|
||||
})?
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let cond_result = block
|
||||
.condition
|
||||
.as_ref()
|
||||
.map(|cond| {
|
||||
self.eval_expr(
|
||||
scope, global, state, lib, this_ptr, cond, level,
|
||||
)
|
||||
.and_then(|v| {
|
||||
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;
|
||||
}
|
||||
|
||||
result
|
||||
} else {
|
||||
// Nothing matches
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
// Non-hashable
|
||||
None
|
||||
Ok(None)
|
||||
};
|
||||
|
||||
if let Some(statements) = stmt_block {
|
||||
if let Ok(Some(statements)) = stmt_block_result {
|
||||
if !statements.is_empty() {
|
||||
self.eval_stmt_block(
|
||||
scope, global, state, lib, this_ptr, statements, true, level,
|
||||
@ -380,15 +462,20 @@ impl Engine {
|
||||
} else {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
} else if let Ok(None) = stmt_block_result {
|
||||
// Default match clause
|
||||
if !def_stmt.is_empty() {
|
||||
if !def_case.is_empty() {
|
||||
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 {
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
stmt_block_result.map(|_| Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
value_result
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,8 +488,8 @@ impl Engine {
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
ERR::LoopBreak(false, _) => (),
|
||||
ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
||||
_ => return Err(err),
|
||||
ERR::LoopBreak(true, _) => break Ok(Dynamic::UNIT),
|
||||
_ => break Err(err),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
@ -414,25 +501,30 @@ impl Engine {
|
||||
// While loop
|
||||
Stmt::While(expr, body, _) => loop {
|
||||
let condition = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, expr.position())
|
||||
})
|
||||
});
|
||||
|
||||
if !condition {
|
||||
return Ok(Dynamic::UNIT);
|
||||
}
|
||||
if !body.is_empty() {
|
||||
match condition {
|
||||
Ok(false) => break Ok(Dynamic::UNIT),
|
||||
Ok(true) if body.is_empty() => (),
|
||||
Ok(true) => {
|
||||
match self
|
||||
.eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level)
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
ERR::LoopBreak(false, _) => (),
|
||||
ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
||||
_ => return Err(err),
|
||||
ERR::LoopBreak(true, _) => break Ok(Dynamic::UNIT),
|
||||
_ => break Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
err => break err.map(|_| Dynamic::UNIT),
|
||||
}
|
||||
},
|
||||
|
||||
// Do loop
|
||||
@ -446,28 +538,36 @@ impl Engine {
|
||||
Ok(_) => (),
|
||||
Err(err) => match *err {
|
||||
ERR::LoopBreak(false, _) => continue,
|
||||
ERR::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
|
||||
_ => return Err(err),
|
||||
ERR::LoopBreak(true, _) => break Ok(Dynamic::UNIT),
|
||||
_ => break Err(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let condition = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.and_then(|v| {
|
||||
v.as_bool().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<bool>(typ, expr.position())
|
||||
})
|
||||
});
|
||||
|
||||
if condition ^ is_while {
|
||||
return Ok(Dynamic::UNIT);
|
||||
match condition {
|
||||
Ok(condition) if condition ^ is_while => break Ok(Dynamic::UNIT),
|
||||
Ok(_) => (),
|
||||
err => break err.map(|_| Dynamic::UNIT),
|
||||
}
|
||||
},
|
||||
|
||||
// For loop
|
||||
Stmt::For(expr, x, _) => {
|
||||
let (Ident { name: var_name, .. }, counter, statements) = x.as_ref();
|
||||
let iter_obj = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
|
||||
let iter_result = self
|
||||
.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();
|
||||
|
||||
// lib should only contain scripts, so technically they cannot have iterators
|
||||
@ -480,9 +580,10 @@ impl Engine {
|
||||
let func = self
|
||||
.global_modules
|
||||
.iter()
|
||||
.find_map(|m| m.get_iter(iter_type))
|
||||
.or_else(|| global.get_iter(iter_type))
|
||||
.or_else(|| {
|
||||
.find_map(|m| m.get_iter(iter_type));
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| {
|
||||
self.global_sub_modules
|
||||
.values()
|
||||
.find_map(|m| m.get_qualified_iter(iter_type))
|
||||
@ -550,8 +651,8 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
if let Err(err) =
|
||||
self.inc_operations(&mut global.num_operations, statements.position())
|
||||
if let Err(err) = self
|
||||
.inc_operations(&mut global.num_operations, statements.position())
|
||||
{
|
||||
loop_result = Err(err);
|
||||
break;
|
||||
@ -584,19 +685,26 @@ impl Engine {
|
||||
} else {
|
||||
Err(ERR::ErrorFor(expr.position()).into())
|
||||
}
|
||||
} else {
|
||||
iter_result
|
||||
}
|
||||
}
|
||||
|
||||
// Continue/Break statement
|
||||
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
|
||||
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
|
||||
.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);
|
||||
|
||||
match result {
|
||||
@ -623,17 +731,15 @@ impl Engine {
|
||||
err_map.insert("source".into(), global.source.clone().into());
|
||||
}
|
||||
|
||||
if err_pos.is_none() {
|
||||
// No position info
|
||||
} else {
|
||||
let line = err_pos.line().unwrap() as INT;
|
||||
let position = if err_pos.is_beginning_of_line() {
|
||||
0
|
||||
} else {
|
||||
err_pos.position().unwrap()
|
||||
} as INT;
|
||||
err_map.insert("line".into(), line.into());
|
||||
err_map.insert("position".into(), position.into());
|
||||
if !err_pos.is_none() {
|
||||
err_map.insert(
|
||||
"line".into(),
|
||||
(err_pos.line().unwrap() as INT).into(),
|
||||
);
|
||||
err_map.insert(
|
||||
"position".into(),
|
||||
(err_pos.position().unwrap_or(0) as INT).into(),
|
||||
);
|
||||
}
|
||||
|
||||
err.dump_fields(&mut err_map);
|
||||
@ -643,12 +749,19 @@ impl Engine {
|
||||
|
||||
let orig_scope_len = scope.len();
|
||||
|
||||
err_var_name
|
||||
catch_var
|
||||
.as_ref()
|
||||
.map(|Ident { name, .. }| scope.push(name.clone(), err_value));
|
||||
|
||||
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);
|
||||
@ -669,27 +782,19 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Throw value
|
||||
Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK_OUT) => {
|
||||
Err(ERR::ErrorRuntime(
|
||||
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.flatten(),
|
||||
*pos,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK) => self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Return value
|
||||
Stmt::Return(_, Some(expr), pos) => Err(ERR::Return(
|
||||
self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.flatten(),
|
||||
*pos,
|
||||
)
|
||||
.into()),
|
||||
Stmt::Return(_, Some(expr), pos) => self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())),
|
||||
|
||||
// Empty return
|
||||
Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
||||
@ -702,12 +807,13 @@ impl Engine {
|
||||
} else {
|
||||
AccessMode::ReadWrite
|
||||
};
|
||||
let export = options.contains(AST_OPTION_PUBLIC);
|
||||
let export = options.contains(AST_OPTION_EXPORTED);
|
||||
|
||||
let value = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
|
||||
.flatten();
|
||||
let value_result = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, expr, level)
|
||||
.map(Dynamic::flatten);
|
||||
|
||||
if let Ok(value) = value_result {
|
||||
let _alias = if !rewind_scope {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -715,8 +821,13 @@ impl Engine {
|
||||
&& entry_type == AccessMode::ReadOnly
|
||||
&& lib.iter().any(|&m| !m.is_empty())
|
||||
{
|
||||
// Add a global constant if at top level and there are functions
|
||||
global.set_constant(var_name.clone(), value.clone());
|
||||
if global.constants.is_none() {
|
||||
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 {
|
||||
@ -738,6 +849,9 @@ impl Engine {
|
||||
}
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
value_result
|
||||
}
|
||||
}
|
||||
|
||||
// Import statement
|
||||
@ -749,34 +863,40 @@ impl Engine {
|
||||
return Err(ERR::ErrorTooManyModules(*_pos).into());
|
||||
}
|
||||
|
||||
if let Some(path) = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)?
|
||||
.try_cast::<crate::ImmutableString>()
|
||||
{
|
||||
let path_result = self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, &expr, level)
|
||||
.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;
|
||||
|
||||
let source = match global.source.as_str() {
|
||||
"" => None,
|
||||
s => Some(s),
|
||||
};
|
||||
let path_pos = expr.position();
|
||||
|
||||
let module = global
|
||||
.embedded_module_resolver
|
||||
let resolver = global.embedded_module_resolver.clone();
|
||||
|
||||
let module_result = resolver
|
||||
.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,
|
||||
result => Some(result),
|
||||
})
|
||||
.or_else(|| {
|
||||
self.module_resolver
|
||||
.as_ref()
|
||||
.map(|r| r.resolve(self, source, &path, path_pos))
|
||||
.map(|r| r.resolve_raw(self, global, &path, path_pos))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
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 !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
@ -792,28 +912,27 @@ impl Engine {
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
Err(self.make_type_mismatch_err::<crate::ImmutableString>("", expr.position()))
|
||||
module_result.map(|_| Dynamic::UNIT)
|
||||
}
|
||||
} else {
|
||||
path_result.map(|_| Dynamic::UNIT)
|
||||
}
|
||||
}
|
||||
|
||||
// Export statement
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Stmt::Export(list, _) => {
|
||||
list.iter().try_for_each(
|
||||
|(Ident { name, pos, .. }, Ident { name: rename, .. })| {
|
||||
Stmt::Export(x, _) => {
|
||||
let (Ident { name, pos, .. }, Ident { name: alias, .. }) = x.as_ref();
|
||||
// Mark scope variables as public
|
||||
if let Some((index, _)) = scope.get_index(name) {
|
||||
scope.add_entry_alias(
|
||||
index,
|
||||
if rename.is_empty() { name } else { rename }.clone(),
|
||||
if alias.is_empty() { name } else { alias }.clone(),
|
||||
);
|
||||
Ok(()) as RhaiResultOf<_>
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into())
|
||||
}
|
||||
},
|
||||
)?;
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
||||
// Share statement
|
||||
@ -831,6 +950,11 @@ impl Engine {
|
||||
}
|
||||
|
||||
_ => unreachable!("statement cannot be evaluated: {:?}", stmt),
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "debugging")]
|
||||
global.debugger.reset_status(reset_debugger);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ use crate::engine::{
|
||||
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::eval::{EvalState, GlobalRuntimeState};
|
||||
use crate::module::Namespace;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr,
|
||||
Identifier, ImmutableString, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR,
|
||||
@ -150,18 +148,26 @@ impl Engine {
|
||||
#[must_use]
|
||||
fn gen_call_signature(
|
||||
&self,
|
||||
namespace: Option<&Namespace>,
|
||||
#[cfg(not(feature = "no_module"))] namespace: Option<&crate::module::Namespace>,
|
||||
fn_name: &str,
|
||||
args: &[&mut Dynamic],
|
||||
) -> String {
|
||||
format!(
|
||||
"{}{}{} ({})",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let (ns, sep) = (
|
||||
namespace.map_or_else(|| String::new(), |ns| ns.to_string()),
|
||||
if namespace.is_some() {
|
||||
Token::DoubleColon.literal_syntax()
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax()
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
#[cfg(feature = "no_module")]
|
||||
let (ns, sep) = ("", "");
|
||||
|
||||
format!(
|
||||
"{}{}{} ({})",
|
||||
ns,
|
||||
sep,
|
||||
fn_name,
|
||||
args.iter()
|
||||
.map(|a| if a.is::<ImmutableString>() {
|
||||
@ -194,6 +200,8 @@ impl Engine {
|
||||
allow_dynamic: bool,
|
||||
is_op_assignment: bool,
|
||||
) -> Option<&'s FnResolutionCacheEntry> {
|
||||
let _global = global;
|
||||
|
||||
if hash_script == 0 {
|
||||
return None;
|
||||
}
|
||||
@ -233,9 +241,12 @@ impl Engine {
|
||||
source: m.id_raw().clone(),
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let func = func
|
||||
.or_else(|| {
|
||||
global.get_qualified_fn(hash).map(|(func, source)| {
|
||||
_global.get_qualified_fn(hash).map(|(func, source)| {
|
||||
FnResolutionCacheEntry {
|
||||
func: func.clone(),
|
||||
source: source
|
||||
@ -506,9 +517,16 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Raise error
|
||||
_ => Err(
|
||||
ERR::ErrorFunctionNotFound(self.gen_call_signature(None, name, args), pos).into(),
|
||||
_ => Err(ERR::ErrorFunctionNotFound(
|
||||
self.gen_call_signature(
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
None,
|
||||
name,
|
||||
args,
|
||||
),
|
||||
pos,
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -889,8 +907,12 @@ impl Engine {
|
||||
) -> RhaiResultOf<(Dynamic, Position)> {
|
||||
Ok((
|
||||
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()
|
||||
} 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
|
||||
} else {
|
||||
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
|
||||
// avoid cloning the value
|
||||
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();
|
||||
|
||||
#[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| {
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
@ -1189,6 +1214,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Call a namespace-qualified function in normal function-call style.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub(crate) fn make_qualified_function_call(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -1196,7 +1222,7 @@ impl Engine {
|
||||
state: &mut EvalState,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
namespace: &Namespace,
|
||||
namespace: &crate::module::Namespace,
|
||||
fn_name: &str,
|
||||
args_expr: &[Expr],
|
||||
constants: &[Dynamic],
|
||||
@ -1215,6 +1241,9 @@ impl Engine {
|
||||
// If so, convert to method-call style in order to leverage potential
|
||||
// &mut first argument and avoid cloning the value
|
||||
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(...)
|
||||
arg_values.push(Dynamic::UNIT);
|
||||
|
||||
|
@ -17,7 +17,7 @@ pub enum CallableFunction {
|
||||
/// and the rest passed by value.
|
||||
Method(Shared<FnAny>),
|
||||
/// An iterator function.
|
||||
Iterator(IteratorFn),
|
||||
Iterator(Shared<IteratorFn>),
|
||||
/// A plugin function,
|
||||
Plugin(Shared<FnPlugin>),
|
||||
/// A script-defined function.
|
||||
@ -177,9 +177,9 @@ impl CallableFunction {
|
||||
/// Get a reference to an iterator function.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_iter_fn(&self) -> Option<IteratorFn> {
|
||||
pub fn get_iter_fn(&self) -> Option<&IteratorFn> {
|
||||
match self {
|
||||
Self::Iterator(f) => Some(*f),
|
||||
Self::Iterator(f) => Some(f.as_ref()),
|
||||
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None,
|
||||
|
||||
#[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"))]
|
||||
impl From<crate::ast::ScriptFnDef> for CallableFunction {
|
||||
#[inline(always)]
|
||||
|
@ -22,7 +22,7 @@ pub use hashing::{
|
||||
combine_hashes, get_hasher,
|
||||
};
|
||||
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,
|
||||
};
|
||||
pub use plugin::PluginFunction;
|
||||
|
@ -215,7 +215,6 @@ impl<'a> NativeCallContext<'a> {
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn global_runtime_state(&self) -> Option<&GlobalRuntimeState> {
|
||||
@ -286,26 +285,29 @@ impl<'a> NativeCallContext<'a> {
|
||||
is_method_call: bool,
|
||||
args: &mut [&mut Dynamic],
|
||||
) -> 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 len = args.len();
|
||||
let args_len = args.len();
|
||||
|
||||
let hash = if is_method_call {
|
||||
FnCallHashes::from_all(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
calc_fn_hash(fn_name, len - 1),
|
||||
calc_fn_hash(fn_name, len),
|
||||
calc_fn_hash(fn_name, args_len - 1),
|
||||
calc_fn_hash(fn_name, args_len),
|
||||
)
|
||||
} else {
|
||||
calc_fn_hash(fn_name, len).into()
|
||||
calc_fn_hash(fn_name, args_len).into()
|
||||
};
|
||||
|
||||
self.engine()
|
||||
.exec_fn_call(
|
||||
&mut self
|
||||
.global
|
||||
.cloned()
|
||||
.unwrap_or_else(|| GlobalRuntimeState::new()),
|
||||
&mut EvalState::new(),
|
||||
&mut global,
|
||||
&mut state,
|
||||
self.lib,
|
||||
fn_name,
|
||||
hash,
|
||||
@ -356,11 +358,11 @@ pub fn shared_take<T>(value: Shared<T>) -> T {
|
||||
shared_try_take(value).ok().expect("not shared")
|
||||
}
|
||||
|
||||
/// Lock a [`Shared`] resource.
|
||||
/// Lock a [`Locked`] resource.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
#[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"))]
|
||||
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();
|
||||
}
|
||||
|
||||
/// A general function trail object.
|
||||
/// General function trail object.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
|
||||
/// A general function trail object.
|
||||
/// General function trail object.
|
||||
#[cfg(feature = "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;
|
||||
|
||||
/// A standard function that gets an iterator from a type.
|
||||
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
||||
/// Function that gets an iterator from a type.
|
||||
#[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"))]
|
||||
pub type FnPlugin = dyn PluginFunction;
|
||||
#[cfg(feature = "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 = "sync"))]
|
||||
pub type OnProgressCallback = Box<dyn Fn(u64) -> Option<Dynamic> + 'static>;
|
||||
/// A standard callback function for progress reporting.
|
||||
pub type OnProgressCallback = dyn Fn(u64) -> Option<Dynamic>;
|
||||
/// Callback function for progress reporting.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[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"))]
|
||||
pub type OnPrintCallback = Box<dyn Fn(&str) + 'static>;
|
||||
/// A standard callback function for printing.
|
||||
pub type OnPrintCallback = dyn Fn(&str);
|
||||
/// Callback function for printing.
|
||||
#[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"))]
|
||||
pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + 'static>;
|
||||
/// A standard callback function for debugging.
|
||||
pub type OnDebugCallback = dyn Fn(&str, Option<&str>, Position);
|
||||
/// Callback function for debugging.
|
||||
#[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"))]
|
||||
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")]
|
||||
pub type OnParseTokenCallback =
|
||||
dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync + 'static;
|
||||
pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync;
|
||||
|
||||
/// A standard callback function for variable access.
|
||||
/// Callback function for variable access.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type OnVarCallback =
|
||||
Box<dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + 'static>;
|
||||
/// A standard callback function for variable access.
|
||||
pub type OnVarCallback = dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>;
|
||||
/// Callback function for variable access.
|
||||
#[cfg(feature = "sync")]
|
||||
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;
|
||||
|
@ -70,7 +70,12 @@ impl Engine {
|
||||
}
|
||||
|
||||
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
|
||||
scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| {
|
||||
@ -78,6 +83,20 @@ impl Engine {
|
||||
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
|
||||
let mut lib_merged = StaticVec::with_capacity(lib.len() + 1);
|
||||
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
|
||||
@ -97,10 +116,9 @@ impl Engine {
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some(ref modules) = fn_def.global {
|
||||
modules
|
||||
.iter()
|
||||
.cloned()
|
||||
.for_each(|(n, m)| global.push_import(n, m));
|
||||
for (n, m) in modules.iter().cloned() {
|
||||
global.push_import(n, m)
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the function
|
||||
@ -144,11 +162,17 @@ impl Engine {
|
||||
// Remove arguments only, leaving new variables in the scope
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@ -161,6 +185,8 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
hash_script: u64,
|
||||
) -> bool {
|
||||
let _global = global;
|
||||
|
||||
let cache = state.fn_resolution_cache_mut();
|
||||
|
||||
if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) {
|
||||
@ -170,9 +196,12 @@ impl Engine {
|
||||
// First check script-defined functions
|
||||
let result = lib.iter().any(|&m| m.contains_fn(hash_script))
|
||||
// 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
|
||||
|| 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
|
||||
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));
|
||||
|
||||
|
20
src/lib.rs
20
src/lib.rs
@ -123,6 +123,7 @@ type UNSIGNED_INT = u64;
|
||||
type UNSIGNED_INT = u32;
|
||||
|
||||
/// The system floating-point type. It is defined as [`f64`].
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
///
|
||||
/// 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.
|
||||
/// It is defined as [`f32`] since the `f32_float` feature is used.
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
///
|
||||
/// 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,
|
||||
};
|
||||
|
||||
/// _(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
|
||||
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
|
||||
#[cfg(not(feature = "internals"))]
|
||||
@ -188,16 +199,19 @@ pub use func::Func;
|
||||
pub use ast::ScriptFnMetadata;
|
||||
|
||||
/// Variable-sized array of [`Dynamic`] values.
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub type Array = Vec<Dynamic>;
|
||||
|
||||
/// Variable-sized array of [`u8`] values (byte array).
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub type Blob = Vec<u8>;
|
||||
|
||||
/// A dictionary of [`Dynamic`] values with string keys.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// [`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")]
|
||||
pub use ast::{
|
||||
ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment,
|
||||
OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
|
||||
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||
AST_OPTION_FLAGS::*,
|
||||
};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
@ -255,6 +270,7 @@ pub use eval::{EvalState, GlobalRuntimeState};
|
||||
pub use func::call::{FnResolutionCache, FnResolutionCacheEntry};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub use module::Namespace;
|
||||
|
||||
/// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a
|
||||
|
@ -1,11 +1,10 @@
|
||||
//! Module defining external-loaded modules for Rhai.
|
||||
|
||||
use crate::ast::{FnAccess, Ident};
|
||||
use crate::ast::FnAccess;
|
||||
use crate::func::{
|
||||
shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction,
|
||||
SendSync,
|
||||
};
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier,
|
||||
@ -19,8 +18,7 @@ use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt,
|
||||
iter::{empty, once},
|
||||
num::NonZeroUsize,
|
||||
ops::{Add, AddAssign, Deref, DerefMut},
|
||||
ops::{Add, AddAssign},
|
||||
};
|
||||
|
||||
/// A type representing the namespace of a function.
|
||||
@ -29,6 +27,8 @@ pub enum FnNamespace {
|
||||
/// Expose to global namespace.
|
||||
Global,
|
||||
/// Module namespace only.
|
||||
///
|
||||
/// Ignored under `no_module`.
|
||||
Internal,
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ impl FuncInfo {
|
||||
ty => ty.into(),
|
||||
}
|
||||
}
|
||||
/// Generate a signature of the function.
|
||||
/// _(metadata)_ Generate a signature of the function.
|
||||
/// Exported under the `metadata` feature only.
|
||||
#[cfg(feature = "metadata")]
|
||||
#[must_use]
|
||||
@ -244,9 +244,9 @@ pub struct Module {
|
||||
/// including those in sub-modules.
|
||||
all_functions: BTreeMap<u64, Shared<CallableFunction>>,
|
||||
/// 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.
|
||||
all_type_iterators: BTreeMap<TypeId, IteratorFn>,
|
||||
all_type_iterators: BTreeMap<TypeId, Shared<IteratorFn>>,
|
||||
/// Is the [`Module`] indexed?
|
||||
indexed: bool,
|
||||
/// Does the [`Module`] contain indexed functions that have been exposed to the global namespace?
|
||||
@ -462,7 +462,7 @@ impl Module {
|
||||
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.
|
||||
#[cfg(feature = "metadata")]
|
||||
#[inline]
|
||||
@ -759,8 +759,7 @@ impl Module {
|
||||
self
|
||||
}
|
||||
|
||||
/// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a
|
||||
/// registered function.
|
||||
/// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a registered function.
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// The [`u64`] hash is calculated by [`build_index`][Module::build_index].
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
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.
|
||||
#[inline]
|
||||
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.variables.extend(other.variables.into_iter());
|
||||
self.functions.extend(other.functions.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.
|
||||
#[inline]
|
||||
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) {
|
||||
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) {
|
||||
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());
|
||||
});
|
||||
other.type_iterators.iter().for_each(|(&k, &v)| {
|
||||
self.type_iterators.entry(k).or_insert(v);
|
||||
});
|
||||
}
|
||||
for (&k, v) in &other.type_iterators {
|
||||
self.type_iterators.entry(k).or_insert_with(|| v.clone());
|
||||
}
|
||||
self.all_functions.clear();
|
||||
self.all_variables.clear();
|
||||
self.all_type_iterators.clear();
|
||||
@ -1456,12 +1456,11 @@ impl Module {
|
||||
other: &Self,
|
||||
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy,
|
||||
) -> &mut Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
other.modules.iter().for_each(|(k, v)| {
|
||||
for (k, v) in &other.modules {
|
||||
let mut m = Self::new();
|
||||
m.merge_filtered(v, _filter);
|
||||
self.set_sub_module(k.clone(), m);
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "no_function")]
|
||||
self.modules
|
||||
.extend(other.modules.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||
@ -1484,7 +1483,8 @@ impl Module {
|
||||
.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_variables.clear();
|
||||
self.all_type_iterators.clear();
|
||||
@ -1658,24 +1658,78 @@ impl Module {
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub fn eval_ast_as_new(
|
||||
scope: crate::Scope,
|
||||
ast: &crate::AST,
|
||||
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> {
|
||||
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
|
||||
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
|
||||
let mut module =
|
||||
scope
|
||||
.into_iter()
|
||||
.fold(Module::new(), |mut module, (_, value, mut aliases)| {
|
||||
let mut module = Module::new();
|
||||
|
||||
// Extra modules left become sub-modules
|
||||
#[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
|
||||
for (_, value, mut aliases) in scope {
|
||||
match aliases.len() {
|
||||
0 => (),
|
||||
1 => {
|
||||
@ -1684,30 +1738,14 @@ impl Module {
|
||||
}
|
||||
_ => {
|
||||
let last_alias = aliases.pop().unwrap();
|
||||
aliases.into_iter().for_each(|alias| {
|
||||
for alias in aliases {
|
||||
module.set_var(alias, value.clone());
|
||||
});
|
||||
}
|
||||
// Avoid cloning the last 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"))]
|
||||
let func_global = func_global.map(|v| v.into_boxed_slice());
|
||||
@ -1765,33 +1803,33 @@ impl Module {
|
||||
path: &mut Vec<&'a str>,
|
||||
variables: &mut BTreeMap<u64, Dynamic>,
|
||||
functions: &mut BTreeMap<u64, Shared<CallableFunction>>,
|
||||
type_iterators: &mut BTreeMap<TypeId, IteratorFn>,
|
||||
type_iterators: &mut BTreeMap<TypeId, Shared<IteratorFn>>,
|
||||
) -> bool {
|
||||
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.
|
||||
path.push(name);
|
||||
if index_module(m, path, variables, functions, type_iterators) {
|
||||
contains_indexed_global_functions = true;
|
||||
}
|
||||
path.pop();
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
variables.insert(hash_var, value.clone());
|
||||
});
|
||||
}
|
||||
|
||||
// Index type iterators
|
||||
module.type_iterators.iter().for_each(|(&type_id, func)| {
|
||||
type_iterators.insert(type_id, *func);
|
||||
for (&type_id, func) in &module.type_iterators {
|
||||
type_iterators.insert(type_id, func.clone());
|
||||
contains_indexed_global_functions = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Index all Rust functions
|
||||
module.functions.iter().for_each(|(&hash, f)| {
|
||||
for (&hash, f) in &module.functions {
|
||||
match f.metadata.namespace {
|
||||
FnNamespace::Global => {
|
||||
// Flatten all functions with global namespace
|
||||
@ -1802,7 +1840,7 @@ impl Module {
|
||||
}
|
||||
match f.metadata.access {
|
||||
FnAccess::Public => (),
|
||||
FnAccess::Private => return, // Do not index private functions
|
||||
FnAccess::Private => continue, // Do not index private functions
|
||||
}
|
||||
|
||||
if !f.func.is_script() {
|
||||
@ -1820,7 +1858,7 @@ impl Module {
|
||||
);
|
||||
functions.insert(hash_qualified_script, f.func.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contains_indexed_global_functions
|
||||
}
|
||||
@ -1865,10 +1903,33 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Set a type iterator into the [`Module`].
|
||||
#[cfg(not(feature = "sync"))]
|
||||
#[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 {
|
||||
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.type_iterators.insert(type_id, func);
|
||||
@ -1900,126 +1961,29 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||
self.all_type_iterators.get(&id).cloned()
|
||||
pub(crate) fn get_qualified_iter(&self, id: TypeId) -> Option<&IteratorFn> {
|
||||
self.all_type_iterators.get(&id).map(|f| f.as_ref())
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||
self.type_iterators.get(&id).cloned()
|
||||
pub(crate) fn get_iter(&self, id: TypeId) -> Option<&IteratorFn> {
|
||||
self.type_iterators.get(&id).map(|f| f.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function
|
||||
/// 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;
|
||||
mod namespace;
|
||||
|
||||
/// Module containing all built-in [module resolvers][ModuleResolver].
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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
117
src/module/namespace.rs
Normal 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
|
||||
}
|
||||
}
|
@ -124,7 +124,7 @@ impl ModuleResolver for ModuleResolversCollection {
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> RhaiResultOf<Shared<Module>> {
|
||||
for resolver in self.0.iter() {
|
||||
for resolver in &self.0 {
|
||||
match resolver.resolve(engine, source_path, path, pos) {
|
||||
Ok(module) => return Ok(module),
|
||||
Err(err) => match *err {
|
||||
|
@ -1,7 +1,8 @@
|
||||
#![cfg(not(feature = "no_std"))]
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use crate::func::native::shared_write_lock;
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
use crate::func::native::locked_write;
|
||||
use crate::{
|
||||
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);
|
||||
|
||||
shared_write_lock(&self.cache).contains_key(&file_path)
|
||||
locked_write(&self.cache).contains_key(&file_path)
|
||||
}
|
||||
/// Empty the internal cache.
|
||||
#[inline]
|
||||
pub fn clear_cache(&mut self) -> &mut Self {
|
||||
shared_write_lock(&self.cache).clear();
|
||||
locked_write(&self.cache).clear();
|
||||
self
|
||||
}
|
||||
/// Remove the specified path from internal cache.
|
||||
@ -227,7 +228,7 @@ impl FileModuleResolver {
|
||||
) -> Option<Shared<Module>> {
|
||||
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)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
@ -252,24 +253,25 @@ impl FileModuleResolver {
|
||||
file_path.set_extension(self.extension.as_str()); // Force extension
|
||||
file_path
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for FileModuleResolver {
|
||||
fn resolve(
|
||||
/// Resolve a module based on a path.
|
||||
fn impl_resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
source_path: Option<&str>,
|
||||
global: Option<&mut GlobalRuntimeState>,
|
||||
source: Option<&str>,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> RhaiResultOf<Shared<Module>> {
|
||||
) -> Result<Shared<Module>, Box<crate::EvalAltResult>> {
|
||||
// Load relative paths from source if there is no base path specified
|
||||
let source_path =
|
||||
source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy()));
|
||||
let source_path = global
|
||||
.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()));
|
||||
|
||||
// See if it is cached
|
||||
if self.is_cache_enabled() {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
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 mut ast = engine
|
||||
@ -295,18 +296,43 @@ impl ModuleResolver for FileModuleResolver {
|
||||
|
||||
ast.set_source(path);
|
||||
|
||||
// Make a module from the AST
|
||||
let m: Shared<Module> = Module::eval_ast_as_new(scope, &ast, engine)
|
||||
let m: Shared<Module> = if let Some(global) = global {
|
||||
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)))?
|
||||
.into();
|
||||
|
||||
// Put it into the cache
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
use crate::func::native::SendSync;
|
||||
use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST};
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -21,11 +22,26 @@ pub trait ModuleResolver: SendSync {
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
source_path: Option<&str>,
|
||||
source: Option<&str>,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> 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.
|
||||
///
|
||||
/// Returns [`None`] (default) if such resolution is not supported
|
||||
@ -40,7 +56,7 @@ pub trait ModuleResolver: SendSync {
|
||||
fn resolve_ast(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
source_path: Option<&str>,
|
||||
source: Option<&str>,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Option<RhaiResultOf<AST>> {
|
||||
|
275
src/optimizer.rs
275
src/optimizer.rs
@ -138,7 +138,7 @@ impl<'a> OptimizerState<'a> {
|
||||
|
||||
self.engine
|
||||
.call_native_fn(
|
||||
&mut GlobalRuntimeState::new(),
|
||||
&mut GlobalRuntimeState::new(&self.engine),
|
||||
&mut EvalState::new(),
|
||||
lib,
|
||||
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
|
||||
// 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
|
||||
|| 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].
|
||||
@ -237,7 +249,7 @@ fn optimize_stmt_block(
|
||||
});
|
||||
|
||||
// Optimize each statement in the block
|
||||
statements.iter_mut().for_each(|stmt| {
|
||||
for stmt in statements.iter_mut() {
|
||||
match stmt {
|
||||
Stmt::Var(value_expr, x, options, _) => {
|
||||
if options.contains(AST_OPTION_CONSTANT) {
|
||||
@ -260,7 +272,7 @@ fn optimize_stmt_block(
|
||||
// Optimize the statement
|
||||
_ => optimize_stmt(stmt, state, preserve_result),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove all pure statements except the last one
|
||||
let mut index = 0;
|
||||
@ -308,7 +320,7 @@ fn optimize_stmt_block(
|
||||
match statements[..] {
|
||||
// { return; } -> {}
|
||||
[Stmt::Return(options, None, _)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements.clear();
|
||||
@ -320,7 +332,7 @@ fn optimize_stmt_block(
|
||||
// { ...; return; } -> { ... }
|
||||
[.., ref last_stmt, Stmt::Return(options, None, _)]
|
||||
if reduce_return
|
||||
&& !options.contains(AST_OPTION_BREAK_OUT)
|
||||
&& !options.contains(AST_OPTION_BREAK)
|
||||
&& !last_stmt.returns_value() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
@ -328,7 +340,7 @@ fn optimize_stmt_block(
|
||||
}
|
||||
// { ...; return val; } -> { ...; val }
|
||||
[.., 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();
|
||||
*statements.last_mut().unwrap() = expr
|
||||
@ -365,7 +377,7 @@ fn optimize_stmt_block(
|
||||
}
|
||||
// { ...; return; } -> { ... }
|
||||
[.., Stmt::Return(options, None, _)]
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
|
||||
if reduce_return && !options.contains(AST_OPTION_BREAK) =>
|
||||
{
|
||||
state.set_dirty();
|
||||
statements.pop().unwrap();
|
||||
@ -373,7 +385,7 @@ fn optimize_stmt_block(
|
||||
// { ...; return pure_val; } -> { ... }
|
||||
[.., Stmt::Return(options, Some(ref expr), _)]
|
||||
if reduce_return
|
||||
&& !options.contains(AST_OPTION_BREAK_OUT)
|
||||
&& !options.contains(AST_OPTION_BREAK)
|
||||
&& expr.is_pure() =>
|
||||
{
|
||||
state.set_dirty();
|
||||
@ -412,26 +424,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
match stmt {
|
||||
// var = var op expr => var op= expr
|
||||
Stmt::Assignment(x, _)
|
||||
if x.1.is_none()
|
||||
&& x.0.is_variable_access(true)
|
||||
&& matches!(&x.2, Expr::FnCall(x2, _)
|
||||
if x.0.is_none()
|
||||
&& x.1.lhs.is_variable_access(true)
|
||||
&& matches!(&x.1.rhs, Expr::FnCall(x2, _)
|
||||
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
|
||||
&& 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, _) => {
|
||||
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]);
|
||||
|
||||
if let Expr::Stack(slot, pos) = value {
|
||||
x.2 =
|
||||
x.1.rhs =
|
||||
Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos);
|
||||
} else {
|
||||
x.2 = value;
|
||||
x.1.rhs = value;
|
||||
}
|
||||
}
|
||||
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
|
||||
Stmt::Assignment(x, _) => match x.0 {
|
||||
Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state, false),
|
||||
_ => {
|
||||
optimize_expr(&mut x.0, state, false);
|
||||
optimize_expr(&mut x.2, state, false);
|
||||
Stmt::Assignment(x, _) => {
|
||||
if !x.1.lhs.is_variable_access(false) {
|
||||
optimize_expr(&mut x.1.lhs, state, false);
|
||||
}
|
||||
optimize_expr(&mut x.1.rhs, state, false);
|
||||
}
|
||||
},
|
||||
|
||||
// if expr {}
|
||||
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);
|
||||
let hash = hasher.finish();
|
||||
|
||||
let table = &mut x.0;
|
||||
let cases = &mut x.cases;
|
||||
|
||||
// First check hashes
|
||||
if let Some(block) = table.get_mut(&hash) {
|
||||
if let Some(mut condition) = mem::take(&mut block.0) {
|
||||
if let Some(block) = cases.get_mut(&hash) {
|
||||
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||
// switch const { case 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);
|
||||
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
||||
|
||||
*stmt = Stmt::If(
|
||||
condition,
|
||||
Box::new((
|
||||
mem::take(&mut block.1),
|
||||
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),
|
||||
mem::take(&mut block.statements),
|
||||
Stmt::Block(
|
||||
def_stmt.into_boxed_slice(),
|
||||
x.1.position().or_else(*pos),
|
||||
x.def_case.position().or_else(*pos),
|
||||
)
|
||||
.into(),
|
||||
)),
|
||||
@ -569,11 +538,68 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
);
|
||||
} else {
|
||||
// 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 =
|
||||
optimize_stmt_block(statements, state, true, true, false);
|
||||
*stmt =
|
||||
Stmt::Block(statements.into_boxed_slice(), stmt_block.position());
|
||||
*stmt = Stmt::Block(
|
||||
statements.into_boxed_slice(),
|
||||
block.statements.position(),
|
||||
);
|
||||
}
|
||||
|
||||
state.set_dirty();
|
||||
@ -581,14 +607,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
}
|
||||
} else {
|
||||
// Multiple ranges - clear the table and just keep the right ranges
|
||||
if !table.is_empty() {
|
||||
if !cases.is_empty() {
|
||||
state.set_dirty();
|
||||
table.clear();
|
||||
cases.clear();
|
||||
}
|
||||
|
||||
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))
|
||||
});
|
||||
@ -597,16 +623,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
state.set_dirty();
|
||||
}
|
||||
|
||||
for (_, _, _, condition, stmt_block) in ranges.iter_mut() {
|
||||
let statements = mem::take(&mut **stmt_block);
|
||||
**stmt_block =
|
||||
for (_, _, _, block) in ranges.iter_mut() {
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
if let Some(mut c) = mem::take(condition) {
|
||||
optimize_expr(&mut c, state, false);
|
||||
match c {
|
||||
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||
optimize_expr(&mut condition, state, false);
|
||||
match condition {
|
||||
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
|
||||
_ => *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
|
||||
state.set_dirty();
|
||||
let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
|
||||
*stmt = Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos));
|
||||
let def_stmt =
|
||||
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
|
||||
Stmt::Switch(match_expr, x, _) => {
|
||||
optimize_expr(match_expr, state, false);
|
||||
x.0.values_mut().for_each(|block| {
|
||||
let statements = mem::take(&mut *block.1);
|
||||
*block.1 = optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
for block in x.cases.values_mut() {
|
||||
let statements = mem::take(&mut *block.statements);
|
||||
*block.statements =
|
||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
if let Some(mut condition) = mem::take(&mut block.0) {
|
||||
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||
optimize_expr(&mut condition, state, false);
|
||||
match condition {
|
||||
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
|
||||
_ => block.0 = Some(condition),
|
||||
_ => block.condition = Some(condition),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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,
|
||||
_ => false,
|
||||
}) {
|
||||
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
|
||||
@ -663,7 +695,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
if body.len() == 1 {
|
||||
match body[0] {
|
||||
// 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
|
||||
state.set_dirty();
|
||||
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
|
||||
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
|
||||
state.set_dirty();
|
||||
*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(),
|
||||
x.0.position(),
|
||||
x.try_block.position(),
|
||||
);
|
||||
}
|
||||
// try { try_block } catch ( var ) { catch_block }
|
||||
Stmt::TryCatch(x, _) => {
|
||||
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false);
|
||||
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false);
|
||||
*x.try_block =
|
||||
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(...)
|
||||
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"))]
|
||||
Expr::Dot(x,_, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||
// map.string
|
||||
(Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||
let prop = p.2.0.as_str();
|
||||
(Expr::Map(m, pos), Expr::Property(p, _)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||
let prop = p.2.as_str();
|
||||
// Map literal where everything is pure - promote the indexed item.
|
||||
// All other items can be thrown away.
|
||||
state.set_dirty();
|
||||
@ -1091,7 +1125,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
||||
},
|
||||
|
||||
// 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
|
||||
*expr = Expr::from_dynamic(state.find_constant(&x.2).unwrap().clone(), *pos);
|
||||
state.set_dirty();
|
||||
@ -1137,13 +1173,13 @@ fn optimize_top_level(
|
||||
);
|
||||
|
||||
// Add constants and variables from the scope
|
||||
scope.iter().for_each(|(name, constant, value)| {
|
||||
for (name, constant, value) in scope.iter() {
|
||||
if !constant {
|
||||
state.push_var(name, AccessMode::ReadWrite, None);
|
||||
} else {
|
||||
state.push_var(name, AccessMode::ReadOnly, Some(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
statements = optimize_stmt_block(statements, &mut state, true, false, true);
|
||||
statements
|
||||
@ -1169,9 +1205,8 @@ pub fn optimize_into_ast(
|
||||
// We only need the script library's signatures for optimization purposes
|
||||
let mut lib2 = crate::Module::new();
|
||||
|
||||
functions
|
||||
.iter()
|
||||
.map(|fn_def| crate::ast::ScriptFnDef {
|
||||
for fn_def in &functions {
|
||||
lib2.set_script_fn(crate::ast::ScriptFnDef {
|
||||
name: fn_def.name.clone(),
|
||||
access: fn_def.access,
|
||||
body: crate::ast::StmtBlock::NONE,
|
||||
@ -1182,33 +1217,25 @@ pub fn optimize_into_ast(
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: None,
|
||||
})
|
||||
.for_each(|fn_def| {
|
||||
lib2.set_script_fn(fn_def);
|
||||
});
|
||||
}
|
||||
|
||||
let lib2 = &[&lib2];
|
||||
|
||||
functions
|
||||
.into_iter()
|
||||
.map(|fn_def| {
|
||||
for fn_def in functions {
|
||||
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
|
||||
|
||||
// Optimize the function body
|
||||
let body = mem::take(&mut *fn_def.body);
|
||||
|
||||
*fn_def.body =
|
||||
optimize_top_level(body, engine, scope, lib2, optimization_level);
|
||||
*fn_def.body = optimize_top_level(body, engine, scope, lib2, optimization_level);
|
||||
|
||||
fn_def
|
||||
})
|
||||
.for_each(|fn_def| {
|
||||
module.set_script_fn(fn_def);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
functions.into_iter().for_each(|fn_def| {
|
||||
for fn_def in functions {
|
||||
module.set_script_fn(fn_def);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::engine::OP_EQUALS;
|
||||
use crate::eval::{calc_index, calc_offset_len};
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::eval::calc_index;
|
||||
use crate::plugin::*;
|
||||
use crate::{
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::eval::{calc_index, calc_offset_len};
|
||||
use crate::plugin::*;
|
||||
|
83
src/packages/debugging.rs
Normal file
83
src/packages/debugging.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -92,13 +92,14 @@ fn collect_fn_metadata(
|
||||
// Create a metadata record for a function.
|
||||
fn make_metadata(
|
||||
dict: &BTreeSet<Identifier>,
|
||||
namespace: Option<Identifier>,
|
||||
#[cfg(not(feature = "no_module"))] namespace: Option<Identifier>,
|
||||
func: &ScriptFnDef,
|
||||
) -> Map {
|
||||
const DICT: &str = "key exists";
|
||||
|
||||
let mut map = Map::new();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if let Some(ns) = namespace {
|
||||
map.insert(dict.get("namespace").expect(DICT).clone(), ns.into());
|
||||
}
|
||||
@ -133,6 +134,7 @@ fn collect_fn_metadata(
|
||||
|
||||
// Intern strings
|
||||
let dict: BTreeSet<Identifier> = [
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"namespace",
|
||||
"name",
|
||||
"access",
|
||||
@ -150,21 +152,52 @@ fn collect_fn_metadata(
|
||||
ctx.iter_namespaces()
|
||||
.flat_map(Module::iter_script_fn)
|
||||
.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()
|
||||
.global_modules
|
||||
.iter()
|
||||
.flat_map(|m| m.iter_script_fn())
|
||||
.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()
|
||||
.global_sub_modules
|
||||
.values()
|
||||
.flat_map(|m| m.iter_script_fn())
|
||||
.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"))]
|
||||
{
|
||||
@ -189,7 +222,7 @@ fn collect_fn_metadata(
|
||||
.for_each(|(_, _, _, _, f)| {
|
||||
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!(
|
||||
"{}{}{}",
|
||||
namespace,
|
||||
@ -197,11 +230,12 @@ fn collect_fn_metadata(
|
||||
ns
|
||||
);
|
||||
scan_module(list, dict, ns.into(), m.as_ref(), filter)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ctx.iter_imports_raw()
|
||||
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter));
|
||||
for (ns, m) in ctx.iter_imports_raw() {
|
||||
scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter)
|
||||
}
|
||||
}
|
||||
|
||||
list
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::def_package;
|
||||
use crate::plugin::*;
|
||||
#[cfg(feature = "no_std")]
|
||||
|
@ -6,6 +6,7 @@ pub(crate) mod arithmetic;
|
||||
pub(crate) mod array_basic;
|
||||
mod bit_field;
|
||||
pub(crate) mod blob_basic;
|
||||
mod debugging;
|
||||
mod fn_basic;
|
||||
mod iter_basic;
|
||||
mod lang_core;
|
||||
@ -24,6 +25,8 @@ pub use array_basic::BasicArrayPackage;
|
||||
pub use bit_field::BitFieldPackage;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub use blob_basic::BasicBlobPackage;
|
||||
#[cfg(feature = "debugging")]
|
||||
pub use debugging::DebuggingPackage;
|
||||
pub use fn_basic::BasicFnPackage;
|
||||
pub use iter_basic::BasicIteratorPackage;
|
||||
pub use lang_core::LanguageCorePackage;
|
||||
|
@ -13,6 +13,7 @@ def_package! {
|
||||
/// * [`BasicStringPackage`][super::BasicStringPackage]
|
||||
/// * [`BasicIteratorPackage`][super::BasicIteratorPackage]
|
||||
/// * [`BasicFnPackage`][super::BasicFnPackage]
|
||||
/// * [`DebuggingPackage`][super::DebuggingPackage]
|
||||
crate::CorePackage => |lib| {
|
||||
lib.standard = true;
|
||||
|
||||
@ -21,5 +22,7 @@ def_package! {
|
||||
super::BasicStringPackage::init(lib);
|
||||
super::BasicIteratorPackage::init(lib);
|
||||
super::BasicFnPackage::init(lib);
|
||||
#[cfg(feature = "debugging")]
|
||||
super::DebuggingPackage::init(lib);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::plugin::*;
|
||||
use crate::{def_package, FnPtr, INT};
|
||||
use std::fmt::{Binary, LowerHex, Octal};
|
||||
|
@ -1,5 +1,3 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::plugin::*;
|
||||
use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, RhaiResultOf, StaticVec, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
|
290
src/parser.rs
290
src/parser.rs
@ -3,12 +3,11 @@
|
||||
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
||||
use crate::api::options::LanguageOptions;
|
||||
use crate::ast::{
|
||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt,
|
||||
StmtBlock, AST_OPTION_FLAGS::*,
|
||||
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||
OpAssignment, ScriptFnDef, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||
};
|
||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::func::hashing::get_hasher;
|
||||
use crate::module::Namespace;
|
||||
use crate::tokenizer::{
|
||||
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
||||
TokenizerControl,
|
||||
@ -16,9 +15,8 @@ use crate::tokenizer::{
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::types::StringsInterner;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Dynamic, Engine, ExclusiveRange,
|
||||
Identifier, ImmutableString, InclusiveRange, LexError, ParseError, Position, Scope, Shared,
|
||||
StaticVec, AST, INT, PERR,
|
||||
calc_fn_hash, Dynamic, Engine, ExclusiveRange, Identifier, ImmutableString, InclusiveRange,
|
||||
LexError, ParseError, Position, Scope, Shared, StaticVec, AST, INT, PERR,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -63,7 +61,7 @@ pub struct ParseState<'e> {
|
||||
pub allow_capture: bool,
|
||||
/// Encapsulates a local stack with imported [module][crate::Module] names.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub modules: StaticVec<Identifier>,
|
||||
pub imports: StaticVec<Identifier>,
|
||||
/// Maximum levels of expression nesting.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub max_expr_depth: Option<NonZeroUsize>,
|
||||
@ -94,7 +92,7 @@ impl<'e> ParseState<'e> {
|
||||
stack: StaticVec::new_const(),
|
||||
entry_stack_len: 0,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules: StaticVec::new_const(),
|
||||
imports: StaticVec::new_const(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +156,7 @@ impl<'e> ParseState<'e> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn find_module(&self, name: &str) -> Option<NonZeroUsize> {
|
||||
self.modules
|
||||
self.imports
|
||||
.iter()
|
||||
.rev()
|
||||
.enumerate()
|
||||
@ -266,18 +264,23 @@ impl Expr {
|
||||
#[must_use]
|
||||
fn into_property(self, state: &mut ParseState) -> 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 getter = state.get_identifier(crate::engine::FN_GET, &ident);
|
||||
let hash_get = calc_fn_hash(&getter, 1);
|
||||
let setter = state.get_identifier(crate::engine::FN_SET, &ident);
|
||||
let hash_set = calc_fn_hash(&setter, 2);
|
||||
|
||||
Self::Property(Box::new((
|
||||
Self::Property(
|
||||
Box::new((
|
||||
(getter, hash_get),
|
||||
(setter, hash_set),
|
||||
(state.get_interned_string("", &ident), pos),
|
||||
)))
|
||||
state.get_interned_string("", &ident),
|
||||
)),
|
||||
pos,
|
||||
)
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
@ -449,7 +452,7 @@ fn parse_fn_call(
|
||||
lib: &mut FnLib,
|
||||
id: Identifier,
|
||||
capture_parent_scope: bool,
|
||||
namespace: Option<Namespace>,
|
||||
#[cfg(not(feature = "no_module"))] namespace: Option<crate::module::Namespace>,
|
||||
settings: ParseSettings,
|
||||
) -> ParseResult<Expr> {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -457,6 +460,7 @@ fn parse_fn_call(
|
||||
|
||||
let (token, token_pos) = input.peek().expect(NEVER_ENDS);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let mut namespace = namespace;
|
||||
let mut args = StaticVec::new_const();
|
||||
|
||||
@ -475,9 +479,8 @@ fn parse_fn_call(
|
||||
Token::RightParen => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
let hash = if let Some(modules) = namespace.as_mut() {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
let hash = if let Some(modules) = namespace.as_mut() {
|
||||
let index = state.find_module(&modules[0].name);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -486,17 +489,19 @@ fn parse_fn_call(
|
||||
let relax = false;
|
||||
|
||||
if !relax && settings.strict_var && index.is_none() {
|
||||
return Err(PERR::ModuleUndefined(modules[0].name.to_string())
|
||||
.into_err(modules[0].pos));
|
||||
return Err(
|
||||
PERR::ModuleUndefined(modules[0].name.to_string()).into_err(modules[0].pos)
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
calc_fn_hash(&id, 0)
|
||||
};
|
||||
#[cfg(feature = "no_module")]
|
||||
let hash = calc_fn_hash(&id, 0);
|
||||
|
||||
let hashes = if is_valid_function_name(&id) {
|
||||
hash.into()
|
||||
@ -509,6 +514,7 @@ fn parse_fn_call(
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_identifier("", id),
|
||||
capture_parent_scope,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
@ -534,9 +540,8 @@ fn parse_fn_call(
|
||||
(Token::RightParen, _) => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
let hash = if let Some(modules) = namespace.as_mut() {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
let hash = if let Some(modules) = namespace.as_mut() {
|
||||
let index = state.find_module(&modules[0].name);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -550,12 +555,17 @@ fn parse_fn_call(
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
hash.into()
|
||||
@ -568,6 +578,7 @@ fn parse_fn_call(
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_identifier("", id),
|
||||
capture_parent_scope,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
@ -985,8 +996,8 @@ fn parse_switch(
|
||||
}
|
||||
}
|
||||
|
||||
let mut table = BTreeMap::<u64, Box<(Option<Expr>, StmtBlock)>>::new();
|
||||
let mut ranges = StaticVec::<(INT, INT, bool, Option<Expr>, StmtBlock)>::new();
|
||||
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
|
||||
let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new();
|
||||
let mut def_pos = Position::NONE;
|
||||
let mut def_stmt = None;
|
||||
|
||||
@ -1050,7 +1061,7 @@ fn parse_switch(
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
if table.contains_key(&hash) {
|
||||
if cases.contains_key(&hash) {
|
||||
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
|
||||
}
|
||||
(Some(hash), None)
|
||||
@ -1092,18 +1103,20 @@ fn parse_switch(
|
||||
value.hash(hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
table
|
||||
.entry(hash)
|
||||
.or_insert_with(|| (condition.clone(), stmt.into()).into());
|
||||
cases.entry(hash).or_insert_with(|| {
|
||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||
block.into()
|
||||
});
|
||||
}
|
||||
// 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
|
||||
}
|
||||
(Some(hash), None) => {
|
||||
table.insert(hash, (condition, stmt.into()).into());
|
||||
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||
cases.insert(hash, block.into());
|
||||
None
|
||||
}
|
||||
(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(
|
||||
item,
|
||||
(table, def_stmt_block, ranges).into(),
|
||||
SwitchCases {
|
||||
cases,
|
||||
def_case,
|
||||
ranges,
|
||||
}
|
||||
.into(),
|
||||
settings.pos,
|
||||
))
|
||||
}
|
||||
@ -1328,6 +1346,11 @@ fn parse_primary(
|
||||
|
||||
// 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) {
|
||||
(Token::Identifier(s), _) => s,
|
||||
token => unreachable!("Token::Identifier expected but gets {:?}", token),
|
||||
@ -1344,7 +1367,7 @@ fn parse_primary(
|
||||
Expr::Variable(
|
||||
None,
|
||||
settings.pos,
|
||||
(None, None, state.get_identifier("", s)).into(),
|
||||
(None, none, state.get_identifier("", s)).into(),
|
||||
)
|
||||
}
|
||||
// Namespace qualification
|
||||
@ -1358,7 +1381,7 @@ fn parse_primary(
|
||||
Expr::Variable(
|
||||
None,
|
||||
settings.pos,
|
||||
(None, None, state.get_identifier("", s)).into(),
|
||||
(None, none, state.get_identifier("", s)).into(),
|
||||
)
|
||||
}
|
||||
// Normal variable access
|
||||
@ -1379,7 +1402,7 @@ fn parse_primary(
|
||||
Expr::Variable(
|
||||
short_index,
|
||||
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
|
||||
Token::Reserved(_) => {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let none = None;
|
||||
#[cfg(feature = "no_module")]
|
||||
let none = ();
|
||||
|
||||
let s = match input.next().expect(NEVER_ENDS) {
|
||||
(Token::Reserved(s), _) => s,
|
||||
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(
|
||||
None,
|
||||
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
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
_ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
|
||||
None,
|
||||
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
|
||||
_ if &*s == KEYWORD_THIS => {
|
||||
@ -1451,6 +1479,7 @@ fn parse_postfix(
|
||||
|
||||
lhs = match (lhs, tail_token) {
|
||||
// Qualified function call with !
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => {
|
||||
return if !match_token(input, Token::LeftParen).0 {
|
||||
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;
|
||||
let ns = namespace.map(|(ns, _)| ns);
|
||||
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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
|
||||
(Expr::Variable(_, pos, x), Token::LeftParen) => {
|
||||
let (_, namespace, name) = *x;
|
||||
let ns = namespace.map(|(ns, _)| ns);
|
||||
let (_, _ns, name) = *x;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let _ns = _ns.map(|(ns, _)| ns);
|
||||
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
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -1498,7 +1547,7 @@ fn parse_postfix(
|
||||
if let Some((ref mut namespace, _)) = namespace {
|
||||
namespace.push(var_name_def);
|
||||
} else {
|
||||
let mut ns = Namespace::new();
|
||||
let mut ns = crate::module::Namespace::new();
|
||||
ns.push(var_name_def);
|
||||
namespace = Some((ns, 42));
|
||||
}
|
||||
@ -1543,6 +1592,7 @@ fn parse_postfix(
|
||||
}
|
||||
|
||||
// Cache the hash key for namespace-qualified variables
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let namespaced_variable = match lhs {
|
||||
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 {
|
||||
@ -1552,8 +1602,9 @@ fn parse_postfix(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
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"))]
|
||||
{
|
||||
@ -1692,20 +1743,20 @@ fn make_assignment_stmt(
|
||||
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
||||
match expr {
|
||||
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(_, _, _)))
|
||||
}
|
||||
Expr::Property(_) => None,
|
||||
Expr::Property(_, _) => None,
|
||||
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
||||
ref e => Some(e.position()),
|
||||
},
|
||||
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(_, _, _))),
|
||||
_ => None,
|
||||
},
|
||||
Expr::Property(_) if parent_is_dot => None,
|
||||
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
||||
Expr::Property(_, _) if parent_is_dot => None,
|
||||
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
||||
e if parent_is_dot => Some(e.position()),
|
||||
_ => None,
|
||||
}
|
||||
@ -1719,9 +1770,10 @@ fn make_assignment_stmt(
|
||||
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
||||
}
|
||||
// var (non-indexed) = rhs
|
||||
Expr::Variable(None, _, ref x) if x.0.is_none() => {
|
||||
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
||||
}
|
||||
Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
|
||||
(op_info, (lhs, rhs).into()).into(),
|
||||
op_pos,
|
||||
)),
|
||||
// var (indexed) = rhs
|
||||
Expr::Variable(i, var_pos, ref x) => {
|
||||
let (index, _, name) = x.as_ref();
|
||||
@ -1730,7 +1782,10 @@ fn make_assignment_stmt(
|
||||
|n| n.get() as usize,
|
||||
);
|
||||
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
|
||||
AccessMode::ReadOnly => {
|
||||
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
|
||||
@ -1749,9 +1804,10 @@ fn make_assignment_stmt(
|
||||
None => {
|
||||
match x.lhs {
|
||||
// var[???] = rhs, var.??? = rhs
|
||||
Expr::Variable(_, _, _) => {
|
||||
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
||||
}
|
||||
Expr::Variable(_, _, _) => Ok(Stmt::Assignment(
|
||||
(op_info, (lhs, rhs).into()).into(),
|
||||
op_pos,
|
||||
)),
|
||||
// expr[???] = rhs, expr.??? = rhs
|
||||
ref expr => {
|
||||
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||
@ -1826,11 +1882,14 @@ fn make_dot_expr(
|
||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos))
|
||||
}
|
||||
// lhs.module::id - syntax error
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(_, Expr::Variable(_, _, x)) => {
|
||||
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos))
|
||||
}
|
||||
#[cfg(feature = "no_module")]
|
||||
(_, Expr::Variable(_, _, _)) => unreachable!("qualified property name"),
|
||||
// lhs.prop
|
||||
(lhs, prop @ Expr::Property(_)) => Ok(Expr::Dot(
|
||||
(lhs, prop @ Expr::Property(_, _)) => Ok(Expr::Dot(
|
||||
BinaryExpr { lhs, rhs: prop }.into(),
|
||||
false,
|
||||
op_pos,
|
||||
@ -1844,7 +1903,7 @@ fn make_dot_expr(
|
||||
};
|
||||
|
||||
match x.lhs {
|
||||
Expr::Variable(_, _, _) | Expr::Property(_) => {
|
||||
Expr::Variable(_, _, _) | Expr::Property(_, _) => {
|
||||
let new_lhs = BinaryExpr {
|
||||
lhs: x.lhs.into_property(state),
|
||||
rhs: x.rhs,
|
||||
@ -2142,7 +2201,19 @@ fn parse_custom_syntax(
|
||||
let name = state.get_identifier("", name);
|
||||
segments.push(name.clone().into());
|
||||
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 => {
|
||||
let (symbol, pos) = parse_symbol(input)?;
|
||||
@ -2520,7 +2591,7 @@ fn parse_let(
|
||||
state.stack.push((name, var_type));
|
||||
|
||||
let export = if is_export {
|
||||
AST_OPTION_PUBLIC
|
||||
AST_OPTION_EXPORTED
|
||||
} else {
|
||||
AST_OPTION_NONE
|
||||
};
|
||||
@ -2564,7 +2635,7 @@ fn parse_import(
|
||||
// import expr as name ...
|
||||
let (name, pos) = parse_var_name(input)?;
|
||||
let name = state.get_identifier("", name);
|
||||
state.modules.push(name.clone());
|
||||
state.imports.push(name.clone());
|
||||
|
||||
Ok(Stmt::Import(
|
||||
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 (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)?;
|
||||
if exports.iter().any(|(_, alias)| alias.name == name.as_ref()) {
|
||||
return Err(PERR::DuplicatedVariable(name.to_string()).into_err(pos));
|
||||
}
|
||||
(Some(name), pos)
|
||||
} else {
|
||||
(None, Position::NONE)
|
||||
};
|
||||
|
||||
exports.push((
|
||||
let export = (
|
||||
Ident {
|
||||
name: state.get_identifier("", id),
|
||||
pos: id_pos,
|
||||
},
|
||||
Ident {
|
||||
name: state.get_identifier("", rename.as_ref().map_or("", <_>::as_ref)),
|
||||
pos: rename_pos,
|
||||
name: state.get_identifier("", alias.as_ref().map_or("", <_>::as_ref)),
|
||||
pos: alias_pos,
|
||||
},
|
||||
));
|
||||
);
|
||||
|
||||
match input.peek().expect(NEVER_ENDS) {
|
||||
(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))
|
||||
Ok(Stmt::Export(export.into(), settings.pos))
|
||||
}
|
||||
|
||||
/// Parse a statement block.
|
||||
@ -2677,7 +2727,7 @@ fn parse_block(
|
||||
state.entry_stack_len = state.stack.len();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let prev_mods_len = state.modules.len();
|
||||
let orig_imports_len = state.imports.len();
|
||||
|
||||
loop {
|
||||
// Terminated?
|
||||
@ -2744,7 +2794,7 @@ fn parse_block(
|
||||
state.entry_stack_len = prev_entry_stack_len;
|
||||
|
||||
#[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))
|
||||
}
|
||||
@ -2925,7 +2975,7 @@ fn parse_stmt(
|
||||
}
|
||||
Token::Break if settings.default_options.allow_loop && settings.is_breakable => {
|
||||
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 => {
|
||||
Err(PERR::LoopBreak.into_err(token_pos))
|
||||
@ -2937,7 +2987,7 @@ fn parse_stmt(
|
||||
.map(|(token, pos)| {
|
||||
let flags = match token {
|
||||
Token::Return => AST_OPTION_NONE,
|
||||
Token::Throw => AST_OPTION_BREAK_OUT,
|
||||
Token::Throw => AST_OPTION_BREAK,
|
||||
token => unreachable!(
|
||||
"Token::Return or Token::Throw expected but gets {:?}",
|
||||
token
|
||||
@ -2996,10 +3046,10 @@ fn parse_try_catch(
|
||||
let mut settings = settings;
|
||||
settings.pos = eat_token(input, Token::Try);
|
||||
|
||||
// try { body }
|
||||
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||
// try { try_block }
|
||||
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);
|
||||
|
||||
if !matched {
|
||||
@ -3009,8 +3059,8 @@ fn parse_try_catch(
|
||||
);
|
||||
}
|
||||
|
||||
// try { body } catch (
|
||||
let err_var = if match_token(input, Token::LeftParen).0 {
|
||||
// try { try_block } catch (
|
||||
let catch_var = if match_token(input, Token::LeftParen).0 {
|
||||
let (name, pos) = parse_var_name(input)?;
|
||||
let (matched, err_pos) = match_token(input, Token::RightParen);
|
||||
|
||||
@ -3029,16 +3079,21 @@ fn parse_try_catch(
|
||||
None
|
||||
};
|
||||
|
||||
// try { body } catch ( var ) { catch_block }
|
||||
let catch_body = parse_block(input, state, lib, settings.level_up())?;
|
||||
// try { try_block } catch ( var ) { catch_block }
|
||||
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
|
||||
state.stack.pop().unwrap();
|
||||
}
|
||||
|
||||
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,
|
||||
))
|
||||
}
|
||||
@ -3162,12 +3217,21 @@ fn make_curry_from_externals(
|
||||
|
||||
args.push(fn_expr);
|
||||
|
||||
args.extend(
|
||||
externals
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|x| Expr::Variable(None, Position::NONE, (None, None, x).into())),
|
||||
);
|
||||
args.extend(externals.iter().cloned().map(|x| {
|
||||
Expr::Variable(
|
||||
None,
|
||||
Position::NONE,
|
||||
(
|
||||
None,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
None,
|
||||
#[cfg(feature = "no_module")]
|
||||
(),
|
||||
x,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}));
|
||||
|
||||
let expr = FnCallExpr {
|
||||
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);
|
||||
statements.extend(externals.into_iter().map(Stmt::Share));
|
||||
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.
|
||||
@ -3451,9 +3515,9 @@ impl Engine {
|
||||
{
|
||||
let mut m = crate::Module::new();
|
||||
|
||||
_lib.into_iter().for_each(|fn_def| {
|
||||
for fn_def in _lib {
|
||||
m.set_script_fn(fn_def);
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(AST::new(statements, m));
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ enum FnType {
|
||||
Native,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum FnNamespace {
|
||||
@ -23,6 +24,7 @@ enum FnNamespace {
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
impl From<crate::FnNamespace> for FnNamespace {
|
||||
fn from(value: crate::FnNamespace) -> Self {
|
||||
match value {
|
||||
@ -62,6 +64,7 @@ struct FnParam<'a> {
|
||||
struct FnMetadata<'a> {
|
||||
pub base_hash: u64,
|
||||
pub full_hash: u64,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub namespace: FnNamespace,
|
||||
pub access: FnAccess,
|
||||
pub name: String,
|
||||
@ -110,6 +113,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
|
||||
Self {
|
||||
base_hash,
|
||||
full_hash,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace: info.metadata.namespace.into(),
|
||||
access: info.metadata.access.into(),
|
||||
name: info.metadata.name.to_string(),
|
||||
@ -206,26 +210,35 @@ impl Engine {
|
||||
let _ast = ast;
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
self.global_modules
|
||||
.iter()
|
||||
.filter(|m| include_packages || !m.standard)
|
||||
.flat_map(|m| m.iter_fn())
|
||||
.for_each(|f| {
|
||||
#[allow(unused_mut)]
|
||||
let mut meta: FnMetadata = f.into();
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
meta.namespace = FnNamespace::Global;
|
||||
}
|
||||
global.functions.push(meta);
|
||||
});
|
||||
|
||||
#[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();
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
meta.namespace = FnNamespace::Global;
|
||||
}
|
||||
global.functions.push(meta);
|
||||
});
|
||||
}
|
||||
|
||||
global.functions.sort();
|
||||
|
||||
|
@ -260,7 +260,11 @@ impl fmt::Display for Position {
|
||||
impl fmt::Debug for Position {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(not(feature = "no_position"))]
|
||||
if self.is_beginning_of_line() {
|
||||
write!(f, "{}", self.line)?;
|
||||
} else {
|
||||
write!(f, "{}:{}", self.line, self.pos)?;
|
||||
}
|
||||
#[cfg(feature = "no_position")]
|
||||
f.write_str("none")?;
|
||||
|
||||
|
@ -1119,18 +1119,18 @@ impl Dynamic {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref mut a, _, ref mut access) => {
|
||||
*access = typ;
|
||||
a.iter_mut().for_each(|v| {
|
||||
for v in a.iter_mut() {
|
||||
v.set_access_mode(typ);
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(_, _, ref mut access) => *access = typ,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref mut m, _, ref mut access) => {
|
||||
*access = typ;
|
||||
m.values_mut().for_each(|v| {
|
||||
for v in m.values_mut() {
|
||||
v.set_access_mode(typ);
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::TimeStamp(_, _, ref mut access) => *access = typ,
|
||||
@ -1708,14 +1708,14 @@ impl Dynamic {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
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>()
|
||||
{
|
||||
return None;
|
||||
} else {
|
||||
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(value)));
|
||||
return Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard)));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
@ -534,8 +534,12 @@ impl Scope<'_> {
|
||||
Self::new(),
|
||||
|mut entries, (index, (name, alias))| {
|
||||
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.values.push(self.values[len - 1 - index].clone());
|
||||
entries.values.push(value);
|
||||
}
|
||||
entries
|
||||
},
|
||||
@ -607,9 +611,9 @@ impl Scope<'_> {
|
||||
impl<K: Into<Identifier>> Extend<(K, Dynamic)> for Scope<'_> {
|
||||
#[inline]
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,7 +629,7 @@ impl<K: Into<Identifier>> FromIterator<(K, Dynamic)> for Scope<'_> {
|
||||
impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
|
||||
#[inline]
|
||||
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(
|
||||
name,
|
||||
if is_constant {
|
||||
@ -635,7 +639,7 @@ impl<K: Into<Identifier>> Extend<(K, bool, Dynamic)> for Scope<'_> {
|
||||
},
|
||||
value,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
78
tests/debugging.rs
Normal file
78
tests/debugging.rs
Normal 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(())
|
||||
}
|
@ -317,11 +317,10 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
foo = calc(foo);
|
||||
hello = `hello, ${foo} worlds!`;
|
||||
|
||||
export
|
||||
x as abc,
|
||||
x as xxx,
|
||||
foo,
|
||||
hello;
|
||||
export x as abc;
|
||||
export x as xxx;
|
||||
export foo;
|
||||
export hello;
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user